@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
@@ -0,0 +1,120 @@
1
+ import { z } from "zod";
2
+ //#region src/session-review.d.ts
3
+ /** Validates one recorded check without inferring success from a completed Task. */
4
+ declare const sessionReviewCheckSchema: z.ZodObject<{
5
+ source: z.ZodString;
6
+ revision: z.ZodNullable<z.ZodString>;
7
+ id: z.ZodString;
8
+ title: z.ZodString;
9
+ status: z.ZodEnum<{
10
+ unknown: "unknown";
11
+ running: "running";
12
+ failed: "failed";
13
+ passed: "passed";
14
+ skipped: "skipped";
15
+ }>;
16
+ command: z.ZodOptional<z.ZodString>;
17
+ details: z.ZodOptional<z.ZodString>;
18
+ }, z.core.$strict>;
19
+ /** Validates a screenshot supplied by the application's authorized review reader. */
20
+ declare const sessionReviewScreenshotSchema: z.ZodObject<{
21
+ source: z.ZodString;
22
+ revision: z.ZodNullable<z.ZodString>;
23
+ id: z.ZodString;
24
+ url: z.ZodString;
25
+ caption: z.ZodString;
26
+ state: z.ZodOptional<z.ZodEnum<{
27
+ before: "before";
28
+ after: "after";
29
+ }>>;
30
+ comparisonId: z.ZodOptional<z.ZodString>;
31
+ width: z.ZodOptional<z.ZodNumber>;
32
+ height: z.ZodOptional<z.ZodNumber>;
33
+ }, z.core.$strict>;
34
+ /** Validates a reported issue and the evidence or uncertainty behind it. */
35
+ declare const sessionReviewIssueSchema: z.ZodObject<{
36
+ source: z.ZodString;
37
+ revision: z.ZodNullable<z.ZodString>;
38
+ id: z.ZodString;
39
+ title: z.ZodString;
40
+ body: z.ZodString;
41
+ status: z.ZodEnum<{
42
+ open: "open";
43
+ resolved: "resolved";
44
+ }>;
45
+ path: z.ZodOptional<z.ZodString>;
46
+ line: z.ZodOptional<z.ZodNumber>;
47
+ priority: z.ZodOptional<z.ZodString>;
48
+ evidence: z.ZodOptional<z.ZodString>;
49
+ limitations: z.ZodOptional<z.ZodString>;
50
+ }, z.core.$strict>;
51
+ /** Validates a review projection with explicit distinctions between missing and empty evidence. */
52
+ declare const sessionReviewSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
53
+ state: z.ZodLiteral<"ready">;
54
+ summary: z.ZodOptional<z.ZodString>;
55
+ checks: z.ZodNullable<z.ZodReadonly<z.ZodArray<z.ZodObject<{
56
+ source: z.ZodString;
57
+ revision: z.ZodNullable<z.ZodString>;
58
+ id: z.ZodString;
59
+ title: z.ZodString;
60
+ status: z.ZodEnum<{
61
+ unknown: "unknown";
62
+ running: "running";
63
+ failed: "failed";
64
+ passed: "passed";
65
+ skipped: "skipped";
66
+ }>;
67
+ command: z.ZodOptional<z.ZodString>;
68
+ details: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strict>>>>;
70
+ screenshots: z.ZodNullable<z.ZodReadonly<z.ZodArray<z.ZodObject<{
71
+ source: z.ZodString;
72
+ revision: z.ZodNullable<z.ZodString>;
73
+ id: z.ZodString;
74
+ url: z.ZodString;
75
+ caption: z.ZodString;
76
+ state: z.ZodOptional<z.ZodEnum<{
77
+ before: "before";
78
+ after: "after";
79
+ }>>;
80
+ comparisonId: z.ZodOptional<z.ZodString>;
81
+ width: z.ZodOptional<z.ZodNumber>;
82
+ height: z.ZodOptional<z.ZodNumber>;
83
+ }, z.core.$strict>>>>;
84
+ issues: z.ZodNullable<z.ZodReadonly<z.ZodArray<z.ZodObject<{
85
+ source: z.ZodString;
86
+ revision: z.ZodNullable<z.ZodString>;
87
+ id: z.ZodString;
88
+ title: z.ZodString;
89
+ body: z.ZodString;
90
+ status: z.ZodEnum<{
91
+ open: "open";
92
+ resolved: "resolved";
93
+ }>;
94
+ path: z.ZodOptional<z.ZodString>;
95
+ line: z.ZodOptional<z.ZodNumber>;
96
+ priority: z.ZodOptional<z.ZodString>;
97
+ evidence: z.ZodOptional<z.ZodString>;
98
+ limitations: z.ZodOptional<z.ZodString>;
99
+ }, z.core.$strict>>>>;
100
+ limitations: z.ZodReadonly<z.ZodArray<z.ZodString>>;
101
+ truncated: z.ZodBoolean;
102
+ executionId: z.ZodNullable<z.ZodString>;
103
+ capturedAt: z.ZodISODateTime;
104
+ }, z.core.$strict>, z.ZodObject<{
105
+ state: z.ZodLiteral<"unavailable">;
106
+ reason: z.ZodString;
107
+ executionId: z.ZodNullable<z.ZodString>;
108
+ capturedAt: z.ZodISODateTime;
109
+ }, z.core.$strict>], "state">;
110
+ /** Recorded check displayed in the operator review screen. */
111
+ type SessionReviewCheck = z.infer<typeof sessionReviewCheckSchema>;
112
+ /** Screenshot and optional before/after grouping supplied by an application. */
113
+ type SessionReviewScreenshot = z.infer<typeof sessionReviewScreenshotSchema>;
114
+ /** Reported issue with its recorded resolution state and supporting evidence. */
115
+ type SessionReviewIssue = z.infer<typeof sessionReviewIssueSchema>;
116
+ /** Provider-neutral evidence for reviewing an operator session. */
117
+ type SessionReview = z.infer<typeof sessionReviewSchema>;
118
+ //#endregion
119
+ export { SessionReview, SessionReviewCheck, SessionReviewIssue, SessionReviewScreenshot, sessionReviewCheckSchema, sessionReviewIssueSchema, sessionReviewSchema, sessionReviewScreenshotSchema };
120
+ //# sourceMappingURL=session-review.d.mts.map
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ //#region src/session-review.ts
3
+ const text = z.string().min(1);
4
+ const source = {
5
+ source: text.describe("Human-readable origin of the evidence, including attempt when known."),
6
+ revision: text.nullable().describe("Exact provider-owned revision, or null when not recorded.")
7
+ };
8
+ /** Validates one recorded check without inferring success from a completed Task. */
9
+ const sessionReviewCheckSchema = z.strictObject({
10
+ id: text.describe("Stable check identity within this review."),
11
+ title: text.describe("Check name."),
12
+ status: z.enum([
13
+ "passed",
14
+ "failed",
15
+ "skipped",
16
+ "running",
17
+ "unknown"
18
+ ]).describe("Recorded outcome."),
19
+ command: text.optional().describe("Exact command, when recorded."),
20
+ details: text.optional().describe("Recorded output or explanation, rendered as Markdown."),
21
+ ...source
22
+ });
23
+ const imageUrl = z.string().refine((value) => {
24
+ if (/[\s\\]/u.test(value)) return false;
25
+ if (value.startsWith("/") && !value.startsWith("//")) return true;
26
+ try {
27
+ const url = new URL(value);
28
+ return ["https:", "http:"].includes(url.protocol) && !url.username && !url.password;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }, "Use an HTTP(S) image URL or an absolute same-origin path");
33
+ /** Validates a screenshot supplied by the application's authorized review reader. */
34
+ const sessionReviewScreenshotSchema = z.strictObject({
35
+ id: text.describe("Stable image identity within this review."),
36
+ url: imageUrl.describe("Browser-readable image URL; the application owns access and expiry."),
37
+ caption: text.describe("Description of what the screenshot demonstrates."),
38
+ state: z.enum(["before", "after"]).optional().describe("Side of a before/after comparison."),
39
+ comparisonId: text.optional().describe("Groups before and after images of the same scenario."),
40
+ width: z.number().int().positive().optional().describe("Captured pixel width."),
41
+ height: z.number().int().positive().optional().describe("Captured pixel height."),
42
+ ...source
43
+ });
44
+ /** Validates a reported issue and the evidence or uncertainty behind it. */
45
+ const sessionReviewIssueSchema = z.strictObject({
46
+ id: text.describe("Stable issue identity within this review."),
47
+ title: text.describe("Short problem description."),
48
+ body: text.describe("Impact and explanation, rendered as Markdown."),
49
+ status: z.enum(["open", "resolved"]).describe("Recorded resolution state, not inferred from Task success."),
50
+ path: text.optional().describe("Repository-relative affected file."),
51
+ line: z.number().int().positive().optional().describe("Affected line in the recorded revision."),
52
+ priority: text.optional().describe("Application-defined urgency label."),
53
+ evidence: text.optional().describe("What was checked and what it showed."),
54
+ limitations: text.optional().describe("What remains uncertain or unchecked."),
55
+ ...source
56
+ });
57
+ const common = {
58
+ executionId: text.nullable().describe("Observed session execution, or null before execution."),
59
+ capturedAt: z.iso.datetime().describe("When this evidence projection was read.")
60
+ };
61
+ /** Validates a review projection with explicit distinctions between missing and empty evidence. */
62
+ const sessionReviewSchema = z.discriminatedUnion("state", [z.strictObject({
63
+ ...common,
64
+ state: z.literal("ready").describe("The configured review reader completed."),
65
+ summary: text.optional().describe("Application-provided summary of the work."),
66
+ checks: z.array(sessionReviewCheckSchema).readonly().nullable().describe("Recorded checks; null means not recorded, not passing."),
67
+ screenshots: z.array(sessionReviewScreenshotSchema).readonly().nullable().describe("Recorded screenshots; null means no capture report is available."),
68
+ issues: z.array(sessionReviewIssueSchema).readonly().nullable().describe("Reported issues; null means no issue report is available."),
69
+ limitations: z.array(text).readonly().describe("Missing evidence, unsupported sources, or other review limitations."),
70
+ truncated: z.boolean().describe("The reader omitted evidence because of its configured limits.")
71
+ }), z.strictObject({
72
+ ...common,
73
+ state: z.literal("unavailable").describe("Review evidence could not be read."),
74
+ reason: text.describe("Why review evidence is unavailable.")
75
+ })]);
76
+ //#endregion
77
+ export { sessionReviewCheckSchema, sessionReviewIssueSchema, sessionReviewSchema, sessionReviewScreenshotSchema };
78
+
79
+ //# sourceMappingURL=session-review.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-review.mjs","names":[],"sources":["../src/session-review.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst text = z.string().min(1);\nconst source = {\n source: text.describe(\"Human-readable origin of the evidence, including attempt when known.\"),\n revision: text.nullable().describe(\"Exact provider-owned revision, or null when not recorded.\"),\n};\n\n/** Validates one recorded check without inferring success from a completed Task. */\nexport const sessionReviewCheckSchema = z.strictObject({\n id: text.describe(\"Stable check identity within this review.\"),\n title: text.describe(\"Check name.\"),\n status: z\n .enum([\"passed\", \"failed\", \"skipped\", \"running\", \"unknown\"])\n .describe(\"Recorded outcome.\"),\n command: text.optional().describe(\"Exact command, when recorded.\"),\n details: text.optional().describe(\"Recorded output or explanation, rendered as Markdown.\"),\n ...source,\n});\n\nconst imageUrl = z.string().refine((value) => {\n if (/[\\s\\\\]/u.test(value)) return false;\n if (value.startsWith(\"/\") && !value.startsWith(\"//\")) return true;\n try {\n const url = new URL(value);\n return [\"https:\", \"http:\"].includes(url.protocol) && !url.username && !url.password;\n } catch {\n return false;\n }\n}, \"Use an HTTP(S) image URL or an absolute same-origin path\");\n\n/** Validates a screenshot supplied by the application's authorized review reader. */\nexport const sessionReviewScreenshotSchema = z.strictObject({\n id: text.describe(\"Stable image identity within this review.\"),\n url: imageUrl.describe(\"Browser-readable image URL; the application owns access and expiry.\"),\n caption: text.describe(\"Description of what the screenshot demonstrates.\"),\n state: z.enum([\"before\", \"after\"]).optional().describe(\"Side of a before/after comparison.\"),\n comparisonId: text.optional().describe(\"Groups before and after images of the same scenario.\"),\n width: z.number().int().positive().optional().describe(\"Captured pixel width.\"),\n height: z.number().int().positive().optional().describe(\"Captured pixel height.\"),\n ...source,\n});\n\n/** Validates a reported issue and the evidence or uncertainty behind it. */\nexport const sessionReviewIssueSchema = z.strictObject({\n id: text.describe(\"Stable issue identity within this review.\"),\n title: text.describe(\"Short problem description.\"),\n body: text.describe(\"Impact and explanation, rendered as Markdown.\"),\n status: z\n .enum([\"open\", \"resolved\"])\n .describe(\"Recorded resolution state, not inferred from Task success.\"),\n path: text.optional().describe(\"Repository-relative affected file.\"),\n line: z.number().int().positive().optional().describe(\"Affected line in the recorded revision.\"),\n priority: text.optional().describe(\"Application-defined urgency label.\"),\n evidence: text.optional().describe(\"What was checked and what it showed.\"),\n limitations: text.optional().describe(\"What remains uncertain or unchecked.\"),\n ...source,\n});\n\nconst common = {\n executionId: text.nullable().describe(\"Observed session execution, or null before execution.\"),\n capturedAt: z.iso.datetime().describe(\"When this evidence projection was read.\"),\n};\n\n/** Validates a review projection with explicit distinctions between missing and empty evidence. */\nexport const sessionReviewSchema = z.discriminatedUnion(\"state\", [\n z.strictObject({\n ...common,\n state: z.literal(\"ready\").describe(\"The configured review reader completed.\"),\n summary: text.optional().describe(\"Application-provided summary of the work.\"),\n checks: z\n .array(sessionReviewCheckSchema)\n .readonly()\n .nullable()\n .describe(\"Recorded checks; null means not recorded, not passing.\"),\n screenshots: z\n .array(sessionReviewScreenshotSchema)\n .readonly()\n .nullable()\n .describe(\"Recorded screenshots; null means no capture report is available.\"),\n issues: z\n .array(sessionReviewIssueSchema)\n .readonly()\n .nullable()\n .describe(\"Reported issues; null means no issue report is available.\"),\n limitations: z\n .array(text)\n .readonly()\n .describe(\"Missing evidence, unsupported sources, or other review limitations.\"),\n truncated: z\n .boolean()\n .describe(\"The reader omitted evidence because of its configured limits.\"),\n }),\n z.strictObject({\n ...common,\n state: z.literal(\"unavailable\").describe(\"Review evidence could not be read.\"),\n reason: text.describe(\"Why review evidence is unavailable.\"),\n }),\n]);\n\n/** Recorded check displayed in the operator review screen. */\nexport type SessionReviewCheck = z.infer<typeof sessionReviewCheckSchema>;\n/** Screenshot and optional before/after grouping supplied by an application. */\nexport type SessionReviewScreenshot = z.infer<typeof sessionReviewScreenshotSchema>;\n/** Reported issue with its recorded resolution state and supporting evidence. */\nexport type SessionReviewIssue = z.infer<typeof sessionReviewIssueSchema>;\n/** Provider-neutral evidence for reviewing an operator session. */\nexport type SessionReview = z.infer<typeof sessionReviewSchema>;\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { signalIdSchema } from "./schema/id.mjs";
2
2
  import { repositoryIdsForSession } from "./eve/task-session.mjs";
3
- import { defineHook } from "eve/hooks";
4
3
  import { ForbiddenError } from "eve/channels/auth";
4
+ import { defineHook } from "eve/hooks";
5
5
  //#region src/signal-triage.ts
6
6
  const signalHeader = "x-factory-signal-id";
7
7
  const attemptHeader = "x-factory-signal-attempt";
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { DecidedSignal, PendingSignal, Signal, SignalOutcome, SignalSource, SignalTriage, signalOutcomeSchema, signalSchema, signalSourceSchema, signalTriageSchema, standardSignalSources } from "./schema/signal.mjs";
2
3
  import { SignalId, signalIdSchema } from "./schema/id.mjs";
3
4
  import { CreateSignalInput, DecideSignalInput, FactoryStores } from "./store/engine.mjs";
package/dist/stall.d.mts CHANGED
@@ -10,6 +10,8 @@ interface StallSweepOptions {
10
10
  now?: DateClock;
11
11
  /** A dispatcher-wide snapshot avoids another complete task read. */
12
12
  tasks?: readonly Task[];
13
+ /** Recover a saved result before marking work stalled; a throw defers stall recovery. */
14
+ recoverResult?: (task: Task) => Promise<boolean>;
13
15
  /** Optional workflow policy that cancels a stalled task instead of retrying it. */
14
16
  cancelStalled?: (task: Task, tasks: readonly Task[]) => MaybePromise<string | null>;
15
17
  }
@@ -30,7 +32,8 @@ declare function stallWindowMinutes(policy: ExecutionPolicy, kind: Task["kind"])
30
32
  * re-queued for a replacement attempt while attempts remain, and failed once
31
33
  * they are exhausted. Only `running` tasks can stall: a task whose result has
32
34
  * already been recorded (verifying, or terminal) is never retried by this
33
- * pass. Workflows explicitly waiting for messages are skipped.
35
+ * pass. Workflows explicitly waiting for messages are skipped. Configured result recovery
36
+ * runs before marking a Task stalled; an error defers that attempt to a later pass.
34
37
  */
35
38
  declare function sweepTaskStalls(stores: TaskStallSweepStores, options: StallSweepOptions): Promise<TaskStallSweepResult>;
36
39
  //#endregion
package/dist/stall.mjs CHANGED
@@ -9,7 +9,8 @@ function stallWindowMinutes(policy, kind) {
9
9
  * re-queued for a replacement attempt while attempts remain, and failed once
10
10
  * they are exhausted. Only `running` tasks can stall: a task whose result has
11
11
  * already been recorded (verifying, or terminal) is never retried by this
12
- * pass. Workflows explicitly waiting for messages are skipped.
12
+ * pass. Workflows explicitly waiting for messages are skipped. Configured result recovery
13
+ * runs before marking a Task stalled; an error defers that attempt to a later pass.
13
14
  */
14
15
  async function sweepTaskStalls(stores, options) {
15
16
  const nowMs = (options.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
@@ -27,9 +28,12 @@ async function sweepTaskStalls(stores, options) {
27
28
  const sinceProgress = nowMs - Date.parse(task.lastProgressAt ?? task.updatedAt);
28
29
  if (sinceProgress < minutes * 6e4) continue;
29
30
  try {
31
+ if (await options.recoverResult?.(task)) continue;
30
32
  const stalled = await stores.tasks.transition(task.id, "stalled", {
31
33
  reason: `no progress for ${Math.floor(sinceProgress / 6e4)} minutes (window ${minutes})`,
32
- expectFrom: "running"
34
+ expectFrom: "running",
35
+ expectAttempt: task.attempt,
36
+ expectExecution: task.execution
33
37
  });
34
38
  result.stalled.push(task.id);
35
39
  newlyStalled.push(stalled);
@@ -1 +1 @@
1
- {"version":3,"file":"stall.mjs","names":[],"sources":["../src/stall.ts"],"sourcesContent":["import type { ExecutionPolicy } from \"./schema/factory-config\";\nimport type { DateClock, MaybePromise } from \"./schema/common\";\nimport type { TaskId } from \"./schema/id\";\nimport type { Task } from \"./schema/task\";\nimport type { FactoryStores } from \"./store/engine\";\n\n/** Execution policy, clock, snapshot, and optional workflow cancellation hook for stall recovery. */\nexport interface StallSweepOptions {\n policy: ExecutionPolicy;\n now?: DateClock;\n /** A dispatcher-wide snapshot avoids another complete task read. */\n tasks?: readonly Task[];\n /** Optional workflow policy that cancels a stalled task instead of retrying it. */\n cancelStalled?: (task: Task, tasks: readonly Task[]) => MaybePromise<string | null>;\n}\n\n/** Task IDs transitioned at each stage of one stall-recovery pass. */\nexport interface TaskStallSweepResult {\n readonly stalled: readonly TaskId[];\n readonly requeued: readonly TaskId[];\n readonly failed: readonly TaskId[];\n readonly cancelled: readonly TaskId[];\n}\n\n/** Task capability required to detect and recover stalled work. */\nexport type TaskStallSweepStores = Pick<FactoryStores, \"tasks\">;\n\n/** Resolve the progress timeout for a Task kind from the configured execution policy. */\nexport function stallWindowMinutes(policy: ExecutionPolicy, kind: Task[\"kind\"]): number {\n return policy.stallMinutesByKind[kind] ?? policy.stallMinutes;\n}\n\n/**\n * The reconciler's stall pass. A running task whose execution has not\n * progressed within its stall window is marked stalled; a stalled task is\n * re-queued for a replacement attempt while attempts remain, and failed once\n * they are exhausted. Only `running` tasks can stall: a task whose result has\n * already been recorded (verifying, or terminal) is never retried by this\n * pass. Workflows explicitly waiting for messages are skipped.\n */\nexport async function sweepTaskStalls(\n stores: TaskStallSweepStores,\n options: StallSweepOptions,\n): Promise<TaskStallSweepResult> {\n const nowMs = (options.now ?? (() => new Date()))().getTime();\n const result: {\n stalled: TaskId[];\n requeued: TaskId[];\n failed: TaskId[];\n cancelled: TaskId[];\n } = {\n stalled: [],\n requeued: [],\n failed: [],\n cancelled: [],\n };\n const tasks = options.tasks ?? (await stores.tasks.list());\n const newlyStalled: Task[] = [];\n\n for (const task of tasks) {\n if (task.waitingForMessages || task.state !== \"running\") {\n continue;\n }\n const minutes = stallWindowMinutes(options.policy, task.kind);\n const sinceProgress = nowMs - Date.parse(task.lastProgressAt ?? task.updatedAt);\n if (sinceProgress < minutes * 60_000) {\n continue;\n }\n // A concurrent writer may have moved the task; skip rather than abort.\n try {\n const stalled = await stores.tasks.transition(task.id, \"stalled\", {\n reason: `no progress for ${Math.floor(sinceProgress / 60_000)} minutes (window ${minutes})`,\n expectFrom: \"running\",\n });\n result.stalled.push(task.id);\n newlyStalled.push(stalled);\n } catch {\n if (options.tasks !== undefined) {\n const current = await stores.tasks.get(task.id).catch(() => null);\n if (current?.state === \"stalled\") newlyStalled.push(current);\n }\n continue;\n }\n }\n\n // Resolve every stalled task, including ones stalled by an earlier pass that\n // crashed before reaching this step.\n const stalledCandidates =\n options.tasks === undefined\n ? await stores.tasks.list()\n : [...tasks.filter((task) => task.state === \"stalled\"), ...newlyStalled];\n for (const task of stalledCandidates) {\n if (task.waitingForMessages || task.state !== \"stalled\") {\n continue;\n }\n const cancellationReason = (await options.cancelStalled?.(task, tasks)) ?? null;\n try {\n if (cancellationReason !== null) {\n await stores.tasks.transition(task.id, \"cancelled\", {\n reason: cancellationReason,\n expectFrom: \"stalled\",\n });\n result.cancelled.push(task.id);\n } else if (task.attempt < options.policy.maxAttempts) {\n await stores.tasks.transition(task.id, \"queued\", {\n reason: `stalled on attempt ${task.attempt} of ${options.policy.maxAttempts}; replacing`,\n phase: \"retry\",\n expectFrom: \"stalled\",\n });\n result.requeued.push(task.id);\n } else {\n await stores.tasks.transition(task.id, \"failed\", {\n reason: `stalled on attempt ${task.attempt} of ${options.policy.maxAttempts}; attempts exhausted`,\n phase: \"retry\",\n expectFrom: \"stalled\",\n });\n result.failed.push(task.id);\n }\n } catch {\n continue;\n }\n }\n return result;\n}\n"],"mappings":";;AA4BA,SAAgB,mBAAmB,QAAyB,MAA4B;CACtF,OAAO,OAAO,mBAAmB,SAAS,OAAO;AACnD;;;;;;;;;AAUA,eAAsB,gBACpB,QACA,SAC+B;CAC/B,MAAM,SAAS,QAAQ,8BAAc,IAAI,KAAK,GAAA,CAAI,CAAC,CAAC,QAAQ;CAC5D,MAAM,SAKF;EACF,SAAS,CAAC;EACV,UAAU,CAAC;EACX,QAAQ,CAAC;EACT,WAAW,CAAC;CACd;CACA,MAAM,QAAQ,QAAQ,SAAU,MAAM,OAAO,MAAM,KAAK;CACxD,MAAM,eAAuB,CAAC;CAE9B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,sBAAsB,KAAK,UAAU,WAC5C;EAEF,MAAM,UAAU,mBAAmB,QAAQ,QAAQ,KAAK,IAAI;EAC5D,MAAM,gBAAgB,QAAQ,KAAK,MAAM,KAAK,kBAAkB,KAAK,SAAS;EAC9E,IAAI,gBAAgB,UAAU,KAC5B;EAGF,IAAI;GACF,MAAM,UAAU,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,WAAW;IAChE,QAAQ,mBAAmB,KAAK,MAAM,gBAAgB,GAAM,EAAE,mBAAmB,QAAQ;IACzF,YAAY;GACd,CAAC;GACD,OAAO,QAAQ,KAAK,KAAK,EAAE;GAC3B,aAAa,KAAK,OAAO;EAC3B,QAAQ;GACN,IAAI,QAAQ,UAAU,KAAA,GAAW;IAC/B,MAAM,UAAU,MAAM,OAAO,MAAM,IAAI,KAAK,EAAE,CAAC,CAAC,YAAY,IAAI;IAChE,IAAI,SAAS,UAAU,WAAW,aAAa,KAAK,OAAO;GAC7D;GACA;EACF;CACF;CAIA,MAAM,oBACJ,QAAQ,UAAU,KAAA,IACd,MAAM,OAAO,MAAM,KAAK,IACxB,CAAC,GAAG,MAAM,QAAQ,SAAS,KAAK,UAAU,SAAS,GAAG,GAAG,YAAY;CAC3E,KAAK,MAAM,QAAQ,mBAAmB;EACpC,IAAI,KAAK,sBAAsB,KAAK,UAAU,WAC5C;EAEF,MAAM,qBAAsB,MAAM,QAAQ,gBAAgB,MAAM,KAAK,KAAM;EAC3E,IAAI;GACF,IAAI,uBAAuB,MAAM;IAC/B,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,aAAa;KAClD,QAAQ;KACR,YAAY;IACd,CAAC;IACD,OAAO,UAAU,KAAK,KAAK,EAAE;GAC/B,OAAO,IAAI,KAAK,UAAU,QAAQ,OAAO,aAAa;IACpD,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,UAAU;KAC/C,QAAQ,sBAAsB,KAAK,QAAQ,MAAM,QAAQ,OAAO,YAAY;KAC5E,OAAO;KACP,YAAY;IACd,CAAC;IACD,OAAO,SAAS,KAAK,KAAK,EAAE;GAC9B,OAAO;IACL,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,UAAU;KAC/C,QAAQ,sBAAsB,KAAK,QAAQ,MAAM,QAAQ,OAAO,YAAY;KAC5E,OAAO;KACP,YAAY;IACd,CAAC;IACD,OAAO,OAAO,KAAK,KAAK,EAAE;GAC5B;EACF,QAAQ;GACN;EACF;CACF;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"stall.mjs","names":[],"sources":["../src/stall.ts"],"sourcesContent":["import type { ExecutionPolicy } from \"./schema/factory-config\";\nimport type { DateClock, MaybePromise } from \"./schema/common\";\nimport type { TaskId } from \"./schema/id\";\nimport type { Task } from \"./schema/task\";\nimport type { FactoryStores } from \"./store/engine\";\n\n/** Execution policy, clock, snapshot, and optional workflow cancellation hook for stall recovery. */\nexport interface StallSweepOptions {\n policy: ExecutionPolicy;\n now?: DateClock;\n /** A dispatcher-wide snapshot avoids another complete task read. */\n tasks?: readonly Task[];\n /** Recover a saved result before marking work stalled; a throw defers stall recovery. */\n recoverResult?: (task: Task) => Promise<boolean>;\n /** Optional workflow policy that cancels a stalled task instead of retrying it. */\n cancelStalled?: (task: Task, tasks: readonly Task[]) => MaybePromise<string | null>;\n}\n\n/** Task IDs transitioned at each stage of one stall-recovery pass. */\nexport interface TaskStallSweepResult {\n readonly stalled: readonly TaskId[];\n readonly requeued: readonly TaskId[];\n readonly failed: readonly TaskId[];\n readonly cancelled: readonly TaskId[];\n}\n\n/** Task capability required to detect and recover stalled work. */\nexport type TaskStallSweepStores = Pick<FactoryStores, \"tasks\">;\n\n/** Resolve the progress timeout for a Task kind from the configured execution policy. */\nexport function stallWindowMinutes(policy: ExecutionPolicy, kind: Task[\"kind\"]): number {\n return policy.stallMinutesByKind[kind] ?? policy.stallMinutes;\n}\n\n/**\n * The reconciler's stall pass. A running task whose execution has not\n * progressed within its stall window is marked stalled; a stalled task is\n * re-queued for a replacement attempt while attempts remain, and failed once\n * they are exhausted. Only `running` tasks can stall: a task whose result has\n * already been recorded (verifying, or terminal) is never retried by this\n * pass. Workflows explicitly waiting for messages are skipped. Configured result recovery\n * runs before marking a Task stalled; an error defers that attempt to a later pass.\n */\nexport async function sweepTaskStalls(\n stores: TaskStallSweepStores,\n options: StallSweepOptions,\n): Promise<TaskStallSweepResult> {\n const nowMs = (options.now ?? (() => new Date()))().getTime();\n const result: {\n stalled: TaskId[];\n requeued: TaskId[];\n failed: TaskId[];\n cancelled: TaskId[];\n } = {\n stalled: [],\n requeued: [],\n failed: [],\n cancelled: [],\n };\n const tasks = options.tasks ?? (await stores.tasks.list());\n const newlyStalled: Task[] = [];\n\n for (const task of tasks) {\n if (task.waitingForMessages || task.state !== \"running\") {\n continue;\n }\n const minutes = stallWindowMinutes(options.policy, task.kind);\n const sinceProgress = nowMs - Date.parse(task.lastProgressAt ?? task.updatedAt);\n if (sinceProgress < minutes * 60_000) {\n continue;\n }\n // A concurrent writer may have moved the task; skip rather than abort.\n try {\n if (await options.recoverResult?.(task)) continue;\n const stalled = await stores.tasks.transition(task.id, \"stalled\", {\n reason: `no progress for ${Math.floor(sinceProgress / 60_000)} minutes (window ${minutes})`,\n expectFrom: \"running\",\n expectAttempt: task.attempt,\n expectExecution: task.execution,\n });\n result.stalled.push(task.id);\n newlyStalled.push(stalled);\n } catch {\n if (options.tasks !== undefined) {\n const current = await stores.tasks.get(task.id).catch(() => null);\n if (current?.state === \"stalled\") newlyStalled.push(current);\n }\n continue;\n }\n }\n\n // Resolve every stalled task, including ones stalled by an earlier pass that\n // crashed before reaching this step.\n const stalledCandidates =\n options.tasks === undefined\n ? await stores.tasks.list()\n : [...tasks.filter((task) => task.state === \"stalled\"), ...newlyStalled];\n for (const task of stalledCandidates) {\n if (task.waitingForMessages || task.state !== \"stalled\") {\n continue;\n }\n const cancellationReason = (await options.cancelStalled?.(task, tasks)) ?? null;\n try {\n if (cancellationReason !== null) {\n await stores.tasks.transition(task.id, \"cancelled\", {\n reason: cancellationReason,\n expectFrom: \"stalled\",\n });\n result.cancelled.push(task.id);\n } else if (task.attempt < options.policy.maxAttempts) {\n await stores.tasks.transition(task.id, \"queued\", {\n reason: `stalled on attempt ${task.attempt} of ${options.policy.maxAttempts}; replacing`,\n phase: \"retry\",\n expectFrom: \"stalled\",\n });\n result.requeued.push(task.id);\n } else {\n await stores.tasks.transition(task.id, \"failed\", {\n reason: `stalled on attempt ${task.attempt} of ${options.policy.maxAttempts}; attempts exhausted`,\n phase: \"retry\",\n expectFrom: \"stalled\",\n });\n result.failed.push(task.id);\n }\n } catch {\n continue;\n }\n }\n return result;\n}\n"],"mappings":";;AA8BA,SAAgB,mBAAmB,QAAyB,MAA4B;CACtF,OAAO,OAAO,mBAAmB,SAAS,OAAO;AACnD;;;;;;;;;;AAWA,eAAsB,gBACpB,QACA,SAC+B;CAC/B,MAAM,SAAS,QAAQ,8BAAc,IAAI,KAAK,GAAA,CAAI,CAAC,CAAC,QAAQ;CAC5D,MAAM,SAKF;EACF,SAAS,CAAC;EACV,UAAU,CAAC;EACX,QAAQ,CAAC;EACT,WAAW,CAAC;CACd;CACA,MAAM,QAAQ,QAAQ,SAAU,MAAM,OAAO,MAAM,KAAK;CACxD,MAAM,eAAuB,CAAC;CAE9B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,sBAAsB,KAAK,UAAU,WAC5C;EAEF,MAAM,UAAU,mBAAmB,QAAQ,QAAQ,KAAK,IAAI;EAC5D,MAAM,gBAAgB,QAAQ,KAAK,MAAM,KAAK,kBAAkB,KAAK,SAAS;EAC9E,IAAI,gBAAgB,UAAU,KAC5B;EAGF,IAAI;GACF,IAAI,MAAM,QAAQ,gBAAgB,IAAI,GAAG;GACzC,MAAM,UAAU,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,WAAW;IAChE,QAAQ,mBAAmB,KAAK,MAAM,gBAAgB,GAAM,EAAE,mBAAmB,QAAQ;IACzF,YAAY;IACZ,eAAe,KAAK;IACpB,iBAAiB,KAAK;GACxB,CAAC;GACD,OAAO,QAAQ,KAAK,KAAK,EAAE;GAC3B,aAAa,KAAK,OAAO;EAC3B,QAAQ;GACN,IAAI,QAAQ,UAAU,KAAA,GAAW;IAC/B,MAAM,UAAU,MAAM,OAAO,MAAM,IAAI,KAAK,EAAE,CAAC,CAAC,YAAY,IAAI;IAChE,IAAI,SAAS,UAAU,WAAW,aAAa,KAAK,OAAO;GAC7D;GACA;EACF;CACF;CAIA,MAAM,oBACJ,QAAQ,UAAU,KAAA,IACd,MAAM,OAAO,MAAM,KAAK,IACxB,CAAC,GAAG,MAAM,QAAQ,SAAS,KAAK,UAAU,SAAS,GAAG,GAAG,YAAY;CAC3E,KAAK,MAAM,QAAQ,mBAAmB;EACpC,IAAI,KAAK,sBAAsB,KAAK,UAAU,WAC5C;EAEF,MAAM,qBAAsB,MAAM,QAAQ,gBAAgB,MAAM,KAAK,KAAM;EAC3E,IAAI;GACF,IAAI,uBAAuB,MAAM;IAC/B,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,aAAa;KAClD,QAAQ;KACR,YAAY;IACd,CAAC;IACD,OAAO,UAAU,KAAK,KAAK,EAAE;GAC/B,OAAO,IAAI,KAAK,UAAU,QAAQ,OAAO,aAAa;IACpD,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,UAAU;KAC/C,QAAQ,sBAAsB,KAAK,QAAQ,MAAM,QAAQ,OAAO,YAAY;KAC5E,OAAO;KACP,YAAY;IACd,CAAC;IACD,OAAO,SAAS,KAAK,KAAK,EAAE;GAC9B,OAAO;IACL,MAAM,OAAO,MAAM,WAAW,KAAK,IAAI,UAAU;KAC/C,QAAQ,sBAAsB,KAAK,QAAQ,MAAM,QAAQ,OAAO,YAAY;KAC5E,OAAO;KACP,YAAY;IACd,CAAC;IACD,OAAO,OAAO,KAAK,KAAK,EAAE;GAC5B;EACF,QAAQ;GACN;EACF;CACF;CACA,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
1
  //#region src/store/driver.d.ts
2
2
  /** Closed set of persistence collections understood by Factory store drivers. */
3
- type CollectionName = "repositories" | "signals" | "tasks" | "changes" | "receipts" | "budgets" | "answers" | "sessions" | "externalSessions" | "transcripts" | "images" | "questionRoutes" | "taskCatalogs" | "changeCatalogs" | "sessionCatalogs" | "externalSessionCatalogs" | "taskRoutes" | "outboxQueues" | "outboxDeliveries" | "taskGraphs" | "taskMessages" | "taskEffects" | "taskInboxes" | "taskMessageProcessing" | "taskMessageDecisions" | "taskMessageDiscards" | "plannerActivations" | "plannerCancellations";
3
+ type CollectionName = "repositories" | "signals" | "tasks" | "changes" | "receipts" | "budgets" | "answers" | "sessions" | "externalSessions" | "transcripts" | "images" | "questionRoutes" | "taskCatalogs" | "changeCatalogs" | "sessionCatalogs" | "externalSessionCatalogs" | "taskRoutes" | "outboxQueues" | "outboxDeliveries" | "communicationSettings" | "taskGraphs" | "taskMessages" | "taskEffects" | "taskInboxes" | "taskMessageProcessing" | "taskMessageDecisions" | "taskMessageDiscards" | "plannerActivations" | "plannerCancellations";
4
4
  /** Opaque persisted value paired with its compare-and-swap version. */
5
5
  interface StoredRecord {
6
6
  readonly value: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"driver.mjs","names":[],"sources":["../../src/store/driver.ts"],"sourcesContent":["/** Closed set of persistence collections understood by Factory store drivers. */\nexport type CollectionName =\n | \"repositories\"\n | \"signals\"\n | \"tasks\"\n | \"changes\"\n | \"receipts\"\n | \"budgets\"\n | \"answers\"\n | \"sessions\"\n | \"externalSessions\"\n | \"transcripts\"\n | \"images\"\n | \"questionRoutes\"\n | \"taskCatalogs\"\n | \"changeCatalogs\"\n | \"sessionCatalogs\"\n | \"externalSessionCatalogs\"\n | \"taskRoutes\"\n | \"outboxQueues\"\n | \"outboxDeliveries\"\n | \"taskGraphs\"\n | \"taskMessages\"\n | \"taskEffects\"\n | \"taskInboxes\"\n | \"taskMessageProcessing\"\n | \"taskMessageDecisions\"\n | \"taskMessageDiscards\"\n | \"plannerActivations\"\n | \"plannerCancellations\";\n\n/** Opaque persisted value paired with its compare-and-swap version. */\nexport interface StoredRecord {\n readonly value: unknown;\n readonly version: number;\n}\n\n/** Collection-listing entry containing its storage key and versioned record. */\nexport interface StoredEntry {\n readonly id: string;\n readonly record: StoredRecord;\n}\n\n/** Collection, storage key, and opaque value for one insert-only write. */\nexport interface InsertStoreRecordInput {\n readonly collection: CollectionName;\n readonly id: string;\n readonly value: unknown;\n}\n\n/** Collection, storage key, opaque value, and compare-and-swap version for one update. */\nexport interface UpdateStoreRecordInput extends InsertStoreRecordInput {\n readonly expectedVersion: number;\n}\n\n/** Error raised when an insert targets an already occupied storage key. */\nexport class RecordExistsError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" already exists in \"${collection}\"`);\n this.name = \"RecordExistsError\";\n }\n}\n\n/** Error raised when an operation requires a storage record that does not exist. */\nexport class RecordNotFoundError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" not found in \"${collection}\"`);\n this.name = \"RecordNotFoundError\";\n }\n}\n\n/** Error raised when atomic update loses its expected-version comparison. */\nexport class VersionConflictError extends Error {\n constructor(collection: CollectionName, id: string, expectedVersion: number) {\n super(`record \"${id}\" in \"${collection}\" changed since version ${expectedVersion} was read`);\n this.name = \"VersionConflictError\";\n }\n}\n\n/**\n * A transient infrastructure failure (network error, 5xx/429 response) that\n * a driver's own bounded, backed-off low-level retries could not resolve.\n * Distinct from VersionConflictError (a concurrent writer legitimately won)\n * and from validation or \"already exists\" errors (the request itself is\n * rejected): the same operation may still succeed if retried again later,\n * but every caller retrying on this error must keep its own bound so a real\n * outage surfaces as a failure instead of hanging a dispatch cycle.\n */\nexport class TransientStoreError extends Error {\n constructor(collection: CollectionName, id: string, cause: unknown) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n super(\n `record \"${id}\" in \"${collection}\" failed after transient retries were exhausted: ${reason}`,\n { cause: cause instanceof Error ? cause : undefined },\n );\n this.name = \"TransientStoreError\";\n }\n}\n\n/**\n * Raw persistence contract. Drivers store opaque values and enforce nothing\n * about their content; every invariant lives in the engine above. `update`\n * must be an atomic compare-and-swap on `expectedVersion`. A driver may\n * throw TransientStoreError for a read or write that failed only because of\n * infrastructure trouble (network errors, 5xx/429 responses); it must not\n * use TransientStoreError for version conflicts or malformed input.\n */\nexport interface StoreDriver {\n readonly name: string;\n get(collection: CollectionName, id: string): Promise<StoredRecord | null>;\n insert(input: InsertStoreRecordInput): Promise<void>;\n update(input: UpdateStoreRecordInput): Promise<void>;\n list(collection: CollectionName): Promise<readonly StoredEntry[]>;\n}\n"],"mappings":";;AAwDA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,uBAAuB,WAAW,EAAE;EACxD,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,kBAAkB,WAAW,EAAE;EACnD,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,YAA4B,IAAY,iBAAyB;EAC3E,MAAM,WAAW,GAAG,QAAQ,WAAW,0BAA0B,gBAAgB,UAAU;EAC3F,KAAK,OAAO;CACd;AACF;;;;;;;;;;AAWA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,YAA4B,IAAY,OAAgB;EAClE,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACpE,MACE,WAAW,GAAG,QAAQ,WAAW,mDAAmD,UACpF,EAAE,OAAO,iBAAiB,QAAQ,QAAQ,KAAA,EAAU,CACtD;EACA,KAAK,OAAO;CACd;AACF"}
1
+ {"version":3,"file":"driver.mjs","names":[],"sources":["../../src/store/driver.ts"],"sourcesContent":["/** Closed set of persistence collections understood by Factory store drivers. */\nexport type CollectionName =\n | \"repositories\"\n | \"signals\"\n | \"tasks\"\n | \"changes\"\n | \"receipts\"\n | \"budgets\"\n | \"answers\"\n | \"sessions\"\n | \"externalSessions\"\n | \"transcripts\"\n | \"images\"\n | \"questionRoutes\"\n | \"taskCatalogs\"\n | \"changeCatalogs\"\n | \"sessionCatalogs\"\n | \"externalSessionCatalogs\"\n | \"taskRoutes\"\n | \"outboxQueues\"\n | \"outboxDeliveries\"\n | \"communicationSettings\"\n | \"taskGraphs\"\n | \"taskMessages\"\n | \"taskEffects\"\n | \"taskInboxes\"\n | \"taskMessageProcessing\"\n | \"taskMessageDecisions\"\n | \"taskMessageDiscards\"\n | \"plannerActivations\"\n | \"plannerCancellations\";\n\n/** Opaque persisted value paired with its compare-and-swap version. */\nexport interface StoredRecord {\n readonly value: unknown;\n readonly version: number;\n}\n\n/** Collection-listing entry containing its storage key and versioned record. */\nexport interface StoredEntry {\n readonly id: string;\n readonly record: StoredRecord;\n}\n\n/** Collection, storage key, and opaque value for one insert-only write. */\nexport interface InsertStoreRecordInput {\n readonly collection: CollectionName;\n readonly id: string;\n readonly value: unknown;\n}\n\n/** Collection, storage key, opaque value, and compare-and-swap version for one update. */\nexport interface UpdateStoreRecordInput extends InsertStoreRecordInput {\n readonly expectedVersion: number;\n}\n\n/** Error raised when an insert targets an already occupied storage key. */\nexport class RecordExistsError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" already exists in \"${collection}\"`);\n this.name = \"RecordExistsError\";\n }\n}\n\n/** Error raised when an operation requires a storage record that does not exist. */\nexport class RecordNotFoundError extends Error {\n constructor(collection: CollectionName, id: string) {\n super(`record \"${id}\" not found in \"${collection}\"`);\n this.name = \"RecordNotFoundError\";\n }\n}\n\n/** Error raised when atomic update loses its expected-version comparison. */\nexport class VersionConflictError extends Error {\n constructor(collection: CollectionName, id: string, expectedVersion: number) {\n super(`record \"${id}\" in \"${collection}\" changed since version ${expectedVersion} was read`);\n this.name = \"VersionConflictError\";\n }\n}\n\n/**\n * A transient infrastructure failure (network error, 5xx/429 response) that\n * a driver's own bounded, backed-off low-level retries could not resolve.\n * Distinct from VersionConflictError (a concurrent writer legitimately won)\n * and from validation or \"already exists\" errors (the request itself is\n * rejected): the same operation may still succeed if retried again later,\n * but every caller retrying on this error must keep its own bound so a real\n * outage surfaces as a failure instead of hanging a dispatch cycle.\n */\nexport class TransientStoreError extends Error {\n constructor(collection: CollectionName, id: string, cause: unknown) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n super(\n `record \"${id}\" in \"${collection}\" failed after transient retries were exhausted: ${reason}`,\n { cause: cause instanceof Error ? cause : undefined },\n );\n this.name = \"TransientStoreError\";\n }\n}\n\n/**\n * Raw persistence contract. Drivers store opaque values and enforce nothing\n * about their content; every invariant lives in the engine above. `update`\n * must be an atomic compare-and-swap on `expectedVersion`. A driver may\n * throw TransientStoreError for a read or write that failed only because of\n * infrastructure trouble (network errors, 5xx/429 responses); it must not\n * use TransientStoreError for version conflicts or malformed input.\n */\nexport interface StoreDriver {\n readonly name: string;\n get(collection: CollectionName, id: string): Promise<StoredRecord | null>;\n insert(input: InsertStoreRecordInput): Promise<void>;\n update(input: UpdateStoreRecordInput): Promise<void>;\n list(collection: CollectionName): Promise<readonly StoredEntry[]>;\n}\n"],"mappings":";;AAyDA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,uBAAuB,WAAW,EAAE;EACxD,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,YAA4B,IAAY;EAClD,MAAM,WAAW,GAAG,kBAAkB,WAAW,EAAE;EACnD,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,YAA4B,IAAY,iBAAyB;EAC3E,MAAM,WAAW,GAAG,QAAQ,WAAW,0BAA0B,gBAAgB,UAAU;EAC3F,KAAK,OAAO;CACd;AACF;;;;;;;;;;AAWA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,YAA4B,IAAY,OAAgB;EAClE,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACpE,MACE,WAAW,GAAG,QAAQ,WAAW,mDAAmD,UACpF,EAAE,OAAO,iBAAiB,QAAQ,QAAQ,KAAA,EAAU,CACtD;EACA,KAAK,OAAO;CACd;AACF"}