157 lines
5.6 KiB
TypeScript
157 lines
5.6 KiB
TypeScript
/**
|
|
* Retry mode for failed tasks.
|
|
*
|
|
* Provides a dedicated conversation loop with failure context,
|
|
* run session data, and piece structure injected into the system prompt.
|
|
*/
|
|
|
|
import {
|
|
initializeSession,
|
|
displayAndClearSessionState,
|
|
runConversationLoop,
|
|
type SessionContext,
|
|
type ConversationStrategy,
|
|
} from './conversationLoop.js';
|
|
import {
|
|
createSelectActionWithoutExecute,
|
|
formatMovementPreviews,
|
|
type PieceContext,
|
|
} from './interactive-summary.js';
|
|
import { resolveLanguage } from './interactive.js';
|
|
import { loadTemplate } from '../../shared/prompts/index.js';
|
|
import { getLabel, getLabelObject } from '../../shared/i18n/index.js';
|
|
import { resolveConfigValues } from '../../infra/config/index.js';
|
|
import type { InstructModeResult, InstructUIText } from '../tasks/list/instructMode.js';
|
|
|
|
/** Failure information for a retry task */
|
|
export interface RetryFailureInfo {
|
|
readonly taskName: string;
|
|
readonly taskContent: string;
|
|
readonly createdAt: string;
|
|
readonly failedMovement: string;
|
|
readonly error: string;
|
|
readonly lastMessage: string;
|
|
readonly retryNote: string;
|
|
}
|
|
|
|
/** Run session reference data for retry prompt */
|
|
export interface RetryRunInfo {
|
|
readonly logsDir: string;
|
|
readonly reportsDir: string;
|
|
readonly task: string;
|
|
readonly piece: string;
|
|
readonly status: string;
|
|
readonly movementLogs: string;
|
|
readonly reports: string;
|
|
}
|
|
|
|
/** Full retry context assembled by the caller */
|
|
export interface RetryContext {
|
|
readonly failure: RetryFailureInfo;
|
|
readonly branchName: string;
|
|
readonly pieceContext: PieceContext;
|
|
readonly run: RetryRunInfo | null;
|
|
readonly previousOrderContent: string | null;
|
|
}
|
|
|
|
const RETRY_TOOLS = ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
|
|
|
|
/**
|
|
* Convert RetryContext into template variable map.
|
|
*/
|
|
export function buildRetryTemplateVars(ctx: RetryContext, lang: 'en' | 'ja', previousOrderContent: string | null = null): Record<string, string | boolean> {
|
|
const hasPiecePreview = !!ctx.pieceContext.movementPreviews?.length;
|
|
const movementDetails = hasPiecePreview
|
|
? formatMovementPreviews(ctx.pieceContext.movementPreviews!, lang)
|
|
: '';
|
|
|
|
const hasRun = ctx.run !== null;
|
|
return {
|
|
taskName: ctx.failure.taskName,
|
|
taskContent: ctx.failure.taskContent,
|
|
branchName: ctx.branchName,
|
|
createdAt: ctx.failure.createdAt,
|
|
failedMovement: ctx.failure.failedMovement,
|
|
failureError: ctx.failure.error,
|
|
failureLastMessage: ctx.failure.lastMessage,
|
|
retryNote: ctx.failure.retryNote,
|
|
hasPiecePreview,
|
|
pieceStructure: ctx.pieceContext.pieceStructure,
|
|
movementDetails,
|
|
hasRun,
|
|
runLogsDir: hasRun ? ctx.run!.logsDir : '',
|
|
runReportsDir: hasRun ? ctx.run!.reportsDir : '',
|
|
runTask: hasRun ? ctx.run!.task : '',
|
|
runPiece: hasRun ? ctx.run!.piece : '',
|
|
runStatus: hasRun ? ctx.run!.status : '',
|
|
runMovementLogs: hasRun ? ctx.run!.movementLogs : '',
|
|
runReports: hasRun ? ctx.run!.reports : '',
|
|
hasOrderContent: previousOrderContent !== null,
|
|
orderContent: previousOrderContent ?? '',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Run retry mode conversation loop.
|
|
*
|
|
* Uses a dedicated system prompt with failure context, run session data,
|
|
* and piece structure injected for the AI assistant.
|
|
*/
|
|
export async function runRetryMode(
|
|
cwd: string,
|
|
retryContext: RetryContext,
|
|
previousOrderContent: string | null,
|
|
): Promise<InstructModeResult> {
|
|
const globalConfig = resolveConfigValues(cwd, ['language', 'provider']);
|
|
const lang = resolveLanguage(globalConfig.language);
|
|
|
|
if (!globalConfig.provider) {
|
|
throw new Error('Provider is not configured.');
|
|
}
|
|
|
|
const baseCtx = initializeSession(cwd, 'retry');
|
|
const ctx: SessionContext = { ...baseCtx, lang, personaName: 'retry' };
|
|
|
|
displayAndClearSessionState(cwd, ctx.lang);
|
|
|
|
const ui = getLabelObject<InstructUIText>('instruct.ui', ctx.lang);
|
|
|
|
const templateVars = buildRetryTemplateVars(retryContext, lang, previousOrderContent);
|
|
const systemPrompt = loadTemplate('score_retry_system_prompt', ctx.lang, templateVars);
|
|
|
|
const retryIntro = getLabel('retry.ui.intro', ctx.lang);
|
|
const introLabel = ctx.lang === 'ja'
|
|
? `## リトライ: ${retryContext.failure.taskName}\n\nブランチ: ${retryContext.branchName}\n\n${retryIntro}`
|
|
: `## Retry: ${retryContext.failure.taskName}\n\nBranch: ${retryContext.branchName}\n\n${retryIntro}`;
|
|
|
|
const policyContent = loadTemplate('score_interactive_policy', ctx.lang, {});
|
|
|
|
function injectPolicy(userMessage: string): string {
|
|
const policyIntro = ctx.lang === 'ja'
|
|
? '以下のポリシーは行動規範です。必ず遵守してください。'
|
|
: 'The following policy defines behavioral guidelines. Please follow them.';
|
|
const reminderLabel = ctx.lang === 'ja'
|
|
? '上記の Policy セクションで定義されたポリシー規範を遵守してください。'
|
|
: 'Please follow the policy guidelines defined in the Policy section above.';
|
|
return `## Policy\n${policyIntro}\n\n${policyContent}\n\n---\n\n${userMessage}\n\n---\n**Policy Reminder:** ${reminderLabel}`;
|
|
}
|
|
|
|
const strategy: ConversationStrategy = {
|
|
systemPrompt,
|
|
allowedTools: RETRY_TOOLS,
|
|
transformPrompt: injectPolicy,
|
|
introMessage: introLabel,
|
|
selectAction: createSelectActionWithoutExecute(ui),
|
|
previousOrderContent: previousOrderContent ?? undefined,
|
|
enableRetryCommand: true,
|
|
};
|
|
|
|
const result = await runConversationLoop(cwd, ctx, strategy, retryContext.pieceContext, undefined);
|
|
|
|
if (result.action === 'cancel') {
|
|
return { action: 'cancel', task: '' };
|
|
}
|
|
|
|
return { action: result.action as InstructModeResult['action'], task: result.task };
|
|
}
|