takt: task-1771451707814 (#314)

This commit is contained in:
nrs 2026-02-19 19:51:18 +09:00 committed by GitHub
parent e742897cac
commit 6371b8f3b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 499 additions and 115 deletions

View File

@ -206,6 +206,7 @@ describe('E2E: Retry mode with failure context injection', () => {
movementPreviews: [],
},
run: null,
previousOrderContent: null,
};
const result = await runRetryMode(tmpDir, retryContext, null);
@ -276,6 +277,7 @@ describe('E2E: Retry mode with failure context injection', () => {
movementLogs: formatted.runMovementLogs,
reports: formatted.runReports,
},
previousOrderContent: null,
};
const result = await runRetryMode(tmpDir, retryContext, null);
@ -331,6 +333,7 @@ describe('E2E: Retry mode with failure context injection', () => {
movementPreviews: [],
},
run: null,
previousOrderContent: null,
};
await runRetryMode(tmpDir, retryContext, null);
@ -366,6 +369,7 @@ describe('E2E: Retry mode with failure context injection', () => {
movementPreviews: [],
},
run: null,
previousOrderContent: null,
};
const result = await runRetryMode(tmpDir, retryContext, null);
@ -404,6 +408,7 @@ describe('E2E: Retry mode with failure context injection', () => {
movementPreviews: [],
},
run: null,
previousOrderContent: null,
};
const result = await runRetryMode(tmpDir, retryContext, null);

View File

@ -0,0 +1,106 @@
/**
* Tests for loadPreviousOrderContent utility function.
*
* Verifies order.md loading from run directories,
* including happy path, missing slug, and missing file cases.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { loadPreviousOrderContent } from '../features/interactive/runSessionReader.js';
function createTmpDir(): string {
const dir = join(tmpdir(), `takt-order-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
mkdirSync(dir, { recursive: true });
return dir;
}
function createRunWithOrder(cwd: string, slug: string, taskContent: string, orderContent: string): void {
const runDir = join(cwd, '.takt', 'runs', slug);
mkdirSync(join(runDir, 'context', 'task'), { recursive: true });
const meta = {
task: taskContent,
piece: 'default',
status: 'completed',
startTime: '2026-02-01T00:00:00.000Z',
logsDirectory: `.takt/runs/${slug}/logs`,
reportDirectory: `.takt/runs/${slug}/reports`,
runSlug: slug,
};
writeFileSync(join(runDir, 'meta.json'), JSON.stringify(meta), 'utf-8');
writeFileSync(join(runDir, 'context', 'task', 'order.md'), orderContent, 'utf-8');
}
function createRunWithoutOrder(cwd: string, slug: string, taskContent: string): void {
const runDir = join(cwd, '.takt', 'runs', slug);
mkdirSync(runDir, { recursive: true });
const meta = {
task: taskContent,
piece: 'default',
status: 'completed',
startTime: '2026-02-01T00:00:00.000Z',
logsDirectory: `.takt/runs/${slug}/logs`,
reportDirectory: `.takt/runs/${slug}/reports`,
runSlug: slug,
};
writeFileSync(join(runDir, 'meta.json'), JSON.stringify(meta), 'utf-8');
}
describe('loadPreviousOrderContent', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = createTmpDir();
});
afterEach(() => {
rmSync(tmpDir, { recursive: true, force: true });
});
it('should return order.md content when run and file exist', () => {
const taskContent = 'Implement feature X';
const orderContent = '# Task\n\nImplement feature X with tests.';
createRunWithOrder(tmpDir, 'run-feature-x', taskContent, orderContent);
const result = loadPreviousOrderContent(tmpDir, taskContent);
expect(result).toBe(orderContent);
});
it('should return null when no matching run exists', () => {
const result = loadPreviousOrderContent(tmpDir, 'Non-existent task');
expect(result).toBeNull();
});
it('should return null when run exists but order.md is missing', () => {
const taskContent = 'Task without order';
createRunWithoutOrder(tmpDir, 'run-no-order', taskContent);
const result = loadPreviousOrderContent(tmpDir, taskContent);
expect(result).toBeNull();
});
it('should return null when .takt/runs directory does not exist', () => {
const emptyDir = join(tmpdir(), `takt-empty-${Date.now()}`);
mkdirSync(emptyDir, { recursive: true });
const result = loadPreviousOrderContent(emptyDir, 'any task');
expect(result).toBeNull();
rmSync(emptyDir, { recursive: true, force: true });
});
it('should match the correct run among multiple runs', () => {
createRunWithOrder(tmpDir, 'run-a', 'Task A', '# Order A');
createRunWithOrder(tmpDir, 'run-b', 'Task B', '# Order B');
expect(loadPreviousOrderContent(tmpDir, 'Task A')).toBe('# Order A');
expect(loadPreviousOrderContent(tmpDir, 'Task B')).toBe('# Order B');
});
});

View File

@ -24,6 +24,7 @@ function createRetryContext(overrides?: Partial<RetryContext>): RetryContext {
movementPreviews: [],
},
run: null,
previousOrderContent: null,
...overrides,
};
}
@ -131,6 +132,22 @@ describe('buildRetryTemplateVars', () => {
expect(vars.movementDetails).toContain('Architect');
});
it('should set hasPreviousOrder=false and empty previousOrderContent when previousOrderContent is null', () => {
const ctx = createRetryContext({ previousOrderContent: null });
const vars = buildRetryTemplateVars(ctx, 'en');
expect(vars.hasPreviousOrder).toBe(false);
expect(vars.previousOrderContent).toBe('');
});
it('should set hasPreviousOrder=true and populate previousOrderContent when provided', () => {
const ctx = createRetryContext({ previousOrderContent: '# Order content' });
const vars = buildRetryTemplateVars(ctx, 'en');
expect(vars.hasPreviousOrder).toBe(true);
expect(vars.previousOrderContent).toBe('# Order content');
});
it('should include retryNote when present', () => {
const ctx = createRetryContext({
failure: {

View File

@ -0,0 +1,198 @@
/**
* Tests for /retry slash command in the conversation loop.
*
* Verifies:
* - /retry with previousOrderContent returns execute action with order content
* - /retry without previousOrderContent shows error and continues loop
* - /retry in retry mode with order.md context in system prompt
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import {
setupRawStdin,
restoreStdin,
toRawInputs,
createMockProvider,
type MockProviderCapture,
} from './helpers/stdinSimulator.js';
// --- Mocks (infrastructure only) ---
vi.mock('../infra/fs/session.js', () => ({
loadNdjsonLog: vi.fn(),
}));
vi.mock('../infra/config/global/globalConfig.js', () => ({
loadGlobalConfig: vi.fn(() => ({ provider: 'mock', language: 'en' })),
getBuiltinPiecesEnabled: vi.fn().mockReturnValue(true),
}));
vi.mock('../infra/providers/index.js', () => ({
getProvider: vi.fn(),
}));
vi.mock('../shared/utils/index.js', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
createLogger: () => ({
info: vi.fn(),
debug: vi.fn(),
error: vi.fn(),
}),
}));
vi.mock('../shared/context.js', () => ({
isQuietMode: vi.fn(() => false),
}));
vi.mock('../infra/config/paths.js', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
loadPersonaSessions: vi.fn(() => ({})),
updatePersonaSession: vi.fn(),
getProjectConfigDir: vi.fn(() => '/tmp'),
loadSessionState: vi.fn(() => null),
clearSessionState: vi.fn(),
}));
vi.mock('../shared/ui/index.js', () => ({
info: vi.fn(),
error: vi.fn(),
blankLine: vi.fn(),
StreamDisplay: vi.fn().mockImplementation(() => ({
createHandler: vi.fn(() => vi.fn()),
flush: vi.fn(),
})),
}));
vi.mock('../shared/prompt/index.js', () => ({
selectOption: vi.fn().mockResolvedValue('execute'),
}));
vi.mock('../shared/i18n/index.js', () => ({
getLabel: vi.fn((_key: string, _lang: string) => 'Mock label'),
getLabelObject: vi.fn(() => ({
intro: 'Retry intro',
resume: 'Resume',
noConversation: 'No conversation',
summarizeFailed: 'Summarize failed',
continuePrompt: 'Continue?',
proposed: 'Proposed:',
actionPrompt: 'What next?',
playNoTask: 'No task',
cancelled: 'Cancelled',
retryNoOrder: 'No previous order found.',
actions: { execute: 'Execute', saveTask: 'Save', continue: 'Continue' },
})),
}));
// --- Imports (after mocks) ---
import { getProvider } from '../infra/providers/index.js';
import { runRetryMode, type RetryContext } from '../features/interactive/retryMode.js';
import { info } from '../shared/ui/index.js';
const mockGetProvider = vi.mocked(getProvider);
const mockInfo = vi.mocked(info);
function createTmpDir(): string {
const dir = join(tmpdir(), `takt-retry-cmd-${Date.now()}-${Math.random().toString(36).slice(2)}`);
mkdirSync(dir, { recursive: true });
return dir;
}
function setupProvider(responses: string[]): MockProviderCapture {
const { provider, capture } = createMockProvider(responses);
mockGetProvider.mockReturnValue(provider);
return capture;
}
function buildRetryContext(overrides?: Partial<RetryContext>): RetryContext {
return {
failure: {
taskName: 'test-task',
taskContent: 'Test task content',
createdAt: '2026-02-15T10:00:00Z',
failedMovement: 'implement',
error: 'Some error',
lastMessage: '',
retryNote: '',
},
branchName: 'takt/test-task',
pieceContext: {
name: 'default',
description: '',
pieceStructure: '',
movementPreviews: [],
},
run: null,
previousOrderContent: null,
...overrides,
};
}
// --- Tests ---
describe('/retry slash command', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = createTmpDir();
vi.clearAllMocks();
});
afterEach(() => {
restoreStdin();
rmSync(tmpDir, { recursive: true, force: true });
});
it('should execute with previous order content when /retry is used', async () => {
const orderContent = '# Task Order\n\nImplement feature X with tests.';
setupRawStdin(toRawInputs(['/retry']));
setupProvider([]);
const retryContext = buildRetryContext({ previousOrderContent: orderContent });
const result = await runRetryMode(tmpDir, retryContext);
expect(result.action).toBe('execute');
expect(result.task).toBe(orderContent);
});
it('should show error and continue when /retry is used without order', async () => {
setupRawStdin(toRawInputs(['/retry', '/cancel']));
setupProvider([]);
const retryContext = buildRetryContext({ previousOrderContent: null });
const result = await runRetryMode(tmpDir, retryContext);
expect(mockInfo).toHaveBeenCalledWith('No previous order found.');
expect(result.action).toBe('cancel');
});
it('should inject order.md content into retry system prompt', async () => {
const orderContent = '# Build login page\n\nWith OAuth2 support.';
setupRawStdin(toRawInputs(['check the order', '/cancel']));
const capture = setupProvider(['I see the order content.']);
const retryContext = buildRetryContext({ previousOrderContent: orderContent });
await runRetryMode(tmpDir, retryContext);
expect(capture.systemPrompts.length).toBeGreaterThan(0);
const systemPrompt = capture.systemPrompts[0]!;
expect(systemPrompt).toContain('Previous Order');
expect(systemPrompt).toContain(orderContent);
});
it('should not include order section when no order content', async () => {
setupRawStdin(toRawInputs(['check the order', '/cancel']));
const capture = setupProvider(['No order found.']);
const retryContext = buildRetryContext({ previousOrderContent: null });
await runRetryMode(tmpDir, retryContext);
expect(capture.systemPrompts.length).toBeGreaterThan(0);
const systemPrompt = capture.systemPrompts[0]!;
expect(systemPrompt).not.toContain('Previous Order');
});
});

View File

@ -0,0 +1,123 @@
/**
* AI call with automatic retry on stale/invalid session.
*
* Extracted from conversationLoop.ts for single-responsibility:
* this module handles only the AI call + retry logic.
*/
import {
updatePersonaSession,
} from '../../infra/config/index.js';
import { isQuietMode } from '../../shared/context.js';
import { createLogger, getErrorMessage } from '../../shared/utils/index.js';
import { info, error, blankLine, StreamDisplay } from '../../shared/ui/index.js';
import { getLabel } from '../../shared/i18n/index.js';
import { EXIT_SIGINT } from '../../shared/exitCodes.js';
import type { ProviderType } from '../../infra/providers/index.js';
import { getProvider } from '../../infra/providers/index.js';
const log = createLogger('ai-caller');
/** Result from a single AI call */
export interface CallAIResult {
content: string;
sessionId?: string;
success: boolean;
}
/** Initialized session context for conversation loops */
export interface SessionContext {
provider: ReturnType<typeof getProvider>;
providerType: ProviderType;
model: string | undefined;
lang: 'en' | 'ja';
personaName: string;
sessionId: string | undefined;
}
/**
* Call AI with automatic retry on stale/invalid session.
*
* On session failure, clears sessionId and retries once without session.
* Updates sessionId and persists it on success.
*/
export async function callAIWithRetry(
prompt: string,
systemPrompt: string,
allowedTools: string[],
cwd: string,
ctx: SessionContext,
): Promise<{ result: CallAIResult | null; sessionId: string | undefined }> {
const display = new StreamDisplay('assistant', isQuietMode());
const abortController = new AbortController();
let sigintCount = 0;
const onSigInt = (): void => {
sigintCount += 1;
if (sigintCount === 1) {
blankLine();
info(getLabel('piece.sigintGraceful', ctx.lang));
abortController.abort();
return;
}
blankLine();
error(getLabel('piece.sigintForce', ctx.lang));
process.exit(EXIT_SIGINT);
};
process.on('SIGINT', onSigInt);
let { sessionId } = ctx;
try {
const agent = ctx.provider.setup({ name: ctx.personaName, systemPrompt });
const response = await agent.call(prompt, {
cwd,
model: ctx.model,
sessionId,
allowedTools,
abortSignal: abortController.signal,
onStream: display.createHandler(),
});
display.flush();
const success = response.status !== 'blocked' && response.status !== 'error';
if (!success && sessionId) {
log.info('Session invalid, retrying without session');
sessionId = undefined;
const retryDisplay = new StreamDisplay('assistant', isQuietMode());
const retryAgent = ctx.provider.setup({ name: ctx.personaName, systemPrompt });
const retry = await retryAgent.call(prompt, {
cwd,
model: ctx.model,
sessionId: undefined,
allowedTools,
abortSignal: abortController.signal,
onStream: retryDisplay.createHandler(),
});
retryDisplay.flush();
if (retry.sessionId) {
sessionId = retry.sessionId;
updatePersonaSession(cwd, ctx.personaName, sessionId, ctx.providerType);
}
return {
result: { content: retry.content, sessionId: retry.sessionId, success: retry.status !== 'blocked' && retry.status !== 'error' },
sessionId,
};
}
if (response.sessionId) {
sessionId = response.sessionId;
updatePersonaSession(cwd, ctx.personaName, sessionId, ctx.providerType);
}
return {
result: { content: response.content, sessionId: response.sessionId, success },
sessionId,
};
} catch (e) {
const msg = getErrorMessage(e);
log.error('AI call failed', { error: msg });
error(msg);
blankLine();
return { result: null, sessionId };
} finally {
process.removeListener('SIGINT', onSigInt);
}
}

View File

@ -3,7 +3,6 @@
*
* Extracts the common patterns:
* - Provider/session initialization
* - AI call with retry on stale session
* - Session state display/clear
* - Conversation loop (slash commands, AI messaging, /go summary)
*/
@ -12,14 +11,12 @@ import chalk from 'chalk';
import {
resolveConfigValues,
loadPersonaSessions,
updatePersonaSession,
loadSessionState,
clearSessionState,
} from '../../infra/config/index.js';
import { isQuietMode } from '../../shared/context.js';
import { getProvider, type ProviderType } from '../../infra/providers/index.js';
import { createLogger, getErrorMessage } from '../../shared/utils/index.js';
import { info, error, blankLine, StreamDisplay } from '../../shared/ui/index.js';
import { createLogger } from '../../shared/utils/index.js';
import { info, error, blankLine } from '../../shared/ui/index.js';
import { getLabel, getLabelObject } from '../../shared/i18n/index.js';
import { readMultilineInput } from './lineEditor.js';
import { selectRecentSession } from './sessionSelector.js';
@ -35,26 +32,12 @@ import {
selectPostSummaryAction,
formatSessionStatus,
} from './interactive.js';
import { callAIWithRetry, type CallAIResult, type SessionContext } from './aiCaller.js';
export { type CallAIResult, type SessionContext, callAIWithRetry } from './aiCaller.js';
const log = createLogger('conversation-loop');
/** Result from a single AI call */
export interface CallAIResult {
content: string;
sessionId?: string;
success: boolean;
}
/** Initialized session context for conversation loops */
export interface SessionContext {
provider: ReturnType<typeof getProvider>;
providerType: ProviderType;
model: string | undefined;
lang: 'en' | 'ja';
personaName: string;
sessionId: string | undefined;
}
/**
* Initialize provider and language for interactive conversation.
*
@ -88,93 +71,6 @@ export function displayAndClearSessionState(cwd: string, lang: 'en' | 'ja'): voi
}
}
/**
* Call AI with automatic retry on stale/invalid session.
*
* On session failure, clears sessionId and retries once without session.
* Updates sessionId and persists it on success.
*/
export async function callAIWithRetry(
prompt: string,
systemPrompt: string,
allowedTools: string[],
cwd: string,
ctx: SessionContext,
): Promise<{ result: CallAIResult | null; sessionId: string | undefined }> {
const display = new StreamDisplay('assistant', isQuietMode());
const abortController = new AbortController();
let sigintCount = 0;
const onSigInt = (): void => {
sigintCount += 1;
if (sigintCount === 1) {
blankLine();
info(getLabel('piece.sigintGraceful', ctx.lang));
abortController.abort();
return;
}
blankLine();
error(getLabel('piece.sigintForce', ctx.lang));
process.exit(EXIT_SIGINT);
};
process.on('SIGINT', onSigInt);
let { sessionId } = ctx;
try {
const agent = ctx.provider.setup({ name: ctx.personaName, systemPrompt });
const response = await agent.call(prompt, {
cwd,
model: ctx.model,
sessionId,
allowedTools,
abortSignal: abortController.signal,
onStream: display.createHandler(),
});
display.flush();
const success = response.status !== 'blocked' && response.status !== 'error';
if (!success && sessionId) {
log.info('Session invalid, retrying without session');
sessionId = undefined;
const retryDisplay = new StreamDisplay('assistant', isQuietMode());
const retryAgent = ctx.provider.setup({ name: ctx.personaName, systemPrompt });
const retry = await retryAgent.call(prompt, {
cwd,
model: ctx.model,
sessionId: undefined,
allowedTools,
abortSignal: abortController.signal,
onStream: retryDisplay.createHandler(),
});
retryDisplay.flush();
if (retry.sessionId) {
sessionId = retry.sessionId;
updatePersonaSession(cwd, ctx.personaName, sessionId, ctx.providerType);
}
return {
result: { content: retry.content, sessionId: retry.sessionId, success: retry.status !== 'blocked' && retry.status !== 'error' },
sessionId,
};
}
if (response.sessionId) {
sessionId = response.sessionId;
updatePersonaSession(cwd, ctx.personaName, sessionId, ctx.providerType);
}
return {
result: { content: response.content, sessionId: response.sessionId, success },
sessionId,
};
} catch (e) {
const msg = getErrorMessage(e);
log.error('AI call failed', { error: msg });
error(msg);
blankLine();
return { result: null, sessionId };
} finally {
process.removeListener('SIGINT', onSigInt);
}
}
export type { PostSummaryAction } from './interactive.js';
/** Strategy for customizing conversation loop behavior */
@ -196,7 +92,7 @@ export interface ConversationStrategy {
/**
* Run the shared conversation loop.
*
* Handles: EOF, /play, /go (summary), /cancel, regular AI messaging.
* Handles: EOF, /play, /retry, /go (summary), /cancel, regular AI messaging.
* The Strategy object controls system prompt, tool access, and prompt transformation.
*/
export async function runConversationLoop(
@ -271,6 +167,15 @@ export async function runConversationLoop(
return { action: 'execute', task };
}
if (trimmed === '/retry') {
if (!strategy.previousOrderContent) {
info(ui.retryNoOrder);
continue;
}
log.info('Retry command — resubmitting previous order.md');
return { action: 'execute', task: strategy.previousOrderContent };
}
if (trimmed.startsWith('/go')) {
const userNote = trimmed.slice(3).trim();
let summaryPrompt = buildSummaryPrompt(

View File

@ -22,7 +22,7 @@ export { passthroughMode } from './passthroughMode.js';
export { quietMode } from './quietMode.js';
export { personaMode } from './personaMode.js';
export { selectRun } from './runSelector.js';
export { listRecentRuns, findRunForTask, loadRunSessionContext, formatRunSessionForPrompt, getRunPaths, type RunSessionContext, type RunPaths } from './runSessionReader.js';
export { listRecentRuns, findRunForTask, loadRunSessionContext, formatRunSessionForPrompt, getRunPaths, loadPreviousOrderContent, type RunSessionContext, type RunPaths } from './runSessionReader.js';
export { runRetryMode, buildRetryTemplateVars, type RetryContext, type RetryFailureInfo, type RetryRunInfo } from './retryMode.js';
export { dispatchConversationAction, type ConversationActionResult } from './actionDispatcher.js';
export { findPreviousOrderContent } from './orderReader.js';

View File

@ -45,6 +45,7 @@ export interface InteractiveUIText {
};
cancelled: string;
playNoTask: string;
retryNoOrder: string;
}
/**

View File

@ -52,6 +52,7 @@ export interface RetryContext {
readonly branchName: string;
readonly pieceContext: PieceContext;
readonly run: RetryRunInfo | null;
readonly previousOrderContent: string | null;
}
const RETRY_TOOLS = ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
@ -66,6 +67,7 @@ export function buildRetryTemplateVars(ctx: RetryContext, lang: 'en' | 'ja', pre
: '';
const hasRun = ctx.run !== null;
const hasPreviousOrder = ctx.previousOrderContent !== null;
return {
taskName: ctx.failure.taskName,

View File

@ -216,6 +216,28 @@ export function loadRunSessionContext(cwd: string, slug: string): RunSessionCont
};
}
/**
* Load the previous order.md content from the run directory.
*
* Uses findRunForTask to locate the matching run by task content,
* then reads order.md from its context/task directory.
*
* @returns The order.md content if found, null otherwise.
*/
export function loadPreviousOrderContent(cwd: string, taskContent: string): string | null {
const slug = findRunForTask(cwd, taskContent);
if (!slug) {
return null;
}
const orderPath = join(cwd, '.takt', 'runs', slug, 'context', 'task', 'order.md');
if (!existsSync(orderPath)) {
return null;
}
return readFileSync(orderPath, 'utf-8');
}
/**
* Format run session context into a text block for the system prompt.
*/

View File

@ -69,6 +69,8 @@ function buildInstructTemplateVars(
? formatRunSessionForPrompt(runSessionContext)
: { runTask: '', runPiece: '', runStatus: '', runMovementLogs: '', runReports: '' };
const hasPreviousOrder = !!previousOrderContent;
return {
taskName,
taskContent,

View File

@ -166,6 +166,7 @@ export async function retryFailedTask(
branchName,
pieceContext,
run: runInfo,
previousOrderContent,
};
const retryResult = await runRetryMode(worktreePath, retryContext, previousOrderContent);

View File

@ -10,7 +10,7 @@ interactive:
conversationLabel: "Conversation:"
noTranscript: "(No local transcript. Summarize the current session context.)"
ui:
intro: "Interactive mode - describe your task. Commands: /go (execute), /play (run now), /resume (load session), /cancel (exit)"
intro: "Interactive mode - describe your task. Commands: /go (execute), /play (run now), /resume (load session), /retry (rerun previous order), /cancel (exit)"
resume: "Resuming previous session"
noConversation: "No conversation yet. Please describe your task first."
summarizeFailed: "Failed to summarize conversation. Please try again."
@ -24,6 +24,7 @@ interactive:
continue: "Continue editing"
cancelled: "Cancelled"
playNoTask: "Please specify task content: /play <task>"
retryNoOrder: "No previous order (order.md) found. /retry is only available during retry."
personaFallback: "No persona available for the first movement. Falling back to assistant mode."
modeSelection:
prompt: "Select interactive mode:"
@ -76,7 +77,7 @@ piece:
# ===== Instruct Mode UI (takt list -> instruct) =====
instruct:
ui:
intro: "Instruct mode - describe additional instructions. Commands: /go (summarize), /cancel (exit)"
intro: "Instruct mode - describe additional instructions. Commands: /go (summarize), /retry (rerun previous order), /cancel (exit)"
resume: "Resuming previous session"
noConversation: "No conversation yet. Please describe your instructions first."
summarizeFailed: "Failed to summarize conversation. Please try again."

View File

@ -10,7 +10,7 @@ interactive:
conversationLabel: "会話:"
noTranscript: "(ローカル履歴なし。現在のセッション文脈を要約してください。)"
ui:
intro: "対話モード - タスク内容を入力してください。コマンド: /go実行, /play即実行, /resumeセッション読込, /cancel終了"
intro: "対話モード - タスク内容を入力してください。コマンド: /go実行, /play即実行, /resumeセッション読込, /retry前回の指示書で再実行, /cancel終了"
resume: "前回のセッションを再開します"
noConversation: "まだ会話がありません。まずタスク内容を入力してください。"
summarizeFailed: "会話の要約に失敗しました。再度お試しください。"
@ -24,6 +24,7 @@ interactive:
continue: "会話を続ける"
cancelled: "キャンセルしました"
playNoTask: "タスク内容を指定してください: /play <タスク内容>"
retryNoOrder: "前回の指示書order.mdが見つかりません。/retry はリトライ時のみ使用できます。"
personaFallback: "先頭ムーブメントにペルソナがありません。アシスタントモードにフォールバックします。"
modeSelection:
prompt: "対話モードを選択してください:"
@ -76,7 +77,7 @@ piece:
# ===== Instruct Mode UI (takt list -> instruct) =====
instruct:
ui:
intro: "指示モード - 追加指示を入力してください。コマンド: /go要約, /cancel終了"
intro: "指示モード - 追加指示を入力してください。コマンド: /go要約, /retry前回の指示書で再実行, /cancel終了"
resume: "前回のセッションを再開します"
noConversation: "まだ会話がありません。まず追加指示を入力してください。"
summarizeFailed: "会話の要約に失敗しました。再度お試しください。"