lazycodex-ai 5.0.0-beta.57 → 5.0.0-beta.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/cli/index.js +18 -22
  2. package/dist/cli-node/index.js +18 -22
  3. package/package.json +1 -1
  4. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  5. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  6. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  7. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  8. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  9. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  10. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  11. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  12. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  13. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  14. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  15. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  16. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  17. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  18. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  19. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  20. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  21. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  22. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  23. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +5 -1
  27. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +4 -3
  28. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.d.ts +5 -1
  29. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +2 -2
  30. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.d.ts +1 -1
  31. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +38 -39
  32. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +163 -36
  33. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.d.ts +2 -0
  34. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.js +125 -0
  35. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +46 -0
  36. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +28 -0
  37. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +127 -0
  38. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.js +1 -0
  39. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.d.ts +3 -0
  40. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.js +3 -0
  41. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  42. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +4 -0
  44. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +8 -2
  45. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +6 -2
  46. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +50 -48
  47. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +159 -0
  48. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +42 -0
  49. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +140 -0
  50. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk.ts +3 -0
  51. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-contract.test.ts +137 -0
  52. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-session-context.test.ts +148 -0
  53. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  74. package/packages/omo-codex/plugin/package-lock.json +12 -12
  75. package/packages/omo-codex/plugin/package.json +1 -1
  76. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +4 -0
  77. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  78. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -1
@@ -3,15 +3,11 @@ import { hasFlag, parseCodexGoalJson, parseRecordEvidenceArgs, positionalText, r
3
3
  import { blockedDecisionHandoff, normalizeCodexGoalMode, printJson, printStatus } from "./cli-output.js";
4
4
  import { parseSteeringProposals, printSteerBatchResult, printSteerResult } from "./cli-steering.js";
5
5
  import { buildCodexGoalInstruction } from "./codex-goal-instruction.js";
6
- import { recordEvidence } from "./evidence.js";
7
6
  import { isEssentialCriterion } from "./goal-status.js";
8
- import { ulwLoopAttemptEvidenceDir } from "./paths.js";
9
- import { addUlwLoopGoal, createUlwLoopPlan, startNextUlwLoop, summarizeUlwLoopPlan } from "./plan-crud.js";
10
- import { readUlwLoopPlan } from "./plan-io.js";
11
- import { recordFinalReviewBlockers } from "./review-blockers.js";
12
- import { statusNextActions } from "./status-next-actions.js";
13
- import { steerUlwLoop } from "./steering.js";
7
+ import { summarizeUlwLoopPlan } from "./plan-crud.js";
8
+ import { createAgentToolkit } from "./sdk.js";
14
9
  import { steerUlwLoopBatch } from "./steering-batch.js";
10
+ import { resolveToolkitSurface } from "./surface.js";
15
11
  import { UlwLoopError } from "./types.js";
16
12
  export async function createGoals(repoRoot, argv, json, scope) {
17
13
  const briefFile = readValue(argv, "--brief-file");
@@ -23,12 +19,12 @@ export async function createGoals(repoRoot, argv, json, scope) {
23
19
  throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
24
20
  }
25
21
  const validationBatchesJson = readValue(argv, "--validation-batch-json");
26
- const plan = await createUlwLoopPlan(repoRoot, {
22
+ const plan = unwrap(await toolkitFor(repoRoot, scope).createGoals({
27
23
  brief,
28
24
  codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")),
29
25
  force: hasFlag(argv, "--force"),
30
26
  ...(validationBatchesJson === undefined ? {} : { validationBatchesJson }),
31
- }, scope);
27
+ }));
32
28
  if (json)
33
29
  printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
34
30
  else {
@@ -37,26 +33,15 @@ export async function createGoals(repoRoot, argv, json, scope) {
37
33
  return 0;
38
34
  }
39
35
  export async function status(repoRoot, json, scope) {
40
- const plan = await readUlwLoopPlan(repoRoot, scope);
41
- if (json) {
42
- const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
43
- const currentAttemptDir = plan.evidenceLayoutVersion === 2 && active
44
- ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope)
45
- : undefined;
46
- printJson({
47
- ok: true,
48
- plan,
49
- summary: summarizeUlwLoopPlan(plan),
50
- ...(currentAttemptDir === undefined ? {} : { currentAttemptDir }),
51
- nextActions: statusNextActions(plan),
52
- });
53
- }
36
+ const status = unwrap(await toolkitFor(repoRoot, scope).status());
37
+ if (json)
38
+ printJson({ ok: true, ...status });
54
39
  else
55
- printStatus(plan);
40
+ printStatus(status.plan);
56
41
  return 0;
57
42
  }
58
43
  export async function completeGoals(repoRoot, argv, json, scope) {
59
- const result = await startNextUlwLoop(repoRoot, { retryFailed: hasFlag(argv, "--retry-failed") }, scope);
44
+ const result = unwrap(await toolkitFor(repoRoot, scope).completeGoals({ retryFailed: hasFlag(argv, "--retry-failed") }));
60
45
  if ("done" in result) {
61
46
  const handoff = blockedDecisionHandoff(result.plan);
62
47
  if (json) {
@@ -84,7 +69,7 @@ export async function steer(repoRoot, argv, json, scope) {
84
69
  const proposals = await parseSteeringProposals(argv);
85
70
  const single = proposals[0];
86
71
  if (single !== undefined && proposals.length === 1 && readValue(argv, "--proposals-json") === undefined) {
87
- const result = await steerUlwLoop(repoRoot, single, scope);
72
+ const result = unwrap(await toolkitFor(repoRoot, scope).steer(single));
88
73
  printSteerResult(result, json);
89
74
  return result.accepted ? 0 : 1;
90
75
  }
@@ -93,7 +78,10 @@ export async function steer(repoRoot, argv, json, scope) {
93
78
  return result.accepted ? 0 : 1;
94
79
  }
95
80
  export async function addGoal(repoRoot, argv, json, scope) {
96
- const result = await addUlwLoopGoal(repoRoot, { title: required(argv, "--title"), objective: required(argv, "--objective") }, scope);
81
+ const result = unwrap(await toolkitFor(repoRoot, scope).addGoal({
82
+ title: required(argv, "--title"),
83
+ objective: required(argv, "--objective"),
84
+ }));
97
85
  if (json)
98
86
  printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
99
87
  else {
@@ -104,16 +92,16 @@ export async function addGoal(repoRoot, argv, json, scope) {
104
92
  }
105
93
  export async function criteria(repoRoot, argv, json, scope) {
106
94
  const goalId = required(argv, "--goal-id");
107
- const goal = findGoal(await readUlwLoopPlan(repoRoot, scope), goalId);
95
+ const result = unwrap(await toolkitFor(repoRoot, scope).criteria({ goalId }));
108
96
  if (json)
109
- printJson({ ok: true, goalId: goal.id, criteria: goal.successCriteria });
97
+ printJson({ ok: true, goalId: result.goalId, criteria: result.criteria });
110
98
  else {
111
- process.stdout.write(`criteria for ${goal.id}:\n${goal.successCriteria.map(formatCriterionForCli).join("\n")}\n`);
99
+ process.stdout.write(`criteria for ${result.goalId}:\n${result.criteria.map(formatCriterionForCli).join("\n")}\n`);
112
100
  }
113
101
  return 0;
114
102
  }
115
103
  export async function captureEvidence(repoRoot, argv, json, scope) {
116
- const result = await recordEvidence(repoRoot, parseRecordEvidenceArgs(argv), scope);
104
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordEvidence(parseRecordEvidenceArgs(argv)));
117
105
  if (json)
118
106
  printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
119
107
  else {
@@ -126,13 +114,13 @@ export async function reviewBlockers(repoRoot, argv, json, scope) {
126
114
  if (codexGoalJson === undefined) {
127
115
  throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
128
116
  }
129
- const result = await recordFinalReviewBlockers(repoRoot, {
117
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordReviewBlockers({
130
118
  goalId: required(argv, "--goal-id"),
131
119
  title: required(argv, "--title"),
132
120
  objective: required(argv, "--objective"),
133
121
  evidence: required(argv, "--evidence"),
134
122
  codexGoalJson,
135
- }, scope);
123
+ }));
136
124
  if (json) {
137
125
  printJson({
138
126
  ok: true,
@@ -150,6 +138,23 @@ export async function reviewBlockers(repoRoot, argv, json, scope) {
150
138
  }
151
139
  return 0;
152
140
  }
141
+ // Every subcommand runs through the SDK so state logic lives in one place; the CLI keeps only argv
142
+ // parsing and the legacy output shapes. A failed envelope is rethrown as the typed error the CLI's
143
+ // JSON error path already prints.
144
+ function toolkitFor(repoRoot, scope) {
145
+ const sessionId = scope?.sessionId?.trim();
146
+ if (sessionId === undefined || sessionId.length === 0) {
147
+ throw new UlwLoopError("Missing --session-id.", "ULW_LOOP_SESSION_ID_REQUIRED", {
148
+ details: { flag: "--session-id" },
149
+ });
150
+ }
151
+ return createAgentToolkit({ cwd: repoRoot, sessionId, surface: resolveToolkitSurface() });
152
+ }
153
+ function unwrap(response) {
154
+ if (response.ok)
155
+ return response.result;
156
+ throw new UlwLoopError(response.error.message, response.error.code);
157
+ }
153
158
  function formatCriterionForCli(criterion) {
154
159
  const marker = isEssentialCriterion(criterion) ? "essential" : "non-essential";
155
160
  return `- ${criterion.id} [${criterion.status}] [${marker}] (${criterion.userModel}) ${criterion.scenario} evidence: ${criterion.capturedEvidence ?? "pending"}`;
@@ -160,9 +165,3 @@ function required(argv, flag) {
160
165
  return value;
161
166
  throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
162
167
  }
163
- function findGoal(plan, goalId) {
164
- const goal = plan.goals.find((candidate) => candidate.id === goalId);
165
- if (goal !== undefined)
166
- return goal;
167
- throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
168
- }
@@ -1569,7 +1569,7 @@ function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletio
1569
1569
  entry.requiredExternalDecision = goal.requiredExternalDecision;
1570
1570
  return entry;
1571
1571
  }
1572
- async function checkpointUlwLoop(repoRoot, args, scope) {
1572
+ async function checkpointUlwLoop(repoRoot, args, scope, dependencies) {
1573
1573
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
1574
1574
  const plan = await readUlwLoopPlan(repoRoot, scope);
1575
1575
  const goal = findGoal2(plan, args.goalId);
@@ -1621,7 +1621,7 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1621
1621
  qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
1622
1622
  repoRoot,
1623
1623
  fs: QUALITY_GATE_FS,
1624
- reviewerSurface: resolveToolkitSurface(),
1624
+ reviewerSurface: dependencies?.surface ?? resolveToolkitSurface(),
1625
1625
  ...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
1626
1626
  });
1627
1627
  requireBatchGate(plan, goal, qualityGate);
@@ -1740,8 +1740,9 @@ function gateTemplate(surface, base) {
1740
1740
  };
1741
1741
  return common;
1742
1742
  }
1743
- async function checkpointTemplate(repoRoot, scope, goalId) {
1743
+ async function checkpointTemplate(repoRoot, scope, goalId, dependencies) {
1744
1744
  const plan = await readUlwLoopPlan(repoRoot, scope);
1745
+ const surface = dependencies?.surface ?? resolveToolkitSurface();
1745
1746
  const targetId = goalId ?? plan.activeGoalId;
1746
1747
  const active = plan.goals.find((goal) => goal.id === targetId);
1747
1748
  if (goalId !== undefined && active === undefined)
@@ -1753,13 +1754,13 @@ async function checkpointTemplate(repoRoot, scope, goalId) {
1753
1754
  "Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
1754
1755
  'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
1755
1756
  'Passing quality-gate-json example requires gateReview {"by":"category:deep","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
1756
- ...resolveToolkitSurface() === "lazycodex" ? [
1757
+ ...surface === "lazycodex" ? [
1757
1758
  "Self-review defaults: manualQa.by and gateReview.by are main-session. Alternatives: manualQa.by accepts lazycodex-qa-executor; gateReview.by accepts lazycodex-gate-reviewer, category:deep, category:unspecified-high, or category:unspecified-low. Optional codeReview.by accepts lazycodex-code-reviewer or main-session."
1758
1759
  ] : [],
1759
1760
  ...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
1760
1761
  ].join(" ");
1761
1762
  return {
1762
- qualityGateTemplate: gateTemplate(resolveToolkitSurface(), attemptDir),
1763
+ qualityGateTemplate: gateTemplate(surface, attemptDir),
1763
1764
  codexGoalTemplate: {
1764
1765
  goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
1765
1766
  },
@@ -3039,6 +3040,129 @@ function ledgerEntry(proposal, audit, at) {
3039
3040
  return entry;
3040
3041
  }
3041
3042
 
3043
+ // components/ulw-loop/src/sdk/manifest.ts
3044
+ var ULW_LOOP_MANIFEST = {
3045
+ version: 1,
3046
+ name: "ulw-loop",
3047
+ operations: [
3048
+ { name: "help", mutating: false },
3049
+ { name: "create-goals", mutating: true },
3050
+ { name: "status", mutating: false },
3051
+ { name: "complete-goals", mutating: true },
3052
+ { name: "checkpoint", mutating: true },
3053
+ { name: "steer", mutating: true },
3054
+ { name: "add-goal", mutating: true },
3055
+ { name: "criteria", mutating: false },
3056
+ { name: "record-evidence", mutating: true },
3057
+ { name: "record-review-blockers", mutating: true }
3058
+ ]
3059
+ };
3060
+
3061
+ // components/ulw-loop/src/sdk/factory.ts
3062
+ function validateContext(context) {
3063
+ if (!context.cwd.trim())
3064
+ throw new UlwLoopError("cwd is required.", "ULW_LOOP_CWD_REQUIRED");
3065
+ if (!context.sessionId.trim())
3066
+ throw new UlwLoopError("ULW_LOOP_SESSION_ID_REQUIRED: sessionId is required.", "ULW_LOOP_SESSION_ID_REQUIRED");
3067
+ if (context.surface !== "omo-senpi" && context.surface !== "lazycodex")
3068
+ throw new UlwLoopError("surface must be omo-senpi or lazycodex.", "ULW_LOOP_SURFACE_INVALID");
3069
+ }
3070
+ function errorDetails(error) {
3071
+ const details = error.details === undefined ? undefined : Object.fromEntries(Object.entries(error.details).map(([key, value]) => [key, String(value)]));
3072
+ return details === undefined ? { code: error.code, message: error.message } : { code: error.code, message: error.message, details };
3073
+ }
3074
+ function failure(operation, error) {
3075
+ return { ok: false, operation, error: errorDetails(error) };
3076
+ }
3077
+ function caught(operation, error) {
3078
+ return failure(operation, error instanceof UlwLoopError ? error : new UlwLoopError(error.message, "ULW_LOOP_ERROR"));
3079
+ }
3080
+ function isKnownRequest(request) {
3081
+ return ULW_LOOP_MANIFEST.operations.some((operation) => operation.name === request.operation);
3082
+ }
3083
+ function unreachable(value) {
3084
+ throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
3085
+ }
3086
+ function nextActionsFrom(result) {
3087
+ if (!("nextActions" in result) || !Array.isArray(result.nextActions))
3088
+ return [];
3089
+ return result.nextActions.filter((action) => typeof action === "string").slice(0, 8);
3090
+ }
3091
+ function createAgentToolkit(context, deps = {}) {
3092
+ validateContext(context);
3093
+ const scope = { sessionId: context.sessionId };
3094
+ const notify = async (operation, response) => {
3095
+ if (deps.hooks?.onOperation !== undefined)
3096
+ await deps.hooks.onOperation({ operation, context, response });
3097
+ return response;
3098
+ };
3099
+ const invoke = async (operation, fn) => {
3100
+ try {
3101
+ const result = await fn();
3102
+ const nextActions = typeof result === "object" && result !== null ? nextActionsFrom(result) : [];
3103
+ return await notify(operation, { ok: true, operation, result, nextActions });
3104
+ } catch (error) {
3105
+ const response = caught(operation, error instanceof Error ? error : new Error("ULW_LOOP_ERROR"));
3106
+ return notify(operation, response);
3107
+ }
3108
+ };
3109
+ const toolkit = {
3110
+ dispatch: async (request) => {
3111
+ if (!isKnownRequest(request))
3112
+ return failure(request.operation, new UlwLoopError(`Unknown operation: ${request.operation}`, "ULW_LOOP_OPERATION_UNKNOWN"));
3113
+ switch (request.operation) {
3114
+ case "help":
3115
+ return toolkit.help();
3116
+ case "create-goals":
3117
+ return toolkit.createGoals(request.args);
3118
+ case "status":
3119
+ return toolkit.status();
3120
+ case "complete-goals":
3121
+ return toolkit.completeGoals(request.args);
3122
+ case "checkpoint":
3123
+ return toolkit.checkpoint(request.args);
3124
+ case "steer":
3125
+ return toolkit.steer(request.args);
3126
+ case "add-goal":
3127
+ return toolkit.addGoal(request.args);
3128
+ case "criteria":
3129
+ return toolkit.criteria(request.args);
3130
+ case "record-evidence":
3131
+ return toolkit.recordEvidence(request.args);
3132
+ case "record-review-blockers":
3133
+ return toolkit.recordReviewBlockers(request.args);
3134
+ default:
3135
+ return unreachable(request);
3136
+ }
3137
+ },
3138
+ help: () => invoke("help", async () => ULW_LOOP_MANIFEST),
3139
+ createGoals: (args) => invoke("create-goals", () => createUlwLoopPlan(context.cwd, args, scope)),
3140
+ status: () => invoke("status", async () => {
3141
+ const plan = await readUlwLoopPlan(context.cwd, scope);
3142
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3143
+ return {
3144
+ plan,
3145
+ summary: summarizeUlwLoopPlan(plan),
3146
+ nextActions: statusNextActions(plan),
3147
+ ...active === undefined || plan.evidenceLayoutVersion !== 2 ? {} : { currentAttemptDir: ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) }
3148
+ };
3149
+ }),
3150
+ completeGoals: (args = {}) => invoke("complete-goals", () => startNextUlwLoop(context.cwd, args, scope)),
3151
+ checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true ? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface }) : checkpointUlwLoop(context.cwd, args, scope, { surface: context.surface })),
3152
+ steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope)),
3153
+ addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope)),
3154
+ criteria: (args) => invoke("criteria", async () => {
3155
+ const plan = await readUlwLoopPlan(context.cwd, scope);
3156
+ const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
3157
+ if (goal === undefined)
3158
+ throw new UlwLoopError(`Unknown ulw-loop id: ${args.goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND");
3159
+ return { goalId: goal.id, criteria: goal.successCriteria };
3160
+ }),
3161
+ recordEvidence: (args) => invoke("record-evidence", () => recordEvidence(context.cwd, args, scope)),
3162
+ recordReviewBlockers: (args) => invoke("record-review-blockers", () => recordFinalReviewBlockers(context.cwd, args, scope))
3163
+ };
3164
+ return toolkit;
3165
+ }
3042
3166
  // components/ulw-loop/src/steering-batch.ts
3043
3167
  async function steerUlwLoopBatch(repoRoot, proposals, scope) {
3044
3168
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
@@ -3128,12 +3252,12 @@ async function createGoals(repoRoot, argv, json, scope) {
3128
3252
  throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
3129
3253
  }
3130
3254
  const validationBatchesJson = readValue(argv, "--validation-batch-json");
3131
- const plan = await createUlwLoopPlan(repoRoot, {
3255
+ const plan = unwrap(await toolkitFor(repoRoot, scope).createGoals({
3132
3256
  brief,
3133
3257
  codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")),
3134
3258
  force: hasFlag(argv, "--force"),
3135
3259
  ...validationBatchesJson === undefined ? {} : { validationBatchesJson }
3136
- }, scope);
3260
+ }));
3137
3261
  if (json)
3138
3262
  printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
3139
3263
  else {
@@ -3146,23 +3270,15 @@ ledger: ${plan.ledgerPath}
3146
3270
  return 0;
3147
3271
  }
3148
3272
  async function status(repoRoot, json, scope) {
3149
- const plan = await readUlwLoopPlan(repoRoot, scope);
3150
- if (json) {
3151
- const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3152
- const currentAttemptDir = plan.evidenceLayoutVersion === 2 && active ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : undefined;
3153
- printJson({
3154
- ok: true,
3155
- plan,
3156
- summary: summarizeUlwLoopPlan(plan),
3157
- ...currentAttemptDir === undefined ? {} : { currentAttemptDir },
3158
- nextActions: statusNextActions(plan)
3159
- });
3160
- } else
3161
- printStatus(plan);
3273
+ const status = unwrap(await toolkitFor(repoRoot, scope).status());
3274
+ if (json)
3275
+ printJson({ ok: true, ...status });
3276
+ else
3277
+ printStatus(status.plan);
3162
3278
  return 0;
3163
3279
  }
3164
3280
  async function completeGoals(repoRoot, argv, json, scope) {
3165
- const result = await startNextUlwLoop(repoRoot, { retryFailed: hasFlag(argv, "--retry-failed") }, scope);
3281
+ const result = unwrap(await toolkitFor(repoRoot, scope).completeGoals({ retryFailed: hasFlag(argv, "--retry-failed") }));
3166
3282
  if ("done" in result) {
3167
3283
  const handoff = blockedDecisionHandoff(result.plan);
3168
3284
  if (json) {
@@ -3191,7 +3307,7 @@ async function steer(repoRoot, argv, json, scope) {
3191
3307
  const proposals = await parseSteeringProposals(argv);
3192
3308
  const single = proposals[0];
3193
3309
  if (single !== undefined && proposals.length === 1 && readValue(argv, "--proposals-json") === undefined) {
3194
- const result = await steerUlwLoop(repoRoot, single, scope);
3310
+ const result = unwrap(await toolkitFor(repoRoot, scope).steer(single));
3195
3311
  printSteerResult(result, json);
3196
3312
  return result.accepted ? 0 : 1;
3197
3313
  }
@@ -3200,7 +3316,10 @@ async function steer(repoRoot, argv, json, scope) {
3200
3316
  return result.accepted ? 0 : 1;
3201
3317
  }
3202
3318
  async function addGoal(repoRoot, argv, json, scope) {
3203
- const result = await addUlwLoopGoal(repoRoot, { title: required4(argv, "--title"), objective: required4(argv, "--objective") }, scope);
3319
+ const result = unwrap(await toolkitFor(repoRoot, scope).addGoal({
3320
+ title: required4(argv, "--title"),
3321
+ objective: required4(argv, "--objective")
3322
+ }));
3204
3323
  if (json)
3205
3324
  printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
3206
3325
  else {
@@ -3212,19 +3331,19 @@ async function addGoal(repoRoot, argv, json, scope) {
3212
3331
  }
3213
3332
  async function criteria(repoRoot, argv, json, scope) {
3214
3333
  const goalId = required4(argv, "--goal-id");
3215
- const goal = findGoal3(await readUlwLoopPlan(repoRoot, scope), goalId);
3334
+ const result = unwrap(await toolkitFor(repoRoot, scope).criteria({ goalId }));
3216
3335
  if (json)
3217
- printJson({ ok: true, goalId: goal.id, criteria: goal.successCriteria });
3336
+ printJson({ ok: true, goalId: result.goalId, criteria: result.criteria });
3218
3337
  else {
3219
- process.stdout.write(`criteria for ${goal.id}:
3220
- ${goal.successCriteria.map(formatCriterionForCli).join(`
3338
+ process.stdout.write(`criteria for ${result.goalId}:
3339
+ ${result.criteria.map(formatCriterionForCli).join(`
3221
3340
  `)}
3222
3341
  `);
3223
3342
  }
3224
3343
  return 0;
3225
3344
  }
3226
3345
  async function captureEvidence(repoRoot, argv, json, scope) {
3227
- const result = await recordEvidence(repoRoot, parseRecordEvidenceArgs(argv), scope);
3346
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordEvidence(parseRecordEvidenceArgs(argv)));
3228
3347
  if (json)
3229
3348
  printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
3230
3349
  else {
@@ -3238,13 +3357,13 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
3238
3357
  if (codexGoalJson === undefined) {
3239
3358
  throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
3240
3359
  }
3241
- const result = await recordFinalReviewBlockers(repoRoot, {
3360
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordReviewBlockers({
3242
3361
  goalId: required4(argv, "--goal-id"),
3243
3362
  title: required4(argv, "--title"),
3244
3363
  objective: required4(argv, "--objective"),
3245
3364
  evidence: required4(argv, "--evidence"),
3246
3365
  codexGoalJson
3247
- }, scope);
3366
+ }));
3248
3367
  if (json) {
3249
3368
  printJson({
3250
3369
  ok: true,
@@ -3262,6 +3381,20 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
3262
3381
  }
3263
3382
  return 0;
3264
3383
  }
3384
+ function toolkitFor(repoRoot, scope) {
3385
+ const sessionId = scope?.sessionId?.trim();
3386
+ if (sessionId === undefined || sessionId.length === 0) {
3387
+ throw new UlwLoopError("Missing --session-id.", "ULW_LOOP_SESSION_ID_REQUIRED", {
3388
+ details: { flag: "--session-id" }
3389
+ });
3390
+ }
3391
+ return createAgentToolkit({ cwd: repoRoot, sessionId, surface: resolveToolkitSurface() });
3392
+ }
3393
+ function unwrap(response) {
3394
+ if (response.ok)
3395
+ return response.result;
3396
+ throw new UlwLoopError(response.error.message, response.error.code);
3397
+ }
3265
3398
  function formatCriterionForCli(criterion) {
3266
3399
  const marker = isEssentialCriterion(criterion) ? "essential" : "non-essential";
3267
3400
  return `- ${criterion.id} [${criterion.status}] [${marker}] (${criterion.userModel}) ${criterion.scenario} evidence: ${criterion.capturedEvidence ?? "pending"}`;
@@ -3272,12 +3405,6 @@ function required4(argv, flag) {
3272
3405
  return value;
3273
3406
  throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
3274
3407
  }
3275
- function findGoal3(plan, goalId) {
3276
- const goal = plan.goals.find((candidate) => candidate.id === goalId);
3277
- if (goal !== undefined)
3278
- return goal;
3279
- throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
3280
- }
3281
3408
 
3282
3409
  // components/ulw-loop/src/cli-commands.ts
3283
3410
  var ULW_LOOP_SUBCOMMANDS = [
@@ -0,0 +1,2 @@
1
+ import type { AgentToolkit, AgentToolkitDependencies, ToolkitContext } from "./types.js";
2
+ export declare function createAgentToolkit(context: ToolkitContext, deps?: AgentToolkitDependencies): AgentToolkit;
@@ -0,0 +1,125 @@
1
+ import { checkpointUlwLoop } from "../checkpoint.js";
2
+ import { checkpointTemplate } from "../checkpoint-template.js";
3
+ import { recordEvidence } from "../evidence.js";
4
+ import { ulwLoopAttemptEvidenceDir } from "../paths.js";
5
+ import { addUlwLoopGoal, createUlwLoopPlan, startNextUlwLoop, summarizeUlwLoopPlan } from "../plan-crud.js";
6
+ import { readUlwLoopPlan } from "../plan-io.js";
7
+ import { recordFinalReviewBlockers } from "../review-blockers.js";
8
+ import { UlwLoopError } from "../runtime.js";
9
+ import { statusNextActions } from "../status-next-actions.js";
10
+ import { steerUlwLoop } from "../steering.js";
11
+ import { ULW_LOOP_MANIFEST } from "./manifest.js";
12
+ function validateContext(context) {
13
+ if (!context.cwd.trim())
14
+ throw new UlwLoopError("cwd is required.", "ULW_LOOP_CWD_REQUIRED");
15
+ if (!context.sessionId.trim())
16
+ throw new UlwLoopError("ULW_LOOP_SESSION_ID_REQUIRED: sessionId is required.", "ULW_LOOP_SESSION_ID_REQUIRED");
17
+ if (context.surface !== "omo-senpi" && context.surface !== "lazycodex")
18
+ throw new UlwLoopError("surface must be omo-senpi or lazycodex.", "ULW_LOOP_SURFACE_INVALID");
19
+ }
20
+ function errorDetails(error) {
21
+ const details = error.details === undefined
22
+ ? undefined
23
+ : Object.fromEntries(Object.entries(error.details).map(([key, value]) => [key, String(value)]));
24
+ return details === undefined
25
+ ? { code: error.code, message: error.message }
26
+ : { code: error.code, message: error.message, details };
27
+ }
28
+ function failure(operation, error) {
29
+ return { ok: false, operation, error: errorDetails(error) };
30
+ }
31
+ function caught(operation, error) {
32
+ return failure(operation, error instanceof UlwLoopError ? error : new UlwLoopError(error.message, "ULW_LOOP_ERROR"));
33
+ }
34
+ function isKnownRequest(request) {
35
+ return ULW_LOOP_MANIFEST.operations.some((operation) => operation.name === request.operation);
36
+ }
37
+ function unreachable(value) {
38
+ throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
39
+ }
40
+ function nextActionsFrom(result) {
41
+ if (!("nextActions" in result) || !Array.isArray(result.nextActions))
42
+ return [];
43
+ return result.nextActions.filter((action) => typeof action === "string").slice(0, 8);
44
+ }
45
+ export function createAgentToolkit(context, deps = {}) {
46
+ validateContext(context);
47
+ const scope = { sessionId: context.sessionId };
48
+ const notify = async (operation, response) => {
49
+ if (deps.hooks?.onOperation !== undefined)
50
+ await deps.hooks.onOperation({ operation, context, response });
51
+ return response;
52
+ };
53
+ const invoke = async (operation, fn) => {
54
+ try {
55
+ const result = await fn();
56
+ const nextActions = typeof result === "object" && result !== null ? nextActionsFrom(result) : [];
57
+ return await notify(operation, { ok: true, operation, result, nextActions });
58
+ }
59
+ catch (error) {
60
+ const response = caught(operation, error instanceof Error ? error : new Error("ULW_LOOP_ERROR"));
61
+ return notify(operation, response);
62
+ }
63
+ };
64
+ const toolkit = {
65
+ dispatch: async (request) => {
66
+ if (!isKnownRequest(request))
67
+ return failure(request.operation, new UlwLoopError(`Unknown operation: ${request.operation}`, "ULW_LOOP_OPERATION_UNKNOWN"));
68
+ switch (request.operation) {
69
+ case "help":
70
+ return toolkit.help();
71
+ case "create-goals":
72
+ return toolkit.createGoals(request.args);
73
+ case "status":
74
+ return toolkit.status();
75
+ case "complete-goals":
76
+ return toolkit.completeGoals(request.args);
77
+ case "checkpoint":
78
+ return toolkit.checkpoint(request.args);
79
+ case "steer":
80
+ return toolkit.steer(request.args);
81
+ case "add-goal":
82
+ return toolkit.addGoal(request.args);
83
+ case "criteria":
84
+ return toolkit.criteria(request.args);
85
+ case "record-evidence":
86
+ return toolkit.recordEvidence(request.args);
87
+ case "record-review-blockers":
88
+ return toolkit.recordReviewBlockers(request.args);
89
+ default:
90
+ return unreachable(request);
91
+ }
92
+ },
93
+ help: () => invoke("help", async () => ULW_LOOP_MANIFEST),
94
+ createGoals: (args) => invoke("create-goals", () => createUlwLoopPlan(context.cwd, args, scope)),
95
+ status: () => invoke("status", async () => {
96
+ const plan = await readUlwLoopPlan(context.cwd, scope);
97
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
98
+ return {
99
+ plan,
100
+ summary: summarizeUlwLoopPlan(plan),
101
+ nextActions: statusNextActions(plan),
102
+ // Attempt directories are an evidence-layout v2 concept; a v1 plan must not advertise one.
103
+ ...(active === undefined || plan.evidenceLayoutVersion !== 2
104
+ ? {}
105
+ : { currentAttemptDir: ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) }),
106
+ };
107
+ }),
108
+ completeGoals: (args = {}) => invoke("complete-goals", () => startNextUlwLoop(context.cwd, args, scope)),
109
+ checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true
110
+ ? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface })
111
+ : checkpointUlwLoop(context.cwd, args, scope, { surface: context.surface })),
112
+ steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope)),
113
+ addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope)),
114
+ criteria: (args) => invoke("criteria", async () => {
115
+ const plan = await readUlwLoopPlan(context.cwd, scope);
116
+ const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
117
+ if (goal === undefined)
118
+ throw new UlwLoopError(`Unknown ulw-loop id: ${args.goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND");
119
+ return { goalId: goal.id, criteria: goal.successCriteria };
120
+ }),
121
+ recordEvidence: (args) => invoke("record-evidence", () => recordEvidence(context.cwd, args, scope)),
122
+ recordReviewBlockers: (args) => invoke("record-review-blockers", () => recordFinalReviewBlockers(context.cwd, args, scope)),
123
+ };
124
+ return toolkit;
125
+ }
@@ -0,0 +1,46 @@
1
+ export declare const ULW_LOOP_OPERATIONS: readonly ["help", "create-goals", "status", "complete-goals", "checkpoint", "steer", "add-goal", "criteria", "record-evidence", "record-review-blockers"];
2
+ export type UlwLoopOperation = (typeof ULW_LOOP_OPERATIONS)[number];
3
+ export interface ToolkitOperationManifest {
4
+ readonly name: UlwLoopOperation;
5
+ readonly mutating: boolean;
6
+ }
7
+ export interface ToolkitManifest {
8
+ readonly version: 1;
9
+ readonly name: "ulw-loop";
10
+ readonly operations: readonly ToolkitOperationManifest[];
11
+ }
12
+ export declare const ULW_LOOP_MANIFEST: {
13
+ version: 1;
14
+ name: "ulw-loop";
15
+ operations: ({
16
+ name: "help";
17
+ mutating: false;
18
+ } | {
19
+ name: "create-goals";
20
+ mutating: true;
21
+ } | {
22
+ name: "status";
23
+ mutating: false;
24
+ } | {
25
+ name: "complete-goals";
26
+ mutating: true;
27
+ } | {
28
+ name: "checkpoint";
29
+ mutating: true;
30
+ } | {
31
+ name: "steer";
32
+ mutating: true;
33
+ } | {
34
+ name: "add-goal";
35
+ mutating: true;
36
+ } | {
37
+ name: "criteria";
38
+ mutating: false;
39
+ } | {
40
+ name: "record-evidence";
41
+ mutating: true;
42
+ } | {
43
+ name: "record-review-blockers";
44
+ mutating: true;
45
+ })[];
46
+ };