@vercel/factory 0.0.15 → 0.0.16

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 (214) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/README.md +49 -261
  3. package/dist/agent-routes.d.mts +47 -3
  4. package/dist/agent-routes.mjs +28 -1
  5. package/dist/agent-routes.mjs.map +1 -1
  6. package/dist/api-contracts.d.mts +20 -1
  7. package/dist/api-contracts.mjs +2 -1
  8. package/dist/api-contracts.mjs.map +1 -1
  9. package/dist/api.d.mts +45 -2
  10. package/dist/api.mjs +199 -10
  11. package/dist/api.mjs.map +1 -1
  12. package/dist/approval-contracts.d.mts +6 -0
  13. package/dist/blob/index.d.mts +51 -14
  14. package/dist/blob/index.mjs +26 -10
  15. package/dist/blob/index.mjs.map +1 -1
  16. package/dist/budget.d.mts +7 -0
  17. package/dist/budget.mjs +6 -0
  18. package/dist/budget.mjs.map +1 -1
  19. package/dist/build-factory.d.mts +1 -0
  20. package/dist/change-verification/dispatch.d.mts +16 -3
  21. package/dist/change-verification/dispatch.mjs +45 -7
  22. package/dist/change-verification/dispatch.mjs.map +1 -1
  23. package/dist/change-verification/eve-tool.d.mts +2 -1
  24. package/dist/change-verification/eve-tool.mjs +35 -47
  25. package/dist/change-verification/eve-tool.mjs.map +1 -1
  26. package/dist/change-verification/result.mjs +130 -0
  27. package/dist/change-verification/result.mjs.map +1 -0
  28. package/dist/changes/eve-record-change.d.mts +2 -2
  29. package/dist/changes/eve-record-change.mjs +43 -9
  30. package/dist/changes/eve-record-change.mjs.map +1 -1
  31. package/dist/changes.d.mts +4 -3
  32. package/dist/changes.mjs +2 -2
  33. package/dist/changes.mjs.map +1 -1
  34. package/dist/client-events.d.mts +10 -3
  35. package/dist/client-events.mjs +6 -2
  36. package/dist/client-events.mjs.map +1 -1
  37. package/dist/client-stream.mjs +8 -2
  38. package/dist/client-stream.mjs.map +1 -1
  39. package/dist/client-transcript.mjs +5 -1
  40. package/dist/client-transcript.mjs.map +1 -1
  41. package/dist/client.d.mts +121 -12
  42. package/dist/client.mjs +117 -9
  43. package/dist/client.mjs.map +1 -1
  44. package/dist/code-review/contracts.d.mts +1 -0
  45. package/dist/code-review/eve-post-review.d.mts +4 -4
  46. package/dist/code-review/eve-post-review.mjs +29 -17
  47. package/dist/code-review/eve-post-review.mjs.map +1 -1
  48. package/dist/code-review/eve-review-comments.d.mts +13 -2
  49. package/dist/code-review/eve-review-comments.mjs +45 -11
  50. package/dist/code-review/eve-review-comments.mjs.map +1 -1
  51. package/dist/code-review/github-reporter.d.mts +2 -0
  52. package/dist/code-review/github-reporter.mjs +7 -5
  53. package/dist/code-review/github-reporter.mjs.map +1 -1
  54. package/dist/code-review.d.mts +3 -2
  55. package/dist/deepsec/eve-tool.mjs +3 -1
  56. package/dist/deepsec/eve-tool.mjs.map +1 -1
  57. package/dist/dispatch.d.mts +79 -8
  58. package/dist/dispatch.mjs +68 -9
  59. package/dist/dispatch.mjs.map +1 -1
  60. package/dist/eve/index.d.mts +70 -10
  61. package/dist/eve/index.mjs +93 -22
  62. package/dist/eve/index.mjs.map +1 -1
  63. package/dist/eve/invoke.mjs +24 -11
  64. package/dist/eve/invoke.mjs.map +1 -1
  65. package/dist/eve/session-client.d.mts +122 -4
  66. package/dist/eve/session-client.mjs +127 -13
  67. package/dist/eve/session-client.mjs.map +1 -1
  68. package/dist/eve/task-execution.d.mts +380 -0
  69. package/dist/eve/task-execution.mjs +57 -2
  70. package/dist/eve/task-execution.mjs.map +1 -1
  71. package/dist/eve/task-session.d.mts +44 -2
  72. package/dist/eve/task-session.mjs +44 -2
  73. package/dist/eve/task-session.mjs.map +1 -1
  74. package/dist/eve/transcript.mjs +5 -1
  75. package/dist/eve/transcript.mjs.map +1 -1
  76. package/dist/execution.d.mts +117 -9
  77. package/dist/execution.mjs +76 -6
  78. package/dist/execution.mjs.map +1 -1
  79. package/dist/finding-remediation/admission.d.mts +2 -0
  80. package/dist/finding-remediation/admission.mjs +4 -1
  81. package/dist/finding-remediation/admission.mjs.map +1 -1
  82. package/dist/findings.d.mts +1 -0
  83. package/dist/github-publication.d.mts +1 -0
  84. package/dist/github-publication.mjs +97 -84
  85. package/dist/github-publication.mjs.map +1 -1
  86. package/dist/github-transfer.d.mts +15 -6
  87. package/dist/github-transfer.mjs +214 -66
  88. package/dist/github-transfer.mjs.map +1 -1
  89. package/dist/github.d.mts +51 -11
  90. package/dist/github.mjs +126 -24
  91. package/dist/github.mjs.map +1 -1
  92. package/dist/inbox-activity.d.mts +53 -0
  93. package/dist/inbox-activity.mjs +41 -0
  94. package/dist/inbox-activity.mjs.map +1 -0
  95. package/dist/index.d.mts +3 -1
  96. package/dist/index.mjs +3 -2
  97. package/dist/intake-contracts.d.mts +0 -1
  98. package/dist/integrations/deepsec.d.mts +1 -0
  99. package/dist/integrations/github.d.mts +2 -2
  100. package/dist/integrations/github.mjs +2 -2
  101. package/dist/integrations/slack.d.mts +3 -1
  102. package/dist/integrations/slack.mjs +3 -1
  103. package/dist/integrations/vercel.d.mts +4 -2
  104. package/dist/integrations/vercel.mjs +3 -2
  105. package/dist/merge-resolution/eve-tools.d.mts +1 -0
  106. package/dist/merge-resolution/eve-tools.mjs +7 -2
  107. package/dist/merge-resolution/eve-tools.mjs.map +1 -1
  108. package/dist/model-settings.d.mts +41 -0
  109. package/dist/model-settings.mjs +35 -0
  110. package/dist/model-settings.mjs.map +1 -0
  111. package/dist/planning/reconcile.mjs +6 -0
  112. package/dist/planning/reconcile.mjs.map +1 -1
  113. package/dist/postgres/index.d.mts +43 -2
  114. package/dist/postgres/index.mjs +40 -2
  115. package/dist/postgres/index.mjs.map +1 -1
  116. package/dist/presets/software-development/dispatch.d.mts +4 -1
  117. package/dist/presets/software-development/dispatch.mjs +2 -1
  118. package/dist/presets/software-development/dispatch.mjs.map +1 -1
  119. package/dist/presets/software-development/task-communication.d.mts +1 -0
  120. package/dist/presets/software-development/task-communication.mjs +48 -11
  121. package/dist/presets/software-development/task-communication.mjs.map +1 -1
  122. package/dist/presets/software-development.d.mts +1 -0
  123. package/dist/pull-requests/github-publisher.d.mts +15 -1
  124. package/dist/pull-requests/github-publisher.mjs +61 -1
  125. package/dist/pull-requests/github-publisher.mjs.map +1 -1
  126. package/dist/pull-requests.d.mts +1 -0
  127. package/dist/sandbox/index.d.mts +1 -0
  128. package/dist/schema/agent-route.d.mts +19 -1
  129. package/dist/schema/agent-route.mjs +19 -1
  130. package/dist/schema/agent-route.mjs.map +1 -1
  131. package/dist/schema/factory-config.d.mts +27 -0
  132. package/dist/schema/factory-config.mjs +33 -3
  133. package/dist/schema/factory-config.mjs.map +1 -1
  134. package/dist/schema/repository.d.mts +4 -0
  135. package/dist/schema/repository.mjs +5 -1
  136. package/dist/schema/repository.mjs.map +1 -1
  137. package/dist/schema/session.d.mts +1 -0
  138. package/dist/schema/session.mjs +1 -0
  139. package/dist/schema/session.mjs.map +1 -1
  140. package/dist/schema/slack-pr-notifications.d.mts +12 -0
  141. package/dist/schema/slack-pr-notifications.mjs +11 -0
  142. package/dist/schema/slack-pr-notifications.mjs.map +1 -0
  143. package/dist/schema/task-graph.d.mts +39 -0
  144. package/dist/schema/task.d.mts +1 -0
  145. package/dist/schema/task.mjs +2 -1
  146. package/dist/schema/task.mjs.map +1 -1
  147. package/dist/schema/transcript.d.mts +6 -0
  148. package/dist/schema/transcript.mjs +2 -1
  149. package/dist/schema/transcript.mjs.map +1 -1
  150. package/dist/schema/work.d.mts +52 -3
  151. package/dist/schema/work.mjs.map +1 -1
  152. package/dist/session-previews.d.mts +76 -0
  153. package/dist/session-previews.mjs +55 -0
  154. package/dist/session-previews.mjs.map +1 -0
  155. package/dist/session-review.d.mts +120 -0
  156. package/dist/session-review.mjs +79 -0
  157. package/dist/session-review.mjs.map +1 -0
  158. package/dist/signal-triage.mjs +1 -1
  159. package/dist/signals.d.mts +1 -0
  160. package/dist/stall.d.mts +4 -1
  161. package/dist/stall.mjs +6 -2
  162. package/dist/stall.mjs.map +1 -1
  163. package/dist/store/driver.d.mts +1 -1
  164. package/dist/store/driver.mjs.map +1 -1
  165. package/dist/store/engine.d.mts +206 -8
  166. package/dist/store/engine.mjs +147 -13
  167. package/dist/store/engine.mjs.map +1 -1
  168. package/dist/store/memory.d.mts +18 -1
  169. package/dist/store/memory.mjs +18 -1
  170. package/dist/store/memory.mjs.map +1 -1
  171. package/dist/store/slack-pr-notifications.d.mts +44 -0
  172. package/dist/store/slack-pr-notifications.mjs +121 -0
  173. package/dist/store/slack-pr-notifications.mjs.map +1 -0
  174. package/dist/store/task-work.d.mts +121 -6
  175. package/dist/store/task-work.mjs +7 -4
  176. package/dist/store/task-work.mjs.map +1 -1
  177. package/dist/sweep.d.mts +28 -6
  178. package/dist/sweep.mjs +34 -6
  179. package/dist/sweep.mjs.map +1 -1
  180. package/dist/task-graph-view.d.mts +3 -0
  181. package/dist/tasks.d.mts +3 -3
  182. package/dist/tasks.mjs +3 -3
  183. package/dist/vercel-git.d.mts +35 -3
  184. package/dist/vercel-git.mjs +265 -33
  185. package/dist/vercel-git.mjs.map +1 -1
  186. package/dist/vercel-github-api.d.mts +103 -0
  187. package/dist/vercel-github-api.mjs +363 -0
  188. package/dist/vercel-github-api.mjs.map +1 -0
  189. package/dist/vercel.d.mts +3 -2
  190. package/dist/vercel.mjs +3 -2
  191. package/dist/vercel.mjs.map +1 -1
  192. package/dist/work-triage.d.mts +1 -0
  193. package/dist/workflows.d.mts +102 -4
  194. package/dist/workflows.mjs +55 -2
  195. package/dist/workflows.mjs.map +1 -1
  196. package/dist/workspace-files-git.d.mts +15 -0
  197. package/dist/workspace-files-git.mjs +61 -0
  198. package/dist/workspace-files-git.mjs.map +1 -0
  199. package/dist/workspace-files.d.mts +107 -0
  200. package/dist/workspace-files.mjs +74 -0
  201. package/dist/workspace-files.mjs.map +1 -0
  202. package/docs/getting-started.md +104 -0
  203. package/docs/index.md +100 -0
  204. package/docs/recipes/cancellation.md +215 -0
  205. package/docs/recipes/custom-workflow.md +153 -0
  206. package/docs/recipes/dependent-tasks.md +207 -0
  207. package/docs/recipes/eve-agent.md +277 -0
  208. package/docs/recipes/human-input.md +204 -0
  209. package/docs/recipes/persistence-recovery.md +268 -0
  210. package/docs/recipes/retry-recovery.md +241 -0
  211. package/docs/recipes/task-messaging.md +215 -0
  212. package/docs/recipes/typed-eve-result.md +161 -0
  213. package/docs/runtime-integration.md +137 -0
  214. package/package.json +17 -6
@@ -1,8 +1,10 @@
1
1
  import { changeIdSchema, taskIdSchema } from "../schema/id.mjs";
2
- import { requireTaskExecution } from "../eve/task-execution.mjs";
2
+ import { requireTaskExecution, requireTaskExecutionIdentity } from "../eve/task-execution.mjs";
3
3
  import { changeEvidenceSchema, changeLevelSchema } from "../schema/change.mjs";
4
+ import { applyVerificationReport, saveVerificationReport, verificationDelivery, verificationOutputSchema, verificationParentAttempt } from "./result.mjs";
4
5
  import { z } from "zod";
5
6
  import { defineTool } from "eve/tools";
7
+ import { isDeepStrictEqual } from "node:util";
6
8
  //#region src/change-verification/eve-tool.ts
7
9
  /**
8
10
  * The verifier's outcome tool. Writes evidence onto the change and moves it
@@ -27,7 +29,33 @@ function createRecordVerificationTool(stores, options = {}) {
27
29
  summary: z.string().min(1).max(2e3).describe("What you checked and what you found.")
28
30
  }),
29
31
  async execute({ taskId, changeId, verdict, evidence, confidence, summary }, ctx) {
30
- const task = await requireTaskExecution(stores, ctx, taskId);
32
+ const task = await requireTaskExecutionIdentity(stores, ctx, taskId);
33
+ const output = verificationOutputSchema.parse({
34
+ changeId,
35
+ verdict,
36
+ evidence,
37
+ confidence,
38
+ summary
39
+ });
40
+ if (![
41
+ "running",
42
+ "succeeded",
43
+ "failed"
44
+ ].includes(task.state)) throw new Error("Task execution is missing, inactive, or replaced");
45
+ const saved = await stores.signals.getByDelivery(verificationDelivery(task));
46
+ if (saved) {
47
+ const recorded = z.object({ output: verificationOutputSchema }).parse(saved.payload).output;
48
+ if (!isDeepStrictEqual(recorded, output)) throw new Error("This verification attempt already recorded a different result");
49
+ const result = await applyVerificationReport(stores, saved);
50
+ if (result.recorded) await options.presentResult?.({
51
+ taskId,
52
+ changeId,
53
+ verdict,
54
+ summary
55
+ });
56
+ return result;
57
+ }
58
+ await requireTaskExecution(stores, ctx, taskId);
31
59
  const change = await stores.changes.get(changeId);
32
60
  if (change === null) return {
33
61
  recorded: false,
@@ -38,60 +66,20 @@ function createRecordVerificationTool(stores, options = {}) {
38
66
  reason: `change is ${change.state}, not open for verification; nothing to record`
39
67
  };
40
68
  const parent = task.parentTaskId === void 0 ? null : await stores.tasks.get(task.parentTaskId);
41
- if (task.changeId !== changeId || parent === null || parent.changeId !== changeId || parent.state !== "verifying" || task.rootTaskId !== parent.rootTaskId || task.repositoryIds.length !== 1 || task.repositoryIds[0] !== change.repositoryId || parent.repositoryIds.length !== 1 || parent.repositoryIds[0] !== change.repositoryId) return {
69
+ if (task.changeId !== changeId || parent === null || parent.changeId !== changeId || parent.state !== "verifying" || parent.attempt !== verificationParentAttempt(task) || task.rootTaskId !== parent.rootTaskId || task.repositoryIds.length !== 1 || task.repositoryIds[0] !== change.repositoryId || parent.repositoryIds.length !== 1 || parent.repositoryIds[0] !== change.repositoryId) return {
42
70
  recorded: false,
43
71
  reason: "Verification Task, parent, Change, and repository must match"
44
72
  };
45
73
  await requireTaskExecution(stores, ctx, taskId);
46
- let updatedChange = change;
47
- if (verdict === "pass" && change.state === "verifying") updatedChange = await stores.changes.transition(changeId, "ready", {
48
- patch: {
49
- evidence,
50
- confidence
51
- },
52
- reason: summary
53
- });
54
- else if (verdict === "fail" && change.state === "ready") updatedChange = await stores.changes.transition(changeId, "verifying", { reason: summary });
55
- const output = {
56
- changeId,
57
- verdict,
58
- evidence,
59
- confidence,
60
- summary
61
- };
62
- if (verdict === "pass") await stores.tasks.transition(taskId, "succeeded", {
63
- expectFrom: "running",
64
- expectAttempt: task.attempt,
65
- expectExecution: task.execution,
66
- output
67
- });
68
- else await stores.tasks.transition(taskId, "failed", {
69
- expectFrom: "running",
70
- expectAttempt: task.attempt,
71
- expectExecution: task.execution,
72
- reason: summary
73
- });
74
- await (verdict === "pass" ? stores.tasks.transition(parent.id, "succeeded", {
75
- expectFrom: "verifying",
76
- expectAttempt: parent.attempt,
77
- output
78
- }) : stores.tasks.transition(parent.id, "failed", {
79
- expectFrom: "verifying",
80
- expectAttempt: parent.attempt,
81
- reason: `verification failed: ${summary}`
82
- })).catch(() => void 0);
83
- await options.presentResult?.({
74
+ const report = await saveVerificationReport(stores, task, output);
75
+ const result = await applyVerificationReport(stores, report);
76
+ if (result.recorded) await options.presentResult?.({
84
77
  taskId,
85
78
  changeId,
86
79
  verdict,
87
80
  summary
88
81
  });
89
- return {
90
- recorded: true,
91
- verdict,
92
- changeState: updatedChange.state,
93
- taskState: verdict === "pass" ? "succeeded" : "failed"
94
- };
82
+ return result;
95
83
  }
96
84
  });
97
85
  }
@@ -1 +1 @@
1
- {"version":3,"file":"eve-tool.mjs","names":[],"sources":["../../src/change-verification/eve-tool.ts"],"sourcesContent":["import { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport {\n changeEvidenceSchema,\n changeLevelSchema,\n type ChangeEvidence,\n type ChangeLevel,\n type ChangeState,\n} from \"../schema/change\";\nimport { changeIdSchema, taskIdSchema } from \"../schema/id\";\nimport type { ChangeId, TaskId } from \"../schema/id\";\nimport type { FactoryStores } from \"../store/engine\";\nimport { requireTaskExecution } from \"../eve/task-execution\";\n\n/** Change and Task capabilities required by the verification-result tool. */\nexport type RecordVerificationToolStores = Pick<FactoryStores, \"changes\" | \"tasks\">;\n\n/** Verified outcome presented after its Task and Change updates commit. */\nexport interface PresentVerificationResultInput {\n taskId: TaskId;\n changeId: ChangeId;\n verdict: \"pass\" | \"fail\";\n summary: string;\n}\n\n/** Configures presentation after a verification verdict has been durably recorded. */\nexport interface RecordVerificationToolOptions {\n presentResult?: (input: PresentVerificationResultInput) => Promise<void>;\n}\n\n/** Evidence and verdict supplied by the verifier for one Change. */\nexport interface RecordVerificationInput {\n taskId: TaskId;\n changeId: ChangeId;\n verdict: \"pass\" | \"fail\";\n evidence: ChangeEvidence;\n confidence: ChangeLevel;\n summary: string;\n}\n\n/** Bounded refusal returned when a verification result cannot be associated safely. */\nexport interface RecordVerificationRefusal {\n readonly recorded: false;\n readonly reason: string;\n}\n\n/** Durable lifecycle state returned after a verification verdict is recorded. */\nexport interface RecordVerificationSuccess {\n readonly recorded: true;\n readonly verdict: \"pass\" | \"fail\";\n readonly changeState: ChangeState;\n readonly taskState: \"succeeded\" | \"failed\";\n}\n\n/** Recorded verification state or bounded association refusal. */\nexport type RecordVerificationResult = RecordVerificationRefusal | RecordVerificationSuccess;\n\n/** Eve outcome tool that records evidence and a verdict for one Change. */\nexport interface RecordVerificationTool extends ToolDefinition<\n RecordVerificationInput,\n RecordVerificationResult\n> {\n execute(input: RecordVerificationInput, ctx: ToolContext): Promise<RecordVerificationResult>;\n}\n\n/**\n * The verifier's outcome tool. Writes evidence onto the change and moves it\n * to ready only when the verdict is pass; a failed verdict leaves the change\n * verifying with the evidence recorded, and the verification task itself\n * ends with a receipt either way. The change's own code_change task follows\n * the verdict too: succeeded on pass, failed on fail with the verifier's\n * summary as the reason, so the ledger never shows a verified-and-rejected fix\n * as still in progress. Fail closed: the agent cannot mark a change ready\n * without supplying every evidence field. Requires the authenticated current\n * Task execution and matching parent, Change, and repository associations.\n */\nexport function createRecordVerificationTool(\n stores: RecordVerificationToolStores,\n options: RecordVerificationToolOptions = {},\n): RecordVerificationTool {\n return defineTool({\n description:\n \"Record the outcome of a verification task: the evidence you gathered and whether the change passed. Pass moves the change to ready for human review; fail keeps it out of review.\",\n inputSchema: z.object({\n taskId: taskIdSchema.describe(\"Your verification task.\"),\n changeId: changeIdSchema.describe(\"The change you verified.\"),\n verdict: z.enum([\"pass\", \"fail\"]),\n evidence: changeEvidenceSchema,\n confidence: changeLevelSchema.describe(\"How certain you are in the verdict.\"),\n summary: z.string().min(1).max(2000).describe(\"What you checked and what you found.\"),\n }),\n async execute(\n { taskId, changeId, verdict, evidence, confidence, summary },\n ctx,\n ): Promise<RecordVerificationResult> {\n const task = await requireTaskExecution(stores, ctx, taskId);\n const change = await stores.changes.get(changeId);\n if (change === null) {\n return { recorded: false as const, reason: `change ${changeId} does not exist` };\n }\n if (change.state !== \"verifying\" && change.state !== \"ready\") {\n return {\n recorded: false as const,\n reason: `change is ${change.state}, not open for verification; nothing to record`,\n };\n }\n\n const parent =\n task.parentTaskId === undefined ? null : await stores.tasks.get(task.parentTaskId);\n if (\n task.changeId !== changeId ||\n parent === null ||\n parent.changeId !== changeId ||\n parent.state !== \"verifying\" ||\n task.rootTaskId !== parent.rootTaskId ||\n task.repositoryIds.length !== 1 ||\n task.repositoryIds[0] !== change.repositoryId ||\n parent.repositoryIds.length !== 1 ||\n parent.repositoryIds[0] !== change.repositoryId\n ) {\n return {\n recorded: false as const,\n reason: \"Verification Task, parent, Change, and repository must match\",\n };\n }\n\n await requireTaskExecution(stores, ctx, taskId);\n\n let updatedChange = change;\n if (verdict === \"pass\" && change.state === \"verifying\") {\n updatedChange = await stores.changes.transition(changeId, \"ready\", {\n patch: { evidence, confidence },\n reason: summary,\n });\n } else if (verdict === \"fail\" && change.state === \"ready\") {\n updatedChange = await stores.changes.transition(changeId, \"verifying\", {\n reason: summary,\n });\n }\n\n const output = { changeId, verdict, evidence, confidence, summary };\n if (verdict === \"pass\") {\n await stores.tasks.transition(taskId, \"succeeded\", {\n expectFrom: \"running\",\n expectAttempt: task.attempt,\n expectExecution: task.execution,\n output,\n });\n } else {\n await stores.tasks.transition(taskId, \"failed\", {\n expectFrom: \"running\",\n expectAttempt: task.attempt,\n expectExecution: task.execution,\n reason: summary,\n });\n }\n\n await (\n verdict === \"pass\"\n ? stores.tasks.transition(parent.id, \"succeeded\", {\n expectFrom: \"verifying\",\n expectAttempt: parent.attempt,\n output,\n })\n : stores.tasks.transition(parent.id, \"failed\", {\n expectFrom: \"verifying\",\n expectAttempt: parent.attempt,\n reason: `verification failed: ${summary}`,\n })\n ).catch(() => undefined);\n\n await options.presentResult?.({ taskId, changeId, verdict, summary });\n\n return {\n recorded: true as const,\n verdict,\n changeState: updatedChange.state,\n taskState: verdict === \"pass\" ? (\"succeeded\" as const) : (\"failed\" as const),\n };\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4EA,SAAgB,6BACd,QACA,UAAyC,CAAC,GAClB;CACxB,OAAO,WAAW;EAChB,aACE;EACF,aAAa,EAAE,OAAO;GACpB,QAAQ,aAAa,SAAS,yBAAyB;GACvD,UAAU,eAAe,SAAS,0BAA0B;GAC5D,SAAS,EAAE,KAAK,CAAC,QAAQ,MAAM,CAAC;GAChC,UAAU;GACV,YAAY,kBAAkB,SAAS,qCAAqC;GAC5E,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,SAAS,sCAAsC;EACtF,CAAC;EACD,MAAM,QACJ,EAAE,QAAQ,UAAU,SAAS,UAAU,YAAY,WACnD,KACmC;GACnC,MAAM,OAAO,MAAM,qBAAqB,QAAQ,KAAK,MAAM;GAC3D,MAAM,SAAS,MAAM,OAAO,QAAQ,IAAI,QAAQ;GAChD,IAAI,WAAW,MACb,OAAO;IAAE,UAAU;IAAgB,QAAQ,UAAU,SAAS;GAAiB;GAEjF,IAAI,OAAO,UAAU,eAAe,OAAO,UAAU,SACnD,OAAO;IACL,UAAU;IACV,QAAQ,aAAa,OAAO,MAAM;GACpC;GAGF,MAAM,SACJ,KAAK,iBAAiB,KAAA,IAAY,OAAO,MAAM,OAAO,MAAM,IAAI,KAAK,YAAY;GACnF,IACE,KAAK,aAAa,YAClB,WAAW,QACX,OAAO,aAAa,YACpB,OAAO,UAAU,eACjB,KAAK,eAAe,OAAO,cAC3B,KAAK,cAAc,WAAW,KAC9B,KAAK,cAAc,OAAO,OAAO,gBACjC,OAAO,cAAc,WAAW,KAChC,OAAO,cAAc,OAAO,OAAO,cAEnC,OAAO;IACL,UAAU;IACV,QAAQ;GACV;GAGF,MAAM,qBAAqB,QAAQ,KAAK,MAAM;GAE9C,IAAI,gBAAgB;GACpB,IAAI,YAAY,UAAU,OAAO,UAAU,aACzC,gBAAgB,MAAM,OAAO,QAAQ,WAAW,UAAU,SAAS;IACjE,OAAO;KAAE;KAAU;IAAW;IAC9B,QAAQ;GACV,CAAC;QACI,IAAI,YAAY,UAAU,OAAO,UAAU,SAChD,gBAAgB,MAAM,OAAO,QAAQ,WAAW,UAAU,aAAa,EACrE,QAAQ,QACV,CAAC;GAGH,MAAM,SAAS;IAAE;IAAU;IAAS;IAAU;IAAY;GAAQ;GAClE,IAAI,YAAY,QACd,MAAM,OAAO,MAAM,WAAW,QAAQ,aAAa;IACjD,YAAY;IACZ,eAAe,KAAK;IACpB,iBAAiB,KAAK;IACtB;GACF,CAAC;QAED,MAAM,OAAO,MAAM,WAAW,QAAQ,UAAU;IAC9C,YAAY;IACZ,eAAe,KAAK;IACpB,iBAAiB,KAAK;IACtB,QAAQ;GACV,CAAC;GAGH,OACE,YAAY,SACR,OAAO,MAAM,WAAW,OAAO,IAAI,aAAa;IAC9C,YAAY;IACZ,eAAe,OAAO;IACtB;GACF,CAAC,IACD,OAAO,MAAM,WAAW,OAAO,IAAI,UAAU;IAC3C,YAAY;IACZ,eAAe,OAAO;IACtB,QAAQ,wBAAwB;GAClC,CAAC,EAAA,CACL,YAAY,KAAA,CAAS;GAEvB,MAAM,QAAQ,gBAAgB;IAAE;IAAQ;IAAU;IAAS;GAAQ,CAAC;GAEpE,OAAO;IACL,UAAU;IACV;IACA,aAAa,cAAc;IAC3B,WAAW,YAAY,SAAU,cAAyB;GAC5D;EACF;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"eve-tool.mjs","names":[],"sources":["../../src/change-verification/eve-tool.ts"],"sourcesContent":["import { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport {\n changeEvidenceSchema,\n changeLevelSchema,\n type ChangeEvidence,\n type ChangeLevel,\n type ChangeState,\n} from \"../schema/change\";\nimport { changeIdSchema, taskIdSchema } from \"../schema/id\";\nimport type { ChangeId, TaskId } from \"../schema/id\";\nimport type { FactoryStores } from \"../store/engine\";\nimport { requireTaskExecution, requireTaskExecutionIdentity } from \"../eve/task-execution\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport {\n applyVerificationReport,\n saveVerificationReport,\n verificationDelivery,\n verificationOutputSchema,\n verificationParentAttempt,\n} from \"./result\";\n\n/** Change and Task capabilities required by the verification-result tool. */\nexport type RecordVerificationToolStores = Pick<FactoryStores, \"changes\" | \"tasks\" | \"signals\">;\n\n/** Verified outcome presented after its Task and Change updates commit. */\nexport interface PresentVerificationResultInput {\n taskId: TaskId;\n changeId: ChangeId;\n verdict: \"pass\" | \"fail\";\n summary: string;\n}\n\n/** Configures presentation after a verification verdict has been durably recorded. */\nexport interface RecordVerificationToolOptions {\n /** Idempotent delivery, retried with the saved verdict after interrupted completion. */\n presentResult?: (input: PresentVerificationResultInput) => Promise<void>;\n}\n\n/** Evidence and verdict supplied by the verifier for one Change. */\nexport interface RecordVerificationInput {\n taskId: TaskId;\n changeId: ChangeId;\n verdict: \"pass\" | \"fail\";\n evidence: ChangeEvidence;\n confidence: ChangeLevel;\n summary: string;\n}\n\n/** Bounded refusal returned when a verification result cannot be associated safely. */\nexport interface RecordVerificationRefusal {\n readonly recorded: false;\n readonly reason: string;\n}\n\n/** Durable lifecycle state returned after a verification verdict is recorded. */\nexport interface RecordVerificationSuccess {\n readonly recorded: true;\n readonly verdict: \"pass\" | \"fail\";\n readonly changeState: ChangeState;\n readonly taskState: \"succeeded\" | \"failed\";\n}\n\n/** Recorded verification state or bounded association refusal. */\nexport type RecordVerificationResult = RecordVerificationRefusal | RecordVerificationSuccess;\n\n/** Eve outcome tool that records evidence and a verdict for one Change. */\nexport interface RecordVerificationTool extends ToolDefinition<\n RecordVerificationInput,\n RecordVerificationResult\n> {\n execute(input: RecordVerificationInput, ctx: ToolContext): Promise<RecordVerificationResult>;\n}\n\n/**\n * The verifier's outcome tool. Writes evidence onto the change and moves it\n * to ready only when the verdict is pass; a failed verdict leaves the change\n * verifying with the evidence recorded, and the verification task itself\n * ends with a receipt either way. The change's own code_change task follows\n * the verdict too: succeeded on pass, failed on fail with the verifier's\n * summary as the reason, so the ledger never shows a verified-and-rejected fix\n * as still in progress. Fail closed: the agent cannot mark a change ready\n * without supplying every evidence field. Requires the authenticated current\n * Task execution and matching parent, Change, and repository associations.\n */\nexport function createRecordVerificationTool(\n stores: RecordVerificationToolStores,\n options: RecordVerificationToolOptions = {},\n): RecordVerificationTool {\n return defineTool({\n description:\n \"Record the outcome of a verification task: the evidence you gathered and whether the change passed. Pass moves the change to ready for human review; fail keeps it out of review.\",\n inputSchema: z.object({\n taskId: taskIdSchema.describe(\"Your verification task.\"),\n changeId: changeIdSchema.describe(\"The change you verified.\"),\n verdict: z.enum([\"pass\", \"fail\"]),\n evidence: changeEvidenceSchema,\n confidence: changeLevelSchema.describe(\"How certain you are in the verdict.\"),\n summary: z.string().min(1).max(2000).describe(\"What you checked and what you found.\"),\n }),\n async execute(\n { taskId, changeId, verdict, evidence, confidence, summary },\n ctx,\n ): Promise<RecordVerificationResult> {\n const task = await requireTaskExecutionIdentity(stores, ctx, taskId);\n const output = verificationOutputSchema.parse({\n changeId,\n verdict,\n evidence,\n confidence,\n summary,\n });\n if (![\"running\", \"succeeded\", \"failed\"].includes(task.state))\n throw new Error(\"Task execution is missing, inactive, or replaced\");\n const saved = await stores.signals.getByDelivery(verificationDelivery(task));\n if (saved) {\n const recorded = z.object({ output: verificationOutputSchema }).parse(saved.payload).output;\n if (!isDeepStrictEqual(recorded, output))\n throw new Error(\"This verification attempt already recorded a different result\");\n const result = await applyVerificationReport(stores, saved);\n if (result.recorded) await options.presentResult?.({ taskId, changeId, verdict, summary });\n return result;\n }\n await requireTaskExecution(stores, ctx, taskId);\n const change = await stores.changes.get(changeId);\n if (change === null) {\n return { recorded: false as const, reason: `change ${changeId} does not exist` };\n }\n if (change.state !== \"verifying\" && change.state !== \"ready\") {\n return {\n recorded: false as const,\n reason: `change is ${change.state}, not open for verification; nothing to record`,\n };\n }\n\n const parent =\n task.parentTaskId === undefined ? null : await stores.tasks.get(task.parentTaskId);\n if (\n task.changeId !== changeId ||\n parent === null ||\n parent.changeId !== changeId ||\n parent.state !== \"verifying\" ||\n parent.attempt !== verificationParentAttempt(task) ||\n task.rootTaskId !== parent.rootTaskId ||\n task.repositoryIds.length !== 1 ||\n task.repositoryIds[0] !== change.repositoryId ||\n parent.repositoryIds.length !== 1 ||\n parent.repositoryIds[0] !== change.repositoryId\n ) {\n return {\n recorded: false as const,\n reason: \"Verification Task, parent, Change, and repository must match\",\n };\n }\n\n await requireTaskExecution(stores, ctx, taskId);\n\n const report = await saveVerificationReport(stores, task, output);\n const result = await applyVerificationReport(stores, report);\n if (result.recorded) await options.presentResult?.({ taskId, changeId, verdict, summary });\n return result;\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqFA,SAAgB,6BACd,QACA,UAAyC,CAAC,GAClB;CACxB,OAAO,WAAW;EAChB,aACE;EACF,aAAa,EAAE,OAAO;GACpB,QAAQ,aAAa,SAAS,yBAAyB;GACvD,UAAU,eAAe,SAAS,0BAA0B;GAC5D,SAAS,EAAE,KAAK,CAAC,QAAQ,MAAM,CAAC;GAChC,UAAU;GACV,YAAY,kBAAkB,SAAS,qCAAqC;GAC5E,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,SAAS,sCAAsC;EACtF,CAAC;EACD,MAAM,QACJ,EAAE,QAAQ,UAAU,SAAS,UAAU,YAAY,WACnD,KACmC;GACnC,MAAM,OAAO,MAAM,6BAA6B,QAAQ,KAAK,MAAM;GACnE,MAAM,SAAS,yBAAyB,MAAM;IAC5C;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,IAAI,CAAC;IAAC;IAAW;IAAa;GAAQ,CAAC,CAAC,SAAS,KAAK,KAAK,GACzD,MAAM,IAAI,MAAM,kDAAkD;GACpE,MAAM,QAAQ,MAAM,OAAO,QAAQ,cAAc,qBAAqB,IAAI,CAAC;GAC3E,IAAI,OAAO;IACT,MAAM,WAAW,EAAE,OAAO,EAAE,QAAQ,yBAAyB,CAAC,CAAC,CAAC,MAAM,MAAM,OAAO,CAAC,CAAC;IACrF,IAAI,CAAC,kBAAkB,UAAU,MAAM,GACrC,MAAM,IAAI,MAAM,+DAA+D;IACjF,MAAM,SAAS,MAAM,wBAAwB,QAAQ,KAAK;IAC1D,IAAI,OAAO,UAAU,MAAM,QAAQ,gBAAgB;KAAE;KAAQ;KAAU;KAAS;IAAQ,CAAC;IACzF,OAAO;GACT;GACA,MAAM,qBAAqB,QAAQ,KAAK,MAAM;GAC9C,MAAM,SAAS,MAAM,OAAO,QAAQ,IAAI,QAAQ;GAChD,IAAI,WAAW,MACb,OAAO;IAAE,UAAU;IAAgB,QAAQ,UAAU,SAAS;GAAiB;GAEjF,IAAI,OAAO,UAAU,eAAe,OAAO,UAAU,SACnD,OAAO;IACL,UAAU;IACV,QAAQ,aAAa,OAAO,MAAM;GACpC;GAGF,MAAM,SACJ,KAAK,iBAAiB,KAAA,IAAY,OAAO,MAAM,OAAO,MAAM,IAAI,KAAK,YAAY;GACnF,IACE,KAAK,aAAa,YAClB,WAAW,QACX,OAAO,aAAa,YACpB,OAAO,UAAU,eACjB,OAAO,YAAY,0BAA0B,IAAI,KACjD,KAAK,eAAe,OAAO,cAC3B,KAAK,cAAc,WAAW,KAC9B,KAAK,cAAc,OAAO,OAAO,gBACjC,OAAO,cAAc,WAAW,KAChC,OAAO,cAAc,OAAO,OAAO,cAEnC,OAAO;IACL,UAAU;IACV,QAAQ;GACV;GAGF,MAAM,qBAAqB,QAAQ,KAAK,MAAM;GAE9C,MAAM,SAAS,MAAM,uBAAuB,QAAQ,MAAM,MAAM;GAChE,MAAM,SAAS,MAAM,wBAAwB,QAAQ,MAAM;GAC3D,IAAI,OAAO,UAAU,MAAM,QAAQ,gBAAgB;IAAE;IAAQ;IAAU;IAAS;GAAQ,CAAC;GACzF,OAAO;EACT;CACF,CAAC;AACH"}
@@ -0,0 +1,130 @@
1
+ import { changeIdSchema, taskIdSchema } from "../schema/id.mjs";
2
+ import { executionReferenceSchema } from "../schema/task.mjs";
3
+ import { changeEvidenceSchema, changeLevelSchema } from "../schema/change.mjs";
4
+ import { z } from "zod";
5
+ import { isDeepStrictEqual } from "node:util";
6
+ //#region src/change-verification/result.ts
7
+ const verificationOutputSchema = z.object({
8
+ changeId: changeIdSchema,
9
+ verdict: z.enum(["pass", "fail"]),
10
+ evidence: changeEvidenceSchema,
11
+ confidence: changeLevelSchema,
12
+ summary: z.string().min(1).max(2e3)
13
+ });
14
+ const verificationReportSchema = z.object({
15
+ taskId: taskIdSchema,
16
+ attempt: z.number().int().positive(),
17
+ execution: executionReferenceSchema,
18
+ parentTaskId: taskIdSchema,
19
+ parentAttempt: z.number().int().positive(),
20
+ output: verificationOutputSchema
21
+ });
22
+ function verificationParentAttempt(task) {
23
+ return z.object({ parentAttempt: z.number().int().positive().default(1) }).parse(task.work.input).parentAttempt;
24
+ }
25
+ function verificationDelivery(task) {
26
+ return {
27
+ source: "change_verification",
28
+ deliveryId: `${task.id}:${task.attempt}`
29
+ };
30
+ }
31
+ async function saveVerificationReport(stores, task, output) {
32
+ const payload = verificationReportSchema.parse({
33
+ taskId: task.id,
34
+ attempt: task.attempt,
35
+ execution: task.execution,
36
+ parentTaskId: task.parentTaskId,
37
+ parentAttempt: verificationParentAttempt(task),
38
+ output
39
+ });
40
+ const report = await stores.signals.create({
41
+ ...verificationDelivery(task),
42
+ repositoryId: task.repositoryIds[0],
43
+ replyTo: task.replyTo,
44
+ payload
45
+ });
46
+ if (report.repositoryId !== task.repositoryIds[0] || !isDeepStrictEqual(report.payload, payload)) throw new Error("This verification attempt already recorded a different result");
47
+ return report;
48
+ }
49
+ async function applyVerificationReport(stores, report) {
50
+ const saved = verificationReportSchema.parse(report.payload);
51
+ const task = await stores.tasks.get(saved.taskId);
52
+ const parent = await stores.tasks.get(saved.parentTaskId);
53
+ const { output } = saved;
54
+ const terminal = output.verdict === "pass" ? "succeeded" : "failed";
55
+ const change = await stores.changes.get(output.changeId);
56
+ if (report.source !== "change_verification" || !task || !parent || !change || report.deliveryId !== verificationDelivery(task).deliveryId || task.attempt !== saved.attempt || !isDeepStrictEqual(task.execution, saved.execution) || task.parentTaskId !== parent.id || verificationParentAttempt(task) !== saved.parentAttempt || parent.attempt !== saved.parentAttempt || task.rootTaskId !== parent.rootTaskId || task.changeId !== change.id || parent.changeId !== change.id || task.repositoryIds.length !== 1 || parent.repositoryIds.length !== 1 || task.repositoryIds[0] !== change.repositoryId || parent.repositoryIds[0] !== change.repositoryId || report.repositoryId !== change.repositoryId || task.state !== "running" && !(task.state === terminal && task.reportRef === report.id) || parent.state !== "verifying" && !(parent.state === terminal && parent.reportRef === report.id)) return {
57
+ recorded: false,
58
+ reason: "Verification result no longer belongs to the current Task and producer attempt"
59
+ };
60
+ let updatedChange = change;
61
+ if (task.state === "running") {
62
+ if (parent.state !== "verifying" || !["verifying", "ready"].includes(change.state)) return {
63
+ recorded: false,
64
+ reason: "Change and producer are no longer open for verification"
65
+ };
66
+ const target = output.verdict === "pass" ? "ready" : "verifying";
67
+ if (change.state !== target) updatedChange = await stores.changes.transition(change.id, target, {
68
+ reason: output.summary,
69
+ patch: {
70
+ evidence: output.evidence,
71
+ confidence: output.confidence
72
+ }
73
+ });
74
+ if (!isDeepStrictEqual(updatedChange.evidence, output.evidence) || updatedChange.confidence !== output.confidence) updatedChange = await stores.changes.updateEvidence(change.id, {
75
+ evidence: output.evidence,
76
+ confidence: output.confidence,
77
+ expected: updatedChange
78
+ });
79
+ const fence = {
80
+ expectFrom: "running",
81
+ expectAttempt: saved.attempt,
82
+ expectExecution: saved.execution,
83
+ patch: { reportRef: report.id },
84
+ phase: "verification"
85
+ };
86
+ if (output.verdict === "pass") await stores.tasks.transition(task.id, "succeeded", {
87
+ ...fence,
88
+ output
89
+ });
90
+ else await stores.tasks.transition(task.id, "failed", {
91
+ ...fence,
92
+ reason: output.summary
93
+ });
94
+ }
95
+ if (parent.state === "verifying") {
96
+ const fence = {
97
+ expectFrom: "verifying",
98
+ expectAttempt: saved.parentAttempt,
99
+ expectExecution: parent.execution,
100
+ patch: { reportRef: report.id },
101
+ phase: "verification"
102
+ };
103
+ if (output.verdict === "pass") await stores.tasks.transition(parent.id, "succeeded", {
104
+ ...fence,
105
+ output
106
+ });
107
+ else await stores.tasks.transition(parent.id, "failed", {
108
+ ...fence,
109
+ reason: `verification failed: ${output.summary}`
110
+ });
111
+ }
112
+ if (report.outcome === "pending") try {
113
+ await stores.signals.decide(report.id, {
114
+ outcome: "accepted",
115
+ reason: "Verification outcome applied"
116
+ });
117
+ } catch (error) {
118
+ if ((await stores.signals.get(report.id))?.outcome !== "accepted") throw error;
119
+ }
120
+ return {
121
+ recorded: true,
122
+ verdict: output.verdict,
123
+ changeState: updatedChange.state,
124
+ taskState: terminal
125
+ };
126
+ }
127
+ //#endregion
128
+ export { applyVerificationReport, saveVerificationReport, verificationDelivery, verificationOutputSchema, verificationParentAttempt, verificationReportSchema };
129
+
130
+ //# sourceMappingURL=result.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.mjs","names":[],"sources":["../../src/change-verification/result.ts"],"sourcesContent":["import { isDeepStrictEqual } from \"node:util\";\nimport { z } from \"zod\";\nimport { changeEvidenceSchema, changeLevelSchema } from \"../schema/change\";\nimport { changeIdSchema, taskIdSchema } from \"../schema/id\";\nimport type { Signal } from \"../schema/signal\";\nimport { executionReferenceSchema, type Task } from \"../schema/task\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type { RecordVerificationResult } from \"./eve-tool\";\n\nexport const verificationOutputSchema = z.object({\n changeId: changeIdSchema,\n verdict: z.enum([\"pass\", \"fail\"]),\n evidence: changeEvidenceSchema,\n confidence: changeLevelSchema,\n summary: z.string().min(1).max(2000),\n});\n\nexport const verificationReportSchema = z.object({\n taskId: taskIdSchema,\n attempt: z.number().int().positive(),\n execution: executionReferenceSchema,\n parentTaskId: taskIdSchema,\n parentAttempt: z.number().int().positive(),\n output: verificationOutputSchema,\n});\n\ntype Stores = Pick<FactoryStores, \"tasks\" | \"changes\" | \"signals\">;\n\nexport function verificationParentAttempt(task: Task): number {\n // Pre-binding verification Tasks only authorize the initial producer attempt.\n return z.object({ parentAttempt: z.number().int().positive().default(1) }).parse(task.work.input)\n .parentAttempt;\n}\n\nexport function verificationDelivery(task: Task) {\n return { source: \"change_verification\", deliveryId: `${task.id}:${task.attempt}` };\n}\n\nexport async function saveVerificationReport(\n stores: Stores,\n task: Task,\n output: z.infer<typeof verificationOutputSchema>,\n) {\n const payload = verificationReportSchema.parse({\n taskId: task.id,\n attempt: task.attempt,\n execution: task.execution,\n parentTaskId: task.parentTaskId,\n parentAttempt: verificationParentAttempt(task),\n output,\n });\n const report = await stores.signals.create({\n ...verificationDelivery(task),\n repositoryId: task.repositoryIds[0]!,\n replyTo: task.replyTo,\n payload,\n });\n if (report.repositoryId !== task.repositoryIds[0] || !isDeepStrictEqual(report.payload, payload))\n throw new Error(\"This verification attempt already recorded a different result\");\n return report;\n}\n\nexport async function applyVerificationReport(\n stores: Stores,\n report: Signal,\n): Promise<RecordVerificationResult> {\n const saved = verificationReportSchema.parse(report.payload);\n const task = await stores.tasks.get(saved.taskId);\n const parent = await stores.tasks.get(saved.parentTaskId);\n const { output } = saved;\n const terminal = output.verdict === \"pass\" ? \"succeeded\" : \"failed\";\n const change = await stores.changes.get(output.changeId);\n if (\n report.source !== \"change_verification\" ||\n !task ||\n !parent ||\n !change ||\n report.deliveryId !== verificationDelivery(task).deliveryId ||\n task.attempt !== saved.attempt ||\n !isDeepStrictEqual(task.execution, saved.execution) ||\n task.parentTaskId !== parent.id ||\n verificationParentAttempt(task) !== saved.parentAttempt ||\n parent.attempt !== saved.parentAttempt ||\n task.rootTaskId !== parent.rootTaskId ||\n task.changeId !== change.id ||\n parent.changeId !== change.id ||\n task.repositoryIds.length !== 1 ||\n parent.repositoryIds.length !== 1 ||\n task.repositoryIds[0] !== change.repositoryId ||\n parent.repositoryIds[0] !== change.repositoryId ||\n report.repositoryId !== change.repositoryId ||\n (task.state !== \"running\" && !(task.state === terminal && task.reportRef === report.id)) ||\n (parent.state !== \"verifying\" && !(parent.state === terminal && parent.reportRef === report.id))\n )\n return {\n recorded: false,\n reason: \"Verification result no longer belongs to the current Task and producer attempt\",\n };\n\n let updatedChange = change;\n if (task.state === \"running\") {\n if (parent.state !== \"verifying\" || ![\"verifying\", \"ready\"].includes(change.state))\n return { recorded: false, reason: \"Change and producer are no longer open for verification\" };\n const target = output.verdict === \"pass\" ? \"ready\" : \"verifying\";\n if (change.state !== target)\n updatedChange = await stores.changes.transition(change.id, target, {\n reason: output.summary,\n patch: { evidence: output.evidence, confidence: output.confidence },\n });\n if (\n !isDeepStrictEqual(updatedChange.evidence, output.evidence) ||\n updatedChange.confidence !== output.confidence\n )\n updatedChange = await stores.changes.updateEvidence(change.id, {\n evidence: output.evidence,\n confidence: output.confidence,\n expected: updatedChange,\n });\n const fence = {\n expectFrom: \"running\" as const,\n expectAttempt: saved.attempt,\n expectExecution: saved.execution,\n patch: { reportRef: report.id },\n phase: \"verification\",\n };\n if (output.verdict === \"pass\")\n await stores.tasks.transition(task.id, \"succeeded\", { ...fence, output });\n else await stores.tasks.transition(task.id, \"failed\", { ...fence, reason: output.summary });\n }\n if (parent.state === \"verifying\") {\n const fence = {\n expectFrom: \"verifying\" as const,\n expectAttempt: saved.parentAttempt,\n expectExecution: parent.execution,\n patch: { reportRef: report.id },\n phase: \"verification\",\n };\n if (output.verdict === \"pass\")\n await stores.tasks.transition(parent.id, \"succeeded\", { ...fence, output });\n else\n await stores.tasks.transition(parent.id, \"failed\", {\n ...fence,\n reason: `verification failed: ${output.summary}`,\n });\n }\n if (report.outcome === \"pending\") {\n try {\n await stores.signals.decide(report.id, {\n outcome: \"accepted\",\n reason: \"Verification outcome applied\",\n });\n } catch (error) {\n if ((await stores.signals.get(report.id))?.outcome !== \"accepted\") throw error;\n }\n }\n return {\n recorded: true,\n verdict: output.verdict,\n changeState: updatedChange.state,\n taskState: terminal,\n };\n}\n"],"mappings":"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"}
@@ -30,8 +30,8 @@ type RecordChangeInput = z.infer<typeof recordChangeInputSchema>;
30
30
  /** Configures policy gates and durable presentation for the software record-change tool. */
31
31
  interface RecordChangeToolOptions {
32
32
  /**
33
- * Pre-action gate for record_change, run as an Eve approval policy before
34
- * the tool executes. Refusals reach the model as a denial with the reason.
33
+ * PR association gate, run as an Eve approval policy and again after
34
+ * authentication before recording. Refusals include the gate's reason.
35
35
  * Gates must fail closed: when they cannot verify, they refuse.
36
36
  */
37
37
  recordChangeGate?: (input: RecordChangeInput) => Promise<PullRequestPublicationGateResult>;
@@ -1,4 +1,5 @@
1
1
  import { repositoryIdSchema, taskIdSchema } from "../schema/id.mjs";
2
+ import { requireTaskExecution, requireTaskExecutionIdentity } from "../eve/task-execution.mjs";
2
3
  import { changeLevelSchema } from "../schema/change.mjs";
3
4
  import { z } from "zod";
4
5
  import { defineTool } from "eve/tools";
@@ -31,13 +32,31 @@ function createRecordChangeTool(stores, options = {}) {
31
32
  reason: verdict.reason
32
33
  };
33
34
  } },
34
- async execute({ taskId, repositoryId, prNumber, prUrl, risk, confidence, summary }) {
35
- const currentTask = await stores.tasks.get(taskId);
36
- if (currentTask === null) throw new Error(`task ${taskId} does not exist`);
35
+ async execute({ taskId, repositoryId, prNumber, prUrl, risk, confidence, summary }, ctx) {
36
+ const currentTask = await requireTaskExecutionIdentity(stores, ctx, taskId);
37
37
  if (currentTask.state !== "running" && currentTask.state !== "verifying" && currentTask.state !== "succeeded") throw new Error(`task ${taskId} is ${currentTask.state}, not running`);
38
+ if (currentTask.repositoryIds.length !== 1 || currentTask.repositoryIds[0] !== repositoryId) throw new Error("Repository does not match the authenticated Task");
39
+ if (currentTask.state !== "running" && currentTask.changeId === void 0) throw new Error(`task ${taskId} has no recorded change`);
38
40
  const linkedChange = currentTask.changeId === void 0 ? void 0 : await stores.changes.get(currentTask.changeId);
39
41
  if (currentTask.changeId !== void 0 && linkedChange === null) throw new Error(`change ${currentTask.changeId} does not exist`);
40
42
  let change = linkedChange ?? (await stores.changes.list()).find((candidate) => candidate.taskId === taskId);
43
+ if (change !== void 0 && (change.repositoryId !== repositoryId || change.prNumber !== prNumber || change.prUrl !== prUrl)) throw new Error("Pull request does not match the Task's recorded Change");
44
+ if (currentTask.state === "running") {
45
+ await requireTaskExecution(stores, ctx, taskId);
46
+ if (recordChangeGate !== void 0) {
47
+ const verdict = await recordChangeGate({
48
+ taskId,
49
+ repositoryId,
50
+ prNumber,
51
+ prUrl,
52
+ risk,
53
+ confidence,
54
+ summary
55
+ });
56
+ if (!verdict.ok) throw new Error(verdict.reason);
57
+ }
58
+ await requireTaskExecution(stores, ctx, taskId);
59
+ }
41
60
  if (change === void 0) {
42
61
  if (currentTask.state !== "running") throw new Error(`task ${taskId} has no recorded change`);
43
62
  change = await stores.changes.create({
@@ -59,14 +78,24 @@ function createRecordChangeTool(stores, options = {}) {
59
78
  }
60
79
  if (change === void 0) throw new Error(`task ${taskId} has no recorded change`);
61
80
  let recordedChange = change;
62
- if (currentTask.state === "running") await options.presentChange?.({
63
- taskId,
64
- changeId: recordedChange.id,
65
- verification,
66
- summary
67
- });
81
+ if (currentTask.state === "succeeded") return {
82
+ change: recordedChange,
83
+ task: currentTask
84
+ };
85
+ if (currentTask.state === "running") {
86
+ await requireTaskExecution(stores, ctx, taskId);
87
+ await options.presentChange?.({
88
+ taskId,
89
+ changeId: recordedChange.id,
90
+ verification,
91
+ summary
92
+ });
93
+ }
68
94
  let task = currentTask;
69
95
  if (task.state === "running") task = await stores.tasks.transition(taskId, "verifying", {
96
+ expectFrom: "running",
97
+ expectAttempt: currentTask.attempt,
98
+ expectExecution: currentTask.execution,
70
99
  patch: { changeId: recordedChange.id },
71
100
  reason: summary
72
101
  });
@@ -74,8 +103,13 @@ function createRecordChangeTool(stores, options = {}) {
74
103
  change: recordedChange,
75
104
  task
76
105
  };
106
+ const completion = await requireTaskExecutionIdentity(stores, ctx, taskId);
107
+ if (completion.state !== "verifying" || completion.changeId !== recordedChange.id) throw new Error("Task is no longer awaiting this Change's completion");
77
108
  if (recordedChange.state === "verifying") recordedChange = await stores.changes.transition(recordedChange.id, "ready", { reason: "independent verification is not configured" });
78
109
  if (task.state === "verifying") task = await stores.tasks.transition(taskId, "succeeded", {
110
+ expectFrom: "verifying",
111
+ expectAttempt: currentTask.attempt,
112
+ expectExecution: currentTask.execution,
79
113
  reason: "change recorded for human review without independent verification",
80
114
  output: {
81
115
  changeId: recordedChange.id,
@@ -1 +1 @@
1
- {"version":3,"file":"eve-record-change.mjs","names":[],"sources":["../../src/changes/eve-record-change.ts"],"sourcesContent":["import { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport type { ChangePresenter } from \"./presentation\";\nimport { changeLevelSchema, type Change } from \"../schema/change\";\nimport { repositoryIdSchema, taskIdSchema } from \"../schema/id\";\nimport type { Task } from \"../schema/task\";\nimport type { PullRequestPublicationGateResult } from \"../pull-requests/contracts\";\nimport type { FactoryStores } from \"../store/engine\";\n\n/** Change and Task capabilities required by the record-change tool. */\nexport type RecordChangeToolStores = Pick<FactoryStores, \"changes\" | \"tasks\">;\n\nconst recordChangeInputSchema = z.object({\n taskId: taskIdSchema,\n repositoryId: repositoryIdSchema,\n prNumber: z.number().int().min(1),\n prUrl: z.url(),\n risk: changeLevelSchema.describe(\"Blast radius of the change if it is wrong.\"),\n confidence: changeLevelSchema.describe(\"How certain you are the change is correct.\"),\n summary: z.string().min(1).describe(\"One-sentence description of the change.\"),\n});\n\n/** Validated input accepted by the tool that links a pull request to its producing Task. */\nexport type RecordChangeInput = z.infer<typeof recordChangeInputSchema>;\n\n/** Configures policy gates and durable presentation for the software record-change tool. */\nexport interface RecordChangeToolOptions {\n /**\n * Pre-action gate for record_change, run as an Eve approval policy before\n * the tool executes. Refusals reach the model as a denial with the reason.\n * Gates must fail closed: when they cannot verify, they refuse.\n */\n recordChangeGate?: (input: RecordChangeInput) => Promise<PullRequestPublicationGateResult>;\n /** Whether recorded code changes wait for an independent verification task. Default: required. */\n verification?: \"required\" | \"disabled\";\n /** Present the pull request after record_change persists its durable state. */\n presentChange?: ChangePresenter;\n}\n\n/** Change and producing Task returned after a pull request is recorded. */\nexport interface RecordChangeResult {\n readonly change: Change;\n readonly task: Task;\n}\n\n/** Eve outcome tool that links a pull request to its producing Task. */\nexport interface RecordChangeTool extends ToolDefinition<RecordChangeInput, RecordChangeResult> {\n execute(input: RecordChangeInput, ctx: ToolContext): Promise<RecordChangeResult>;\n}\n\n/** Create the supplied software workflow's pull-request outcome tool. */\nexport function createRecordChangeTool(\n stores: RecordChangeToolStores,\n options: RecordChangeToolOptions = {},\n): RecordChangeTool {\n const recordChangeGate = options.recordChangeGate;\n const verification = options.verification ?? \"required\";\n return defineTool({\n description:\n verification === \"required\"\n ? \"Record the pull request produced for a code_change task. Creates the change, links it to the task, and moves the task to verifying.\"\n : \"Record the pull request produced for a code_change task. Creates the change and marks it ready for human review without independent verification.\",\n inputSchema: recordChangeInputSchema,\n ...(recordChangeGate === undefined\n ? {}\n : {\n approval: async ({ toolInput }: { toolInput?: unknown }) => {\n const parsed = recordChangeInputSchema.safeParse(toolInput);\n if (!parsed.success) {\n return {\n type: \"denied\" as const,\n reason: \"record_change input is malformed; fix the arguments and retry\",\n };\n }\n const verdict = await recordChangeGate(parsed.data);\n return verdict.ok\n ? (\"not-applicable\" as const)\n : { type: \"denied\" as const, reason: verdict.reason };\n },\n }),\n async execute({\n taskId,\n repositoryId,\n prNumber,\n prUrl,\n risk,\n confidence,\n summary,\n }): Promise<RecordChangeResult> {\n const currentTask = await stores.tasks.get(taskId);\n if (currentTask === null) throw new Error(`task ${taskId} does not exist`);\n if (\n currentTask.state !== \"running\" &&\n currentTask.state !== \"verifying\" &&\n currentTask.state !== \"succeeded\"\n ) {\n throw new Error(`task ${taskId} is ${currentTask.state}, not running`);\n }\n const linkedChange =\n currentTask.changeId === undefined\n ? undefined\n : await stores.changes.get(currentTask.changeId);\n if (currentTask.changeId !== undefined && linkedChange === null) {\n throw new Error(`change ${currentTask.changeId} does not exist`);\n }\n let change =\n linkedChange ??\n (await stores.changes.list()).find((candidate) => candidate.taskId === taskId);\n\n if (change === undefined) {\n if (currentTask.state !== \"running\") {\n throw new Error(`task ${taskId} has no recorded change`);\n }\n change = await stores.changes.create({\n repositoryId,\n taskId,\n prNumber,\n prUrl,\n author: \"factory\",\n risk,\n confidence,\n evidence: {\n reproVerified: false,\n testsAdded: 0,\n checksGreen: false,\n attackPassed: false,\n scopeMatches: false,\n },\n });\n }\n if (change === undefined) throw new Error(`task ${taskId} has no recorded change`);\n let recordedChange = change;\n\n if (currentTask.state === \"running\") {\n await options.presentChange?.({\n taskId,\n changeId: recordedChange.id,\n verification,\n summary,\n });\n }\n\n let task: Task = currentTask;\n if (task.state === \"running\") {\n task = await stores.tasks.transition(taskId, \"verifying\", {\n patch: { changeId: recordedChange.id },\n reason: summary,\n });\n }\n if (verification === \"required\") return { change: recordedChange, task };\n\n if (recordedChange.state === \"verifying\") {\n recordedChange = await stores.changes.transition(recordedChange.id, \"ready\", {\n reason: \"independent verification is not configured\",\n });\n }\n if (task.state === \"verifying\") {\n task = await stores.tasks.transition(taskId, \"succeeded\", {\n reason: \"change recorded for human review without independent verification\",\n output: {\n changeId: recordedChange.id,\n prUrl: recordedChange.prUrl,\n summary,\n verification,\n },\n });\n }\n return { change: recordedChange, task };\n },\n });\n}\n"],"mappings":";;;;;AAYA,MAAM,0BAA0B,EAAE,OAAO;CACvC,QAAQ;CACR,cAAc;CACd,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;CAChC,OAAO,EAAE,IAAI;CACb,MAAM,kBAAkB,SAAS,4CAA4C;CAC7E,YAAY,kBAAkB,SAAS,4CAA4C;CACnF,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,yCAAyC;AAC/E,CAAC;;AA+BD,SAAgB,uBACd,QACA,UAAmC,CAAC,GAClB;CAClB,MAAM,mBAAmB,QAAQ;CACjC,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,OAAO,WAAW;EAChB,aACE,iBAAiB,aACb,wIACA;EACN,aAAa;EACb,GAAI,qBAAqB,KAAA,IACrB,CAAC,IACD,EACE,UAAU,OAAO,EAAE,gBAAyC;GAC1D,MAAM,SAAS,wBAAwB,UAAU,SAAS;GAC1D,IAAI,CAAC,OAAO,SACV,OAAO;IACL,MAAM;IACN,QAAQ;GACV;GAEF,MAAM,UAAU,MAAM,iBAAiB,OAAO,IAAI;GAClD,OAAO,QAAQ,KACV,mBACD;IAAE,MAAM;IAAmB,QAAQ,QAAQ;GAAO;EACxD,EACF;EACJ,MAAM,QAAQ,EACZ,QACA,cACA,UACA,OACA,MACA,YACA,WAC8B;GAC9B,MAAM,cAAc,MAAM,OAAO,MAAM,IAAI,MAAM;GACjD,IAAI,gBAAgB,MAAM,MAAM,IAAI,MAAM,QAAQ,OAAO,gBAAgB;GACzE,IACE,YAAY,UAAU,aACtB,YAAY,UAAU,eACtB,YAAY,UAAU,aAEtB,MAAM,IAAI,MAAM,QAAQ,OAAO,MAAM,YAAY,MAAM,cAAc;GAEvE,MAAM,eACJ,YAAY,aAAa,KAAA,IACrB,KAAA,IACA,MAAM,OAAO,QAAQ,IAAI,YAAY,QAAQ;GACnD,IAAI,YAAY,aAAa,KAAA,KAAa,iBAAiB,MACzD,MAAM,IAAI,MAAM,UAAU,YAAY,SAAS,gBAAgB;GAEjE,IAAI,SACF,iBACC,MAAM,OAAO,QAAQ,KAAK,EAAA,CAAG,MAAM,cAAc,UAAU,WAAW,MAAM;GAE/E,IAAI,WAAW,KAAA,GAAW;IACxB,IAAI,YAAY,UAAU,WACxB,MAAM,IAAI,MAAM,QAAQ,OAAO,wBAAwB;IAEzD,SAAS,MAAM,OAAO,QAAQ,OAAO;KACnC;KACA;KACA;KACA;KACA,QAAQ;KACR;KACA;KACA,UAAU;MACR,eAAe;MACf,YAAY;MACZ,aAAa;MACb,cAAc;MACd,cAAc;KAChB;IACF,CAAC;GACH;GACA,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,MAAM,QAAQ,OAAO,wBAAwB;GACjF,IAAI,iBAAiB;GAErB,IAAI,YAAY,UAAU,WACxB,MAAM,QAAQ,gBAAgB;IAC5B;IACA,UAAU,eAAe;IACzB;IACA;GACF,CAAC;GAGH,IAAI,OAAa;GACjB,IAAI,KAAK,UAAU,WACjB,OAAO,MAAM,OAAO,MAAM,WAAW,QAAQ,aAAa;IACxD,OAAO,EAAE,UAAU,eAAe,GAAG;IACrC,QAAQ;GACV,CAAC;GAEH,IAAI,iBAAiB,YAAY,OAAO;IAAE,QAAQ;IAAgB;GAAK;GAEvE,IAAI,eAAe,UAAU,aAC3B,iBAAiB,MAAM,OAAO,QAAQ,WAAW,eAAe,IAAI,SAAS,EAC3E,QAAQ,6CACV,CAAC;GAEH,IAAI,KAAK,UAAU,aACjB,OAAO,MAAM,OAAO,MAAM,WAAW,QAAQ,aAAa;IACxD,QAAQ;IACR,QAAQ;KACN,UAAU,eAAe;KACzB,OAAO,eAAe;KACtB;KACA;IACF;GACF,CAAC;GAEH,OAAO;IAAE,QAAQ;IAAgB;GAAK;EACxC;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"eve-record-change.mjs","names":[],"sources":["../../src/changes/eve-record-change.ts"],"sourcesContent":["import { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport type { ChangePresenter } from \"./presentation\";\nimport { changeLevelSchema, type Change } from \"../schema/change\";\nimport { repositoryIdSchema, taskIdSchema } from \"../schema/id\";\nimport type { Task } from \"../schema/task\";\nimport type { PullRequestPublicationGateResult } from \"../pull-requests/contracts\";\nimport type { FactoryStores } from \"../store/engine\";\nimport { requireTaskExecution, requireTaskExecutionIdentity } from \"../eve/task-execution\";\n\n/** Change and Task capabilities required by the record-change tool. */\nexport type RecordChangeToolStores = Pick<FactoryStores, \"changes\" | \"tasks\">;\n\nconst recordChangeInputSchema = z.object({\n taskId: taskIdSchema,\n repositoryId: repositoryIdSchema,\n prNumber: z.number().int().min(1),\n prUrl: z.url(),\n risk: changeLevelSchema.describe(\"Blast radius of the change if it is wrong.\"),\n confidence: changeLevelSchema.describe(\"How certain you are the change is correct.\"),\n summary: z.string().min(1).describe(\"One-sentence description of the change.\"),\n});\n\n/** Validated input accepted by the tool that links a pull request to its producing Task. */\nexport type RecordChangeInput = z.infer<typeof recordChangeInputSchema>;\n\n/** Configures policy gates and durable presentation for the software record-change tool. */\nexport interface RecordChangeToolOptions {\n /**\n * PR association gate, run as an Eve approval policy and again after\n * authentication before recording. Refusals include the gate's reason.\n * Gates must fail closed: when they cannot verify, they refuse.\n */\n recordChangeGate?: (input: RecordChangeInput) => Promise<PullRequestPublicationGateResult>;\n /** Whether recorded code changes wait for an independent verification task. Default: required. */\n verification?: \"required\" | \"disabled\";\n /** Present the pull request after record_change persists its durable state. */\n presentChange?: ChangePresenter;\n}\n\n/** Change and producing Task returned after a pull request is recorded. */\nexport interface RecordChangeResult {\n readonly change: Change;\n readonly task: Task;\n}\n\n/** Eve outcome tool that links a pull request to its producing Task. */\nexport interface RecordChangeTool extends ToolDefinition<RecordChangeInput, RecordChangeResult> {\n execute(input: RecordChangeInput, ctx: ToolContext): Promise<RecordChangeResult>;\n}\n\n/** Create the supplied software workflow's pull-request outcome tool. */\nexport function createRecordChangeTool(\n stores: RecordChangeToolStores,\n options: RecordChangeToolOptions = {},\n): RecordChangeTool {\n const recordChangeGate = options.recordChangeGate;\n const verification = options.verification ?? \"required\";\n return defineTool({\n description:\n verification === \"required\"\n ? \"Record the pull request produced for a code_change task. Creates the change, links it to the task, and moves the task to verifying.\"\n : \"Record the pull request produced for a code_change task. Creates the change and marks it ready for human review without independent verification.\",\n inputSchema: recordChangeInputSchema,\n ...(recordChangeGate === undefined\n ? {}\n : {\n approval: async ({ toolInput }: { toolInput?: unknown }) => {\n const parsed = recordChangeInputSchema.safeParse(toolInput);\n if (!parsed.success) {\n return {\n type: \"denied\" as const,\n reason: \"record_change input is malformed; fix the arguments and retry\",\n };\n }\n const verdict = await recordChangeGate(parsed.data);\n return verdict.ok\n ? (\"not-applicable\" as const)\n : { type: \"denied\" as const, reason: verdict.reason };\n },\n }),\n async execute(\n { taskId, repositoryId, prNumber, prUrl, risk, confidence, summary },\n ctx,\n ): Promise<RecordChangeResult> {\n const currentTask = await requireTaskExecutionIdentity(stores, ctx, taskId);\n if (\n currentTask.state !== \"running\" &&\n currentTask.state !== \"verifying\" &&\n currentTask.state !== \"succeeded\"\n ) {\n throw new Error(`task ${taskId} is ${currentTask.state}, not running`);\n }\n if (currentTask.repositoryIds.length !== 1 || currentTask.repositoryIds[0] !== repositoryId) {\n throw new Error(\"Repository does not match the authenticated Task\");\n }\n if (currentTask.state !== \"running\" && currentTask.changeId === undefined) {\n throw new Error(`task ${taskId} has no recorded change`);\n }\n const linkedChange =\n currentTask.changeId === undefined\n ? undefined\n : await stores.changes.get(currentTask.changeId);\n if (currentTask.changeId !== undefined && linkedChange === null) {\n throw new Error(`change ${currentTask.changeId} does not exist`);\n }\n let change =\n linkedChange ??\n (await stores.changes.list()).find((candidate) => candidate.taskId === taskId);\n\n if (\n change !== undefined &&\n (change.repositoryId !== repositoryId ||\n change.prNumber !== prNumber ||\n change.prUrl !== prUrl)\n ) {\n throw new Error(\"Pull request does not match the Task's recorded Change\");\n }\n if (currentTask.state === \"running\") {\n await requireTaskExecution(stores, ctx, taskId);\n if (recordChangeGate !== undefined) {\n const verdict = await recordChangeGate({\n taskId,\n repositoryId,\n prNumber,\n prUrl,\n risk,\n confidence,\n summary,\n });\n if (!verdict.ok) throw new Error(verdict.reason);\n }\n await requireTaskExecution(stores, ctx, taskId);\n }\n\n if (change === undefined) {\n if (currentTask.state !== \"running\") {\n throw new Error(`task ${taskId} has no recorded change`);\n }\n change = await stores.changes.create({\n repositoryId,\n taskId,\n prNumber,\n prUrl,\n author: \"factory\",\n risk,\n confidence,\n evidence: {\n reproVerified: false,\n testsAdded: 0,\n checksGreen: false,\n attackPassed: false,\n scopeMatches: false,\n },\n });\n }\n if (change === undefined) throw new Error(`task ${taskId} has no recorded change`);\n let recordedChange = change;\n\n if (currentTask.state === \"succeeded\") return { change: recordedChange, task: currentTask };\n\n if (currentTask.state === \"running\") {\n await requireTaskExecution(stores, ctx, taskId);\n await options.presentChange?.({\n taskId,\n changeId: recordedChange.id,\n verification,\n summary,\n });\n }\n\n let task: Task = currentTask;\n if (task.state === \"running\") {\n task = await stores.tasks.transition(taskId, \"verifying\", {\n expectFrom: \"running\",\n expectAttempt: currentTask.attempt,\n expectExecution: currentTask.execution,\n patch: { changeId: recordedChange.id },\n reason: summary,\n });\n }\n if (verification === \"required\") return { change: recordedChange, task };\n\n const completion = await requireTaskExecutionIdentity(stores, ctx, taskId);\n if (completion.state !== \"verifying\" || completion.changeId !== recordedChange.id) {\n throw new Error(\"Task is no longer awaiting this Change's completion\");\n }\n if (recordedChange.state === \"verifying\") {\n recordedChange = await stores.changes.transition(recordedChange.id, \"ready\", {\n reason: \"independent verification is not configured\",\n });\n }\n if (task.state === \"verifying\") {\n task = await stores.tasks.transition(taskId, \"succeeded\", {\n expectFrom: \"verifying\",\n expectAttempt: currentTask.attempt,\n expectExecution: currentTask.execution,\n reason: \"change recorded for human review without independent verification\",\n output: {\n changeId: recordedChange.id,\n prUrl: recordedChange.prUrl,\n summary,\n verification,\n },\n });\n }\n return { change: recordedChange, task };\n },\n });\n}\n"],"mappings":"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"}
@@ -1,12 +1,13 @@
1
+ /// <reference types="node" />
1
2
  import { ChangeId, RepositoryId, changeIdSchema } from "./schema/id.mjs";
2
3
  import { Change, ChangeEvidence, ChangeInState, ChangeLevel, ChangeState, changeEvidenceSchema, changeLevelSchema, changeSchema, changeStateSchema } from "./schema/change.mjs";
3
- import { ChangeAssociationError, ChangePatch, ChangeTransitionArguments, ChangeTransitionOptions, CreateChangeInput, FactoryStores } from "./store/engine.mjs";
4
+ import { ChangeAssociationError, ChangePatch, ChangeTransitionArguments, ChangeTransitionOptions, CreateChangeInput, FactoryStores, UpdateChangeEvidenceInput } from "./store/engine.mjs";
4
5
  import { changeInitialState, changeTransitions } from "./schema/transitions.mjs";
5
6
  import { ChangePresenter, PresentChangeInput } from "./changes/presentation.mjs";
6
7
  import { RecordChangeInput, RecordChangeResult, RecordChangeTool, RecordChangeToolOptions, RecordChangeToolStores, createRecordChangeTool } from "./changes/eve-record-change.mjs";
7
8
  import { formatChangeVerificationBrief } from "./change-verification/brief.mjs";
8
- import { ChangeVerificationStores, EnsureChangeVerificationTasksOptions, EnsureChangeVerificationTasksResult, ensureChangeVerificationTasks } from "./change-verification/dispatch.mjs";
9
9
  import { PresentVerificationResultInput, RecordVerificationInput, RecordVerificationRefusal, RecordVerificationResult, RecordVerificationSuccess, RecordVerificationTool, RecordVerificationToolOptions, RecordVerificationToolStores, createRecordVerificationTool } from "./change-verification/eve-tool.mjs";
10
+ import { ChangeVerificationStores, EnsureChangeVerificationTasksOptions, EnsureChangeVerificationTasksResult, RecoverChangeVerificationResultOptions, ensureChangeVerificationTasks, recoverChangeVerificationResult } from "./change-verification/dispatch.mjs";
10
11
  import { CreateMultiRepositoryChangeTasksInput, CreateMultiRepositoryChangeTasksStores, MultiRepositoryChangePlan, ReconcileMultiRepositoryChangeTasksResult, ReconcileMultiRepositoryChangeTasksStores, createMultiRepositoryChangeTasks, reconcileMultiRepositoryChangeTasks } from "./multi-repository-changes/coordination.mjs";
11
12
  //#region src/changes.d.ts
12
13
  /** Provider identity and author required to represent an existing pull request as a Change. */
@@ -19,5 +20,5 @@ interface AdoptPullRequestInput {
19
20
  /** Create a conservative, unverified Change for a pull request not authored by Factory. */
20
21
  declare function adoptPullRequest(stores: Pick<FactoryStores, "changes">, input: AdoptPullRequestInput): Promise<Change>;
21
22
  //#endregion
22
- export { AdoptPullRequestInput, type Change, ChangeAssociationError, type ChangeEvidence, type ChangeId, type ChangeInState, type ChangeLevel, type ChangePatch, type ChangePresenter, type ChangeState, type ChangeTransitionArguments, type ChangeTransitionOptions, type ChangeVerificationStores, type CreateChangeInput, type CreateMultiRepositoryChangeTasksInput, type CreateMultiRepositoryChangeTasksStores, type EnsureChangeVerificationTasksOptions, type EnsureChangeVerificationTasksResult, type MultiRepositoryChangePlan, type PresentChangeInput, type PresentVerificationResultInput, type ReconcileMultiRepositoryChangeTasksResult, type ReconcileMultiRepositoryChangeTasksStores, type RecordChangeInput, type RecordChangeResult, type RecordChangeTool, type RecordChangeToolOptions, type RecordChangeToolStores, type RecordVerificationInput, type RecordVerificationRefusal, type RecordVerificationResult, type RecordVerificationSuccess, type RecordVerificationTool, type RecordVerificationToolOptions, type RecordVerificationToolStores, adoptPullRequest, changeEvidenceSchema, changeIdSchema, changeInitialState, changeLevelSchema, changeSchema, changeStateSchema, changeTransitions, createMultiRepositoryChangeTasks, createRecordChangeTool, createRecordVerificationTool, ensureChangeVerificationTasks, formatChangeVerificationBrief, reconcileMultiRepositoryChangeTasks };
23
+ export { AdoptPullRequestInput, type Change, ChangeAssociationError, type ChangeEvidence, type ChangeId, type ChangeInState, type ChangeLevel, type ChangePatch, type ChangePresenter, type ChangeState, type ChangeTransitionArguments, type ChangeTransitionOptions, type ChangeVerificationStores, type CreateChangeInput, type CreateMultiRepositoryChangeTasksInput, type CreateMultiRepositoryChangeTasksStores, type EnsureChangeVerificationTasksOptions, type EnsureChangeVerificationTasksResult, type MultiRepositoryChangePlan, type PresentChangeInput, type PresentVerificationResultInput, type ReconcileMultiRepositoryChangeTasksResult, type ReconcileMultiRepositoryChangeTasksStores, type RecordChangeInput, type RecordChangeResult, type RecordChangeTool, type RecordChangeToolOptions, type RecordChangeToolStores, type RecordVerificationInput, type RecordVerificationRefusal, type RecordVerificationResult, type RecordVerificationSuccess, type RecordVerificationTool, type RecordVerificationToolOptions, type RecordVerificationToolStores, type RecoverChangeVerificationResultOptions, type UpdateChangeEvidenceInput, adoptPullRequest, changeEvidenceSchema, changeIdSchema, changeInitialState, changeLevelSchema, changeSchema, changeStateSchema, changeTransitions, createMultiRepositoryChangeTasks, createRecordChangeTool, createRecordVerificationTool, ensureChangeVerificationTasks, formatChangeVerificationBrief, reconcileMultiRepositoryChangeTasks, recoverChangeVerificationResult };
23
24
  //# sourceMappingURL=changes.d.mts.map
package/dist/changes.mjs CHANGED
@@ -4,7 +4,7 @@ import { changeEvidenceSchema, changeLevelSchema, changeSchema, changeStateSchem
4
4
  import { ChangeAssociationError } from "./store/engine.mjs";
5
5
  import { createRecordChangeTool } from "./changes/eve-record-change.mjs";
6
6
  import { formatChangeVerificationBrief } from "./change-verification/brief.mjs";
7
- import { ensureChangeVerificationTasks } from "./change-verification/dispatch.mjs";
7
+ import { ensureChangeVerificationTasks, recoverChangeVerificationResult } from "./change-verification/dispatch.mjs";
8
8
  import { createRecordVerificationTool } from "./change-verification/eve-tool.mjs";
9
9
  import { createMultiRepositoryChangeTasks, reconcileMultiRepositoryChangeTasks } from "./multi-repository-changes/coordination.mjs";
10
10
  //#region src/changes.ts
@@ -24,6 +24,6 @@ async function adoptPullRequest(stores, input) {
24
24
  });
25
25
  }
26
26
  //#endregion
27
- export { ChangeAssociationError, adoptPullRequest, changeEvidenceSchema, changeIdSchema, changeInitialState, changeLevelSchema, changeSchema, changeStateSchema, changeTransitions, createMultiRepositoryChangeTasks, createRecordChangeTool, createRecordVerificationTool, ensureChangeVerificationTasks, formatChangeVerificationBrief, reconcileMultiRepositoryChangeTasks };
27
+ export { ChangeAssociationError, adoptPullRequest, changeEvidenceSchema, changeIdSchema, changeInitialState, changeLevelSchema, changeSchema, changeStateSchema, changeTransitions, createMultiRepositoryChangeTasks, createRecordChangeTool, createRecordVerificationTool, ensureChangeVerificationTasks, formatChangeVerificationBrief, reconcileMultiRepositoryChangeTasks, recoverChangeVerificationResult };
28
28
 
29
29
  //# sourceMappingURL=changes.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"changes.mjs","names":[],"sources":["../src/changes.ts"],"sourcesContent":["import type { Change } from \"./schema/change\";\nimport type { RepositoryId } from \"./schema/id\";\nimport type { FactoryStores } from \"./store/engine\";\n\nexport { type ChangePresenter, type PresentChangeInput } from \"./changes/presentation\";\nexport {\n createRecordChangeTool,\n type RecordChangeInput,\n type RecordChangeResult,\n type RecordChangeTool,\n type RecordChangeToolStores,\n type RecordChangeToolOptions,\n} from \"./changes/eve-record-change\";\nexport { formatChangeVerificationBrief } from \"./change-verification/brief\";\nexport {\n ensureChangeVerificationTasks,\n type ChangeVerificationStores,\n type EnsureChangeVerificationTasksOptions,\n type EnsureChangeVerificationTasksResult,\n} from \"./change-verification/dispatch\";\nexport {\n createRecordVerificationTool,\n type PresentVerificationResultInput,\n type RecordVerificationInput,\n type RecordVerificationRefusal,\n type RecordVerificationResult,\n type RecordVerificationSuccess,\n type RecordVerificationTool,\n type RecordVerificationToolStores,\n type RecordVerificationToolOptions,\n} from \"./change-verification/eve-tool\";\nexport {\n createMultiRepositoryChangeTasks,\n reconcileMultiRepositoryChangeTasks,\n type CreateMultiRepositoryChangeTasksInput,\n type CreateMultiRepositoryChangeTasksStores,\n type MultiRepositoryChangePlan,\n type ReconcileMultiRepositoryChangeTasksResult,\n type ReconcileMultiRepositoryChangeTasksStores,\n} from \"./multi-repository-changes/coordination\";\n\n/** Provider identity and author required to represent an existing pull request as a Change. */\nexport interface AdoptPullRequestInput {\n repositoryId: RepositoryId;\n prNumber: number;\n prUrl: string;\n author: string;\n}\n\n/** Create a conservative, unverified Change for a pull request not authored by Factory. */\nexport async function adoptPullRequest(\n stores: Pick<FactoryStores, \"changes\">,\n input: AdoptPullRequestInput,\n): Promise<Change> {\n return stores.changes.create({\n ...input,\n risk: \"low\",\n confidence: \"low\",\n evidence: {\n reproVerified: false,\n testsAdded: 0,\n checksGreen: false,\n attackPassed: false,\n scopeMatches: false,\n },\n });\n}\n\nexport {\n changeEvidenceSchema,\n changeSchema,\n changeStateSchema,\n changeLevelSchema,\n type Change,\n type ChangeEvidence,\n type ChangeInState,\n type ChangeState,\n type ChangeLevel,\n} from \"./schema/change\";\nexport { changeIdSchema, type ChangeId } from \"./schema/id\";\nexport { changeInitialState, changeTransitions } from \"./schema/transitions\";\nexport {\n ChangeAssociationError,\n type ChangePatch,\n type ChangeTransitionOptions,\n type ChangeTransitionArguments,\n type CreateChangeInput,\n} from \"./store/engine\";\n"],"mappings":";;;;;;;;;;;AAkDA,eAAsB,iBACpB,QACA,OACiB;CACjB,OAAO,OAAO,QAAQ,OAAO;EAC3B,GAAG;EACH,MAAM;EACN,YAAY;EACZ,UAAU;GACR,eAAe;GACf,YAAY;GACZ,aAAa;GACb,cAAc;GACd,cAAc;EAChB;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"changes.mjs","names":[],"sources":["../src/changes.ts"],"sourcesContent":["import type { Change } from \"./schema/change\";\nimport type { RepositoryId } from \"./schema/id\";\nimport type { FactoryStores } from \"./store/engine\";\n\nexport { type ChangePresenter, type PresentChangeInput } from \"./changes/presentation\";\nexport {\n createRecordChangeTool,\n type RecordChangeInput,\n type RecordChangeResult,\n type RecordChangeTool,\n type RecordChangeToolStores,\n type RecordChangeToolOptions,\n} from \"./changes/eve-record-change\";\nexport { formatChangeVerificationBrief } from \"./change-verification/brief\";\nexport {\n ensureChangeVerificationTasks,\n recoverChangeVerificationResult,\n type ChangeVerificationStores,\n type EnsureChangeVerificationTasksOptions,\n type EnsureChangeVerificationTasksResult,\n type RecoverChangeVerificationResultOptions,\n} from \"./change-verification/dispatch\";\nexport {\n createRecordVerificationTool,\n type PresentVerificationResultInput,\n type RecordVerificationInput,\n type RecordVerificationRefusal,\n type RecordVerificationResult,\n type RecordVerificationSuccess,\n type RecordVerificationTool,\n type RecordVerificationToolStores,\n type RecordVerificationToolOptions,\n} from \"./change-verification/eve-tool\";\nexport {\n createMultiRepositoryChangeTasks,\n reconcileMultiRepositoryChangeTasks,\n type CreateMultiRepositoryChangeTasksInput,\n type CreateMultiRepositoryChangeTasksStores,\n type MultiRepositoryChangePlan,\n type ReconcileMultiRepositoryChangeTasksResult,\n type ReconcileMultiRepositoryChangeTasksStores,\n} from \"./multi-repository-changes/coordination\";\n\n/** Provider identity and author required to represent an existing pull request as a Change. */\nexport interface AdoptPullRequestInput {\n repositoryId: RepositoryId;\n prNumber: number;\n prUrl: string;\n author: string;\n}\n\n/** Create a conservative, unverified Change for a pull request not authored by Factory. */\nexport async function adoptPullRequest(\n stores: Pick<FactoryStores, \"changes\">,\n input: AdoptPullRequestInput,\n): Promise<Change> {\n return stores.changes.create({\n ...input,\n risk: \"low\",\n confidence: \"low\",\n evidence: {\n reproVerified: false,\n testsAdded: 0,\n checksGreen: false,\n attackPassed: false,\n scopeMatches: false,\n },\n });\n}\n\nexport {\n changeEvidenceSchema,\n changeSchema,\n changeStateSchema,\n changeLevelSchema,\n type Change,\n type ChangeEvidence,\n type ChangeInState,\n type ChangeState,\n type ChangeLevel,\n} from \"./schema/change\";\nexport { changeIdSchema, type ChangeId } from \"./schema/id\";\nexport { changeInitialState, changeTransitions } from \"./schema/transitions\";\nexport {\n ChangeAssociationError,\n type ChangePatch,\n type ChangeTransitionOptions,\n type ChangeTransitionArguments,\n type CreateChangeInput,\n type UpdateChangeEvidenceInput,\n} from \"./store/engine\";\n"],"mappings":";;;;;;;;;;;AAoDA,eAAsB,iBACpB,QACA,OACiB;CACjB,OAAO,OAAO,QAAQ,OAAO;EAC3B,GAAG;EACH,MAAM;EACN,YAAY;EACZ,UAAU;GACR,eAAe;GACf,YAAY;GACZ,aAAa;GACb,cAAc;GACd,cAAc;EAChB;CACF,CAAC;AACH"}
@@ -1,14 +1,21 @@
1
1
  import { z } from "zod";
2
2
  //#region src/client-events.d.ts
3
- /** Validates one newline-delimited event from a Factory execution stream. */
4
- declare const streamEventSchema: z.ZodObject<{
3
+ /** Validates an execution stream event, normalizing payloadless completion events. */
4
+ declare const streamEventSchema: z.ZodUnion<[z.ZodObject<{
5
5
  type: z.ZodString;
6
6
  data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
7
7
  meta: z.ZodOptional<z.ZodObject<{
8
8
  id: z.ZodOptional<z.ZodString>;
9
9
  at: z.ZodOptional<z.ZodString>;
10
10
  }, z.core.$strip>>;
11
- }, z.core.$strip>;
11
+ }, z.core.$strip>, z.ZodObject<{
12
+ meta: z.ZodOptional<z.ZodObject<{
13
+ id: z.ZodOptional<z.ZodString>;
14
+ at: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>>;
16
+ type: z.ZodLiteral<"session.completed">;
17
+ data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
18
+ }, z.core.$strip>]>;
12
19
  /** One validated event received from a Factory execution stream. */
13
20
  type StreamEvent = z.infer<typeof streamEventSchema>;
14
21
  /** Parses validated newline-delimited events from a response body. */
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  //#region src/client-events.ts
3
- /** Validates one newline-delimited event from a Factory execution stream. */
4
- const streamEventSchema = z.object({
3
+ const eventWithDataSchema = z.object({
5
4
  type: z.string(),
6
5
  data: z.record(z.string(), z.unknown()),
7
6
  meta: z.object({
@@ -9,6 +8,11 @@ const streamEventSchema = z.object({
9
8
  at: z.string().optional()
10
9
  }).optional()
11
10
  });
11
+ /** Validates an execution stream event, normalizing payloadless completion events. */
12
+ const streamEventSchema = eventWithDataSchema.or(eventWithDataSchema.extend({
13
+ type: z.literal("session.completed"),
14
+ data: z.record(z.string(), z.unknown()).default({})
15
+ }));
12
16
  /** Parses validated newline-delimited events from a response body. */
13
17
  async function* readEvents(body) {
14
18
  const reader = body.getReader();
@@ -1 +1 @@
1
- {"version":3,"file":"client-events.mjs","names":[],"sources":["../src/client-events.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/** Validates one newline-delimited event from a Factory execution stream. */\nexport const streamEventSchema = z.object({\n type: z.string(),\n data: z.record(z.string(), z.unknown()),\n meta: z.object({ id: z.string().optional(), at: z.string().optional() }).optional(),\n});\n/** One validated event received from a Factory execution stream. */\nexport type StreamEvent = z.infer<typeof streamEventSchema>;\n\n/** Parses validated newline-delimited events from a response body. */\nexport async function* readEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<StreamEvent> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let pending = \"\";\n try {\n for (;;) {\n const { done, value } = await reader.read();\n pending += done ? decoder.decode() : decoder.decode(value, { stream: true });\n let newline: number;\n while ((newline = pending.indexOf(\"\\n\")) >= 0) {\n const line = pending.slice(0, newline).trim();\n pending = pending.slice(newline + 1);\n if (line) yield streamEventSchema.parse(JSON.parse(line));\n }\n if (pending.length > 2_000_000) throw new Error(\"Stream event exceeds the 2 MB limit\");\n if (done) {\n if (pending.trim()) yield streamEventSchema.parse(JSON.parse(pending));\n return;\n }\n }\n } finally {\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n}\n"],"mappings":";;;AAGA,MAAa,oBAAoB,EAAE,OAAO;CACxC,MAAM,EAAE,OAAO;CACf,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CACtC,MAAM,EAAE,OAAO;EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS;EAAG,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS;CAAE,CAAC,CAAC,CAAC,SAAS;AACpF,CAAC;;AAKD,gBAAuB,WAAW,MAA+D;CAC/F,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,UAAU;CACd,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,WAAW,OAAO,QAAQ,OAAO,IAAI,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;GAC3E,IAAI;GACJ,QAAQ,UAAU,QAAQ,QAAQ,IAAI,MAAM,GAAG;IAC7C,MAAM,OAAO,QAAQ,MAAM,GAAG,OAAO,CAAC,CAAC,KAAK;IAC5C,UAAU,QAAQ,MAAM,UAAU,CAAC;IACnC,IAAI,MAAM,MAAM,kBAAkB,MAAM,KAAK,MAAM,IAAI,CAAC;GAC1D;GACA,IAAI,QAAQ,SAAS,KAAW,MAAM,IAAI,MAAM,qCAAqC;GACrF,IAAI,MAAM;IACR,IAAI,QAAQ,KAAK,GAAG,MAAM,kBAAkB,MAAM,KAAK,MAAM,OAAO,CAAC;IACrE;GACF;EACF;CACF,UAAU;EACR,MAAM,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;EACpC,OAAO,YAAY;CACrB;AACF"}
1
+ {"version":3,"file":"client-events.mjs","names":[],"sources":["../src/client-events.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst eventWithDataSchema = z.object({\n type: z.string(),\n data: z.record(z.string(), z.unknown()),\n meta: z.object({ id: z.string().optional(), at: z.string().optional() }).optional(),\n});\n/** Validates an execution stream event, normalizing payloadless completion events. */\nexport const streamEventSchema = eventWithDataSchema.or(\n eventWithDataSchema.extend({\n type: z.literal(\"session.completed\"),\n data: z.record(z.string(), z.unknown()).default({}),\n }),\n);\n/** One validated event received from a Factory execution stream. */\nexport type StreamEvent = z.infer<typeof streamEventSchema>;\n\n/** Parses validated newline-delimited events from a response body. */\nexport async function* readEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<StreamEvent> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let pending = \"\";\n try {\n for (;;) {\n const { done, value } = await reader.read();\n pending += done ? decoder.decode() : decoder.decode(value, { stream: true });\n let newline: number;\n while ((newline = pending.indexOf(\"\\n\")) >= 0) {\n const line = pending.slice(0, newline).trim();\n pending = pending.slice(newline + 1);\n if (line) yield streamEventSchema.parse(JSON.parse(line));\n }\n if (pending.length > 2_000_000) throw new Error(\"Stream event exceeds the 2 MB limit\");\n if (done) {\n if (pending.trim()) yield streamEventSchema.parse(JSON.parse(pending));\n return;\n }\n }\n } finally {\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n}\n"],"mappings":";;AAEA,MAAM,sBAAsB,EAAE,OAAO;CACnC,MAAM,EAAE,OAAO;CACf,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CACtC,MAAM,EAAE,OAAO;EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS;EAAG,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS;CAAE,CAAC,CAAC,CAAC,SAAS;AACpF,CAAC;;AAED,MAAa,oBAAoB,oBAAoB,GACnD,oBAAoB,OAAO;CACzB,MAAM,EAAE,QAAQ,mBAAmB;CACnC,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AACpD,CAAC,CACH;;AAKA,gBAAuB,WAAW,MAA+D;CAC/F,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,UAAU;CACd,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,WAAW,OAAO,QAAQ,OAAO,IAAI,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;GAC3E,IAAI;GACJ,QAAQ,UAAU,QAAQ,QAAQ,IAAI,MAAM,GAAG;IAC7C,MAAM,OAAO,QAAQ,MAAM,GAAG,OAAO,CAAC,CAAC,KAAK;IAC5C,UAAU,QAAQ,MAAM,UAAU,CAAC;IACnC,IAAI,MAAM,MAAM,kBAAkB,MAAM,KAAK,MAAM,IAAI,CAAC;GAC1D;GACA,IAAI,QAAQ,SAAS,KAAW,MAAM,IAAI,MAAM,qCAAqC;GACrF,IAAI,MAAM;IACR,IAAI,QAAQ,KAAK,GAAG,MAAM,kBAAkB,MAAM,KAAK,MAAM,OAAO,CAAC;IACrE;GACF;EACF;CACF,UAAU;EACR,MAAM,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;EACpC,OAAO,YAAY;CACrB;AACF"}
@@ -123,7 +123,7 @@ async function hydrateCurrent(client, options) {
123
123
  401,
124
124
  403
125
125
  ].includes(error.status)) return false;
126
- if (error instanceof ApiError && options.taskId && error.status === 404) return false;
126
+ if (error instanceof ApiError && error.status === 404) return false;
127
127
  if (error instanceof ApiError && error.status === 409) {
128
128
  await options.onConflict();
129
129
  return false;
@@ -189,7 +189,13 @@ async function followSessionStream(client, options) {
189
189
  401,
190
190
  403
191
191
  ].includes(error.status)) return;
192
- if (error instanceof ApiError && options.taskId && error.status === 404) return;
192
+ if (error instanceof ApiError && error.status === 404) {
193
+ if (transcript.state === "running") {
194
+ transcript.state = "connecting";
195
+ options.onChange();
196
+ }
197
+ return;
198
+ }
193
199
  if (error instanceof ApiError && error.status === 409) {
194
200
  await options.onConflict();
195
201
  return;