@sentropic/h2a 0.97.2 → 0.97.3
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/bin.js +13 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +1 -0
- package/dist/mcp.js.map +1 -1
- package/dist/runtime/identity/bindings.d.ts.map +1 -1
- package/dist/runtime/identity/bindings.js +16 -1
- package/dist/runtime/identity/bindings.js.map +1 -1
- package/dist/runtime/identity/live.d.ts.map +1 -1
- package/dist/runtime/identity/live.js +15 -9
- package/dist/runtime/identity/live.js.map +1 -1
- package/dist/runtime/local-files/locks.d.ts +20 -0
- package/dist/runtime/local-files/locks.d.ts.map +1 -1
- package/dist/runtime/local-files/locks.js +42 -2
- package/dist/runtime/local-files/locks.js.map +1 -1
- package/dist/runtime/local-files/store.d.ts.map +1 -1
- package/dist/runtime/local-files/store.js +42 -4
- package/dist/runtime/local-files/store.js.map +1 -1
- package/dist/runtime/mcp/discovery-pagination.d.ts +73 -0
- package/dist/runtime/mcp/discovery-pagination.d.ts.map +1 -0
- package/dist/runtime/mcp/discovery-pagination.js +446 -0
- package/dist/runtime/mcp/discovery-pagination.js.map +1 -0
- package/dist/runtime/mcp/frame-budget.d.ts +149 -0
- package/dist/runtime/mcp/frame-budget.d.ts.map +1 -0
- package/dist/runtime/mcp/frame-budget.js +275 -0
- package/dist/runtime/mcp/frame-budget.js.map +1 -0
- package/dist/runtime/mcp/handlers.d.ts +14 -3
- package/dist/runtime/mcp/handlers.d.ts.map +1 -1
- package/dist/runtime/mcp/handlers.js +22 -1
- package/dist/runtime/mcp/handlers.js.map +1 -1
- package/dist/runtime/mcp/index.d.ts +1 -0
- package/dist/runtime/mcp/index.d.ts.map +1 -1
- package/dist/runtime/mcp/index.js +1 -0
- package/dist/runtime/mcp/index.js.map +1 -1
- package/dist/runtime/mcp/notifications.d.ts +13 -1
- package/dist/runtime/mcp/notifications.d.ts.map +1 -1
- package/dist/runtime/mcp/notifications.js +36 -20
- package/dist/runtime/mcp/notifications.js.map +1 -1
- package/dist/runtime/mcp/payload-store.d.ts +67 -0
- package/dist/runtime/mcp/payload-store.d.ts.map +1 -0
- package/dist/runtime/mcp/payload-store.js +242 -0
- package/dist/runtime/mcp/payload-store.js.map +1 -0
- package/dist/runtime/mcp/phase-trace.d.ts +98 -0
- package/dist/runtime/mcp/phase-trace.d.ts.map +1 -0
- package/dist/runtime/mcp/phase-trace.js +179 -0
- package/dist/runtime/mcp/phase-trace.js.map +1 -0
- package/dist/runtime/mcp/server.d.ts +13 -1
- package/dist/runtime/mcp/server.d.ts.map +1 -1
- package/dist/runtime/mcp/server.js +57 -3
- package/dist/runtime/mcp/server.js.map +1 -1
- package/dist/runtime/mcp/stdio.d.ts.map +1 -1
- package/dist/runtime/mcp/stdio.js +176 -15
- package/dist/runtime/mcp/stdio.js.map +1 -1
- package/dist/runtime/mcp/tools.d.ts.map +1 -1
- package/dist/runtime/mcp/tools.js +31 -4
- package/dist/runtime/mcp/tools.js.map +1 -1
- package/dist/runtime/mcp-central.d.ts.map +1 -1
- package/dist/runtime/mcp-central.js +17 -7
- package/dist/runtime/mcp-central.js.map +1 -1
- package/dist/runtime/mcp-http/hosted-mcp-server.d.ts.map +1 -1
- package/dist/runtime/mcp-http/hosted-mcp-server.js +16 -6
- package/dist/runtime/mcp-http/hosted-mcp-server.js.map +1 -1
- package/dist/runtime/mcp-http/readonly-allowlist.d.ts.map +1 -1
- package/dist/runtime/mcp-http/readonly-allowlist.js +3 -0
- package/dist/runtime/mcp-http/readonly-allowlist.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EACX,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EACX,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC"}
|
|
@@ -5,4 +5,5 @@ export { H2A_MCP_READY_FILE_ENV, H2A_MCP_READY_KIND, H2A_MCP_READY_NONCE_ENV, ru
|
|
|
5
5
|
export { buildH2aRunInvocation, executeH2aRun, executeH2aRunWithSpawn, handleH2aRun, recordMcpRunDelegation, H2A_RUN_API_VERSION, validateH2aRunRequest } from "./agent-launch.js";
|
|
6
6
|
export { SessionRegistry } from "./sessions.js";
|
|
7
7
|
export { NotificationDispatcher } from "./notifications.js";
|
|
8
|
+
export { createMcpTrace, getActiveMcpTrace, setActiveMcpTrace, TRACE_LINE_PREFIX, TRACE_DETAIL_ENV } from "./phase-trace.js";
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/runtime/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EAIrB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,4BAA4B,EAG7B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAGN,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EAEZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EAItB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EAGvB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/runtime/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EAIrB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,4BAA4B,EAG7B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAGN,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EAEZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EAItB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EAGvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAOjB,MAAM,kBAAkB,CAAC"}
|
|
@@ -11,7 +11,18 @@ export interface McpPushNotification {
|
|
|
11
11
|
data: Record<string, unknown>;
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* L1: a sink may return a receipt so the dispatcher advances a session's
|
|
16
|
+
* snapshot ONLY after the notification left bounded (or its intact bytes were
|
|
17
|
+
* persisted). `void`/`undefined` is treated as accepted, keeping existing mock
|
|
18
|
+
* sinks working. When a push is not accepted (persistence impossible), the
|
|
19
|
+
* session's snapshot is NOT advanced, so the diff is retried next tick — no ACK
|
|
20
|
+
* of a purely-ephemeral event is fabricated.
|
|
21
|
+
*/
|
|
22
|
+
export type NotificationReceipt = {
|
|
23
|
+
readonly accepted: boolean;
|
|
24
|
+
} | void;
|
|
25
|
+
export type NotificationSink = (notification: McpPushNotification) => NotificationReceipt;
|
|
15
26
|
/**
|
|
16
27
|
* Owns the per-session diff snapshots and the periodic scan that turns
|
|
17
28
|
* filesystem changes into pushed JSON-RPC notifications. One dispatcher per
|
|
@@ -42,6 +53,7 @@ export declare class NotificationDispatcher {
|
|
|
42
53
|
* tests can drive the dispatch deterministically without a timer.
|
|
43
54
|
*/
|
|
44
55
|
tick(): void;
|
|
56
|
+
/** Emit one notification; returns whether the sink accepted it (see NotificationReceipt). */
|
|
45
57
|
private push;
|
|
46
58
|
}
|
|
47
59
|
//# sourceMappingURL=notifications.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,kEAAkE;AAClE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE;QACN,KAAK,EAAE,2BAA2B,CAAC;QACnC,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC/B,CAAC;CACH;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,mBAAmB,KAAK,
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,kEAAkE;AAClE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE;QACN,KAAK,EAAE,2BAA2B,CAAC;QACnC,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC/B,CAAC;CACH;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,mBAAmB,KAAK,mBAAmB,CAAC;AAsD1F;;;;;GAKG;AACH,qBAAa,sBAAsB;IAK/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ,CAAC,UAAU;IAR7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IACjE,OAAO,CAAC,KAAK,CAAC,CAAiC;gBAG5B,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,MAAM,EACrB,IAAI,EAAE,gBAAgB,GAAG,SAAS,EACzB,UAAU,EAAE,MAAM;IAGrC,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI;IAIjD;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAC,CAA6B;IACpD,iBAAiB,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI;IAIrE,KAAK,IAAI,IAAI;IAMb,IAAI,IAAI,IAAI;IAMZ;;;OAGG;IACH,IAAI,IAAI,IAAI;IAqIZ,6FAA6F;IAC7F,OAAO,CAAC,IAAI;CAsBb"}
|
|
@@ -92,6 +92,11 @@ export class NotificationDispatcher {
|
|
|
92
92
|
const activeBlockages = listBlockages(this.root).filter(isActiveBlockage);
|
|
93
93
|
for (const session of ownSessions) {
|
|
94
94
|
const prev = this.snapshots.get(session.sessionId) ?? emptyDiffState();
|
|
95
|
+
// L1: a session's snapshot advances only if EVERY emitted diff was accepted
|
|
96
|
+
// (bounded/persisted). One unaccepted push holds the whole session snapshot
|
|
97
|
+
// so the diffs are retried next tick — an at-least-once guarantee that never
|
|
98
|
+
// fabricates an ACK for a dropped ephemeral notification.
|
|
99
|
+
let accepted = true;
|
|
95
100
|
// 1. Presence join/leave (excluding self)
|
|
96
101
|
const others = new Set([...peerIds].filter((sid) => sid !== session.sessionId));
|
|
97
102
|
if (isInterestedIn(session, "presence.peer_joined")) {
|
|
@@ -99,14 +104,14 @@ export class NotificationDispatcher {
|
|
|
99
104
|
if (!prev.peers.has(sid)) {
|
|
100
105
|
const peer = freshPeers.find((p) => p.sessionId === sid);
|
|
101
106
|
if (peer) {
|
|
102
|
-
this.push(session.sessionId, "presence.peer_joined", {
|
|
107
|
+
accepted = this.push(session.sessionId, "presence.peer_joined", {
|
|
103
108
|
peer: {
|
|
104
109
|
sessionId: peer.sessionId,
|
|
105
110
|
instance: peer.instance,
|
|
106
111
|
host: peer.host,
|
|
107
112
|
interests: peer.interests
|
|
108
113
|
}
|
|
109
|
-
});
|
|
114
|
+
}) && accepted;
|
|
110
115
|
}
|
|
111
116
|
}
|
|
112
117
|
}
|
|
@@ -114,9 +119,9 @@ export class NotificationDispatcher {
|
|
|
114
119
|
if (isInterestedIn(session, "presence.peer_left")) {
|
|
115
120
|
for (const sid of prev.peers) {
|
|
116
121
|
if (!others.has(sid)) {
|
|
117
|
-
this.push(session.sessionId, "presence.peer_left", {
|
|
122
|
+
accepted = this.push(session.sessionId, "presence.peer_left", {
|
|
118
123
|
peer: { sessionId: sid }
|
|
119
|
-
});
|
|
124
|
+
}) && accepted;
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
}
|
|
@@ -127,10 +132,10 @@ export class NotificationDispatcher {
|
|
|
127
132
|
if (isInterestedIn(session, "inbox.envelope_arrived")) {
|
|
128
133
|
for (const envelopeId of inboxIds) {
|
|
129
134
|
if (!prev.inbox.envelopeIds.has(envelopeId)) {
|
|
130
|
-
this.push(session.sessionId, "inbox.envelope_arrived", {
|
|
135
|
+
accepted = this.push(session.sessionId, "inbox.envelope_arrived", {
|
|
131
136
|
instance: session.instance,
|
|
132
137
|
envelopeId
|
|
133
|
-
});
|
|
138
|
+
}) && accepted;
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
// EVO-1 wake (bug #3): nudge the host every tick while the inbox is
|
|
@@ -148,11 +153,11 @@ export class NotificationDispatcher {
|
|
|
148
153
|
const count = readJournalLength(this.store, negotiationId);
|
|
149
154
|
const prevCount = prev.negotiations.get(negotiationId)?.count ?? 0;
|
|
150
155
|
if (count > prevCount) {
|
|
151
|
-
this.push(session.sessionId, "negotiation.event_appended", {
|
|
156
|
+
accepted = this.push(session.sessionId, "negotiation.event_appended", {
|
|
152
157
|
negotiationId,
|
|
153
158
|
newEntries: count - prevCount,
|
|
154
159
|
totalEntries: count
|
|
155
|
-
});
|
|
160
|
+
}) && accepted;
|
|
156
161
|
}
|
|
157
162
|
negotiationSnap.set(negotiationId, { count });
|
|
158
163
|
}
|
|
@@ -165,40 +170,51 @@ export class NotificationDispatcher {
|
|
|
165
170
|
if (isInterestedIn(session, "peer.blocked")) {
|
|
166
171
|
for (const b of inScope) {
|
|
167
172
|
if (!prev.blocked.has(b.instance)) {
|
|
168
|
-
this.push(session.sessionId, "peer.blocked", {
|
|
173
|
+
accepted = this.push(session.sessionId, "peer.blocked", {
|
|
169
174
|
instance: b.instance,
|
|
170
175
|
scope: b.scope,
|
|
171
176
|
reason: b.reason,
|
|
172
177
|
...(b.needs ? { needs: b.needs } : {})
|
|
173
|
-
});
|
|
178
|
+
}) && accepted;
|
|
174
179
|
}
|
|
175
180
|
}
|
|
176
181
|
}
|
|
177
182
|
if (isInterestedIn(session, "peer.unblocked")) {
|
|
178
183
|
for (const instance of prev.blocked) {
|
|
179
184
|
if (!blockedNow.has(instance)) {
|
|
180
|
-
this.push(session.sessionId, "peer.unblocked", { instance });
|
|
185
|
+
accepted = this.push(session.sessionId, "peer.unblocked", { instance }) && accepted;
|
|
181
186
|
}
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
if (accepted) {
|
|
190
|
+
this.snapshots.set(session.sessionId, {
|
|
191
|
+
peers: others,
|
|
192
|
+
inbox: { envelopeIds: new Set(inboxIds) },
|
|
193
|
+
negotiations: negotiationSnap,
|
|
194
|
+
blocked: blockedNow
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// else: keep the previous snapshot so the unaccepted diffs are retried.
|
|
190
198
|
}
|
|
191
199
|
}
|
|
200
|
+
/** Emit one notification; returns whether the sink accepted it (see NotificationReceipt). */
|
|
192
201
|
push(sessionId, topic, data) {
|
|
193
202
|
if (!this.sink)
|
|
194
|
-
return;
|
|
203
|
+
return true;
|
|
195
204
|
if (!H2A_SESSION_NOTIFICATION_TOPICS.includes(topic))
|
|
196
|
-
return;
|
|
197
|
-
this.sink({
|
|
205
|
+
return true;
|
|
206
|
+
const receipt = this.sink({
|
|
198
207
|
jsonrpc: "2.0",
|
|
199
208
|
method: "notifications/h2a",
|
|
200
209
|
params: { topic, sessionId, data }
|
|
201
210
|
});
|
|
211
|
+
// Accepted unless the sink explicitly returns `{ accepted: false }`. A sink
|
|
212
|
+
// that returns void, or any other value (e.g. a legacy `array.push` count),
|
|
213
|
+
// is treated as accepted so existing sinks keep advancing the snapshot.
|
|
214
|
+
return !(receipt !== null &&
|
|
215
|
+
typeof receipt === "object" &&
|
|
216
|
+
"accepted" in receipt &&
|
|
217
|
+
receipt.accepted === false);
|
|
202
218
|
}
|
|
203
219
|
}
|
|
204
220
|
//# sourceMappingURL=notifications.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/runtime/mcp/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,gBAAgB,EAGjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/runtime/mcp/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,gBAAgB,EAGjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AA+CxD,SAAS,cAAc,CACrB,OAAmB,EACnB,KAAkC;IAElC,OAAO,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,YAAY,CAAC,KAAiB,EAAE,QAAgB;IACvD,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAiB,EAAE,aAAqB;IACjE,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,sBAAsB,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,SAAS,cAAc;IACrB,OAAO;QACL,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,GAAG,EAAE,EAAE;QACjC,YAAY,EAAE,IAAI,GAAG,EAAE;QACvB,OAAO,EAAE,IAAI,GAAG,EAAE;KACnB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,sBAAsB;IAKd;IACA;IACA;IACT;IACS;IARF,SAAS,GAAG,IAAI,GAAG,EAA4B,CAAC;IACzD,KAAK,CAAkC;IAE/C,YACmB,QAAyB,EACzB,KAAiB,EACjB,IAAY,EACrB,IAAkC,EACzB,UAAkB;QAJlB,aAAQ,GAAR,QAAQ,CAAiB;QACzB,UAAK,GAAL,KAAK,CAAY;QACjB,SAAI,GAAJ,IAAI,CAAQ;QACrB,SAAI,GAAJ,IAAI,CAA8B;QACzB,eAAU,GAAV,UAAU,CAAQ;IAClC,CAAC;IAEJ,OAAO,CAAC,IAAkC;QACxC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACK,cAAc,CAA8B;IACpD,iBAAiB,CAAC,EAA4C;QAC5D,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;IAC3B,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACvB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO;QACxB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO;QACvB,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACzC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAErC,+DAA+D;QAC/D,kEAAkE;QAClE,0DAA0D;QAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAE5D,4EAA4E;QAC5E,qDAAqD;QACrD,MAAM,eAAe,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAE1E,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,cAAc,EAAE,CAAC;YACvE,4EAA4E;YAC5E,4EAA4E;YAC5E,6EAA6E;YAC7E,0DAA0D;YAC1D,IAAI,QAAQ,GAAG,IAAI,CAAC;YAEpB,0CAA0C;YAC1C,MAAM,MAAM,GAAG,IAAI,GAAG,CACpB,CAAC,GAAG,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,OAAO,CAAC,SAAS,CAAC,CACxD,CAAC;YACF,IAAI,cAAc,CAAC,OAAO,EAAE,sBAAsB,CAAC,EAAE,CAAC;gBACpD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;oBACzB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;wBACzB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,GAAG,CAAC,CAAC;wBACzD,IAAI,IAAI,EAAE,CAAC;4BACT,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,sBAAsB,EAAE;gCAC9D,IAAI,EAAE;oCACJ,SAAS,EAAE,IAAI,CAAC,SAAS;oCACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oCACvB,IAAI,EAAE,IAAI,CAAC,IAAI;oCACf,SAAS,EAAE,IAAI,CAAC,SAAS;iCAC1B;6BACF,CAAC,IAAI,QAAQ,CAAC;wBACjB,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YACD,IAAI,cAAc,CAAC,OAAO,EAAE,oBAAoB,CAAC,EAAE,CAAC;gBAClD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;oBAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;wBACrB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,oBAAoB,EAAE;4BAC5D,IAAI,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE;yBACzB,CAAC,IAAI,QAAQ,CAAC;oBACjB,CAAC;gBACH,CAAC;YACH,CAAC;YAED,oBAAoB;YACpB,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,wBAAwB,CAAC;gBAChE,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC;gBAC5C,CAAC,CAAC,EAAE,CAAC;YACP,IAAI,cAAc,CAAC,OAAO,EAAE,wBAAwB,CAAC,EAAE,CAAC;gBACtD,KAAK,MAAM,UAAU,IAAI,QAAQ,EAAE,CAAC;oBAClC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;wBAC5C,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,wBAAwB,EAAE;4BAChE,QAAQ,EAAE,OAAO,CAAC,QAAQ;4BAC1B,UAAU;yBACX,CAAC,IAAI,QAAQ,CAAC;oBACjB,CAAC;gBACH,CAAC;gBACD,oEAAoE;gBACpE,yEAAyE;gBACzE,yEAAyE;gBACzE,0EAA0E;gBAC1E,wEAAwE;gBACxE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;oBAAE,IAAI,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACnE,CAAC;YAED,2EAA2E;YAC3E,MAAM,eAAe,GAAG,IAAI,GAAG,EAA+B,CAAC;YAC/D,IAAI,cAAc,CAAC,OAAO,EAAE,4BAA4B,CAAC,EAAE,CAAC;gBAC1D,KAAK,MAAM,aAAa,IAAI,OAAO,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC;oBAC3D,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;oBAC3D,MAAM,SAAS,GACb,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;oBACnD,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;wBACtB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,4BAA4B,EAAE;4BACpE,aAAa;4BACb,UAAU,EAAE,KAAK,GAAG,SAAS;4BAC7B,YAAY,EAAE,KAAK;yBACpB,CAAC,IAAI,QAAQ,CAAC;oBACjB,CAAC;oBACD,eAAe,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAChD,CAAC;YACH,CAAC;YAED,wEAAwE;YACxE,uEAAuE;YACvE,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CACpC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ;gBAC/B,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAC7C,CAAC;YACF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC3D,IAAI,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,CAAC;gBAC5C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;oBACxB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAClC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,EAAE;4BACtD,QAAQ,EAAE,CAAC,CAAC,QAAQ;4BACpB,KAAK,EAAE,CAAC,CAAC,KAAK;4BACd,MAAM,EAAE,CAAC,CAAC,MAAM;4BAChB,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBACvC,CAAC,IAAI,QAAQ,CAAC;oBACjB,CAAC;gBACH,CAAC;YACH,CAAC;YACD,IAAI,cAAc,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,CAAC;gBAC9C,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAC9B,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,QAAQ,CAAC;oBACtF,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE;oBACpC,KAAK,EAAE,MAAM;oBACb,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,EAAE;oBACzC,YAAY,EAAE,eAAe;oBAC7B,OAAO,EAAE,UAAU;iBACpB,CAAC,CAAC;YACL,CAAC;YACD,wEAAwE;QAC1E,CAAC;IACH,CAAC;IAED,6FAA6F;IACrF,IAAI,CACV,SAAiB,EACjB,KAAkC,EAClC,IAA6B;QAE7B,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAC5B,IAAI,CAAC,+BAA+B,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC;YACxB,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,mBAAmB;YAC3B,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE;SACnC,CAAC,CAAC;QACH,4EAA4E;QAC5E,4EAA4E;QAC5E,wEAAwE;QACxE,OAAO,CAAC,CACN,OAAO,KAAK,IAAI;YAChB,OAAO,OAAO,KAAK,QAAQ;YAC3B,UAAU,IAAI,OAAO;YACpB,OAAkC,CAAC,QAAQ,KAAK,KAAK,CACvD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable recovery store for oversize MCP outputs (lot L1).
|
|
3
|
+
*
|
|
4
|
+
* When a legitimate response or notification exceeds the frame budget
|
|
5
|
+
* (`frame-budget.ts`), the INTACT original bytes are persisted here BEFORE the
|
|
6
|
+
* bounded error/notice is emitted, and the client recovers them in budget-sized
|
|
7
|
+
* chunks through the `h2a_read_payload` tool. The store never holds tool
|
|
8
|
+
* arguments or a private key — only output already destined for THIS client —
|
|
9
|
+
* and it never re-executes an effectful tool: it hands back stored bytes.
|
|
10
|
+
*
|
|
11
|
+
* Security properties (DEC-116 lineage):
|
|
12
|
+
* - Root/tenant confinement: one store per store-root; a ref is a 256-bit
|
|
13
|
+
* random capability whose file name IS the ref, so there is no free path, no
|
|
14
|
+
* `../`, and no cross-tenant lookup. Symlinks are refused on read.
|
|
15
|
+
* - Atomic publication: content is staged to a temp file (0600) and renamed.
|
|
16
|
+
* - Integrity: every payload carries a SHA-256 the reader verifies after
|
|
17
|
+
* reassembly.
|
|
18
|
+
* - Durability across restart: refs survive; nothing is purged at boot. Only a
|
|
19
|
+
* dedicated maintenance sweep removes EXPIRED refs, never live data.
|
|
20
|
+
*/
|
|
21
|
+
/** Announced recovery window: a ref is readable for 24h after it is written. */
|
|
22
|
+
export declare const PAYLOAD_TTL_MS: number;
|
|
23
|
+
/** Proposed per-root ceiling (measured against the store dir before a write). */
|
|
24
|
+
export declare const PAYLOAD_ROOT_QUOTA_BYTES: number;
|
|
25
|
+
/** Hard maximum a single read may return (also bounded again by the frame budget). */
|
|
26
|
+
export declare const PAYLOAD_MAX_READ_BYTES = 65536;
|
|
27
|
+
export interface PayloadRecoveryRef {
|
|
28
|
+
/** Opaque 256-bit capability. */
|
|
29
|
+
readonly ref: string;
|
|
30
|
+
readonly sha256: string;
|
|
31
|
+
readonly totalBytes: number;
|
|
32
|
+
readonly expiresAt: string;
|
|
33
|
+
}
|
|
34
|
+
export type PayloadReadError = "payload_not_found" | "payload_expired" | "invalid_offset" | "payload_corrupt";
|
|
35
|
+
export interface PayloadReadChunk {
|
|
36
|
+
readonly ref: string;
|
|
37
|
+
readonly encoding: "base64";
|
|
38
|
+
readonly data: string;
|
|
39
|
+
readonly offset: number;
|
|
40
|
+
readonly nextOffset: number | null;
|
|
41
|
+
readonly totalBytes: number;
|
|
42
|
+
readonly sha256: string;
|
|
43
|
+
readonly expiresAt: string;
|
|
44
|
+
}
|
|
45
|
+
export declare class PayloadStoreError extends Error {
|
|
46
|
+
readonly reason: "recovery_unavailable";
|
|
47
|
+
readonly cause: string;
|
|
48
|
+
constructor(reason: "recovery_unavailable", cause: string, message: string);
|
|
49
|
+
}
|
|
50
|
+
export interface PayloadStore {
|
|
51
|
+
/** The scope (store-root) every ref of this store is bound to. */
|
|
52
|
+
readonly scope: string;
|
|
53
|
+
/** Persist output bytes and return the recovery ref, or throw PayloadStoreError. */
|
|
54
|
+
persistOutput(bytes: Buffer): PayloadRecoveryRef;
|
|
55
|
+
/** Read a budget-sized chunk of a ref, or a typed read error. */
|
|
56
|
+
readPayload(ref: string, offset: number, maxBytes: number): PayloadReadChunk | {
|
|
57
|
+
error: PayloadReadError;
|
|
58
|
+
};
|
|
59
|
+
/** Remove ONLY expired refs (dedicated maintenance; never touches live data). */
|
|
60
|
+
sweepExpired(now?: number): number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A payload store confined to `<root>/mcp-payloads/`. `scope` defaults to the
|
|
64
|
+
* root, so two roots (two tenants) never share a namespace.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createPayloadStore(root: string, scope?: string): PayloadStore;
|
|
67
|
+
//# sourceMappingURL=payload-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload-store.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/payload-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoBH,gFAAgF;AAChF,eAAO,MAAM,cAAc,QAAsB,CAAC;AAElD,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,QAAoB,CAAC;AAE1D,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,QAAQ,CAAC;AAI5C,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAWD,MAAM,MAAM,gBAAgB,GACxB,mBAAmB,GACnB,iBAAiB,GACjB,gBAAgB,GAChB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,MAAM,EAAE,sBAAsB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM;gBADb,MAAM,EAAE,sBAAsB,EAC9B,KAAK,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAAC;IACjD,iEAAiE;IACjE,WAAW,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,gBAAgB,GAAG;QAAE,KAAK,EAAE,gBAAgB,CAAA;KAAE,CAAC;IAClD,iFAAiF;IACjF,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;AAqBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAa,GAAG,YAAY,CAwLnF"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable recovery store for oversize MCP outputs (lot L1).
|
|
3
|
+
*
|
|
4
|
+
* When a legitimate response or notification exceeds the frame budget
|
|
5
|
+
* (`frame-budget.ts`), the INTACT original bytes are persisted here BEFORE the
|
|
6
|
+
* bounded error/notice is emitted, and the client recovers them in budget-sized
|
|
7
|
+
* chunks through the `h2a_read_payload` tool. The store never holds tool
|
|
8
|
+
* arguments or a private key — only output already destined for THIS client —
|
|
9
|
+
* and it never re-executes an effectful tool: it hands back stored bytes.
|
|
10
|
+
*
|
|
11
|
+
* Security properties (DEC-116 lineage):
|
|
12
|
+
* - Root/tenant confinement: one store per store-root; a ref is a 256-bit
|
|
13
|
+
* random capability whose file name IS the ref, so there is no free path, no
|
|
14
|
+
* `../`, and no cross-tenant lookup. Symlinks are refused on read.
|
|
15
|
+
* - Atomic publication: content is staged to a temp file (0600) and renamed.
|
|
16
|
+
* - Integrity: every payload carries a SHA-256 the reader verifies after
|
|
17
|
+
* reassembly.
|
|
18
|
+
* - Durability across restart: refs survive; nothing is purged at boot. Only a
|
|
19
|
+
* dedicated maintenance sweep removes EXPIRED refs, never live data.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
22
|
+
import { chmodSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
/** Announced recovery window: a ref is readable for 24h after it is written. */
|
|
25
|
+
export const PAYLOAD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
/** Proposed per-root ceiling (measured against the store dir before a write). */
|
|
27
|
+
export const PAYLOAD_ROOT_QUOTA_BYTES = 128 * 1024 * 1024;
|
|
28
|
+
/** Hard maximum a single read may return (also bounded again by the frame budget). */
|
|
29
|
+
export const PAYLOAD_MAX_READ_BYTES = 65536;
|
|
30
|
+
const REF_RE = /^[0-9a-f]{64}$/;
|
|
31
|
+
export class PayloadStoreError extends Error {
|
|
32
|
+
reason;
|
|
33
|
+
cause;
|
|
34
|
+
constructor(reason, cause, message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.reason = reason;
|
|
37
|
+
this.cause = cause;
|
|
38
|
+
this.name = "PayloadStoreError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function binPath(dir, ref) {
|
|
42
|
+
return join(dir, `${ref}.bin`);
|
|
43
|
+
}
|
|
44
|
+
function metaPath(dir, ref) {
|
|
45
|
+
return join(dir, `${ref}.json`);
|
|
46
|
+
}
|
|
47
|
+
function dirBytes(dir) {
|
|
48
|
+
let total = 0;
|
|
49
|
+
for (const entry of readdirSync(dir)) {
|
|
50
|
+
try {
|
|
51
|
+
total += statSync(join(dir, entry)).size;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* a racing unlink is not a measurement failure */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return total;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A payload store confined to `<root>/mcp-payloads/`. `scope` defaults to the
|
|
61
|
+
* root, so two roots (two tenants) never share a namespace.
|
|
62
|
+
*/
|
|
63
|
+
export function createPayloadStore(root, scope = root) {
|
|
64
|
+
const dir = join(root, "mcp-payloads");
|
|
65
|
+
function ensureDir() {
|
|
66
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
67
|
+
try {
|
|
68
|
+
chmodSync(dir, 0o700);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* best effort; a pre-existing dir may be owned differently in tests */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function sweepExpired(now = Date.now()) {
|
|
75
|
+
if (!existsSync(dir))
|
|
76
|
+
return 0;
|
|
77
|
+
let removed = 0;
|
|
78
|
+
for (const entry of readdirSync(dir)) {
|
|
79
|
+
if (!entry.endsWith(".json"))
|
|
80
|
+
continue;
|
|
81
|
+
const ref = entry.slice(0, -".json".length);
|
|
82
|
+
if (!REF_RE.test(ref))
|
|
83
|
+
continue;
|
|
84
|
+
try {
|
|
85
|
+
const meta = JSON.parse(readFileSync(join(dir, entry), "utf8"));
|
|
86
|
+
if (Date.parse(meta.expiresAt) <= now) {
|
|
87
|
+
try {
|
|
88
|
+
unlinkSync(metaPath(dir, ref));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* already gone */
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
unlinkSync(binPath(dir, ref));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* already gone */
|
|
98
|
+
}
|
|
99
|
+
removed += 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* a corrupt meta is left for a human sweep, never live source data */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return removed;
|
|
107
|
+
}
|
|
108
|
+
function persistOutput(bytes) {
|
|
109
|
+
try {
|
|
110
|
+
ensureDir();
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
throw new PayloadStoreError("recovery_unavailable", err.code ?? "mkdir_failed", `cannot create payload store directory: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
// Reclaim expired space before enforcing the quota; never evict live data.
|
|
116
|
+
let used = 0;
|
|
117
|
+
try {
|
|
118
|
+
sweepExpired();
|
|
119
|
+
used = dirBytes(dir);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
used = 0;
|
|
123
|
+
}
|
|
124
|
+
if (used + bytes.length > PAYLOAD_ROOT_QUOTA_BYTES) {
|
|
125
|
+
throw new PayloadStoreError("recovery_unavailable", "quota_exceeded", `payload store quota exceeded for ${dir}`);
|
|
126
|
+
}
|
|
127
|
+
const ref = randomBytes(32).toString("hex");
|
|
128
|
+
const sha256 = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
const meta = {
|
|
131
|
+
sha256,
|
|
132
|
+
totalBytes: bytes.length,
|
|
133
|
+
createdAt: new Date(now).toISOString(),
|
|
134
|
+
expiresAt: new Date(now + PAYLOAD_TTL_MS).toISOString(),
|
|
135
|
+
scope
|
|
136
|
+
};
|
|
137
|
+
const tmpBin = binPath(dir, `.${ref}.tmp`);
|
|
138
|
+
const tmpMeta = metaPath(dir, `.${ref}.tmp`);
|
|
139
|
+
try {
|
|
140
|
+
writeFileSync(tmpBin, bytes, { mode: 0o600, flag: "wx" });
|
|
141
|
+
writeFileSync(tmpMeta, `${JSON.stringify(meta)}\n`, { mode: 0o600, flag: "wx" });
|
|
142
|
+
renameSync(tmpBin, binPath(dir, ref));
|
|
143
|
+
renameSync(tmpMeta, metaPath(dir, ref));
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
for (const p of [tmpBin, tmpMeta, binPath(dir, ref), metaPath(dir, ref)]) {
|
|
147
|
+
try {
|
|
148
|
+
unlinkSync(p);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
/* best effort cleanup of a half-written record */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new PayloadStoreError("recovery_unavailable", err.code ?? "write_failed", `cannot persist payload: ${err.message}`);
|
|
155
|
+
}
|
|
156
|
+
return { ref, sha256, totalBytes: bytes.length, expiresAt: meta.expiresAt };
|
|
157
|
+
}
|
|
158
|
+
function readMeta(ref) {
|
|
159
|
+
const mp = metaPath(dir, ref);
|
|
160
|
+
let st;
|
|
161
|
+
try {
|
|
162
|
+
st = lstatSync(mp);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
if (!st.isFile())
|
|
168
|
+
return undefined; // refuse a symlink/dir masquerading as a ref
|
|
169
|
+
try {
|
|
170
|
+
return JSON.parse(readFileSync(mp, "utf8"));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function readPayload(ref, offset, maxBytes) {
|
|
177
|
+
if (typeof ref !== "string" || !REF_RE.test(ref)) {
|
|
178
|
+
return { error: "payload_not_found" };
|
|
179
|
+
}
|
|
180
|
+
const meta = readMeta(ref);
|
|
181
|
+
if (!meta || meta.scope !== scope)
|
|
182
|
+
return { error: "payload_not_found" };
|
|
183
|
+
if (Date.parse(meta.expiresAt) <= Date.now())
|
|
184
|
+
return { error: "payload_expired" };
|
|
185
|
+
if (!Number.isInteger(offset) || offset < 0 || offset > meta.totalBytes) {
|
|
186
|
+
return { error: "invalid_offset" };
|
|
187
|
+
}
|
|
188
|
+
const bp = binPath(dir, ref);
|
|
189
|
+
let bst;
|
|
190
|
+
try {
|
|
191
|
+
bst = lstatSync(bp);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return { error: "payload_not_found" };
|
|
195
|
+
}
|
|
196
|
+
if (!bst.isFile() || bst.size !== meta.totalBytes)
|
|
197
|
+
return { error: "payload_corrupt" };
|
|
198
|
+
const cap = Math.max(1, Math.min(Number.isInteger(maxBytes) && maxBytes > 0 ? maxBytes : PAYLOAD_MAX_READ_BYTES, PAYLOAD_MAX_READ_BYTES));
|
|
199
|
+
const remaining = meta.totalBytes - offset;
|
|
200
|
+
const chunkLen = Math.min(cap, remaining);
|
|
201
|
+
const buf = Buffer.allocUnsafe(chunkLen);
|
|
202
|
+
let fd;
|
|
203
|
+
try {
|
|
204
|
+
fd = openSync(bp, "r");
|
|
205
|
+
let read = 0;
|
|
206
|
+
while (read < chunkLen) {
|
|
207
|
+
const n = readSync(fd, buf, read, chunkLen - read, offset + read);
|
|
208
|
+
if (n === 0)
|
|
209
|
+
break;
|
|
210
|
+
read += n;
|
|
211
|
+
}
|
|
212
|
+
if (read !== chunkLen)
|
|
213
|
+
return { error: "payload_corrupt" };
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return { error: "payload_corrupt" };
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
if (fd !== undefined) {
|
|
220
|
+
try {
|
|
221
|
+
closeSync(fd);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* fd closed on error already */
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const nextOffset = offset + chunkLen;
|
|
229
|
+
return {
|
|
230
|
+
ref,
|
|
231
|
+
encoding: "base64",
|
|
232
|
+
data: buf.toString("base64"),
|
|
233
|
+
offset,
|
|
234
|
+
nextOffset: nextOffset >= meta.totalBytes ? null : nextOffset,
|
|
235
|
+
totalBytes: meta.totalBytes,
|
|
236
|
+
sha256: meta.sha256,
|
|
237
|
+
expiresAt: meta.expiresAt
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return { scope, persistOutput, readPayload, sweepExpired };
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=payload-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload-store.js","sourceRoot":"","sources":["../../../src/runtime/mcp/payload-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-phase MCP boot/serve tracing (lot L0 — measurement & diagnosis only).
|
|
3
|
+
*
|
|
4
|
+
* Emits correlated, closed-field JSONL spans to an injected stderr writer so a
|
|
5
|
+
* host or an out-of-process measurement probe can reconstruct the whole
|
|
6
|
+
* `mcp-serve` lifecycle — process start, identity resolution, lock waits,
|
|
7
|
+
* registry reads, transport `initialize` / `tools/list` / first tool — WITHOUT
|
|
8
|
+
* pulling in the CLI or the heavy runtime. It has three properties the rest of
|
|
9
|
+
* the fix relies on:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Passive.** A measurement callback can never fail the MCP protocol:
|
|
12
|
+
* every emit is wrapped, a writer throw is swallowed, and the trace has no
|
|
13
|
+
* side effect on the JSON-RPC stream (it only ever writes to stderr).
|
|
14
|
+
* 2. **Confidential.** Fields are a fixed whitelist and every string is
|
|
15
|
+
* sanitized + length-capped, so no PEM, token, lock payload or binding
|
|
16
|
+
* content can ever reach an event — even if a caller passes it by mistake.
|
|
17
|
+
* 3. **Monotonic + correlated.** Every event carries `attemptId` (a locally
|
|
18
|
+
* minted UUID — a child process cannot inherit an identity from the MCP
|
|
19
|
+
* client), `pid`, a per-trace `seq`, and a monotonic `monotonicMs`. Two
|
|
20
|
+
* processes are correlated by matching events, never by subtracting their
|
|
21
|
+
* independent monotonic clocks.
|
|
22
|
+
*
|
|
23
|
+
* `H2A_MCP_TRACE=1` turns on the detailed spans (`begin`/`end` of internal
|
|
24
|
+
* phases). Lifecycle milestones and failure diagnostics stay visible without
|
|
25
|
+
* the flag.
|
|
26
|
+
*/
|
|
27
|
+
/** Log line prefix; every trace event is `${TRACE_LINE_PREFIX}${json}\n` on stderr. */
|
|
28
|
+
export declare const TRACE_LINE_PREFIX = "h2a.mcp.phase ";
|
|
29
|
+
/** Env flag that turns on detailed (`begin`/`end`) spans. */
|
|
30
|
+
export declare const TRACE_DETAIL_ENV = "H2A_MCP_TRACE";
|
|
31
|
+
export type McpTraceRole = "server" | "identity-child";
|
|
32
|
+
export type McpTraceEventKind = "begin" | "end" | "error" | "sample";
|
|
33
|
+
/** The wire event. `v:1` is the schema version; keep additions backward-safe. */
|
|
34
|
+
export interface McpTraceEvent {
|
|
35
|
+
readonly v: 1;
|
|
36
|
+
readonly attemptId: string;
|
|
37
|
+
readonly pid: number;
|
|
38
|
+
readonly role: McpTraceRole;
|
|
39
|
+
readonly seq: number;
|
|
40
|
+
readonly monotonicMs: number;
|
|
41
|
+
readonly phase: string;
|
|
42
|
+
readonly event: McpTraceEventKind;
|
|
43
|
+
readonly durationMs?: number;
|
|
44
|
+
readonly requestId?: string | number;
|
|
45
|
+
readonly method?: string;
|
|
46
|
+
readonly tool?: string;
|
|
47
|
+
readonly bytes?: number;
|
|
48
|
+
readonly waitMs?: number;
|
|
49
|
+
readonly holdMs?: number;
|
|
50
|
+
readonly code?: string | number;
|
|
51
|
+
}
|
|
52
|
+
/** The only fields a caller may attach. Everything else is dropped. */
|
|
53
|
+
export interface McpTraceFields {
|
|
54
|
+
readonly requestId?: string | number;
|
|
55
|
+
readonly method?: string;
|
|
56
|
+
readonly tool?: string;
|
|
57
|
+
readonly bytes?: number;
|
|
58
|
+
readonly waitMs?: number;
|
|
59
|
+
readonly holdMs?: number;
|
|
60
|
+
readonly code?: string | number;
|
|
61
|
+
readonly durationMs?: number;
|
|
62
|
+
}
|
|
63
|
+
export interface McpTraceOptions {
|
|
64
|
+
readonly role?: McpTraceRole;
|
|
65
|
+
/** Locally minted UUID; never taken from the MCP client. */
|
|
66
|
+
readonly attemptId?: string;
|
|
67
|
+
readonly pid?: number;
|
|
68
|
+
/** Injected sink; defaults to a swallow-on-throw `process.stderr` writer. */
|
|
69
|
+
readonly writer?: (line: string) => void;
|
|
70
|
+
/** Monotonic clock; defaults to `performance.now`. */
|
|
71
|
+
readonly now?: () => number;
|
|
72
|
+
/** Force detailed spans on/off; defaults to `env[H2A_MCP_TRACE] === "1"`. */
|
|
73
|
+
readonly detailed?: boolean;
|
|
74
|
+
readonly env?: Record<string, string | undefined>;
|
|
75
|
+
}
|
|
76
|
+
export interface McpTrace {
|
|
77
|
+
readonly attemptId: string;
|
|
78
|
+
readonly role: McpTraceRole;
|
|
79
|
+
readonly pid: number;
|
|
80
|
+
readonly detailed: boolean;
|
|
81
|
+
/** A lifecycle milestone (`sample`). Always emitted — this is a diagnostic. */
|
|
82
|
+
phase(name: string, fields?: McpTraceFields): void;
|
|
83
|
+
/** Start of a detailed span. Emitted only when `detailed`. */
|
|
84
|
+
begin(name: string, fields?: McpTraceFields): void;
|
|
85
|
+
/** End of a detailed span. Emitted only when `detailed`. */
|
|
86
|
+
end(name: string, fields?: McpTraceFields): void;
|
|
87
|
+
/** A failure. Always emitted, flag or not. */
|
|
88
|
+
error(name: string, fields?: McpTraceFields): void;
|
|
89
|
+
/** Wrap `fn`: `begin`/`end` when detailed, an `error` event on throw always. */
|
|
90
|
+
span<T>(name: string, fn: () => T, fields?: McpTraceFields): T;
|
|
91
|
+
/** A correlated sub-trace (same `attemptId`) for a child role. */
|
|
92
|
+
child(role: McpTraceRole, options?: Pick<McpTraceOptions, "pid" | "writer">): McpTrace;
|
|
93
|
+
}
|
|
94
|
+
/** Create a fresh trace. `attemptId` defaults to a locally minted UUID. */
|
|
95
|
+
export declare function createMcpTrace(options?: McpTraceOptions): McpTrace;
|
|
96
|
+
export declare function setActiveMcpTrace(trace: McpTrace | undefined): void;
|
|
97
|
+
export declare function getActiveMcpTrace(): McpTrace | undefined;
|
|
98
|
+
//# sourceMappingURL=phase-trace.d.ts.map
|