@caupulican/pi-adaptative 0.86.2 → 0.86.5

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 (226) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/core/agent-paths.d.ts +13 -0
  3. package/dist/core/agent-paths.d.ts.map +1 -1
  4. package/dist/core/agent-paths.js +23 -0
  5. package/dist/core/agent-paths.js.map +1 -1
  6. package/dist/core/autonomy/bounded-completion.d.ts +7 -0
  7. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  8. package/dist/core/autonomy/bounded-completion.js +12 -4
  9. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  10. package/dist/core/autonomy/lane-tool-surface.d.ts +2 -0
  11. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  12. package/dist/core/autonomy/lane-tool-surface.js +6 -3
  13. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  14. package/dist/core/autonomy/subagent-prompt.js +1 -1
  15. package/dist/core/autonomy/subagent-prompt.js.map +1 -1
  16. package/dist/core/background-lane-controller.d.ts +25 -2
  17. package/dist/core/background-lane-controller.d.ts.map +1 -1
  18. package/dist/core/background-lane-controller.js +67 -0
  19. package/dist/core/background-lane-controller.js.map +1 -1
  20. package/dist/core/delegation/replay-safe-mailbox-bounds.d.ts +4 -0
  21. package/dist/core/delegation/replay-safe-mailbox-bounds.d.ts.map +1 -0
  22. package/dist/core/delegation/replay-safe-mailbox-bounds.js +20 -0
  23. package/dist/core/delegation/replay-safe-mailbox-bounds.js.map +1 -0
  24. package/dist/core/delegation/sanitized-context-fork.d.ts +25 -0
  25. package/dist/core/delegation/sanitized-context-fork.d.ts.map +1 -0
  26. package/dist/core/delegation/sanitized-context-fork.js +299 -0
  27. package/dist/core/delegation/sanitized-context-fork.js.map +1 -0
  28. package/dist/core/delegation/session-root-mailbox.d.ts +75 -0
  29. package/dist/core/delegation/session-root-mailbox.d.ts.map +1 -0
  30. package/dist/core/delegation/session-root-mailbox.js +627 -0
  31. package/dist/core/delegation/session-root-mailbox.js.map +1 -0
  32. package/dist/core/delegation/worker-agent-control-coordinator.d.ts +102 -9
  33. package/dist/core/delegation/worker-agent-control-coordinator.d.ts.map +1 -1
  34. package/dist/core/delegation/worker-agent-control-coordinator.js +1307 -125
  35. package/dist/core/delegation/worker-agent-control-coordinator.js.map +1 -1
  36. package/dist/core/delegation/worker-agent-control.d.ts +183 -8
  37. package/dist/core/delegation/worker-agent-control.d.ts.map +1 -1
  38. package/dist/core/delegation/worker-agent-control.js +778 -48
  39. package/dist/core/delegation/worker-agent-control.js.map +1 -1
  40. package/dist/core/delegation/worker-attempt-executor.d.ts +9 -0
  41. package/dist/core/delegation/worker-attempt-executor.d.ts.map +1 -1
  42. package/dist/core/delegation/worker-attempt-executor.js +479 -214
  43. package/dist/core/delegation/worker-attempt-executor.js.map +1 -1
  44. package/dist/core/delegation/worker-authority-resolver.d.ts +9 -0
  45. package/dist/core/delegation/worker-authority-resolver.d.ts.map +1 -1
  46. package/dist/core/delegation/worker-authority-resolver.js +22 -14
  47. package/dist/core/delegation/worker-authority-resolver.js.map +1 -1
  48. package/dist/core/delegation/worker-context-fork-store.d.ts +71 -0
  49. package/dist/core/delegation/worker-context-fork-store.d.ts.map +1 -0
  50. package/dist/core/delegation/worker-context-fork-store.js +437 -0
  51. package/dist/core/delegation/worker-context-fork-store.js.map +1 -0
  52. package/dist/core/delegation/worker-context-inheritance-policy.d.ts +18 -0
  53. package/dist/core/delegation/worker-context-inheritance-policy.d.ts.map +1 -0
  54. package/dist/core/delegation/worker-context-inheritance-policy.js +28 -0
  55. package/dist/core/delegation/worker-context-inheritance-policy.js.map +1 -0
  56. package/dist/core/delegation/worker-conversation-revision.d.ts +31 -0
  57. package/dist/core/delegation/worker-conversation-revision.d.ts.map +1 -0
  58. package/dist/core/delegation/worker-conversation-revision.js +203 -0
  59. package/dist/core/delegation/worker-conversation-revision.js.map +1 -0
  60. package/dist/core/delegation/worker-conversation-store.d.ts +130 -18
  61. package/dist/core/delegation/worker-conversation-store.d.ts.map +1 -1
  62. package/dist/core/delegation/worker-conversation-store.js +1263 -79
  63. package/dist/core/delegation/worker-conversation-store.js.map +1 -1
  64. package/dist/core/delegation/worker-delegation-controller.d.ts +29 -2
  65. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
  66. package/dist/core/delegation/worker-delegation-controller.js +625 -111
  67. package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
  68. package/dist/core/delegation/worker-delegation-request.d.ts +2 -0
  69. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -1
  70. package/dist/core/delegation/worker-delegation-request.js.map +1 -1
  71. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +20 -1
  72. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -1
  73. package/dist/core/delegation/worker-dispatch-scheduler.js +210 -37
  74. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -1
  75. package/dist/core/delegation/worker-fleet-limits.d.ts +38 -0
  76. package/dist/core/delegation/worker-fleet-limits.d.ts.map +1 -0
  77. package/dist/core/delegation/worker-fleet-limits.js +113 -0
  78. package/dist/core/delegation/worker-fleet-limits.js.map +1 -0
  79. package/dist/core/delegation/worker-lane-projection.d.ts +1 -0
  80. package/dist/core/delegation/worker-lane-projection.d.ts.map +1 -1
  81. package/dist/core/delegation/worker-lane-projection.js +6 -2
  82. package/dist/core/delegation/worker-lane-projection.js.map +1 -1
  83. package/dist/core/delegation/worker-lifecycle.d.ts +20 -3
  84. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -1
  85. package/dist/core/delegation/worker-lifecycle.js +42 -18
  86. package/dist/core/delegation/worker-lifecycle.js.map +1 -1
  87. package/dist/core/delegation/worker-notification-coordinator.d.ts +4 -0
  88. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -1
  89. package/dist/core/delegation/worker-notification-coordinator.js +45 -4
  90. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -1
  91. package/dist/core/delegation/worker-provider-turn-protocol.d.ts +50 -0
  92. package/dist/core/delegation/worker-provider-turn-protocol.d.ts.map +1 -0
  93. package/dist/core/delegation/worker-provider-turn-protocol.js +185 -0
  94. package/dist/core/delegation/worker-provider-turn-protocol.js.map +1 -0
  95. package/dist/core/delegation/worker-recovery-coordinator.d.ts +45 -4
  96. package/dist/core/delegation/worker-recovery-coordinator.d.ts.map +1 -1
  97. package/dist/core/delegation/worker-recovery-coordinator.js +223 -17
  98. package/dist/core/delegation/worker-recovery-coordinator.js.map +1 -1
  99. package/dist/core/delegation/worker-retry-policy.d.ts +36 -0
  100. package/dist/core/delegation/worker-retry-policy.d.ts.map +1 -0
  101. package/dist/core/delegation/worker-retry-policy.js +41 -0
  102. package/dist/core/delegation/worker-retry-policy.js.map +1 -0
  103. package/dist/core/delegation/worker-runner.d.ts +3 -1
  104. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  105. package/dist/core/delegation/worker-runner.js +4 -2
  106. package/dist/core/delegation/worker-runner.js.map +1 -1
  107. package/dist/core/delegation/worker-task-view.d.ts +43 -0
  108. package/dist/core/delegation/worker-task-view.d.ts.map +1 -0
  109. package/dist/core/delegation/worker-task-view.js +194 -0
  110. package/dist/core/delegation/worker-task-view.js.map +1 -0
  111. package/dist/core/delegation/worker-terminal-handoff-coordinator.d.ts +55 -0
  112. package/dist/core/delegation/worker-terminal-handoff-coordinator.d.ts.map +1 -0
  113. package/dist/core/delegation/worker-terminal-handoff-coordinator.js +179 -0
  114. package/dist/core/delegation/worker-terminal-handoff-coordinator.js.map +1 -0
  115. package/dist/core/delegation/worker-tree-budget-coordinator.d.ts +2 -2
  116. package/dist/core/delegation/worker-tree-budget-coordinator.d.ts.map +1 -1
  117. package/dist/core/delegation/worker-tree-budget-coordinator.js +12 -46
  118. package/dist/core/delegation/worker-tree-budget-coordinator.js.map +1 -1
  119. package/dist/core/keybindings.d.ts +10 -0
  120. package/dist/core/keybindings.d.ts.map +1 -1
  121. package/dist/core/keybindings.js +2 -0
  122. package/dist/core/keybindings.js.map +1 -1
  123. package/dist/core/orchestration/attempt-ordering.d.ts +9 -0
  124. package/dist/core/orchestration/attempt-ordering.d.ts.map +1 -0
  125. package/dist/core/orchestration/attempt-ordering.js +29 -0
  126. package/dist/core/orchestration/attempt-ordering.js.map +1 -0
  127. package/dist/core/orchestration/capability-gateway.d.ts +19 -0
  128. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -1
  129. package/dist/core/orchestration/capability-gateway.js +19 -2
  130. package/dist/core/orchestration/capability-gateway.js.map +1 -1
  131. package/dist/core/orchestration/contracts.d.ts +43 -3
  132. package/dist/core/orchestration/contracts.d.ts.map +1 -1
  133. package/dist/core/orchestration/contracts.js +24 -0
  134. package/dist/core/orchestration/contracts.js.map +1 -1
  135. package/dist/core/orchestration/delegation-ledger.d.ts +8 -3
  136. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -1
  137. package/dist/core/orchestration/delegation-ledger.js +148 -55
  138. package/dist/core/orchestration/delegation-ledger.js.map +1 -1
  139. package/dist/core/orchestration/event-store.d.ts +6 -3
  140. package/dist/core/orchestration/event-store.d.ts.map +1 -1
  141. package/dist/core/orchestration/event-store.js +228 -30
  142. package/dist/core/orchestration/event-store.js.map +1 -1
  143. package/dist/core/orchestration/task-runtime-codecs.d.ts +31 -0
  144. package/dist/core/orchestration/task-runtime-codecs.d.ts.map +1 -0
  145. package/dist/core/orchestration/task-runtime-codecs.js +1471 -0
  146. package/dist/core/orchestration/task-runtime-codecs.js.map +1 -0
  147. package/dist/core/orchestration/task-runtime-projection.d.ts +30 -0
  148. package/dist/core/orchestration/task-runtime-projection.d.ts.map +1 -0
  149. package/dist/core/orchestration/task-runtime-projection.js +340 -0
  150. package/dist/core/orchestration/task-runtime-projection.js.map +1 -0
  151. package/dist/core/orchestration/task-runtime-reducer.d.ts +38 -0
  152. package/dist/core/orchestration/task-runtime-reducer.d.ts.map +1 -0
  153. package/dist/core/orchestration/task-runtime-reducer.js +1270 -0
  154. package/dist/core/orchestration/task-runtime-reducer.js.map +1 -0
  155. package/dist/core/orchestration/task-runtime-state.d.ts +142 -0
  156. package/dist/core/orchestration/task-runtime-state.d.ts.map +1 -0
  157. package/dist/core/orchestration/task-runtime-state.js +14 -0
  158. package/dist/core/orchestration/task-runtime-state.js.map +1 -0
  159. package/dist/core/orchestration/task-runtime.d.ts +44 -98
  160. package/dist/core/orchestration/task-runtime.d.ts.map +1 -1
  161. package/dist/core/orchestration/task-runtime.js +357 -1113
  162. package/dist/core/orchestration/task-runtime.js.map +1 -1
  163. package/dist/core/orchestration/worker-context-fork-reference.d.ts +20 -0
  164. package/dist/core/orchestration/worker-context-fork-reference.d.ts.map +1 -0
  165. package/dist/core/orchestration/worker-context-fork-reference.js +53 -0
  166. package/dist/core/orchestration/worker-context-fork-reference.js.map +1 -0
  167. package/dist/core/research/research-runner.d.ts +2 -0
  168. package/dist/core/research/research-runner.d.ts.map +1 -1
  169. package/dist/core/research/research-runner.js +1 -0
  170. package/dist/core/research/research-runner.js.map +1 -1
  171. package/dist/core/runtime-builder.d.ts.map +1 -1
  172. package/dist/core/runtime-builder.js +8 -0
  173. package/dist/core/runtime-builder.js.map +1 -1
  174. package/dist/core/secrets/credential-project.d.ts.map +1 -1
  175. package/dist/core/secrets/credential-project.js +24 -2
  176. package/dist/core/secrets/credential-project.js.map +1 -1
  177. package/dist/core/security/secret-text.d.ts +3 -0
  178. package/dist/core/security/secret-text.d.ts.map +1 -1
  179. package/dist/core/security/secret-text.js +15 -0
  180. package/dist/core/security/secret-text.js.map +1 -1
  181. package/dist/core/settings-manager.d.ts +3 -0
  182. package/dist/core/settings-manager.d.ts.map +1 -1
  183. package/dist/core/settings-manager.js +160 -8
  184. package/dist/core/settings-manager.js.map +1 -1
  185. package/dist/core/tools/delegate.d.ts +31 -4
  186. package/dist/core/tools/delegate.d.ts.map +1 -1
  187. package/dist/core/tools/delegate.js +751 -98
  188. package/dist/core/tools/delegate.js.map +1 -1
  189. package/dist/main.d.ts.map +1 -1
  190. package/dist/main.js +1 -0
  191. package/dist/main.js.map +1 -1
  192. package/dist/modes/interactive/components/activity-lane.d.ts +5 -0
  193. package/dist/modes/interactive/components/activity-lane.d.ts.map +1 -1
  194. package/dist/modes/interactive/components/activity-lane.js +133 -49
  195. package/dist/modes/interactive/components/activity-lane.js.map +1 -1
  196. package/dist/modes/interactive/components/agents-overlay.d.ts +40 -0
  197. package/dist/modes/interactive/components/agents-overlay.d.ts.map +1 -0
  198. package/dist/modes/interactive/components/agents-overlay.js +153 -0
  199. package/dist/modes/interactive/components/agents-overlay.js.map +1 -0
  200. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  201. package/dist/modes/interactive/components/settings-selector.js +18 -13
  202. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  203. package/dist/modes/interactive/interactive-event-controller.d.ts +1 -1
  204. package/dist/modes/interactive/interactive-event-controller.d.ts.map +1 -1
  205. package/dist/modes/interactive/interactive-event-controller.js +6 -3
  206. package/dist/modes/interactive/interactive-event-controller.js.map +1 -1
  207. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  208. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  209. package/dist/modes/interactive/interactive-mode.js +51 -0
  210. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  211. package/dist/modes/interactive/key-handlers.d.ts +1 -0
  212. package/dist/modes/interactive/key-handlers.d.ts.map +1 -1
  213. package/dist/modes/interactive/key-handlers.js +1 -0
  214. package/dist/modes/interactive/key-handlers.js.map +1 -1
  215. package/docs/harness-architecture.md +61 -17
  216. package/docs/settings.md +17 -9
  217. package/docs/worker-profiles.md +6 -2
  218. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  219. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  220. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  221. package/examples/extensions/sandbox/package-lock.json +2 -2
  222. package/examples/extensions/sandbox/package.json +1 -1
  223. package/examples/extensions/with-deps/package-lock.json +2 -2
  224. package/examples/extensions/with-deps/package.json +1 -1
  225. package/npm-shrinkwrap.json +12 -12
  226. package/package.json +4 -4
@@ -1,15 +1,45 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { existsSync } from "node:fs";
3
3
  import { workerAgentMailboxFile } from "../agent-paths.js";
4
+ import { parseBoundedStringArray } from "../orchestration/bounded-string-array.js";
5
+ import { MAX_ORCHESTRATION_COLLECTION_LENGTH, MAX_ORCHESTRATION_IDENTIFIER_LENGTH, } from "../orchestration/contracts.js";
4
6
  import { withFileLockSync, writeFileAtomicSync } from "../util/atomic-file.js";
5
7
  import { readBoundedTextFileSync } from "../util/bounded-file.js";
8
+ import { createReplaySafeMailboxBounder } from "./replay-safe-mailbox-bounds.js";
6
9
  const MAX_MAILBOX_MESSAGES = 64;
7
10
  const MAX_MAILBOX_MESSAGE_CHARS = 4_096;
8
- const MAX_MAILBOX_BYTES = 128 * 1024;
11
+ const MAX_ORDINARY_MAILBOX_BYTES = 128 * 1024;
9
12
  const MAX_MAILBOX_MESSAGE_ID_CHARS = 512;
10
13
  const MAX_MAILBOX_TIMESTAMP_CHARS = 128;
11
14
  const MAX_MAILBOX_IDENTITY_CHARS = 512;
12
- const MAX_MAILBOX_RETAINED_MESSAGES = MAX_MAILBOX_MESSAGES * 2;
15
+ const MAX_MAILBOX_IDEMPOTENCY_KEY_CHARS = 2_048;
16
+ const MAX_ORDINARY_RETAINED_MESSAGES = MAX_MAILBOX_MESSAGES * 2;
17
+ const MAX_MANDATORY_RETAINED_MESSAGES = MAX_MAILBOX_MESSAGES;
18
+ const MAX_MAILBOX_RETAINED_MESSAGES = MAX_ORDINARY_RETAINED_MESSAGES + MAX_MANDATORY_RETAINED_MESSAGES;
19
+ const MAX_REPLAY_EVIDENCE_SLOTS = MAX_ORDINARY_RETAINED_MESSAGES * 4;
20
+ const MAX_MAILBOX_REPLAY_RECEIPTS = MAX_REPLAY_EVIDENCE_SLOTS + MAX_ORDINARY_RETAINED_MESSAGES;
21
+ const MAX_MAILBOX_TRANSITION_TIMESTAMP = "9999-12-31T23:59:59.999Z";
22
+ const MAX_ENCODED_MANDATORY_MESSAGE_BYTES = Buffer.byteLength(JSON.stringify({
23
+ messageId: `worker-message-${"f".repeat(64)}`,
24
+ kind: "follow_up",
25
+ content: `c${"\0".repeat(MAX_MAILBOX_MESSAGE_CHARS - 1)}`,
26
+ senderAgentId: `s${"\0".repeat(MAX_MAILBOX_IDENTITY_CHARS - 1)}`,
27
+ threadId: `t${"\0".repeat(MAX_MAILBOX_IDENTITY_CHARS - 1)}`,
28
+ replyToMessageId: `r${"\0".repeat(MAX_MAILBOX_MESSAGE_ID_CHARS - 1)}`,
29
+ expectReply: true,
30
+ task: {
31
+ kind: "terminal_handoff",
32
+ sourceAttemptId: `a${"\0".repeat(MAX_MAILBOX_IDENTITY_CHARS - 1)}`,
33
+ },
34
+ createdAt: MAX_MAILBOX_TRANSITION_TIMESTAMP,
35
+ deliveredAt: MAX_MAILBOX_TRANSITION_TIMESTAMP,
36
+ }), "utf-8") + 1;
37
+ // The additive reserve keeps every previously valid 128 KiB ordinary mailbox migration-safe and
38
+ // covers one maximum accepted source acknowledgement transaction (locked by the saturation tests).
39
+ const MAX_MANDATORY_TRANSITION_RESERVE_BYTES = Math.max(48 * 1024, MAX_ENCODED_MANDATORY_MESSAGE_BYTES + 1_024);
40
+ const MAX_MAILBOX_BYTES = MAX_ORDINARY_MAILBOX_BYTES + MAX_MANDATORY_TRANSITION_RESERVE_BYTES;
41
+ const CONTENT_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
42
+ const assertWorkerAgentMailboxBounds = createReplaySafeMailboxBounder("Worker agent mailbox", MAX_MAILBOX_RETAINED_MESSAGES, MAX_MAILBOX_REPLAY_RECEIPTS, MAX_MAILBOX_BYTES);
13
43
  function mailboxDigest(parentSessionId, agentId) {
14
44
  return createHash("sha256")
15
45
  .update("pi-worker-agent-mailbox-v1")
@@ -27,6 +57,92 @@ function assertIdentity(value, label) {
27
57
  throw new TypeError(`Worker ${label} exceeds 512 characters.`);
28
58
  return normalized;
29
59
  }
60
+ /** Derive one target-fenced mailbox replay identity from a host-owned broadcast call identity. */
61
+ export function workerAgentBroadcastTargetIdempotencyKey(baseIdempotencyKey, agentId) {
62
+ const replayIdentity = baseIdempotencyKey.trim();
63
+ if (!replayIdentity || replayIdentity.length > MAX_MAILBOX_IDEMPOTENCY_KEY_CHARS) {
64
+ throw new TypeError("Worker broadcast idempotency key is invalid.");
65
+ }
66
+ const targetAgentId = assertIdentity(agentId, "agent id");
67
+ return `worker-broadcast-${createHash("sha256")
68
+ .update("pi-worker-broadcast-target-v1")
69
+ .update("\0")
70
+ .update(replayIdentity)
71
+ .update("\0")
72
+ .update(targetAgentId)
73
+ .digest("hex")}`;
74
+ }
75
+ /** Session-scoped replay identity. The coordinator fences one accepted id to exactly one target mailbox. */
76
+ export function workerAgentMessageId(parentSessionId, idempotencyKey) {
77
+ const sessionId = assertIdentity(parentSessionId, "parent session id");
78
+ const replayIdentity = idempotencyKey.trim();
79
+ if (!replayIdentity || replayIdentity.length > MAX_MAILBOX_IDEMPOTENCY_KEY_CHARS) {
80
+ throw new TypeError("Worker control idempotency key is invalid.");
81
+ }
82
+ return `worker-message-${createHash("sha256")
83
+ .update("pi-worker-agent-message-v2")
84
+ .update("\0")
85
+ .update(sessionId)
86
+ .update("\0")
87
+ .update(replayIdentity)
88
+ .digest("hex")}`;
89
+ }
90
+ function contentDigest(domain, content) {
91
+ return createHash("sha256").update(domain).update("\0").update(content).digest("hex");
92
+ }
93
+ function controlIntentDigest(input) {
94
+ return contentDigest("pi-worker-agent-control-intent-v1", JSON.stringify([
95
+ input.content,
96
+ input.senderAgentId ?? null,
97
+ input.threadId ?? null,
98
+ input.replyToMessageId ?? null,
99
+ input.expectReply === true,
100
+ input.task ?? null,
101
+ ]));
102
+ }
103
+ function replyIntentDigest(content) {
104
+ return contentDigest("pi-worker-reply-content-v1", content);
105
+ }
106
+ function controlReplayReceipt(messageId, intentDigest, deliveredAt) {
107
+ return { kind: "control", messageId, intentDigest, ...(deliveredAt ? { deliveredAt } : {}) };
108
+ }
109
+ function controlReplayReceiptFor(receipts, messageId) {
110
+ return receipts.find((receipt) => receipt.kind === "control" && receipt.messageId === messageId);
111
+ }
112
+ function replyReplayReceiptFor(receipts, messageId) {
113
+ return receipts.find((receipt) => receipt.kind === "reply" && receipt.messageId === messageId);
114
+ }
115
+ function requiredMailboxTimestamp(value, label) {
116
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_MAILBOX_TIMESTAMP_CHARS) {
117
+ throw new Error(`Worker agent mailbox contains an invalid message ${label}.`);
118
+ }
119
+ const parsed = new Date(value);
120
+ if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== value) {
121
+ throw new Error(`Worker agent mailbox contains an invalid message ${label}.`);
122
+ }
123
+ return value;
124
+ }
125
+ function transitionTimestamp(floor) {
126
+ const current = new Date().toISOString();
127
+ return Date.parse(current) < Date.parse(floor) ? floor : current;
128
+ }
129
+ function parseReplyReceiptFields(receipt, label) {
130
+ if (typeof receipt.replyMessageId !== "string" ||
131
+ !receipt.replyMessageId ||
132
+ receipt.replyMessageId.length > MAX_MAILBOX_MESSAGE_ID_CHARS ||
133
+ typeof receipt.requestSenderId !== "string" ||
134
+ !receipt.requestSenderId ||
135
+ receipt.requestSenderId.length > MAX_MAILBOX_IDENTITY_CHARS ||
136
+ typeof receipt.contentDigest !== "string" ||
137
+ !CONTENT_DIGEST_PATTERN.test(receipt.contentDigest)) {
138
+ throw new Error(`Worker agent mailbox contains invalid ${label}.`);
139
+ }
140
+ return {
141
+ replyMessageId: receipt.replyMessageId,
142
+ requestSenderId: receipt.requestSenderId,
143
+ contentDigest: receipt.contentDigest,
144
+ };
145
+ }
30
146
  function parseState(raw, parentSessionId, agentId) {
31
147
  let candidate;
32
148
  try {
@@ -46,6 +162,42 @@ function parseState(raw, parentSessionId, agentId) {
46
162
  throw new Error("Worker agent mailbox identity conflicts with the requested logical worker.");
47
163
  }
48
164
  const messages = state.messages.map((message) => {
165
+ let task;
166
+ let replyReceipt;
167
+ if (message?.task !== undefined) {
168
+ if (!message.task || typeof message.task !== "object" || Array.isArray(message.task)) {
169
+ throw new Error("Worker agent mailbox contains invalid task-bearing message metadata.");
170
+ }
171
+ const taskRecord = message.task;
172
+ if (taskRecord.kind === "agent_turn" &&
173
+ Object.keys(taskRecord).every((field) => field === "kind" || field === "dependsOnTaskIds")) {
174
+ const dependsOnTaskIds = normalizeWorkerAgentDependencyTaskIds(taskRecord.dependsOnTaskIds);
175
+ task = {
176
+ kind: "agent_turn",
177
+ ...(dependsOnTaskIds.length > 0 ? { dependsOnTaskIds } : {}),
178
+ };
179
+ }
180
+ else if (taskRecord.kind === "terminal_handoff" &&
181
+ typeof taskRecord.sourceAttemptId === "string" &&
182
+ taskRecord.sourceAttemptId.length > 0 &&
183
+ taskRecord.sourceAttemptId.length <= MAX_MAILBOX_IDENTITY_CHARS &&
184
+ Object.keys(taskRecord).every((field) => field === "kind" || field === "sourceAttemptId")) {
185
+ task = { kind: "terminal_handoff", sourceAttemptId: taskRecord.sourceAttemptId };
186
+ }
187
+ else {
188
+ throw new Error("Worker agent mailbox contains invalid task-bearing message metadata.");
189
+ }
190
+ }
191
+ if (message?.replyReceipt !== undefined) {
192
+ if (!message.replyReceipt || typeof message.replyReceipt !== "object" || Array.isArray(message.replyReceipt)) {
193
+ throw new Error("Worker agent mailbox contains invalid reply receipt metadata.");
194
+ }
195
+ const receipt = message.replyReceipt;
196
+ if (!Object.keys(receipt).every((field) => field === "replyMessageId" || field === "requestSenderId" || field === "contentDigest")) {
197
+ throw new Error("Worker agent mailbox contains invalid reply receipt metadata.");
198
+ }
199
+ replyReceipt = parseReplyReceiptFields(receipt, "reply receipt metadata");
200
+ }
49
201
  if (!message ||
50
202
  typeof message !== "object" ||
51
203
  Array.isArray(message) ||
@@ -79,17 +231,192 @@ function parseState(raw, parentSessionId, agentId) {
79
231
  (message.repliedAt !== undefined &&
80
232
  (typeof message.repliedAt !== "string" ||
81
233
  message.repliedAt.length === 0 ||
82
- message.repliedAt.length > MAX_MAILBOX_TIMESTAMP_CHARS))) {
234
+ message.repliedAt.length > MAX_MAILBOX_TIMESTAMP_CHARS)) ||
235
+ (message.failedAt !== undefined &&
236
+ (typeof message.failedAt !== "string" ||
237
+ message.failedAt.length === 0 ||
238
+ message.failedAt.length > MAX_MAILBOX_TIMESTAMP_CHARS)) ||
239
+ (message.failureReason !== undefined &&
240
+ (typeof message.failureReason !== "string" ||
241
+ message.failureReason.length === 0 ||
242
+ message.failureReason.length > MAX_MAILBOX_IDENTITY_CHARS))) {
83
243
  throw new Error("Worker agent mailbox contains an invalid message.");
84
244
  }
85
- return { ...message };
245
+ const createdAt = requiredMailboxTimestamp(message.createdAt, "creation timestamp");
246
+ const deliveredAt = message.deliveredAt === undefined
247
+ ? undefined
248
+ : requiredMailboxTimestamp(message.deliveredAt, "delivery timestamp");
249
+ const repliedAt = message.repliedAt === undefined ? undefined : requiredMailboxTimestamp(message.repliedAt, "reply timestamp");
250
+ const failedAt = message.failedAt === undefined ? undefined : requiredMailboxTimestamp(message.failedAt, "failure timestamp");
251
+ if (deliveredAt !== undefined && Date.parse(deliveredAt) < Date.parse(createdAt)) {
252
+ throw new Error("Worker agent mailbox message delivery timestamp predates creation.");
253
+ }
254
+ if (repliedAt !== undefined && deliveredAt === undefined) {
255
+ throw new Error("Worker agent mailbox message reply timestamp has no delivery timestamp.");
256
+ }
257
+ if (repliedAt !== undefined && deliveredAt !== undefined && Date.parse(repliedAt) < Date.parse(deliveredAt)) {
258
+ throw new Error("Worker agent mailbox message reply timestamp predates delivery.");
259
+ }
260
+ if (repliedAt !== undefined && message.expectReply !== true) {
261
+ throw new Error("Worker agent mailbox replied message did not expect a reply.");
262
+ }
263
+ if (failedAt !== undefined && Date.parse(failedAt) < Date.parse(createdAt)) {
264
+ throw new Error("Worker agent mailbox message failure timestamp predates creation.");
265
+ }
266
+ if ((failedAt === undefined) !== (message.failureReason === undefined) ||
267
+ (failedAt !== undefined && deliveredAt !== undefined)) {
268
+ throw new Error("Worker agent mailbox contains invalid task failure state.");
269
+ }
270
+ if (failedAt !== undefined && (task === undefined || repliedAt !== undefined || replyReceipt !== undefined)) {
271
+ throw new Error("Worker agent mailbox contains invalid task failure state.");
272
+ }
273
+ if ((repliedAt === undefined) !== (replyReceipt === undefined)) {
274
+ throw new Error("Worker agent mailbox replied message is missing its durable reply receipt.");
275
+ }
276
+ return {
277
+ ...message,
278
+ createdAt,
279
+ ...(deliveredAt ? { deliveredAt } : {}),
280
+ ...(repliedAt ? { repliedAt } : {}),
281
+ ...(replyReceipt ? { replyReceipt } : {}),
282
+ ...(failedAt ? { failedAt, failureReason: message.failureReason } : {}),
283
+ ...(task ? { task } : {}),
284
+ };
86
285
  });
87
- const pendingMessages = messages.filter((message) => message.deliveredAt === undefined);
88
- if (pendingMessages.length > MAX_MAILBOX_MESSAGES)
286
+ if (new Set(messages.map((message) => message.messageId)).size !== messages.length) {
287
+ throw new Error("Worker agent mailbox contains duplicate message identities.");
288
+ }
289
+ const pendingMessages = messages.filter(isPendingMessage);
290
+ if (pendingMessages.filter((message) => !hasExternalReplayAuthority(message)).length > MAX_MAILBOX_MESSAGES)
89
291
  throw new Error("Worker agent mailbox exceeds its pending-message bound.");
292
+ if (pendingMessages.filter(hasExternalReplayAuthority).length > MAX_MAILBOX_MESSAGES) {
293
+ throw new Error("Worker agent mailbox exceeds its mandatory pending-message bound.");
294
+ }
90
295
  if (messages.length > MAX_MAILBOX_RETAINED_MESSAGES)
91
296
  throw new Error("Worker agent mailbox exceeds its retained-message bound.");
92
- return { version: 1, parentSessionId, agentId, messages };
297
+ if (messages.filter(usesMandatoryRetainedReserve).length > MAX_MANDATORY_RETAINED_MESSAGES) {
298
+ throw new Error("Worker agent mailbox exceeds its mandatory retained-message reserve.");
299
+ }
300
+ if (messages.filter((message) => !usesMandatoryRetainedReserve(message)).length > MAX_ORDINARY_RETAINED_MESSAGES) {
301
+ throw new Error("Worker agent mailbox exceeds its ordinary retained-message bound.");
302
+ }
303
+ if (state.replyAcknowledgements !== undefined && !Array.isArray(state.replyAcknowledgements)) {
304
+ throw new Error("Worker agent mailbox contains invalid reply acknowledgement state.");
305
+ }
306
+ const messagesById = new Map(messages.map((message) => [message.messageId, message]));
307
+ const acknowledgedMessageIds = new Set();
308
+ const acknowledgementIds = new Set();
309
+ const replyAcknowledgements = (state.replyAcknowledgements ?? []).map((acknowledgement) => {
310
+ if (!acknowledgement ||
311
+ typeof acknowledgement !== "object" ||
312
+ Array.isArray(acknowledgement) ||
313
+ typeof acknowledgement.messageId !== "string" ||
314
+ acknowledgement.messageId.length === 0 ||
315
+ acknowledgement.messageId.length > MAX_MAILBOX_MESSAGE_ID_CHARS ||
316
+ typeof acknowledgement.acknowledgementId !== "string" ||
317
+ acknowledgement.acknowledgementId.length === 0 ||
318
+ acknowledgement.acknowledgementId.length > MAX_MAILBOX_MESSAGE_ID_CHARS ||
319
+ typeof acknowledgement.replyContent !== "string" ||
320
+ acknowledgement.replyContent.length === 0 ||
321
+ acknowledgement.replyContent.length > MAX_MAILBOX_MESSAGE_CHARS ||
322
+ acknowledgement.replyContent.trim() !== acknowledgement.replyContent ||
323
+ acknowledgedMessageIds.has(acknowledgement.messageId) ||
324
+ acknowledgementIds.has(acknowledgement.acknowledgementId)) {
325
+ throw new Error("Worker agent mailbox contains invalid reply acknowledgement state.");
326
+ }
327
+ const message = messagesById.get(acknowledgement.messageId);
328
+ if (!message ||
329
+ message.deliveredAt === undefined ||
330
+ message.expectReply !== true ||
331
+ message.repliedAt === undefined ||
332
+ message.replyReceipt?.replyMessageId !== acknowledgement.acknowledgementId ||
333
+ message.replyReceipt.contentDigest !== replyIntentDigest(acknowledgement.replyContent)) {
334
+ throw new Error("Worker agent mailbox reply acknowledgement does not reference a retained request.");
335
+ }
336
+ acknowledgedMessageIds.add(acknowledgement.messageId);
337
+ acknowledgementIds.add(acknowledgement.acknowledgementId);
338
+ return {
339
+ messageId: acknowledgement.messageId,
340
+ acknowledgementId: acknowledgement.acknowledgementId,
341
+ replyContent: acknowledgement.replyContent,
342
+ };
343
+ });
344
+ if (state.replayReceipts !== undefined && !Array.isArray(state.replayReceipts)) {
345
+ throw new Error("Worker agent mailbox contains invalid replay receipt state.");
346
+ }
347
+ const replayIdentities = new Set();
348
+ const replyMessageIds = new Set();
349
+ const replayReceipts = (state.replayReceipts ?? []).map((receipt) => {
350
+ if (!receipt ||
351
+ typeof receipt !== "object" ||
352
+ Array.isArray(receipt) ||
353
+ typeof receipt.messageId !== "string" ||
354
+ !receipt.messageId ||
355
+ receipt.messageId.length > MAX_MAILBOX_MESSAGE_ID_CHARS) {
356
+ throw new Error("Worker agent mailbox contains invalid replay receipt state.");
357
+ }
358
+ const replayIdentity = `${String(receipt.kind)}:${receipt.messageId}`;
359
+ if (replayIdentities.has(replayIdentity)) {
360
+ throw new Error("Worker agent mailbox contains duplicate replay receipt identities.");
361
+ }
362
+ replayIdentities.add(replayIdentity);
363
+ if (receipt.kind === "control") {
364
+ if (typeof receipt.intentDigest !== "string" ||
365
+ !CONTENT_DIGEST_PATTERN.test(receipt.intentDigest) ||
366
+ !Object.keys(receipt).every((field) => field === "kind" || field === "messageId" || field === "intentDigest" || field === "deliveredAt")) {
367
+ throw new Error("Worker agent mailbox contains invalid control replay receipt state.");
368
+ }
369
+ const deliveredAt = receipt.deliveredAt === undefined
370
+ ? undefined
371
+ : requiredMailboxTimestamp(receipt.deliveredAt, "replay delivery timestamp");
372
+ return controlReplayReceipt(receipt.messageId, receipt.intentDigest, deliveredAt);
373
+ }
374
+ if (receipt.kind !== "reply" ||
375
+ !Object.keys(receipt).every((field) => field === "kind" ||
376
+ field === "messageId" ||
377
+ field === "replyMessageId" ||
378
+ field === "requestSenderId" ||
379
+ field === "contentDigest")) {
380
+ throw new Error("Worker agent mailbox contains invalid reply replay receipt state.");
381
+ }
382
+ const replyReceipt = parseReplyReceiptFields(receipt, "reply replay receipt state");
383
+ if (replyMessageIds.has(replyReceipt.replyMessageId)) {
384
+ throw new Error("Worker agent mailbox contains duplicate reply replay identities.");
385
+ }
386
+ replyMessageIds.add(replyReceipt.replyMessageId);
387
+ return {
388
+ kind: "reply",
389
+ messageId: receipt.messageId,
390
+ ...replyReceipt,
391
+ };
392
+ });
393
+ if (replayReceipts.length > MAX_MAILBOX_REPLAY_RECEIPTS) {
394
+ throw new Error("Worker agent mailbox exceeds its replay receipt bound.");
395
+ }
396
+ if (replayReceipts.filter((receipt) => receipt.kind === "control").length > MAX_REPLAY_EVIDENCE_SLOTS) {
397
+ throw new Error("Worker agent mailbox exceeds its control replay receipt bound.");
398
+ }
399
+ if (replayReceipts.length +
400
+ messages.filter((message) => message.expectReply === true && message.repliedAt === undefined).length >
401
+ MAX_MAILBOX_REPLAY_RECEIPTS) {
402
+ throw new Error("Worker agent mailbox exceeds its projected replay evidence bound.");
403
+ }
404
+ for (const message of messages) {
405
+ const controlReceipt = controlReplayReceiptFor(replayReceipts, message.messageId);
406
+ if (controlReceipt && controlReceipt.deliveredAt !== message.deliveredAt) {
407
+ throw new Error("Worker agent mailbox control delivery conflicts with its durable replay receipt.");
408
+ }
409
+ if (message.replyReceipt) {
410
+ const replyReceipt = replyReplayReceiptFor(replayReceipts, message.messageId);
411
+ if (!replyReceipt ||
412
+ replyReceipt.replyMessageId !== message.replyReceipt.replyMessageId ||
413
+ replyReceipt.requestSenderId !== message.replyReceipt.requestSenderId ||
414
+ replyReceipt.contentDigest !== message.replyReceipt.contentDigest) {
415
+ throw new Error("Worker agent mailbox reply conflicts with its durable replay receipt.");
416
+ }
417
+ }
418
+ }
419
+ return { version: 1, parentSessionId, agentId, messages, replyAcknowledgements, replayReceipts };
93
420
  }
94
421
  function normalizeOptionalIdentity(value, label) {
95
422
  if (value === undefined)
@@ -100,17 +427,135 @@ function normalizeOptionalIdentity(value, label) {
100
427
  }
101
428
  return normalized;
102
429
  }
103
- function pruneDeliveredHistory(messages) {
104
- const pending = messages.filter((message) => message.deliveredAt === undefined);
105
- const awaitingReply = messages.filter((message) => message.deliveredAt !== undefined && message.expectReply === true && message.repliedAt === undefined);
106
- const retainedIds = new Set([...pending, ...awaitingReply].map((message) => message.messageId));
107
- const remainingSlots = Math.max(0, MAX_MAILBOX_RETAINED_MESSAGES - retainedIds.size);
108
- const deliveredCandidates = messages.filter((message) => message.deliveredAt !== undefined && !retainedIds.has(message.messageId));
109
- const recentDelivered = remainingSlots > 0 ? deliveredCandidates.slice(-remainingSlots) : [];
110
- return [...pending, ...awaitingReply, ...recentDelivered].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
430
+ export function normalizeWorkerAgentDependencyTaskIds(value) {
431
+ return parseBoundedStringArray(value === undefined ? [] : value, {
432
+ maxEntries: MAX_ORCHESTRATION_COLLECTION_LENGTH,
433
+ maxLength: MAX_ORCHESTRATION_IDENTIFIER_LENGTH,
434
+ trim: true,
435
+ invalidMessage: "Worker dependency task ids must contain bounded, non-empty strings.",
436
+ duplicateMessage: "Worker dependency task ids must contain unique strings.",
437
+ createError: (message) => new TypeError(message),
438
+ });
439
+ }
440
+ function normalizeTaskMetadata(task) {
441
+ if (task === undefined)
442
+ return undefined;
443
+ if (task.kind === "agent_turn") {
444
+ const dependsOnTaskIds = normalizeWorkerAgentDependencyTaskIds(task.dependsOnTaskIds);
445
+ return {
446
+ kind: "agent_turn",
447
+ ...(dependsOnTaskIds.length > 0 ? { dependsOnTaskIds } : {}),
448
+ };
449
+ }
450
+ const sourceAttemptId = normalizeOptionalIdentity(task.sourceAttemptId, "terminal source attempt id");
451
+ if (!sourceAttemptId)
452
+ throw new TypeError("Worker terminal source attempt id is invalid.");
453
+ return { kind: "terminal_handoff", sourceAttemptId };
454
+ }
455
+ function sameTaskMetadata(left, right) {
456
+ if (left?.kind !== right?.kind)
457
+ return false;
458
+ if (left?.kind === "agent_turn" && right?.kind === "agent_turn") {
459
+ const leftDependencies = left.dependsOnTaskIds ?? [];
460
+ const rightDependencies = right.dependsOnTaskIds ?? [];
461
+ return (leftDependencies.length === rightDependencies.length &&
462
+ leftDependencies.every((dependencyId, index) => dependencyId === rightDependencies[index]));
463
+ }
464
+ if (left?.kind === "terminal_handoff" && right?.kind === "terminal_handoff") {
465
+ return left.sourceAttemptId === right.sourceAttemptId;
466
+ }
467
+ return true;
468
+ }
469
+ function hasExternalReplayAuthority(message) {
470
+ return message.replyToMessageId !== undefined || message.task?.kind === "terminal_handoff";
471
+ }
472
+ function isPendingMessage(message) {
473
+ return message.deliveredAt === undefined && message.failedAt === undefined;
474
+ }
475
+ function usesMandatoryRetainedReserve(message) {
476
+ return isPendingMessage(message) && hasExternalReplayAuthority(message);
477
+ }
478
+ function projectedReplayEvidenceSlots(state) {
479
+ return (state.replayReceipts.length +
480
+ state.messages.filter((message) => message.expectReply === true && message.repliedAt === undefined).length);
481
+ }
482
+ function normalizeReplyAcknowledgement(messageId, acknowledgementId) {
483
+ const normalizedMessageId = messageId.trim();
484
+ if (!normalizedMessageId || normalizedMessageId.length > MAX_MAILBOX_MESSAGE_ID_CHARS) {
485
+ throw new TypeError("A worker control message id is invalid.");
486
+ }
487
+ const normalizedAcknowledgementId = acknowledgementId.trim();
488
+ if (!normalizedAcknowledgementId || normalizedAcknowledgementId.length > MAX_MAILBOX_MESSAGE_ID_CHARS) {
489
+ throw new TypeError("A worker reply acknowledgement id is invalid.");
490
+ }
491
+ return { messageId: normalizedMessageId, acknowledgementId: normalizedAcknowledgementId };
492
+ }
493
+ function normalizeReplyContent(replyContent) {
494
+ const normalized = replyContent.trim();
495
+ if (!normalized || normalized.length > MAX_MAILBOX_MESSAGE_CHARS) {
496
+ throw new TypeError(`Worker reply content must contain from 1 through ${MAX_MAILBOX_MESSAGE_CHARS.toLocaleString("en-US")} characters.`);
497
+ }
498
+ return normalized;
499
+ }
500
+ function pruneDeliveredHistory(parentSessionId, agentId, messages, replyAcknowledgements, replayReceipts, ordinaryAdmission) {
501
+ const protectedReplyIds = new Set(replyAcknowledgements.map((acknowledgement) => acknowledgement.messageId));
502
+ const protectedMessages = messages.filter((message) => isPendingMessage(message) ||
503
+ (message.expectReply === true && message.repliedAt === undefined) ||
504
+ protectedReplyIds.has(message.messageId));
505
+ const protectedMandatory = protectedMessages.filter(usesMandatoryRetainedReserve);
506
+ const protectedOrdinary = protectedMessages.filter((message) => !usesMandatoryRetainedReserve(message));
507
+ if (protectedMandatory.length > MAX_MANDATORY_RETAINED_MESSAGES) {
508
+ throw new Error("Worker agent mailbox mandatory retained-message reserve is exhausted.");
509
+ }
510
+ if (protectedOrdinary.length > MAX_ORDINARY_RETAINED_MESSAGES) {
511
+ throw new Error("Worker agent mailbox ordinary retained-message capacity is exhausted.");
512
+ }
513
+ const retainedIds = new Set(protectedMessages.map((message) => message.messageId));
514
+ const deliveredCandidates = messages.filter((message) => (message.deliveredAt !== undefined || message.failedAt !== undefined) && !retainedIds.has(message.messageId));
515
+ const remainingOrdinarySlots = MAX_ORDINARY_RETAINED_MESSAGES - protectedOrdinary.length;
516
+ const recentDelivered = remainingOrdinarySlots > 0 ? deliveredCandidates.slice(-remainingOrdinarySlots) : [];
517
+ let retained = [...protectedMessages, ...recentDelivered].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
518
+ const retainedReplayReceipts = [...replayReceipts];
519
+ while (true) {
520
+ const state = {
521
+ version: 1,
522
+ parentSessionId,
523
+ agentId,
524
+ messages: retained,
525
+ replyAcknowledgements: [...replyAcknowledgements],
526
+ replayReceipts: retainedReplayReceipts,
527
+ };
528
+ const encodedBytes = ordinaryAdmission ? ordinaryAdmissionEncodedBytes(state) : encodedStateBytes(state);
529
+ const encodedLimit = ordinaryAdmission ? MAX_ORDINARY_MAILBOX_BYTES : MAX_MAILBOX_BYTES;
530
+ if (encodedBytes <= encodedLimit)
531
+ break;
532
+ const oldestCompletedIndex = retained.findIndex((message) => !retainedIds.has(message.messageId));
533
+ if (oldestCompletedIndex >= 0) {
534
+ retained = retained.filter((_, index) => index !== oldestCompletedIndex);
535
+ continue;
536
+ }
537
+ break;
538
+ }
539
+ return { messages: retained, replayReceipts: retainedReplayReceipts };
111
540
  }
112
541
  function encodedStateBytes(state) {
113
- return Buffer.byteLength(JSON.stringify(state), "utf-8");
542
+ return Buffer.byteLength(`${JSON.stringify(state)}\n`, "utf-8");
543
+ }
544
+ function ordinaryAdmissionEncodedBytes(state) {
545
+ const projectedDeliveryIds = new Set(state.messages
546
+ .filter((message) => message.deliveredAt === undefined &&
547
+ message.failedAt === undefined &&
548
+ !hasExternalReplayAuthority(message))
549
+ .map((message) => message.messageId));
550
+ return encodedStateBytes({
551
+ ...state,
552
+ messages: state.messages.map((message) => projectedDeliveryIds.has(message.messageId)
553
+ ? { ...message, deliveredAt: MAX_MAILBOX_TRANSITION_TIMESTAMP }
554
+ : message),
555
+ replayReceipts: state.replayReceipts.map((receipt) => receipt.kind === "control" && receipt.deliveredAt === undefined && projectedDeliveryIds.has(receipt.messageId)
556
+ ? { ...receipt, deliveredAt: MAX_MAILBOX_TRANSITION_TIMESTAMP }
557
+ : receipt),
558
+ });
114
559
  }
115
560
  /**
116
561
  * Bounded durable inbox for a single logical worker agent.
@@ -127,6 +572,13 @@ export class WorkerAgentMailbox {
127
572
  this.file = workerAgentMailboxFile(options.agentDir, this.parentSessionId, mailboxDigest(this.parentSessionId, this.agentId));
128
573
  }
129
574
  enqueue(input) {
575
+ const accepted = this.enqueueWithReceipt(input);
576
+ if (accepted.status !== "retained")
577
+ throw new Error("Unkeyed worker control unexpectedly resolved as a replay.");
578
+ return accepted.message;
579
+ }
580
+ /** Enqueue with exact creation evidence for an idempotent surrounding acceptance flow. */
581
+ enqueueWithReceipt(input) {
130
582
  const content = input.content.trim();
131
583
  if (!content)
132
584
  throw new TypeError("A worker control message is required.");
@@ -136,21 +588,15 @@ export class WorkerAgentMailbox {
136
588
  const senderAgentId = normalizeOptionalIdentity(input.senderAgentId, "sender agent id");
137
589
  const threadId = normalizeOptionalIdentity(input.threadId, "thread id");
138
590
  const replyToMessageId = normalizeOptionalIdentity(input.replyToMessageId, "reply message id");
591
+ const task = normalizeTaskMetadata(input.task);
139
592
  const idempotencyKey = input.idempotencyKey?.trim();
140
- if (input.idempotencyKey !== undefined && (!idempotencyKey || idempotencyKey.length > 2_048)) {
593
+ if (input.idempotencyKey !== undefined &&
594
+ (!idempotencyKey || idempotencyKey.length > MAX_MAILBOX_IDEMPOTENCY_KEY_CHARS)) {
141
595
  throw new TypeError("Worker control idempotency key is invalid.");
142
596
  }
143
597
  const message = {
144
598
  messageId: idempotencyKey
145
- ? `worker-message-${createHash("sha256")
146
- .update("pi-worker-agent-message-v1")
147
- .update("\0")
148
- .update(this.parentSessionId)
149
- .update("\0")
150
- .update(this.agentId)
151
- .update("\0")
152
- .update(idempotencyKey)
153
- .digest("hex")}`
599
+ ? workerAgentMessageId(this.parentSessionId, idempotencyKey)
154
600
  : `worker-message-${randomUUID()}`,
155
601
  kind: input.kind,
156
602
  content,
@@ -158,39 +604,93 @@ export class WorkerAgentMailbox {
158
604
  ...(threadId ? { threadId } : {}),
159
605
  ...(replyToMessageId ? { replyToMessageId } : {}),
160
606
  ...(input.expectReply === true ? { expectReply: true } : {}),
607
+ ...(task ? { task } : {}),
161
608
  createdAt: new Date().toISOString(),
162
609
  };
610
+ const intentDigest = idempotencyKey ? controlIntentDigest(message) : undefined;
611
+ const externallyReplayOwned = hasExternalReplayAuthority(message);
163
612
  let queued = message;
164
613
  let created = false;
614
+ let completedReplay = false;
165
615
  this.update((state) => {
166
616
  const existing = state.messages.find((candidate) => candidate.messageId === message.messageId);
617
+ const replayReceipt = controlReplayReceiptFor(state.replayReceipts, message.messageId);
618
+ const addsControlReceipt = intentDigest !== undefined && !externallyReplayOwned && replayReceipt === undefined;
619
+ if (addsControlReceipt &&
620
+ state.replayReceipts.filter((receipt) => receipt.kind === "control").length >= MAX_REPLAY_EVIDENCE_SLOTS) {
621
+ throw new Error(`Worker agent mailbox control replay receipt capacity reached its ${MAX_REPLAY_EVIDENCE_SLOTS} entry limit.`);
622
+ }
167
623
  if (existing) {
168
- const sameIntent = existing.kind === message.kind &&
169
- existing.content === message.content &&
624
+ // `kind` is the delivery-mode projection at first acceptance. It is deliberately not
625
+ // part of task intent and is never rewritten when a retry observes a different live
626
+ // activity state. The durable `task` identity owns whether an idle turn is required.
627
+ const sameIntent = existing.content === message.content &&
170
628
  existing.senderAgentId === message.senderAgentId &&
171
629
  existing.threadId === message.threadId &&
172
630
  existing.replyToMessageId === message.replyToMessageId &&
173
- existing.expectReply === message.expectReply;
631
+ existing.expectReply === message.expectReply &&
632
+ sameTaskMetadata(existing.task, message.task);
174
633
  if (!sameIntent)
175
634
  throw new Error("Worker control idempotency identity conflicts with an existing message.");
635
+ if (intentDigest && replayReceipt && replayReceipt.intentDigest !== intentDigest) {
636
+ throw new Error("Worker control idempotency identity conflicts with its durable replay receipt.");
637
+ }
176
638
  queued = existing;
639
+ return intentDigest && !externallyReplayOwned && !replayReceipt
640
+ ? {
641
+ ...state,
642
+ replayReceipts: [
643
+ ...state.replayReceipts,
644
+ controlReplayReceipt(message.messageId, intentDigest, existing.deliveredAt),
645
+ ],
646
+ }
647
+ : state;
648
+ }
649
+ if (replayReceipt) {
650
+ if (!intentDigest || replayReceipt.intentDigest !== intentDigest) {
651
+ throw new Error("Worker control idempotency identity conflicts with its durable replay receipt.");
652
+ }
653
+ completedReplay = true;
177
654
  return state;
178
655
  }
179
- if (state.messages.filter((candidate) => candidate.deliveredAt === undefined).length >= MAX_MAILBOX_MESSAGES) {
180
- throw new Error(`Worker agent mailbox reached its ${MAX_MAILBOX_MESSAGES} message limit.`);
656
+ const pending = state.messages.filter((candidate) => candidate.deliveredAt === undefined && candidate.failedAt === undefined);
657
+ if (pending.filter((candidate) => hasExternalReplayAuthority(candidate) === externallyReplayOwned).length >=
658
+ MAX_MAILBOX_MESSAGES) {
659
+ throw new Error(externallyReplayOwned
660
+ ? `Worker agent mailbox reached its ${MAX_MAILBOX_MESSAGES} mandatory message limit.`
661
+ : `Worker agent mailbox reached its ${MAX_MAILBOX_MESSAGES} message limit.`);
181
662
  }
182
663
  created = true;
183
- return { ...state, messages: [...state.messages, message] };
184
- });
664
+ return {
665
+ ...state,
666
+ messages: [...state.messages, message],
667
+ ...(intentDigest && !externallyReplayOwned
668
+ ? {
669
+ replayReceipts: [
670
+ ...state.replayReceipts,
671
+ controlReplayReceipt(message.messageId, intentDigest),
672
+ ],
673
+ }
674
+ : {}),
675
+ };
676
+ }, !externallyReplayOwned, true);
185
677
  if (created)
186
678
  this.notify();
187
- return structuredClone(queued);
679
+ if (completedReplay)
680
+ return { status: "completed_replay", messageId: message.messageId, created: false };
681
+ return { status: "retained", messageId: queued.messageId, message: structuredClone(queued), created };
188
682
  }
189
683
  pending(kind) {
190
684
  return this.read()
191
- .messages.filter((message) => message.deliveredAt === undefined && (kind === undefined || message.kind === kind))
685
+ .messages.filter((message) => message.deliveredAt === undefined &&
686
+ message.failedAt === undefined &&
687
+ (kind === undefined || message.kind === kind))
192
688
  .map((message) => structuredClone(message));
193
689
  }
690
+ /** Oldest-first executable intents that have not reached the durable transcript boundary. */
691
+ pendingTaskBearing() {
692
+ return this.pending().filter((message) => message.task !== undefined);
693
+ }
194
694
  acknowledgeDelivered(messageId) {
195
695
  this.markTimestamp(messageId, "deliveredAt");
196
696
  }
@@ -206,8 +706,154 @@ export class WorkerAgentMailbox {
206
706
  const message = this.read().messages.find((candidate) => candidate.messageId === normalized);
207
707
  return message ? structuredClone(message) : undefined;
208
708
  }
209
- markReplied(messageId) {
210
- return this.markTimestamp(messageId, "repliedAt");
709
+ hasControlReplayReceipt(messageId) {
710
+ const normalized = messageId.trim();
711
+ if (!normalized)
712
+ throw new TypeError("A worker control message id is required.");
713
+ return controlReplayReceiptFor(this.read().replayReceipts, normalized) !== undefined;
714
+ }
715
+ hasDeliveredControlReceipt(messageId) {
716
+ const normalized = messageId.trim();
717
+ if (!normalized)
718
+ throw new TypeError("A worker control message id is required.");
719
+ const state = this.read();
720
+ const message = state.messages.find((candidate) => candidate.messageId === normalized);
721
+ return (message?.deliveredAt !== undefined ||
722
+ controlReplayReceiptFor(state.replayReceipts, normalized)?.deliveredAt !== undefined);
723
+ }
724
+ resolveCompletedReply(messageId, content) {
725
+ const normalized = messageId.trim();
726
+ if (!normalized)
727
+ throw new TypeError("A worker control message id is required.");
728
+ const normalizedContent = normalizeReplyContent(content);
729
+ const state = this.read();
730
+ const request = state.messages.find((candidate) => candidate.messageId === normalized);
731
+ const receipt = request?.replyReceipt ?? replyReplayReceiptFor(state.replayReceipts, normalized);
732
+ if (!receipt)
733
+ return undefined;
734
+ if (receipt.contentDigest !== replyIntentDigest(normalizedContent)) {
735
+ throw new Error("Worker reply identity conflicts with its durable source receipt.");
736
+ }
737
+ return {
738
+ replyMessageId: receipt.replyMessageId,
739
+ requestSenderId: receipt.requestSenderId,
740
+ contentDigest: receipt.contentDigest,
741
+ };
742
+ }
743
+ getReplyAcknowledgementId(messageId) {
744
+ const normalized = messageId.trim();
745
+ if (!normalized)
746
+ throw new TypeError("A worker control message id is required.");
747
+ return this.read().replyAcknowledgements.find((acknowledgement) => acknowledgement.messageId === normalized)
748
+ ?.acknowledgementId;
749
+ }
750
+ listReplyAcknowledgements() {
751
+ return this.read().replyAcknowledgements.map((acknowledgement) => ({ ...acknowledgement }));
752
+ }
753
+ /**
754
+ * Mark one request replied while retaining the exact target receipt until transcript consumption.
755
+ * The acknowledgement id is the durable target reply id, so a retry adopts a crash-left marker.
756
+ */
757
+ beginReplyAcknowledgement(messageId, acknowledgementId, replyContent) {
758
+ const normalized = normalizeReplyAcknowledgement(messageId, acknowledgementId);
759
+ const normalizedContent = normalizeReplyContent(replyContent);
760
+ const contentDigest = replyIntentDigest(normalizedContent);
761
+ let acquired = false;
762
+ let changed = false;
763
+ this.update((state) => {
764
+ const request = state.messages.find((message) => message.messageId === normalized.messageId);
765
+ if (!request || request.deliveredAt === undefined || request.expectReply !== true)
766
+ return state;
767
+ if (!request.senderAgentId) {
768
+ throw new Error("Worker reply request has no routable requester.");
769
+ }
770
+ const receipt = {
771
+ replyMessageId: normalized.acknowledgementId,
772
+ requestSenderId: request.senderAgentId,
773
+ contentDigest,
774
+ };
775
+ const durableReceipt = replyReplayReceiptFor(state.replayReceipts, normalized.messageId);
776
+ if (durableReceipt &&
777
+ (durableReceipt.replyMessageId !== receipt.replyMessageId ||
778
+ durableReceipt.requestSenderId !== receipt.requestSenderId ||
779
+ durableReceipt.contentDigest !== receipt.contentDigest)) {
780
+ throw new Error("Worker reply identity conflicts with its durable replay receipt.");
781
+ }
782
+ const existing = state.replyAcknowledgements.find((acknowledgement) => acknowledgement.messageId === normalized.messageId ||
783
+ acknowledgement.acknowledgementId === normalized.acknowledgementId);
784
+ if (existing) {
785
+ if (existing.messageId !== normalized.messageId ||
786
+ existing.acknowledgementId !== normalized.acknowledgementId) {
787
+ throw new Error("Worker reply acknowledgement identity conflicts with an active transaction.");
788
+ }
789
+ if (request?.replyReceipt?.replyMessageId !== receipt.replyMessageId ||
790
+ request.replyReceipt.requestSenderId !== receipt.requestSenderId ||
791
+ request.replyReceipt.contentDigest !== receipt.contentDigest ||
792
+ existing.replyContent !== normalizedContent) {
793
+ throw new Error("Worker reply content conflicts with its active acknowledgement transaction.");
794
+ }
795
+ acquired = true;
796
+ return state;
797
+ }
798
+ if (request.repliedAt !== undefined)
799
+ return state;
800
+ acquired = true;
801
+ changed = true;
802
+ return {
803
+ ...state,
804
+ messages: state.messages.map((message) => message.messageId === normalized.messageId
805
+ ? {
806
+ ...message,
807
+ repliedAt: transitionTimestamp(message.deliveredAt ?? message.createdAt),
808
+ replyReceipt: receipt,
809
+ }
810
+ : message),
811
+ replyAcknowledgements: [...state.replyAcknowledgements, { ...normalized, replyContent: normalizedContent }],
812
+ replayReceipts: durableReceipt
813
+ ? state.replayReceipts
814
+ : [...state.replayReceipts, { kind: "reply", messageId: normalized.messageId, ...receipt }],
815
+ };
816
+ });
817
+ if (changed)
818
+ this.notify();
819
+ return acquired;
820
+ }
821
+ /** Dead-letter only an ordinary executable turn with no reply or terminal-delivery obligation. */
822
+ deadLetterOrdinaryTask(messageId, reason) {
823
+ const normalized = messageId.trim();
824
+ const failureReason = reason.trim();
825
+ if (!normalized)
826
+ throw new TypeError("A worker control message id is required.");
827
+ if (!failureReason || failureReason.length > MAX_MAILBOX_IDENTITY_CHARS) {
828
+ throw new TypeError("A worker task failure reason is invalid.");
829
+ }
830
+ let changed = false;
831
+ this.update((state) => ({
832
+ ...state,
833
+ messages: state.messages.map((message) => {
834
+ if (message.messageId !== normalized ||
835
+ message.task?.kind !== "agent_turn" ||
836
+ message.replyToMessageId !== undefined ||
837
+ message.expectReply === true ||
838
+ message.deliveredAt !== undefined ||
839
+ message.failedAt !== undefined) {
840
+ return message;
841
+ }
842
+ changed = true;
843
+ return { ...message, failedAt: transitionTimestamp(message.createdAt), failureReason };
844
+ }),
845
+ }), true);
846
+ if (changed)
847
+ this.notify();
848
+ return changed;
849
+ }
850
+ /** Commit one exact reply acknowledgement and release its protected history slot. */
851
+ commitReplyAcknowledgement(messageId, acknowledgementId) {
852
+ return this.finishReplyAcknowledgement(messageId, acknowledgementId, false);
853
+ }
854
+ /** Roll back one exact reply acknowledgement without clearing a later or unrelated reply. */
855
+ rollbackReplyAcknowledgement(messageId, acknowledgementId) {
856
+ return this.finishReplyAcknowledgement(messageId, acknowledgementId, true);
211
857
  }
212
858
  markTimestamp(messageId, field) {
213
859
  const normalized = messageId.trim();
@@ -215,13 +861,56 @@ export class WorkerAgentMailbox {
215
861
  throw new TypeError("A worker control message id is required.");
216
862
  let changed = false;
217
863
  this.update((state) => {
864
+ let deliveredAt;
218
865
  const messages = state.messages.map((message) => {
219
- if (message.messageId !== normalized || message[field] !== undefined)
866
+ if (message.messageId !== normalized || message[field] !== undefined || message.failedAt !== undefined) {
220
867
  return message;
868
+ }
221
869
  changed = true;
222
- return { ...message, [field]: new Date().toISOString() };
870
+ deliveredAt = transitionTimestamp(message.createdAt);
871
+ return { ...message, [field]: deliveredAt };
223
872
  });
224
- return changed ? { ...state, messages: pruneDeliveredHistory(messages) } : state;
873
+ if (!changed || !deliveredAt)
874
+ return state;
875
+ return {
876
+ ...state,
877
+ messages,
878
+ replayReceipts: state.replayReceipts.map((receipt) => receipt.kind === "control" && receipt.messageId === normalized ? { ...receipt, deliveredAt } : receipt),
879
+ };
880
+ });
881
+ if (changed)
882
+ this.notify();
883
+ return changed;
884
+ }
885
+ finishReplyAcknowledgement(messageId, acknowledgementId, rollback) {
886
+ const normalized = normalizeReplyAcknowledgement(messageId, acknowledgementId);
887
+ let changed = false;
888
+ this.update((state) => {
889
+ const acknowledgement = state.replyAcknowledgements.find((candidate) => candidate.messageId === normalized.messageId &&
890
+ candidate.acknowledgementId === normalized.acknowledgementId);
891
+ if (!acknowledgement)
892
+ return state;
893
+ changed = true;
894
+ const messages = rollback
895
+ ? state.messages.map((message) => {
896
+ if (message.messageId !== normalized.messageId || message.repliedAt === undefined)
897
+ return message;
898
+ const restored = { ...message };
899
+ delete restored.repliedAt;
900
+ delete restored.replyReceipt;
901
+ return restored;
902
+ })
903
+ : state.messages;
904
+ return {
905
+ ...state,
906
+ messages,
907
+ replyAcknowledgements: state.replyAcknowledgements.filter((candidate) => candidate !== acknowledgement),
908
+ ...(rollback
909
+ ? {
910
+ replayReceipts: state.replayReceipts.filter((receipt) => receipt.kind !== "reply" || receipt.messageId !== normalized.messageId),
911
+ }
912
+ : {}),
913
+ };
225
914
  });
226
915
  if (changed)
227
916
  this.notify();
@@ -232,24 +921,65 @@ export class WorkerAgentMailbox {
232
921
  return () => this.listeners.delete(listener);
233
922
  }
234
923
  notify() {
235
- for (const listener of this.listeners)
236
- listener();
924
+ for (const listener of this.listeners) {
925
+ try {
926
+ listener();
927
+ }
928
+ catch {
929
+ // Mailbox subscribers are advisory; durable mutation results remain authoritative.
930
+ }
931
+ }
237
932
  }
238
933
  read() {
239
934
  if (!existsSync(this.file)) {
240
- return { version: 1, parentSessionId: this.parentSessionId, agentId: this.agentId, messages: [] };
935
+ return {
936
+ version: 1,
937
+ parentSessionId: this.parentSessionId,
938
+ agentId: this.agentId,
939
+ messages: [],
940
+ replyAcknowledgements: [],
941
+ replayReceipts: [],
942
+ };
241
943
  }
242
944
  return parseState(readBoundedTextFileSync(this.file, MAX_MAILBOX_BYTES, "Worker agent mailbox durable size bound"), this.parentSessionId, this.agentId);
243
945
  }
244
- update(mutator) {
946
+ update(mutator, ordinaryAdmission = false, replayEvidenceAdmission = false) {
245
947
  withFileLockSync(this.file, () => {
246
948
  const state = this.read();
949
+ const previousOrdinaryBytes = ordinaryAdmissionEncodedBytes(state);
950
+ const previousReplayEvidenceSlots = projectedReplayEvidenceSlots(state);
247
951
  const mutated = mutator(state);
248
- const next = { ...mutated, messages: pruneDeliveredHistory(mutated.messages) };
249
- if (next.messages.length > MAX_MAILBOX_RETAINED_MESSAGES || encodedStateBytes(next) > MAX_MAILBOX_BYTES) {
250
- throw new Error("Worker agent mailbox exceeds its durable size bound.");
952
+ const retained = pruneDeliveredHistory(this.parentSessionId, this.agentId, mutated.messages, mutated.replyAcknowledgements, mutated.replayReceipts, ordinaryAdmission);
953
+ const next = {
954
+ ...mutated,
955
+ messages: retained.messages,
956
+ replayReceipts: retained.replayReceipts,
957
+ };
958
+ const addedReplayReceipt = mutated.replayReceipts.length > state.replayReceipts.length;
959
+ const bytesWithoutAddedReceipt = addedReplayReceipt
960
+ ? encodedStateBytes({ ...next, replayReceipts: next.replayReceipts.slice(0, -1) })
961
+ : undefined;
962
+ const changed = JSON.stringify(next) !== JSON.stringify(state);
963
+ const nextOrdinaryBytes = ordinaryAdmissionEncodedBytes(next);
964
+ if (ordinaryAdmission &&
965
+ changed &&
966
+ nextOrdinaryBytes > MAX_ORDINARY_MAILBOX_BYTES &&
967
+ nextOrdinaryBytes > previousOrdinaryBytes) {
968
+ throw new Error(addedReplayReceipt
969
+ ? "Worker agent mailbox replay receipt storage exhausted its mandatory control byte reserve."
970
+ : "Worker agent mailbox passive control storage exhausted its mandatory control byte reserve.");
971
+ }
972
+ const nextReplayEvidenceSlots = projectedReplayEvidenceSlots(next);
973
+ if (nextReplayEvidenceSlots > MAX_MAILBOX_REPLAY_RECEIPTS) {
974
+ throw new Error("Worker agent mailbox exceeds its projected replay evidence bound.");
975
+ }
976
+ if (replayEvidenceAdmission &&
977
+ nextReplayEvidenceSlots > MAX_REPLAY_EVIDENCE_SLOTS &&
978
+ nextReplayEvidenceSlots > previousReplayEvidenceSlots) {
979
+ throw new Error(`Worker agent mailbox replay evidence capacity reached its ${MAX_REPLAY_EVIDENCE_SLOTS} slot limit.`);
251
980
  }
252
- if (JSON.stringify(next) !== JSON.stringify(state))
981
+ assertWorkerAgentMailboxBounds(next.messages.length, next.replayReceipts.length, state.replayReceipts.length, encodedStateBytes(next), bytesWithoutAddedReceipt);
982
+ if (changed)
253
983
  writeFileAtomicSync(this.file, `${JSON.stringify(next)}\n`, { mode: 0o600 });
254
984
  });
255
985
  }