import assert from "node:assert/strict"; import test from "node:test"; import { createAgentOptimisticOutbox } from "../src/lib/agent-optimistic-outbox.ts"; const canonical = (id, content = "Hello", time = "2026-10-14T12:01:00.000Z") => ({ id, content, createdAt: time, role: "user", name: "buzzert", metadata: { kind: "message", entryId: 123, status: "complete", channel: "sybil" }, }); const deferred = () => { let resolve, reject; const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); return { promise, resolve, reject }; }; function fixture(submit, legacy = {}) { let stored = null; let serial = 0; const changes = []; let box; const storage = { read: () => structuredClone(stored), write: rows => { stored = structuredClone(rows); }, ...legacy }; const open = () => { box = createAgentOptimisticOutbox({ submit, storage, newId: () => `request-${++serial}`, now: () => Date.parse("2026-10-14T12:00:00.000Z"), changed: () => changes.push(box?.snapshot()) }); return box; }; return { open, changes, stored: () => stored, storage }; } test("send publishes and persists one canonical row before POST, retaining it through receipt and echo", async () => { const network = deferred(); let called = false; const state = fixture(async (_content, requestId) => { called = true; assert.equal(requestId, "request-1"); return network.promise; }); const box = state.open(); const done = box.send("Hello"); const first = box.display([])[0]; assert.equal(called, false, "network starts after synchronous local publication"); assert.equal(first.id, "inbound:sybil:request-1"); assert.equal(first.metadata.status, "sending"); assert.equal(state.stored()[0].id, first.id); assert.equal(state.changes.length, 1); network.resolve({ status: "queued", messageId: first.id }); await done; assert.equal(box.display([])[0].metadata.status, "queued"); assert.equal(box.display([])[0].createdAt, first.createdAt); const echo = canonical(first.id); box.reconcile({ messages: [echo] }); const displayed = box.display([echo]); assert.equal(displayed.length, 1); assert.equal(displayed[0].id, first.id); assert.equal(displayed[0].createdAt, first.createdAt, "server receipt time cannot reorder the optimistic bubble"); assert.equal(displayed[0].metadata.status, "complete"); assert.deepEqual(state.stored(), []); assert.equal(box.display([]).length, 1, "even a stale display snapshot cannot create an acknowledgment gap"); }); test("canonical echo before an HTTP failure wins and never paints a delivered bubble failed", async () => { const network = deferred(); const state = fixture(() => network.promise); const box = state.open(); const done = box.send("Hello"); const row = box.display([])[0]; const echo = canonical(row.id); box.reconcile({ messages: [echo] }); network.reject(new Error("response lost")); await done; assert.equal(box.display([echo])[0].metadata.status, "complete"); assert.equal(box.display([echo])[0].metadata.error, undefined); assert.deepEqual(state.stored(), []); }); test("queue echo before HTTP acknowledgment preserves one row when the queue snapshot disappears", async () => { const network = deferred(); const state = fixture(() => network.promise); const box = state.open(); const done = box.send("Hello"); const row = box.display([])[0]; const queued = { ...canonical(row.id), metadata: { kind: "message", status: "queued" } }; box.reconcile({ messages: [], activity: { queuedMessages: [queued] } }); assert.equal(box.display([queued]).length, 1); box.reconcile({ messages: [], activity: { queuedMessages: [] } }); assert.equal(box.display([])[0].id, row.id, "queue-to-history transfer must not hide the bubble"); network.reject(new Error("HTTP acknowledgment lost")); await done; assert.equal(box.display([])[0].metadata.status, "queued"); }); test("failed retry and reload preserve the message ID, timestamp, and original receipt key", async () => { const requests = []; let fail = true; const state = fixture(async (content, requestId) => { requests.push({ content, requestId }); if (fail) throw new Error("network down"); return { status: "placed", messageId: `inbound:sybil:${requestId}` }; }); let box = state.open(); await assert.rejects(box.send("Hello"), /network down/); const failed = box.display([])[0]; assert.equal(failed.metadata.status, "failed"); assert.equal(failed.metadata.error, "network down"); box = state.open(); assert.equal(requests.length, 1, "restoring drafts never sends automatically"); assert.equal(box.display([])[0].metadata.status, "failed"); assert.equal(box.display([])[0].createdAt, failed.createdAt); fail = false; const retry = box.retry(failed.id); assert.equal(box.display([])[0].metadata.status, "sending"); await retry; assert.deepEqual(requests.map(row => row.requestId), ["request-1", "request-1"]); assert.equal(box.display([]).length, 1); assert.equal(box.display([])[0].metadata.status, "complete"); assert.equal(box.display([])[0].createdAt, failed.createdAt); }); test("in-flight reload becomes unconfirmed and two identical sends remain independent rows", async () => { const first = deferred(), second = deferred(); let calls = 0; const state = fixture(() => ++calls === 1 ? first.promise : second.promise); const box = state.open(); const send1 = box.send("Same content"), send2 = box.send("Same content"); const rows = box.display([]); assert.equal(rows.length, 2); assert.notEqual(rows[0].id, rows[1].id); assert.ok(rows[0].createdAt < rows[1].createdAt, "same-millisecond sends retain insertion order"); const reloaded = state.open(); assert.deepEqual(reloaded.display([]).map(row => row.metadata.status), ["unconfirmed", "unconfirmed"]); assert.deepEqual(reloaded.display([]).map(row => row.id), rows.map(row => row.id)); first.resolve({ status: "queued" }); second.resolve({ status: "queued" }); await Promise.all([send1, send2]); }); test("legacy failed cards join their saved receipt keys without automatic delivery", () => { const state = fixture(() => assert.fail("migration must not submit"), { legacyRows: () => [{ content: "old message", status: "failed", error: "old error" }], legacyIds: () => [{ content: "old message", requestId: "old-request" }], }); const box = state.open(); assert.equal(box.display([])[0].id, "inbound:sybil:old-request"); assert.equal(box.display([])[0].metadata.status, "failed"); assert.equal(box.display([])[0].metadata.error, "old error"); const migrated = state.stored()[0]; assert.equal(migrated.id, "inbound:sybil:old-request"); assert.equal(state.open().display([])[0].createdAt, migrated.createdAt, "migration is saved before any retry"); }); test("storage failures still leave a failed inline row and prevent untracked POST", async () => { let calls = 0, visible = 0; const box = createAgentOptimisticOutbox({ submit: async () => { calls++; return { status: "queued" }; }, storage: { read: () => null, write: () => { throw new Error("storage unavailable"); } }, newId: () => "request", changed: () => visible++, }); const sending = box.send("Do not lose this text"); assert.equal(box.display([])[0].metadata.status, "failed"); assert.equal(box.display([])[0].content, "Do not lose this text"); assert.equal(visible, 1); await assert.rejects(sending, /storage unavailable/); assert.equal(calls, 0); });