@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.
Files changed (213) hide show
  1. package/dist/agent-admin-handlers.d.ts +56 -0
  2. package/dist/agent-admin-handlers.d.ts.map +1 -0
  3. package/dist/agent-admin-handlers.js +70 -0
  4. package/dist/agent-admin-handlers.js.map +1 -0
  5. package/dist/agent-handlers.d.ts +8 -7
  6. package/dist/agent-handlers.d.ts.map +1 -1
  7. package/dist/agent-handlers.js.map +1 -1
  8. package/dist/agent-selection-root.d.ts +29 -0
  9. package/dist/agent-selection-root.d.ts.map +1 -0
  10. package/dist/agent-selection-root.js +126 -0
  11. package/dist/agent-selection-root.js.map +1 -0
  12. package/dist/attendance-wiring.d.ts +44 -0
  13. package/dist/attendance-wiring.d.ts.map +1 -0
  14. package/dist/attendance-wiring.js +432 -0
  15. package/dist/attendance-wiring.js.map +1 -0
  16. package/dist/backup-restore-handlers.d.ts +10 -0
  17. package/dist/backup-restore-handlers.d.ts.map +1 -0
  18. package/dist/backup-restore-handlers.js +82 -0
  19. package/dist/backup-restore-handlers.js.map +1 -0
  20. package/dist/boot-agents.d.ts +64 -0
  21. package/dist/boot-agents.d.ts.map +1 -0
  22. package/dist/boot-agents.js +241 -0
  23. package/dist/boot-agents.js.map +1 -0
  24. package/dist/boot-connection-state.d.ts +40 -0
  25. package/dist/boot-connection-state.d.ts.map +1 -0
  26. package/dist/boot-connection-state.js +136 -0
  27. package/dist/boot-connection-state.js.map +1 -0
  28. package/dist/boot-core.d.ts +36 -0
  29. package/dist/boot-core.d.ts.map +1 -0
  30. package/dist/boot-core.js +292 -0
  31. package/dist/boot-core.js.map +1 -0
  32. package/dist/boot-parked-content.d.ts +34 -0
  33. package/dist/boot-parked-content.d.ts.map +1 -0
  34. package/dist/boot-parked-content.js +527 -0
  35. package/dist/boot-parked-content.js.map +1 -0
  36. package/dist/boot-sweeps.d.ts +22 -0
  37. package/dist/boot-sweeps.d.ts.map +1 -0
  38. package/dist/boot-sweeps.js +50 -0
  39. package/dist/boot-sweeps.js.map +1 -0
  40. package/dist/connection-agents.d.ts +16 -0
  41. package/dist/connection-agents.d.ts.map +1 -0
  42. package/dist/connection-agents.js +68 -0
  43. package/dist/connection-agents.js.map +1 -0
  44. package/dist/content-park.d.ts.map +1 -1
  45. package/dist/content-park.js +274 -25
  46. package/dist/content-park.js.map +1 -1
  47. package/dist/daemon-handle.d.ts +57 -0
  48. package/dist/daemon-handle.d.ts.map +1 -0
  49. package/dist/daemon-handle.js +2 -0
  50. package/dist/daemon-handle.js.map +1 -0
  51. package/dist/daemon-status-report.d.ts +40 -0
  52. package/dist/daemon-status-report.d.ts.map +1 -0
  53. package/dist/daemon-status-report.js +78 -0
  54. package/dist/daemon-status-report.js.map +1 -0
  55. package/dist/daemon.d.ts +5 -66
  56. package/dist/daemon.d.ts.map +1 -1
  57. package/dist/daemon.js +332 -4894
  58. package/dist/daemon.js.map +1 -1
  59. package/dist/delivery-open-registry.d.ts +1 -1
  60. package/dist/delivery-open-registry.js +1 -1
  61. package/dist/directory-connect.d.ts +27 -0
  62. package/dist/directory-connect.d.ts.map +1 -0
  63. package/dist/directory-connect.js +98 -0
  64. package/dist/directory-connect.js.map +1 -0
  65. package/dist/disconnect-cleanup.d.ts +45 -0
  66. package/dist/disconnect-cleanup.d.ts.map +1 -0
  67. package/dist/disconnect-cleanup.js +65 -0
  68. package/dist/disconnect-cleanup.js.map +1 -0
  69. package/dist/document-surface.d.ts +32 -0
  70. package/dist/document-surface.d.ts.map +1 -0
  71. package/dist/document-surface.js +150 -0
  72. package/dist/document-surface.js.map +1 -0
  73. package/dist/document-wiring.d.ts +53 -0
  74. package/dist/document-wiring.d.ts.map +1 -0
  75. package/dist/document-wiring.js +323 -0
  76. package/dist/document-wiring.js.map +1 -0
  77. package/dist/held-content.d.ts +7 -3
  78. package/dist/held-content.d.ts.map +1 -1
  79. package/dist/held-content.js.map +1 -1
  80. package/dist/inbound-sessions.d.ts +9 -1
  81. package/dist/inbound-sessions.d.ts.map +1 -1
  82. package/dist/inbound-sessions.js +9 -1
  83. package/dist/inbound-sessions.js.map +1 -1
  84. package/dist/index.d.ts +1 -2
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +1 -2
  87. package/dist/index.js.map +1 -1
  88. package/dist/ipc-surface.d.ts +44 -0
  89. package/dist/ipc-surface.d.ts.map +1 -0
  90. package/dist/ipc-surface.js +106 -0
  91. package/dist/ipc-surface.js.map +1 -0
  92. package/dist/network-directory-node.d.ts +0 -18
  93. package/dist/network-directory-node.d.ts.map +1 -1
  94. package/dist/network-directory-node.js +1 -38
  95. package/dist/network-directory-node.js.map +1 -1
  96. package/dist/notification-handlers.d.ts.map +1 -1
  97. package/dist/notification-handlers.js +2 -0
  98. package/dist/notification-handlers.js.map +1 -1
  99. package/dist/operator-guidance.d.ts +25 -0
  100. package/dist/operator-guidance.d.ts.map +1 -0
  101. package/dist/operator-guidance.js +50 -0
  102. package/dist/operator-guidance.js.map +1 -0
  103. package/dist/park-recovery.d.ts +22 -1
  104. package/dist/park-recovery.d.ts.map +1 -1
  105. package/dist/park-recovery.js.map +1 -1
  106. package/dist/park-refusals.d.ts +147 -0
  107. package/dist/park-refusals.d.ts.map +1 -0
  108. package/dist/park-refusals.js +331 -0
  109. package/dist/park-refusals.js.map +1 -0
  110. package/dist/refusal-notices.d.ts.map +1 -1
  111. package/dist/refusal-notices.js +22 -2
  112. package/dist/refusal-notices.js.map +1 -1
  113. package/dist/refusal-reasons.d.ts +13 -0
  114. package/dist/refusal-reasons.d.ts.map +1 -1
  115. package/dist/refusal-reasons.js +19 -0
  116. package/dist/refusal-reasons.js.map +1 -1
  117. package/dist/register-handler.d.ts +8 -7
  118. package/dist/register-handler.d.ts.map +1 -1
  119. package/dist/register-handler.js.map +1 -1
  120. package/dist/session-content-context.d.ts +144 -0
  121. package/dist/session-content-context.d.ts.map +1 -0
  122. package/dist/session-content-context.js +2 -0
  123. package/dist/session-content-context.js.map +1 -0
  124. package/dist/session-content-handlers.d.ts.map +1 -1
  125. package/dist/session-content-handlers.js +2 -0
  126. package/dist/session-content-handlers.js.map +1 -1
  127. package/dist/session-content-ingest.d.ts +209 -0
  128. package/dist/session-content-ingest.d.ts.map +1 -0
  129. package/dist/session-content-ingest.js +2216 -0
  130. package/dist/session-content-ingest.js.map +1 -0
  131. package/dist/session-content-send.d.ts +184 -0
  132. package/dist/session-content-send.d.ts.map +1 -0
  133. package/dist/session-content-send.js +1300 -0
  134. package/dist/session-content-send.js.map +1 -0
  135. package/dist/session-lifecycle.d.ts +303 -0
  136. package/dist/session-lifecycle.d.ts.map +1 -0
  137. package/dist/session-lifecycle.js +1643 -0
  138. package/dist/session-lifecycle.js.map +1 -0
  139. package/dist/session-node-factory.d.ts +18 -0
  140. package/dist/session-node-factory.d.ts.map +1 -0
  141. package/dist/session-node-factory.js +182 -0
  142. package/dist/session-node-factory.js.map +1 -0
  143. package/dist/session-node-manager.d.ts +68 -766
  144. package/dist/session-node-manager.d.ts.map +1 -1
  145. package/dist/session-node-manager.js +966 -7940
  146. package/dist/session-node-manager.js.map +1 -1
  147. package/dist/session-node-types.d.ts +30 -0
  148. package/dist/session-node-types.d.ts.map +1 -1
  149. package/dist/session-node-types.js.map +1 -1
  150. package/dist/session-notify.d.ts +46 -0
  151. package/dist/session-notify.d.ts.map +1 -0
  152. package/dist/session-notify.js +115 -0
  153. package/dist/session-notify.js.map +1 -0
  154. package/dist/session-read-handlers.d.ts.map +1 -1
  155. package/dist/session-read-handlers.js +0 -1
  156. package/dist/session-read-handlers.js.map +1 -1
  157. package/dist/session-relay.d.ts +361 -0
  158. package/dist/session-relay.d.ts.map +1 -0
  159. package/dist/session-relay.js +1471 -0
  160. package/dist/session-relay.js.map +1 -0
  161. package/dist/session-salt-agreement.d.ts +0 -7
  162. package/dist/session-salt-agreement.d.ts.map +1 -1
  163. package/dist/session-salt-agreement.js +0 -7
  164. package/dist/session-salt-agreement.js.map +1 -1
  165. package/dist/session-salts.d.ts +13 -0
  166. package/dist/session-salts.d.ts.map +1 -1
  167. package/dist/session-salts.js +13 -0
  168. package/dist/session-salts.js.map +1 -1
  169. package/dist/session-seal.d.ts +336 -0
  170. package/dist/session-seal.d.ts.map +1 -0
  171. package/dist/session-seal.js +948 -0
  172. package/dist/session-seal.js.map +1 -0
  173. package/dist/session-views.d.ts +46 -0
  174. package/dist/session-views.d.ts.map +1 -0
  175. package/dist/session-views.js +197 -0
  176. package/dist/session-views.js.map +1 -0
  177. package/dist/signal-handlers.d.ts +66 -0
  178. package/dist/signal-handlers.d.ts.map +1 -0
  179. package/dist/signal-handlers.js +975 -0
  180. package/dist/signal-handlers.js.map +1 -0
  181. package/dist/signaling-wiring.d.ts +100 -0
  182. package/dist/signaling-wiring.d.ts.map +1 -0
  183. package/dist/signaling-wiring.js +392 -0
  184. package/dist/signaling-wiring.js.map +1 -0
  185. package/dist/start-agent.d.ts +64 -0
  186. package/dist/start-agent.d.ts.map +1 -0
  187. package/dist/start-agent.js +136 -0
  188. package/dist/start-agent.js.map +1 -0
  189. package/dist/status-handler.d.ts +46 -0
  190. package/dist/status-handler.d.ts.map +1 -0
  191. package/dist/status-handler.js +58 -0
  192. package/dist/status-handler.js.map +1 -0
  193. package/dist/test-handlers.d.ts +62 -0
  194. package/dist/test-handlers.d.ts.map +1 -0
  195. package/dist/test-handlers.js +239 -0
  196. package/dist/test-handlers.js.map +1 -0
  197. package/dist/types.d.ts +0 -11
  198. package/dist/types.d.ts.map +1 -1
  199. package/dist/types.js +0 -12
  200. package/dist/types.js.map +1 -1
  201. package/dist/unresolved-nodes-report.d.ts +14 -0
  202. package/dist/unresolved-nodes-report.d.ts.map +1 -0
  203. package/dist/unresolved-nodes-report.js +96 -0
  204. package/dist/unresolved-nodes-report.js.map +1 -0
  205. package/dist/who-resolver.d.ts +15 -0
  206. package/dist/who-resolver.d.ts.map +1 -0
  207. package/dist/who-resolver.js +46 -0
  208. package/dist/who-resolver.js.map +1 -0
  209. package/dist/wire-content-hash.d.ts +0 -2
  210. package/dist/wire-content-hash.d.ts.map +1 -1
  211. package/dist/wire-content-hash.js +0 -4
  212. package/dist/wire-content-hash.js.map +1 -1
  213. 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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"}