@zq-silk/yui 0.13.9 → 0.14.0

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 (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -1,76 +1,178 @@
1
1
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
2
2
  const RECENT_DEDUPE_KEY_LIMIT = 256;
3
- function requireText(value, label) {
4
- const normalized = value.trim();
5
- if (normalized.length === 0) {
6
- throw new Error(`${label} must not be empty`);
7
- }
8
- return normalized;
3
+ export function createWorkMailbox(target) {
4
+ return {
5
+ schemaVersion: 5,
6
+ target: copyTarget(target),
7
+ nextSequence: 1,
8
+ processing: null,
9
+ pending: null,
10
+ recentDedupeKeys: []
11
+ };
9
12
  }
10
- function requireInteger(value, minimum, label) {
11
- if (!Number.isInteger(value) || value < minimum) {
12
- throw new Error(`${label} must be an integer greater than or equal to ${minimum}`);
13
+ export function validateWorkMailbox(value) {
14
+ const mailbox = record(value, "WorkMailbox");
15
+ exact(mailbox, ["schemaVersion", "target", "nextSequence", "processing", "pending", "recentDedupeKeys"], "WorkMailbox");
16
+ if (mailbox.schemaVersion !== 5)
17
+ throw new Error("WorkMailbox must use schemaVersion 5");
18
+ const target = parseTarget(mailbox.target);
19
+ const nextSequence = requireInteger(mailbox.nextSequence, 1, "WorkMailbox nextSequence");
20
+ const processing = mailbox.processing === null ? null : parseProcessing(mailbox.processing);
21
+ const pending = mailbox.pending === null ? null : parseBatch(mailbox.pending, "WorkMailbox pending");
22
+ const recentDedupeKeys = requireStringArray(mailbox.recentDedupeKeys, "WorkMailbox recent dedupe keys");
23
+ if (recentDedupeKeys.length > RECENT_DEDUPE_KEY_LIMIT) {
24
+ throw new Error("WorkMailbox recent dedupe keys exceed the bounded limit");
13
25
  }
14
- return value;
15
- }
16
- function record(value, label) {
17
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
18
- throw new Error(`${label} must be an object`);
26
+ const activeKeys = new Set();
27
+ for (const batch of [processing?.batch, pending]) {
28
+ if (batch === null || batch === undefined)
29
+ continue;
30
+ if (batch.toSequence >= nextSequence) {
31
+ throw new Error("WorkMailbox batch sequence must be lower than nextSequence");
32
+ }
33
+ for (const key of batch.dedupeKeys) {
34
+ if (activeKeys.has(key) || recentDedupeKeys.includes(key)) {
35
+ throw new Error("WorkMailbox batches contain an already-consumed dedupe key");
36
+ }
37
+ activeKeys.add(key);
38
+ }
19
39
  }
20
- return value;
40
+ return {
41
+ schemaVersion: 5,
42
+ target,
43
+ nextSequence,
44
+ processing,
45
+ pending,
46
+ recentDedupeKeys
47
+ };
21
48
  }
22
- function exact(value, keys, label) {
23
- const expected = new Set(keys);
24
- const unknown = Object.keys(value).find((key) => !expected.has(key));
25
- if (unknown !== undefined)
26
- throw new Error(`${label} has unknown field: ${unknown}`);
27
- const missing = keys.find((key) => !Object.hasOwn(value, key));
28
- if (missing !== undefined)
29
- throw new Error(`${label} is missing field: ${missing}`);
49
+ export function enqueueSignal(mailbox, signal) {
50
+ validateWorkMailbox(mailbox);
51
+ const reason = requireText(signal.reason, "signal reason");
52
+ const occurredAt = requireTimestamp(signal.occurredAt, "signal occurredAt");
53
+ const source = requireText(signal.source ?? "yui", "signal source");
54
+ const sequence = mailbox.nextSequence;
55
+ const dedupeKey = signal.dedupeKey === undefined
56
+ ? `sequence:${sequence}`
57
+ : requireText(signal.dedupeKey, "signal dedupeKey");
58
+ if (mailboxContainsDedupeKey(mailbox, dedupeKey))
59
+ return mailbox;
60
+ const incoming = {
61
+ fromSequence: sequence,
62
+ toSequence: sequence,
63
+ reasons: [reason],
64
+ refs: appendUnique([], signal.refs.map(copyRef), mailboxEntityRefKey),
65
+ requestCount: 1,
66
+ firstQueuedAt: occurredAt,
67
+ lastQueuedAt: occurredAt,
68
+ sources: [source],
69
+ dedupeKeys: [dedupeKey],
70
+ ...(signal.factRevision === undefined
71
+ ? {}
72
+ : { highestFactRevision: requireInteger(signal.factRevision, 0, "signal factRevision") })
73
+ };
74
+ return validateWorkMailbox({
75
+ ...mailbox,
76
+ nextSequence: sequence + 1,
77
+ pending: mailbox.pending === null ? incoming : mergeBatches(mailbox.pending, incoming)
78
+ });
30
79
  }
31
- function requireString(value, label) {
32
- if (typeof value !== "string")
33
- throw new Error(`${label} must be a string`);
34
- return requireText(value, label);
80
+ export function mailboxHasPending(mailbox) {
81
+ return mailbox.pending !== null;
35
82
  }
36
- function requireTimestamp(value, label) {
37
- const timestamp = requireString(value, label);
38
- if (!Number.isFinite(Date.parse(timestamp)))
39
- throw new Error(`${label} must be a timestamp`);
40
- return timestamp;
83
+ export function nextPendingBatch(mailbox) {
84
+ return mailbox.pending;
41
85
  }
42
- function requireStringArray(value, label) {
43
- if (!Array.isArray(value))
44
- throw new Error(`${label} must be an array`);
45
- const result = value.map((item, index) => requireString(item, `${label}[${index}]`));
46
- if (new Set(result).size !== result.length)
47
- throw new Error(`${label} must not contain duplicates`);
48
- return result;
86
+ export function mailboxHasWork(mailbox) {
87
+ return mailbox.processing !== null || mailbox.pending !== null;
49
88
  }
50
- function copyTarget(target) {
51
- switch (target.kind) {
52
- case "task":
53
- return { kind: "task", taskId: requireText(target.taskId, "taskId") };
54
- case "role":
55
- return {
56
- kind: "role",
57
- taskId: requireText(target.taskId, "taskId"),
58
- roleName: requireText(target.roleName, "roleName")
59
- };
60
- case "role-runtime":
61
- return {
62
- kind: "role-runtime",
63
- taskId: requireText(target.taskId, "taskId"),
64
- roleName: requireText(target.roleName, "roleName")
65
- };
66
- case "global-role-runtime":
67
- return {
68
- kind: "global-role-runtime",
69
- roleName: requireText(target.roleName, "roleName")
70
- };
71
- case "operator":
72
- return { kind: "operator" };
89
+ export function mailboxBatches(mailbox) {
90
+ return [mailbox.processing?.batch, mailbox.pending]
91
+ .filter((batch) => batch !== null && batch !== undefined);
92
+ }
93
+ export function claimPending(mailbox, options) {
94
+ validateWorkMailbox(mailbox);
95
+ if (mailbox.processing !== null)
96
+ throw new Error("Mailbox is already processing a batch");
97
+ if (mailbox.pending === null)
98
+ throw new Error("Mailbox has no pending work to claim");
99
+ return validateWorkMailbox({
100
+ ...mailbox,
101
+ processing: {
102
+ batchId: requireText(options.batchId, "batch id"),
103
+ batch: mailbox.pending,
104
+ owner: requireText(options.owner, "claim owner"),
105
+ startedAt: requireTimestamp(options.startedAt, "claim startedAt")
106
+ },
107
+ pending: null
108
+ });
109
+ }
110
+ export function bindExecution(mailbox, batchId, executionRef) {
111
+ const processing = requireProcessing(mailbox, batchId);
112
+ return validateWorkMailbox({
113
+ ...mailbox,
114
+ processing: { ...processing, executionRef: copyRef(executionRef) }
115
+ });
116
+ }
117
+ export function completeProcessing(mailbox, batchId) {
118
+ const processing = requireProcessing(mailbox, batchId);
119
+ return validateWorkMailbox({
120
+ ...mailbox,
121
+ processing: null,
122
+ recentDedupeKeys: rememberDedupeKeys(mailbox.recentDedupeKeys, processing.batch.dedupeKeys)
123
+ });
124
+ }
125
+ export function releaseProcessing(mailbox, batchId) {
126
+ const processing = requireProcessing(mailbox, batchId);
127
+ return validateWorkMailbox({
128
+ ...mailbox,
129
+ processing: null,
130
+ pending: mailbox.pending === null
131
+ ? processing.batch
132
+ : mergeBatches(processing.batch, mailbox.pending)
133
+ });
134
+ }
135
+ /**
136
+ * Consumes a whole wake or the exact submitted sequence prefix. Newer signals
137
+ * merged while the Provider request was in flight remain pending.
138
+ */
139
+ export function consumePendingBatch(mailbox, through) {
140
+ validateWorkMailbox(mailbox);
141
+ if (mailbox.pending === null)
142
+ throw new Error("Mailbox has no pending batch");
143
+ const pending = mailbox.pending;
144
+ if (through !== undefined) {
145
+ if (through.fromSequence !== pending.fromSequence
146
+ || !Number.isInteger(through.toSequence)
147
+ || through.toSequence < through.fromSequence
148
+ || through.toSequence > pending.toSequence) {
149
+ throw new Error("Mailbox consumed sequence does not match the pending prefix");
150
+ }
151
+ if (through.toSequence < pending.toSequence) {
152
+ const consumedCount = through.toSequence - through.fromSequence + 1;
153
+ const keysAreSequenceAligned = pending.dedupeKeys.length === pending.requestCount;
154
+ const consumedKeys = keysAreSequenceAligned
155
+ ? pending.dedupeKeys.slice(0, consumedCount)
156
+ : [];
157
+ return validateWorkMailbox({
158
+ ...mailbox,
159
+ pending: {
160
+ ...pending,
161
+ fromSequence: through.toSequence + 1,
162
+ requestCount: pending.toSequence - through.toSequence,
163
+ dedupeKeys: keysAreSequenceAligned
164
+ ? pending.dedupeKeys.slice(consumedCount)
165
+ : pending.dedupeKeys
166
+ },
167
+ recentDedupeKeys: rememberDedupeKeys(mailbox.recentDedupeKeys, consumedKeys)
168
+ });
169
+ }
73
170
  }
171
+ return validateWorkMailbox({
172
+ ...mailbox,
173
+ recentDedupeKeys: rememberDedupeKeys(mailbox.recentDedupeKeys, pending.dedupeKeys),
174
+ pending: null
175
+ });
74
176
  }
75
177
  export function mailboxTargetKey(target) {
76
178
  const copied = copyTarget(target);
@@ -84,45 +186,22 @@ export function mailboxTargetKey(target) {
84
186
  return `global-role-runtime/${encodeURIComponent(copied.roleName)}`;
85
187
  }
86
188
  }
87
- function copyRef(ref) {
88
- if (!("taskId" in ref)) {
89
- if (ref.type !== "task" && ref.type !== "session") {
90
- throw new Error(`entity reference taskId is required for ${ref.type}`);
91
- }
92
- return {
93
- type: ref.type,
94
- id: requireText(ref.id, "entity reference id")
95
- };
96
- }
97
- const validated = validateTaskRecordReference({
98
- taskId: ref.taskId,
99
- localId: ref.id
100
- }, mailboxTaskRecordKind(ref.type));
101
- return {
102
- type: ref.type,
103
- taskId: validated.taskId,
104
- id: validated.localId
105
- };
106
- }
107
189
  export function mailboxEntityRefKey(ref) {
108
190
  return !("taskId" in ref)
109
191
  ? `${ref.type}\u0000${ref.id}`
110
192
  : `${ref.type}\u0000${ref.taskId}\u0000${ref.id}`;
111
193
  }
112
- function appendUnique(existing, incoming, keyOf) {
113
- const result = [...existing];
114
- const keys = new Set(existing.map(keyOf));
115
- for (const value of incoming) {
116
- const key = keyOf(value);
117
- if (!keys.has(key)) {
118
- keys.add(key);
119
- result.push(value);
120
- }
121
- }
122
- return result;
194
+ function mailboxContainsDedupeKey(mailbox, key) {
195
+ return mailbox.recentDedupeKeys.includes(key)
196
+ || mailbox.pending?.dedupeKeys.includes(key) === true
197
+ || mailbox.processing?.batch.dedupeKeys.includes(key) === true;
198
+ }
199
+ function rememberDedupeKeys(existing, dedupeKeys) {
200
+ return appendUnique(existing, dedupeKeys, (value) => value)
201
+ .slice(-RECENT_DEDUPE_KEY_LIMIT);
123
202
  }
124
203
  function mergeBatches(left, right) {
125
- const merged = {
204
+ return {
126
205
  fromSequence: Math.min(left.fromSequence, right.fromSequence),
127
206
  toSequence: Math.max(left.toSequence, right.toSequence),
128
207
  reasons: appendUnique(left.reasons, right.reasons, (reason) => reason),
@@ -136,158 +215,12 @@ function mergeBatches(left, right) {
136
215
  : right.lastQueuedAt,
137
216
  sources: appendUnique(left.sources, right.sources, (source) => source),
138
217
  dedupeKeys: appendUnique(left.dedupeKeys, right.dedupeKeys, (key) => key),
139
- deliveryModes: appendUnique(left.deliveryModes, right.deliveryModes, (mode) => mode),
140
218
  ...((left.highestFactRevision ?? right.highestFactRevision) === undefined
141
219
  ? {}
142
220
  : {
143
221
  highestFactRevision: Math.max(left.highestFactRevision ?? 0, right.highestFactRevision ?? 0)
144
222
  })
145
223
  };
146
- return merged;
147
- }
148
- function requireProcessing(mailbox, batchId) {
149
- const normalizedBatchId = requireText(batchId, "batch id");
150
- if (mailbox.processing === null) {
151
- throw new Error("Mailbox has no processing batch");
152
- }
153
- if (mailbox.processing.batchId !== normalizedBatchId) {
154
- throw new Error(`Mailbox processing batch id does not match ${normalizedBatchId}`);
155
- }
156
- return mailbox.processing;
157
- }
158
- export function createWorkMailbox(target) {
159
- return {
160
- schemaVersion: 3,
161
- target: copyTarget(target),
162
- nextSequence: 1,
163
- processing: null,
164
- pending: {
165
- normal: null,
166
- userCorrection: null,
167
- cursors: { normal: 0, userCorrection: 0 },
168
- recentDedupeKeys: []
169
- },
170
- inputDelivery: null
171
- };
172
- }
173
- export function validateWorkMailbox(value) {
174
- const mailbox = record(value, "WorkMailbox");
175
- exact(mailbox, ["schemaVersion", "target", "nextSequence", "processing", "pending", "inputDelivery"], "WorkMailbox");
176
- if (mailbox.schemaVersion !== 3)
177
- throw new Error("WorkMailbox must use schemaVersion 3");
178
- const target = parseTarget(mailbox.target);
179
- const nextSequence = requireInteger(mailbox.nextSequence, 1, "WorkMailbox nextSequence");
180
- const processing = mailbox.processing === null ? null : parseProcessing(mailbox.processing);
181
- const pending = parsePendingLanes(mailbox.pending);
182
- for (const cursor of [pending.cursors.normal, pending.cursors.userCorrection]) {
183
- if (cursor >= nextSequence) {
184
- throw new Error("WorkMailbox lane cursor must be lower than nextSequence");
185
- }
186
- }
187
- if (pending.normal !== null && pending.normal.fromSequence <= pending.cursors.normal) {
188
- throw new Error("WorkMailbox normal pending batch is behind its cursor");
189
- }
190
- if (pending.userCorrection !== null
191
- && pending.userCorrection.fromSequence <= pending.cursors.userCorrection) {
192
- throw new Error("WorkMailbox user-correction pending batch is behind its cursor");
193
- }
194
- const inputDelivery = mailbox.inputDelivery === null
195
- ? null
196
- : parseInputDelivery(mailbox.inputDelivery);
197
- const batches = [
198
- processing?.batch,
199
- pending.normal,
200
- pending.userCorrection,
201
- inputDelivery?.batch
202
- ].filter((batch) => batch !== null && batch !== undefined);
203
- const activeDedupeKeys = new Set();
204
- for (const batch of batches) {
205
- if (batch.toSequence >= nextSequence) {
206
- throw new Error("WorkMailbox batch sequence must be lower than nextSequence");
207
- }
208
- for (const dedupeKey of batch.dedupeKeys) {
209
- if (activeDedupeKeys.has(dedupeKey)) {
210
- throw new Error("WorkMailbox active batches share a dedupe key");
211
- }
212
- activeDedupeKeys.add(dedupeKey);
213
- }
214
- }
215
- if (inputDelivery !== null && target.kind !== "role" && target.kind !== "operator") {
216
- throw new Error("WorkMailbox inputDelivery requires a Role or Operator target");
217
- }
218
- return { schemaVersion: 3, target, nextSequence, processing, pending, inputDelivery };
219
- }
220
- function parsePendingLanes(value) {
221
- const lanes = record(value, "WorkMailbox pending");
222
- exact(lanes, ["normal", "userCorrection", "cursors", "recentDedupeKeys"], "WorkMailbox pending");
223
- const cursors = record(lanes.cursors, "WorkMailbox pending cursors");
224
- exact(cursors, ["normal", "userCorrection"], "WorkMailbox pending cursors");
225
- const recentDedupeKeys = requireStringArray(lanes.recentDedupeKeys, "WorkMailbox recent dedupe keys");
226
- if (recentDedupeKeys.length > RECENT_DEDUPE_KEY_LIMIT) {
227
- throw new Error("WorkMailbox recent dedupe keys exceed the bounded limit");
228
- }
229
- return {
230
- normal: lanes.normal === null ? null : parseBatch(lanes.normal, "WorkMailbox pending normal"),
231
- userCorrection: lanes.userCorrection === null
232
- ? null
233
- : parseBatch(lanes.userCorrection, "WorkMailbox pending userCorrection"),
234
- cursors: {
235
- normal: requireInteger(cursors.normal, 0, "WorkMailbox normal cursor"),
236
- userCorrection: requireInteger(cursors.userCorrection, 0, "WorkMailbox user-correction cursor")
237
- },
238
- recentDedupeKeys
239
- };
240
- }
241
- function parseTarget(value) {
242
- const target = record(value, "WorkMailbox target");
243
- switch (target.kind) {
244
- case "operator":
245
- exact(target, ["kind"], "WorkMailbox operator target");
246
- return { kind: "operator" };
247
- case "task":
248
- exact(target, ["kind", "taskId"], "WorkMailbox task target");
249
- return { kind: "task", taskId: requireString(target.taskId, "WorkMailbox target taskId") };
250
- case "role":
251
- exact(target, ["kind", "taskId", "roleName"], "WorkMailbox role target");
252
- return {
253
- kind: "role",
254
- taskId: requireString(target.taskId, "WorkMailbox target taskId"),
255
- roleName: requireString(target.roleName, "WorkMailbox target roleName")
256
- };
257
- case "role-runtime":
258
- exact(target, ["kind", "taskId", "roleName"], "WorkMailbox role runtime target");
259
- return {
260
- kind: "role-runtime",
261
- taskId: requireString(target.taskId, "WorkMailbox target taskId"),
262
- roleName: requireString(target.roleName, "WorkMailbox target roleName")
263
- };
264
- case "global-role-runtime":
265
- exact(target, ["kind", "roleName"], "WorkMailbox global role runtime target");
266
- return {
267
- kind: "global-role-runtime",
268
- roleName: requireString(target.roleName, "WorkMailbox target roleName")
269
- };
270
- default:
271
- throw new Error("WorkMailbox target kind is invalid");
272
- }
273
- }
274
- function parseRef(value, label) {
275
- const ref = record(value, label);
276
- const types = [
277
- "task", "run", "work-item", "input", "session", "message", "event"
278
- ];
279
- if (!types.includes(ref.type))
280
- throw new Error(`${label} type is invalid`);
281
- if (ref.type === "task" || ref.type === "session") {
282
- exact(ref, ["type", "id"], label);
283
- return { type: ref.type, id: requireString(ref.id, `${label} id`) };
284
- }
285
- exact(ref, ["type", "taskId", "id"], label);
286
- return copyRef({
287
- type: ref.type,
288
- taskId: requireString(ref.taskId, `${label} taskId`),
289
- id: requireString(ref.id, `${label} id`)
290
- });
291
224
  }
292
225
  function parseBatch(value, label) {
293
226
  const batch = record(value, label);
@@ -300,8 +233,7 @@ function parseBatch(value, label) {
300
233
  "firstQueuedAt",
301
234
  "lastQueuedAt",
302
235
  "sources",
303
- "dedupeKeys",
304
- "deliveryModes"
236
+ "dedupeKeys"
305
237
  ];
306
238
  exact(batch, batch.highestFactRevision === undefined ? required : [...required, "highestFactRevision"], label);
307
239
  const fromSequence = requireInteger(batch.fromSequence, 1, `${label} fromSequence`);
@@ -316,10 +248,10 @@ function parseBatch(value, label) {
316
248
  if (!Array.isArray(batch.refs))
317
249
  throw new Error(`${label} refs must be an array`);
318
250
  const refs = batch.refs.map((ref, index) => parseRef(ref, `${label} refs[${index}]`));
319
- const refKeys = refs.map(mailboxEntityRefKey);
320
- if (new Set(refKeys).size !== refKeys.length)
251
+ if (new Set(refs.map(mailboxEntityRefKey)).size !== refs.length) {
321
252
  throw new Error(`${label} refs must not contain duplicates`);
322
- const result = {
253
+ }
254
+ return {
323
255
  fromSequence,
324
256
  toSequence,
325
257
  reasons,
@@ -329,384 +261,182 @@ function parseBatch(value, label) {
329
261
  lastQueuedAt: requireTimestamp(batch.lastQueuedAt, `${label} lastQueuedAt`),
330
262
  sources: requireStringArray(batch.sources, `${label} sources`),
331
263
  dedupeKeys: requireStringArray(batch.dedupeKeys, `${label} dedupeKeys`),
332
- deliveryModes: parseDeliveryModes(batch.deliveryModes, `${label} deliveryModes`),
333
264
  ...(batch.highestFactRevision === undefined
334
265
  ? {}
335
266
  : {
336
267
  highestFactRevision: requireInteger(batch.highestFactRevision, 0, `${label} highestFactRevision`)
337
268
  })
338
269
  };
339
- return result;
340
- }
341
- function parseDeliveryModes(value, label) {
342
- const modes = requireStringArray(value, label);
343
- for (const mode of modes) {
344
- if (mode !== "followup" && mode !== "steer-if-safe" && mode !== "inject") {
345
- throw new Error(`${label} contains an invalid delivery mode`);
346
- }
347
- }
348
- return modes;
349
270
  }
350
271
  function parseProcessing(value) {
351
272
  const processing = record(value, "WorkMailbox processing");
352
- const required = ["batchId", "lane", "batch", "owner", "startedAt"];
273
+ const required = ["batchId", "batch", "owner", "startedAt"];
353
274
  exact(processing, processing.executionRef === undefined ? required : [...required, "executionRef"], "WorkMailbox processing");
354
275
  const result = {
355
276
  batchId: requireString(processing.batchId, "WorkMailbox processing batchId"),
356
- lane: parseLane(processing.lane, "WorkMailbox processing lane"),
357
277
  batch: parseBatch(processing.batch, "WorkMailbox processing batch"),
358
278
  owner: requireString(processing.owner, "WorkMailbox processing owner"),
359
279
  startedAt: requireTimestamp(processing.startedAt, "WorkMailbox processing startedAt")
360
280
  };
361
281
  return processing.executionRef === undefined
362
282
  ? result
363
- : { ...result, executionRef: parseRef(processing.executionRef, "WorkMailbox processing executionRef") };
283
+ : {
284
+ ...result,
285
+ executionRef: parseRef(processing.executionRef, "WorkMailbox processing executionRef")
286
+ };
364
287
  }
365
- function parseInputDelivery(value) {
366
- const input = record(value, "WorkMailbox inputDelivery");
367
- const required = [
368
- "attemptId",
369
- "lane",
370
- "mode",
371
- "batch",
372
- "owner",
373
- "status",
374
- "startedAt",
375
- "executionRef"
376
- ];
377
- const optional = [
378
- ...(input.pushedAt === undefined ? [] : ["pushedAt"]),
379
- ...(input.unknownReason === undefined ? [] : ["unknownReason"]),
380
- ...(input.providerFence === undefined ? [] : ["providerFence"])
381
- ];
382
- exact(input, [...required, ...optional], "WorkMailbox inputDelivery");
383
- const lane = parseLane(input.lane, "WorkMailbox inputDelivery lane");
384
- const mode = parseDeliveryMode(input.mode, "WorkMailbox inputDelivery mode");
385
- if (input.status !== "dispatching" && input.status !== "delivery-unknown") {
386
- throw new Error("WorkMailbox inputDelivery status is invalid");
387
- }
388
- const status = input.status;
389
- if ((status === "delivery-unknown") !== (input.unknownReason !== undefined)) {
390
- throw new Error("WorkMailbox delivery-unknown requires exactly one unknownReason");
391
- }
392
- const result = {
393
- attemptId: requireString(input.attemptId, "WorkMailbox inputDelivery attemptId"),
394
- lane,
395
- mode,
396
- batch: parseBatch(input.batch, "WorkMailbox inputDelivery batch"),
397
- owner: requireString(input.owner, "WorkMailbox inputDelivery owner"),
398
- status,
399
- startedAt: requireTimestamp(input.startedAt, "WorkMailbox inputDelivery startedAt"),
400
- executionRef: parseRef(input.executionRef, "WorkMailbox inputDelivery executionRef"),
401
- ...(input.pushedAt === undefined
402
- ? {}
403
- : { pushedAt: requireTimestamp(input.pushedAt, "WorkMailbox inputDelivery pushedAt") }),
404
- ...(input.unknownReason === undefined
405
- ? {}
406
- : { unknownReason: requireString(input.unknownReason, "WorkMailbox inputDelivery unknownReason") }),
407
- ...(input.providerFence === undefined
408
- ? {}
409
- : { providerFence: parseProviderFence(input.providerFence) })
410
- };
411
- if (result.pushedAt !== undefined
412
- && Date.parse(result.pushedAt) < Date.parse(result.startedAt)) {
413
- throw new Error("WorkMailbox inputDelivery pushedAt is earlier than startedAt");
288
+ function parseTarget(value) {
289
+ const target = record(value, "WorkMailbox target");
290
+ switch (target.kind) {
291
+ case "operator":
292
+ exact(target, ["kind"], "WorkMailbox operator target");
293
+ return { kind: "operator" };
294
+ case "task":
295
+ exact(target, ["kind", "taskId"], "WorkMailbox task target");
296
+ return { kind: "task", taskId: requireString(target.taskId, "WorkMailbox target taskId") };
297
+ case "role":
298
+ case "role-runtime":
299
+ exact(target, ["kind", "taskId", "roleName"], `WorkMailbox ${target.kind} target`);
300
+ return {
301
+ kind: target.kind,
302
+ taskId: requireString(target.taskId, "WorkMailbox target taskId"),
303
+ roleName: requireString(target.roleName, "WorkMailbox target roleName")
304
+ };
305
+ case "global-role-runtime":
306
+ exact(target, ["kind", "roleName"], "WorkMailbox global role runtime target");
307
+ return {
308
+ kind: "global-role-runtime",
309
+ roleName: requireString(target.roleName, "WorkMailbox target roleName")
310
+ };
311
+ default:
312
+ throw new Error("WorkMailbox target kind is invalid");
414
313
  }
415
- return result;
416
314
  }
417
- function parseProviderFence(value) {
418
- const fence = record(value, "WorkMailbox inputDelivery providerFence");
419
- exact(fence, fence.nativeTurnId === undefined
420
- ? ["conversationId", "activationId"]
421
- : ["conversationId", "activationId", "nativeTurnId"], "WorkMailbox inputDelivery providerFence");
422
- return {
423
- conversationId: requireString(fence.conversationId, "Provider Conversation id"),
424
- activationId: requireString(fence.activationId, "Provider Activation id"),
425
- ...(fence.nativeTurnId === undefined
426
- ? {}
427
- : { nativeTurnId: requireString(fence.nativeTurnId, "Provider Turn id") })
428
- };
315
+ function parseRef(value, label) {
316
+ const ref = record(value, label);
317
+ const types = [
318
+ "task", "turn", "work-item", "input", "session", "message", "event"
319
+ ];
320
+ if (!types.includes(ref.type))
321
+ throw new Error(`${label} type is invalid`);
322
+ if (ref.type === "task" || ref.type === "session") {
323
+ exact(ref, ["type", "id"], label);
324
+ return { type: ref.type, id: requireString(ref.id, `${label} id`) };
325
+ }
326
+ exact(ref, ["type", "taskId", "id"], label);
327
+ return copyRef({
328
+ type: ref.type,
329
+ taskId: requireString(ref.taskId, `${label} taskId`),
330
+ id: requireString(ref.id, `${label} id`)
331
+ });
429
332
  }
430
- function parseLane(value, label) {
431
- if (value !== "normal" && value !== "user-correction") {
432
- throw new Error(`${label} is invalid`);
333
+ function requireProcessing(mailbox, batchId) {
334
+ validateWorkMailbox(mailbox);
335
+ const normalizedBatchId = requireText(batchId, "batch id");
336
+ if (mailbox.processing === null)
337
+ throw new Error("Mailbox has no processing batch");
338
+ if (mailbox.processing.batchId !== normalizedBatchId) {
339
+ throw new Error(`Mailbox processing batch id does not match ${normalizedBatchId}`);
433
340
  }
434
- return value;
341
+ return mailbox.processing;
435
342
  }
436
- function parseDeliveryMode(value, label) {
437
- if (value !== "followup" && value !== "steer-if-safe" && value !== "inject") {
438
- throw new Error(`${label} is invalid`);
343
+ function copyTarget(target) {
344
+ switch (target.kind) {
345
+ case "operator": return { kind: "operator" };
346
+ case "task": return { kind: "task", taskId: requireText(target.taskId, "taskId") };
347
+ case "role":
348
+ case "role-runtime":
349
+ return {
350
+ kind: target.kind,
351
+ taskId: requireText(target.taskId, "taskId"),
352
+ roleName: requireText(target.roleName, "roleName")
353
+ };
354
+ case "global-role-runtime":
355
+ return {
356
+ kind: "global-role-runtime",
357
+ roleName: requireText(target.roleName, "roleName")
358
+ };
439
359
  }
440
- return value;
441
360
  }
442
- export function enqueueSignal(mailbox, signal) {
443
- const reason = requireText(signal.reason, "signal reason");
444
- const occurredAt = requireText(signal.occurredAt, "signal occurredAt");
445
- const lane = signal.lane ?? "normal";
446
- const laneKey = lane === "normal" ? "normal" : "userCorrection";
447
- const deliveryMode = signal.deliveryMode ?? "followup";
448
- const source = requireText(signal.source ?? "yui", "signal source");
449
- const refs = signal.refs.map(copyRef);
450
- const sequence = mailbox.nextSequence;
451
- const dedupeKey = signal.dedupeKey === undefined
452
- ? `sequence:${sequence}`
453
- : requireText(signal.dedupeKey, "signal dedupeKey");
454
- if (mailboxContainsDedupeKey(mailbox, dedupeKey))
455
- return mailbox;
456
- const incoming = {
457
- fromSequence: sequence,
458
- toSequence: sequence,
459
- reasons: [reason],
460
- refs: appendUnique([], refs, mailboxEntityRefKey),
461
- requestCount: 1,
462
- firstQueuedAt: occurredAt,
463
- lastQueuedAt: occurredAt,
464
- sources: [source],
465
- dedupeKeys: [dedupeKey],
466
- deliveryModes: [deliveryMode],
467
- ...(signal.factRevision === undefined
468
- ? {}
469
- : { highestFactRevision: requireInteger(signal.factRevision, 0, "signal factRevision") })
470
- };
471
- return {
472
- ...mailbox,
473
- nextSequence: sequence + 1,
474
- pending: {
475
- ...mailbox.pending,
476
- [laneKey]: mailbox.pending[laneKey] === null
477
- ? incoming
478
- : mergeBatches(mailbox.pending[laneKey], incoming)
361
+ function copyRef(ref) {
362
+ if (!("taskId" in ref)) {
363
+ if (ref.type !== "task" && ref.type !== "session") {
364
+ throw new Error(`entity reference taskId is required for ${ref.type}`);
479
365
  }
480
- };
481
- }
482
- function mailboxContainsDedupeKey(mailbox, key) {
483
- if (mailbox.pending.recentDedupeKeys.includes(key))
484
- return true;
485
- return [
486
- mailbox.pending.normal,
487
- mailbox.pending.userCorrection,
488
- mailbox.processing?.batch,
489
- mailbox.inputDelivery?.batch
490
- ].some((batch) => batch?.dedupeKeys.includes(key) === true);
491
- }
492
- export function pendingLane(mailbox, lane) {
493
- return lane === "normal" ? mailbox.pending.normal : mailbox.pending.userCorrection;
494
- }
495
- export function mailboxHasPending(mailbox) {
496
- return mailbox.pending.normal !== null || mailbox.pending.userCorrection !== null;
497
- }
498
- export function nextPendingLane(mailbox) {
499
- if (mailbox.pending.userCorrection !== null)
500
- return "user-correction";
501
- return mailbox.pending.normal === null ? null : "normal";
502
- }
503
- export function nextPendingBatch(mailbox) {
504
- const lane = nextPendingLane(mailbox);
505
- return lane === null ? null : pendingLane(mailbox, lane);
506
- }
507
- export function mailboxHasWork(mailbox) {
508
- return mailbox.processing !== null
509
- || mailbox.inputDelivery !== null
510
- || mailboxHasPending(mailbox);
511
- }
512
- export function mailboxBatches(mailbox) {
513
- return [
514
- mailbox.processing?.batch,
515
- mailbox.inputDelivery?.batch,
516
- mailbox.pending.userCorrection,
517
- mailbox.pending.normal
518
- ].filter((batch) => batch !== null && batch !== undefined);
366
+ return { type: ref.type, id: requireText(ref.id, "entity reference id") };
367
+ }
368
+ const validated = validateTaskRecordReference({
369
+ taskId: ref.taskId,
370
+ localId: ref.id
371
+ }, mailboxTaskRecordKind(ref.type));
372
+ return { type: ref.type, taskId: validated.taskId, id: validated.localId };
519
373
  }
520
374
  function mailboxTaskRecordKind(type) {
521
375
  switch (type) {
522
- case "run": return "agentRun";
376
+ case "turn": return "turn";
523
377
  case "work-item": return "workItem";
524
378
  case "input": return "inputRequest";
525
379
  case "message": return "message";
526
380
  case "event": return "event";
527
381
  }
528
382
  }
529
- export function claimPending(mailbox, options) {
530
- if (mailbox.processing !== null) {
531
- throw new Error("Mailbox is already processing a batch");
532
- }
533
- const lane = options.lane ?? nextPendingLane(mailbox);
534
- if (lane === null) {
535
- throw new Error("Mailbox has no pending work to claim");
536
- }
537
- const batch = pendingLane(mailbox, lane);
538
- if (batch === null)
539
- throw new Error(`Mailbox has no pending ${lane} work to claim`);
540
- const processing = {
541
- batchId: requireText(options.batchId, "batch id"),
542
- lane,
543
- batch,
544
- owner: requireText(options.owner, "claim owner"),
545
- startedAt: requireText(options.startedAt, "claim startedAt")
546
- };
547
- return {
548
- ...mailbox,
549
- processing,
550
- pending: {
551
- ...mailbox.pending,
552
- [lane === "normal" ? "normal" : "userCorrection"]: null
553
- }
554
- };
555
- }
556
- export function bindExecution(mailbox, batchId, executionRef) {
557
- const processing = requireProcessing(mailbox, batchId);
558
- return {
559
- ...mailbox,
560
- processing: { ...processing, executionRef: copyRef(executionRef) }
561
- };
562
- }
563
- export function completeProcessing(mailbox, batchId) {
564
- const processing = requireProcessing(mailbox, batchId);
565
- const lane = processing.lane;
566
- return validateWorkMailbox({
567
- ...mailbox,
568
- processing: null,
569
- pending: advanceLaneCursor(mailbox.pending, lane, processing.batch)
570
- });
571
- }
572
- export function releaseProcessing(mailbox, batchId) {
573
- const processing = requireProcessing(mailbox, batchId);
574
- const lane = processing.lane;
575
- const key = lane === "normal" ? "normal" : "userCorrection";
576
- const released = mailbox.pending[key] === null
577
- ? processing.batch
578
- : mergeBatches(processing.batch, mailbox.pending[key]);
579
- return {
580
- ...mailbox,
581
- processing: null,
582
- pending: { ...mailbox.pending, [key]: released }
583
- };
584
- }
585
- function requireInputDelivery(mailbox, attemptId) {
586
- const id = requireText(attemptId, "input delivery attempt id");
587
- if (mailbox.inputDelivery === null)
588
- throw new Error("Mailbox has no input delivery");
589
- if (mailbox.inputDelivery.attemptId !== id) {
590
- throw new Error(`Mailbox input delivery does not match ${id}`);
383
+ function appendUnique(existing, incoming, keyOf) {
384
+ const result = [...existing];
385
+ const keys = new Set(existing.map(keyOf));
386
+ for (const value of incoming) {
387
+ const key = keyOf(value);
388
+ if (keys.has(key))
389
+ continue;
390
+ keys.add(key);
391
+ result.push(value);
591
392
  }
592
- return mailbox.inputDelivery;
393
+ return result;
593
394
  }
594
- export function claimInputDelivery(mailbox, options) {
595
- if (mailbox.inputDelivery !== null) {
596
- if (mailbox.inputDelivery.attemptId === options.attemptId)
597
- return mailbox;
598
- throw new Error("Mailbox already has an input delivery");
395
+ function record(value, label) {
396
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
397
+ throw new Error(`${label} must be an object`);
599
398
  }
600
- const batch = pendingLane(mailbox, options.lane);
601
- if (batch === null)
602
- throw new Error(`Mailbox has no pending ${options.lane} input`);
603
- const inputDelivery = {
604
- attemptId: requireText(options.attemptId, "input delivery attempt id"),
605
- lane: options.lane,
606
- mode: options.mode,
607
- batch,
608
- owner: requireText(options.owner, "input delivery owner"),
609
- status: "dispatching",
610
- startedAt: requireTimestamp(options.startedAt, "input delivery startedAt"),
611
- executionRef: copyRef(options.executionRef),
612
- ...(options.providerFence === undefined
613
- ? {}
614
- : { providerFence: parseProviderFence(options.providerFence) })
615
- };
616
- return validateWorkMailbox({
617
- ...mailbox,
618
- pending: {
619
- ...mailbox.pending,
620
- [options.lane === "normal" ? "normal" : "userCorrection"]: null
621
- },
622
- inputDelivery
623
- });
624
- }
625
- export function markInputDeliveryPushed(mailbox, attemptId, pushedAt) {
626
- const delivery = requireInputDelivery(mailbox, attemptId);
627
- if (delivery.pushedAt !== undefined)
628
- return mailbox;
629
- const timestamp = requireTimestamp(pushedAt.toISOString(), "input delivery pushedAt");
630
- return validateWorkMailbox({
631
- ...mailbox,
632
- inputDelivery: { ...delivery, pushedAt: timestamp }
633
- });
399
+ return value;
634
400
  }
635
- export function markInputDeliveryUnknown(mailbox, attemptId, reason, observedAt) {
636
- const delivery = requireInputDelivery(mailbox, attemptId);
637
- const timestamp = requireTimestamp(observedAt.toISOString(), "delivery unknown observedAt");
638
- return validateWorkMailbox({
639
- ...mailbox,
640
- inputDelivery: {
641
- ...delivery,
642
- status: "delivery-unknown",
643
- unknownReason: requireText(reason, "delivery unknown reason"),
644
- pushedAt: delivery.pushedAt ?? timestamp
645
- }
646
- });
401
+ function exact(value, keys, label) {
402
+ const expected = new Set(keys);
403
+ const unknown = Object.keys(value).find((key) => !expected.has(key));
404
+ if (unknown !== undefined)
405
+ throw new Error(`${label} has unknown field: ${unknown}`);
406
+ const missing = keys.find((key) => !Object.hasOwn(value, key));
407
+ if (missing !== undefined)
408
+ throw new Error(`${label} is missing field: ${missing}`);
647
409
  }
648
- export function completeInputDelivery(mailbox, attemptId, _acceptedAt) {
649
- const delivery = requireInputDelivery(mailbox, attemptId);
650
- return validateWorkMailbox({
651
- ...mailbox,
652
- inputDelivery: null,
653
- pending: advanceLaneCursor(mailbox.pending, delivery.lane, delivery.batch)
654
- });
410
+ function requireText(value, label) {
411
+ if (typeof value !== "string" || value.includes("\0"))
412
+ throw new Error(`${label} is invalid`);
413
+ const normalized = value.trim();
414
+ if (normalized.length === 0)
415
+ throw new Error(`${label} must not be empty`);
416
+ return normalized;
655
417
  }
656
- export function consumePendingBatch(mailbox, lane) {
657
- const key = lane === "normal" ? "normal" : "userCorrection";
658
- const batch = mailbox.pending[key];
659
- if (batch === null)
660
- throw new Error(`Mailbox has no pending ${lane} batch`);
661
- return validateWorkMailbox({
662
- ...mailbox,
663
- pending: {
664
- ...advanceLaneCursor(mailbox.pending, lane, batch),
665
- [key]: null
666
- }
667
- });
418
+ function requireString(value, label) {
419
+ if (typeof value !== "string")
420
+ throw new Error(`${label} must be a string`);
421
+ return requireText(value, label);
668
422
  }
669
- function advanceLaneCursor(pending, lane, batch) {
670
- const key = lane === "normal" ? "normal" : "userCorrection";
671
- const recentDedupeKeys = appendUnique(pending.recentDedupeKeys, batch.dedupeKeys, (value) => value).slice(-RECENT_DEDUPE_KEY_LIMIT);
672
- return {
673
- ...pending,
674
- cursors: {
675
- ...pending.cursors,
676
- [key]: Math.max(pending.cursors[key], batch.toSequence)
677
- },
678
- recentDedupeKeys
679
- };
423
+ function requireTimestamp(value, label) {
424
+ const timestamp = requireString(value, label);
425
+ if (!Number.isFinite(Date.parse(timestamp)))
426
+ throw new Error(`${label} must be a timestamp`);
427
+ return timestamp;
680
428
  }
681
- export function releaseInputDelivery(mailbox, attemptId) {
682
- const delivery = requireInputDelivery(mailbox, attemptId);
683
- if (delivery.pushedAt !== undefined || delivery.status === "delivery-unknown") {
684
- throw new Error("A pushed or delivery-unknown input cannot return to pending");
429
+ function requireInteger(value, minimum, label) {
430
+ if (!Number.isInteger(value) || value < minimum) {
431
+ throw new Error(`${label} must be an integer greater than or equal to ${minimum}`);
685
432
  }
686
- const key = delivery.lane === "normal" ? "normal" : "userCorrection";
687
- return validateWorkMailbox({
688
- ...mailbox,
689
- pending: {
690
- ...mailbox.pending,
691
- [key]: mailbox.pending[key] === null
692
- ? delivery.batch
693
- : mergeBatches(delivery.batch, mailbox.pending[key])
694
- },
695
- inputDelivery: null
696
- });
433
+ return value;
697
434
  }
698
- /** Exact Provider readback proved that a previously uncertain mutation is absent. */
699
- export function resolveInputDeliveryNotAccepted(mailbox, attemptId) {
700
- const delivery = requireInputDelivery(mailbox, attemptId);
701
- const key = delivery.lane === "normal" ? "normal" : "userCorrection";
702
- return validateWorkMailbox({
703
- ...mailbox,
704
- pending: {
705
- ...mailbox.pending,
706
- [key]: mailbox.pending[key] === null
707
- ? delivery.batch
708
- : mergeBatches(delivery.batch, mailbox.pending[key])
709
- },
710
- inputDelivery: null
711
- });
435
+ function requireStringArray(value, label) {
436
+ if (!Array.isArray(value))
437
+ throw new Error(`${label} must be an array`);
438
+ const result = value.map((item, index) => requireString(item, `${label}[${index}]`));
439
+ if (new Set(result).size !== result.length)
440
+ throw new Error(`${label} must not contain duplicates`);
441
+ return result;
712
442
  }