/** * pi-loop iteration-prompt builder (pure). The stable molde re-injected each * iteration (autonomous objective vs verbatim task; fixed vs dynamic cadence * guidance). Extracted from index.ts with the body verbatim; the only change is * the parameter type, decoupled from LoopState into a structural input so this * leaf has no cycle back to index.ts. Depth-one sibling imported via "./prompt.js". */ import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent"; import { formatInterval } from "./interval.js"; /** Structural subset of LoopState that makeLoopIterationPrompt reads. A full LoopState satisfies it. */ export interface LoopIterationPromptInput { loopId: string; task: string; mode: "dynamic" | "fixed"; intervalMs?: number; iteration: number; maxIterations: number; lastReason?: string; autonomous?: boolean; ultracode?: boolean; } /** Molde estable de prompt de iteración (cf. makeWorkflowWakePrompt). */ export function makeLoopIterationPrompt(loop: LoopIterationPromptInput): string { const lines: string[] = []; lines.push(`You are running a /loop iteration (loop ${loop.loopId}).`); lines.push(""); if (loop.autonomous) { // Autonomous mode (P2): no conventional user task. The "task" field holds the // recurring OBJECTIVE; the re-injected text is this sentinel, not a user message. lines.push("AUTONOMOUS LOOP (no interactive user this turn)."); lines.push("This iteration was generated by the /loop extension to pursue a recurring objective:"); lines.push(""); lines.push("OBJECTIVE (verbatim):"); lines.push(loop.task); lines.push(""); lines.push( "Make autonomous progress on the objective, but stay conservative: destructive/irreversible actions are gated this turn. If you need a human decision, stop and surface it rather than guessing.", ); lines.push(""); } else { lines.push("TASK (verbatim):"); lines.push(loop.task); lines.push(""); } lines.push(`This is iteration ${loop.iteration}/${loop.maxIterations}.`); if (loop.lastReason) { lines.push(`Previous decision: ${loop.lastReason}`); } if (loop.ultracode) { lines.push( `ULTRACODE: prefer driving this work via dynamic workflows when it earns its cost. Scout inline first with cheap read-only probes; orchestrate (dynamic_workflow action=start) only for exhaustiveness, independent confidence, or scale, with explicit concurrency/maxAgents. Inspect the catalog (dynamic_workflow action=scaffold) and reuse an exact-fit workflow or write a gitignored ${CONFIG_DIR_NAME}/workflows/drafts/.js draft.`, ); } lines.push(""); lines.push("Do EXACTLY ONE iteration of the task now, then decide whether to continue:"); if (loop.mode === "fixed") { // Fixed mode: the period is owned by the extension; the model only decides // continue vs stop. loop_schedule is a no-op here (it cannot change cadence). const periodSec = Math.round((loop.intervalMs ?? 0) / 1000); lines.push( `- This loop runs on a FIXED interval (every ${formatInterval(periodSec)}). You do NOT control the cadence; do not try to change it.`, ); lines.push( "- If more work remains, just finish this iteration; the next one will fire automatically on schedule.", ); lines.push("- If the task is complete or no further iterations help, call loop_stop(reason) to end the loop."); lines.push( `The loop will keep firing on its fixed interval and will hard-stop at iteration ${loop.maxIterations} (or when its time budget is exhausted).`, ); } else { lines.push( "- If more work / waiting is needed, call loop_schedule(delaySeconds, reason) to schedule the next iteration. Think about WHAT you are waiting for, not how long you want to sleep; pick a cadence accordingly.", ); lines.push("- If the task is complete or no further iterations help, call loop_stop(reason) to end the loop."); lines.push( `If you do neither, the loop will auto-reschedule defensively and will hard-stop at iteration ${loop.maxIterations}.`, ); } return lines.join("\n"); }