@cello-protocol/daemon 0.0.46 → 0.0.47
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/daemon.d.ts.map +1 -1
- package/dist/daemon.js +67 -6
- package/dist/daemon.js.map +1 -1
- package/dist/session-node-manager.d.ts +13 -0
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +37 -2
- package/dist/session-node-manager.js.map +1 -1
- package/package.json +2 -2
package/dist/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAQrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAU/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAsB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAQ/G,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA0InF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;;;;;OAQG;IACH,gBAAgB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;CACtC;AA2CD,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAE1D,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAQrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAU/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAsB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAQ/G,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA0InF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;;;;;OAQG;IACH,gBAAgB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;CACtC;AA2CD,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAE1D,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAy8L7E"}
|
package/dist/daemon.js
CHANGED
|
@@ -786,6 +786,24 @@ export async function startDaemon(config) {
|
|
|
786
786
|
cursor += 1;
|
|
787
787
|
advanceConnectionCursor(connectionId, sessionId, cursor);
|
|
788
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* DOD-CURSOR-DURABLE-1: the same hole-safe walk, applied to the PERSISTED per-(agent, session)
|
|
791
|
+
* read watermark. Used by cello_get_transcript, whose delivery covers BOTH directions.
|
|
792
|
+
*
|
|
793
|
+
* Walks a CONTIGUOUS run from the agent's current watermark, stopping at the first gap — for the
|
|
794
|
+
* identical reason safeCursorAdvance does: leaf indices are contiguous across both directions, so
|
|
795
|
+
* a gap can hide an unread RECEIVED message (e.g. a row that failed to decrypt is absent from
|
|
796
|
+
* `messages`). Advancing past it would mark unseen counterparty content as read and unblock a
|
|
797
|
+
* send that never saw it — defeating the very guarantee this gate exists to enforce. Monotonic:
|
|
798
|
+
* advanceLastDeliveredSeq takes MAX, so this can never lower a watermark.
|
|
799
|
+
*/
|
|
800
|
+
function safeWatermarkAdvance(agentName, sessionId, deliveredSeqs) {
|
|
801
|
+
let frontier = sessionNodeManager.getLastDeliveredSeq(agentName, sessionId);
|
|
802
|
+
while (deliveredSeqs.has(frontier + 1))
|
|
803
|
+
frontier += 1;
|
|
804
|
+
if (frontier >= 0)
|
|
805
|
+
sessionNodeManager.advanceLastDeliveredSeq(agentName, sessionId, frontier);
|
|
806
|
+
}
|
|
789
807
|
// M8C-AWAY-1: away response — an unattended Primary auto-answers session requests + messages
|
|
790
808
|
// with the default transparent away text and queues them (the DoD's own mandated default).
|
|
791
809
|
// CORE ships now; the operator-configurable custom-text / opaque-privacy-mode SWITCH is PARKED
|
|
@@ -3455,7 +3473,16 @@ export async function startDaemon(config) {
|
|
|
3455
3473
|
// reviewer HIGH finding (aa5928e2/a9099571): recordTranscriptMessage swallows a DB write
|
|
3456
3474
|
// failure without rolling back the tree's leaf count, so a hole in `messages` is possible in
|
|
3457
3475
|
// principle; the contiguous-run walk refuses to vault the cursor past such a hole even here.
|
|
3458
|
-
|
|
3476
|
+
const deliveredSeqs = new Set(messages.map((m) => m.sequence));
|
|
3477
|
+
safeCursorAdvance(connectionId, sessionId, deliveredSeqs);
|
|
3478
|
+
// DOD-CURSOR-DURABLE-1 (AC3): reading the full history IS reading — so this must advance the
|
|
3479
|
+
// PERSISTED watermark too, not just this connection's in-memory cursor. Previously it advanced
|
|
3480
|
+
// only the cursor, which made the gate's own guidance ("call cello_get_transcript, then retry")
|
|
3481
|
+
// a dead end for any stateless client: a fresh process reads the transcript, exits, and the next
|
|
3482
|
+
// process still presents an unread count. Same contiguous-run safety as the cursor — never vault
|
|
3483
|
+
// past a gap (an undecryptable row is absent from `messages`, so the walk stops there and those
|
|
3484
|
+
// messages correctly stay unread).
|
|
3485
|
+
safeWatermarkAdvance(agentName, sessionId, deliveredSeqs);
|
|
3459
3486
|
return { ok: true, session_id: sessionId, messages, undecryptable };
|
|
3460
3487
|
});
|
|
3461
3488
|
// cello_list_sessions: the discovery surface — every persisted session for the
|
|
@@ -5019,18 +5046,52 @@ export async function startDaemon(config) {
|
|
|
5019
5046
|
// rather than let it send blind — the WhatsApp-group-chat model. Runs BEFORE M9's
|
|
5020
5047
|
// governance-decisions parsing below — an access-control gate should short-circuit before any
|
|
5021
5048
|
// unrelated prep work for a send that may not even be allowed to proceed.
|
|
5049
|
+
// DOD-CURSOR-DURABLE-1: the gate now consults TWO authorities and passes if EITHER says the
|
|
5050
|
+
// caller is caught up.
|
|
5051
|
+
//
|
|
5052
|
+
// 1. the connection cursor (in-memory, per-connection) — today's rule, UNCHANGED. A
|
|
5053
|
+
// long-lived client (the MCP shim, one socket for the whole session) satisfies this
|
|
5054
|
+
// exactly as before, so the shipped single-connection path is byte-for-byte identical.
|
|
5055
|
+
//
|
|
5056
|
+
// 2. the persisted per-(agent, session) read watermark — NEW. A STATELESS client (the `cello`
|
|
5057
|
+
// CLI runs a fresh process, and therefore a fresh connection, per command) always presents
|
|
5058
|
+
// cursor -1, so authority 1 can never be satisfied by it: after the counterparty spoke
|
|
5059
|
+
// once, every CLI send was refused forever, even though the agent had demonstrably read
|
|
5060
|
+
// the message. That made a two-way conversation impossible from bash — and it silently hit
|
|
5061
|
+
// any RECONNECTING MCP client too, whose fresh connectionId also resets the cursor to -1.
|
|
5062
|
+
//
|
|
5063
|
+
// What the gate protects, precisely: an agent must never reply to COUNTERPARTY content nobody
|
|
5064
|
+
// on its side has seen. That guarantee is preserved and is now durable — it survives the socket.
|
|
5065
|
+
//
|
|
5066
|
+
// What it deliberately no longer protects (the approved trade — see
|
|
5067
|
+
// 2026-07-11_cursor-durable-read-before-write-design.md §6, Andre's go): a message this agent
|
|
5068
|
+
// SENT from a DIFFERENT local connection no longer blocks. Two attended windows on one agent
|
|
5069
|
+
// used to gate each other; now they do not, because a locally-authored message is not "unread
|
|
5070
|
+
// counterparty content" — the AGENT wrote it. The principal here is the agent, not the socket,
|
|
5071
|
+
// and the daemon cannot referee which of an operator's own windows a human is looking at.
|
|
5022
5072
|
const currentSeq = record.message_count - 1;
|
|
5023
|
-
const
|
|
5024
|
-
|
|
5073
|
+
const connectionCursor = getConnectionCursor(connectionId, sessionId);
|
|
5074
|
+
const unreadReceived = sessionNodeManager.getUnreadReceivedCount(agentName, sessionId);
|
|
5075
|
+
const caughtUp = connectionCursor >= currentSeq || unreadReceived === 0;
|
|
5076
|
+
if (!caughtUp) {
|
|
5025
5077
|
// M8C-CURSOR-1 (reviewer MEDIUM fix): every sibling rejection in this handler logs; this
|
|
5026
5078
|
// gate must too — a security-relevant control-flow path with no observability is a gap.
|
|
5027
|
-
|
|
5079
|
+
// Both authorities are logged, so the next reader can tell WHICH one refused.
|
|
5080
|
+
logger.warn("session.send.blocked", {
|
|
5081
|
+
sessionId,
|
|
5082
|
+
currentSeq,
|
|
5083
|
+
lastReadSeq: connectionCursor,
|
|
5084
|
+
unreadReceived,
|
|
5085
|
+
connectionId,
|
|
5086
|
+
agentName,
|
|
5087
|
+
});
|
|
5028
5088
|
return {
|
|
5029
5089
|
ok: false,
|
|
5030
5090
|
reason: "session_not_current",
|
|
5031
5091
|
current_seq: currentSeq,
|
|
5032
|
-
last_read_seq:
|
|
5033
|
-
|
|
5092
|
+
last_read_seq: connectionCursor,
|
|
5093
|
+
unread_received: unreadReceived,
|
|
5094
|
+
guidance: `This agent hasn't read ${unreadReceived} received message(s) on session ${sessionId}. Call cello_receive (or cello_get_transcript for the full history) to read them, then retry the send.`,
|
|
5034
5095
|
};
|
|
5035
5096
|
}
|
|
5036
5097
|
// M9-FEED-001 §6: the agent's governance re-send decisions, keyed by the flagId a prior `warn`
|