@vercel/factory 0.0.15 → 0.0.17

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 +362 -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 +20 -6
@@ -81,12 +81,17 @@ interface DecideSignalInput {
81
81
  readonly reason?: string;
82
82
  }
83
83
  interface CreateTaskInputBase {
84
+ /** Repository authorization scope; children must remain within their parent's scope. */
84
85
  repositoryIds: RepositoryIds;
86
+ /** Descriptive work vocabulary; routing comes only from `work.route`. */
85
87
  kind: Task["kind"];
88
+ /** Destination for lifecycle communication produced by this Task. */
86
89
  replyTo: ReplyTo;
87
- /** Same key creates one task and returns it on workflow retries. */
90
+ /** Same key under the same parent creates one Task and returns its current snapshot on retries. */
88
91
  dedupeKey?: string;
92
+ /** Optional application-owned grouping label. */
89
93
  area?: string;
94
+ /** Optional associated software Change. */
90
95
  changeId?: ChangeId;
91
96
  /** Stamp the task as needing a human decision before dispatch. */
92
97
  approval?: "required";
@@ -97,10 +102,14 @@ interface CreateTaskInputBase {
97
102
  }
98
103
  /** Provenance required when admitting a root Task with no parent to inherit from. */
99
104
  type RootTaskOrigin = {
105
+ /** Durable inbound Signal that caused the root work. */
100
106
  signalId: SignalId;
107
+ /** Authenticated operator additionally associated with the work. */
101
108
  operator?: string;
102
109
  } | {
110
+ /** Optional inbound Signal additionally associated with operator-created work. */
103
111
  signalId?: SignalId;
112
+ /** Authenticated operator that caused the root work. */
104
113
  operator: string;
105
114
  };
106
115
  /** Explicit work, graph placement, provenance, and policy input for Task admission. */
@@ -174,17 +183,30 @@ declare const stepUsageSchema: z.ZodObject<{
174
183
  type StepUsage = z.infer<typeof stepUsageSchema>;
175
184
  /** Attempt and provider execution that must still own a Task usage observation. */
176
185
  interface TaskExecutionFence {
186
+ /** Current positive attempt that emitted the observation. */
177
187
  readonly attempt: number;
188
+ /** Exact provider execution that emitted the observation. */
178
189
  readonly execution: ExecutionReference;
179
190
  }
191
+ /** Error raised when a Task operation loses its exact-attempt concurrency fence. */
192
+ declare class TaskAttemptConflictError extends Error {
193
+ readonly taskId: TaskId;
194
+ readonly actualAttempt: number;
195
+ readonly expectedAttempt: number;
196
+ constructor(taskId: TaskId, actualAttempt: number, expectedAttempt: number);
197
+ }
180
198
  /** Provider execution and optional attempt fence recorded on one running Task. */
181
199
  interface RecordTaskExecutionInput {
200
+ /** Provider handle to persist for later inspection, cancellation, and callback fencing. */
182
201
  readonly execution: ExecutionReference;
202
+ /** Current running attempt required at the write; identical replay is then a no-op while running. */
183
203
  readonly expectAttempt?: number;
184
204
  }
185
205
  /** One measured usage step and optional exact-execution fence for a Task. */
186
206
  interface RecordTaskUsageInput {
207
+ /** Monotonic step observation to aggregate once per provider turn. */
187
208
  readonly step: StepUsage;
209
+ /** Optional current-attempt and execution guard for provider-originated observations. */
188
210
  readonly fence?: TaskExecutionFence;
189
211
  }
190
212
  interface TaskTransitionContext {
@@ -246,6 +268,12 @@ interface CreateChangeInput {
246
268
  }
247
269
  /** Verification and merge fields that may accompany a legal Change transition. */
248
270
  type ChangePatch = Partial<Pick<Change, "confidence" | "evidence" | "decidedBy" | "mergeSha">>;
271
+ /** Trust evidence update fenced to the complete observed Change snapshot. */
272
+ interface UpdateChangeEvidenceInput {
273
+ evidence: Change["evidence"];
274
+ confidence: ChangeLevel;
275
+ expected: Change;
276
+ }
249
277
  interface ChangeTransitionContext {
250
278
  patch?: ChangePatch;
251
279
  reason?: string;
@@ -272,7 +300,9 @@ interface BudgetNoticeStampResult {
272
300
  }
273
301
  /** Measured cost and optional audit reason used to settle one terminal Task. */
274
302
  interface SettleTaskInput {
303
+ /** Final measured Task cost, normalized to micro-dollar precision. */
275
304
  readonly cost: Cost;
305
+ /** Optional audit context retained on the immutable settlement receipt. */
276
306
  readonly reason?: string;
277
307
  }
278
308
  /** Delivery attempt, failure reason, and retry delay for one outbox message. */
@@ -319,6 +349,7 @@ type AppendReceiptInput<State extends ReceiptState = StandardReceiptState, Kind
319
349
  declare class SessionConflictError extends Error {}
320
350
  /** Validating engine API for all canonical Factory state and auxiliary persistence. */
321
351
  interface FactoryStores {
352
+ /** Name supplied by the configured persistence driver for diagnostics and health reporting. */
322
353
  driverName: string;
323
354
  /** Trusted workflow records bound to the same persistence backend as every validating store. */
324
355
  workflowRecords: Pick<StoreDriver, "get" | "insert" | "update">;
@@ -326,19 +357,26 @@ interface FactoryStores {
326
357
  taskEffects: TaskEffectStore;
327
358
  /** Trusted workflow capabilities; application transports must authenticate and authorize callers. */
328
359
  graphs: TaskGraphStore;
360
+ /** Validated, authorized Task-message operations; omitted protocols remain unavailable. */
329
361
  messages: TaskMessageStore;
362
+ /** Workflow completion, message waiting, graph recovery, and usage aggregation operations. */
330
363
  work: TaskWorkStore;
364
+ /** Materialized transcript snapshots keyed by exact provider execution. */
331
365
  transcripts: {
332
366
  get(execution: ExecutionReference): Promise<TranscriptSnapshot | null>;
333
367
  /** Persist only forward progress; safe to repeat from response-boundary hooks. */
334
368
  put(execution: ExecutionReference, snapshot: TranscriptSnapshot): Promise<TranscriptSnapshot>;
335
369
  };
370
+ /** Owner-scoped image persistence and attachment validation. */
336
371
  images: {
337
372
  put(owner: string, input: z.infer<typeof imageUploadSchema>): Promise<ImageAttachment>;
338
373
  get(owner: string, id: ImageId): Promise<StoredImage | null>;
339
374
  validate(owner: string, attachments: readonly ImageAttachment[]): Promise<readonly ImageAttachment[]>;
340
375
  };
376
+ /** Durable operator-session admission, binding, continuation, and notification operations. */
341
377
  sessions: {
378
+ /** Looks up an admitted session without recomputing defaults on a repeated operation. */
379
+ getByOperation(owner: string, operationId: string): Promise<AgentSession | null>;
342
380
  create(input: Omit<AgentSession, "schemaVersion" | "id" | "createdAt" | "execution" | "taskId" | "notifications" | "attached" | "continuations" | "external"> & {
343
381
  operationId: string;
344
382
  }): Promise<AgentSession>;
@@ -371,14 +409,23 @@ interface FactoryStores {
371
409
  bind(id: AgentSessionId, reference: AgentSessionBinding): Promise<AgentSession>;
372
410
  notify(event: CommunicationEvent): Promise<void>;
373
411
  };
412
+ /** Configured repository snapshots used for admission and routing. */
374
413
  repositories: {
414
+ /** Atomically replaces model overrides when their observed revision still matches. */
415
+ setModelDefaults(id: RepositoryId, input: {
416
+ expectedRevision: number;
417
+ agents: Readonly<Record<string, string>>;
418
+ }): Promise<Repository>;
375
419
  put(repository: Repository): Promise<Repository>;
376
420
  get(id: RepositoryId): Promise<Repository | null>;
377
421
  list(): Promise<readonly Repository[]>;
378
422
  };
423
+ /** External intake records and their explicit triage lifecycle. */
379
424
  signals: {
380
425
  create(input: CreateSignalInput): Promise<Signal>;
381
426
  get(id: SignalId): Promise<Signal | null>;
427
+ /** Read a deterministic source/delivery record without scanning the Signal collection. */
428
+ getByDelivery(input: Pick<CreateSignalInput, "source" | "deliveryId">): Promise<Signal | null>;
382
429
  beginTriage(id: SignalId, options: {
383
430
  expectedAttempt: number;
384
431
  by: string;
@@ -387,10 +434,61 @@ interface FactoryStores {
387
434
  decide(id: SignalId, input: DecideSignalInput): Promise<Signal>;
388
435
  list(): Promise<readonly Signal[]>;
389
436
  };
437
+ /** Canonical Task lifecycle operations. */
390
438
  tasks: {
439
+ /** Pins an application's model choice once, fencing the current attempt. */
440
+ pinModel(id: TaskId, input: {
441
+ attempt: number;
442
+ model: string;
443
+ }): Promise<Task>;
444
+ /**
445
+ * Validates and durably admits a queued Task into its canonical graph.
446
+ *
447
+ * @remarks
448
+ * Admission writes the immutable graph node before the mutable Task snapshot. A failure after
449
+ * graph admission can therefore leave a repairable missing snapshot; call
450
+ * `stores.work.recover(rootTaskId)` with the known root to restore it. The method returns only
451
+ * after both admission and the canonical Task snapshot are durable.
452
+ *
453
+ * A `dedupeKey` derives a stable ID within the parent scope. An identical retry returns the
454
+ * Task's current mutable snapshot, while changed immutable admission data is refused. Without
455
+ * a key, every call creates a new Task.
456
+ *
457
+ * @param input - Explicit workflow work, scope, provenance, graph placement, and policy.
458
+ * @returns The newly admitted Task, or the current snapshot selected by `dedupeKey`.
459
+ * @throws When workflow input is invalid or its exact version is unregistered, parent or
460
+ * dependency constraints fail, or a dedupe key conflicts with an earlier admission.
461
+ */
391
462
  create(input: CreateTaskInput): Promise<Task>;
463
+ /**
464
+ * Returns the validated current Task snapshot without repairing a missing admitted snapshot.
465
+ *
466
+ * @param id - Exact Task ID to read.
467
+ * @returns Current Task state, or `null` when its canonical snapshot is absent.
468
+ */
392
469
  get(id: TaskId): Promise<Task | null>;
393
470
  findOpenQuestions(replyTo: ReplyTo): Promise<readonly Task[]>;
471
+ /**
472
+ * Performs one legal lifecycle transition with optional attempt, source-state, execution, and
473
+ * question fences.
474
+ *
475
+ * @remarks
476
+ * Lifecycle evidence is appended before the final Task-state write. A successful completion
477
+ * first persists its validated `workResult`, then its receipt, then the terminal state. The
478
+ * promise resolves only after the final Task snapshot is durable, but a rejected call may have
479
+ * persisted prepared output or its idempotent receipt before a later write failed. Reread
480
+ * canonical Task state before retrying, and retain the same fences so a late callback cannot
481
+ * advance a replacement attempt or execution. Transitions that re-enter `queued`, and
482
+ * `verifying` to `running`, increment the attempt and clear attempt-owned execution state.
483
+ *
484
+ * @param id - Exact Task to advance.
485
+ * @param to - Legal destination state.
486
+ * @param args - Destination data and optional concurrency fences.
487
+ * @returns The durably updated Task snapshot.
488
+ * @throws `InvalidTransitionError` for an illegal or stale source state,
489
+ * `TaskAttemptConflictError` for a replaced attempt, or a persistence conflict when a
490
+ * concurrent writer wins.
491
+ */
394
492
  transition<To extends TaskState>(id: TaskId, to: To, ...args: TaskTransitionArguments<NoInfer<To>>): Promise<Task>;
395
493
  /** Remove a receipt after its CommunicationEvent is durable in the outbox. */
396
494
  acknowledgeCommunication(id: TaskId, receiptId: ReceiptId): Promise<Task>;
@@ -403,14 +501,51 @@ interface FactoryStores {
403
501
  reject(id: TaskId, decision: RejectionDecision): Promise<Task>;
404
502
  /**
405
503
  * Record the provider's handle on the running attempt's execution, so the
406
- * reconciler can inspect or cancel it. Refused unless the task is running.
504
+ * reconciler can inspect or cancel it.
505
+ *
506
+ * @remarks
507
+ * The returned Task contains the durably recorded handle. Supply `expectAttempt` for launch
508
+ * callbacks: while that attempt remains running, replaying the same handle is a no-op, while a
509
+ * different handle or replaced attempt is refused. Terminal Tasks reject every binding call.
510
+ * The engine retries bounded transient driver failures, but surfaces compare-and-swap
511
+ * conflicts; callers should reread and reconcile rather than start an unkeyed replacement
512
+ * execution.
513
+ *
514
+ * @param id - Exact running Task to bind.
515
+ * @param input - Provider reference and recommended attempt fence.
516
+ * @returns The Task containing the durable execution reference.
517
+ * @throws When the Task is not running, the attempt or existing execution differs, or bounded
518
+ * persistence retries are exhausted.
407
519
  */
408
520
  recordExecution(id: TaskId, input: RecordTaskExecutionInput): Promise<Task>;
409
- /** Add one model step's usage to the task. Idempotent per (turnId, stepIndex). Never settles. */
521
+ /**
522
+ * Adds one provider step's measured usage without settling the Task.
523
+ *
524
+ * @remarks
525
+ * Step indexes advance monotonically per `turnId`; replaying the recorded index or an earlier
526
+ * one returns the current Task without charging it again. Use an execution fence for provider
527
+ * callbacks. Version races are retried, transient storage failures are retried within bounded
528
+ * limits, and new usage after settlement is refused so measured cost cannot silently diverge
529
+ * from the immutable settlement receipt.
530
+ *
531
+ * @param id - Exact Task to charge.
532
+ * @param input - Validated step observation and optional execution fence.
533
+ * @returns The Task after aggregation, or unchanged when the step was already covered.
534
+ */
410
535
  recordUsage(id: TaskId, input: RecordTaskUsageInput): Promise<Task>;
411
536
  /**
412
- * Record measured cost on a terminal task. Idempotent: an already settled
413
- * task returns unchanged and writes no second receipt.
537
+ * Records measured cost on a terminal Task using receipt-first persistence.
538
+ *
539
+ * @remarks
540
+ * Cost is normalized to micro-dollar precision. The immutable receipt is written before the
541
+ * Task snapshot, so retrying after an interrupted snapshot write adopts the receipt's original
542
+ * cost. Once settled, every replay returns the unchanged Task even if a different cost is
543
+ * supplied; callers must treat the first settlement as authoritative.
544
+ *
545
+ * @param id - Exact terminal Task to settle.
546
+ * @param input - Final measured cost and optional audit reason.
547
+ * @returns The Task with its authoritative `settledUsd` value.
548
+ * @throws `SettlementError` when the Task is not terminal.
414
549
  */
415
550
  settle(id: TaskId, input: SettleTaskInput): Promise<Task>;
416
551
  /**
@@ -426,12 +561,16 @@ interface FactoryStores {
426
561
  forReplyTo(replyTo: ReplyTo): Promise<readonly Task[]>;
427
562
  list(): Promise<readonly Task[]>;
428
563
  };
564
+ /** Canonical software Change admission and lifecycle operations. */
429
565
  changes: {
430
566
  create(input: CreateChangeInput): Promise<Change>;
431
567
  get(id: ChangeId): Promise<Change | null>;
568
+ /** Save evidence without inventing a lifecycle transition; reject a changed snapshot. */
569
+ updateEvidence(id: ChangeId, input: UpdateChangeEvidenceInput): Promise<Change>;
432
570
  transition<To extends ChangeState>(id: ChangeId, to: To, ...args: ChangeTransitionArguments<NoInfer<To>>): Promise<ChangeInState<To>>;
433
571
  list(): Promise<readonly Change[]>;
434
572
  };
573
+ /** Immutable audit evidence appended by engine and application policy. */
435
574
  receipts: {
436
575
  append<const State extends ReceiptState, const Kind extends ReceiptKind, const Phase extends ReceiptPhase | undefined = undefined>(input: AppendReceiptCallInput<State, Kind, Phase> & {
437
576
  state: State;
@@ -441,6 +580,7 @@ interface FactoryStores {
441
580
  get(id: ReceiptId): Promise<Receipt | null>;
442
581
  list(): Promise<readonly Receipt[]>;
443
582
  };
583
+ /** Monthly budget snapshots, limits, and once-only notification stamps. */
444
584
  budgets: {
445
585
  /**
446
586
  * Insert-once per month; refreshes limits when configuration changed and
@@ -454,9 +594,11 @@ interface FactoryStores {
454
594
  */
455
595
  stampNotice(month: BudgetMonth, notice: "warning" | "exhausted"): Promise<BudgetNoticeStampResult>;
456
596
  };
597
+ /** Immutable authenticated answers addressed by deterministic event ID. */
457
598
  answers: {
458
599
  get(id: AnswerId): Promise<AnswerEvent | null>;
459
600
  };
601
+ /** Durable communication delivery queue with attempt-fenced leases. */
460
602
  outbox: {
461
603
  enqueue(event: CommunicationEvent): Promise<OutboxMessage>;
462
604
  get(id: OutboxId): Promise<OutboxMessage | null>;
@@ -468,18 +610,74 @@ interface FactoryStores {
468
610
  }
469
611
  /** Persistence, workflow, messaging, clock, and wakeup configuration for the Factory engine. */
470
612
  interface CreateStoresOptions {
613
+ /** Persistence implementation. The engine owns validation and lifecycle policy above it. */
471
614
  driver: StoreDriver;
472
- /** Awaited post-commit wakeups. Canonical work remains recoverable if publishing fails. */
615
+ /**
616
+ * Optional post-commit wakeup transport. Delivery failure is reported through its `onError`
617
+ * callback and never rolls back the canonical write.
618
+ */
473
619
  workNotifications?: WorkNotifications;
620
+ /** ISO timestamp source used for engine-owned records. Defaults to the current wall clock. */
474
621
  now?: IsoDateTimeClock;
622
+ /**
623
+ * Every workflow version that stored Tasks may reference. Omission registers only the generic
624
+ * `task@1` workflow. A supplied registry replaces that default, so include `defaultTaskWorkflow`
625
+ * when Factory tools or human-question flows may create generic work.
626
+ */
475
627
  workflows?: WorkflowRegistry;
628
+ /**
629
+ * Versioned Task-message protocols and deny-by-default authorization. Omit when the runtime
630
+ * does not use Task messages.
631
+ */
476
632
  messages?: {
633
+ /** Exact protocol versions accepted by this runtime. */
477
634
  protocols: TaskMessageProtocols;
635
+ /** Trusted application policy applied to every message operation. */
478
636
  authorize: TaskMessageAuthorize;
479
637
  };
480
638
  }
481
- /** Creates the validating Factory store engine over a deliberately policy-free driver. */
639
+ /**
640
+ * Creates the validating Factory store engine over a deliberately policy-free driver.
641
+ *
642
+ * @remarks
643
+ * The returned stores validate entities on read and write, enforce legal transitions and
644
+ * compare-and-swap fences, and emit immutable receipts. Construct one configured instance per
645
+ * runtime and share it with launchers, hooks, and tools. A custom workflow must be registered on
646
+ * every process that may read its Tasks; omission is a configuration error rather than permission
647
+ * to select a newer workflow version. Supplying `workflows` replaces the default registry, so
648
+ * include `defaultTaskWorkflow` when generic `task@1` work, Factory tools, or human questions may
649
+ * be created.
650
+ *
651
+ * Wakeups run after their source write commits. Notification failure therefore cannot roll the
652
+ * mutation back; reconciliation must be able to rediscover canonical state.
653
+ *
654
+ * @param options - Persistence and exact workflow/message configuration for this runtime.
655
+ * @returns The complete set of validating Factory stores backed by `options.driver`.
656
+ * @see The version-matched `docs/getting-started.md` shipped with the package.
657
+ *
658
+ * @example
659
+ * ```ts
660
+ * import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
661
+ * import {
662
+ * defaultTaskWorkflow,
663
+ * defineWorkflow,
664
+ * defineWorkflows,
665
+ * } from "@vercel/factory/workflows";
666
+ * import { z } from "zod";
667
+ *
668
+ * const greeting = defineWorkflow({
669
+ * id: "greeting",
670
+ * version: 1,
671
+ * input: z.object({ name: z.string() }),
672
+ * output: z.object({ message: z.string() }),
673
+ * });
674
+ * const stores = createStores({
675
+ * driver: createInMemoryDriver(),
676
+ * workflows: defineWorkflows([defaultTaskWorkflow, greeting]),
677
+ * });
678
+ * ```
679
+ */
482
680
  declare function createStores(options: CreateStoresOptions): FactoryStores;
483
681
  //#endregion
484
- export { AgentSessionBinding, AnswerEventConflictError, AnswerQuestionInput, AnswerQuestionResult, AppendReceiptInput, ApprovalDecision, ApprovalError, BudgetNoticeStampResult, CacheContinuationContextInput, ChangeAssociationError, ChangePatch, ChangeTransitionArguments, ChangeTransitionOptions, CreateChangeInput, CreateSignalInput, CreateStoresOptions, CreateTaskInput, DecideSignalInput, EnsureBudgetInput, FactoryStores, ImageInputError, OutboxEventConflictError, RecordTaskExecutionInput, RecordTaskUsageInput, RejectionDecision, RetryOutboxMessageInput, RootTaskOrigin, SessionConflictError, SettleTaskInput, SettlementError, StepUsage, TaskExecutionFence, TaskPatch, TaskTransitionArguments, TaskTransitionOptions, createStores };
682
+ export { AgentSessionBinding, AnswerEventConflictError, AnswerQuestionInput, AnswerQuestionResult, AppendReceiptInput, ApprovalDecision, ApprovalError, BudgetNoticeStampResult, CacheContinuationContextInput, ChangeAssociationError, ChangePatch, ChangeTransitionArguments, ChangeTransitionOptions, CreateChangeInput, CreateSignalInput, CreateStoresOptions, CreateTaskInput, DecideSignalInput, EnsureBudgetInput, FactoryStores, ImageInputError, OutboxEventConflictError, RecordTaskExecutionInput, RecordTaskUsageInput, RejectionDecision, RetryOutboxMessageInput, RootTaskOrigin, SessionConflictError, SettleTaskInput, SettlementError, StepUsage, TaskAttemptConflictError, TaskExecutionFence, TaskPatch, TaskTransitionArguments, TaskTransitionOptions, UpdateChangeEvidenceInput, createStores };
485
683
  //# sourceMappingURL=engine.d.mts.map
@@ -139,6 +139,19 @@ const stepUsageSchema = z.strictObject({
139
139
  cacheReadTokens: z.number().int().nonnegative().optional().describe("Cached input tokens read for this step."),
140
140
  cacheWriteTokens: z.number().int().nonnegative().optional().describe("Input tokens written to cache for this step.")
141
141
  });
142
+ /** Error raised when a Task operation loses its exact-attempt concurrency fence. */
143
+ var TaskAttemptConflictError = class extends Error {
144
+ taskId;
145
+ actualAttempt;
146
+ expectedAttempt;
147
+ constructor(taskId, actualAttempt, expectedAttempt) {
148
+ super(`task ${taskId} is on attempt ${actualAttempt}, not ${expectedAttempt}`);
149
+ this.taskId = taskId;
150
+ this.actualAttempt = actualAttempt;
151
+ this.expectedAttempt = expectedAttempt;
152
+ this.name = "TaskAttemptConflictError";
153
+ }
154
+ };
142
155
  /** Error raised when an idempotent session operation conflicts with its original intent. */
143
156
  var SessionConflictError = class extends Error {};
144
157
  function sameStoredValue(left, right) {
@@ -170,7 +183,7 @@ function needsLifecycleCommunication(task, state, phase) {
170
183
  if (state === "dispatched") return phase === "retry";
171
184
  if (state === "failed" || state === "cancelled") return true;
172
185
  if (state !== "succeeded") return false;
173
- return task.question === void 0 && task.changeId === void 0;
186
+ return task.question === void 0;
174
187
  }
175
188
  const changeReceiptStates = {
176
189
  verifying: "verifying",
@@ -219,7 +232,47 @@ async function nextTransientRetry(error, attempt, startedAt) {
219
232
  await new Promise((resolve) => setTimeout(resolve, nextDelayMs));
220
233
  return attempt + 1;
221
234
  }
222
- /** Creates the validating Factory store engine over a deliberately policy-free driver. */
235
+ /**
236
+ * Creates the validating Factory store engine over a deliberately policy-free driver.
237
+ *
238
+ * @remarks
239
+ * The returned stores validate entities on read and write, enforce legal transitions and
240
+ * compare-and-swap fences, and emit immutable receipts. Construct one configured instance per
241
+ * runtime and share it with launchers, hooks, and tools. A custom workflow must be registered on
242
+ * every process that may read its Tasks; omission is a configuration error rather than permission
243
+ * to select a newer workflow version. Supplying `workflows` replaces the default registry, so
244
+ * include `defaultTaskWorkflow` when generic `task@1` work, Factory tools, or human questions may
245
+ * be created.
246
+ *
247
+ * Wakeups run after their source write commits. Notification failure therefore cannot roll the
248
+ * mutation back; reconciliation must be able to rediscover canonical state.
249
+ *
250
+ * @param options - Persistence and exact workflow/message configuration for this runtime.
251
+ * @returns The complete set of validating Factory stores backed by `options.driver`.
252
+ * @see The version-matched `docs/getting-started.md` shipped with the package.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
257
+ * import {
258
+ * defaultTaskWorkflow,
259
+ * defineWorkflow,
260
+ * defineWorkflows,
261
+ * } from "@vercel/factory/workflows";
262
+ * import { z } from "zod";
263
+ *
264
+ * const greeting = defineWorkflow({
265
+ * id: "greeting",
266
+ * version: 1,
267
+ * input: z.object({ name: z.string() }),
268
+ * output: z.object({ message: z.string() }),
269
+ * });
270
+ * const stores = createStores({
271
+ * driver: createInMemoryDriver(),
272
+ * workflows: defineWorkflows([defaultTaskWorkflow, greeting]),
273
+ * });
274
+ * ```
275
+ */
223
276
  function createStores(options) {
224
277
  const { driver } = options;
225
278
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
@@ -794,6 +847,9 @@ function createStores(options) {
794
847
  }
795
848
  },
796
849
  sessions: {
850
+ async getByOperation(owner, operationId) {
851
+ return stores.sessions.get(derivedId("ses", JSON.stringify([owner, operationId])));
852
+ },
797
853
  async create({ operationId, ...input }) {
798
854
  if (operationId.trim().length === 0 || operationId.length > 200) throw new Error("operationId must contain 1–200 characters");
799
855
  const id = derivedId("ses", JSON.stringify([input.owner, operationId]));
@@ -1104,20 +1160,44 @@ function createStores(options) {
1104
1160
  }
1105
1161
  },
1106
1162
  repositories: {
1163
+ async setModelDefaults(id, input) {
1164
+ const existing = await mustRead("repositories", id, repositorySchema);
1165
+ if ((existing.value.modelDefaults?.revision ?? 0) !== input.expectedRevision) throw new VersionConflictError("repositories", id, existing.version);
1166
+ const next = repositorySchema.parse({
1167
+ ...existing.value,
1168
+ modelDefaults: {
1169
+ revision: input.expectedRevision + 1,
1170
+ agents: input.agents
1171
+ }
1172
+ });
1173
+ await driver.update({
1174
+ collection: "repositories",
1175
+ id,
1176
+ value: next,
1177
+ expectedVersion: existing.version
1178
+ });
1179
+ return next;
1180
+ },
1107
1181
  async put(repository) {
1108
- const parsed = repositorySchema.parse(repository);
1182
+ let parsed = repositorySchema.parse(repository);
1109
1183
  const existing = await read("repositories", parsed.id, repositorySchema);
1110
1184
  if (existing === null) await driver.insert({
1111
1185
  collection: "repositories",
1112
1186
  id: parsed.id,
1113
1187
  value: parsed
1114
1188
  });
1115
- else await driver.update({
1116
- collection: "repositories",
1117
- id: parsed.id,
1118
- value: parsed,
1119
- expectedVersion: existing.version
1120
- });
1189
+ else {
1190
+ parsed = repositorySchema.parse({
1191
+ ...parsed,
1192
+ modelDefaults: existing.value.modelDefaults
1193
+ });
1194
+ await driver.update({
1195
+ collection: "repositories",
1196
+ id: parsed.id,
1197
+ value: parsed,
1198
+ expectedVersion: existing.version
1199
+ });
1200
+ }
1121
1201
  return parsed;
1122
1202
  },
1123
1203
  async get(id) {
@@ -1158,6 +1238,9 @@ function createStores(options) {
1158
1238
  const record = await read("signals", id, signalSchema);
1159
1239
  return record === null ? null : record.value;
1160
1240
  },
1241
+ async getByDelivery({ source, deliveryId }) {
1242
+ return stores.signals.get(derivedId("sig", `${source}:${deliveryId}`));
1243
+ },
1161
1244
  async beginTriage(id, options) {
1162
1245
  const existing = await mustRead("signals", id, signalSchema);
1163
1246
  const current = existing.value;
@@ -1246,6 +1329,28 @@ function createStores(options) {
1246
1329
  }
1247
1330
  },
1248
1331
  tasks: {
1332
+ async pinModel(id, input) {
1333
+ for (let retry = 0; retry < 5; retry++) {
1334
+ const existing = await mustRead("tasks", id, storedTaskSchema);
1335
+ if (existing.value.attempt !== input.attempt) throw new Error("Cannot select a model for a replaced Task attempt");
1336
+ if (existing.value.model !== void 0) {
1337
+ if (existing.value.model !== input.model) throw new Error("Task model cannot be replaced");
1338
+ return existing.value;
1339
+ }
1340
+ if (!["queued", "running"].includes(existing.value.state)) throw new Error("Select a model before work completes or waits");
1341
+ const next = taskSchema.parse({
1342
+ ...existing.value,
1343
+ model: input.model
1344
+ });
1345
+ try {
1346
+ await updateTask(next, existing.version);
1347
+ return next;
1348
+ } catch (error) {
1349
+ if (!(error instanceof VersionConflictError) || retry === 4) throw error;
1350
+ }
1351
+ }
1352
+ throw new Error("Task model selection conflicted repeatedly");
1353
+ },
1249
1354
  async create(value) {
1250
1355
  const input = createTaskInputSchema.parse(value);
1251
1356
  const timestamp = now();
@@ -1323,7 +1428,7 @@ function createStores(options) {
1323
1428
  const existing = await mustRead("tasks", id, storedTaskSchema);
1324
1429
  const task = existing.value;
1325
1430
  if (task.state !== "running") throw new InvalidTransitionError(task.state, "running", legalNextStates(taskTransitions, task.state));
1326
- if (expectAttempt !== void 0 && task.attempt !== expectAttempt) throw new Error(`task ${id} is on attempt ${task.attempt}, not ${expectAttempt}`);
1431
+ if (expectAttempt !== void 0 && task.attempt !== expectAttempt) throw new TaskAttemptConflictError(id, task.attempt, expectAttempt);
1327
1432
  if (expectAttempt !== void 0 && task.execution) {
1328
1433
  if (!sameStoredValue(task.execution, execution)) throw new Error(`Task ${id} already has a different execution for attempt ${expectAttempt}`);
1329
1434
  return task;
@@ -1531,7 +1636,7 @@ function createStores(options) {
1531
1636
  if (transitionOptions.patch !== void 0 && "usage" in transitionOptions.patch) throw new SettlementError(id, "usage is set only by recordUsage, not a transition");
1532
1637
  let existing = await mustRead("tasks", id, storedTaskSchema);
1533
1638
  if (transitionOptions.expectFrom !== void 0 && existing.value.state !== transitionOptions.expectFrom) throw new InvalidTransitionError(existing.value.state, to, legalNextStates(taskTransitions, existing.value.state));
1534
- if (transitionOptions.expectAttempt !== void 0 && existing.value.attempt !== transitionOptions.expectAttempt) throw new Error(`task ${id} is on attempt ${existing.value.attempt}, not ${transitionOptions.expectAttempt}`);
1639
+ if (transitionOptions.expectAttempt !== void 0 && existing.value.attempt !== transitionOptions.expectAttempt) throw new TaskAttemptConflictError(id, existing.value.attempt, transitionOptions.expectAttempt);
1535
1640
  if (transitionOptions.expectExecution !== void 0 && !sameStoredValue(existing.value.execution, transitionOptions.expectExecution)) throw new Error(`Task ${id} execution has been replaced`);
1536
1641
  if (transitionOptions.expectQuestionTaskId !== void 0 && existing.value.questionTaskId !== transitionOptions.expectQuestionTaskId) throw new Error(`task ${id} is no longer waiting for question ${transitionOptions.expectQuestionTaskId}`);
1537
1642
  assertTransition(taskTransitions, {
@@ -1815,8 +1920,25 @@ function createStores(options) {
1815
1920
  await projectSafely(() => projectChangeCatalog(change, 1));
1816
1921
  return change;
1817
1922
  } catch (error) {
1818
- if (error instanceof RecordExistsError) {
1923
+ if (error instanceof RecordExistsError) for (;;) {
1819
1924
  const existing = await mustRead("changes", change.id, changeSchema);
1925
+ if (change.taskId !== void 0) {
1926
+ if (existing.value.prUrl !== change.prUrl || existing.value.taskId !== void 0 && existing.value.taskId !== change.taskId) throw new ChangeAssociationError(change.taskId, change.repositoryId, "pull request is already recorded with a different URL or producing Task");
1927
+ if (existing.value.taskId === void 0) {
1928
+ const associated = changeSchema.parse({
1929
+ ...existing.value,
1930
+ taskId: change.taskId,
1931
+ updatedAt: now()
1932
+ });
1933
+ try {
1934
+ await updateChange(associated, existing.version);
1935
+ return associated;
1936
+ } catch (conflict) {
1937
+ if (conflict instanceof VersionConflictError) continue;
1938
+ throw conflict;
1939
+ }
1940
+ }
1941
+ }
1820
1942
  await projectSafely(() => projectChangeCatalog(existing.value, existing.version));
1821
1943
  return existing.value;
1822
1944
  }
@@ -1827,6 +1949,18 @@ function createStores(options) {
1827
1949
  const record = await read("changes", id, changeSchema);
1828
1950
  return record === null ? null : record.value;
1829
1951
  },
1952
+ async updateEvidence(id, input) {
1953
+ const existing = await mustRead("changes", id, changeSchema);
1954
+ if (!sameStoredValue(existing.value, input.expected)) throw new VersionConflictError("changes", id, existing.version);
1955
+ const next = changeSchema.parse({
1956
+ ...existing.value,
1957
+ evidence: input.evidence,
1958
+ confidence: input.confidence,
1959
+ updatedAt: now()
1960
+ });
1961
+ await updateChange(next, existing.version);
1962
+ return next;
1963
+ },
1830
1964
  async transition(id, to, ...args) {
1831
1965
  const transitionOptions = args[0] ?? {};
1832
1966
  const existing = await mustRead("changes", id, changeSchema);
@@ -2170,6 +2304,6 @@ function createStores(options) {
2170
2304
  return stores;
2171
2305
  }
2172
2306
  //#endregion
2173
- export { AnswerEventConflictError, ApprovalError, ChangeAssociationError, ImageInputError, OutboxEventConflictError, SessionConflictError, SettlementError, createStores };
2307
+ export { AnswerEventConflictError, ApprovalError, ChangeAssociationError, ImageInputError, OutboxEventConflictError, SessionConflictError, SettlementError, TaskAttemptConflictError, createStores };
2174
2308
 
2175
2309
  //# sourceMappingURL=engine.mjs.map