@cello-protocol/daemon 0.0.52 → 0.0.54
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-handlers.d.ts +57 -0
- package/dist/agent-handlers.d.ts.map +1 -0
- package/dist/agent-handlers.js +360 -0
- package/dist/agent-handlers.js.map +1 -0
- package/dist/agent-id-migration.d.ts.map +1 -1
- package/dist/agent-id-migration.js +1 -0
- package/dist/agent-id-migration.js.map +1 -1
- package/dist/agent-loader.d.ts +3 -4
- package/dist/agent-loader.d.ts.map +1 -1
- package/dist/agent-loader.js +3 -4
- package/dist/agent-loader.js.map +1 -1
- package/dist/cbor-blob-migration.d.ts +8 -0
- package/dist/cbor-blob-migration.d.ts.map +1 -0
- package/dist/cbor-blob-migration.js +115 -0
- package/dist/cbor-blob-migration.js.map +1 -0
- package/dist/close-session-handler.d.ts +41 -0
- package/dist/close-session-handler.d.ts.map +1 -0
- package/dist/close-session-handler.js +399 -0
- package/dist/close-session-handler.js.map +1 -0
- package/dist/consortium-bootstrap.d.ts +83 -0
- package/dist/consortium-bootstrap.d.ts.map +1 -0
- package/dist/consortium-bootstrap.js +120 -0
- package/dist/consortium-bootstrap.js.map +1 -0
- package/dist/contact-handlers.d.ts +56 -0
- package/dist/contact-handlers.d.ts.map +1 -0
- package/dist/contact-handlers.js +303 -0
- package/dist/contact-handlers.js.map +1 -0
- package/dist/content-park-client.d.ts.map +1 -1
- package/dist/content-park-client.js +7 -7
- package/dist/content-park-client.js.map +1 -1
- package/dist/content-park.d.ts +15 -0
- package/dist/content-park.d.ts.map +1 -0
- package/dist/content-park.js +227 -0
- package/dist/content-park.js.map +1 -0
- package/dist/daemon.d.ts +0 -11
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +407 -4552
- package/dist/daemon.js.map +1 -1
- package/dist/db-identity-store.d.ts +31 -35
- package/dist/db-identity-store.d.ts.map +1 -1
- package/dist/db-identity-store.js +62 -66
- package/dist/db-identity-store.js.map +1 -1
- package/dist/directory-bootstrap.d.ts +27 -31
- package/dist/directory-bootstrap.d.ts.map +1 -1
- package/dist/directory-bootstrap.js +18 -21
- package/dist/directory-bootstrap.js.map +1 -1
- package/dist/frame-values.d.ts +3 -0
- package/dist/frame-values.d.ts.map +1 -0
- package/dist/frame-values.js +73 -0
- package/dist/frame-values.js.map +1 -0
- package/dist/identity-migration.d.ts +8 -8
- package/dist/identity-migration.d.ts.map +1 -1
- package/dist/identity-migration.js +30 -31
- package/dist/identity-migration.js.map +1 -1
- package/dist/inbound-seal-request.d.ts +17 -0
- package/dist/inbound-seal-request.d.ts.map +1 -0
- package/dist/inbound-seal-request.js +155 -0
- package/dist/inbound-seal-request.js.map +1 -0
- package/dist/inbound-sessions.d.ts +54 -0
- package/dist/inbound-sessions.d.ts.map +1 -0
- package/dist/inbound-sessions.js +603 -0
- package/dist/inbound-sessions.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -10
- package/dist/index.js.map +1 -1
- package/dist/initiate-session-handler.d.ts +22 -0
- package/dist/initiate-session-handler.d.ts.map +1 -0
- package/dist/initiate-session-handler.js +150 -0
- package/dist/initiate-session-handler.js.map +1 -0
- package/dist/network-directory-node.d.ts +6 -6
- package/dist/network-directory-node.d.ts.map +1 -1
- package/dist/network-directory-node.js +29 -28
- package/dist/network-directory-node.js.map +1 -1
- package/dist/notification-handlers.d.ts +32 -0
- package/dist/notification-handlers.d.ts.map +1 -0
- package/dist/notification-handlers.js +73 -0
- package/dist/notification-handlers.js.map +1 -0
- package/dist/outbound-sessions.d.ts +54 -0
- package/dist/outbound-sessions.d.ts.map +1 -0
- package/dist/outbound-sessions.js +429 -0
- package/dist/outbound-sessions.js.map +1 -0
- package/dist/park-envelope.d.ts.map +1 -1
- package/dist/park-envelope.js +3 -2
- package/dist/park-envelope.js.map +1 -1
- package/dist/register-handler.d.ts +35 -0
- package/dist/register-handler.d.ts.map +1 -0
- package/dist/register-handler.js +179 -0
- package/dist/register-handler.js.map +1 -0
- package/dist/registration-manager.d.ts.map +1 -1
- package/dist/registration-manager.js +3 -6
- package/dist/registration-manager.js.map +1 -1
- package/dist/relay-receipt-store.d.ts.map +1 -1
- package/dist/relay-receipt-store.js +1 -2
- package/dist/relay-receipt-store.js.map +1 -1
- package/dist/retry-queue.d.ts +18 -60
- package/dist/retry-queue.d.ts.map +1 -1
- package/dist/retry-queue.js +46 -97
- package/dist/retry-queue.js.map +1 -1
- package/dist/seal-coordinator.d.ts +47 -0
- package/dist/seal-coordinator.d.ts.map +1 -0
- package/dist/seal-coordinator.js +551 -0
- package/dist/seal-coordinator.js.map +1 -0
- package/dist/seal-flows.d.ts +50 -0
- package/dist/seal-flows.d.ts.map +1 -0
- package/dist/seal-flows.js +454 -0
- package/dist/seal-flows.js.map +1 -0
- package/dist/seal-frontier-verify.d.ts +9 -9
- package/dist/seal-frontier-verify.d.ts.map +1 -1
- package/dist/seal-frontier-verify.js +11 -12
- package/dist/seal-frontier-verify.js.map +1 -1
- package/dist/seal-leaf.d.ts +58 -0
- package/dist/seal-leaf.d.ts.map +1 -0
- package/dist/seal-leaf.js +111 -0
- package/dist/seal-leaf.js.map +1 -0
- package/dist/seal-receipt-upgrade.d.ts +1 -1
- package/dist/seal-receipt-upgrade.js +1 -1
- package/dist/seal-upgrade.d.ts +10 -10
- package/dist/seal-upgrade.d.ts.map +1 -1
- package/dist/seal-upgrade.js +23 -24
- package/dist/seal-upgrade.js.map +1 -1
- package/dist/session-assignment-parser.d.ts +16 -19
- package/dist/session-assignment-parser.d.ts.map +1 -1
- package/dist/session-assignment-parser.js +30 -29
- package/dist/session-assignment-parser.js.map +1 -1
- package/dist/session-ceremony.d.ts +3 -6
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +7 -9
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-content-handlers.d.ts +25 -0
- package/dist/session-content-handlers.d.ts.map +1 -0
- package/dist/session-content-handlers.js +436 -0
- package/dist/session-content-handlers.js.map +1 -0
- package/dist/session-name.d.ts +35 -0
- package/dist/session-name.d.ts.map +1 -0
- package/dist/session-name.js +60 -0
- package/dist/session-name.js.map +1 -0
- package/dist/session-node-manager.d.ts +26 -62
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +115 -92
- package/dist/session-node-manager.js.map +1 -1
- package/dist/session-read-handlers.d.ts +38 -0
- package/dist/session-read-handlers.d.ts.map +1 -0
- package/dist/session-read-handlers.js +233 -0
- package/dist/session-read-handlers.js.map +1 -0
- package/dist/session-relay-client.d.ts +8 -8
- package/dist/session-relay-client.d.ts.map +1 -1
- package/dist/session-relay-client.js +44 -46
- package/dist/session-relay-client.js.map +1 -1
- package/dist/session-tree.d.ts +11 -23
- package/dist/session-tree.d.ts.map +1 -1
- package/dist/session-tree.js +14 -26
- package/dist/session-tree.js.map +1 -1
- package/dist/signaling-connect.d.ts +2 -5
- package/dist/signaling-connect.d.ts.map +1 -1
- package/dist/signaling-connect.js +7 -12
- package/dist/signaling-connect.js.map +1 -1
- package/dist/sqlcipher-db.d.ts +18 -19
- package/dist/sqlcipher-db.d.ts.map +1 -1
- package/dist/sqlcipher-db.js +30 -30
- package/dist/sqlcipher-db.js.map +1 -1
- package/dist/telegram-doorbell.d.ts +38 -0
- package/dist/telegram-doorbell.d.ts.map +1 -0
- package/dist/telegram-doorbell.js +139 -0
- package/dist/telegram-doorbell.js.map +1 -0
- package/dist/types.d.ts +86 -79
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/dist/vocabulary.d.ts +30 -30
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +41 -49
- package/dist/vocabulary.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cello_send and cello_receive — the content path.
|
|
3
|
+
*
|
|
4
|
+
* The two biggest handlers in the daemon, and the two I had put on a DO-NOT-CUT list. That list was
|
|
5
|
+
* written before anything had been measured, and it was wrong: these close over eleven things, not
|
|
6
|
+
* the whole daemon. They are listed below, which is the point of the move.
|
|
7
|
+
*
|
|
8
|
+
* They stay TOGETHER because they are two halves of one state machine — the read cursor. cello_send
|
|
9
|
+
* appends to the daemon-owned tree; cello_receive advances the per-connection cursor and the durable
|
|
10
|
+
* watermark. Splitting them would put the cursor's writer and its reader in different modules.
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
13
|
+
import { MAX_CONTENT_BYTES } from "@cello-protocol/protocol-types";
|
|
14
|
+
import { TIER } from "./contacts-tier-migration.js";
|
|
15
|
+
import { GATEWAY_UNAVAILABLE, GOVERNANCE_TIMEOUT } from "@cello-protocol/gateway";
|
|
16
|
+
export function registerSessionContentHandlers(deps) {
|
|
17
|
+
const { handlers, logger, sessionNodeManager, securityGateway, retryQueue, getConnState, resolveCurrentAgent, NO_CURRENT_AGENT_RESPONSE, getConnectionCursor, advanceConnectionCursor, safeCursorAdvance, clearTelegramRung, } = deps;
|
|
18
|
+
// ─── CELLO-M7-DAEMON-004: cello_send (live send + daemon-owned tree append) ──
|
|
19
|
+
handlers.set("cello_send", async (params, connectionId) => {
|
|
20
|
+
const connState = getConnState(connectionId);
|
|
21
|
+
// CC-3 / M8C-AUTOSTART-1 F18: explicit { agent } > current > sole online agent.
|
|
22
|
+
const agentName = resolveCurrentAgent(connState, params?.agent);
|
|
23
|
+
if (!agentName)
|
|
24
|
+
return NO_CURRENT_AGENT_RESPONSE;
|
|
25
|
+
// round-2 BLOCKING: read the snake_case public field cello-mcp.ts actually sends.
|
|
26
|
+
const sessionId = params?.session_id;
|
|
27
|
+
const contentStr = typeof params?.content === "string" ? params.content : undefined;
|
|
28
|
+
if (!sessionId || contentStr === undefined) {
|
|
29
|
+
return { ok: false, reason: "missing_params", guidance: "Provide 'session_id' (hex) and 'content' (string) parameters." };
|
|
30
|
+
}
|
|
31
|
+
// DOD-LOOP-1: the (agent, session_id) lookup is itself the ownership scope.
|
|
32
|
+
const record = sessionNodeManager.getSessionRecord(agentName, sessionId);
|
|
33
|
+
if (!record) {
|
|
34
|
+
return { ok: false, reason: "session_not_found", guidance: "No session found with this ID. Check cello_sessions for active sessions." };
|
|
35
|
+
}
|
|
36
|
+
if (record.agent_name !== agentName) {
|
|
37
|
+
return { ok: false, reason: "session_not_owned", guidance: "This session belongs to a different agent. Call cello_use_agent to switch to the agent that owns it, then retry." };
|
|
38
|
+
}
|
|
39
|
+
if (record.status !== "active") {
|
|
40
|
+
return { ok: false, reason: "session_not_active", guidance: `Session is '${record.status}', not active. Content can only be sent on an active session. If it is interrupted, call cello_close_session to seal it.` };
|
|
41
|
+
}
|
|
42
|
+
// M8C-CURSOR-1: read-before-write gate. current_seq is the tree's highest leaf index
|
|
43
|
+
// (message_count is kept in sync with leafCount on every append, both directions — DAEMON-004
|
|
44
|
+
// finding #2), so it reflects EVERY message in the session regardless of which connection sent
|
|
45
|
+
// or received it. If this connection hasn't read up to current_seq (e.g. a second attended
|
|
46
|
+
// session on the same agent that hasn't polled since the other connection's last send), refuse
|
|
47
|
+
// rather than let it send blind — the WhatsApp-group-chat model. Runs BEFORE the
|
|
48
|
+
// governance-decisions parsing below: an access-control gate must short-circuit before any
|
|
49
|
+
// unrelated prep work for a send that may not be allowed to proceed at all.
|
|
50
|
+
//
|
|
51
|
+
// The gate consults TWO authorities and passes if EITHER says the caller is caught up:
|
|
52
|
+
//
|
|
53
|
+
// 1. the connection cursor (in-memory, per-connection) — satisfied by a long-lived client
|
|
54
|
+
// like the MCP shim, which holds one socket for the whole session.
|
|
55
|
+
//
|
|
56
|
+
// 2. the persisted per-(agent, session) read watermark. A STATELESS client CANNOT satisfy
|
|
57
|
+
// authority 1: the `cello` CLI runs a fresh process, and therefore a fresh connection, per
|
|
58
|
+
// command, so it always presents cursor -1. Without the watermark, every CLI send is
|
|
59
|
+
// refused forever once the counterparty has spoken — as is every send from a RECONNECTING
|
|
60
|
+
// MCP client, whose new connectionId also resets the cursor to -1. Do not remove it.
|
|
61
|
+
//
|
|
62
|
+
// What the gate protects, precisely: an agent must never reply to COUNTERPARTY content nobody
|
|
63
|
+
// on its side has seen. That guarantee is durable — it survives the socket.
|
|
64
|
+
//
|
|
65
|
+
// What it deliberately does NOT protect: a message this agent SENT from a different local
|
|
66
|
+
// connection does not block. Two attended windows on one agent do not gate each other, because
|
|
67
|
+
// locally-authored content is not "unread counterparty content" — the AGENT wrote it. The
|
|
68
|
+
// principal is the agent, not the socket, and the daemon cannot referee which of an operator's
|
|
69
|
+
// own windows a human is looking at. This is deliberate; do not "fix" it.
|
|
70
|
+
const currentSeq = record.message_count - 1;
|
|
71
|
+
const connectionCursor = getConnectionCursor(connectionId, sessionId);
|
|
72
|
+
const unreadReceived = sessionNodeManager.getUnreadReceivedCount(agentName, sessionId);
|
|
73
|
+
const caughtUp = connectionCursor >= currentSeq || unreadReceived === 0;
|
|
74
|
+
if (!caughtUp) {
|
|
75
|
+
// M8C-CURSOR-1 (reviewer MEDIUM fix): every sibling rejection in this handler logs; this
|
|
76
|
+
// gate must too — a security-relevant control-flow path with no observability is a gap.
|
|
77
|
+
// Both authorities are logged, so the next reader can tell WHICH one refused.
|
|
78
|
+
logger.warn("session.send.blocked", {
|
|
79
|
+
sessionId,
|
|
80
|
+
currentSeq,
|
|
81
|
+
lastReadSeq: connectionCursor,
|
|
82
|
+
unreadReceived,
|
|
83
|
+
connectionId,
|
|
84
|
+
agentName,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
reason: "session_not_current",
|
|
89
|
+
current_seq: currentSeq,
|
|
90
|
+
last_read_seq: connectionCursor,
|
|
91
|
+
unread_received: unreadReceived,
|
|
92
|
+
// DOD-ONBOARD-HELP-1 §5: say what is wrong and what to do, in that order, in plain words.
|
|
93
|
+
// The old text opened with the internal noun ("this agent hasn't read…"); a refused send is
|
|
94
|
+
// the most common wall a new operator hits, so it leads with the COUNT and the FIX. The
|
|
95
|
+
// session id is interpolated so the remedy is copy-pasteable, not a template to fill in.
|
|
96
|
+
// Rendered per surface at the IPC boundary — a CLI caller sees `cello receive <id>`.
|
|
97
|
+
guidance: `${unreadReceived} unread message(s) — run cello_receive ${sessionId} first to read them, then send again. (Or cello_transcript ${sessionId} for the whole conversation.) You are blocked from replying to something you haven't read.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// M9-FEED-001 §6: the agent's governance re-send decisions, keyed by the flagId a prior `warn`
|
|
101
|
+
// returned. Optional; validated shape only (the gateway re-scans + applies them, INV-4). A
|
|
102
|
+
// malformed map is ignored rather than failing the send (the gateway will just re-warn).
|
|
103
|
+
const rawDecisions = params?.governance_decisions;
|
|
104
|
+
let governanceDecisions;
|
|
105
|
+
if (rawDecisions && typeof rawDecisions === "object" && !Array.isArray(rawDecisions)) {
|
|
106
|
+
const valid = {};
|
|
107
|
+
for (const [k, v] of Object.entries(rawDecisions)) {
|
|
108
|
+
if (v === "redact" || v === "allow_once" || v === "allow_always")
|
|
109
|
+
valid[k] = v;
|
|
110
|
+
}
|
|
111
|
+
if (Object.keys(valid).length > 0)
|
|
112
|
+
governanceDecisions = valid;
|
|
113
|
+
}
|
|
114
|
+
const correlationId = randomUUID();
|
|
115
|
+
const contentBytes = new TextEncoder().encode(contentStr);
|
|
116
|
+
// CELLO-M7-MSG-001 (AC-013/AC-018/AC-021): enforce the 1 MB application content cap
|
|
117
|
+
// BEFORE any transmission or hash/leaf production. This replaces the silent oversize
|
|
118
|
+
// decode-failure → desync: the send is rejected with a distinct, diagnosable reason
|
|
119
|
+
// and actionable guidance; no content frame is transmitted, no leaf is appended, and
|
|
120
|
+
// the session stays usable.
|
|
121
|
+
if (contentBytes.length > MAX_CONTENT_BYTES) {
|
|
122
|
+
logger.warn("content.rejected.too_large", {
|
|
123
|
+
sessionId,
|
|
124
|
+
contentSize: contentBytes.length,
|
|
125
|
+
cap: MAX_CONTENT_BYTES,
|
|
126
|
+
correlationId,
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
reason: "content_too_large",
|
|
131
|
+
guidance: `This message is ${contentBytes.length} bytes, over the ${MAX_CONTENT_BYTES}-byte (1 MB) per-message content cap. Split it into multiple messages each under the cap, or use the large-object/file transfer path for large payloads (not cello_send). Nothing was sent and the session is still active — retry with smaller content.`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const recipientPubkey = record.counterparty_pubkey;
|
|
135
|
+
// M9 outbound screening seam (INV-5/SI-001). Screen BEFORE anything reaches the wire. The
|
|
136
|
+
// gateway verdict drives the four cello_send outcomes (M9-FEED-001): block / warn → NOT sent;
|
|
137
|
+
// allow → sent as-is; redact → sent in ALTERED form. A configured-but-unreachable gateway fails
|
|
138
|
+
// closed (block, gateway_unavailable), so a screening outage can never let content out ungated.
|
|
139
|
+
const outboundVerdict = await securityGateway.screenOutbound(contentBytes, {
|
|
140
|
+
direction: "outbound",
|
|
141
|
+
agentName: record.agent_name,
|
|
142
|
+
sessionId,
|
|
143
|
+
correlationId,
|
|
144
|
+
...(governanceDecisions !== undefined ? { governanceDecisions } : {}),
|
|
145
|
+
});
|
|
146
|
+
if (outboundVerdict.disposition === "block") {
|
|
147
|
+
if (outboundVerdict.reason === GOVERNANCE_TIMEOUT) {
|
|
148
|
+
logger.error("security.gateway.timeout", { sessionId, reason: outboundVerdict.reason, correlationId });
|
|
149
|
+
}
|
|
150
|
+
else if (outboundVerdict.reason === GATEWAY_UNAVAILABLE) {
|
|
151
|
+
logger.error("security.gateway.unavailable", { direction: "outbound", reason: outboundVerdict.reason, correlationId });
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
logger.info("security.verdict.returned", { disposition: "block", sessionId, reason: outboundVerdict.reason, correlationId });
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
reason: outboundVerdict.reason ?? "blocked_by_governance",
|
|
159
|
+
guidance: outboundVerdict.guidance ??
|
|
160
|
+
"This message was blocked by the security gateway and was NOT sent. The session is still active.",
|
|
161
|
+
blocks: (outboundVerdict.events ?? []).filter((e) => e.disposition === "block"),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (outboundVerdict.disposition === "warn") {
|
|
165
|
+
logger.info("security.verdict.returned", { disposition: "warn", sessionId, correlationId });
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
reason: "governance_warn",
|
|
169
|
+
guidance: outboundVerdict.guidance ??
|
|
170
|
+
"This message was held for a governance decision and was NOT sent. Re-send the same content with a " +
|
|
171
|
+
"governance_decisions map ({flagId: redact | allow_once | allow_always}) to resolve each flagged item.",
|
|
172
|
+
flags: (outboundVerdict.events ?? []).filter((e) => e.disposition === "warn"),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// FAIL-CLOSED (code-review MED): a `redact` verdict MUST carry the redacted content. If it ever
|
|
176
|
+
// arrives without it, sending the original `contentBytes` would leak the pre-redaction draft — the
|
|
177
|
+
// one place M9 could fail OPEN. Treat it as a block, never an allow-original. (Unreachable today:
|
|
178
|
+
// the gateway always includes content on redact; this is the defensive floor.)
|
|
179
|
+
if (outboundVerdict.disposition === "redact" && outboundVerdict.content === undefined) {
|
|
180
|
+
logger.error("security.verdict.redact_without_content", { sessionId, correlationId });
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
reason: "redact_without_content",
|
|
184
|
+
guidance: "The security gateway returned a redact verdict without the redacted content. To avoid " +
|
|
185
|
+
"leaking the original, nothing was sent. This is a gateway fault — check the gateway logs and retry.",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// allow or redact → send. On redact the ALTERED bytes are what go on the wire AND what the leaf
|
|
189
|
+
// hash binds — the transcript records what was actually sent, not the pre-redaction draft.
|
|
190
|
+
const modified = outboundVerdict.disposition === "redact" && outboundVerdict.content !== undefined;
|
|
191
|
+
const sendBytes = modified ? new Uint8Array(outboundVerdict.content) : contentBytes;
|
|
192
|
+
const contentHash = createHash("sha256").update(new Uint8Array([0x00])).update(sendBytes).digest();
|
|
193
|
+
const contentHashHex = Buffer.from(contentHash).toString("hex");
|
|
194
|
+
const sendResult = await sessionNodeManager.sendContent(record.agent_name, sessionId, sendBytes, new Uint8Array(contentHash), correlationId);
|
|
195
|
+
if (!sendResult.ok) {
|
|
196
|
+
// DB-001 / dead-channel contract: never silently drop, never desync. Preserve
|
|
197
|
+
// the content in the durable retry_queue so it is retried on reconnect, and
|
|
198
|
+
// surface a named, diagnosable failure.
|
|
199
|
+
const nonce = randomUUID();
|
|
200
|
+
try {
|
|
201
|
+
retryQueue.enqueue(sessionId, new TextEncoder().encode(nonce), sendBytes);
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
logger.error("session.content.queue.failed", {
|
|
205
|
+
sessionId,
|
|
206
|
+
error: err instanceof Error ? err.message : String(err),
|
|
207
|
+
correlationId,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
logger.warn("session.content.send.failed", {
|
|
211
|
+
sessionId,
|
|
212
|
+
recipientPubkey,
|
|
213
|
+
reason: sendResult.reason,
|
|
214
|
+
errorMessage: sendResult.error,
|
|
215
|
+
correlationId,
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
reason: sendResult.reason,
|
|
220
|
+
guidance: "The content could not be delivered over the session stream right now. It has been queued in the durable retry queue and will be retried when the counterparty reconnects. The session remains usable — check cello_status for the counterparty's status.",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// Delivered directly OR dispatched to relay (DOD-LEAVEMSG-1) — either way the content is now
|
|
224
|
+
// part of the daemon-owned tree: the relay witness (R1) already assigned it a sequence before
|
|
225
|
+
// direct delivery was even attempted, so a parked message occupies the SAME leaf position it
|
|
226
|
+
// would have taken if delivered live. Append once, for both outcomes.
|
|
227
|
+
const { leafIndex, newRootHex } = sessionNodeManager.appendSessionLeaf(record.agent_name, sessionId, "msg", contentHashHex, correlationId);
|
|
228
|
+
// DOD-LOG-1: persist the readable SENT plaintext to the durable transcript, keyed by the
|
|
229
|
+
// canonical leaf sequence so it joins the committed hash chain (survives restart).
|
|
230
|
+
// M9 merge fix: use sendBytes (the ALTERED bytes on a redact verdict), never the pre-redaction
|
|
231
|
+
// contentBytes — the leaf hash above already binds sendBytes; the transcript must match what
|
|
232
|
+
// actually went on the wire, not the pre-redaction draft (M9's own stated seam invariant).
|
|
233
|
+
sessionNodeManager.recordTranscriptMessage(record.agent_name, sessionId, leafIndex, "sent", sendBytes, correlationId);
|
|
234
|
+
// M8C-CURSOR-1: the sender authored this leaf — advance ITS OWN cursor so it doesn't get
|
|
235
|
+
// blocked by session_not_current on its own just-sent message.
|
|
236
|
+
advanceConnectionCursor(connectionId, sessionId, leafIndex);
|
|
237
|
+
void newRootHex;
|
|
238
|
+
// CC-1 (2026-07-07): operator engagement promotes the counterparty to a known contact. A
|
|
239
|
+
// committed reply — past the read-before-write gate, content now on the wire AND in the tree —
|
|
240
|
+
// IS the operator choosing to trust this sender; the inbound-accept path deliberately no longer
|
|
241
|
+
// auto-adds (that defeated screening + anti-spam). For an OUTBOUND session the counterparty is
|
|
242
|
+
// already a contact (cello_initiate_session added it), so this is an idempotent no-op there; it
|
|
243
|
+
// matters for inbound-originated sessions, where the reply is the trust signal. addContact is
|
|
244
|
+
// INSERT OR IGNORE — it never refreshes added_at. DOD-TIER-4 AC3: engaging (a committed reply into
|
|
245
|
+
// an inbound session I accepted) makes the counterparty KNOWN, provenance 'accepted'. For an
|
|
246
|
+
// OUTBOUND session the row already exists ('initiated', KNOWN) and INSERT OR IGNORE leaves it
|
|
247
|
+
// untouched — 'initiated' correctly wins there.
|
|
248
|
+
sessionNodeManager.addContact(record.agent_name, recipientPubkey, undefined, "accepted", TIER.KNOWN);
|
|
249
|
+
if (modified) {
|
|
250
|
+
logger.info("security.verdict.returned", { disposition: "redact", sessionId, sequenceNumber: leafIndex, correlationId });
|
|
251
|
+
}
|
|
252
|
+
if (!sendResult.delivered) {
|
|
253
|
+
// DOD-LEAVEMSG-1 (sender half): direct delivery failed but the sealed, hashed content was
|
|
254
|
+
// successfully deposited at the relay (pickup_queue) — this is a SUCCESS outcome, not a
|
|
255
|
+
// failure. The recipient's daemon pulls it via RELAYWAKE on next reconnect. Reporting this
|
|
256
|
+
// as ok:false (the pre-LEAVEMSG-1 behavior) misrepresented an in-flight message as lost.
|
|
257
|
+
logger.info("session.content.dispatched_to_relay", {
|
|
258
|
+
sessionId,
|
|
259
|
+
recipientPubkey,
|
|
260
|
+
contentHashHex,
|
|
261
|
+
sequenceNumber: leafIndex,
|
|
262
|
+
correlationId,
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
ok: true,
|
|
266
|
+
sequence_number: leafIndex,
|
|
267
|
+
delivered: false,
|
|
268
|
+
reason: "dispatched_to_relay",
|
|
269
|
+
modified,
|
|
270
|
+
guidance: "The counterparty is not directly reachable right now, so this message was sealed and dispatched to relay store-and-forward. It will be delivered the next time the counterparty's daemon reconnects — no further action is needed.",
|
|
271
|
+
...(modified ? { transformations: (outboundVerdict.events ?? []).filter((e) => e.disposition === "redact") } : {}),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
logger.info("session.content.sent", {
|
|
275
|
+
sessionId,
|
|
276
|
+
recipientPubkey,
|
|
277
|
+
contentHashHex,
|
|
278
|
+
sequenceNumber: leafIndex,
|
|
279
|
+
correlationId,
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
ok: true,
|
|
283
|
+
sequence_number: leafIndex,
|
|
284
|
+
delivered: true,
|
|
285
|
+
modified,
|
|
286
|
+
// On a redact, tell the agent exactly what was transformed (the §6 sender-side surface).
|
|
287
|
+
...(modified ? { transformations: (outboundVerdict.events ?? []).filter((e) => e.disposition === "redact") } : {}),
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
// ─── CELLO-M7-DAEMON-004 / F1-a: cello_receive (BLOCKING, session-scoped) ────────
|
|
291
|
+
// F1-a fix: the daemon port had dropped the blocking receive (the handler was a
|
|
292
|
+
// non-blocking buf.shift). It now BLOCKS up to timeout_ms, polling the received-content
|
|
293
|
+
// buffer — resolved by the next arrival, a terminal seal answer (F1-b), or timeout. This is
|
|
294
|
+
// the "blocking receive variant" the guidance names.
|
|
295
|
+
//
|
|
296
|
+
// ONE name (DOD-ONBOARD-HELP-1, Andre 2026-07-11). This was briefly registered under
|
|
297
|
+
// `cello_receive_session` as well — one implementation behind two names. That alias is DELETED:
|
|
298
|
+
// it accepted or joined nothing (inbound sessions are auto-accepted by the standing receiver),
|
|
299
|
+
// so it was a second name for a step that does not exist, and its help said otherwise.
|
|
300
|
+
const RECEIVE_DEFAULT_TIMEOUT_MS = 30000; // matches the cello-mcp shim's documented default
|
|
301
|
+
const handleReceive = async (params, connectionId) => {
|
|
302
|
+
const connState = getConnState(connectionId);
|
|
303
|
+
// CC-3 / M8C-AUTOSTART-1 F18: explicit { agent } > current > sole online agent.
|
|
304
|
+
const agentName = resolveCurrentAgent(connState, params?.agent);
|
|
305
|
+
if (!agentName)
|
|
306
|
+
return NO_CURRENT_AGENT_RESPONSE;
|
|
307
|
+
// Read the snake_case public field cello-mcp.ts actually sends.
|
|
308
|
+
const sessionId = params?.session_id;
|
|
309
|
+
if (!sessionId) {
|
|
310
|
+
return { ok: false, reason: "missing_params", guidance: "Provide 'session_id' (hex) to receive content for a specific session." };
|
|
311
|
+
}
|
|
312
|
+
const record = sessionNodeManager.getSessionRecord(agentName, sessionId);
|
|
313
|
+
// DOD-UNREAD-1 D4b: a TRANSCRIPT-ONLY session — received rows exist but no sessions row (the
|
|
314
|
+
// pre-D3/D4a phantom residue: the counterparty's reply landed while this side refused the
|
|
315
|
+
// session). Those rows are counted unread by getUnreadSummary, so cello_receive MUST be able
|
|
316
|
+
// to read them or the badge can never clear. Reading is a transcript operation: the since_seq
|
|
317
|
+
// catch-up below works from the durable transcript alone. Only a session with NEITHER a row
|
|
318
|
+
// NOR transcript rows is truly not found.
|
|
319
|
+
let transcriptOnly = false;
|
|
320
|
+
if (!record) {
|
|
321
|
+
if (sessionNodeManager.readTranscript(agentName, sessionId).messages.length === 0) {
|
|
322
|
+
return { ok: false, reason: "session_not_found", guidance: "No session found with this ID. Check cello_sessions." };
|
|
323
|
+
}
|
|
324
|
+
transcriptOnly = true;
|
|
325
|
+
}
|
|
326
|
+
// (D4 review F3: the old `record.agent_name !== agentName` → session_not_owned branch was dead
|
|
327
|
+
// code — getSessionRecord is keyed by (agent_name, session_id), so a record can never belong to
|
|
328
|
+
// a different agent. A cross-agent session id simply has no record under this agent.)
|
|
329
|
+
// M8C-SINCESEQ-1: stateless catch-up. When since_seq is provided, return a BATCH of received
|
|
330
|
+
// transcript messages with sequence > since_seq (durable transcript, not the ephemeral buffer —
|
|
331
|
+
// so concurrent arrivals don't shift what a given since_seq returns; no replay race). Replaces
|
|
332
|
+
// the cello_get_transcript workaround for away-then-return. Received-direction only (the messages
|
|
333
|
+
// you'd have gotten live). Advances the read watermark (delivery marks read — clears INBOX
|
|
334
|
+
// unread). A distinct early branch: the plain (no since_seq) receive is entirely unchanged.
|
|
335
|
+
const rawSince = params?.since_seq;
|
|
336
|
+
if (typeof rawSince === "number" && Number.isFinite(rawSince)) {
|
|
337
|
+
const sinceSeq = rawSince;
|
|
338
|
+
// D4b AC2: a transcript-only session has no record to attribute from — `from` is null,
|
|
339
|
+
// NEVER the string "unknown" (the transcript stores no counterparty; don't invent one).
|
|
340
|
+
const from = record ? record.counterparty_pubkey : null;
|
|
341
|
+
const { messages } = sessionNodeManager.readTranscript(agentName, sessionId);
|
|
342
|
+
const received = messages.filter((m) => m.direction === "received" && m.sequence > sinceSeq);
|
|
343
|
+
if (received.length > 0) {
|
|
344
|
+
// readTranscript is ordered by sequence ASC → the last is the max.
|
|
345
|
+
const maxSeq = received[received.length - 1].sequence;
|
|
346
|
+
sessionNodeManager.advanceLastDeliveredSeq(agentName, sessionId, maxSeq);
|
|
347
|
+
clearTelegramRung(agentName, sessionId); // M8C-TGDOOR-1: read clears the ring
|
|
348
|
+
}
|
|
349
|
+
// M8C-CURSOR-1 (reviewer HIGH fix): only advance through the CONTIGUOUS run this batch
|
|
350
|
+
// actually delivered — if a sent leaf from another local connection sits in a gap, this
|
|
351
|
+
// correctly refuses to advance past it (cello_get_transcript is still required to catch up).
|
|
352
|
+
safeCursorAdvance(connectionId, sessionId, new Set(received.map((m) => m.sequence)));
|
|
353
|
+
logger.info("session.receive.since_seq", { sessionId, agentName, since_seq: sinceSeq, count: received.length });
|
|
354
|
+
return {
|
|
355
|
+
ok: true,
|
|
356
|
+
since_seq: sinceSeq,
|
|
357
|
+
count: received.length,
|
|
358
|
+
messages: received.map((m) => ({ sequence: m.sequence, content: m.text, from })),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// D4b AC3: the plain (blocking) receive waits on a LIVE session's buffer — a transcript-only
|
|
362
|
+
// session has no live node and nothing will ever arrive. Waiting to a null timeout would be
|
|
363
|
+
// misleading and session_not_found would be a lie (the transcript exists). A distinct reason
|
|
364
|
+
// points the caller at the read that works.
|
|
365
|
+
if (transcriptOnly) {
|
|
366
|
+
return {
|
|
367
|
+
ok: false,
|
|
368
|
+
reason: "session_not_live",
|
|
369
|
+
guidance: "This session exists only as a durable transcript (no live session — it was never established or predates this daemon). Read it with cello_receive { since_seq } (e.g. since_seq: -1 for everything) or cello_transcript.",
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const rawTimeout = params?.timeout_ms;
|
|
373
|
+
const timeoutMs = typeof rawTimeout === "number" && Number.isFinite(rawTimeout) && rawTimeout >= 0
|
|
374
|
+
? rawTimeout
|
|
375
|
+
: RECEIVE_DEFAULT_TIMEOUT_MS;
|
|
376
|
+
const deadline = Date.now() + timeoutMs;
|
|
377
|
+
for (;;) {
|
|
378
|
+
// 1) Deliverable content wins — drain one buffered message per call (FIFO).
|
|
379
|
+
const entry = sessionNodeManager.takeReceivedContent(agentName, sessionId);
|
|
380
|
+
if (entry) {
|
|
381
|
+
// M8C-INBOX-1 (N3): delivery marks read — advance the persisted read watermark so this
|
|
382
|
+
// message no longer counts as unread in cello_check_notifications. Monotonic (never lowers).
|
|
383
|
+
sessionNodeManager.advanceLastDeliveredSeq(agentName, sessionId, entry.sequenceNumber);
|
|
384
|
+
clearTelegramRung(agentName, sessionId); // M8C-TGDOOR-1: read clears the ring
|
|
385
|
+
// M8C-CURSOR-1 (reviewer HIGH fix): a single delivered message only proves THIS sequence
|
|
386
|
+
// was read — safeCursorAdvance refuses to vault past a gap (e.g. an unread sent leaf from
|
|
387
|
+
// another local connection) even though this specific sequence number is now known.
|
|
388
|
+
safeCursorAdvance(connectionId, sessionId, new Set([entry.sequenceNumber]));
|
|
389
|
+
return {
|
|
390
|
+
ok: true,
|
|
391
|
+
content: Buffer.from(entry.contentHex, "hex").toString("utf8"),
|
|
392
|
+
sessionId,
|
|
393
|
+
sequence_number: entry.sequenceNumber,
|
|
394
|
+
senderPubkey: entry.senderPubkey,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// 2) F1-b: the session sealed while we were (or before we started) waiting — return the
|
|
398
|
+
// terminal answer instead of hanging to timeout. unread_count reports messages that
|
|
399
|
+
// were evicted unread (still durable — recoverable via cello_get_transcript).
|
|
400
|
+
const terminal = sessionNodeManager.peekTerminalMarker(agentName, sessionId);
|
|
401
|
+
if (terminal) {
|
|
402
|
+
const sealedRoot = sessionNodeManager.getSealedRootHex(agentName, sessionId);
|
|
403
|
+
return {
|
|
404
|
+
ok: true,
|
|
405
|
+
type: "session_sealed",
|
|
406
|
+
session_id: sessionId,
|
|
407
|
+
...(sealedRoot ? { sealed_root: sealedRoot } : {}),
|
|
408
|
+
unread_count: terminal.unreadCount,
|
|
409
|
+
guidance: terminal.unreadCount > 0
|
|
410
|
+
? `The session has been sealed by both parties. ${terminal.unreadCount} message(s) arrived that were not read live — call cello_transcript to retrieve the full sealed history. No further actions are required on this session.`
|
|
411
|
+
: "The session has been sealed by both parties. The full history is available via cello_transcript. No further actions are required on this session.",
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// 3) Out of time — non-blocking-equivalent empty answer.
|
|
415
|
+
const remaining = deadline - Date.now();
|
|
416
|
+
if (remaining <= 0) {
|
|
417
|
+
// M8B F16: a dead session must not return the SAME null timeout as a
|
|
418
|
+
// quiet-but-healthy one. The liveness signal (session.liveness.changed → gone,
|
|
419
|
+
// tracked per session by the node manager) finally reaches the MCP surface here.
|
|
420
|
+
if (sessionNodeManager.getSessionLiveness(agentName, sessionId) === "gone") {
|
|
421
|
+
return {
|
|
422
|
+
ok: true,
|
|
423
|
+
content: null,
|
|
424
|
+
reason: "counterparty_gone",
|
|
425
|
+
liveness: "gone",
|
|
426
|
+
guidance: "The counterparty's session connection has dropped (liveness: gone) — it may have crashed or gone offline. No more content will arrive on the direct path. Call cello_close_session to seal the session; if the counterparty never co-closes, a unilateral seal becomes available after the directory's delivery-grace window.",
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return { ok: true, content: null, guidance: "No content arrived within timeout_ms. Call cello_receive again to keep waiting, or read cello_transcript for the full session history." };
|
|
430
|
+
}
|
|
431
|
+
await new Promise((r) => setTimeout(r, Math.min(20, remaining)));
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
handlers.set("cello_receive", handleReceive);
|
|
435
|
+
}
|
|
436
|
+
//# sourceMappingURL=session-content-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-content-handlers.js","sourceRoot":"","sources":["../src/session-content-handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-SESSION-NAME-1 — validation for a session's human-readable name.
|
|
3
|
+
*
|
|
4
|
+
* This lives in the DAEMON, not in protocol-types, and that is deliberate: a session name is local
|
|
5
|
+
* and cosmetic. protocol-types holds the wire encoders and the TBS builders — putting a validator
|
|
6
|
+
* there would say the name is protocol, and the next reader would reasonably wire it into a frame.
|
|
7
|
+
* It is a sticky note on your own copy of the folder.
|
|
8
|
+
*
|
|
9
|
+
* It is NOT handle-shaped. Do not reach for MONIKER_RE: a moniker is a handle a counterparty sees,
|
|
10
|
+
* so it is deliberately narrow; this is a DESCRIPTION ("Q3 budget review with Bob") that only its
|
|
11
|
+
* author reads. Letters, digits, spaces, punctuation, accents, CJK and emoji are all legal.
|
|
12
|
+
*/
|
|
13
|
+
/** Free text, but bounded: long enough for a sentence, short enough to list in a terminal. */
|
|
14
|
+
export declare const SESSION_NAME_MAX_LENGTH = 200;
|
|
15
|
+
export type SessionNameResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
value: string | null;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: "session_name_not_a_string" | "session_name_control_chars" | "session_name_too_long";
|
|
21
|
+
guidance: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Validate a session name.
|
|
25
|
+
*
|
|
26
|
+
* Returns the trimmed name, or `null` for "no name" — which is a legal, meaningful value, not a
|
|
27
|
+
* failure. `null`, `undefined` and an all-whitespace string all mean CLEAR.
|
|
28
|
+
*
|
|
29
|
+
* Rejects rather than repairs. A 201-character name is refused, not truncated, and a name carrying
|
|
30
|
+
* a newline is refused, not stripped: silently altering what the operator typed and then storing it
|
|
31
|
+
* under their name is worse than telling them it was wrong. Each refusal names its own cause, so
|
|
32
|
+
* "why was my name rejected" never requires reading the daemon log.
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateSessionName(raw: unknown): SessionNameResult;
|
|
35
|
+
//# sourceMappingURL=session-name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-name.d.ts","sourceRoot":"","sources":["../src/session-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,8FAA8F;AAC9F,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAK3C,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,2BAA2B,GAAG,4BAA4B,GAAG,uBAAuB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAElI;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAkCnE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-SESSION-NAME-1 — validation for a session's human-readable name.
|
|
3
|
+
*
|
|
4
|
+
* This lives in the DAEMON, not in protocol-types, and that is deliberate: a session name is local
|
|
5
|
+
* and cosmetic. protocol-types holds the wire encoders and the TBS builders — putting a validator
|
|
6
|
+
* there would say the name is protocol, and the next reader would reasonably wire it into a frame.
|
|
7
|
+
* It is a sticky note on your own copy of the folder.
|
|
8
|
+
*
|
|
9
|
+
* It is NOT handle-shaped. Do not reach for MONIKER_RE: a moniker is a handle a counterparty sees,
|
|
10
|
+
* so it is deliberately narrow; this is a DESCRIPTION ("Q3 budget review with Bob") that only its
|
|
11
|
+
* author reads. Letters, digits, spaces, punctuation, accents, CJK and emoji are all legal.
|
|
12
|
+
*/
|
|
13
|
+
/** Free text, but bounded: long enough for a sentence, short enough to list in a terminal. */
|
|
14
|
+
export const SESSION_NAME_MAX_LENGTH = 200;
|
|
15
|
+
/** C0 (including \n, \r, \t, \0), DEL, and C1. Rejected, never stripped. */
|
|
16
|
+
const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/;
|
|
17
|
+
/**
|
|
18
|
+
* Validate a session name.
|
|
19
|
+
*
|
|
20
|
+
* Returns the trimmed name, or `null` for "no name" — which is a legal, meaningful value, not a
|
|
21
|
+
* failure. `null`, `undefined` and an all-whitespace string all mean CLEAR.
|
|
22
|
+
*
|
|
23
|
+
* Rejects rather than repairs. A 201-character name is refused, not truncated, and a name carrying
|
|
24
|
+
* a newline is refused, not stripped: silently altering what the operator typed and then storing it
|
|
25
|
+
* under their name is worse than telling them it was wrong. Each refusal names its own cause, so
|
|
26
|
+
* "why was my name rejected" never requires reading the daemon log.
|
|
27
|
+
*/
|
|
28
|
+
export function validateSessionName(raw) {
|
|
29
|
+
if (raw === null || raw === undefined)
|
|
30
|
+
return { ok: true, value: null };
|
|
31
|
+
if (typeof raw !== "string") {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
reason: "session_name_not_a_string",
|
|
35
|
+
guidance: "A session name must be a string (or null to clear it).",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Control characters are checked BEFORE trimming: trim() would silently eat a leading \n or \t
|
|
39
|
+
// and let a name through that the operator never meant to type — and a name that renders across
|
|
40
|
+
// two lines corrupts every listing it appears in.
|
|
41
|
+
if (CONTROL_CHARS.test(raw)) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
reason: "session_name_control_chars",
|
|
45
|
+
guidance: "A session name cannot contain control characters (newlines, tabs, NUL). It is a single-line label — spaces, punctuation, accents and emoji are all fine.",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const trimmed = raw.trim();
|
|
49
|
+
if (trimmed.length === 0)
|
|
50
|
+
return { ok: true, value: null };
|
|
51
|
+
if (trimmed.length > SESSION_NAME_MAX_LENGTH) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
reason: "session_name_too_long",
|
|
55
|
+
guidance: `A session name is at most ${SESSION_NAME_MAX_LENGTH} characters (this one is ${trimmed.length}). It is refused rather than truncated — a half-name is a name you did not choose.`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { ok: true, value: trimmed };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=session-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-name.js","sourceRoot":"","sources":["../src/session-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,8FAA8F;AAC9F,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAE3C,4EAA4E;AAC5E,MAAM,aAAa,GAAG,8BAA8B,CAAC;AAMrD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAExE,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,2BAA2B;YACnC,QAAQ,EAAE,wDAAwD;SACnE,CAAC;IACJ,CAAC;IAED,+FAA+F;IAC/F,gGAAgG;IAChG,kDAAkD;IAClD,IAAI,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,4BAA4B;YACpC,QAAQ,EAAE,0JAA0J;SACrK,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAE3D,IAAI,OAAO,CAAC,MAAM,GAAG,uBAAuB,EAAE,CAAC;QAC7C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,uBAAuB;YAC/B,QAAQ,EAAE,6BAA6B,uBAAuB,4BAA4B,OAAO,CAAC,MAAM,oFAAoF;SAC7L,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACtC,CAAC"}
|