diff --git a/.pi/extensions/goal-driven-loop/ui-prompts.ts b/.pi/extensions/goal-driven-loop/ui-prompts.ts new file mode 100644 index 0000000..3f8355e --- /dev/null +++ b/.pi/extensions/goal-driven-loop/ui-prompts.ts @@ -0,0 +1,106 @@ +/** + * UI prompts for goal-driven-loop. + * + * Wraps Pi's UI methods with timed auto-defaults to avoid + * blocking the user forever. + */ + +export interface UIContext { + hasUI: boolean; + select: ( + title: string, + options: string[], + opts?: { timeout?: number } + ) => Promise; + notify: (message: string, level?: "info" | "warn" | "error") => void; + confirm: ( + title: string, + message: string, + opts?: { timeout?: number } + ) => Promise; +} + +const DEFAULT_TIMEOUT_MS = 10_000; +const STAGNATION_NOTIFY_THRESHOLD = 2; + +/** + * Prompt the user to resolve a stale goal state. + * Returns the choice or undefined on timeout. + * Auto-default: "continue" + */ +export async function promptStaleState( + ctx: UIContext +): Promise<"continue" | "replace" | "abandon" | undefined> { + if (!ctx.hasUI) { + return "continue"; + } + + const choice = await ctx.select( + "已有目标在进行中", + ["继续当前目标", "替换为新目标", "放弃当前目标"], + { timeout: DEFAULT_TIMEOUT_MS } + ); + + switch (choice) { + case "继续当前目标": + return "continue"; + case "替换为新目标": + return "replace"; + case "放弃当前目标": + return "abandon"; + default: + return "continue"; + } +} + +/** + * Notify the user that the goal is being delegated to comet. + */ +export function notifyCoarseGrainedDelegation( + ctx: UIContext, + goal: string +): void { + if (!ctx.hasUI) return; + ctx.notify( + `目标粒度较大,已委托给 comet 全流程处理:${goal}`, + "info" + ); +} + +/** + * Notify the user about mid-loop granularity upgrade. + */ +export function notifyMidLoopUpgrade(ctx: UIContext, goal: string): void { + if (!ctx.hasUI) return; + ctx.notify( + `执行中发现目标比预期复杂,已升级到 comet:${goal}`, + "info" + ); +} + +/** + * Notify the user about stagnation that auto-recovery couldn't fix. + * Called after STAGNATION_NOTIFY_THRESHOLD auto-recovery attempts. + */ +export function notifyStagnation( + ctx: UIContext, + goal: string, + passedCount: number, + totalCount: number, + iteration: number +): void { + if (!ctx.hasUI) return; + ctx.notify( + `目标停滞:${goal} (${passedCount}/${totalCount} 通过, 第 ${iteration} 轮)。自动策略调整未奏效,请人工介入。`, + "warn" + ); +} + +/** + * Check if we should notify the user about stagnation yet. + */ +export function shouldNotifyStagnation( + consecutiveStagnations: number +): boolean { + return consecutiveStagnations > STAGNATION_NOTIFY_THRESHOLD; +}