@songsid/agend 2.1.7-beta.3 → 2.1.7-beta.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 (92) hide show
  1. package/README.md +2 -0
  2. package/README.zh-TW.md +2 -0
  3. package/dist/agent-cli-instructions.md +5 -0
  4. package/dist/agent-cli.js +13 -0
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.d.ts +2 -0
  7. package/dist/agent-endpoint.js +48 -5
  8. package/dist/agent-endpoint.js.map +1 -1
  9. package/dist/backend/codex-session-lookup.d.ts +59 -0
  10. package/dist/backend/codex-session-lookup.js +139 -0
  11. package/dist/backend/codex-session-lookup.js.map +1 -0
  12. package/dist/backend/codex.d.ts +29 -0
  13. package/dist/backend/codex.js +183 -10
  14. package/dist/backend/codex.js.map +1 -1
  15. package/dist/backend/types.d.ts +21 -0
  16. package/dist/backend/types.js.map +1 -1
  17. package/dist/channel/mcp-operation-response.d.ts +3 -0
  18. package/dist/channel/mcp-operation-response.js +21 -0
  19. package/dist/channel/mcp-operation-response.js.map +1 -0
  20. package/dist/channel/mcp-operation-response.test.d.ts +1 -0
  21. package/dist/channel/mcp-operation-response.test.js +23 -0
  22. package/dist/channel/mcp-operation-response.test.js.map +1 -0
  23. package/dist/channel/mcp-server.js +20 -5
  24. package/dist/channel/mcp-server.js.map +1 -1
  25. package/dist/channel/mcp-tools.js +2 -0
  26. package/dist/channel/mcp-tools.js.map +1 -1
  27. package/dist/cli.js +68 -2
  28. package/dist/cli.js.map +1 -1
  29. package/dist/daemon.d.ts +34 -0
  30. package/dist/daemon.js +415 -41
  31. package/dist/daemon.js.map +1 -1
  32. package/dist/delivery-outbox.d.ts +174 -0
  33. package/dist/delivery-outbox.js +824 -0
  34. package/dist/delivery-outbox.js.map +1 -0
  35. package/dist/delivery-outbox.test.d.ts +1 -0
  36. package/dist/delivery-outbox.test.js +597 -0
  37. package/dist/delivery-outbox.test.js.map +1 -0
  38. package/dist/delivery-queue-evidence.d.ts +15 -0
  39. package/dist/delivery-queue-evidence.js +35 -0
  40. package/dist/delivery-queue-evidence.js.map +1 -0
  41. package/dist/delivery-queue-evidence.test.d.ts +1 -0
  42. package/dist/delivery-queue-evidence.test.js +23 -0
  43. package/dist/delivery-queue-evidence.test.js.map +1 -0
  44. package/dist/delivery-reconciliation.d.ts +49 -0
  45. package/dist/delivery-reconciliation.js +296 -0
  46. package/dist/delivery-reconciliation.js.map +1 -0
  47. package/dist/delivery-reconciliation.test.d.ts +1 -0
  48. package/dist/delivery-reconciliation.test.js +411 -0
  49. package/dist/delivery-reconciliation.test.js.map +1 -0
  50. package/dist/fleet-manager.d.ts +39 -1
  51. package/dist/fleet-manager.js +345 -50
  52. package/dist/fleet-manager.js.map +1 -1
  53. package/dist/instance-lifecycle.d.ts +18 -0
  54. package/dist/instance-lifecycle.js +116 -3
  55. package/dist/instance-lifecycle.js.map +1 -1
  56. package/dist/instructions.js +3 -0
  57. package/dist/instructions.js.map +1 -1
  58. package/dist/locale.js +6 -0
  59. package/dist/locale.js.map +1 -1
  60. package/dist/outbound-handlers.d.ts +25 -1
  61. package/dist/outbound-handlers.js +119 -8
  62. package/dist/outbound-handlers.js.map +1 -1
  63. package/dist/outbound-schemas.d.ts +7 -0
  64. package/dist/outbound-schemas.js +14 -0
  65. package/dist/outbound-schemas.js.map +1 -1
  66. package/dist/pane-write-lock.d.ts +2 -0
  67. package/dist/pane-write-lock.js +19 -0
  68. package/dist/pane-write-lock.js.map +1 -1
  69. package/dist/remote-credential-check.d.ts +39 -0
  70. package/dist/remote-credential-check.js +81 -0
  71. package/dist/remote-credential-check.js.map +1 -0
  72. package/dist/scheduler/scheduler.d.ts +5 -1
  73. package/dist/scheduler/scheduler.js +60 -20
  74. package/dist/scheduler/scheduler.js.map +1 -1
  75. package/dist/scheduler/scheduler.test.js +41 -2
  76. package/dist/scheduler/scheduler.test.js.map +1 -1
  77. package/dist/scheduler/types.d.ts +1 -1
  78. package/dist/tmux-manager.d.ts +3 -3
  79. package/dist/tmux-manager.js +6 -6
  80. package/dist/tmux-manager.js.map +1 -1
  81. package/dist/tool-permissions.js +6 -6
  82. package/dist/tool-permissions.js.map +1 -1
  83. package/dist/transcript-monitor.d.ts +3 -1
  84. package/dist/transcript-monitor.js +15 -0
  85. package/dist/transcript-monitor.js.map +1 -1
  86. package/dist/transcript-sources.d.ts +9 -0
  87. package/dist/transcript-sources.js +14 -0
  88. package/dist/transcript-sources.js.map +1 -1
  89. package/dist/usage/providers.d.ts +11 -6
  90. package/dist/usage/providers.js +17 -10
  91. package/dist/usage/providers.js.map +1 -1
  92. package/package.json +1 -1
@@ -0,0 +1,597 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { spawn } from "node:child_process";
6
+ import { createInterface } from "node:readline";
7
+ import { once } from "node:events";
8
+ import { pathToFileURL } from "node:url";
9
+ import { DeliveryOutbox, DURABLE_DELIVERY_ABORT_BACKOFF_MS, DURABLE_DELIVERY_MAX_AGE_MS, DURABLE_DELIVERY_MAX_ATTEMPTS } from "./delivery-outbox.js";
10
+ import { finishTargetReconciliation } from "./delivery-reconciliation.js";
11
+ const roots = [];
12
+ function tempDb() {
13
+ const root = mkdtempSync(join(tmpdir(), "agend-outbox-test-"));
14
+ roots.push(root);
15
+ return join(root, "delivery-outbox.db");
16
+ }
17
+ function input(overrides = {}) {
18
+ const id = overrides.operationId ?? "op-1";
19
+ const target = overrides.targetInstance ?? "worker";
20
+ return {
21
+ operationId: id,
22
+ sourceKey: `mcp:source:${id}:${target}:fleet_inbound`,
23
+ sourceInstance: "source",
24
+ sourceDaemonBootId: "source-boot-1",
25
+ targetInstance: target,
26
+ kind: "fleet_inbound",
27
+ correlationId: "same-correlation",
28
+ payload: { type: "fleet_inbound", content: "hello", meta: {} },
29
+ ...overrides,
30
+ };
31
+ }
32
+ afterEach(() => {
33
+ vi.useRealTimers();
34
+ for (const root of roots.splice(0))
35
+ rmSync(root, { recursive: true, force: true });
36
+ });
37
+ describe("DeliveryOutbox", () => {
38
+ it("commits admission before returning and makes an operation retry idempotent", () => {
39
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
40
+ const first = outbox.admit(input());
41
+ const retry = outbox.admit(input());
42
+ expect(first.inserted).toBe(true);
43
+ expect(retry.inserted).toBe(false);
44
+ expect(retry.delivery.deliveryId).toBe(first.delivery.deliveryId);
45
+ expect(retry.delivery.state).toBe("queued");
46
+ outbox.close();
47
+ });
48
+ it("recovers a pre-begin dispatch after SIGKILL when target stays stopped, then expires it visibly", async () => {
49
+ const dbPath = tempDb();
50
+ const storeUrl = pathToFileURL(join(process.cwd(), "src/delivery-outbox.ts")).href;
51
+ const managerUrl = pathToFileURL(join(process.cwd(), "src/fleet-manager.ts")).href;
52
+ const daemonUrl = pathToFileURL(join(process.cwd(), "src/daemon.ts")).href;
53
+ const script = [
54
+ `import { FleetManager } from ${JSON.stringify(managerUrl)};`,
55
+ `import { DeliveryOutbox } from ${JSON.stringify(storeUrl)};`,
56
+ `import { Daemon } from ${JSON.stringify(daemonUrl)};`,
57
+ `import { mkdirSync, writeFileSync } from "node:fs";`,
58
+ `import pino from "pino";`,
59
+ `const manager = new FleetManager(${JSON.stringify(dirname(dbPath))});`,
60
+ `const outbox = new DeliveryOutbox(${JSON.stringify(dbPath)}, manager.managerBootId);`,
61
+ `manager.deliveryOutbox = outbox;`,
62
+ `manager.shuttingDown = true;`,
63
+ `const logger = pino({ level: "silent" });`,
64
+ `const makeDaemon = name => new Daemon(name, { working_directory: "/tmp", log_level: "error" }, ${JSON.stringify(dirname(dbPath))} + "/instances/" + name, false, undefined, undefined, logger);`,
65
+ `const source = makeDaemon("source");`,
66
+ `const target = makeDaemon("worker");`,
67
+ `source.setDeliveryOutboxPort(outbox); target.setDeliveryOutboxPort(outbox);`,
68
+ `manager.lifecycle.daemons.set("source", source); manager.lifecycle.daemons.set("worker", target);`,
69
+ `const accepted = manager.admitDurableDelivery({ operationId: "op-1", sourceDaemonBootId: source.bootId, sourceInstance: "source", targetInstance: "worker", kind: "fleet_inbound", correlationId: "same-correlation", payload: { type: "fleet_inbound", content: "hello", meta: {} } });`,
70
+ `const claimed = outbox.claimNext(manager.managerBootId, name => manager.lifecycle.daemons.get(name)?.bootId ?? null, new Set());`,
71
+ `const targetDaemon = manager.lifecycle.daemons.get("worker");`,
72
+ `const instanceDir = ${JSON.stringify(dirname(dbPath))} + "/instances/worker";`,
73
+ `mkdirSync(instanceDir, { recursive: true }); writeFileSync(instanceDir + "/window-id", "@worker");`,
74
+ `targetDaemon.tmux = { capturePane: async () => "❯", pasteBuffer: async () => true, sendSpecialKey: async () => true, getLastPasteError: () => undefined, isLastPasteFailureRecoverable: () => true };`,
75
+ `targetDaemon.controlClient = {};`,
76
+ `targetDaemon.paneReadinessForDelivery = async () => "busy";`,
77
+ `targetDaemon.probeBlockingDialog = async () => ({ state: "clear" });`,
78
+ `targetDaemon.hasPositiveDeliveryInput = async () => false;`,
79
+ `targetDaemon.waitForPaneReadyForDelivery = async () => { process.stdout.write("dispatch-waiting\\n"); return new Promise(() => {}); };`,
80
+ `targetDaemon.pushChannelMessage("hello", { delivery_id: accepted.deliveryId, delivery_attempt: String(claimed.attemptNo), from_instance: "source", correlation_id: "same-correlation", user: "instance:source", user_id: "instance:source", message_id: "message-crash-before-begin", chat_id: "", thread_id: "", ts: new Date().toISOString() });`,
81
+ `setInterval(() => {}, 1000);`,
82
+ ].join("\n");
83
+ const child = spawn(process.execPath, ["--import", "tsx", "--input-type=module", "-e", script], {
84
+ cwd: process.cwd(),
85
+ stdio: ["ignore", "pipe", "pipe"],
86
+ });
87
+ let childStderr = "";
88
+ child.stderr.setEncoding("utf8").on("data", chunk => { childStderr += chunk; });
89
+ const lines = createInterface({ input: child.stdout });
90
+ const committed = new Promise((resolve, reject) => {
91
+ const timer = setTimeout(() => {
92
+ child.kill("SIGKILL");
93
+ reject(new Error(`writer child did not reach the pre-begin wait: ${childStderr}`));
94
+ }, 8_000);
95
+ lines.once("line", line => {
96
+ clearTimeout(timer);
97
+ if (line === "dispatch-waiting")
98
+ resolve();
99
+ else
100
+ reject(new Error(`unexpected child output: ${line}`));
101
+ });
102
+ child.once("error", err => {
103
+ clearTimeout(timer);
104
+ reject(err);
105
+ });
106
+ });
107
+ await committed;
108
+ child.kill("SIGKILL");
109
+ await once(child, "exit");
110
+ lines.close();
111
+ const replacementManager = new (await import("./fleet-manager.js")).FleetManager(dirname(dbPath));
112
+ replacementManager.shuttingDown = true;
113
+ // Exercise FleetManager's production startup wiring: it opens the durable
114
+ // store and runs recoverForBoot itself before any target Daemon is started.
115
+ replacementManager.ensureDeliveryOutbox();
116
+ const restarted = replacementManager.deliveryOutbox;
117
+ const { Daemon } = await import("./daemon.js");
118
+ const pinoModule = await import("pino");
119
+ const logger = pinoModule.default({ level: "silent" });
120
+ const makeDaemon = (name) => new Daemon(name, { working_directory: "/tmp", log_level: "error", restart_policy: { max_retries: 10, backoff: "exponential", reset_after: 300 }, context_guardian: { max_age_hours: 4, grace_period_ms: 600000 } }, join(dirname(dbPath), "instances", name), false, undefined, undefined, logger);
121
+ const sourceDaemon = makeDaemon("source");
122
+ sourceDaemon.setDeliveryOutboxPort(restarted);
123
+ replacementManager.lifecycle.daemons.set("source", sourceDaemon);
124
+ replacementManager.onDaemonReady("source", sourceDaemon.bootId);
125
+ const statusAfterCrash = replacementManager.queryDurableDeliveryStatus("source", { operationId: "op-1" });
126
+ expect(statusAfterCrash.items).toMatchObject([
127
+ { operation_id: "op-1", target_instance: "worker", state: "queued", safe_to_retry: true },
128
+ ]);
129
+ expect(statusAfterCrash.items[0]).not.toHaveProperty("payload");
130
+ const pendingAfterRecovery = restarted.listPending();
131
+ expect(pendingAfterRecovery).toMatchObject([
132
+ { operationId: "op-1", targetInstance: "worker", state: "queued", payload: { content: "hello" } },
133
+ { operationId: expect.stringContaining("notice:"), targetInstance: "source", state: "queued" },
134
+ ]);
135
+ expect(replacementManager.lifecycle.daemons.has("worker")).toBe(false);
136
+ expect(restarted.getUnansweredAccepted("source", sourceDaemon.bootId)).toHaveLength(1);
137
+ const queuedDelivery = pendingAfterRecovery.find(row => row.operationId === "op-1");
138
+ const createdAt = restarted.db.prepare("SELECT created_at FROM deliveries WHERE delivery_id=?").get(queuedDelivery.deliveryId).created_at;
139
+ expect(restarted.expireStale(Date.parse(createdAt) + DURABLE_DELIVERY_MAX_AGE_MS + 1)).toBe(1);
140
+ expect(restarted.get(queuedDelivery.deliveryId)).toMatchObject({ state: "failed" });
141
+ const failureNotice = restarted.db.prepare("SELECT state,target_instance,payload_json FROM deliveries WHERE kind='delivery_outcome_notice' AND operation_id=?").get(`notice:op-1:${queuedDelivery.deliveryId}`);
142
+ expect(failureNotice?.state).toBe("queued");
143
+ expect(failureNotice?.target_instance).toBe("source");
144
+ expect(JSON.parse(failureNotice.payload_json).content).toContain("state=failed");
145
+ restarted.close();
146
+ });
147
+ it("replays a whole-process SIGKILL after raw paste but before Enter", async () => {
148
+ const dbPath = tempDb();
149
+ const rawContent = " /compact\n--keep-space ";
150
+ const storeUrl = pathToFileURL(join(process.cwd(), "src/delivery-outbox.ts")).href;
151
+ const managerUrl = pathToFileURL(join(process.cwd(), "src/fleet-manager.ts")).href;
152
+ const daemonUrl = pathToFileURL(join(process.cwd(), "src/daemon.ts")).href;
153
+ const script = [
154
+ `import { FleetManager } from ${JSON.stringify(managerUrl)};`,
155
+ `import { DeliveryOutbox } from ${JSON.stringify(storeUrl)};`,
156
+ `import { Daemon } from ${JSON.stringify(daemonUrl)};`,
157
+ `import { mkdirSync, writeFileSync } from "node:fs";`,
158
+ `import pino from "pino";`,
159
+ `const root = ${JSON.stringify(dirname(dbPath))};`,
160
+ `const manager = new FleetManager(root);`,
161
+ `const outbox = new DeliveryOutbox(${JSON.stringify(dbPath)}, manager.managerBootId);`,
162
+ `manager.deliveryOutbox = outbox; manager.shuttingDown = true;`,
163
+ `const logger = pino({ level: "silent" });`,
164
+ `const makeDaemon = name => new Daemon(name, { working_directory: "/tmp", log_level: "error" }, root + "/instances/" + name, false, undefined, undefined, logger);`,
165
+ `const source = makeDaemon("source"); const target = makeDaemon("worker");`,
166
+ `source.setDeliveryOutboxPort(outbox); target.setDeliveryOutboxPort(outbox);`,
167
+ `manager.lifecycle.daemons.set("source", source); manager.lifecycle.daemons.set("worker", target);`,
168
+ `const accepted = outbox.admit({ operationId: "op-raw-paste-crash", sourceKey: "schedule:silent-1:run-1:worker:raw_paste", sourceInstance: "source", sourceDaemonBootId: source.bootId, targetInstance: "worker", kind: "raw_paste", correlationId: "schedule:silent-1:run-1", payload: { type: "raw_paste", content: ${JSON.stringify(rawContent)}, schedule_id: "silent-1", schedule_run_id: "run-1" } });`,
169
+ `const claimed = outbox.claimNext(manager.managerBootId, name => manager.lifecycle.daemons.get(name)?.bootId ?? null, new Set());`,
170
+ `const instanceDir = root + "/instances/worker"; mkdirSync(instanceDir, { recursive: true }); writeFileSync(instanceDir + "/window-id", "@worker");`,
171
+ `target.wake = async () => {}; target.waitForInputTransientToClear = async () => true;`,
172
+ `target.getWindowId = () => "@worker";`,
173
+ `target.tmux = { getWindowId: () => "@worker", capturePane: async () => "❯", pasteBuffer: async () => { process.stdout.write("raw-paste-started\\n"); return new Promise(() => {}); }, sendSpecialKey: async () => true, getLastPasteError: () => undefined, isLastPasteFailureRecoverable: () => true };`,
174
+ `target.queueRawPaste(${JSON.stringify(rawContent)}, 0, false, { deliveryId: accepted.delivery.deliveryId, attemptNo: claimed.attemptNo, submissionMode: "raw_paste" });`,
175
+ `setInterval(() => {}, 1000);`,
176
+ ].join("\n");
177
+ const child = spawn(process.execPath, ["--import", "tsx", "--input-type=module", "-e", script], {
178
+ cwd: process.cwd(),
179
+ stdio: ["ignore", "pipe", "pipe"],
180
+ });
181
+ const lines = createInterface({ input: child.stdout });
182
+ const pasted = new Promise((resolve, reject) => {
183
+ const timer = setTimeout(() => reject(new Error("child did not reach the pane paste")), 10_000);
184
+ lines.once("line", line => {
185
+ clearTimeout(timer);
186
+ if (line === "raw-paste-started")
187
+ resolve();
188
+ else
189
+ reject(new Error(`unexpected child output: ${line}`));
190
+ });
191
+ child.once("error", err => {
192
+ clearTimeout(timer);
193
+ reject(err);
194
+ });
195
+ });
196
+ await pasted;
197
+ child.kill("SIGKILL");
198
+ await once(child, "exit");
199
+ lines.close();
200
+ const replacementManager = new (await import("./fleet-manager.js")).FleetManager(dirname(dbPath));
201
+ replacementManager.shuttingDown = true;
202
+ replacementManager.ensureDeliveryOutbox();
203
+ const restarted = replacementManager.deliveryOutbox;
204
+ const { Daemon } = await import("./daemon.js");
205
+ const pinoModule = await import("pino");
206
+ const logger = pinoModule.default({ level: "silent" });
207
+ const makeDaemon = (name) => new Daemon(name, { working_directory: "/tmp", log_level: "error", restart_policy: { max_retries: 10, backoff: "exponential", reset_after: 300 }, context_guardian: { max_age_hours: 4, grace_period_ms: 600000 } }, join(dirname(dbPath), "instances", name), false, undefined, undefined, logger);
208
+ const sourceDaemon = makeDaemon("source");
209
+ sourceDaemon.setDeliveryOutboxPort(restarted);
210
+ replacementManager.lifecycle.daemons.set("source", sourceDaemon);
211
+ replacementManager.onDaemonReady("source", sourceDaemon.bootId);
212
+ const row = restarted.getByOperation("source", "op-raw-paste-crash")[0];
213
+ expect(row).toMatchObject({ state: "reconciliation_pending", reconciliationPending: true, targetInstance: "worker" });
214
+ const candidate = restarted.getReconciliationCandidates("worker")[0];
215
+ expect(candidate.attempt.enterStartedAt).toBeNull();
216
+ expect(candidate.attempt.submissionMode).toBe("raw_paste");
217
+ const reconciled = await finishTargetReconciliation(restarted, {
218
+ targetInstance: "worker",
219
+ sessionName: "test-session",
220
+ savedWindowId: "@worker",
221
+ attempts: [{
222
+ candidate,
223
+ paneWindowId: "@worker",
224
+ panePid: child.pid ?? null,
225
+ pane: `❯ ${rawContent}`,
226
+ paneCaptureError: null,
227
+ }],
228
+ }, true);
229
+ expect(reconciled).toEqual({ delivered: 0, retry: 1, uncertain: 0, safeToStart: true });
230
+ expect(restarted.get(row.deliveryId)).toMatchObject({ state: "retry_wait", reconciliationPending: false });
231
+ expect(restarted.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(0);
232
+ expect(restarted.listPending().filter(item => item.kind === "post_restart_outcome_notice")).toHaveLength(0);
233
+ restarted.close();
234
+ });
235
+ it("whole-process SIGKILL after raw_paste Enter is uncertain and never auto-replayed", async () => {
236
+ const dbPath = tempDb();
237
+ const storeUrl = pathToFileURL(join(process.cwd(), "src/delivery-outbox.ts")).href;
238
+ const managerUrl = pathToFileURL(join(process.cwd(), "src/fleet-manager.ts")).href;
239
+ const daemonUrl = pathToFileURL(join(process.cwd(), "src/daemon.ts")).href;
240
+ const script = [
241
+ `import { FleetManager } from ${JSON.stringify(managerUrl)};`,
242
+ `import { DeliveryOutbox } from ${JSON.stringify(storeUrl)};`,
243
+ `import { Daemon } from ${JSON.stringify(daemonUrl)};`,
244
+ `import { mkdirSync, writeFileSync } from "node:fs";`,
245
+ `import pino from "pino";`,
246
+ `const root = ${JSON.stringify(dirname(dbPath))}; const db = ${JSON.stringify(dbPath)};`,
247
+ `const manager = new FleetManager(root); manager.shuttingDown = true;`,
248
+ `const outbox = new DeliveryOutbox(db, manager.managerBootId); manager.deliveryOutbox = outbox;`,
249
+ `const logger = pino({ level: "silent" });`,
250
+ `const source = new Daemon("source", { backend: "codex", working_directory: "/tmp", log_level: "error" }, root + "/instances/source", false, undefined, undefined, logger);`,
251
+ `const target = new Daemon("worker", { backend: "codex", working_directory: "/tmp", log_level: "error" }, root + "/instances/worker", false, undefined, undefined, logger);`,
252
+ `source.setDeliveryOutboxPort(outbox); target.setDeliveryOutboxPort(outbox);`,
253
+ `manager.lifecycle.daemons.set("source", source); manager.lifecycle.daemons.set("worker", target);`,
254
+ `const row = outbox.admit({ operationId: "op-raw-enter-crash", sourceKey: "schedule:silent-2:run-1:worker:raw_paste", sourceInstance: "source", sourceDaemonBootId: source.bootId, targetInstance: "worker", kind: "raw_paste", correlationId: "schedule:silent-2:run-1", payload: { type: "raw_paste", content: "/compact", schedule_id: "silent-2", schedule_run_id: "run-1" } }).delivery;`,
255
+ `const claimed = outbox.claimNext(manager.managerBootId, name => manager.lifecycle.daemons.get(name)?.bootId ?? null, new Set());`,
256
+ `const instanceDir = root + "/instances/worker"; mkdirSync(instanceDir, { recursive: true }); writeFileSync(instanceDir + "/window-id", "@worker");`,
257
+ `target.wake = async () => {}; target.waitForInputTransientToClear = async () => true; target.getWindowId = () => "@worker";`,
258
+ `target.tmux = { getWindowId: () => "@worker", capturePane: async () => "❯", pasteBuffer: async () => true, sendSpecialKey: async () => { process.stdout.write("raw-enter-accepted\\n"); return new Promise(() => {}); }, getLastPasteError: () => undefined, isLastPasteFailureRecoverable: () => true };`,
259
+ `target.queueRawPaste("/compact", 0, false, { deliveryId: row.deliveryId, attemptNo: claimed.attemptNo, submissionMode: "raw_paste" });`,
260
+ `setInterval(() => {}, 1000);`,
261
+ ].join("\n");
262
+ const child = spawn(process.execPath, ["--import", "tsx", "--input-type=module", "-e", script], {
263
+ cwd: process.cwd(),
264
+ stdio: ["ignore", "pipe", "pipe"],
265
+ });
266
+ let childStderr = "";
267
+ child.stderr.setEncoding("utf8").on("data", chunk => { childStderr += chunk; });
268
+ const lines = createInterface({ input: child.stdout });
269
+ const entered = new Promise((resolve, reject) => {
270
+ const timer = setTimeout(() => reject(new Error(`writer child did not reach raw Enter: ${childStderr}`)), 10_000);
271
+ lines.once("line", line => {
272
+ clearTimeout(timer);
273
+ if (line === "raw-enter-accepted")
274
+ resolve();
275
+ else
276
+ reject(new Error(`unexpected child output: ${line}`));
277
+ });
278
+ child.once("error", err => {
279
+ clearTimeout(timer);
280
+ reject(err);
281
+ });
282
+ });
283
+ await entered;
284
+ child.kill("SIGKILL");
285
+ await once(child, "exit");
286
+ lines.close();
287
+ const replacementManager = new (await import("./fleet-manager.js")).FleetManager(dirname(dbPath));
288
+ replacementManager.shuttingDown = true;
289
+ replacementManager.ensureDeliveryOutbox();
290
+ const replacement = replacementManager.deliveryOutbox;
291
+ const candidate = replacement.getReconciliationCandidates("worker")[0];
292
+ expect(candidate.attempt.enterStartedAt).toBeTruthy();
293
+ expect(candidate.attempt.submissionMode).toBe("raw_paste");
294
+ const reconciled = await finishTargetReconciliation(replacement, {
295
+ targetInstance: "worker",
296
+ sessionName: "test-session",
297
+ savedWindowId: "@worker",
298
+ attempts: [{
299
+ candidate, paneWindowId: "@worker", panePid: child.pid ?? null,
300
+ pane: "❯ /compact", paneCaptureError: null,
301
+ }],
302
+ }, true);
303
+ expect(reconciled).toEqual({ delivered: 0, retry: 0, uncertain: 1, safeToStart: true });
304
+ expect(replacement.get(candidate.deliveryId)).toMatchObject({ state: "uncertain" });
305
+ expect(replacement.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(1);
306
+ replacement.close();
307
+ });
308
+ it("whole-process SIGKILL after tmux accepts Enter stays uncertain even if the stale pane still shows the marker", async () => {
309
+ const dbPath = tempDb();
310
+ const transcriptPath = join(dirname(dbPath), "empty-codex-rollout.jsonl");
311
+ writeFileSync(transcriptPath, "");
312
+ const storeUrl = pathToFileURL(join(process.cwd(), "src/delivery-outbox.ts")).href;
313
+ const daemonUrl = pathToFileURL(join(process.cwd(), "src/daemon.ts")).href;
314
+ const managerUrl = pathToFileURL(join(process.cwd(), "src/fleet-manager.ts")).href;
315
+ const script = [
316
+ `import { DeliveryOutbox } from ${JSON.stringify(storeUrl)};`,
317
+ `import { Daemon } from ${JSON.stringify(daemonUrl)};`,
318
+ `import { FleetManager } from ${JSON.stringify(managerUrl)};`,
319
+ `import { mkdirSync, writeFileSync } from "node:fs";`,
320
+ `import pino from "pino";`,
321
+ `const db = ${JSON.stringify(dbPath)};`,
322
+ `const root = ${JSON.stringify(dirname(dbPath))};`,
323
+ `const manager = new FleetManager(root); manager.shuttingDown = true;`,
324
+ `const outbox = new DeliveryOutbox(db, manager.managerBootId); manager.deliveryOutbox = outbox;`,
325
+ `const logger = pino({ level: "silent" });`,
326
+ `const source = new Daemon("source", { backend: "codex", working_directory: "/tmp", log_level: "error" }, root + "/instances/source", false, undefined, undefined, logger);`,
327
+ `const target = new Daemon("worker", { backend: "codex", working_directory: "/tmp", log_level: "error" }, root + "/instances/worker", false, undefined, undefined, logger);`,
328
+ `mkdirSync(root + "/instances/worker", { recursive: true }); writeFileSync(root + "/instances/worker/window-id", "@old");`,
329
+ `source.setDeliveryOutboxPort(outbox); target.setDeliveryOutboxPort(outbox);`,
330
+ `manager.lifecycle.daemons.set("source", source); manager.lifecycle.daemons.set("worker", target);`,
331
+ `const row = outbox.admit(${JSON.stringify(input({ operationId: "op-enter-crash", sourceKey: "source:op-enter-crash" }))}).delivery;`,
332
+ `const claimed = outbox.claimNext(manager.managerBootId, name => manager.lifecycle.daemons.get(name)?.bootId ?? null, new Set());`,
333
+ `target.backend = { binaryName: "codex", supportsQueuedInput: () => true };`,
334
+ `target.controlClient = {};`,
335
+ `target.paneReadinessForDelivery = async () => "busy";`,
336
+ `target.probeBlockingDialog = async () => ({ state: "clear" });`,
337
+ `target.hasPositiveDeliveryInput = async () => true;`,
338
+ `target.waitForInputTransientToClear = async () => true;`,
339
+ `target.capturePaneEvidence = async () => ({ captured: true });`,
340
+ `target.tmux = { getWindowId: () => "@old", capturePane: async () => "", pasteBuffer: async () => true, sendSpecialKey: async () => { process.stdout.write("tmux-enter-accepted\\n"); return new Promise(() => {}); }, getLastPasteError: () => undefined, isLastPasteFailureRecoverable: () => true };`,
341
+ `target.pushChannelMessage("hello", { delivery_id: row.deliveryId, delivery_attempt: String(claimed.attemptNo), from_instance: "source", correlation_id: "same-correlation", user: "instance:source", user_id: "instance:source", message_id: "message-native-queue-crash", chat_id: "", thread_id: "", ts: new Date().toISOString() });`,
342
+ `setInterval(() => {}, 1000);`,
343
+ ].join("\n");
344
+ const child = spawn(process.execPath, ["--import", "tsx", "--input-type=module", "-e", script], {
345
+ cwd: process.cwd(),
346
+ stdio: ["ignore", "pipe", "pipe"],
347
+ });
348
+ let childStderr = "";
349
+ child.stderr.setEncoding("utf8").on("data", chunk => { childStderr += chunk; });
350
+ const lines = createInterface({ input: child.stdout });
351
+ const accepted = new Promise((resolve, reject) => {
352
+ const timer = setTimeout(() => reject(new Error(`writer child did not reach tmux Enter: ${childStderr}`)), 8_000);
353
+ lines.once("line", line => {
354
+ clearTimeout(timer);
355
+ if (line === "tmux-enter-accepted")
356
+ resolve();
357
+ else
358
+ reject(new Error(`unexpected child output: ${line}`));
359
+ });
360
+ child.once("error", err => {
361
+ clearTimeout(timer);
362
+ reject(err);
363
+ });
364
+ });
365
+ await accepted;
366
+ child.kill("SIGKILL");
367
+ await once(child, "exit");
368
+ lines.close();
369
+ const replacementManager = new (await import("./fleet-manager.js")).FleetManager(dirname(dbPath));
370
+ replacementManager.shuttingDown = true;
371
+ replacementManager.ensureDeliveryOutbox();
372
+ const replacement = replacementManager.deliveryOutbox;
373
+ expect(replacement.getReconciliationCandidates("worker")).toHaveLength(1);
374
+ const candidate = replacement.getReconciliationCandidates("worker")[0];
375
+ expect(candidate.attempt.enterStartedAt).toBeTruthy();
376
+ expect(candidate.attempt).toMatchObject({
377
+ backend: "codex",
378
+ backendVersion: null,
379
+ submissionMode: "native_queue_handoff",
380
+ queueResumePolicy: "unknown",
381
+ });
382
+ const reconciled = await finishTargetReconciliation(replacement, {
383
+ targetInstance: "worker",
384
+ sessionName: "test-session",
385
+ savedWindowId: "@old",
386
+ attempts: [{
387
+ candidate,
388
+ paneWindowId: "@old",
389
+ panePid: null,
390
+ pane: `[agend-delivery-id:${candidate.deliveryId}]\n› [from:source] hello`,
391
+ paneCaptureError: null,
392
+ }],
393
+ }, true);
394
+ expect(reconciled).toEqual({ delivered: 0, retry: 0, uncertain: 1, safeToStart: true });
395
+ expect(replacement.get(candidate.deliveryId)).toMatchObject({ state: "uncertain" });
396
+ expect(replacement.queryStatusForInstance("source", { operationId: "op-enter-crash" }).items)
397
+ .toMatchObject([{ delivery_id: candidate.deliveryId, state: "uncertain", safe_to_retry: false }]);
398
+ expect(replacement.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(1);
399
+ replacement.close();
400
+ });
401
+ it("fences begin by attempt and target generation; abort safely returns work to retry", () => {
402
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
403
+ const row = outbox.admit(input()).delivery;
404
+ const claimed = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
405
+ expect(outbox.begin(row.deliveryId, "old-target", claimed.attemptNo)).toBe("stale");
406
+ expect(outbox.begin(row.deliveryId, "target-boot-1", claimed.attemptNo)).toBe("begun");
407
+ expect(outbox.begin(row.deliveryId, "target-boot-1", claimed.attemptNo)).toBe("duplicate");
408
+ expect(outbox.begin(row.deliveryId, "target-boot-1", claimed.attemptNo + 1)).toBe("stale");
409
+ expect(outbox.abort(row.deliveryId, "target-boot-1", claimed.attemptNo, "pane lock recheck")).toBe(true);
410
+ expect(outbox.abort(row.deliveryId, "target-boot-1", claimed.attemptNo, "retry duplicate")).toBe(true);
411
+ expect(outbox.get(row.deliveryId)?.state).toBe("retry_wait");
412
+ expect(Date.parse(outbox.get(row.deliveryId).nextAttemptAt)).toBeGreaterThanOrEqual(Date.now() + DURABLE_DELIVERY_ABORT_BACKOFF_MS - 100);
413
+ outbox.close();
414
+ });
415
+ it("persists transcript checkpoint and Enter-start before recovery can classify the attempt", () => {
416
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
417
+ const row = outbox.admit(input()).delivery;
418
+ const claimed = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
419
+ const evidence = {
420
+ backend: "codex",
421
+ windowId: "@old",
422
+ transcriptPath: "/tmp/rollout.jsonl",
423
+ transcriptOffset: 2048,
424
+ transcriptSessionId: "rollout-session-1",
425
+ submissionMode: "idle_submit",
426
+ };
427
+ expect(outbox.begin(row.deliveryId, "target-boot-1", claimed.attemptNo, evidence)).toBe("begun");
428
+ const rawAttempt = outbox.db.prepare("SELECT backend,window_id,transcript_path,transcript_offset,transcript_session_id,submission_mode,enter_started_at FROM delivery_attempts WHERE delivery_id=?").get(row.deliveryId);
429
+ expect(rawAttempt).toMatchObject({
430
+ backend: "codex", window_id: "@old", transcript_path: "/tmp/rollout.jsonl",
431
+ transcript_offset: 2048, transcript_session_id: "rollout-session-1", submission_mode: "idle_submit",
432
+ enter_started_at: null,
433
+ });
434
+ expect(outbox.markEnterStarted(row.deliveryId, "target-boot-1", claimed.attemptNo)).toBe(true);
435
+ expect(outbox.db.prepare("SELECT enter_started_at FROM delivery_attempts WHERE delivery_id=?").get(row.deliveryId).enter_started_at).toBeTruthy();
436
+ expect(outbox.recoverForBoot("manager-2").reconciliationPending).toBe(1);
437
+ const candidate = outbox.getReconciliationCandidates("worker")[0];
438
+ expect(candidate).toMatchObject({ state: "reconciliation_pending", attempt: { enterStartedAt: expect.any(String) } });
439
+ expect(outbox.reconcileAttempt(row.deliveryId, "target-boot-1", claimed.attemptNo, "uncertain", "no transcript marker after Enter")).toBe(true);
440
+ expect(outbox.get(row.deliveryId)).toMatchObject({ state: "uncertain", reconciliationPending: false });
441
+ expect(outbox.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(1);
442
+ outbox.close();
443
+ });
444
+ it("holds only a target lane until its recovered attempt is classified", () => {
445
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
446
+ const active = outbox.admit(input({ operationId: "active", sourceKey: "active", targetInstance: "worker-a" })).delivery;
447
+ const newer = outbox.admit(input({ operationId: "newer", sourceKey: "newer", targetInstance: "worker-a" })).delivery;
448
+ const independent = outbox.admit(input({ operationId: "other", sourceKey: "other", targetInstance: "worker-b" })).delivery;
449
+ const first = outbox.claimNext("manager-1", target => `boot-${target}`, new Set());
450
+ expect(first.deliveryId).toBe(active.deliveryId);
451
+ expect(outbox.begin(active.deliveryId, first.targetDaemonBootId, first.attemptNo)).toBe("begun");
452
+ expect(outbox.markEnterStarted(active.deliveryId, first.targetDaemonBootId, first.attemptNo)).toBe(true);
453
+ outbox.recoverForBoot("manager-2");
454
+ const available = outbox.claimNext("manager-2", target => `replacement-${target}`, new Set());
455
+ expect(available?.deliveryId).toBe(independent.deliveryId);
456
+ expect(outbox.get(newer.deliveryId)?.state).toBe("queued");
457
+ expect(outbox.reconcileAttempt(active.deliveryId, first.targetDaemonBootId, first.attemptNo, "uncertain", "ambiguous")).toBe(true);
458
+ const next = outbox.claimNext("manager-2", target => `replacement-${target}`, new Set());
459
+ expect(next?.deliveryId).toBe(newer.deliveryId);
460
+ outbox.close();
461
+ });
462
+ it("surfaces a reconciliation that remains pending through the visible TTL bound", () => {
463
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
464
+ const row = outbox.admit(input()).delivery;
465
+ const claimed = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
466
+ expect(outbox.begin(row.deliveryId, claimed.targetDaemonBootId, claimed.attemptNo, {
467
+ backend: "muse", windowId: "@old", transcriptPath: null, transcriptOffset: null,
468
+ transcriptSessionId: null, submissionMode: "idle_submit",
469
+ })).toBe("begun");
470
+ outbox.markEnterStarted(row.deliveryId, claimed.targetDaemonBootId, claimed.attemptNo);
471
+ outbox.recoverForBoot("manager-2");
472
+ outbox.db.prepare("UPDATE deliveries SET created_at=? WHERE delivery_id=?")
473
+ .run(new Date(Date.now() - 2_000).toISOString(), row.deliveryId);
474
+ expect(outbox.expireStale(Date.now(), 1_000)).toBe(1);
475
+ expect(outbox.get(row.deliveryId)).toMatchObject({ state: "uncertain", reconciliationPending: false });
476
+ expect(outbox.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(1);
477
+ outbox.close();
478
+ });
479
+ it("keeps FIFO per target across retry backoff while allowing another target to progress", () => {
480
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
481
+ const first = outbox.admit(input({ operationId: "op-1", sourceKey: "one", targetInstance: "worker-a" })).delivery;
482
+ const second = outbox.admit(input({ operationId: "op-2", sourceKey: "two", targetInstance: "worker-a" })).delivery;
483
+ const independent = outbox.admit(input({ operationId: "op-3", sourceKey: "three", targetInstance: "worker-b" })).delivery;
484
+ const claimed = outbox.claimNext("manager-1", target => `boot-${target}`, new Set());
485
+ expect(claimed.deliveryId).toBe(first.deliveryId);
486
+ expect(outbox.retryBeforeBegin(first.deliveryId, claimed.targetDaemonBootId, claimed.attemptNo, "temporarily unavailable", 60_000)).toBe(true);
487
+ const next = outbox.claimNext("manager-1", target => `boot-${target}`, new Set());
488
+ expect(next?.deliveryId).toBe(independent.deliveryId);
489
+ expect(outbox.get(second.deliveryId)?.state).toBe("queued");
490
+ outbox.close();
491
+ });
492
+ it("recovers unsubmitted leases and fences old target generations", () => {
493
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
494
+ const queued = outbox.admit(input({ operationId: "queued", sourceKey: "queued" })).delivery;
495
+ const claimed = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
496
+ expect(outbox.recoverForBoot("manager-2").queued).toBe(1);
497
+ expect(outbox.get(queued.deliveryId)?.state).toBe("queued");
498
+ const afterBoot = outbox.claimNext("manager-2", () => "target-boot-2", new Set());
499
+ expect(outbox.begin(afterBoot.deliveryId, "target-boot-1", afterBoot.attemptNo)).toBe("stale");
500
+ expect(outbox.begin(afterBoot.deliveryId, "target-boot-2", afterBoot.attemptNo)).toBe("begun");
501
+ expect(outbox.recoverTargetGeneration("worker", "target-boot-3").reconciliationPending).toBe(1);
502
+ expect(outbox.get(claimed.deliveryId)).toMatchObject({ state: "reconciliation_pending", reconciliationPending: true });
503
+ outbox.close();
504
+ });
505
+ it("tracks the MCP response boundary and deduplicates post-restart notices", () => {
506
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
507
+ const parent = outbox.admit(input()).delivery;
508
+ expect(outbox.getUnansweredAccepted("source", "source-boot-2")).toHaveLength(1);
509
+ expect(outbox.markResponseDelivered("source", "op-1")).toBe(1);
510
+ expect(outbox.getUnansweredAccepted("source", "source-boot-2")).toHaveLength(0);
511
+ const unanswered = outbox.admit(input({
512
+ operationId: "op-2",
513
+ sourceKey: "mcp:source:op-2:worker:fleet_inbound",
514
+ })).delivery;
515
+ const notice = outbox.admitPostRestartOutcomeNotice(unanswered, "source-boot-2");
516
+ const duplicate = outbox.admitPostRestartOutcomeNotice(unanswered, "source-boot-2");
517
+ expect(notice?.payload.content).toContain("Do not resend this operation");
518
+ expect(duplicate?.deliveryId).toBe(notice?.deliveryId);
519
+ const broadcastSibling = outbox.admit(input({
520
+ operationId: "op-2",
521
+ sourceKey: "mcp:source:op-2:worker-2::broadcast",
522
+ targetInstance: "worker-2",
523
+ kind: "broadcast",
524
+ })).delivery;
525
+ const siblingNotice = outbox.admitPostRestartOutcomeNotice(broadcastSibling, "source-boot-2");
526
+ expect(siblingNotice?.deliveryId).not.toBe(notice?.deliveryId);
527
+ expect(outbox.get(parent.deliveryId)?.responseDeliveredAt).not.toBeNull();
528
+ outbox.close();
529
+ });
530
+ it("turns a target-unavailable row into a visible terminal failure at its TTL", () => {
531
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
532
+ const row = outbox.admit(input()).delivery;
533
+ expect(outbox.claimNext("manager-1", () => null, new Set())).toBeUndefined();
534
+ const expired = outbox.expireStale(Date.now() + DURABLE_DELIVERY_MAX_AGE_MS + 1);
535
+ expect(expired).toBe(1);
536
+ expect(outbox.get(row.deliveryId)).toMatchObject({ state: "failed", lastError: "delivery TTL expired before target became available" });
537
+ expect(outbox.listPending()).toEqual(expect.arrayContaining([
538
+ expect.objectContaining({ kind: "delivery_outcome_notice", targetInstance: "source", state: "queued" }),
539
+ ]));
540
+ outbox.close();
541
+ });
542
+ it("does not spend submission attempts on repeated pre-begin readiness deferrals", () => {
543
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
544
+ const row = outbox.admit(input()).delivery;
545
+ for (let attempt = 1; attempt <= DURABLE_DELIVERY_MAX_ATTEMPTS + 1; attempt++) {
546
+ const claimed = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
547
+ expect(claimed.attemptNo).toBe(attempt);
548
+ expect(outbox.retryBeforeBegin(row.deliveryId, "target-boot-1", attempt, "readiness timeout", 0)).toBe(true);
549
+ }
550
+ expect(outbox.get(row.deliveryId)?.state).toBe("retry_wait");
551
+ expect(outbox.get(row.deliveryId)?.lastError).toBe("readiness timeout");
552
+ expect(outbox.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(0);
553
+ outbox.close();
554
+ });
555
+ it("bounds actual pane submission attempts and atomically queues one notice", () => {
556
+ vi.useFakeTimers();
557
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
558
+ const row = outbox.admit(input()).delivery;
559
+ for (let attempt = 1; attempt <= DURABLE_DELIVERY_MAX_ATTEMPTS; attempt++) {
560
+ const claimed = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
561
+ expect(claimed.attemptNo).toBe(attempt);
562
+ expect(outbox.begin(row.deliveryId, "target-boot-1", attempt)).toBe("begun");
563
+ expect(outbox.abort(row.deliveryId, "target-boot-1", attempt, "pane confirmed no write")).toBe(true);
564
+ vi.advanceTimersByTime(DURABLE_DELIVERY_ABORT_BACKOFF_MS + 1);
565
+ }
566
+ const exhausted = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
567
+ expect(exhausted.attemptNo).toBe(DURABLE_DELIVERY_MAX_ATTEMPTS + 1);
568
+ expect(outbox.begin(row.deliveryId, "target-boot-1", exhausted.attemptNo)).toBe("stale");
569
+ expect(outbox.get(row.deliveryId)?.state).toBe("failed");
570
+ expect(outbox.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(1);
571
+ outbox.close();
572
+ });
573
+ it("atomically notices uncertain submissions and never recursively notices a failed notice", () => {
574
+ vi.useFakeTimers();
575
+ const outbox = new DeliveryOutbox(tempDb(), "manager-1");
576
+ const row = outbox.admit(input()).delivery;
577
+ const claimed = outbox.claimNext("manager-1", () => "target-boot-1", new Set());
578
+ expect(outbox.begin(row.deliveryId, "target-boot-1", claimed.attemptNo)).toBe("begun");
579
+ expect(outbox.complete(row.deliveryId, "target-boot-1", claimed.attemptNo, "uncertain", "pane outcome unknown")).toBe(true);
580
+ const notice = outbox.listPending().find(item => item.kind === "delivery_outcome_notice");
581
+ expect(notice.payload.content).toContain("state=uncertain");
582
+ expect(notice.payload.content).toContain("Do not resend");
583
+ for (let attempt = 1; attempt <= DURABLE_DELIVERY_MAX_ATTEMPTS; attempt++) {
584
+ const noticeClaim = outbox.claimNext("manager-1", () => "source-boot-1", new Set());
585
+ expect(noticeClaim.deliveryId).toBe(notice.deliveryId);
586
+ expect(outbox.begin(notice.deliveryId, "source-boot-1", attempt)).toBe("begun");
587
+ expect(outbox.abort(notice.deliveryId, "source-boot-1", attempt, "source unavailable")).toBe(true);
588
+ vi.advanceTimersByTime(DURABLE_DELIVERY_ABORT_BACKOFF_MS + 1);
589
+ }
590
+ const finalClaim = outbox.claimNext("manager-1", () => "source-boot-1", new Set());
591
+ expect(outbox.begin(notice.deliveryId, "source-boot-1", finalClaim.attemptNo)).toBe("stale");
592
+ expect(outbox.get(notice.deliveryId)?.state).toBe("failed");
593
+ expect(outbox.listPending().filter(item => item.kind === "delivery_outcome_notice")).toHaveLength(0);
594
+ outbox.close();
595
+ });
596
+ });
597
+ //# sourceMappingURL=delivery-outbox.test.js.map