@vercel/factory 0.0.17 → 0.0.18

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 (186) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/api-contracts.d.mts +665 -715
  3. package/dist/api-contracts.mjs +91 -4
  4. package/dist/api-contracts.mjs.map +1 -1
  5. package/dist/api-task-graph.mjs +14 -5
  6. package/dist/api-task-graph.mjs.map +1 -1
  7. package/dist/api.d.mts +58 -11
  8. package/dist/api.mjs +715 -399
  9. package/dist/api.mjs.map +1 -1
  10. package/dist/approval-access.d.mts +1 -1
  11. package/dist/approval-contracts.d.mts +3 -4
  12. package/dist/approval-contracts.mjs +1 -2
  13. package/dist/approval-contracts.mjs.map +1 -1
  14. package/dist/blob/index.d.mts +1 -1
  15. package/dist/budget.mjs +20 -3
  16. package/dist/budget.mjs.map +1 -1
  17. package/dist/change-verification/brief.d.mts +1 -1
  18. package/dist/change-verification/dispatch.mjs +8 -1
  19. package/dist/change-verification/dispatch.mjs.map +1 -1
  20. package/dist/changes/eve-record-change.mjs +4 -1
  21. package/dist/changes/eve-record-change.mjs.map +1 -1
  22. package/dist/client-stream.d.mts +19 -2
  23. package/dist/client-stream.mjs +115 -53
  24. package/dist/client-stream.mjs.map +1 -1
  25. package/dist/client-transcript.d.mts +25 -2
  26. package/dist/client-transcript.mjs +95 -13
  27. package/dist/client-transcript.mjs.map +1 -1
  28. package/dist/client.d.mts +145 -19
  29. package/dist/client.mjs +89 -16
  30. package/dist/client.mjs.map +1 -1
  31. package/dist/code-review/contracts.d.mts +1 -0
  32. package/dist/code-review/eve-post-review.d.mts +4 -2
  33. package/dist/code-review/eve-post-review.mjs +8 -0
  34. package/dist/code-review/eve-post-review.mjs.map +1 -1
  35. package/dist/code-review/github-reporter.d.mts +6 -2
  36. package/dist/code-review/github-reporter.mjs +86 -35
  37. package/dist/code-review/github-reporter.mjs.map +1 -1
  38. package/dist/code-review/preparation.d.mts +2 -1
  39. package/dist/code-review/preparation.mjs +2 -1
  40. package/dist/code-review/preparation.mjs.map +1 -1
  41. package/dist/code-review/stall.mjs +11 -2
  42. package/dist/code-review/stall.mjs.map +1 -1
  43. package/dist/connectors/github.d.mts +1 -1
  44. package/dist/connectors/index.d.mts +1 -1
  45. package/dist/connectors/slack.d.mts +1 -1
  46. package/dist/delivery-metrics/metrics.d.mts +1 -1
  47. package/dist/dispatch.mjs +3 -3
  48. package/dist/dispatch.mjs.map +1 -1
  49. package/dist/eve/index.d.mts +1 -1
  50. package/dist/eve/index.mjs +8 -1
  51. package/dist/eve/index.mjs.map +1 -1
  52. package/dist/eve/task-execution.d.mts +4 -4
  53. package/dist/eve/transcript.d.mts +3 -1
  54. package/dist/eve/transcript.mjs.map +1 -1
  55. package/dist/finding-remediation/admission.d.mts +1 -1
  56. package/dist/finding-remediation/brief.d.mts +1 -1
  57. package/dist/github-app.d.mts +1 -1
  58. package/dist/github-publication.d.mts +1 -1
  59. package/dist/github.d.mts +9 -1
  60. package/dist/github.mjs +29 -11
  61. package/dist/github.mjs.map +1 -1
  62. package/dist/index.d.mts +1 -1
  63. package/dist/intake-contracts.d.mts +1 -0
  64. package/dist/intake-contracts.mjs +2 -1
  65. package/dist/intake-contracts.mjs.map +1 -1
  66. package/dist/integrations/github-issues.d.mts +53 -0
  67. package/dist/integrations/github-issues.mjs +170 -0
  68. package/dist/integrations/github-issues.mjs.map +1 -0
  69. package/dist/integrations/github.d.mts +2 -1
  70. package/dist/integrations/github.mjs +2 -1
  71. package/dist/multi-repository-changes/coordination.mjs +1 -1
  72. package/dist/planning/reconcile.mjs +15 -2
  73. package/dist/planning/reconcile.mjs.map +1 -1
  74. package/dist/postgres/index.d.mts +2 -1
  75. package/dist/postgres/index.mjs +11 -5
  76. package/dist/postgres/index.mjs.map +1 -1
  77. package/dist/postgres/indexed-codec.mjs +137 -0
  78. package/dist/postgres/indexed-codec.mjs.map +1 -0
  79. package/dist/postgres/indexed-schema.mjs +99 -0
  80. package/dist/postgres/indexed-schema.mjs.map +1 -0
  81. package/dist/postgres/indexed.d.mts +28 -0
  82. package/dist/postgres/indexed.mjs +277 -0
  83. package/dist/postgres/indexed.mjs.map +1 -0
  84. package/dist/postgres/record-predicates.mjs +23 -0
  85. package/dist/postgres/record-predicates.mjs.map +1 -0
  86. package/dist/presets/software-development/deepsec-brief.d.mts +1 -1
  87. package/dist/presets/software-development/dispatch.mjs +1 -1
  88. package/dist/presets/software-development/dispatch.mjs.map +1 -1
  89. package/dist/presets/software-development/recovery.mjs +9 -2
  90. package/dist/presets/software-development/recovery.mjs.map +1 -1
  91. package/dist/pull-requests/eve-fetch.d.mts +4 -1
  92. package/dist/pull-requests/eve-fetch.mjs +9 -0
  93. package/dist/pull-requests/eve-fetch.mjs.map +1 -1
  94. package/dist/pull-requests/github-publisher.d.mts +1 -1
  95. package/dist/pull-requests/github-publisher.mjs +19 -7
  96. package/dist/pull-requests/github-publisher.mjs.map +1 -1
  97. package/dist/repositories.mjs +20 -13
  98. package/dist/repositories.mjs.map +1 -1
  99. package/dist/sandbox/execution-run-ledger.d.mts +2 -2
  100. package/dist/sandbox/execution-run-ledger.mjs.map +1 -1
  101. package/dist/sandbox/execution.d.mts +2 -2
  102. package/dist/sandbox/execution.mjs.map +1 -1
  103. package/dist/schema/catalog.mjs +1 -1
  104. package/dist/schema/change.d.mts +1 -1
  105. package/dist/schema/communication.d.mts +2 -2
  106. package/dist/schema/factory-config.d.mts +1 -1
  107. package/dist/schema/outbox.d.mts +6 -6
  108. package/dist/schema/session-summary.d.mts +218 -0
  109. package/dist/schema/session-summary.mjs +68 -0
  110. package/dist/schema/session-summary.mjs.map +1 -0
  111. package/dist/schema/session.d.mts +39 -59
  112. package/dist/schema/session.mjs +6 -6
  113. package/dist/schema/session.mjs.map +1 -1
  114. package/dist/schema/task-graph.d.mts +24 -24
  115. package/dist/schema/transcript.d.mts +141 -3
  116. package/dist/schema/transcript.mjs +19 -3
  117. package/dist/schema/transcript.mjs.map +1 -1
  118. package/dist/schema/transitions.d.mts +1 -1
  119. package/dist/schema/transitions.mjs +1 -1
  120. package/dist/schema/transitions.mjs.map +1 -1
  121. package/dist/signals.d.mts +1 -1
  122. package/dist/stall.mjs +10 -2
  123. package/dist/stall.mjs.map +1 -1
  124. package/dist/storage.d.mts +10 -1
  125. package/dist/storage.mjs +7 -1
  126. package/dist/store/approvals.d.mts +14 -0
  127. package/dist/store/approvals.mjs +78 -0
  128. package/dist/store/approvals.mjs.map +1 -0
  129. package/dist/store/conversations.d.mts +59 -0
  130. package/dist/store/conversations.mjs +549 -0
  131. package/dist/store/conversations.mjs.map +1 -0
  132. package/dist/store/driver.d.mts +1 -1
  133. package/dist/store/driver.mjs.map +1 -1
  134. package/dist/store/engine.d.mts +38 -16
  135. package/dist/store/engine.mjs +229 -106
  136. package/dist/store/engine.mjs.map +1 -1
  137. package/dist/store/indexed-engine.d.mts +57 -0
  138. package/dist/store/indexed-engine.mjs +155 -0
  139. package/dist/store/indexed-engine.mjs.map +1 -0
  140. package/dist/store/indexed-memory.d.mts +7 -0
  141. package/dist/store/indexed-memory.mjs +200 -0
  142. package/dist/store/indexed-memory.mjs.map +1 -0
  143. package/dist/store/indexed-projections.d.mts +280 -0
  144. package/dist/store/indexed-projections.mjs +481 -0
  145. package/dist/store/indexed-projections.mjs.map +1 -0
  146. package/dist/store/indexed.d.mts +101 -0
  147. package/dist/store/indexed.mjs +19 -0
  148. package/dist/store/indexed.mjs.map +1 -0
  149. package/dist/store/iterate.d.mts +9 -0
  150. package/dist/store/iterate.mjs +23 -0
  151. package/dist/store/iterate.mjs.map +1 -0
  152. package/dist/store/query-scope.mjs +62 -0
  153. package/dist/store/query-scope.mjs.map +1 -0
  154. package/dist/store/query.d.mts +32 -0
  155. package/dist/store/query.mjs +103 -0
  156. package/dist/store/query.mjs.map +1 -0
  157. package/dist/store/session-history.mjs +24 -0
  158. package/dist/store/session-history.mjs.map +1 -0
  159. package/dist/store/slack-pr-notifications.d.mts +2 -2
  160. package/dist/store/slack-pr-notifications.mjs.map +1 -1
  161. package/dist/store/state.d.mts +26 -0
  162. package/dist/store/state.mjs +95 -0
  163. package/dist/store/state.mjs.map +1 -0
  164. package/dist/store/task-effects.d.mts +1 -1
  165. package/dist/store/task-graphs.d.mts +1 -1
  166. package/dist/store/task-messages.d.mts +1 -1
  167. package/dist/store/task-work.d.mts +1 -1
  168. package/dist/store/task-work.mjs +1 -1
  169. package/dist/store/transcripts.mjs +308 -0
  170. package/dist/store/transcripts.mjs.map +1 -0
  171. package/dist/sweep.d.mts +1 -1
  172. package/dist/sweep.mjs +12 -7
  173. package/dist/sweep.mjs.map +1 -1
  174. package/dist/task-communication.mjs +15 -1
  175. package/dist/task-communication.mjs.map +1 -1
  176. package/dist/task-protocols.d.mts +3 -3
  177. package/dist/tasks.d.mts +1 -1
  178. package/dist/vercel.d.mts +1 -1
  179. package/dist/work-triage/find-existing-work.d.mts +1 -1
  180. package/dist/work-triage/find-existing-work.mjs +12 -2
  181. package/dist/work-triage/find-existing-work.mjs.map +1 -1
  182. package/docs/index.md +4 -0
  183. package/docs/recipes/persistence-recovery.md +15 -9
  184. package/docs/recipes/postgres-indexed-engine.md +108 -0
  185. package/docs/recipes/postgres-indexed-storage.md +145 -0
  186. package/package.json +3 -1
@@ -5,12 +5,12 @@ import { budgetSchema } from "../schema/budget.mjs";
5
5
  import { parseAgentRouteBinding } from "../schema/agent-route.mjs";
6
6
  import { jsonValueSchema, taskWorkSchema } from "../schema/work.mjs";
7
7
  import { taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema } from "../schema/task.mjs";
8
+ import { RecordExistsError, RecordNotFoundError, TransientStoreError, VersionConflictError } from "./driver.mjs";
8
9
  import { InvalidTransitionError, assertTransition, changeInitialState, changeTransitions, legalNextStates, signalOutcomeTransitions, signalTriageTransitions, taskInitialState, taskTransitions } from "../schema/transitions.mjs";
9
10
  import { costSchema, receiptSchema } from "../schema/receipt.mjs";
10
11
  import { imageAttachmentSchema, imageAttachmentsSchema, imageIdSchema, imageUploadSchema } from "../image-input.mjs";
11
12
  import { transcriptSnapshotSchema } from "../schema/transcript.mjs";
12
13
  import { routeTaskWork } from "../agent-routes.mjs";
13
- import { RecordExistsError, RecordNotFoundError, TransientStoreError, VersionConflictError } from "./driver.mjs";
14
14
  import { answerEventSchema } from "../schema/answer.mjs";
15
15
  import { taskGraphDependenciesSchema } from "../schema/task-graph.mjs";
16
16
  import { defineTaskMessageProtocols } from "../schema/task-message.mjs";
@@ -18,17 +18,22 @@ import { createTaskGraphStore } from "./task-graphs.mjs";
18
18
  import { createTaskMessageStore } from "./task-messages.mjs";
19
19
  import { createTaskWorkStore } from "./task-work.mjs";
20
20
  import { createTaskEffectStore } from "./task-effects.mjs";
21
+ import { pageRecords, recordQuerySchema } from "./query.mjs";
22
+ import { changeSchema } from "../schema/change.mjs";
23
+ import { agentSessionIdSchema, agentSessionSchema } from "../schema/session.mjs";
24
+ import { signalSchema } from "../schema/signal.mjs";
25
+ import { communicationEventSchema } from "../schema/communication.mjs";
26
+ import { sessionContinuationRecordSchema, sessionNotificationRecordSchema } from "./session-history.mjs";
27
+ import { factoryIndexKey } from "./indexed-projections.mjs";
28
+ import { recordQueryScope } from "./query-scope.mjs";
21
29
  import { defaultTaskWorkflow, defineWorkflows } from "../workflows.mjs";
22
30
  import { createWorkNotifier } from "./work-notifications.mjs";
23
- import { communicationEventSchema } from "../schema/communication.mjs";
24
- import { agentSessionIdSchema, agentSessionSchema } from "../schema/session.mjs";
25
31
  import { externalSessionSchema, storedSessionSchema } from "../schema/stored-session.mjs";
26
- import { changeSchema } from "../schema/change.mjs";
27
32
  import { outboxMessageSchema, outboxQueueSchema } from "../schema/outbox.mjs";
28
33
  import { storedTaskSchema } from "../schema/stored-task.mjs";
29
34
  import { changeCatalogSchema, externalSessionCatalogSchema, sessionCatalogSchema, taskCatalogSchema, taskRouteSchema } from "../schema/catalog.mjs";
35
+ import { pageTranscriptSnapshot } from "./transcripts.mjs";
30
36
  import { questionRouteSchema } from "../schema/question-route.mjs";
31
- import { signalSchema } from "../schema/signal.mjs";
32
37
  import { createHash, randomUUID } from "node:crypto";
33
38
  import { z } from "zod";
34
39
  import { isDeepStrictEqual } from "node:util";
@@ -177,9 +182,10 @@ const taskReceiptStates = {
177
182
  needs_human: "needs_human",
178
183
  cancelled: "cancelled"
179
184
  };
180
- function needsLifecycleCommunication(task, state, phase) {
185
+ function needsLifecycleCommunication(task, state, phase, parent, includeChild) {
181
186
  if (state === "needs_human") return task.question !== void 0;
182
- if (task.parentTaskId !== void 0) return false;
187
+ const isConversationDelegation = task.parentTaskId !== void 0 && parent?.work.route?.id === "conversation";
188
+ if (task.parentTaskId !== void 0 && !isConversationDelegation && !includeChild?.(task, state)) return false;
183
189
  if (state === "dispatched") return phase === "retry";
184
190
  if (state === "failed" || state === "cancelled") return true;
185
191
  if (state !== "succeeded") return false;
@@ -274,6 +280,9 @@ async function nextTransientRetry(error, attempt, startedAt) {
274
280
  * ```
275
281
  */
276
282
  function createStores(options) {
283
+ return createStoreEngine(options);
284
+ }
285
+ function createStoreEngine(options, indexed) {
277
286
  const { driver } = options;
278
287
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
279
288
  const workflows = options.workflows ?? defineWorkflows([defaultTaskWorkflow]);
@@ -301,8 +310,41 @@ function createStores(options) {
301
310
  if (record === null) throw new RecordNotFoundError(collection, id);
302
311
  return record;
303
312
  }
313
+ async function listPage(collections, schema, input, catalog) {
314
+ const query = recordQuerySchema.parse(input);
315
+ if (indexed) {
316
+ const page = await indexed.queryRecords({
317
+ sources: collections.map((collection) => ({
318
+ collection,
319
+ index: factoryIndexKey(recordQueryScope(collection, query))
320
+ })),
321
+ ...query
322
+ });
323
+ return {
324
+ items: page.entries.map((entry) => {
325
+ if (entry.collection === "sessions") storedSessionSchema.parse(entry.record.value);
326
+ if (entry.collection === "externalSessions") externalSessionSchema.parse(entry.record.value);
327
+ return schema.parse(entry.record.value);
328
+ }),
329
+ nextCursor: page.nextCursor
330
+ };
331
+ }
332
+ const values = catalog ? await catalog() : (await Promise.all(collections.map((collection) => listAll(collection, schema)))).flat();
333
+ return pageRecords(collections.join(":"), values, query, (value) => {
334
+ const key = z.object({
335
+ id: z.string().optional(),
336
+ receiptId: z.string().optional()
337
+ }).parse(value);
338
+ return z.string().min(1).parse(key.id ?? key.receiptId);
339
+ });
340
+ }
304
341
  async function listAll(collection, schema) {
305
- return (await driver.list(collection)).map((entry) => schema.parse(entry.record.value));
342
+ if (indexed) throw new Error("Indexed stores require bounded queries instead of collection lists");
343
+ return (await driver.list(collection)).map((entry) => {
344
+ if (collection === "sessions") storedSessionSchema.parse(entry.record.value);
345
+ if (collection === "externalSessions") externalSessionSchema.parse(entry.record.value);
346
+ return schema.parse(entry.record.value);
347
+ });
306
348
  }
307
349
  async function forEachConcurrent(values, limit, operation) {
308
350
  let next = 0;
@@ -313,12 +355,10 @@ function createStores(options) {
313
355
  const catalogShards = "0123456789abcdef".split("");
314
356
  const shardFor = (id) => createHash("sha256").update(id).digest("hex")[0];
315
357
  function sessionSummary(session) {
316
- return agentSessionSchema.parse({
317
- ...session,
318
- continuations: session.continuations?.map(({ context: _context, ...continuation }) => continuation)
319
- });
358
+ return agentSessionSchema.parse(session);
320
359
  }
321
360
  async function projectCatalogEntry(collection, prefix, schema, value, sourceVersion) {
361
+ if (indexed) return;
322
362
  const id = `${prefix}-${shardFor(value.id)}`;
323
363
  for (;;) {
324
364
  const existing = await driver.get(collection, id);
@@ -360,6 +400,7 @@ function createStores(options) {
360
400
  const projectSessionCatalog = (value, sourceVersion) => projectCatalogEntry("sessionCatalogs", "sessions", sessionCatalogSchema, sessionSummary(value), sourceVersion);
361
401
  const projectExternalSessionCatalog = (value, sourceVersion) => projectCatalogEntry("externalSessionCatalogs", "external-sessions", externalSessionCatalogSchema, sessionSummary(value), sourceVersion);
362
402
  async function markCatalogRepair(collection, prefix, schema, sourceId, sourceVersion) {
403
+ if (indexed) return;
363
404
  const id = `${prefix}-${shardFor(sourceId)}`;
364
405
  for (;;) {
365
406
  const existing = await driver.get(collection, id);
@@ -400,6 +441,7 @@ function createStores(options) {
400
441
  const markSessionCatalogRepair = (id, sourceVersion) => markCatalogRepair("sessionCatalogs", "sessions", sessionCatalogSchema, id, sourceVersion);
401
442
  const markExternalSessionCatalogRepair = (id, sourceVersion) => markCatalogRepair("externalSessionCatalogs", "external-sessions", externalSessionCatalogSchema, id, sourceVersion);
402
443
  async function projectTaskRoute(value, sourceVersion) {
444
+ if (indexed) return;
403
445
  if (value.replyTo.channel !== "api") return;
404
446
  const id = derivedId("task-route", JSON.stringify([value.replyTo.channel, value.replyTo.address]));
405
447
  for (;;) {
@@ -438,6 +480,7 @@ function createStores(options) {
438
480
  }
439
481
  }
440
482
  async function markTaskRouteRepair(value, sourceVersion) {
483
+ if (indexed) return;
441
484
  if (value.replyTo.channel !== "api") return;
442
485
  const id = derivedId("task-route", JSON.stringify([value.replyTo.channel, value.replyTo.address]));
443
486
  for (;;) {
@@ -488,6 +531,7 @@ function createStores(options) {
488
531
  }
489
532
  }
490
533
  async function catalogValues(source, collection, prefix, sourceSchema, catalogSchema, project, fallbackValue = (value) => value) {
534
+ if (indexed) throw new Error("Indexed stores require bounded queries instead of catalogs");
491
535
  let catalogs = await Promise.all(catalogShards.map((shard) => driver.get(collection, `${prefix}-${shard}`)));
492
536
  if (catalogs.some((catalog) => catalog === null)) {
493
537
  const entries = await driver.list(source);
@@ -650,7 +694,8 @@ function createStores(options) {
650
694
  reason: reason ?? `rejected by ${by}`,
651
695
  attempt: task.attempt
652
696
  });
653
- if (needsLifecycleCommunication(next, "cancelled", "approval")) next = taskSchema.parse({
697
+ const rejectionParent = next.parentTaskId ? await stores.tasks.get(next.parentTaskId) : null;
698
+ if (needsLifecycleCommunication(next, "cancelled", "approval", rejectionParent, options.includeChildTaskCommunication)) next = taskSchema.parse({
654
699
  ...next,
655
700
  pendingCommunicationReceiptIds: [.../* @__PURE__ */ new Set([...next.pendingCommunicationReceiptIds ?? [], cancelled.receiptId])]
656
701
  });
@@ -698,6 +743,41 @@ function createStores(options) {
698
743
  }
699
744
  }
700
745
  }
746
+ async function commitLatestContinuation(session) {
747
+ if (!session.latestContinuationId) return null;
748
+ for (;;) {
749
+ const record = await mustRead("sessionContinuations", session.latestContinuationId, sessionContinuationRecordSchema);
750
+ if (record.value.sessionId !== session.id) throw new SessionConflictError("Continuation belongs to another session");
751
+ if (record.value.committed) return record.value;
752
+ try {
753
+ const next = {
754
+ ...record.value,
755
+ committed: true
756
+ };
757
+ await driver.update({
758
+ collection: "sessionContinuations",
759
+ id: record.value.id,
760
+ value: next,
761
+ expectedVersion: record.version
762
+ });
763
+ return next;
764
+ } catch (error) {
765
+ if (!(error instanceof VersionConflictError)) throw error;
766
+ }
767
+ }
768
+ }
769
+ function withinSession(id, query, committed) {
770
+ const parsed = recordQuerySchema.parse(query);
771
+ const alternatives = parsed.match === void 0 ? [{}] : Array.isArray(parsed.match) ? parsed.match : [parsed.match];
772
+ return {
773
+ ...parsed,
774
+ match: alternatives.map((match) => ({
775
+ ...match,
776
+ sessionId: id,
777
+ ...committed === void 0 ? {} : { committed }
778
+ }))
779
+ };
780
+ }
701
781
  const stores = {
702
782
  driverName: driver.name,
703
783
  workflowRecords: {
@@ -752,6 +832,21 @@ function createStores(options) {
752
832
  }
753
833
  }),
754
834
  transcripts: {
835
+ async head(execution) {
836
+ const snapshot = await stores.transcripts.get(execution);
837
+ return snapshot === null ? null : {
838
+ ...snapshot,
839
+ blocks: [],
840
+ blockCount: snapshot.blocks.length
841
+ };
842
+ },
843
+ async page(execution, input) {
844
+ const snapshot = await stores.transcripts.get(execution);
845
+ return snapshot === null ? null : pageTranscriptSnapshot(execution, snapshot, input);
846
+ },
847
+ async block(execution, blockId) {
848
+ return (await stores.transcripts.get(execution))?.blocks.find((block) => block.id === blockId) ?? null;
849
+ },
755
850
  async get(execution) {
756
851
  const record = await read("transcripts", derivedId("transcript", JSON.stringify([execution.provider, execution.sessionId])), storedTranscriptSchema);
757
852
  if (record === null) return null;
@@ -874,8 +969,8 @@ function createStores(options) {
874
969
  const record = await mustRead("sessions", id, storedSessionSchema);
875
970
  await projectSafely(() => projectSessionCatalog(record.value, record.version));
876
971
  const existing = record.value;
877
- const { createdAt: _at, execution: _execution, taskId: _task, notifications: _notifications, continuations: _continuations, ...intent } = existing;
878
- const { createdAt: _newAt, notifications: _newNotifications, ...nextIntent } = candidate;
972
+ const { createdAt: _at, execution: _execution, taskId: _task, latestNotificationId: _notification, latestContinuationId: _continuation, ...intent } = existing;
973
+ const { createdAt: _newAt, ...nextIntent } = candidate;
879
974
  if (!sameStoredValue(intent, nextIntent)) throw new SessionConflictError("Session operationId was reused with different input");
880
975
  return existing;
881
976
  }
@@ -955,24 +1050,66 @@ function createStores(options) {
955
1050
  return existing.value;
956
1051
  }
957
1052
  },
1053
+ async getContinuation(id, taskId) {
1054
+ const record = (await listPage(["sessionContinuations"], sessionContinuationRecordSchema, {
1055
+ limit: 1,
1056
+ match: {
1057
+ sessionId: id,
1058
+ taskId
1059
+ }
1060
+ })).items[0];
1061
+ if (!record) return null;
1062
+ if (record.committed) return record.continuation;
1063
+ return (await stores.sessions.get(id))?.latestContinuationId === record.id ? record.continuation : null;
1064
+ },
1065
+ async getContinuationByOperation(id, operationId) {
1066
+ const key = derivedId("scont", JSON.stringify([id, operationId]));
1067
+ const record = await read("sessionContinuations", key, sessionContinuationRecordSchema);
1068
+ if (!record) return null;
1069
+ if (record.value.sessionId !== id) throw new SessionConflictError("Continuation belongs to another session");
1070
+ if (record.value.committed) return record.value.continuation;
1071
+ return (await stores.sessions.get(id))?.latestContinuationId === key ? record.value.continuation : null;
1072
+ },
1073
+ async listContinuations(id, query) {
1074
+ await commitLatestContinuation((await mustRead("sessions", id, storedSessionSchema)).value);
1075
+ const page = await listPage(["sessionContinuations"], sessionContinuationRecordSchema, withinSession(id, query, true));
1076
+ return {
1077
+ items: page.items.map((record) => record.continuation),
1078
+ nextCursor: page.nextCursor
1079
+ };
1080
+ },
1081
+ async listNotifications(id, query) {
1082
+ const page = await listPage(["sessionNotifications"], sessionNotificationRecordSchema, withinSession(id, query));
1083
+ return {
1084
+ items: page.items.map((record) => record.event),
1085
+ nextCursor: page.nextCursor
1086
+ };
1087
+ },
958
1088
  async cacheContinuationContext(id, input) {
959
1089
  const { taskId, context, attachments } = input;
1090
+ const session = await mustRead("sessions", id, storedSessionSchema);
1091
+ const continuation = await stores.sessions.getContinuation(id, taskId);
1092
+ if (!continuation) throw new SessionConflictError("Task is not a conversation continuation");
1093
+ await stores.images.validate(session.value.owner, attachments ?? []);
1094
+ const key = derivedId("scont", JSON.stringify([id, continuation.operationId]));
960
1095
  for (;;) {
961
- const record = await mustRead("sessions", id, storedSessionSchema);
962
- const continuation = record.value.continuations?.find((item) => item.taskId === taskId);
963
- if (!continuation) throw new SessionConflictError("Task is not a conversation continuation");
964
- if (continuation.context !== void 0) return continuation.context;
965
- await stores.images.validate(record.value.owner, attachments ?? []);
966
- const next = storedSessionSchema.parse({
1096
+ const record = await mustRead("sessionContinuations", key, sessionContinuationRecordSchema);
1097
+ if (record.value.continuation.context !== void 0) return record.value.continuation.context;
1098
+ const next = sessionContinuationRecordSchema.parse({
967
1099
  ...record.value,
968
- continuations: record.value.continuations?.map((item) => item.taskId === taskId ? {
969
- ...item,
1100
+ continuation: {
1101
+ ...record.value.continuation,
970
1102
  context,
971
1103
  ...attachments?.length ? { contextAttachments: attachments } : {}
972
- } : item)
1104
+ }
973
1105
  });
974
1106
  try {
975
- await updateSession(next, record.version);
1107
+ await driver.update({
1108
+ collection: "sessionContinuations",
1109
+ id: key,
1110
+ value: next,
1111
+ expectedVersion: record.version
1112
+ });
976
1113
  return context;
977
1114
  } catch (error) {
978
1115
  if (!(error instanceof VersionConflictError)) throw error;
@@ -981,16 +1118,19 @@ function createStores(options) {
981
1118
  },
982
1119
  async continue(id, input) {
983
1120
  if (input.operationId.trim().length === 0 || input.operationId.length > 200) throw new SessionConflictError("operationId must contain 1–200 characters");
1121
+ const continuationId = derivedId("scont", JSON.stringify([id, input.operationId]));
984
1122
  const dedupeKey = `session:${id}:follow-up:${input.operationId}`;
985
1123
  for (;;) {
986
1124
  const record = await mustRead("sessions", id, storedSessionSchema);
987
1125
  const session = record.value;
1126
+ const latestRecord = await commitLatestContinuation(session);
988
1127
  await stores.images.validate(session.owner, input.attachments ?? []);
989
1128
  if (session.repositoryId === void 0 || session.taskId === void 0) throw new SessionConflictError("Follow-up work requires a repository-bound task conversation");
990
1129
  const parent = (await mustRead("tasks", input.parentTaskId, storedTaskSchema)).value;
991
1130
  if (parent.id !== session.taskId && !(parent.replyTo.channel === "api" && parent.replyTo.address === id)) throw new SessionConflictError("Follow-up parent is outside this conversation");
992
1131
  if (parent.repositoryIds.length !== 1 || parent.repositoryIds[0] !== session.repositoryId) throw new SessionConflictError("Follow-up repository differs from its conversation");
993
- const previous = session.continuations?.find((item) => item.operationId === input.operationId);
1132
+ const previousRecord = await read("sessionContinuations", continuationId, sessionContinuationRecordSchema);
1133
+ const previous = previousRecord?.value.continuation;
994
1134
  if (previous !== void 0 && !sameStoredValue({
995
1135
  operationId: input.operationId,
996
1136
  parentTaskId: input.parentTaskId,
@@ -1014,14 +1154,14 @@ function createStores(options) {
1014
1154
  createdAt: now()
1015
1155
  };
1016
1156
  let next = session;
1017
- if (previous === void 0) {
1157
+ if (!previousRecord?.value.committed) {
1018
1158
  if (![
1019
1159
  "succeeded",
1020
1160
  "failed",
1021
1161
  "cancelled",
1022
1162
  "verifying"
1023
1163
  ].includes(parent.state)) throw new SessionConflictError("Wait for this task or answer its pending question before starting a follow-up");
1024
- const latest = session.continuations?.at(-1);
1164
+ const latest = latestRecord?.continuation;
1025
1165
  if (latest !== void 0 && latest.taskId !== parent.id) {
1026
1166
  let ancestor = parent;
1027
1167
  const visited = /* @__PURE__ */ new Set();
@@ -1039,12 +1179,27 @@ function createStores(options) {
1039
1179
  }
1040
1180
  next = storedSessionSchema.parse({
1041
1181
  ...session,
1042
- continuations: [...session.continuations ?? [], continuation]
1182
+ latestContinuationId: continuationId
1043
1183
  });
1184
+ if (previousRecord && previousRecord.value.previousId !== session.latestContinuationId) throw new SessionConflictError("Conversation changed before this follow-up was admitted; refresh and use a new operationId");
1044
1185
  try {
1186
+ if (!previousRecord) await driver.insert({
1187
+ collection: "sessionContinuations",
1188
+ id: continuationId,
1189
+ value: sessionContinuationRecordSchema.parse({
1190
+ id: continuationId,
1191
+ sessionId: id,
1192
+ taskId: continuation.taskId,
1193
+ previousId: session.latestContinuationId,
1194
+ committed: false,
1195
+ createdAt: continuation.createdAt,
1196
+ continuation
1197
+ })
1198
+ });
1045
1199
  await updateSession(next, record.version);
1200
+ await commitLatestContinuation(next);
1046
1201
  } catch (error) {
1047
- if (error instanceof VersionConflictError) continue;
1202
+ if (error instanceof VersionConflictError || error instanceof RecordExistsError) continue;
1048
1203
  throw error;
1049
1204
  }
1050
1205
  }
@@ -1099,11 +1254,8 @@ function createStores(options) {
1099
1254
  if (records.length > 1) throw new SessionConflictError("Session ID exists in multiple collections");
1100
1255
  return records[0]?.value ?? null;
1101
1256
  },
1102
- async list() {
1103
- return (await Promise.all([listAll("sessions", storedSessionSchema), listAll("externalSessions", externalSessionSchema)])).flat();
1104
- },
1105
- async summaries() {
1106
- return (await Promise.all([catalogValues("sessions", "sessionCatalogs", "sessions", storedSessionSchema, sessionCatalogSchema, projectSessionCatalog, sessionSummary), catalogValues("externalSessions", "externalSessionCatalogs", "external-sessions", externalSessionSchema, externalSessionCatalogSchema, projectExternalSessionCatalog, sessionSummary)])).flat();
1257
+ list(query) {
1258
+ return listPage(["sessions", "externalSessions"], agentSessionSchema, query);
1107
1259
  },
1108
1260
  async bind(id, reference) {
1109
1261
  const external = await read("externalSessions", id, externalSessionSchema);
@@ -1139,16 +1291,36 @@ function createStores(options) {
1139
1291
  async notify(input) {
1140
1292
  const event = communicationEventSchema.parse(input);
1141
1293
  if (event.replyTo.channel !== "api") throw new SessionConflictError("Expected an API notification");
1142
- for (;;) {
1143
- const record = await mustRead("sessions", event.replyTo.address, storedSessionSchema);
1144
- const existing = record.value.notifications.find((item) => item.eventId === event.eventId);
1145
- if (existing !== void 0) {
1146
- if (!sameStoredValue(existing, event)) throw new SessionConflictError("Notification eventId was reused");
1147
- return;
1294
+ const id = agentSessionIdSchema.parse(event.replyTo.address);
1295
+ await mustRead("sessions", id, storedSessionSchema);
1296
+ const key = derivedId("snotice", JSON.stringify([id, event.eventId]));
1297
+ let notification = await read("sessionNotifications", key, sessionNotificationRecordSchema);
1298
+ if (!notification) {
1299
+ try {
1300
+ await driver.insert({
1301
+ collection: "sessionNotifications",
1302
+ id: key,
1303
+ value: sessionNotificationRecordSchema.parse({
1304
+ id: key,
1305
+ sessionId: id,
1306
+ createdAt: now(),
1307
+ event
1308
+ })
1309
+ });
1310
+ } catch (error) {
1311
+ if (!(error instanceof RecordExistsError)) throw error;
1148
1312
  }
1313
+ notification = await mustRead("sessionNotifications", key, sessionNotificationRecordSchema);
1314
+ }
1315
+ if (!sameStoredValue(notification.value.event, event)) throw new SessionConflictError("Notification eventId was reused");
1316
+ for (;;) {
1317
+ const record = await mustRead("sessions", id, storedSessionSchema);
1318
+ if (record.value.latestNotificationId === key) return;
1319
+ const latest = record.value.latestNotificationId ? await mustRead("sessionNotifications", record.value.latestNotificationId, sessionNotificationRecordSchema) : null;
1320
+ if (latest && (latest.value.createdAt > notification.value.createdAt || latest.value.createdAt === notification.value.createdAt && latest.value.id > key)) return;
1149
1321
  const next = storedSessionSchema.parse({
1150
1322
  ...record.value,
1151
- notifications: [...record.value.notifications, event]
1323
+ latestNotificationId: key
1152
1324
  });
1153
1325
  try {
1154
1326
  await updateSession(next, record.version);
@@ -1204,8 +1376,8 @@ function createStores(options) {
1204
1376
  const record = await read("repositories", id, repositorySchema);
1205
1377
  return record === null ? null : record.value;
1206
1378
  },
1207
- list() {
1208
- return listAll("repositories", repositorySchema);
1379
+ list(query) {
1380
+ return listPage(["repositories"], repositorySchema, query);
1209
1381
  }
1210
1382
  },
1211
1383
  signals: {
@@ -1324,8 +1496,8 @@ function createStores(options) {
1324
1496
  });
1325
1497
  return next;
1326
1498
  },
1327
- list() {
1328
- return listAll("signals", signalSchema);
1499
+ list(query) {
1500
+ return listPage(["signals"], signalSchema, query);
1329
1501
  }
1330
1502
  },
1331
1503
  tasks: {
@@ -1743,7 +1915,8 @@ function createStores(options) {
1743
1915
  ...next,
1744
1916
  failureReceiptId: receipt.receiptId
1745
1917
  });
1746
- if (needsLifecycleCommunication(next, receiptState, transitionOptions.phase)) next = taskSchema.parse({
1918
+ const transitionParent = next.parentTaskId ? await stores.tasks.get(next.parentTaskId) : null;
1919
+ if (needsLifecycleCommunication(next, receiptState, transitionOptions.phase, transitionParent, options.includeChildTaskCommunication)) next = taskSchema.parse({
1747
1920
  ...next,
1748
1921
  pendingCommunicationReceiptIds: [...next.pendingCommunicationReceiptIds ?? [], receipt.receiptId]
1749
1922
  });
@@ -1828,60 +2001,10 @@ function createStores(options) {
1828
2001
  }
1829
2002
  return question;
1830
2003
  },
1831
- async forReplyTo(replyTo) {
1832
- const id = derivedId("task-route", JSON.stringify([replyTo.channel, replyTo.address]));
1833
- let route = await read("taskRoutes", id, taskRouteSchema);
1834
- let discovered = [];
1835
- if (route === null) {
1836
- discovered = (await stores.tasks.list()).filter((task) => task.replyTo.channel === replyTo.channel && task.replyTo.address === replyTo.address);
1837
- for (const task of discovered) {
1838
- const record = await mustRead("tasks", task.id, storedTaskSchema);
1839
- await projectSafely(() => projectTaskRoute(record.value, record.version));
1840
- }
1841
- if (discovered.length === 0) {
1842
- const empty = taskRouteSchema.parse({
1843
- schemaVersion: 1,
1844
- id,
1845
- replyTo,
1846
- entries: [],
1847
- updatedAt: now()
1848
- });
1849
- await projectSafely(async () => {
1850
- try {
1851
- await driver.insert({
1852
- collection: "taskRoutes",
1853
- id,
1854
- value: empty
1855
- });
1856
- } catch (error) {
1857
- if (!(error instanceof RecordExistsError)) throw error;
1858
- }
1859
- });
1860
- }
1861
- route = await read("taskRoutes", id, taskRouteSchema);
1862
- }
1863
- if (route === null) return discovered;
1864
- const values = new Map(route.value.entries.map((entry) => [entry.value.id, entry.value]));
1865
- await forEachConcurrent(route.value.repairs, 16, async (repair) => {
1866
- const repairTaskId = taskIdSchema.parse(repair.id);
1867
- const record = await driver.get("tasks", repairTaskId);
1868
- if (record === null) {
1869
- values.delete(repairTaskId);
1870
- return;
1871
- }
1872
- const canonical = storedTaskSchema.parse(record.value);
1873
- if (canonical.replyTo.channel === replyTo.channel && canonical.replyTo.address === replyTo.address) values.set(canonical.id, canonical);
1874
- else values.delete(canonical.id);
1875
- await projectSafely(() => projectTaskRoute(canonical, record.version));
1876
- });
1877
- const tasks = [...values.values()];
1878
- for (const task of tasks) validateTaskWork(task);
1879
- return tasks;
1880
- },
1881
- async list() {
1882
- const tasks = await catalogValues("tasks", "taskCatalogs", "tasks", storedTaskSchema, taskCatalogSchema, projectTaskCatalog);
1883
- for (const task of tasks) validateTaskWork(task);
1884
- return tasks;
2004
+ async list(query) {
2005
+ const page = await listPage(["tasks"], storedTaskSchema, query, () => catalogValues("tasks", "taskCatalogs", "tasks", storedTaskSchema, taskCatalogSchema, projectTaskCatalog));
2006
+ for (const task of page.items) validateTaskWork(task);
2007
+ return page;
1885
2008
  }
1886
2009
  },
1887
2010
  changes: {
@@ -1994,8 +2117,8 @@ function createStores(options) {
1994
2117
  await updateChange(next, existing.version);
1995
2118
  return next;
1996
2119
  },
1997
- list() {
1998
- return catalogValues("changes", "changeCatalogs", "changes", changeSchema, changeCatalogSchema, projectChangeCatalog);
2120
+ list(query) {
2121
+ return listPage(["changes"], changeSchema, query, () => catalogValues("changes", "changeCatalogs", "changes", changeSchema, changeCatalogSchema, projectChangeCatalog));
1999
2122
  }
2000
2123
  },
2001
2124
  receipts: {
@@ -2010,8 +2133,8 @@ function createStores(options) {
2010
2133
  const record = await read("receipts", id, receiptSchema);
2011
2134
  return record === null ? null : record.value;
2012
2135
  },
2013
- list() {
2014
- return listAll("receipts", receiptSchema);
2136
+ list(query) {
2137
+ return listPage(["receipts"], receiptSchema, query);
2015
2138
  }
2016
2139
  },
2017
2140
  budgets: {
@@ -2304,6 +2427,6 @@ function createStores(options) {
2304
2427
  return stores;
2305
2428
  }
2306
2429
  //#endregion
2307
- export { AnswerEventConflictError, ApprovalError, ChangeAssociationError, ImageInputError, OutboxEventConflictError, SessionConflictError, SettlementError, TaskAttemptConflictError, createStores };
2430
+ export { AnswerEventConflictError, ApprovalError, ChangeAssociationError, ImageInputError, OutboxEventConflictError, SessionConflictError, SettlementError, TaskAttemptConflictError, createStoreEngine, createStores };
2308
2431
 
2309
2432
  //# sourceMappingURL=engine.mjs.map