@gajae-code/coding-agent 0.12.5 → 0.12.6

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 (127) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +4 -1
  3. package/dist/types/eval/backend.d.ts +2 -2
  4. package/dist/types/exec/bash-executor.d.ts +10 -8
  5. package/dist/types/extensibility/extensions/runner.d.ts +9 -7
  6. package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
  7. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  8. package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
  9. package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
  10. package/dist/types/gjc-runtime/team-store.d.ts +0 -4
  11. package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
  12. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  13. package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
  14. package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
  15. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
  16. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
  17. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
  18. package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
  19. package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
  20. package/dist/types/modes/components/plugin-settings.d.ts +2 -0
  21. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  22. package/dist/types/sdk/broker/broker.d.ts +18 -0
  23. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
  24. package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
  25. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -3
  26. package/dist/types/sdk/bus/index.d.ts +4 -0
  27. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
  28. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
  29. package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
  30. package/dist/types/sdk/host/host.d.ts +23 -2
  31. package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
  32. package/dist/types/session/agent-session.d.ts +5 -4
  33. package/dist/types/session/artifacts.d.ts +8 -5
  34. package/dist/types/session/attempt-record-store.d.ts +19 -0
  35. package/dist/types/session/client-bridge.d.ts +14 -0
  36. package/dist/types/session/internal/managed-session-storage.d.ts +5 -2
  37. package/dist/types/session/messages.d.ts +4 -1
  38. package/dist/types/session/session-manager.d.ts +6 -4
  39. package/dist/types/session/session-storage.d.ts +8 -0
  40. package/dist/types/session/streaming-output.d.ts +32 -0
  41. package/dist/types/task/receipt.d.ts +2 -0
  42. package/dist/types/task/types.d.ts +178 -0
  43. package/dist/types/tools/bash.d.ts +1 -1
  44. package/dist/types/tools/fetch.d.ts +3 -0
  45. package/dist/types/tools/output-meta.d.ts +37 -5
  46. package/dist/types/tools/tool-result.d.ts +1 -0
  47. package/dist/types/utils/artifact-id.d.ts +2 -0
  48. package/package.json +8 -8
  49. package/scripts/capture-sticky-viewport-showcase.ts +131 -5
  50. package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
  51. package/scripts/verify-sticky-viewport-showcase.ts +620 -93
  52. package/src/config/model-profiles.ts +8 -0
  53. package/src/config/settings-schema.ts +17 -3
  54. package/src/cursor.ts +20 -13
  55. package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
  56. package/src/eval/backend.ts +2 -2
  57. package/src/exec/bash-executor.ts +425 -151
  58. package/src/extensibility/extensions/runner.ts +80 -6
  59. package/src/extensibility/extensions/wrapper.ts +22 -15
  60. package/src/extensibility/runtime-skill-discovery.ts +4 -6
  61. package/src/gjc-runtime/launch-tmux.ts +309 -293
  62. package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
  63. package/src/gjc-runtime/psmux-detect.ts +52 -6
  64. package/src/gjc-runtime/team-launch.ts +50 -2
  65. package/src/gjc-runtime/team-runtime.ts +716 -251
  66. package/src/gjc-runtime/team-store.ts +25 -7
  67. package/src/gjc-runtime/team-workers.ts +161 -1
  68. package/src/gjc-runtime/tmux-common.ts +16 -4
  69. package/src/gjc-runtime/tmux-gc.ts +37 -5
  70. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
  71. package/src/gjc-runtime/tmux-provider-context.ts +451 -0
  72. package/src/gjc-runtime/tmux-sessions.ts +409 -156
  73. package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
  74. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
  75. package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
  76. package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
  77. package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
  78. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  79. package/src/internal-urls/agent-protocol.ts +98 -38
  80. package/src/internal-urls/artifact-protocol.ts +28 -17
  81. package/src/internal-urls/docs-index.generated.ts +5 -5
  82. package/src/internal-urls/local-protocol.ts +39 -18
  83. package/src/internal-urls/registry-helpers.ts +62 -0
  84. package/src/modes/acp/acp-agent.ts +51 -13
  85. package/src/modes/acp/acp-client-bridge.ts +154 -11
  86. package/src/modes/components/gjc-bundle-settings.ts +2 -0
  87. package/src/modes/components/plugin-settings.ts +5 -0
  88. package/src/modes/components/settings-selector.ts +4 -0
  89. package/src/modes/controllers/selector-controller.ts +1 -0
  90. package/src/prompts/tools/bash.md +2 -2
  91. package/src/prompts/tools/monitor.md +4 -4
  92. package/src/sdk/acp/adapter.ts +200 -9
  93. package/src/sdk/broker/broker.ts +93 -10
  94. package/src/sdk/broker/lifecycle-ledger.ts +227 -11
  95. package/src/sdk/broker/lifecycle.ts +691 -92
  96. package/src/sdk/bus/chat-daemon-control.ts +32 -6
  97. package/src/sdk/bus/index.ts +347 -14
  98. package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
  99. package/src/sdk/bus/telegram-daemon.ts +283 -44
  100. package/src/sdk/bus/topic-registry.ts +133 -10
  101. package/src/sdk/host/host.ts +187 -9
  102. package/src/sdk/host/reverse-leases.ts +249 -42
  103. package/src/sdk/session.ts +7 -6
  104. package/src/session/agent-session.ts +930 -354
  105. package/src/session/artifacts.ts +211 -74
  106. package/src/session/attempt-record-store.ts +106 -0
  107. package/src/session/client-bridge.ts +15 -0
  108. package/src/session/internal/managed-session-scope.ts +252 -67
  109. package/src/session/internal/managed-session-storage.ts +105 -5
  110. package/src/session/messages.ts +32 -7
  111. package/src/session/session-manager.ts +19 -21
  112. package/src/session/session-storage.ts +146 -24
  113. package/src/session/streaming-output.ts +198 -40
  114. package/src/task/index.ts +45 -0
  115. package/src/task/provider-retry-status.ts +4 -1
  116. package/src/task/receipt.ts +4 -0
  117. package/src/task/render.ts +30 -0
  118. package/src/task/types.ts +34 -0
  119. package/src/tools/bash.ts +818 -239
  120. package/src/tools/eval.ts +99 -4
  121. package/src/tools/fetch.ts +77 -15
  122. package/src/tools/find.ts +7 -3
  123. package/src/tools/monitor.ts +7 -1
  124. package/src/tools/output-meta.ts +706 -116
  125. package/src/tools/search.ts +9 -4
  126. package/src/tools/tool-result.ts +1 -0
  127. package/src/utils/artifact-id.ts +6 -0
@@ -92,6 +92,13 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
92
92
  critic: "openai-codex/gpt-5.6-sol:max",
93
93
  architect: "openai-codex/gpt-5.6-sol:xhigh",
94
94
  }),
95
+ profile("lunamaxxing", ["openai-codex"], {
96
+ default: "openai-codex/gpt-5.6-luna:medium",
97
+ executor: "openai-codex/gpt-5.6-luna:xhigh",
98
+ planner: "openai-codex/gpt-5.6-luna:max",
99
+ critic: "openai-codex/gpt-5.6-luna:max",
100
+ architect: "openai-codex/gpt-5.6-luna:max",
101
+ }),
95
102
  profile("opencodego", ["opencode-go"], {
96
103
  default: "opencode-go/kimi-k2.6",
97
104
  executor: "opencode-go/deepseek-v4-flash",
@@ -327,6 +334,7 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
327
334
  "codex-eco": { displayName: "Codex Eco", providerGroup: "CODEX" },
328
335
  "codex-medium": { displayName: "Codex Medium", providerGroup: "CODEX" },
329
336
  "codex-pro": { displayName: "Codex Pro", providerGroup: "CODEX" },
337
+ lunamaxxing: { displayName: "LunaMaxxing", providerGroup: "CODEX" },
330
338
  opencodego: { displayName: "OpenCodeGo", providerGroup: "OPENCODEGO" },
331
339
  "claude-opus": { displayName: "Claude Opus", providerGroup: "CLAUDE" },
332
340
  "claude-fable": { displayName: "Claude Fable", providerGroup: "CLAUDE" },
@@ -3744,6 +3744,11 @@ function schemaPaths(value: Record<string, unknown>, prefix = ""): string[] {
3744
3744
  return paths;
3745
3745
  }
3746
3746
 
3747
+ function validArraySettingValue(value: unknown, allowedValues: readonly string[] | undefined): boolean {
3748
+ if (!Array.isArray(value)) return false;
3749
+ return !allowedValues || value.every(item => typeof item === "string" && allowedValues.includes(item));
3750
+ }
3751
+
3747
3752
  function validSettingValue(definition: (typeof SETTINGS_SCHEMA)[SettingPath], value: unknown): boolean {
3748
3753
  return (
3749
3754
  (definition.type === "boolean" && typeof value === "boolean") ||
@@ -3755,7 +3760,8 @@ function validSettingValue(definition: (typeof SETTINGS_SCHEMA)[SettingPath], va
3755
3760
  (definition.type === "enum" &&
3756
3761
  typeof value === "string" &&
3757
3762
  (definition.values as readonly string[]).includes(value)) ||
3758
- (definition.type === "array" && Array.isArray(value)) ||
3763
+ (definition.type === "array" &&
3764
+ validArraySettingValue(value, "items" in definition ? definition.items?.enum : undefined)) ||
3759
3765
  (definition.type === "record" && !!value && typeof value === "object" && !Array.isArray(value))
3760
3766
  );
3761
3767
  }
@@ -3792,8 +3798,16 @@ export function reconcileSettingsSchema(raw: Record<string, unknown>): {
3792
3798
  schemaSetAtPath(settings, path, next);
3793
3799
  issues.push({ path, kind: "coerced", detail: `Coerced ${typeof value} to ${definition.type}.` });
3794
3800
  }
3795
- if (!validSettingValue(definition, next))
3796
- issues.push({ path, kind: "invalid", detail: `Expected ${definition.type}.` });
3801
+ if (!validSettingValue(definition, next)) {
3802
+ const arrayItemEnum =
3803
+ definition.type === "array" && Array.isArray(next) && "items" in definition
3804
+ ? definition.items?.enum
3805
+ : undefined;
3806
+ const detail = arrayItemEnum
3807
+ ? `Expected array items to be one of: ${arrayItemEnum.join(", ")}.`
3808
+ : `Expected ${definition.type}.`;
3809
+ issues.push({ path, kind: "invalid", detail });
3810
+ }
3797
3811
  if (
3798
3812
  definition.type === "record" &&
3799
3813
  "valueSchema" in definition &&
package/src/cursor.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  ToolResultMessage,
15
15
  } from "@gajae-code/ai";
16
16
  import { sanitizeText } from "@gajae-code/utils";
17
+ import { finalizeToolResultForDelivery } from "./tools/output-meta";
17
18
  import { resolveToCwd } from "./tools/path-utils";
18
19
 
19
20
  interface CursorExecBridgeOptions {
@@ -48,6 +49,14 @@ function buildToolErrorResult(message: string): AgentToolResult<unknown> {
48
49
  };
49
50
  }
50
51
 
52
+ async function buildFinalToolErrorResult(
53
+ options: CursorExecBridgeOptions,
54
+ toolName: string,
55
+ message: string,
56
+ ): Promise<AgentToolResult<unknown>> {
57
+ return finalizeToolResultForDelivery(buildToolErrorResult(message), toolName, options.getToolContext?.());
58
+ }
59
+
51
60
  async function executeTool(
52
61
  options: CursorExecBridgeOptions,
53
62
  toolName: string,
@@ -56,11 +65,12 @@ async function executeTool(
56
65
  ): Promise<ToolResultMessage> {
57
66
  const tool = options.tools.get(toolName);
58
67
  if (!tool) {
59
- const result = buildToolErrorResult(`Tool "${toolName}" not available`);
68
+ const result = await buildFinalToolErrorResult(options, toolName, `Tool "${toolName}" not available`);
60
69
  return createToolResultMessage(toolCallId, toolName, result, true);
61
70
  }
62
71
 
63
72
  options.emitEvent?.({ type: "tool_execution_start", toolCallId, toolName, args });
73
+ const context = options.getToolContext?.();
64
74
 
65
75
  let result: AgentToolResult<unknown>;
66
76
  let isError = false;
@@ -82,16 +92,10 @@ async function executeTool(
82
92
  : undefined;
83
93
 
84
94
  try {
85
- result = await tool.execute(
86
- toolCallId,
87
- args as Record<string, unknown>,
88
- undefined,
89
- onUpdate,
90
- options.getToolContext?.(),
91
- );
95
+ result = await tool.execute(toolCallId, args as Record<string, unknown>, undefined, onUpdate, context);
92
96
  } catch (error) {
93
97
  const message = error instanceof Error ? error.message : String(error);
94
- result = buildToolErrorResult(message);
98
+ result = await buildFinalToolErrorResult(options, toolName, message);
95
99
  isError = true;
96
100
  }
97
101
 
@@ -133,6 +137,7 @@ async function executeDelete(options: CursorExecBridgeOptions, pathArg: string,
133
137
  result = buildToolErrorResult(message);
134
138
  isError = true;
135
139
  }
140
+ result = await finalizeToolResultForDelivery(result, toolName, options.getToolContext?.());
136
141
 
137
142
  options.emitEvent?.({ type: "tool_execution_end", toolCallId, toolName, result, isError });
138
143
  return createToolResultMessage(toolCallId, toolName, result, isError);
@@ -222,7 +227,9 @@ export class CursorExecHandlers implements ICursorExecHandlers {
222
227
  const globPath = `${args.path || "."}/${args.glob}`;
223
228
  return executeTool(this.#optionsForCall(), "find", toolCallId, { paths: [globPath] });
224
229
  }
225
- const result = buildToolErrorResult(
230
+ const result = await buildFinalToolErrorResult(
231
+ this.#optionsForCall(),
232
+ "search",
226
233
  "Cursor grep request rejected: pattern must not be empty. Provide a non-empty search pattern.",
227
234
  );
228
235
  return createToolResultMessage(toolCallId, "search", result, true);
@@ -272,7 +279,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
272
279
  const toolName = "bash";
273
280
  const tool = options.tools.get(toolName);
274
281
  if (!tool) {
275
- const result = buildToolErrorResult(`Tool "${toolName}" not available`);
282
+ const result = await buildFinalToolErrorResult(options, toolName, `Tool "${toolName}" not available`);
276
283
  return createToolResultMessage(toolCallId, toolName, result, true);
277
284
  }
278
285
 
@@ -331,7 +338,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
331
338
  result = await tool.execute(toolCallId, toolArgs, undefined, onUpdate, options.getToolContext?.());
332
339
  } catch (error) {
333
340
  const message = error instanceof Error ? error.message : String(error);
334
- result = buildToolErrorResult(message);
341
+ result = await buildFinalToolErrorResult(options, toolName, message);
335
342
  isError = true;
336
343
  }
337
344
 
@@ -381,7 +388,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
381
388
  if (!tool) {
382
389
  const availableTools = Array.from(options.tools.keys()).filter(name => name.startsWith("mcp__"));
383
390
  const message = formatMcpToolErrorMessage(toolName, availableTools);
384
- const result = buildToolErrorResult(message);
391
+ const result = await buildFinalToolErrorResult(options, toolName, message);
385
392
  return createToolResultMessage(toolCallId, toolName, result, true);
386
393
  }
387
394
 
@@ -295,7 +295,8 @@ The heavyweight gate runs **once per boundary generation**, not once per story a
295
295
  One generation freezes the change set and reviews it exactly once:
296
296
 
297
297
  1. Run implementation verification for the boundary's cumulative change set.
298
- 2. **Freeze the change set.** Compute one immutable `sourceHash` over the reviewed source. Every lane in this generation inspects that same frozen snapshot; a lane verdict carrying a different `sourceHash` is rejected.
298
+ 2. **Freeze the change set.** Use the native coordinator: run `gjc ultragoal review-source freeze --json` before dispatch, then create each lane with `gjc ultragoal review-source dispatch --cohort-id <id> --task-id <id> --lane <cleaner|architect|qa|critic> --rerun-command <command> --json` and pass the returned leader-issued `reviewSource` object to `task`. The runtime derives and persists the canonical source identity; never hand-author or copy a `sourceHash`. Every delivered lane receipt is classified `current`, `stale_review_delivery`, or `invalid_provenance` against the active source, and only current runtime-owned delivery IDs may enter the gate.
299
+
299
300
  3. **Run the cohort lanes on the frozen snapshot** — at most one `cleaner`, one `architect`, and one `qa` lane per generation. They may run in parallel because they share the frozen source; a second architect or QA lane in the same generation is rejected. The `cleaner` lane is the internal ai-slop-cleaner skill fragment run over the frozen change set: a read-only detector that emits an `AI SLOP CLEANUP REPORT`, and it still runs and records a passed/no-op report when there are no relevant edits. Its BLOCKING findings join the cohort findings rather than starting their own fix loop; advisory findings are included in the gate report only and are not written to the Ultragoal ledger.
300
301
  4. Delegate an `architect` review covering all three lanes:
301
302
  - architecture-side: system boundaries, layering, data/control flow, operational risks.
@@ -309,11 +310,13 @@ One generation freezes the change set and reviews it exactly once:
309
310
  - API/package surfaces require a real artifact file or typed receipt whose artifact `kind` contains one of `api`, `package`, `consumer`, `black-box`, or `test-report`; examples: `api-package-test-report`, `package-consumer-report`, `black-box-api-receipt`. Algorithm/math surfaces require a real artifact file or typed receipt whose artifact `kind` contains one of `property`, `boundary`, `edge`, `adversarial`, `failure`, `math`, `algorithm`, or `test-report`; examples: `property-test-report`, `algorithm-boundary-report`. Bare `inlineEvidence` text alone is not sufficient for any surface.
310
311
  - The mandatory **computer-use** red-team suite (`kill-switch-bypass`, `suspended-enforcement`, `permission-revoked`, …) is conditional, not universal: require it only when computer/desktop control is genuinely part of the product surface being dogfooded. For every other product type, prove the change through the matching live surface instead — browser-use automation for web/GUI, bash/CLI live invocation or argv replay for CLI, and real artifacts or typed receipts for API/package/algorithm/math. Editing docs, prompts, or skills that merely mention computer-use does not by itself make the computer-use suite applicable; pick the red-team surface that matches what the change actually ships.
311
312
  7. The executor QA/red-team lane must report a matrix using `executorQa.contractCoverage`, `executorQa.surfaceEvidence`, `executorQa.adversarialCases`, and `executorQa.artifactRefs`. Not-applicable rows are allowed only in `contractCoverage` and `surfaceEvidence`; each `status: "not_applicable"` row requires `contractRef` plus `reason`. `adversarialCases` rows cannot be not-applicable.
312
- 8. **Join before repairing.** Fold all three lane verdicts and the final code review into the strict gate under `iteration.reviewCohort` (`reviewGeneration`, `sourceHash`, `joined: true`, and the three `lanes`). No lane may checkpoint on its own, and no fix work starts until the findings are joined. Clean means `architectReview.architectureStatus`, `architectReview.productStatus`, and `architectReview.codeStatus` are all `"CLEAR"`, `architectReview.recommendation` is `"APPROVE"`, executor QA statuses are `"passed"`, iteration is `"passed"` with `fullRerun: true`, the cohort is joined with every lane clean and hash-bound, every evidence field is non-empty, every required matrix row is present, and every blockers array is empty. `COMMENT`, `WATCH`, `REQUEST CHANGES`, `BLOCK`, missing evidence, missing or shallow matrix rows, plan/code mismatches, or non-empty blockers are non-clean.
313
+ 8. **Join before repairing.** Fold all three lane verdicts and the final code review into the strict gate under `iteration.reviewCohort` (`cohortId`, `reviewGeneration`, runtime-derived `sourceHash`, `joined: true`, and the three lanes with runtime-issued `deliveryId`). No lane may checkpoint on its own, and no fix work starts until the findings are joined. A `stale_review_delivery` stays visible for audit, is excluded from approval and blocker authority, and must be rerun using the rendered command. Clean means `architectReview.architectureStatus`, `architectReview.productStatus`, and `architectReview.codeStatus` are all `"CLEAR"`, `architectReview.recommendation` is `"APPROVE"`, executor QA statuses are `"passed"`, iteration is `"passed"` with `fullRerun: true`, the cohort is joined with every lane clean and receipt-bound to the active snapshot, every evidence field is non-empty, every required matrix row is present, and every blockers array is empty. `COMMENT`, `WATCH`, `REQUEST CHANGES`, `BLOCK`, stale/invalid provenance, missing evidence, missing or shallow matrix rows, plan/code mismatches, or non-empty blockers are non-clean.
313
314
  9. If the joined findings contain any blocker, do **not** checkpoint `complete` and do **not** call `goal({"op":"complete"})`. Record **one consolidated blocker batch** for all findings from the whole cohort instead of one story per lane:
314
315
  ```sh
315
316
  gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve verification blockers" --objective "<blocker-resolution objective>" --evidence "<joined cohort findings>"
316
317
  ```
318
+
319
+ Review-blocker recursion cap (#3613): `record-review-blockers` dedups identical-objective blockers (same trimmed objective + same blocked goal + open status) and bounds the number of unresolved review_blocker descents per blocked goal to **3**. Descents 1..3 may exist; an attempt to create a 4th throws a typed `review_blocker_recursion_cap` terminal handoff (CLI exit 1, operator-visible marker) — never silently auto-completing findings. When the cap fires, record a human pause/escalation or resolve existing blockers before recording more.
317
320
  10. One consolidated fix batch produces exactly **one new generation**. Re-freeze the fixed source as a new `sourceHash`, bump `reviewGeneration`, and set `deltaOnly: true` with `priorGenerationSourceHash` and the `deltaPaths` actually changed. Generation 2+ reviews are **delta-only**: they may not pull in unrelated scope without an explicit `scopeExpansion` carrying `severity`, `novelty`, and `justification`. Repeat until a generation joins clean.
318
321
  11. Only after a generation joins clean, checkpoint the story as complete with a structured quality gate. The terminal critic runs **once** on that final joined generation; when `criticReview.sourceHash` is present it must match the cohort's `sourceHash`. The checkpoint creates a receipt in `ledger.jsonl`; `goals.json.status` alone is not proof. In aggregate mode, the final aggregate receipt must exist before the agent calls `goal({"op":"complete"})` to reconcile the inline UX goal state.
319
322
 
@@ -11,8 +11,8 @@ export interface ExecutorBackendExecOptions {
11
11
  session: ToolSession;
12
12
  deadlineMs: number;
13
13
  reset: boolean;
14
- artifactPath: string | undefined;
15
- artifactId: string | undefined;
14
+ artifactPath?: string;
15
+ artifactId?: string;
16
16
  onChunk: (chunk: string) => void;
17
17
  }
18
18