@cello-protocol/daemon 0.0.196 → 0.0.198
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.
- package/dist/agent-admin-handlers.d.ts +56 -0
- package/dist/agent-admin-handlers.d.ts.map +1 -0
- package/dist/agent-admin-handlers.js +70 -0
- package/dist/agent-admin-handlers.js.map +1 -0
- package/dist/agent-handlers.d.ts +8 -7
- package/dist/agent-handlers.d.ts.map +1 -1
- package/dist/agent-handlers.js.map +1 -1
- package/dist/agent-selection-root.d.ts +29 -0
- package/dist/agent-selection-root.d.ts.map +1 -0
- package/dist/agent-selection-root.js +126 -0
- package/dist/agent-selection-root.js.map +1 -0
- package/dist/attendance-wiring.d.ts +44 -0
- package/dist/attendance-wiring.d.ts.map +1 -0
- package/dist/attendance-wiring.js +432 -0
- package/dist/attendance-wiring.js.map +1 -0
- package/dist/backup-restore-handlers.d.ts +10 -0
- package/dist/backup-restore-handlers.d.ts.map +1 -0
- package/dist/backup-restore-handlers.js +82 -0
- package/dist/backup-restore-handlers.js.map +1 -0
- package/dist/boot-agents.d.ts +64 -0
- package/dist/boot-agents.d.ts.map +1 -0
- package/dist/boot-agents.js +241 -0
- package/dist/boot-agents.js.map +1 -0
- package/dist/boot-connection-state.d.ts +40 -0
- package/dist/boot-connection-state.d.ts.map +1 -0
- package/dist/boot-connection-state.js +136 -0
- package/dist/boot-connection-state.js.map +1 -0
- package/dist/boot-core.d.ts +36 -0
- package/dist/boot-core.d.ts.map +1 -0
- package/dist/boot-core.js +292 -0
- package/dist/boot-core.js.map +1 -0
- package/dist/boot-parked-content.d.ts +34 -0
- package/dist/boot-parked-content.d.ts.map +1 -0
- package/dist/boot-parked-content.js +527 -0
- package/dist/boot-parked-content.js.map +1 -0
- package/dist/boot-sweeps.d.ts +22 -0
- package/dist/boot-sweeps.d.ts.map +1 -0
- package/dist/boot-sweeps.js +50 -0
- package/dist/boot-sweeps.js.map +1 -0
- package/dist/connection-agents.d.ts +16 -0
- package/dist/connection-agents.d.ts.map +1 -0
- package/dist/connection-agents.js +68 -0
- package/dist/connection-agents.js.map +1 -0
- package/dist/content-park.d.ts.map +1 -1
- package/dist/content-park.js +274 -25
- package/dist/content-park.js.map +1 -1
- package/dist/daemon-handle.d.ts +57 -0
- package/dist/daemon-handle.d.ts.map +1 -0
- package/dist/daemon-handle.js +2 -0
- package/dist/daemon-handle.js.map +1 -0
- package/dist/daemon-status-report.d.ts +40 -0
- package/dist/daemon-status-report.d.ts.map +1 -0
- package/dist/daemon-status-report.js +78 -0
- package/dist/daemon-status-report.js.map +1 -0
- package/dist/daemon.d.ts +5 -66
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +332 -4894
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-open-registry.d.ts +1 -1
- package/dist/delivery-open-registry.js +1 -1
- package/dist/directory-connect.d.ts +27 -0
- package/dist/directory-connect.d.ts.map +1 -0
- package/dist/directory-connect.js +98 -0
- package/dist/directory-connect.js.map +1 -0
- package/dist/disconnect-cleanup.d.ts +45 -0
- package/dist/disconnect-cleanup.d.ts.map +1 -0
- package/dist/disconnect-cleanup.js +65 -0
- package/dist/disconnect-cleanup.js.map +1 -0
- package/dist/document-surface.d.ts +32 -0
- package/dist/document-surface.d.ts.map +1 -0
- package/dist/document-surface.js +150 -0
- package/dist/document-surface.js.map +1 -0
- package/dist/document-wiring.d.ts +53 -0
- package/dist/document-wiring.d.ts.map +1 -0
- package/dist/document-wiring.js +323 -0
- package/dist/document-wiring.js.map +1 -0
- package/dist/held-content.d.ts +7 -3
- package/dist/held-content.d.ts.map +1 -1
- package/dist/held-content.js.map +1 -1
- package/dist/inbound-sessions.d.ts +9 -1
- package/dist/inbound-sessions.d.ts.map +1 -1
- package/dist/inbound-sessions.js +9 -1
- package/dist/inbound-sessions.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/ipc-surface.d.ts +44 -0
- package/dist/ipc-surface.d.ts.map +1 -0
- package/dist/ipc-surface.js +106 -0
- package/dist/ipc-surface.js.map +1 -0
- package/dist/network-directory-node.d.ts +0 -18
- package/dist/network-directory-node.d.ts.map +1 -1
- package/dist/network-directory-node.js +1 -38
- package/dist/network-directory-node.js.map +1 -1
- package/dist/notification-handlers.d.ts.map +1 -1
- package/dist/notification-handlers.js +2 -0
- package/dist/notification-handlers.js.map +1 -1
- package/dist/operator-guidance.d.ts +25 -0
- package/dist/operator-guidance.d.ts.map +1 -0
- package/dist/operator-guidance.js +50 -0
- package/dist/operator-guidance.js.map +1 -0
- package/dist/park-recovery.d.ts +22 -1
- package/dist/park-recovery.d.ts.map +1 -1
- package/dist/park-recovery.js.map +1 -1
- package/dist/park-refusals.d.ts +147 -0
- package/dist/park-refusals.d.ts.map +1 -0
- package/dist/park-refusals.js +331 -0
- package/dist/park-refusals.js.map +1 -0
- package/dist/refusal-notices.d.ts.map +1 -1
- package/dist/refusal-notices.js +22 -2
- package/dist/refusal-notices.js.map +1 -1
- package/dist/refusal-reasons.d.ts +13 -0
- package/dist/refusal-reasons.d.ts.map +1 -1
- package/dist/refusal-reasons.js +19 -0
- package/dist/refusal-reasons.js.map +1 -1
- package/dist/register-handler.d.ts +8 -7
- package/dist/register-handler.d.ts.map +1 -1
- package/dist/register-handler.js.map +1 -1
- package/dist/session-content-context.d.ts +144 -0
- package/dist/session-content-context.d.ts.map +1 -0
- package/dist/session-content-context.js +2 -0
- package/dist/session-content-context.js.map +1 -0
- package/dist/session-content-handlers.d.ts.map +1 -1
- package/dist/session-content-handlers.js +2 -0
- package/dist/session-content-handlers.js.map +1 -1
- package/dist/session-content-ingest.d.ts +209 -0
- package/dist/session-content-ingest.d.ts.map +1 -0
- package/dist/session-content-ingest.js +2216 -0
- package/dist/session-content-ingest.js.map +1 -0
- package/dist/session-content-send.d.ts +184 -0
- package/dist/session-content-send.d.ts.map +1 -0
- package/dist/session-content-send.js +1300 -0
- package/dist/session-content-send.js.map +1 -0
- package/dist/session-lifecycle.d.ts +303 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +1643 -0
- package/dist/session-lifecycle.js.map +1 -0
- package/dist/session-node-factory.d.ts +18 -0
- package/dist/session-node-factory.d.ts.map +1 -0
- package/dist/session-node-factory.js +182 -0
- package/dist/session-node-factory.js.map +1 -0
- package/dist/session-node-manager.d.ts +68 -766
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +966 -7940
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-node-types.d.ts +30 -0
- package/dist/session-node-types.d.ts.map +1 -1
- package/dist/session-node-types.js.map +1 -1
- package/dist/session-notify.d.ts +46 -0
- package/dist/session-notify.d.ts.map +1 -0
- package/dist/session-notify.js +115 -0
- package/dist/session-notify.js.map +1 -0
- package/dist/session-read-handlers.d.ts.map +1 -1
- package/dist/session-read-handlers.js +0 -1
- package/dist/session-read-handlers.js.map +1 -1
- package/dist/session-relay.d.ts +361 -0
- package/dist/session-relay.d.ts.map +1 -0
- package/dist/session-relay.js +1471 -0
- package/dist/session-relay.js.map +1 -0
- package/dist/session-salt-agreement.d.ts +0 -7
- package/dist/session-salt-agreement.d.ts.map +1 -1
- package/dist/session-salt-agreement.js +0 -7
- package/dist/session-salt-agreement.js.map +1 -1
- package/dist/session-salts.d.ts +13 -0
- package/dist/session-salts.d.ts.map +1 -1
- package/dist/session-salts.js +13 -0
- package/dist/session-salts.js.map +1 -1
- package/dist/session-seal.d.ts +336 -0
- package/dist/session-seal.d.ts.map +1 -0
- package/dist/session-seal.js +948 -0
- package/dist/session-seal.js.map +1 -0
- package/dist/session-views.d.ts +46 -0
- package/dist/session-views.d.ts.map +1 -0
- package/dist/session-views.js +197 -0
- package/dist/session-views.js.map +1 -0
- package/dist/signal-handlers.d.ts +66 -0
- package/dist/signal-handlers.d.ts.map +1 -0
- package/dist/signal-handlers.js +975 -0
- package/dist/signal-handlers.js.map +1 -0
- package/dist/signaling-wiring.d.ts +100 -0
- package/dist/signaling-wiring.d.ts.map +1 -0
- package/dist/signaling-wiring.js +392 -0
- package/dist/signaling-wiring.js.map +1 -0
- package/dist/start-agent.d.ts +64 -0
- package/dist/start-agent.d.ts.map +1 -0
- package/dist/start-agent.js +136 -0
- package/dist/start-agent.js.map +1 -0
- package/dist/status-handler.d.ts +46 -0
- package/dist/status-handler.d.ts.map +1 -0
- package/dist/status-handler.js +58 -0
- package/dist/status-handler.js.map +1 -0
- package/dist/test-handlers.d.ts +62 -0
- package/dist/test-handlers.d.ts.map +1 -0
- package/dist/test-handlers.js +239 -0
- package/dist/test-handlers.js.map +1 -0
- package/dist/types.d.ts +0 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -12
- package/dist/types.js.map +1 -1
- package/dist/unresolved-nodes-report.d.ts +14 -0
- package/dist/unresolved-nodes-report.d.ts.map +1 -0
- package/dist/unresolved-nodes-report.js +96 -0
- package/dist/unresolved-nodes-report.js.map +1 -0
- package/dist/who-resolver.d.ts +15 -0
- package/dist/who-resolver.d.ts.map +1 -0
- package/dist/who-resolver.js +46 -0
- package/dist/who-resolver.js.map +1 -0
- package/dist/wire-content-hash.d.ts +0 -2
- package/dist/wire-content-hash.d.ts.map +1 -1
- package/dist/wire-content-hash.js +0 -4
- package/dist/wire-content-hash.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an UNATTENDED agent does when someone reaches it: answer once, say so plainly, and — if the
|
|
3
|
+
* caller keeps talking to an empty room — close the conversation rather than leave it open forever.
|
|
4
|
+
*
|
|
5
|
+
* "Attended" means a live client has claimed the agent with `cello_use_agent`. Several sessions
|
|
6
|
+
* attending one agent at once is legitimate and permanent — co-attendance, not exclusivity — which
|
|
7
|
+
* is why the COUNT is kept separate from the boolean and both read the same `currentAgent` map the
|
|
8
|
+
* doorbell routes on. They cannot disagree about who is present.
|
|
9
|
+
*
|
|
10
|
+
* Eleven dependencies, under the order's bound of ~12 — counted, because the first version of
|
|
11
|
+
* this line said seven. This block was never entangled with the boot
|
|
12
|
+
* sequence; it just happened to be written in the middle of it.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ IT PLACES ITS OWN LEAVES. The one-shot rejection sends content, appends the leaf and records
|
|
15
|
+
* the transcript row, so this file belongs in the seal call-site enforcer's CALLERS list — it is
|
|
16
|
+
* added there in the same commit, because that guard stops SCANNING a file that leaves the list
|
|
17
|
+
* rather than going red.
|
|
18
|
+
*/
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
|
+
import { countAttendance, ContentTakeLedger } from "./co-attendance.js";
|
|
21
|
+
import { isOwnAwayAutoReply, AWAY_AUTO_REPLY_TEXTS, markAsAutoReply, isAutoReplyMarked } from "./away-detection.js";
|
|
22
|
+
import { LEAF_KIND_MSG } from "./session-relay-client.js";
|
|
23
|
+
import { sentAuthorship } from "./session-content-handlers.js";
|
|
24
|
+
import { escalateToUnilateralSeal as runUnilateralEscalation, UNILATERAL_SEAL_TIMEOUT_MS } from "./seal-escalation.js";
|
|
25
|
+
export function createAttendanceWiring(deps) {
|
|
26
|
+
const { logger, sessionNodeManager, perConnectionState, keyProviders, securityGateway, handleActiveSealFlow, sealKey, sealInterruptedInProgress, pendingSealWaiters, pendingUnilateralWaiters, sendOver, } = deps;
|
|
27
|
+
// M8C-AWAY-1: away response — an unattended Primary auto-answers session requests + messages
|
|
28
|
+
// with the default transparent away text and queues them (the DoD's own mandated default).
|
|
29
|
+
// CORE ships now; the operator-configurable custom-text / opaque-privacy-mode SWITCH is PARKED
|
|
30
|
+
// on M9-CFG-001, journaled as D15 (M8C-DECISIONS.md, mirrors D14) — a genuine per-agent operator
|
|
31
|
+
// preference that needs the deferred config store, whereas transparent is already the correct,
|
|
32
|
+
// non-fake default this unit must ship regardless.
|
|
33
|
+
// "Attended" per the design doc (2026-07-01 command-surface discussion, Agent State Model):
|
|
34
|
+
// Primary + a live client session has claimed the agent via use_agent. Scanning
|
|
35
|
+
// perConnectionState is cheap (a handful of connections) and needs no separate tracked state.
|
|
36
|
+
function isAttended(agentName) {
|
|
37
|
+
for (const s of perConnectionState.values())
|
|
38
|
+
if (s.currentAgent === agentName)
|
|
39
|
+
return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// DOD-COATTEND-VISIBLE-1: the COUNT, deliberately kept separate from the boolean above. Several
|
|
43
|
+
// sessions attending one agent is legitimate and permanent (co-attendance, not exclusivity — spec
|
|
44
|
+
// §3); what was missing is that nobody was ever TOLD. `isAttended` is left byte-identical because
|
|
45
|
+
// M8C-AWAY-1's auto-ack suppression hangs off its early return, and both read the same
|
|
46
|
+
// `currentAgent` map the doorbell routes on, so the count can never disagree with who gets woken.
|
|
47
|
+
function attendanceCount(agentName) {
|
|
48
|
+
return countAttendance(perConnectionState, agentName);
|
|
49
|
+
}
|
|
50
|
+
// Which connection consumed which leaf, so the session that finds an empty buffer can be told
|
|
51
|
+
// whether its counterparty is quiet or its sibling was faster. Written at the destructive drain in
|
|
52
|
+
// session-content-handlers; read at that handler's timeout. Delivery itself is unchanged.
|
|
53
|
+
const contentTakes = new ContentTakeLedger();
|
|
54
|
+
/**
|
|
55
|
+
* In-flight background seal ceremonies — `DOD-M15-CLOSEWAIT-1` review MEDIUM-6.
|
|
56
|
+
*
|
|
57
|
+
* The close now answers at commitment and finishes the ceremony detached. Without this the tail
|
|
58
|
+
* was the ONE background task `stop()` could cut at an arbitrary point, while it cancels or awaits
|
|
59
|
+
* every other one.
|
|
60
|
+
*/
|
|
61
|
+
const backgroundSeals = new Set();
|
|
62
|
+
// DOD-AWAY-WRAP-1 AC1: request text is a leave-a-message greeting; agentName is spliced in at
|
|
63
|
+
// the call site so it names the specific away agent.
|
|
64
|
+
// DOD-AWAY-ACK-ONESHOT-TEXT-1 (live defect 2026-07-24): the ack must state the one-shot rule —
|
|
65
|
+
// without it a cooperative caller LLM has no reason to stop, sends a follow-up, and eats the
|
|
66
|
+
// DOD-INBOX-ONESHOT-1 rejection the design itself invited.
|
|
67
|
+
// ONE definition, shared with the detector in away-detection.ts. A second copy here is how a
|
|
68
|
+
// reworded away message stops being recognised as machine traffic and the mutual-seal loop
|
|
69
|
+
// (DOD-AWAY-MUTUAL-SEAL-1) quietly comes back.
|
|
70
|
+
const AWAY_MESSAGE_TEXT = AWAY_AUTO_REPLY_TEXTS.oneShot;
|
|
71
|
+
// M8C-CONTACT-1: "unknown senders learn only 'dispatched' by default" — a single shared,
|
|
72
|
+
// deliberately minimal template regardless of kind, distinct from AWAY-1's richer per-type text.
|
|
73
|
+
const STRANGER_TEXT = "Dispatched.";
|
|
74
|
+
// Coalescing: one away ack per (agent, session, kind) per away period — cleared when the agent
|
|
75
|
+
// becomes attended again (cello_use_agent) so the NEXT away period gets a fresh ack rather than
|
|
76
|
+
// staying silent forever. Known imprecision (journaled, not silent): clearing fires on ANY
|
|
77
|
+
// use_agent selecting this name, even if another connection kept it attended throughout — an
|
|
78
|
+
// edge case, not a correctness gap in the core "queue + one calm ack while genuinely away" promise.
|
|
79
|
+
const awayAckSent = new Set();
|
|
80
|
+
async function sendAwayResponse(agentName, sessionId, kind) {
|
|
81
|
+
if (isAttended(agentName))
|
|
82
|
+
return;
|
|
83
|
+
// DOD-AWAY-WRAP-1 AC2: if the triggering message carries [[WRAP]], the caller is done — skip the
|
|
84
|
+
// away reply entirely and let the seal ceremony proceed. The dedupKey is intentionally NOT added
|
|
85
|
+
// here: the WRAP path is a silent close, not an away period that should be deduped.
|
|
86
|
+
if (kind === "message") {
|
|
87
|
+
const latestHex = sessionNodeManager.peekLatestReceivedContentHex(agentName, sessionId);
|
|
88
|
+
if (latestHex !== null) {
|
|
89
|
+
const text = Buffer.from(latestHex, "hex").toString("utf8");
|
|
90
|
+
// DOD-WRAP-SUBSTRING-1: match the APPENDED token, not any substring —
|
|
91
|
+
// DOD-SIGNAL-TOKEN-1 always appends the real token at the END of the body. A substring
|
|
92
|
+
// match would classify a mere mention of [[WRAP]] (sent signal:"over") as a close
|
|
93
|
+
// signal, silently skipping both the away reply and the oneshot rejection.
|
|
94
|
+
if (text.trimEnd().endsWith("[[WRAP]]")) {
|
|
95
|
+
logger.info("session.away.response.skipped_wrap", { agentName, sessionId });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// DOD-AWAY-MUTUAL-SEAL-1: an away responder must not answer ANOTHER away responder, and
|
|
99
|
+
// must not count one toward the one-shot rule.
|
|
100
|
+
//
|
|
101
|
+
// When both agents are unattended, each side's auto-reply arrives at the other as an inbound
|
|
102
|
+
// message. The second such arrival looks exactly like "a caller who ignored the
|
|
103
|
+
// leave-a-message instruction" — so the one-shot fires on BOTH sides, each sends a [[WRAP]]
|
|
104
|
+
// rejection and initiates a seal, and two distinct-sender SEAL ctrl leaves is precisely what
|
|
105
|
+
// notarizes a session.
|
|
106
|
+
//
|
|
107
|
+
// Measured from the relay's own log 2026-08-09: sealed THREE SECONDS after opening, its
|
|
108
|
+
// entire content two machines telling each other nobody was home. Neither daemon learned —
|
|
109
|
+
// the seal completion is pushed with no pull twin — so both showed `active`, and the
|
|
110
|
+
// operators returned and talked into a closed room for 68 minutes before finding out.
|
|
111
|
+
//
|
|
112
|
+
// Returned silently rather than answered: a reply would continue the ping-pong, and there is
|
|
113
|
+
// nobody on the far side to read one.
|
|
114
|
+
if (isOwnAwayAutoReply(text)) {
|
|
115
|
+
logger.info("session.away.mutual.skipped", {
|
|
116
|
+
agentName,
|
|
117
|
+
sessionId,
|
|
118
|
+
// DOD-M12B-AWAY-MARK-1: WHICH branch matched. The marker is in-band and therefore
|
|
119
|
+
// typeable, so prefixing every message with it skips the one-shot auto-close for free —
|
|
120
|
+
// the cost of that attack fell from "reproduce the exact away wording" to "type 15
|
|
121
|
+
// characters". This field is what makes a peer doing it on every message visible;
|
|
122
|
+
// without it the line reads as routine machine-to-machine traffic.
|
|
123
|
+
matched: isAutoReplyMarked(text) ? "marker" : "legacy_exact",
|
|
124
|
+
impact: "no away reply and no one-shot seal — two away agents must not notarize a conversation nobody had",
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const dedupKey = `${agentName}:${sessionId}:${kind}`;
|
|
131
|
+
// F3 fix: a dedicated guard prevents re-entry after the rejection fires — without it a
|
|
132
|
+
// rapid-fire sender could trigger multiple rejection sends and concurrent seal submits while
|
|
133
|
+
// the session remains active (seal failed). This key is never cleared (no re-attend resets
|
|
134
|
+
// it — once rejected, the session is closing regardless).
|
|
135
|
+
const rejectedKey = `${agentName}:${sessionId}:rejected`;
|
|
136
|
+
if (awayAckSent.has(dedupKey)) {
|
|
137
|
+
// DOD-INBOX-ONESHOT-1: a second inbound message while the first away ack is still live means
|
|
138
|
+
// the caller ignored the leave-a-message instruction. Send one [[WRAP]]-bearing rejection and
|
|
139
|
+
// immediately initiate the seal so the session closes without operator intervention.
|
|
140
|
+
if (kind === "message" && !awayAckSent.has(rejectedKey)) {
|
|
141
|
+
awayAckSent.add(rejectedKey); // guard BEFORE async work — concurrent arrivals must not re-enter
|
|
142
|
+
const record2 = sessionNodeManager.getSessionRecord(agentName, sessionId);
|
|
143
|
+
if (record2 && record2.status === "active") {
|
|
144
|
+
// Reviewer F2: token at the END — the daemon's own output must honor the
|
|
145
|
+
// DOD-SIGNAL-TOKEN-1 append-at-end contract that DOD-WRAP-SUBSTRING-1 detection
|
|
146
|
+
// is anchored on (a counterparty daemon's end-anchored detector must see this close).
|
|
147
|
+
// DOD-M12B-AWAY-MARK-1: machine-generated, so it is marked — and the marker goes at the
|
|
148
|
+
// FRONT precisely so [[WRAP]] keeps the end position the counterparty's detector anchors on.
|
|
149
|
+
const rejectText = markAsAutoReply("This inbox only accepts one message per visit. Closing. [[WRAP]]");
|
|
150
|
+
const rejectBytes = new TextEncoder().encode(rejectText);
|
|
151
|
+
// B2b: one decision point for the hash AND its algorithm — see `contentHashForSession`.
|
|
152
|
+
const reject = await sessionNodeManager.contentHashForSession(agentName, sessionId, rejectBytes);
|
|
153
|
+
// Best-effort: a send failure still triggers the seal — we are closing regardless.
|
|
154
|
+
const sendResult = await sessionNodeManager.sendContent(agentName, sessionId, rejectBytes, new Uint8Array(reject.hash), randomUUID(), LEAF_KIND_MSG, reject.alg);
|
|
155
|
+
// M12-P13: commit the leaf when the rejection went out OR when it is durably queued —
|
|
156
|
+
// either way the relay already witnessed its sequence. This caller is the sharpest case
|
|
157
|
+
// of the three: the seal is initiated immediately below, so a hole here does not merely
|
|
158
|
+
// stall the far side, it seals a tree that is one leaf short of a sequence the
|
|
159
|
+
// counterparty will still receive content at. The roots then cannot agree, and the
|
|
160
|
+
// session is unsealable for good.
|
|
161
|
+
if (sendResult.ok || sendResult.durable) {
|
|
162
|
+
const rejectHashHex = Buffer.from(reject.hash).toString("hex");
|
|
163
|
+
// DOD-M12B-INDEX-1: at the relay's position, not the tail. This is the riskiest append
|
|
164
|
+
// in the codebase for that — the seal is initiated a few lines below, so a leaf at the
|
|
165
|
+
// wrong index does not merely stall the far side, it seals a tree the counterparty can
|
|
166
|
+
// never agree with.
|
|
167
|
+
// The proof travels with the leaf, not only with the transcript row below: when this
|
|
168
|
+
// append is HELD behind a gap, the `recordTranscriptMessage` call is skipped entirely
|
|
169
|
+
// and the held entry is the only thing that reaches the row on release.
|
|
170
|
+
const placed = sessionNodeManager.placeOwnLeaf(agentName, sessionId, rejectHashHex, rejectBytes, sendResult.sequenceNumber, randomUUID(), "msg", sentAuthorship(sendResult));
|
|
171
|
+
if (placed.placed) {
|
|
172
|
+
sessionNodeManager.recordTranscriptMessage(agentName, sessionId, placed.leafIndex, "sent", rejectBytes, randomUUID(), sentAuthorship(sendResult));
|
|
173
|
+
}
|
|
174
|
+
logger.info("session.away.inbox.oneshot.rejected", { agentName, sessionId, sequenceNumber: placed.placed ? placed.leafIndex : placed.heldAt, committed: placed.placed, queued: !sendResult.ok });
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Not queued anywhere — the rejection is gone. We still seal (we are closing regardless),
|
|
178
|
+
// and the counterparty simply never learns why, so say that plainly rather than at warn.
|
|
179
|
+
logger.error("session.away.inbox.oneshot.reject_send_failed", {
|
|
180
|
+
agentName, sessionId, reason: sendResult.reason, cause: sendResult.cause,
|
|
181
|
+
impact: "the rejection is lost and was NOT queued — the session seals with no explanation to the counterparty",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// DOD-INBOX-ONESHOT-1 / DOD-SEAL-BILATERAL-TIMEOUT-1: initiate the seal via the
|
|
185
|
+
// relay-mediated path (submitSealLeaf → bilateral wait → unilateral escalation).
|
|
186
|
+
// The signaling-only path (handleActiveSealFlow) suffers a leaf_count_mismatch race:
|
|
187
|
+
// this party's tree includes the just-sent rejection, but the counterparty's tree may
|
|
188
|
+
// not have ingested it yet when the seal request arrives over the signaling channel.
|
|
189
|
+
// The relay path avoids this entirely — it posts a SEAL ctrl leaf and waits for the
|
|
190
|
+
// counterparty to independently co-seal; no bilateral leaf-count comparison needed.
|
|
191
|
+
/**
|
|
192
|
+
* DOD-M15-DIVERGE-1 (review HIGH-3) — the gate has to hold on the path with no operator.
|
|
193
|
+
*
|
|
194
|
+
* `cello_close_session` refuses a diverged record, but this autonomous path never
|
|
195
|
+
* consulted `sealReadiness` at all: it read `placed.placed` and discarded
|
|
196
|
+
* `placed.diverged`, then initiated the seal directly. So "a diverged session is blocked
|
|
197
|
+
* from sealing" held for the close a human drives and not for the one that runs itself —
|
|
198
|
+
* which is the worse of the two, because the append two lines above is, by its own
|
|
199
|
+
* comment, "the riskiest append in the codebase" for exactly this reason.
|
|
200
|
+
*
|
|
201
|
+
* THE LOG IS THE SURFACE HERE, and that is not a weakening of Invariant 2. There is no
|
|
202
|
+
* caller to answer — nothing is awaiting a response on this path — so the log carries the
|
|
203
|
+
* whole warning rather than half of it.
|
|
204
|
+
*/
|
|
205
|
+
const oneshotReadiness = sessionNodeManager.sealReadiness(agentName, sessionId);
|
|
206
|
+
if (oneshotReadiness.diverged) {
|
|
207
|
+
logger.warn("session.away.inbox.oneshot.seal_skipped_diverged", {
|
|
208
|
+
agentName, sessionId,
|
|
209
|
+
treeSize: oneshotReadiness.treeSize, highWaterSeq: oneshotReadiness.highWaterSeq,
|
|
210
|
+
impact: "this side's tree parted from the relay's ordering, so the seal was NOT initiated — the session stays closeable by hand, where the operator is told what parted and can compare counts with the counterparty before deciding",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
void (async () => {
|
|
215
|
+
const correlationId = randomUUID();
|
|
216
|
+
const sk = sealKey(agentName, sessionId);
|
|
217
|
+
if (sealInterruptedInProgress.has(sk))
|
|
218
|
+
return;
|
|
219
|
+
sealInterruptedInProgress.add(sk);
|
|
220
|
+
try {
|
|
221
|
+
// DOD-M15-SEALWIRE-1 bullet 2 (review F4): SealCompletion is now a union — a refused
|
|
222
|
+
// certificate resolves the waiter rather than dropping it, so this path must handle it
|
|
223
|
+
// too rather than treating a refusal as a seal.
|
|
224
|
+
let resolveSeal;
|
|
225
|
+
const sealedP = new Promise((r) => { resolveSeal = r; });
|
|
226
|
+
pendingSealWaiters.set(sk, resolveSeal);
|
|
227
|
+
const submit = await sessionNodeManager.submitSealLeaf(agentName, sessionId, correlationId);
|
|
228
|
+
if (!submit.ok && submit.reason !== "responder_seal_already_submitted") {
|
|
229
|
+
pendingSealWaiters.delete(sk);
|
|
230
|
+
if (submit.reason === "relay_unavailable") {
|
|
231
|
+
const fallback = await handleActiveSealFlow(sessionId, record2, correlationId);
|
|
232
|
+
if (fallback.ok) {
|
|
233
|
+
logger.info("session.away.inbox.oneshot.seal_initiated", { agentName, sessionId, path: "signaling_fallback" });
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
logger.warn("session.away.inbox.oneshot.seal_initiate_failed", { agentName, sessionId, reason: fallback.reason, path: "signaling_fallback" });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
logger.warn("session.away.inbox.oneshot.seal_initiate_failed", { agentName, sessionId, reason: submit.reason });
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
logger.info("session.away.inbox.oneshot.seal_initiated", { agentName, sessionId, path: "relay" });
|
|
245
|
+
const bilateralTimeoutMs = Number(process.env["CELLO_SEAL_BILATERAL_TIMEOUT_MS"]) || 660_000;
|
|
246
|
+
let timer;
|
|
247
|
+
const timeoutP = new Promise((r) => { timer = setTimeout(() => r(null), bilateralTimeoutMs); });
|
|
248
|
+
const sealedCompletion = await Promise.race([sealedP, timeoutP]);
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
pendingSealWaiters.delete(sk);
|
|
251
|
+
if (sealedCompletion !== null) {
|
|
252
|
+
// DOD-M15-SEALWIRE-1 bullet 2 (review F4): a REFUSED certificate resolves the waiter
|
|
253
|
+
// now rather than being dropped, so this path must tell the two apart — logging a
|
|
254
|
+
// refusal as "sealed" would be the silent acceptance the whole bullet exists to stop.
|
|
255
|
+
if ("refused" in sealedCompletion) {
|
|
256
|
+
logger.error("session.away.inbox.oneshot.seal_refused", {
|
|
257
|
+
agentName, sessionId, reason: sealedCompletion.reason, detail: sealedCompletion.detail,
|
|
258
|
+
impact: "the away auto-seal was REFUSED: the directory returned a validly signed root " +
|
|
259
|
+
"that does not describe this conversation. The session is NOT sealed and nothing " +
|
|
260
|
+
"was signed with this agent's key.",
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
logger.info("session.away.inbox.oneshot.sealed", { agentName, sessionId, sealedRoot: sealedCompletion.rootHex });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Bilateral timeout — escalate to unilateral seal.
|
|
268
|
+
const escalation = submit.ok
|
|
269
|
+
? { reportedRootHex: submit.reportedRootHex, sequenceNumber: submit.sequenceNumber }
|
|
270
|
+
: submit.reason === "responder_seal_already_submitted" &&
|
|
271
|
+
typeof submit.reportedRootHex === "string" &&
|
|
272
|
+
typeof submit.sequenceNumber === "number"
|
|
273
|
+
? { reportedRootHex: submit.reportedRootHex, sequenceNumber: submit.sequenceNumber }
|
|
274
|
+
: null;
|
|
275
|
+
if (!escalation) {
|
|
276
|
+
logger.warn("session.away.inbox.oneshot.seal_pending", { agentName, sessionId });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// DOD-M12B-SEAL-ESCALATE-DUP-1: THE SHARED ESCALATION, not a second copy.
|
|
280
|
+
//
|
|
281
|
+
// This used to be a line-for-line duplicate with its own hardcoded 30 s timeout, and
|
|
282
|
+
// it missed every refusal the other one gained: an empty carry, a gappy chain, two of
|
|
283
|
+
// our own ctrl leaves (permanently unsealable), and a bilateral seal already running.
|
|
284
|
+
// It spent the full timeout on each and then reported `seal_unilateral_timeout` — the
|
|
285
|
+
// label that names our own wait. Sharing the body is what stops that drifting again.
|
|
286
|
+
const uni = await runUnilateralEscalation({
|
|
287
|
+
logger, sessionNodeManager, sendOver, pendingUnilateralWaiters, sealKey,
|
|
288
|
+
getKeyProvider: (a) => keyProviders.get(a),
|
|
289
|
+
timeoutMs: UNILATERAL_SEAL_TIMEOUT_MS,
|
|
290
|
+
}, agentName, sessionId, escalation, correlationId, { refuseOnUnusableCarry: true });
|
|
291
|
+
if (uni.ok) {
|
|
292
|
+
logger.info("session.away.inbox.oneshot.sealed", { agentName, sessionId, sealedRoot: uni.sealed_root, sealType: "unilateral" });
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// CARRY THE GUIDANCE. The four refusals this path just gained come with the
|
|
296
|
+
// sentence that tells an operator whether to retry or force-abandon — and the log
|
|
297
|
+
// is this path's ONLY surface, so dropping it leaves a nameable cause with no
|
|
298
|
+
// action attached. The old reasons had no guidance to lose; these do.
|
|
299
|
+
logger.warn("session.away.inbox.oneshot.seal_unilateral_failed", {
|
|
300
|
+
agentName, sessionId, reason: uni.reason, guidance: uni.guidance,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
sealInterruptedInProgress.delete(sealKey(agentName, sessionId));
|
|
306
|
+
}
|
|
307
|
+
})();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const record = sessionNodeManager.getSessionRecord(agentName, sessionId);
|
|
314
|
+
if (!record || record.status !== "active")
|
|
315
|
+
return;
|
|
316
|
+
// Select the ack wording by whether the sender is a known contact — STRANGER_TEXT
|
|
317
|
+
// ("Dispatched.") for unknown, system default for known. The inbound accept path does NOT
|
|
318
|
+
// auto-add the sender: an unattended stranger STAYS unknown across every inbound interaction.
|
|
319
|
+
// Promotion requires operator engagement — an outbound initiate, a cello_send reply, or an
|
|
320
|
+
// explicit contact add. This is a plain read of current contact state; nothing downstream
|
|
321
|
+
// depends on its ordering.
|
|
322
|
+
const isKnown = sessionNodeManager.isKnown(agentName, record.counterparty_pubkey);
|
|
323
|
+
// DOD-AWAY-WRAP-1 AC1: request kind uses a leave-a-message greeting that names the away agent.
|
|
324
|
+
const systemDefault = kind === "request"
|
|
325
|
+
? AWAY_AUTO_REPLY_TEXTS.offerFor(agentName)
|
|
326
|
+
: AWAY_MESSAGE_TEXT;
|
|
327
|
+
awayAckSent.add(dedupKey); // guard BEFORE the async send — concurrent arrivals must not double-ack
|
|
328
|
+
try {
|
|
329
|
+
// DOD-AWAY-TIER-1: resolve most-specific-first — per-contact away_message → per-tier away
|
|
330
|
+
// (settings) → agent default (settings) → the system default (code, per kind). Total.
|
|
331
|
+
// DOD-M12B-AWAY-MARK-1: THE choke point. Every away reply this daemon sends passes through
|
|
332
|
+
// this one expression — the per-contact message, the per-tier message, the agent default, the
|
|
333
|
+
// system default and the stranger ack — so marking here is what makes "an operator's
|
|
334
|
+
// CONFIGURED away message is indistinguishable from a person" go away, which is the half
|
|
335
|
+
// exact-text matching could not reach by construction. markAsAutoReply is idempotent, so the
|
|
336
|
+
// system defaults (already marked at their source) do not pick up a second token.
|
|
337
|
+
const awayText = sessionNodeManager.resolveAwayMessage(agentName, record.counterparty_pubkey)
|
|
338
|
+
?? (isKnown ? systemDefault : STRANGER_TEXT);
|
|
339
|
+
const draftBytes = new TextEncoder().encode(awayText);
|
|
340
|
+
// SI (AWAY-TIER-1): an away message is now operator-configurable, i.e. an outbound DISCLOSURE.
|
|
341
|
+
// Screen it on the outbound path like any content — it does NOT bypass the gateway. A block/warn
|
|
342
|
+
// verdict means it is not sent (the dedup guard stays set — one screen per away period, no spam);
|
|
343
|
+
// a redact verdict sends the ALTERED bytes.
|
|
344
|
+
const awayVerdict = await securityGateway.screenOutbound(draftBytes, {
|
|
345
|
+
direction: "outbound", agentName, sessionId, correlationId: randomUUID(),
|
|
346
|
+
});
|
|
347
|
+
if (awayVerdict.disposition === "block" || awayVerdict.disposition === "warn") {
|
|
348
|
+
logger.info("session.away.response.screened_out", { agentName, sessionId, kind, disposition: awayVerdict.disposition });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (awayVerdict.disposition === "redact" && awayVerdict.content === undefined) {
|
|
352
|
+
logger.error("session.away.response.redact_without_content", { agentName, sessionId, kind });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// DOD-M12B-AWAY-MARK-1: mark AFTER screening, never before. A redact verdict REPLACES the
|
|
356
|
+
// bytes, and marking the draft would let that replacement silently strip the marker — an away
|
|
357
|
+
// reply back on the wire indistinguishable from a person, with no log line saying so. Marking
|
|
358
|
+
// here also means the gateway screens the operator's actual disclosure rather than a daemon
|
|
359
|
+
// token bolted to the front of it. markAsAutoReply is idempotent and the system defaults are
|
|
360
|
+
// marked at their source, so nothing gains a second token.
|
|
361
|
+
const screenedBytes = awayVerdict.disposition === "redact" && awayVerdict.content !== undefined
|
|
362
|
+
? new Uint8Array(awayVerdict.content)
|
|
363
|
+
: draftBytes;
|
|
364
|
+
const contentBytes = new TextEncoder().encode(markAsAutoReply(new TextDecoder().decode(screenedBytes)));
|
|
365
|
+
// B2b: one decision point for the hash AND its algorithm — see `contentHashForSession`.
|
|
366
|
+
const away = await sessionNodeManager.contentHashForSession(agentName, sessionId, contentBytes);
|
|
367
|
+
const sendResult = await sessionNodeManager.sendContent(agentName, sessionId, contentBytes, new Uint8Array(away.hash), randomUUID(), LEAF_KIND_MSG, away.alg);
|
|
368
|
+
if (!sendResult.ok && !sendResult.durable) {
|
|
369
|
+
// Reviewer MEDIUM fix: a transient failure must NOT permanently silence the rest of this
|
|
370
|
+
// away period — clear the guard so the next inbound arrival retries the ack.
|
|
371
|
+
awayAckSent.delete(dedupKey);
|
|
372
|
+
// M12-P13: this branch now means the reply is GONE, not merely late (the queued case is
|
|
373
|
+
// handled below), so it is an error and it says what the consequence is. It was a bare warn
|
|
374
|
+
// when it fired live on 2026-08-05 and read as routine churn.
|
|
375
|
+
logger.error("session.away.response.failed", {
|
|
376
|
+
agentName, sessionId, kind, reason: sendResult.reason, cause: sendResult.cause,
|
|
377
|
+
impact: "the away reply is lost and was NOT queued — the counterparty gets no acknowledgement",
|
|
378
|
+
});
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const contentHashHex = Buffer.from(away.hash).toString("hex");
|
|
382
|
+
if (!sendResult.ok) {
|
|
383
|
+
// M12-P13 (found live 2026-08-05, M12 Entry 89): the reply is durably queued and already
|
|
384
|
+
// owns the sequence the relay witnessed for it, so its leaf MUST be committed here. Without
|
|
385
|
+
// it this side's tree stays one short of that sequence, and since `nextExpected` is the tree
|
|
386
|
+
// size, every message the counterparty sends afterwards is held behind a gap nothing can
|
|
387
|
+
// fill. That is exactly how the receiver stranded its own session at sequence 0.
|
|
388
|
+
//
|
|
389
|
+
// The dedup guard deliberately STAYS SET: a queued reply is coming, and re-sending on the
|
|
390
|
+
// next arrival would mint a second greeting at a second sequence.
|
|
391
|
+
// DOD-M12B-INDEX-1: the queued reply owns the position the relay witnessed for it, and
|
|
392
|
+
// that is where its leaf goes.
|
|
393
|
+
// Witnessed and SIGNED — only the direct hand-off failed — so the proof exists and must
|
|
394
|
+
// reach the leaf. This is the site `sentAuthorship`'s own comment calls dead-by-construction
|
|
395
|
+
// under an `ok`-gated read; the same reasoning applies to the leaf, not just the row.
|
|
396
|
+
const placedQueued = sessionNodeManager.placeOwnLeaf(agentName, sessionId, contentHashHex, contentBytes, sendResult.sequenceNumber, randomUUID(), "msg", sentAuthorship(sendResult));
|
|
397
|
+
if (placedQueued.placed) {
|
|
398
|
+
sessionNodeManager.recordTranscriptMessage(agentName, sessionId, placedQueued.leafIndex, "sent", contentBytes, randomUUID(), sentAuthorship(sendResult));
|
|
399
|
+
}
|
|
400
|
+
logger.info("session.away.response.deferred", {
|
|
401
|
+
agentName, sessionId, kind, isKnown,
|
|
402
|
+
sequenceNumber: placedQueued.placed ? placedQueued.leafIndex : placedQueued.heldAt,
|
|
403
|
+
committed: placedQueued.placed,
|
|
404
|
+
reason: sendResult.reason, cause: sendResult.cause,
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
// DOD-M12B-INDEX-1: the away responder fires while inbound is still arriving, so it is the
|
|
409
|
+
// path most likely to have a gap open under it — exactly where a tail append does damage.
|
|
410
|
+
const placedReply = sessionNodeManager.placeOwnLeaf(agentName, sessionId, contentHashHex, contentBytes, sendResult.sequenceNumber, randomUUID(), "msg", sentAuthorship(sendResult));
|
|
411
|
+
if (placedReply.placed) {
|
|
412
|
+
sessionNodeManager.recordTranscriptMessage(agentName, sessionId, placedReply.leafIndex, "sent", contentBytes, randomUUID(), sentAuthorship(sendResult));
|
|
413
|
+
}
|
|
414
|
+
logger.info("session.away.response.sent", {
|
|
415
|
+
agentName, sessionId, kind, isKnown,
|
|
416
|
+
sequenceNumber: placedReply.placed ? placedReply.leafIndex : placedReply.heldAt,
|
|
417
|
+
committed: placedReply.placed,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
// Reviewer MEDIUM fix: same as above — an unexpected throw must not permanently lock out
|
|
422
|
+
// future retries for the rest of this away period.
|
|
423
|
+
awayAckSent.delete(dedupKey);
|
|
424
|
+
logger.warn("session.away.response.failed", { agentName, sessionId, kind, error: err instanceof Error ? err.message : String(err) });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// `isAttended` is NOT returned: its only caller in the repo is `sendAwayResponse`, which moved
|
|
428
|
+
// with it. Returning it would make a private local reachable through an exported factory for no
|
|
429
|
+
// consumer — new surface, which Rule D forbids.
|
|
430
|
+
return { attendanceCount, sendAwayResponse, contentTakes, backgroundSeals, awayAckSent };
|
|
431
|
+
}
|
|
432
|
+
//# sourceMappingURL=attendance-wiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attendance-wiring.js","sourceRoot":"","sources":["../src/attendance-wiring.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IpcHandler } from "./ipc-server.js";
|
|
2
|
+
import type { Logger } from "./types.js";
|
|
3
|
+
export interface BackupRestoreDeps {
|
|
4
|
+
handlers: Map<string, IpcHandler>;
|
|
5
|
+
logger: Logger;
|
|
6
|
+
/** Where the daemon keeps its data — the database this writes out and reads back sits under it. */
|
|
7
|
+
celloDir: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function registerBackupRestoreHandlers(deps: BackupRestoreDeps): void;
|
|
10
|
+
//# sourceMappingURL=backup-restore-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-restore-handlers.d.ts","sourceRoot":"","sources":["../src/backup-restore-handlers.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAuE3E"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backup and restore: write this machine's agent to a file, and put one back.
|
|
3
|
+
*
|
|
4
|
+
* Three dependencies — the handler map, a logger, and where the daemon keeps its data. That is the
|
|
5
|
+
* whole reason this is its own module rather than sitting with the other admin verbs: it touches
|
|
6
|
+
* files and nothing else, so it needs none of the signaling, consortium or ceremony machinery they
|
|
7
|
+
* do, and merging them would push one context over the bound to save one call site.
|
|
8
|
+
*
|
|
9
|
+
* The backup file is as sensitive as a private key. `VACUUM INTO` takes a consistent snapshot with
|
|
10
|
+
* the database still open, which is what makes this safe to run against the daemon serving the call.
|
|
11
|
+
*/
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { createBackup, inspectBackup } from "./backup-restore.js";
|
|
14
|
+
export function registerBackupRestoreHandlers(deps) {
|
|
15
|
+
const { handlers, logger, celloDir } = deps;
|
|
16
|
+
/**
|
|
17
|
+
* DOD-M15-BACKUP-1 — export the agent, so losing the machine does not lose the identity.
|
|
18
|
+
*
|
|
19
|
+
* `VACUUM INTO` takes a consistent snapshot with the database still open, so this is safe to call
|
|
20
|
+
* on a running daemon — which matters, because the daemon is what serves the tool.
|
|
21
|
+
*/
|
|
22
|
+
handlers.set("cello_backup", async (params, _connectionId) => {
|
|
23
|
+
const outPath = typeof params?.["path"] === "string" ? params["path"] : "";
|
|
24
|
+
if (!outPath) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
reason: "missing_path",
|
|
28
|
+
guidance: "Give an absolute path to write the backup to, e.g. { path: \"/Users/you/cello-agent.cello-backup\" }. " +
|
|
29
|
+
"It is not written to a default location on purpose: the file contains the key to your agent, " +
|
|
30
|
+
"so where it lands is a decision you should make deliberately.",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const res = await createBackup({
|
|
34
|
+
dbPath: join(celloDir, "sessions.db"),
|
|
35
|
+
outPath,
|
|
36
|
+
logger,
|
|
37
|
+
...(params?.["overwrite"] === true ? { overwrite: true } : {}),
|
|
38
|
+
});
|
|
39
|
+
return res.ok
|
|
40
|
+
? { ok: true, path: res.path, bytes: res.bytes, guidance: res.guidance }
|
|
41
|
+
: { ok: false, reason: res.reason, guidance: res.guidance };
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* RESTORE IS REFUSED WHILE THIS DAEMON IS RUNNING, and that is not a limitation to apologise for.
|
|
45
|
+
*
|
|
46
|
+
* The daemon holds the database open with a write lock. Overwriting the file underneath an open
|
|
47
|
+
* SQLite handle risks the handle flushing its own pages back over the restored ones — the restore
|
|
48
|
+
* appears to succeed and the database is a hybrid of two identities, which is worse than either
|
|
49
|
+
* failing cleanly.
|
|
50
|
+
*
|
|
51
|
+
* The capability itself lives in `backup-restore.ts` (a daemon module, per the DoD), and the CLI
|
|
52
|
+
* calls it with the daemon stopped. So the honest answer here is the exact sequence, not an
|
|
53
|
+
* attempt.
|
|
54
|
+
*/
|
|
55
|
+
handlers.set("cello_restore", async (params, _connectionId) => {
|
|
56
|
+
const archivePath = typeof params?.["path"] === "string" ? params["path"] : "";
|
|
57
|
+
const inspected = archivePath ? await inspectBackup(archivePath) : null;
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: "daemon_running",
|
|
61
|
+
...(inspected?.ok
|
|
62
|
+
? { archive_verified: true, backup_created_at: new Date(inspected.createdAt).toISOString() }
|
|
63
|
+
: inspected
|
|
64
|
+
? { archive_verified: false, archive_problem: inspected.reason }
|
|
65
|
+
: {}),
|
|
66
|
+
guidance: (inspected?.ok
|
|
67
|
+
? `That archive is valid (taken ${new Date(inspected.createdAt).toISOString()}). `
|
|
68
|
+
: inspected
|
|
69
|
+
? `WARNING: that archive did not validate (${inspected.reason}) — fix that before going further. `
|
|
70
|
+
: "") +
|
|
71
|
+
"Restoring REPLACES this machine's agent database, and it cannot be done while the daemon " +
|
|
72
|
+
"is holding it open — the running daemon could flush its own pages back over the restored " +
|
|
73
|
+
"ones and leave a database that is half one identity and half another. Stop the daemon " +
|
|
74
|
+
"first:\n\n cello logout\n cello restore " +
|
|
75
|
+
(archivePath || "<archive>") +
|
|
76
|
+
"\n cello login\n\n" +
|
|
77
|
+
"Anything that happened on this machine since the backup was taken will be gone — restore " +
|
|
78
|
+
"replaces, it does not merge.",
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=backup-restore-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-restore-handlers.js","sourceRoot":"","sources":["../src/backup-restore-handlers.ts"],"names":[],"mappings":"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"}
|