@sentropic/h2a 0.97.2 → 0.97.4
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 +13 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +94 -37
- 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 +2 -0
- package/dist/mcp.js.map +1 -1
- package/dist/runtime/identity/bindings.d.ts +33 -0
- package/dist/runtime/identity/bindings.d.ts.map +1 -1
- package/dist/runtime/identity/bindings.js +65 -2
- package/dist/runtime/identity/bindings.js.map +1 -1
- package/dist/runtime/identity/index.d.ts +4 -4
- package/dist/runtime/identity/index.d.ts.map +1 -1
- package/dist/runtime/identity/index.js +2 -2
- package/dist/runtime/identity/index.js.map +1 -1
- package/dist/runtime/identity/live.d.ts +63 -0
- package/dist/runtime/identity/live.d.ts.map +1 -1
- package/dist/runtime/identity/live.js +346 -65
- package/dist/runtime/identity/live.js.map +1 -1
- package/dist/runtime/identity/worker.d.ts +19 -0
- package/dist/runtime/identity/worker.d.ts.map +1 -0
- package/dist/runtime/identity/worker.js +189 -0
- package/dist/runtime/identity/worker.js.map +1 -0
- package/dist/runtime/local-files/locks.d.ts +34 -0
- package/dist/runtime/local-files/locks.d.ts.map +1 -1
- package/dist/runtime/local-files/locks.js +55 -2
- package/dist/runtime/local-files/locks.js.map +1 -1
- package/dist/runtime/local-files/store.d.ts +21 -0
- package/dist/runtime/local-files/store.d.ts.map +1 -1
- package/dist/runtime/local-files/store.js +79 -10
- 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/identity-state.d.ts +131 -0
- package/dist/runtime/mcp/identity-state.d.ts.map +1 -0
- package/dist/runtime/mcp/identity-state.js +288 -0
- package/dist/runtime/mcp/identity-state.js.map +1 -0
- package/dist/runtime/mcp/index.d.ts +2 -0
- package/dist/runtime/mcp/index.d.ts.map +1 -1
- package/dist/runtime/mcp/index.js +2 -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 +36 -1
- package/dist/runtime/mcp/server.d.ts.map +1 -1
- package/dist/runtime/mcp/server.js +162 -5
- package/dist/runtime/mcp/server.js.map +1 -1
- package/dist/runtime/mcp/sessions.d.ts +12 -2
- package/dist/runtime/mcp/sessions.d.ts.map +1 -1
- package/dist/runtime/mcp/sessions.js +15 -3
- package/dist/runtime/mcp/sessions.js.map +1 -1
- package/dist/runtime/mcp/stdio.d.ts +21 -0
- package/dist/runtime/mcp/stdio.d.ts.map +1 -1
- package/dist/runtime/mcp/stdio.js +378 -98
- 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 +47 -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 +6 -0
- package/dist/runtime/mcp-http/readonly-allowlist.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 — identity-independent MCP connection + asynchronous identity readiness.
|
|
3
|
+
*
|
|
4
|
+
* The MCP transport (initialize / tools/list) must answer IMMEDIATELY, without
|
|
5
|
+
* waiting on the shared-identity critical section (the keypair read, the
|
|
6
|
+
* registry parse, and above all the identity/registry lock wait). Those run in a
|
|
7
|
+
* dedicated, non-detached CHILD process (`../identity/worker.js`) so they never
|
|
8
|
+
* block the parent's event loop — a `Promise`/`setImmediate` around the sync
|
|
9
|
+
* lock in the PARENT would not help, because the lock wait (and the ~17 MB
|
|
10
|
+
* registry parse) are synchronous and would still stall `initialize`.
|
|
11
|
+
*
|
|
12
|
+
* This module owns the readiness STATE MACHINE and the single 20 s deadline
|
|
13
|
+
* measured from entering `identity_pending`. Activation (opening the real
|
|
14
|
+
* presence session, reading the private key, publishing the correlated readiness
|
|
15
|
+
* ACK, arming the live signer) is delegated to the caller through `activate`,
|
|
16
|
+
* because that side lives with the SessionRegistry in the stdio transport. The
|
|
17
|
+
* controller only transitions to `identity_ready` once activation reports
|
|
18
|
+
* success — never on the child's raw result, never with a provisional key, never
|
|
19
|
+
* with an early availability ACK.
|
|
20
|
+
*/
|
|
21
|
+
import type { H2ASendSigner } from "../send.js";
|
|
22
|
+
/** The single identity deadline (ms), measured from entering identity_pending. */
|
|
23
|
+
export declare const MCP_IDENTITY_TIMEOUT_MS = 20000;
|
|
24
|
+
export type IdentityFailureCode = "identity_timeout" | "storage_readonly" | "storage_permission_denied" | "identity_worker_failed" | "identity_proof_failed" | "identity_storage_failed" | "session_open_failed" | "readiness_ack_failed";
|
|
25
|
+
export type McpIdentityStatus = {
|
|
26
|
+
state: "identity_disabled";
|
|
27
|
+
} | {
|
|
28
|
+
state: "identity_pending";
|
|
29
|
+
attemptId: string;
|
|
30
|
+
elapsedMs: number;
|
|
31
|
+
timeoutMs: 20000;
|
|
32
|
+
} | {
|
|
33
|
+
state: "identity_ready";
|
|
34
|
+
attemptId: string;
|
|
35
|
+
instance: string;
|
|
36
|
+
sessionId: string;
|
|
37
|
+
signingAvailable: boolean;
|
|
38
|
+
} | {
|
|
39
|
+
state: "identity_failed";
|
|
40
|
+
attemptId: string;
|
|
41
|
+
cause: IdentityFailureCode;
|
|
42
|
+
message: string;
|
|
43
|
+
retryable: false;
|
|
44
|
+
elapsedMs: number;
|
|
45
|
+
};
|
|
46
|
+
export interface McpIdentityController {
|
|
47
|
+
status(): McpIdentityStatus;
|
|
48
|
+
start(): void;
|
|
49
|
+
cancel(reason: "transport_closed" | "signal"): void;
|
|
50
|
+
signer(): H2ASendSigner | undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The identity attributes the worker resolves and hands back over IPC. It
|
|
54
|
+
* carries NO private key material — only paths, which the parent re-validates
|
|
55
|
+
* against `identityKeyPaths(root, instance)` before reading.
|
|
56
|
+
*/
|
|
57
|
+
export interface ResolvedIdentityMessage {
|
|
58
|
+
readonly instance: string;
|
|
59
|
+
readonly host: string;
|
|
60
|
+
readonly workspace?: unknown;
|
|
61
|
+
readonly name?: string;
|
|
62
|
+
readonly action: "override" | "reclaim" | "mint";
|
|
63
|
+
readonly providerSessionId?: string;
|
|
64
|
+
readonly providerSessionSource?: string;
|
|
65
|
+
readonly privateKeyPath: string;
|
|
66
|
+
readonly publicKeyPath: string;
|
|
67
|
+
readonly migrationNotice?: string;
|
|
68
|
+
readonly legacyInstance?: string;
|
|
69
|
+
readonly delegationEligible?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** The deferred identity request the CLI hands to the transport (no lock work). */
|
|
72
|
+
export interface McpIdentityRequest {
|
|
73
|
+
readonly root: string;
|
|
74
|
+
readonly host: string;
|
|
75
|
+
readonly cwd: string;
|
|
76
|
+
readonly explicitInstance?: string;
|
|
77
|
+
readonly name?: string;
|
|
78
|
+
readonly scopes?: readonly string[];
|
|
79
|
+
readonly declaredCapabilities?: readonly string[];
|
|
80
|
+
/** Provider env captured from the launching session (never re-derived in child). */
|
|
81
|
+
readonly providerEnv?: Readonly<Record<string, string>>;
|
|
82
|
+
}
|
|
83
|
+
/** Result of a caller-supplied activation attempt (session + ACK + signer). */
|
|
84
|
+
export type ActivationResult = {
|
|
85
|
+
ok: true;
|
|
86
|
+
sessionId: string;
|
|
87
|
+
signer?: H2ASendSigner;
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
cause: IdentityFailureCode;
|
|
91
|
+
message: string;
|
|
92
|
+
};
|
|
93
|
+
export interface CreateIdentityControllerOptions {
|
|
94
|
+
readonly request: McpIdentityRequest;
|
|
95
|
+
/**
|
|
96
|
+
* Perform the real activation once the worker resolved a valid identity and
|
|
97
|
+
* the deadline still holds: open the presence session, read the private key,
|
|
98
|
+
* publish the correlated readiness ACK, build the live signer. Returning
|
|
99
|
+
* `{ok:false}` fails the identity terminally (no partial availability).
|
|
100
|
+
*/
|
|
101
|
+
readonly activate: (identity: ResolvedIdentityMessage) => ActivationResult;
|
|
102
|
+
/** Diagnostic sink (stderr). Never protocol traffic. */
|
|
103
|
+
readonly log?: (line: string) => void;
|
|
104
|
+
/** Test seam: fork a worker child. Production uses the real fork below. */
|
|
105
|
+
readonly spawnWorker?: (request: McpIdentityRequest, budgetMs: number) => IdentityWorkerHandle;
|
|
106
|
+
/** Test seam: monotonic clock in ns. Defaults to process.hrtime.bigint. */
|
|
107
|
+
readonly nowNs?: () => bigint;
|
|
108
|
+
/** Test seam: deadline in ms. Defaults to MCP_IDENTITY_TIMEOUT_MS. */
|
|
109
|
+
readonly timeoutMs?: number;
|
|
110
|
+
}
|
|
111
|
+
/** Minimal handle over the identity worker child (real fork or test double). */
|
|
112
|
+
export interface IdentityWorkerHandle {
|
|
113
|
+
onResolved(cb: (identity: ResolvedIdentityMessage) => void): void;
|
|
114
|
+
onError(cb: (cause: IdentityFailureCode, message: string) => void): void;
|
|
115
|
+
/** Fires when the child exits WITHOUT having produced a result. */
|
|
116
|
+
onExitWithoutResult(cb: () => void): void;
|
|
117
|
+
cancel(reason: string): void;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Real worker fork. Non-detached, argv-only, stdout IGNORED (it must never write
|
|
121
|
+
* protocol traffic), stderr piped for diagnostics, IPC typed. cwd/provider env
|
|
122
|
+
* are captured explicitly so the child does not inherit an artificial
|
|
123
|
+
* conversation. No shell, and NO PEM ever crosses IPC.
|
|
124
|
+
*/
|
|
125
|
+
export declare function forkIdentityWorker(request: McpIdentityRequest, budgetMs: number, log?: (line: string) => void): IdentityWorkerHandle;
|
|
126
|
+
/**
|
|
127
|
+
* Build the identity controller for one MCP attachment. It starts inert and
|
|
128
|
+
* transitions `identity_pending → identity_ready | identity_failed` exactly once.
|
|
129
|
+
*/
|
|
130
|
+
export declare function createIdentityController(options: CreateIdentityControllerOptions): McpIdentityController;
|
|
131
|
+
//# sourceMappingURL=identity-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-state.d.ts","sourceRoot":"","sources":["../../../src/runtime/mcp/identity-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 — identity-independent MCP connection + asynchronous identity readiness.
|
|
3
|
+
*
|
|
4
|
+
* The MCP transport (initialize / tools/list) must answer IMMEDIATELY, without
|
|
5
|
+
* waiting on the shared-identity critical section (the keypair read, the
|
|
6
|
+
* registry parse, and above all the identity/registry lock wait). Those run in a
|
|
7
|
+
* dedicated, non-detached CHILD process (`../identity/worker.js`) so they never
|
|
8
|
+
* block the parent's event loop — a `Promise`/`setImmediate` around the sync
|
|
9
|
+
* lock in the PARENT would not help, because the lock wait (and the ~17 MB
|
|
10
|
+
* registry parse) are synchronous and would still stall `initialize`.
|
|
11
|
+
*
|
|
12
|
+
* This module owns the readiness STATE MACHINE and the single 20 s deadline
|
|
13
|
+
* measured from entering `identity_pending`. Activation (opening the real
|
|
14
|
+
* presence session, reading the private key, publishing the correlated readiness
|
|
15
|
+
* ACK, arming the live signer) is delegated to the caller through `activate`,
|
|
16
|
+
* because that side lives with the SessionRegistry in the stdio transport. The
|
|
17
|
+
* controller only transitions to `identity_ready` once activation reports
|
|
18
|
+
* success — never on the child's raw result, never with a provisional key, never
|
|
19
|
+
* with an early availability ACK.
|
|
20
|
+
*/
|
|
21
|
+
import { fork } from "node:child_process";
|
|
22
|
+
import { identityKeyPaths } from "../identity/live.js";
|
|
23
|
+
import { getActiveMcpTrace } from "./phase-trace.js";
|
|
24
|
+
/** The single identity deadline (ms), measured from entering identity_pending. */
|
|
25
|
+
export const MCP_IDENTITY_TIMEOUT_MS = 20_000;
|
|
26
|
+
function randomAttemptId() {
|
|
27
|
+
// A short, non-secret correlation id for the attempt (never a nonce/key).
|
|
28
|
+
return `att:${Math.random().toString(16).slice(2, 10)}${Date.now().toString(16)}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Real worker fork. Non-detached, argv-only, stdout IGNORED (it must never write
|
|
32
|
+
* protocol traffic), stderr piped for diagnostics, IPC typed. cwd/provider env
|
|
33
|
+
* are captured explicitly so the child does not inherit an artificial
|
|
34
|
+
* conversation. No shell, and NO PEM ever crosses IPC.
|
|
35
|
+
*/
|
|
36
|
+
export function forkIdentityWorker(request, budgetMs, log) {
|
|
37
|
+
const capturedEnv = { ...process.env, ...(request.providerEnv ?? {}) };
|
|
38
|
+
let child;
|
|
39
|
+
let resolvedCb;
|
|
40
|
+
let errorCb;
|
|
41
|
+
let exitCb;
|
|
42
|
+
let sawResult = false;
|
|
43
|
+
let started = false;
|
|
44
|
+
const ensureStarted = () => {
|
|
45
|
+
if (started)
|
|
46
|
+
return;
|
|
47
|
+
started = true;
|
|
48
|
+
try {
|
|
49
|
+
child = fork(new URL("../identity/worker.js", import.meta.url), [], {
|
|
50
|
+
cwd: request.cwd,
|
|
51
|
+
env: capturedEnv,
|
|
52
|
+
stdio: ["ignore", "ignore", "pipe", "ipc"]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
queueMicrotask(() => errorCb?.("identity_worker_failed", err instanceof Error ? err.message : String(err)));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Forward the worker's stderr LINE BY LINE so a `h2a.mcp.phase …` trace span
|
|
60
|
+
// (L0, role=identity-child) is never split across a chunk boundary when it is
|
|
61
|
+
// re-emitted on the parent's stderr. Diagnostics flow through unchanged.
|
|
62
|
+
let stderrBuf = "";
|
|
63
|
+
child.stderr?.on("data", (chunk) => {
|
|
64
|
+
stderrBuf += String(chunk);
|
|
65
|
+
let nl;
|
|
66
|
+
while ((nl = stderrBuf.indexOf("\n")) !== -1) {
|
|
67
|
+
const line = stderrBuf.slice(0, nl);
|
|
68
|
+
stderrBuf = stderrBuf.slice(nl + 1);
|
|
69
|
+
if (line.length > 0)
|
|
70
|
+
log?.(`identity-worker: ${line}`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
child.stderr?.on("end", () => {
|
|
74
|
+
if (stderrBuf.trimEnd().length > 0)
|
|
75
|
+
log?.(`identity-worker: ${stderrBuf.trimEnd()}`);
|
|
76
|
+
stderrBuf = "";
|
|
77
|
+
});
|
|
78
|
+
child.on("message", (msg) => {
|
|
79
|
+
const m = msg;
|
|
80
|
+
if (!m || typeof m !== "object")
|
|
81
|
+
return;
|
|
82
|
+
if (m.kind === "identity_resolved") {
|
|
83
|
+
sawResult = true;
|
|
84
|
+
resolvedCb?.(m.identity);
|
|
85
|
+
}
|
|
86
|
+
else if (m.kind === "identity_error") {
|
|
87
|
+
sawResult = true;
|
|
88
|
+
const e = m;
|
|
89
|
+
errorCb?.(e.cause ?? "identity_worker_failed", e.message ?? "identity worker error");
|
|
90
|
+
}
|
|
91
|
+
// A `cancel_ack` needs no parent action beyond letting the child exit.
|
|
92
|
+
});
|
|
93
|
+
child.on("error", (err) => {
|
|
94
|
+
if (sawResult)
|
|
95
|
+
return;
|
|
96
|
+
errorCb?.("identity_worker_failed", err instanceof Error ? err.message : String(err));
|
|
97
|
+
});
|
|
98
|
+
child.on("exit", () => {
|
|
99
|
+
if (!sawResult)
|
|
100
|
+
exitCb?.();
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
// Correlate the worker's L0 spans to THIS attempt's trace (same attemptId,
|
|
104
|
+
// role=identity-child) so the relocated identity/lock/registry spans stay
|
|
105
|
+
// joinable with the parent's server-role boot spans.
|
|
106
|
+
const traceAttemptId = getActiveMcpTrace()?.attemptId;
|
|
107
|
+
child.send({
|
|
108
|
+
kind: "resolve_identity",
|
|
109
|
+
v: 1,
|
|
110
|
+
request,
|
|
111
|
+
budgetMs,
|
|
112
|
+
...(traceAttemptId !== undefined ? { traceAttemptId } : {})
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
errorCb?.("identity_worker_failed", err instanceof Error ? err.message : String(err));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
onResolved(cb) {
|
|
121
|
+
resolvedCb = cb;
|
|
122
|
+
ensureStarted();
|
|
123
|
+
},
|
|
124
|
+
onError(cb) {
|
|
125
|
+
errorCb = cb;
|
|
126
|
+
},
|
|
127
|
+
onExitWithoutResult(cb) {
|
|
128
|
+
exitCb = cb;
|
|
129
|
+
},
|
|
130
|
+
cancel(reason) {
|
|
131
|
+
try {
|
|
132
|
+
child?.send({ kind: "cancel", reason });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* channel already gone; the child's own finally releases its lock */
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Build the identity controller for one MCP attachment. It starts inert and
|
|
142
|
+
* transitions `identity_pending → identity_ready | identity_failed` exactly once.
|
|
143
|
+
*/
|
|
144
|
+
export function createIdentityController(options) {
|
|
145
|
+
const timeoutMs = options.timeoutMs ?? MCP_IDENTITY_TIMEOUT_MS;
|
|
146
|
+
const nowNs = options.nowNs ?? process.hrtime.bigint;
|
|
147
|
+
const spawnWorker = options.spawnWorker ??
|
|
148
|
+
((req, budget) => forkIdentityWorker(req, budget, options.log));
|
|
149
|
+
let state = { state: "identity_disabled" };
|
|
150
|
+
let attemptId = "";
|
|
151
|
+
let startedNs = 0n;
|
|
152
|
+
let deadlineTimer;
|
|
153
|
+
let worker;
|
|
154
|
+
let liveSigner;
|
|
155
|
+
let terminal = false;
|
|
156
|
+
let cancelled = false;
|
|
157
|
+
const elapsedMs = () => Number((nowNs() - startedNs) / 1000000n);
|
|
158
|
+
const clearTimer = () => {
|
|
159
|
+
if (deadlineTimer) {
|
|
160
|
+
clearTimeout(deadlineTimer);
|
|
161
|
+
deadlineTimer = undefined;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const fail = (cause, message) => {
|
|
165
|
+
if (terminal)
|
|
166
|
+
return;
|
|
167
|
+
terminal = true;
|
|
168
|
+
clearTimer();
|
|
169
|
+
state = {
|
|
170
|
+
state: "identity_failed",
|
|
171
|
+
attemptId,
|
|
172
|
+
cause,
|
|
173
|
+
message,
|
|
174
|
+
retryable: false,
|
|
175
|
+
elapsedMs: elapsedMs()
|
|
176
|
+
};
|
|
177
|
+
liveSigner = undefined;
|
|
178
|
+
// L0 trace reuse: the identity lifecycle on the PARENT (the deep provider /
|
|
179
|
+
// registry / lock spans now run in the child, off this event loop).
|
|
180
|
+
getActiveMcpTrace()?.phase("identity_failed", { code: cause });
|
|
181
|
+
options.log?.(`identity failed (${cause}): ${message}`);
|
|
182
|
+
try {
|
|
183
|
+
worker?.cancel("failed");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
/* best effort */
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const onResolved = (identity) => {
|
|
190
|
+
if (terminal || cancelled)
|
|
191
|
+
return;
|
|
192
|
+
// Deadline re-check at the activation boundary: a late worker result never
|
|
193
|
+
// reactivates the connection.
|
|
194
|
+
if (elapsedMs() >= timeoutMs) {
|
|
195
|
+
fail("identity_timeout", `identity did not become ready within ${timeoutMs}ms`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Validate the returned key paths against the canonical layout for this
|
|
199
|
+
// instance — NEVER read an arbitrary path the child claimed. An explicit
|
|
200
|
+
// `--instance` override resolves with NO key paths (presence-only, no
|
|
201
|
+
// signer); that is allowed and only skips this check.
|
|
202
|
+
if (identity.privateKeyPath) {
|
|
203
|
+
const expected = identityKeyPaths(options.request.root, identity.instance);
|
|
204
|
+
if (identity.privateKeyPath !== expected.privateKeyPath ||
|
|
205
|
+
identity.publicKeyPath !== expected.publicKeyPath) {
|
|
206
|
+
fail("identity_proof_failed", "resolved identity key paths do not match the canonical layout");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
let result;
|
|
211
|
+
try {
|
|
212
|
+
result = options.activate(identity);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
fail("session_open_failed", err instanceof Error ? err.message : String(err));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!result.ok) {
|
|
219
|
+
fail(result.cause, result.message);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// F1: a FULLY-SUCCESSFUL activation is terminal-ready. We do NOT re-check the
|
|
223
|
+
// deadline here: activation already opened the presence session, armed the
|
|
224
|
+
// signer/wake and published the correlated readiness ACK — failing now would
|
|
225
|
+
// leave a live ACK + presence + wake behind a `failed` state (a wedged
|
|
226
|
+
// connection). The deadline is enforced BEFORE activation (above) and by the
|
|
227
|
+
// parent timer; if activation itself fails, `activate` rolls its own partial
|
|
228
|
+
// work back before returning `{ok:false}`.
|
|
229
|
+
terminal = true;
|
|
230
|
+
clearTimer();
|
|
231
|
+
liveSigner = result.signer;
|
|
232
|
+
state = {
|
|
233
|
+
state: "identity_ready",
|
|
234
|
+
attemptId,
|
|
235
|
+
instance: identity.instance,
|
|
236
|
+
sessionId: result.sessionId,
|
|
237
|
+
signingAvailable: liveSigner !== undefined
|
|
238
|
+
};
|
|
239
|
+
getActiveMcpTrace()?.phase("identity_ready", {
|
|
240
|
+
code: liveSigner ? "signing" : "presence_only"
|
|
241
|
+
});
|
|
242
|
+
options.log?.(`identity ready: ${identity.instance} (signing ${liveSigner ? "available" : "unavailable"})`);
|
|
243
|
+
};
|
|
244
|
+
return {
|
|
245
|
+
status() {
|
|
246
|
+
if (state.state === "identity_pending") {
|
|
247
|
+
return { state: "identity_pending", attemptId, elapsedMs: elapsedMs(), timeoutMs: 20000 };
|
|
248
|
+
}
|
|
249
|
+
return state;
|
|
250
|
+
},
|
|
251
|
+
start() {
|
|
252
|
+
if (state.state !== "identity_disabled")
|
|
253
|
+
return;
|
|
254
|
+
attemptId = randomAttemptId();
|
|
255
|
+
startedNs = nowNs();
|
|
256
|
+
state = { state: "identity_pending", attemptId, elapsedMs: 0, timeoutMs: 20000 };
|
|
257
|
+
// L0 trace reuse: mark that identity entered the async pending window on the
|
|
258
|
+
// parent (initialize/tools-list are already answerable — identity is NOT on
|
|
259
|
+
// the boot critical path anymore, which is the #249 fix).
|
|
260
|
+
getActiveMcpTrace()?.phase("identity_pending");
|
|
261
|
+
deadlineTimer = setTimeout(() => {
|
|
262
|
+
fail("identity_timeout", `identity did not become ready within ${timeoutMs}ms`);
|
|
263
|
+
}, timeoutMs);
|
|
264
|
+
deadlineTimer.unref?.();
|
|
265
|
+
worker = spawnWorker(options.request, timeoutMs);
|
|
266
|
+
worker.onError((cause, message) => fail(cause, message));
|
|
267
|
+
worker.onExitWithoutResult(() => fail("identity_worker_failed", "identity worker exited without a result"));
|
|
268
|
+
worker.onResolved(onResolved);
|
|
269
|
+
},
|
|
270
|
+
cancel(reason) {
|
|
271
|
+
cancelled = true;
|
|
272
|
+
clearTimer();
|
|
273
|
+
try {
|
|
274
|
+
worker?.cancel(reason);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
/* best effort */
|
|
278
|
+
}
|
|
279
|
+
// Transport is gone: no activation may follow. Keep the last observable
|
|
280
|
+
// state for any in-flight status read, but disarm the signer.
|
|
281
|
+
liveSigner = undefined;
|
|
282
|
+
},
|
|
283
|
+
signer() {
|
|
284
|
+
return liveSigner;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=identity-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-state.js","sourceRoot":"","sources":["../../../src/runtime/mcp/identity-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,4 +5,6 @@ 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, type H2aRunExecutor, type H2aRunInvocation, type H2aRunRequest } from "./agent-launch.js";
|
|
6
6
|
export { SessionRegistry, type OpenSessionRequest, type SessionRegistryOptions } from "./sessions.js";
|
|
7
7
|
export { NotificationDispatcher, type McpPushNotification, type NotificationSink } from "./notifications.js";
|
|
8
|
+
export { createMcpTrace, getActiveMcpTrace, setActiveMcpTrace, TRACE_LINE_PREFIX, TRACE_DETAIL_ENV, type McpTrace, type McpTraceEvent, type McpTraceFields, type McpTraceOptions, type McpTraceRole, type McpTraceEventKind } from "./phase-trace.js";
|
|
9
|
+
export { createIdentityController, forkIdentityWorker, MCP_IDENTITY_TIMEOUT_MS, type ActivationResult, type CreateIdentityControllerOptions, type IdentityFailureCode, type IdentityWorkerHandle, type McpIdentityController, type McpIdentityRequest, type McpIdentityStatus, type ResolvedIdentityMessage } from "./identity-state.js";
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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;AAE1B,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACpC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC7B,MAAM,qBAAqB,CAAC"}
|
|
@@ -5,4 +5,6 @@ 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";
|
|
9
|
+
export { createIdentityController, forkIdentityWorker, MCP_IDENTITY_TIMEOUT_MS } from "./identity-state.js";
|
|
8
10
|
//# 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;AAE1B,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,uBAAuB,EASxB,MAAM,qBAAqB,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
|