Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion desktop/src/features/agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,18 @@ with a TypeScript lookup table or an id comparison in a component.

Known Desktops exposes an owner-private, explicitly selected agent+Desktop Stop,
not inferred agent location. The app-scoped receiver subscribes live only;
reopening never replays commands. An explicit retry republishes the exact request;
reopening never replays commands. Receiver initialization reports a safe failure
stage without exposing raw transport/IPC exceptions. Transient initialization
failures and transient CLOSED states recover through a bounded receiver-owner
budget; each attempt uses a fresh live-only subscription and repeats
projection-only sync before admission. Terminal closure or exhausted recovery
stays in the scope-owned notification, whose deliberate retry resets the receiver
budget. A known latched-terminal relay session also reports immediately during
initialization without consuming that budget; unknown and transient failures
remain bounded retries. Recovery must discard queued callbacks from the retired
receiver, not retry an operation, and must respect the relay rate-limit gate.
A readiness timeout is unconfirmed delivery, not a failed initialization; late
EOSE clears that warning after successful projection. An explicit operation retry republishes the exact request;
the relay redelivers stored Stop duplicates without repeating relay side effects.
The receiver returns saved results or Unknown, never repeats a consumed Stop.
Native owner-delegation and community checks
Expand Down
143 changes: 143 additions & 0 deletions desktop/src/features/agents/desktopLifecycle.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,146 @@ test("receiver projects history without executing it and invalidates live work o
assert.equal(f.errors.length, 1);
close();
});

for (const stage of [
"subscription",
"history",
"projection",
"reconciliation",
]) {
test(`receiver reports safe ${stage} failure and never admits queued work`, async () => {
const f = fixture();
const secret = "private key /home/private bearer secret";
if (stage === "subscription")
f.relay.subscribeLive = async () => {
throw Error(secret);
};
if (stage === "history")
f.relay.fetchEvents = async () => {
throw Error(secret);
};
const ipc = async (command, args) => {
if (
command === "observe_desktop_placement" &&
((stage === "projection" && !args.reconcile) ||
(stage === "reconciliation" && args.reconcile))
)
throw Error(secret);
return f.ipc(command, args);
};
const started = receiveLifecycle(
scope,
() => true,
(e) => f.errors.push(e),
ipc,
f.relay,
);
f.deliver({ id: "queued", kind: 50182 });
await assert.rejects(started, (error) => {
assert.match(error.message, new RegExp(`${stage}: request failed`));
assert.doesNotMatch(error.message, /private|bearer|secret/);
return true;
});
await tick();
assert.equal(
f.calls.filter(([c]) => c === "receive_desktop_lifecycle").length,
0,
);
assert.deepEqual(
f.errors,
[],
"discarded callbacks cannot replace the startup diagnosis",
);
});
}

test("explicit receiver recovery discards old queued work and projects history without replay", async () => {
const f = fixture();
let rejectHistory;
f.relay.fetchEvents = () =>
new Promise((_, reject) => {
rejectHistory = reject;
});
const failed = receiveLifecycle(
scope,
() => true,
() => {},
f.ipc,
f.relay,
);
await tick();
f.deliver({ id: "old-live", kind: 50182 });
rejectHistory(Error("Timed out while loading channel history."));
await assert.rejects(failed, /history: history timed out/);
f.relay.fetchEvents = async () => [{ id: "old-live", kind: 50182 }];
const close = await receiveLifecycle(
scope,
() => true,
() => {},
f.ipc,
f.relay,
);
await tick();
assert.equal(
f.calls.filter(([c]) => c === "receive_desktop_lifecycle").length,
0,
);
f.deliver({ id: "new-live", kind: 50182 });
await tick();
assert.equal(
f.calls.filter(([c]) => c === "receive_desktop_lifecycle").length,
1,
);
f.deliver({ id: "closed-queue", kind: 50182 });
close();
await tick();
assert.equal(
f.calls.filter(([c]) => c === "receive_desktop_lifecycle").length,
1,
);
});

test("readiness timeout is distinct, late EOSE recovers, CLOSED retires old callbacks", async () => {
const f = fixture();
let notify, deliver;
let closed = 0,
ready = 0;
f.relay.subscribeLive = async (
_filter,
event,
_onReady,
timeout,
options,
) => {
assert.equal(timeout, 5000);
deliver = event;
assert.equal(options.closedRecovery, "explicit");
notify = options.onState;
notify("timeout");
return () => {
closed++;
};
};
const close = await receiveLifecycle(
scope,
() => true,
(e) => f.errors.push(e),
f.ipc,
f.relay,
() => ready++,
);
assert.match(f.errors[0], /readiness timed out/);
assert.equal(ready, 0);
notify("eose");
assert.equal(ready, 1);
notify("closed");
deliver({ id: "late", kind: 50182 });
await tick();
assert.equal(closed, 1);
assert.equal(
f.calls.filter(([c]) => c === "receive_desktop_lifecycle").length,
0,
);
assert.match(f.errors.at(-1), /subscription closed/);
close();
});
Loading
Loading