import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan } from "./types.js"; import { UlwLoopError } from "./types.js"; export const ULW_LOOP_HELP = `Usage: omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook) omo-agent-toolkit help | --help | -h (this message) omo-agent-toolkit ulw-loop help omo-agent-toolkit ulw-loop create-goals --brief "..." [--brief-file ] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--validation-batch-json ] [--force] [--json] omo-agent-toolkit ulw-loop status [--json] omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json] omo-agent-toolkit ulw-loop criteria --goal-id [--json] omo-agent-toolkit ulw-loop record-evidence --goal-id --criterion-id --status pass|fail|blocked --evidence "..." [--notes "..."] [--json] omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id ] [--json] omo-agent-toolkit ulw-loop checkpoint --goal-id --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json] omo-agent-toolkit ulw-loop steer --kind ... --evidence "..." --rationale "..." [--proposals-json ] [--json] omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json] omo-agent-toolkit ulw-loop record-review-blockers --goal-id --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json] Every state subcommand needs a session scope: [--session-id ] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop// and the unscoped root is never used implicitly. status --json exposes the currentAttemptDir; put all quality-gate artifacts under it. Every subcommand accepts --help | -h to print its own usage line.`; export function subcommandHelp(subcommand: string): string { const lines = ULW_LOOP_HELP.split("\n").filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`), ); if (lines.length === 0) return ULW_LOOP_HELP; return ["Usage:", ...lines].join("\n"); } type CriteriaCounts = { readonly pass: number; readonly total: number }; export function printJson(value: unknown): void { process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); } export function printJsonError(error: unknown): void { if (error instanceof UlwLoopError) { printJson({ ok: false, error: { code: error.code, message: error.message, ...(error.details === undefined ? {} : { details: error.details }), }, }); return; } if (error instanceof Error) { printJson({ ok: false, error: { code: "ULW_LOOP_UNEXPECTED", message: error.message } }); return; } printJson({ ok: false, error: { code: "ULW_LOOP_UNKNOWN", message: "unknown error" } }); } function criteriaCounts(goal: UlwLoopItem): CriteriaCounts { let pass = 0; for (const criterion of goal.successCriteria) if (criterion.status === "pass") pass += 1; return { pass, total: goal.successCriteria.length }; } export function printStatus(plan: UlwLoopPlan): void { let totalCriteria = 0; let passCriteria = 0; const lines = ["ulw-loop status", "", "goals:"]; for (const goal of plan.goals) { const counts = criteriaCounts(goal); totalCriteria += counts.total; passCriteria += counts.pass; const marker = goal.id === plan.activeGoalId ? "*" : "-"; lines.push(`${marker} ${goal.id} [${goal.status}] ${goal.title} (criteria: ${counts.pass}/${counts.total})`); } lines.push("", "summary:", `total goals: ${plan.goals.length}`, `criteria: ${passCriteria}/${totalCriteria} pass`); process.stdout.write(`${lines.join("\n")}\n`); } export function blockedDecisionHandoff(plan: UlwLoopPlan): string { const blocked = plan.goals.find((goal) => goal.status === "needs_user_decision" && goal.nonRetriable); if (blocked === undefined) return ""; return [ "ulw-loop: blocked on repeated external authorization; no retryable failed goals remain.", `Goal: ${blocked.id} - ${blocked.title}`, `Required external decision: ${blocked.requiredExternalDecision ?? "provide the missing authorization or choose a different unblock path"}.`, "Do not run complete-goals --retry-failed again until external state changes or the user authorizes an unblock path.", ].join("\n"); } export function normalizeCodexGoalMode(value: string | undefined): UlwLoopCodexGoalMode { if (value === undefined) return "aggregate"; if (value === "aggregate" || value === "per_story") return value; throw new UlwLoopError( "Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } }, ); }