immune-brain 3.2.2 → 3.4.0
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/README.md +2 -2
- package/README.zh-CN.md +3 -3
- package/package.json +5 -3
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +19 -73
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +1 -2
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +2 -2
- package/plugins/immune-brain/dist/BASELINE.md +1 -1
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +317 -316
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-brainstorm.md +1 -1
- package/plugins/immune-brain/dist/imm-loop.md +15 -10
- package/plugins/immune-brain/dist/imm-planner.md +22 -17
- package/plugins/immune-brain/hooks/hooks.json +0 -10
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +0 -8
- package/plugins/immune-brain/runtime/claude/capability.ts +4 -10
- package/plugins/immune-brain/runtime/claude/interaction.ts +55 -22
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +63 -73
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +233 -55
- package/plugins/immune-brain/runtime/claude/review_host.ts +2 -138
- package/plugins/immune-brain/runtime/kernel/application.ts +0 -1
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +1 -7
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +0 -7
- package/plugins/immune-brain/runtime/kernel/completion.ts +1 -3
- package/plugins/immune-brain/runtime/kernel/reducer.ts +0 -31
- package/plugins/immune-brain/runtime/kernel/types.ts +0 -2
- package/plugins/immune-brain/runtime/kernel/validation.ts +1 -3
- package/plugins/immune-brain/runtime/plugin_version.ts +2 -0
- package/plugins/immune-brain/skills/BASELINE.md +1 -1
- package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +4 -0
- package/plugins/immune-brain/skills/imm-loop/SKILL.md +4 -0
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +6 -0
|
@@ -2,11 +2,12 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// plugins/immune-brain/runtime/claude/mcp_server.ts
|
|
5
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
5
6
|
import { createInterface } from "node:readline";
|
|
6
7
|
import { stdin, stdout } from "node:process";
|
|
7
8
|
|
|
8
9
|
// plugins/immune-brain/runtime/claude/capability.ts
|
|
9
|
-
var MIN_CLAUDE_CODE_VERSION = "2.1.
|
|
10
|
+
var MIN_CLAUDE_CODE_VERSION = "2.1.236";
|
|
10
11
|
var HOST_ID = "claude-code";
|
|
11
12
|
var CORE_CONTRACT = "assurance_kernel/host_adapter/claude-code/v1";
|
|
12
13
|
function parseSemver(value) {
|
|
@@ -22,11 +23,6 @@ function compareSemver(left, right) {
|
|
|
22
23
|
throw new Error(`invalid semver: ${!a ? left : right}`);
|
|
23
24
|
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
24
25
|
}
|
|
25
|
-
function parsePermissionMode(raw) {
|
|
26
|
-
if (raw === "manual" || raw === "acceptEdits" || raw === "auto" || raw === "bypassPermissions" || raw === "dontAsk")
|
|
27
|
-
return raw;
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
26
|
function probeHost(env = process.env, platform = process.platform, hostVersion) {
|
|
31
27
|
const version = hostVersion ?? env.CLAUDE_CODE_VERSION ?? env.CLAUDE_CLI_VERSION;
|
|
32
28
|
if (!version)
|
|
@@ -42,57 +38,60 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
|
|
|
42
38
|
if (platform !== "darwin" && platform !== "linux") {
|
|
43
39
|
return { ok: false, reason: `unsupported platform ${platform}; native Windows is out of scope` };
|
|
44
40
|
}
|
|
45
|
-
|
|
46
|
-
if (rawPermissionMode !== undefined && rawPermissionMode !== "") {
|
|
47
|
-
const permissionMode = parsePermissionMode(rawPermissionMode);
|
|
48
|
-
if (!permissionMode)
|
|
49
|
-
return { ok: false, reason: `unsupported permission mode ${rawPermissionMode}` };
|
|
50
|
-
return { ok: true, version, permissionMode, platform };
|
|
51
|
-
}
|
|
52
|
-
return { ok: true, version, permissionMode: "manual", platform };
|
|
41
|
+
return { ok: true, version, platform };
|
|
53
42
|
}
|
|
54
43
|
|
|
44
|
+
// plugins/immune-brain/runtime/plugin_version.ts
|
|
45
|
+
var PLUGIN_VERSION = "3.4.0";
|
|
46
|
+
|
|
55
47
|
// plugins/immune-brain/runtime/claude/interaction.ts
|
|
56
48
|
import { createHash, randomUUID } from "node:crypto";
|
|
57
49
|
var PRIVILEGED_OPERATIONS = [
|
|
58
50
|
"enroll",
|
|
59
51
|
"request_authorization",
|
|
60
52
|
"approve_breaking_intent_revision",
|
|
61
|
-
"stop"
|
|
62
|
-
"repair_authority_state"
|
|
53
|
+
"stop"
|
|
63
54
|
];
|
|
55
|
+
var RECOVERY_ACTIONS = {
|
|
56
|
+
interaction_not_opened: "retry through a fresh native gate in the current Host",
|
|
57
|
+
user_denied: "wait for a fresh literal-user request",
|
|
58
|
+
user_cancelled: "wait for a fresh literal-user request",
|
|
59
|
+
correlation_missing: "retry through a fresh native gate in the current Host",
|
|
60
|
+
unsupported_host: "upgrade to a supported Claude Code version and retry in the current Host",
|
|
61
|
+
workspace_changed: "review the current workspace and retry through a fresh native gate"
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
class NativeAuthorityError extends Error {
|
|
65
|
+
reasonCode;
|
|
66
|
+
recoveryAction;
|
|
67
|
+
constructor(reasonCode, detail, recoveryAction = RECOVERY_ACTIONS[reasonCode]) {
|
|
68
|
+
super(`${reasonCode}: ${detail}; recovery: ${recoveryAction}`);
|
|
69
|
+
this.reasonCode = reasonCode;
|
|
70
|
+
this.recoveryAction = recoveryAction;
|
|
71
|
+
this.name = "NativeAuthorityError";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
64
74
|
function isPrivilegedOperation(operation) {
|
|
65
75
|
return PRIVILEGED_OPERATIONS.includes(operation);
|
|
66
76
|
}
|
|
67
77
|
function privilegedAnnotations() {
|
|
68
|
-
return {
|
|
69
|
-
destructiveHint: true,
|
|
70
|
-
"anthropic/requiresUserInteraction": true
|
|
71
|
-
};
|
|
78
|
+
return { destructiveHint: true };
|
|
72
79
|
}
|
|
73
80
|
function evaluateNativeGate(input) {
|
|
74
81
|
if (!isPrivilegedOperation(input.operation))
|
|
75
82
|
return { ok: true };
|
|
76
83
|
if (!input.interactive)
|
|
77
|
-
return { ok: false,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return { ok: false, reason: `unsupported permission mode ${String(input.permissionMode)}` };
|
|
81
|
-
if (mode === "dontAsk")
|
|
82
|
-
return { ok: false, reason: "dontAsk cannot mint authority" };
|
|
83
|
-
if (!input.requiresUserInteraction) {
|
|
84
|
-
return { ok: false, reason: "privileged operation requires anthropic/requiresUserInteraction" };
|
|
85
|
-
}
|
|
86
|
-
if (input.decision === "deny")
|
|
87
|
-
return { ok: false, reason: "native interaction denied" };
|
|
84
|
+
return { ok: false, error: new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable") };
|
|
85
|
+
if (input.decision === "decline")
|
|
86
|
+
return { ok: false, error: new NativeAuthorityError("user_denied", "native interaction declined") };
|
|
88
87
|
if (input.decision === "cancel")
|
|
89
|
-
return { ok: false,
|
|
88
|
+
return { ok: false, error: new NativeAuthorityError("user_cancelled", "native interaction cancelled") };
|
|
90
89
|
if (input.decision !== "accept")
|
|
91
|
-
return { ok: false,
|
|
90
|
+
return { ok: false, error: new NativeAuthorityError("interaction_not_opened", "native interaction returned no decision") };
|
|
92
91
|
return { ok: true };
|
|
93
92
|
}
|
|
94
93
|
function confirmationRef(input) {
|
|
95
|
-
return `claude-confirm-${createHash("sha256").update(`${input.
|
|
94
|
+
return `claude-confirm-${createHash("sha256").update(`${input.connectionId}\x00${input.toolCallId}\x00${input.requestId}\x00${input.operation}\x00${input.taskId}\x00${input.intentRevision ?? ""}\x00${input.intentContentHash ?? ""}\x00${input.bindingDigest ?? ""}`).digest("hex").slice(0, 16)}`;
|
|
96
95
|
}
|
|
97
96
|
function enrollmentNonce() {
|
|
98
97
|
return randomUUID();
|
|
@@ -109,7 +108,6 @@ var AGENT_TOOL = "Agent";
|
|
|
109
108
|
|
|
110
109
|
class MemoryHookEventLog {
|
|
111
110
|
events = [];
|
|
112
|
-
consumed = [];
|
|
113
111
|
append(event) {
|
|
114
112
|
this.events.push(event);
|
|
115
113
|
return true;
|
|
@@ -130,18 +128,8 @@ class MemoryHookEventLog {
|
|
|
130
128
|
this.events.splice(i, 1);
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
|
-
consumeElicitation(sessionId, toolCallId) {
|
|
134
|
-
this.consumed.push(`${sessionId}\x00${toolCallId}`);
|
|
135
|
-
this.events.push({ type: "ElicitationConsumed", sessionId, toolCallId });
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
consumedKeys() {
|
|
139
|
-
return [...this.consumed];
|
|
140
|
-
}
|
|
141
131
|
}
|
|
142
132
|
var CACHE_DIR = "immune-brain-claude";
|
|
143
|
-
var CONSUMED_FILE = "consumed.jsonl";
|
|
144
|
-
var CONSUMED_DIR = "consumed";
|
|
145
133
|
function sessionHash(sessionId) {
|
|
146
134
|
return createHash2("sha256").update(sessionId).digest("hex");
|
|
147
135
|
}
|
|
@@ -174,27 +162,6 @@ function ensurePrivateDir(dir) {
|
|
|
174
162
|
return false;
|
|
175
163
|
}
|
|
176
164
|
}
|
|
177
|
-
function claimAtomicKey(dir, keyName) {
|
|
178
|
-
const claimsDir = join(dir, CONSUMED_DIR);
|
|
179
|
-
if (!ensurePrivateDir(claimsDir))
|
|
180
|
-
return false;
|
|
181
|
-
const claimPath = join(claimsDir, `${keyName}.claim`);
|
|
182
|
-
const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
183
|
-
let fd;
|
|
184
|
-
try {
|
|
185
|
-
fd = openSync(claimPath, flags, 384);
|
|
186
|
-
} catch (error) {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
const stat = fstatSync(fd);
|
|
191
|
-
if (!stat.isFile() || !ownedByUs(stat) || (stat.mode & 511) !== 384)
|
|
192
|
-
return false;
|
|
193
|
-
return true;
|
|
194
|
-
} finally {
|
|
195
|
-
closeSync(fd);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
165
|
function appendPrivate(path, dir, line) {
|
|
199
166
|
if (!ensurePrivateDir(dir))
|
|
200
167
|
return false;
|
|
@@ -255,7 +222,7 @@ class FileHookEventLog {
|
|
|
255
222
|
if (!ensurePrivateDir(dir))
|
|
256
223
|
return [];
|
|
257
224
|
try {
|
|
258
|
-
return readdirSync(dir).filter((name) => name.endsWith(".jsonl")
|
|
225
|
+
return readdirSync(dir).filter((name) => name.endsWith(".jsonl")).flatMap((name) => this.readFile(join(dir, name)));
|
|
259
226
|
} catch {
|
|
260
227
|
return [];
|
|
261
228
|
}
|
|
@@ -294,36 +261,6 @@ class FileHookEventLog {
|
|
|
294
261
|
rmSync(path, { force: true });
|
|
295
262
|
} catch {}
|
|
296
263
|
}
|
|
297
|
-
consumeElicitation(sessionId, toolCallId) {
|
|
298
|
-
const dir = cacheDir(this.root);
|
|
299
|
-
const claimKey = createHash2("sha256").update(`${sessionId}\x00${toolCallId}`).digest("hex");
|
|
300
|
-
const keyClaimed = claimAtomicKey(dir, claimKey);
|
|
301
|
-
if (!keyClaimed)
|
|
302
|
-
return false;
|
|
303
|
-
if (!appendPrivate(join(dir, CONSUMED_FILE), dir, `${JSON.stringify({ sessionId, toolCallId })}
|
|
304
|
-
`)) {
|
|
305
|
-
try {
|
|
306
|
-
rmSync(join(dir, CONSUMED_DIR, `${claimKey}.claim`), { force: true });
|
|
307
|
-
} catch {}
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
if (!this.consumedKeys().includes(`${sessionId}\x00${toolCallId}`))
|
|
311
|
-
return false;
|
|
312
|
-
return this.append({ type: "ElicitationConsumed", sessionId, toolCallId });
|
|
313
|
-
}
|
|
314
|
-
consumedKeys() {
|
|
315
|
-
if (!ensurePrivateDir(cacheDir(this.root)))
|
|
316
|
-
return [];
|
|
317
|
-
const text = readPrivate(join(cacheDir(this.root), CONSUMED_FILE));
|
|
318
|
-
if (!text)
|
|
319
|
-
return [];
|
|
320
|
-
try {
|
|
321
|
-
return text.split(`
|
|
322
|
-
`).filter(Boolean).map((line) => JSON.parse(line)).map((item) => `${item.sessionId}\x00${item.toolCallId}`);
|
|
323
|
-
} catch {
|
|
324
|
-
return [];
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
264
|
}
|
|
328
265
|
function bindsStart(event, pending) {
|
|
329
266
|
if (event.taskId && event.taskId !== pending.request.taskId)
|
|
@@ -347,11 +284,9 @@ class ClaudeReviewHost {
|
|
|
347
284
|
host = "claude-code";
|
|
348
285
|
pending = new Map;
|
|
349
286
|
appliedBySession = new Map;
|
|
350
|
-
consumedElicitations = new Set;
|
|
351
287
|
constructor(log = new MemoryHookEventLog) {
|
|
352
288
|
this.log = log;
|
|
353
289
|
}
|
|
354
|
-
confirmations = new Map;
|
|
355
290
|
prepareReview(request) {
|
|
356
291
|
const initialCursors = new Map;
|
|
357
292
|
const sessionCursors = new Map;
|
|
@@ -381,8 +316,6 @@ ${request.prompt}`,
|
|
|
381
316
|
this.log.append(event);
|
|
382
317
|
}
|
|
383
318
|
drain() {
|
|
384
|
-
for (const key of this.log.consumedKeys())
|
|
385
|
-
this.consumedElicitations.add(key);
|
|
386
319
|
for (const sessionId of this.log.sessions()) {
|
|
387
320
|
const events = this.log.list(sessionId);
|
|
388
321
|
let start = this.appliedBySession.get(sessionId) ?? 0;
|
|
@@ -391,34 +324,10 @@ ${request.prompt}`,
|
|
|
391
324
|
let ended = false;
|
|
392
325
|
for (let i = start;i < events.length; i++) {
|
|
393
326
|
const event = events[i];
|
|
394
|
-
if (event.type === "ElicitationResult") {
|
|
395
|
-
const key = `${event.sessionId}\x00${event.toolCallId}`;
|
|
396
|
-
if (this.consumedElicitations.has(key))
|
|
397
|
-
continue;
|
|
398
|
-
if (this.confirmations.has(key)) {
|
|
399
|
-
if (this.log.consumeElicitation(event.sessionId, event.toolCallId)) {
|
|
400
|
-
this.confirmations.delete(key);
|
|
401
|
-
this.consumedElicitations.add(key);
|
|
402
|
-
}
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
this.confirmations.set(key, event.decision);
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
if (event.type === "ElicitationConsumed") {
|
|
409
|
-
const key = `${event.sessionId}\x00${event.toolCallId}`;
|
|
410
|
-
this.consumedElicitations.add(key);
|
|
411
|
-
this.confirmations.delete(key);
|
|
412
|
-
continue;
|
|
413
|
-
}
|
|
414
327
|
if (event.type === "SessionEnd") {
|
|
415
328
|
this.log.clear(event.sessionId);
|
|
416
329
|
ended = true;
|
|
417
330
|
this.appliedBySession.delete(event.sessionId);
|
|
418
|
-
for (const key of this.confirmations.keys()) {
|
|
419
|
-
if (key.startsWith(`${event.sessionId}\x00`))
|
|
420
|
-
this.confirmations.delete(key);
|
|
421
|
-
}
|
|
422
331
|
for (const [id, state] of this.pending) {
|
|
423
332
|
if (state.startEvent?.sessionId === event.sessionId || state.postEvent?.sessionId === event.sessionId || state.stopEvent?.sessionId === event.sessionId) {
|
|
424
333
|
this.pending.delete(id);
|
|
@@ -455,37 +364,6 @@ ${request.prompt}`,
|
|
|
455
364
|
}
|
|
456
365
|
}
|
|
457
366
|
}
|
|
458
|
-
peekConfirmation(sessionId, toolCallId) {
|
|
459
|
-
this.drain();
|
|
460
|
-
return this.confirmations.has(`${sessionId}\x00${toolCallId}`);
|
|
461
|
-
}
|
|
462
|
-
takeConfirmation(sessionId, toolCallId) {
|
|
463
|
-
this.drain();
|
|
464
|
-
const key = `${sessionId}\x00${toolCallId}`;
|
|
465
|
-
const decision = this.confirmations.get(key);
|
|
466
|
-
if (!decision)
|
|
467
|
-
return;
|
|
468
|
-
if (!this.log.consumeElicitation(sessionId, toolCallId)) {
|
|
469
|
-
this.confirmations.delete(key);
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
this.confirmations.delete(key);
|
|
473
|
-
this.consumedElicitations.add(key);
|
|
474
|
-
this.drain();
|
|
475
|
-
return decision;
|
|
476
|
-
}
|
|
477
|
-
sessionOfElicitation(toolCallId) {
|
|
478
|
-
this.drain();
|
|
479
|
-
const sessions = [];
|
|
480
|
-
for (const key of this.confirmations.keys()) {
|
|
481
|
-
const sep = key.lastIndexOf("\x00");
|
|
482
|
-
if (sep >= 0 && key.slice(sep + 1) === toolCallId)
|
|
483
|
-
sessions.push(key.slice(0, sep));
|
|
484
|
-
}
|
|
485
|
-
if (sessions.length !== 1)
|
|
486
|
-
return;
|
|
487
|
-
return sessions[0];
|
|
488
|
-
}
|
|
489
367
|
applyReviewEvent(event, state) {
|
|
490
368
|
if (state.error)
|
|
491
369
|
return;
|
|
@@ -618,8 +496,6 @@ function parseHookStdin(raw) {
|
|
|
618
496
|
return null;
|
|
619
497
|
}
|
|
620
498
|
const hookType = String(payload.hook_event_name ?? payload.type ?? "");
|
|
621
|
-
if (hookType === "ElicitationResult" && (typeof payload.session_id !== "string" || !payload.session_id))
|
|
622
|
-
return null;
|
|
623
499
|
const sessionId = String(payload.session_id ?? payload.sessionId ?? process.env.CLAUDE_SESSION_ID ?? "");
|
|
624
500
|
if (!sessionId)
|
|
625
501
|
return null;
|
|
@@ -711,17 +587,6 @@ function parseHookStdin(raw) {
|
|
|
711
587
|
}
|
|
712
588
|
if (hookType === "SessionEnd")
|
|
713
589
|
return { type: "SessionEnd", sessionId };
|
|
714
|
-
if (hookType === "ElicitationResult") {
|
|
715
|
-
const rawToolCallId = payload.tool_use_id ?? payload.toolCallId;
|
|
716
|
-
if (typeof rawToolCallId !== "string" || !rawToolCallId)
|
|
717
|
-
return null;
|
|
718
|
-
const toolCallId = rawToolCallId;
|
|
719
|
-
const raw2 = payload.decision ?? payload.result ?? payload.action;
|
|
720
|
-
const decision = raw2 === "accept" ? "accept" : raw2 === "deny" ? "deny" : raw2 === "cancel" ? "cancel" : null;
|
|
721
|
-
if (!toolCallId || !decision)
|
|
722
|
-
return null;
|
|
723
|
-
return { type: "ElicitationResult", sessionId, toolCallId, decision };
|
|
724
|
-
}
|
|
725
590
|
return null;
|
|
726
591
|
}
|
|
727
592
|
|
|
@@ -1484,8 +1349,6 @@ class AssuranceCoordinator {
|
|
|
1484
1349
|
return this.unknownAfterCommit(taskId, "qa", operationId, boundedAssuranceError(error));
|
|
1485
1350
|
}
|
|
1486
1351
|
}
|
|
1487
|
-
if (projection.projection.next_obligation === "authorize_user")
|
|
1488
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: operationId };
|
|
1489
1352
|
if (projection.projection.next_obligation !== "run_qa" && projection.projection.next_obligation !== "run_review")
|
|
1490
1353
|
return { state: "blocked", reason: `Kernel requires ${projection.projection.next_obligation}` };
|
|
1491
1354
|
const qaAlreadySettled = projection.projection.next_obligation === "run_review";
|
|
@@ -1583,8 +1446,6 @@ class AssuranceCoordinator {
|
|
|
1583
1446
|
return this.unknownAfterCommit(taskId, "qa", operationId, boundedAssuranceError(error));
|
|
1584
1447
|
}
|
|
1585
1448
|
}
|
|
1586
|
-
if (fresh.projection.next_obligation === "authorize_user")
|
|
1587
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: operationId };
|
|
1588
1449
|
if (fresh.projection.next_obligation !== "run_review") {
|
|
1589
1450
|
if (authorityCommitted && aborted())
|
|
1590
1451
|
return this.unknownAfterCommit(taskId, "qa", operationId, "QA settlement projection did not require Review after cancellation");
|
|
@@ -1747,8 +1608,6 @@ class AssuranceCoordinator {
|
|
|
1747
1608
|
return this.unknownAfterCommit(taskId, "review", reservation.operationId, boundedAssuranceError(error));
|
|
1748
1609
|
}
|
|
1749
1610
|
}
|
|
1750
|
-
if (settled.projection.next_obligation === "authorize_user")
|
|
1751
|
-
return { state: "awaiting_user", operation: "record-user-approval", operation_id: reservation.operationId };
|
|
1752
1611
|
return { state: "blocked", reason: `Kernel requires ${settled.projection.next_obligation} after Review` };
|
|
1753
1612
|
}
|
|
1754
1613
|
abandonReview(taskId, reason) {
|
|
@@ -3633,7 +3492,6 @@ var ACTION_V2_TYPES = [
|
|
|
3633
3492
|
"record_finding",
|
|
3634
3493
|
"resolve_finding",
|
|
3635
3494
|
"record_approval",
|
|
3636
|
-
"record_user_approval",
|
|
3637
3495
|
"revise_intent",
|
|
3638
3496
|
"approve_breaking_intent_revision",
|
|
3639
3497
|
"request_rework",
|
|
@@ -3696,8 +3554,7 @@ function parseTaskAction(raw) {
|
|
|
3696
3554
|
};
|
|
3697
3555
|
break;
|
|
3698
3556
|
}
|
|
3699
|
-
case "record_approval":
|
|
3700
|
-
case "record_user_approval": {
|
|
3557
|
+
case "record_approval": {
|
|
3701
3558
|
rejectUnknown2(value, [...ACTION_BASE_FIELDS, "approval"], "action", violations);
|
|
3702
3559
|
const approval = parseApprovalV2(value.approval, 0, violations, true);
|
|
3703
3560
|
if (approval.review_revision && approval.kind !== "review")
|
|
@@ -4904,7 +4761,7 @@ function commitTerminalLocked(root, taskId, transaction, tombstone) {
|
|
|
4904
4761
|
var REQUIRED_ATTESTATIONS = {
|
|
4905
4762
|
routine: ["qa"],
|
|
4906
4763
|
material: ["qa", "review"],
|
|
4907
|
-
critical: ["qa", "review"
|
|
4764
|
+
critical: ["qa", "review"]
|
|
4908
4765
|
};
|
|
4909
4766
|
function archiveActivePlanningPath(path) {
|
|
4910
4767
|
const matched = path.match(/^docs\/(plans|specs)\/([^/]+)$/);
|
|
@@ -5009,8 +4866,6 @@ function projectTask(intent, record, currentDiffHash, currentIntentContentHash,
|
|
|
5009
4866
|
nextObligation = "run_qa";
|
|
5010
4867
|
} else if (decision.missing_approval_kinds.includes("review")) {
|
|
5011
4868
|
nextObligation = "run_review";
|
|
5012
|
-
} else if (decision.missing_approval_kinds.includes("user")) {
|
|
5013
|
-
nextObligation = "authorize_user";
|
|
5014
4869
|
} else if (decision.complete) {
|
|
5015
4870
|
nextObligation = "complete";
|
|
5016
4871
|
}
|
|
@@ -5036,8 +4891,6 @@ function deriveAssuranceAuthorization(input) {
|
|
|
5036
4891
|
state: "none",
|
|
5037
4892
|
blocked: `resolve-user-decision requires exactly one open user decision; found ${input.open_user_decision_count}`
|
|
5038
4893
|
};
|
|
5039
|
-
if (input.next_obligation === "authorize_user")
|
|
5040
|
-
return { state: "record_user_approval", blocked: null };
|
|
5041
4894
|
return { state: "none", blocked: null };
|
|
5042
4895
|
}
|
|
5043
4896
|
function emptyProjection() {
|
|
@@ -5288,7 +5141,7 @@ function intentRefMatches(intent, ref) {
|
|
|
5288
5141
|
return ref.path === `docs/plans/${intent.task_id}.intent.json` && ref.content_hash === canonicalIntentHash(intent);
|
|
5289
5142
|
}
|
|
5290
5143
|
function hasPrivilegedKind(action) {
|
|
5291
|
-
return action.type === "record_approval" || action.type === "
|
|
5144
|
+
return action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5292
5145
|
}
|
|
5293
5146
|
function findingsDigestV2(findings) {
|
|
5294
5147
|
const normalized = findings.map((finding) => ({
|
|
@@ -5440,36 +5293,6 @@ function reduceTask(recordRaw, actionRaw, authorityAudit = null, changedPaths) {
|
|
|
5440
5293
|
appendHistory(record, action, from, approval.id, authorityAudit);
|
|
5441
5294
|
break;
|
|
5442
5295
|
}
|
|
5443
|
-
case "record_user_approval": {
|
|
5444
|
-
if (record.lifecycle !== "active" || record.artifact_state !== "frozen")
|
|
5445
|
-
throw new KernelInvariantError([
|
|
5446
|
-
`cannot record user approval while state is ${stateOf(record)}`
|
|
5447
|
-
]);
|
|
5448
|
-
const approval = action.approval;
|
|
5449
|
-
if (approval.kind !== "user")
|
|
5450
|
-
throw new KernelInvariantError([
|
|
5451
|
-
"record_user_approval requires kind user"
|
|
5452
|
-
]);
|
|
5453
|
-
if (!authorityAudit || authorityAudit.authority_kind !== "user")
|
|
5454
|
-
throw new KernelInvariantError([
|
|
5455
|
-
"record_user_approval requires user authority"
|
|
5456
|
-
]);
|
|
5457
|
-
if (approval.task_revision !== record.intent_snapshot.revision)
|
|
5458
|
-
throw new KernelInvariantError(["approval task_revision must equal the current intent revision"]);
|
|
5459
|
-
if (approval.intent_content_hash !== record.intent_ref.content_hash)
|
|
5460
|
-
throw new KernelInvariantError(["approval intent_content_hash must equal the current intent hash"]);
|
|
5461
|
-
if (approval.diff_hash !== diffHash)
|
|
5462
|
-
throw new KernelInvariantError(["approval diff_hash must equal the action diff hash"]);
|
|
5463
|
-
if (record.attestations.some((item) => item.id === approval.id))
|
|
5464
|
-
throw new KernelInvariantError([
|
|
5465
|
-
`attestations contains duplicate id ${approval.id}`
|
|
5466
|
-
]);
|
|
5467
|
-
if (approval.review_revision)
|
|
5468
|
-
throw new KernelInvariantError(["review_revision is only valid on review approvals"]);
|
|
5469
|
-
record.attestations.push({ ...approval, acceptance_results: [] });
|
|
5470
|
-
appendHistory(record, action, from, approval.id, authorityAudit);
|
|
5471
|
-
break;
|
|
5472
|
-
}
|
|
5473
5296
|
case "revise_intent":
|
|
5474
5297
|
case "approve_breaking_intent_revision": {
|
|
5475
5298
|
if (record.lifecycle !== "active")
|
|
@@ -5706,7 +5529,7 @@ function applyTaskAction(input) {
|
|
|
5706
5529
|
"intent token does not match the committed record intent"
|
|
5707
5530
|
]);
|
|
5708
5531
|
}
|
|
5709
|
-
const privileged = action.type === "record_approval" || action.type === "
|
|
5532
|
+
const privileged = action.type === "record_approval" || action.type === "approve_breaking_intent_revision" || action.type === "request_rework" || action.type === "stop" || action.type === "resolve_user_decision";
|
|
5710
5533
|
const expectedAuthority = privileged ? {
|
|
5711
5534
|
task_id,
|
|
5712
5535
|
action,
|
|
@@ -5828,8 +5651,6 @@ function capabilityActionFor(input) {
|
|
|
5828
5651
|
switch (input.op) {
|
|
5829
5652
|
case "record_approval":
|
|
5830
5653
|
return { ...base, approval: input.approval };
|
|
5831
|
-
case "record_user_approval":
|
|
5832
|
-
return { ...base, approval: input.approval };
|
|
5833
5654
|
case "request_rework":
|
|
5834
5655
|
return { ...base, findings: input.findings };
|
|
5835
5656
|
case "stop":
|
|
@@ -6014,10 +5835,6 @@ function createCanaryApplication(registry) {
|
|
|
6014
5835
|
capability = operation.capability;
|
|
6015
5836
|
action = { ...base, type: "record_approval", approval: operation.approval };
|
|
6016
5837
|
break;
|
|
6017
|
-
case "record_user_approval":
|
|
6018
|
-
capability = operation.capability;
|
|
6019
|
-
action = { ...base, type: "record_user_approval", approval: operation.approval };
|
|
6020
|
-
break;
|
|
6021
5838
|
case "revise_intent":
|
|
6022
5839
|
action = {
|
|
6023
5840
|
...base,
|
|
@@ -6763,6 +6580,10 @@ async function mintCapability(registry, input) {
|
|
|
6763
6580
|
};
|
|
6764
6581
|
return registry.issue(binding);
|
|
6765
6582
|
}
|
|
6583
|
+
function throwIfCancelled(signal) {
|
|
6584
|
+
if (signal?.aborted)
|
|
6585
|
+
throw new NativeAuthorityError("user_cancelled", "Tool call was cancelled");
|
|
6586
|
+
}
|
|
6766
6587
|
|
|
6767
6588
|
class ClaudeRuntime {
|
|
6768
6589
|
host;
|
|
@@ -6770,8 +6591,7 @@ class ClaudeRuntime {
|
|
|
6770
6591
|
cwd;
|
|
6771
6592
|
env;
|
|
6772
6593
|
interactive;
|
|
6773
|
-
|
|
6774
|
-
decisions;
|
|
6594
|
+
requestConfirmation;
|
|
6775
6595
|
hostVersion;
|
|
6776
6596
|
mutationRegistry = null;
|
|
6777
6597
|
enrollmentRegistry = createEnrollmentAuthorityRegistry();
|
|
@@ -6780,8 +6600,7 @@ class ClaudeRuntime {
|
|
|
6780
6600
|
this.cwd = options.cwd;
|
|
6781
6601
|
this.env = options.env ?? process.env;
|
|
6782
6602
|
this.interactive = options.interactive ?? true;
|
|
6783
|
-
this.
|
|
6784
|
-
this.decisions = options.decisions ?? new Map;
|
|
6603
|
+
this.requestConfirmation = options.requestConfirmation;
|
|
6785
6604
|
this.host = options.host ?? new ClaudeReviewHost(new FileHookEventLog);
|
|
6786
6605
|
if (options.ports) {
|
|
6787
6606
|
this.coordinator = new AssuranceCoordinator({ ...options.ports, host: this.host });
|
|
@@ -6795,6 +6614,9 @@ class ClaudeRuntime {
|
|
|
6795
6614
|
bindHostVersion(version) {
|
|
6796
6615
|
this.hostVersion = version || undefined;
|
|
6797
6616
|
}
|
|
6617
|
+
bindNativeConfirmation(port) {
|
|
6618
|
+
this.requestConfirmation = port;
|
|
6619
|
+
}
|
|
6798
6620
|
async shutdown() {
|
|
6799
6621
|
await this.coordinator.onSessionShutdown();
|
|
6800
6622
|
}
|
|
@@ -6830,32 +6652,28 @@ class ClaudeRuntime {
|
|
|
6830
6652
|
this.app ??= createCanaryApplication(this.mutationRegistry);
|
|
6831
6653
|
return { registry: this.mutationRegistry, app: this.app };
|
|
6832
6654
|
}
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
if (configured === "deny" || configured === "cancel")
|
|
6836
|
-
this.gate(operation, meta);
|
|
6837
|
-
}
|
|
6838
|
-
gate(operation, meta, binding = {}) {
|
|
6655
|
+
async gate(operation, meta, binding = {}) {
|
|
6656
|
+
throwIfCancelled(meta.signal);
|
|
6839
6657
|
const probe = probeHost(this.env, process.platform, this.hostVersion);
|
|
6840
6658
|
if (!probe.ok)
|
|
6841
|
-
throw new
|
|
6842
|
-
const
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
});
|
|
6659
|
+
throw new NativeAuthorityError("unsupported_host", probe.reason);
|
|
6660
|
+
const interactive = meta.interactive ?? this.interactive;
|
|
6661
|
+
if (!interactive)
|
|
6662
|
+
throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
|
|
6663
|
+
if (!isPrivilegedOperation(operation))
|
|
6664
|
+
throw new Error(`unsupported native operation ${operation}`);
|
|
6665
|
+
if (!this.requestConfirmation)
|
|
6666
|
+
throw new NativeAuthorityError("interaction_not_opened", "native confirmation port is unavailable");
|
|
6667
|
+
const result = await this.requestConfirmation({ operation, taskId: meta.taskId, toolCallId: meta.toolCallId, signal: meta.signal, ...binding });
|
|
6668
|
+
throwIfCancelled(meta.signal);
|
|
6669
|
+
const gate = evaluateNativeGate({ operation, interactive, decision: result.decision });
|
|
6853
6670
|
if (!gate.ok)
|
|
6854
|
-
throw
|
|
6671
|
+
throw gate.error;
|
|
6855
6672
|
return {
|
|
6856
6673
|
confirmation_ref: confirmationRef({
|
|
6857
|
-
|
|
6674
|
+
connectionId: meta.sessionId,
|
|
6858
6675
|
toolCallId: meta.toolCallId,
|
|
6676
|
+
requestId: result.requestId,
|
|
6859
6677
|
operation,
|
|
6860
6678
|
taskId: meta.taskId,
|
|
6861
6679
|
...binding
|
|
@@ -6866,19 +6684,21 @@ class ClaudeRuntime {
|
|
|
6866
6684
|
return projectAssurance(this.cwd, taskId, diffSnapshotOf);
|
|
6867
6685
|
}
|
|
6868
6686
|
async enroll(taskId, meta) {
|
|
6869
|
-
this.rejectBeforePreparation("enroll", { ...meta, taskId });
|
|
6870
6687
|
const now = new Date().toISOString();
|
|
6871
6688
|
const preparation = await preparePiCanary(this.cwd, { task_id: taskId, now });
|
|
6872
|
-
const
|
|
6689
|
+
const intent = await readTaskIntent(this.cwd, taskId);
|
|
6690
|
+
const gate = await this.gate("enroll", { ...meta, taskId }, {
|
|
6691
|
+
risk: intent.intent.risk,
|
|
6873
6692
|
intentRevision: preparation.intent?.revision,
|
|
6874
6693
|
intentContentHash: preparation.intent?.content_hash,
|
|
6875
6694
|
bindingDigest: preparation.digest
|
|
6876
6695
|
});
|
|
6877
6696
|
const { unchanged } = await revalidatePiCanary(this.cwd, { task_id: taskId, now }, preparation);
|
|
6878
6697
|
if (!unchanged)
|
|
6879
|
-
throw new
|
|
6698
|
+
throw new NativeAuthorityError("workspace_changed", "workspace changed after native confirmation");
|
|
6880
6699
|
if (!preparation.intent)
|
|
6881
6700
|
throw new Error("enrollment requires a readable TaskIntent");
|
|
6701
|
+
throwIfCancelled(meta.signal);
|
|
6882
6702
|
const nonce = enrollmentNonce();
|
|
6883
6703
|
const binding = {
|
|
6884
6704
|
task_id: taskId,
|
|
@@ -6914,22 +6734,16 @@ class ClaudeRuntime {
|
|
|
6914
6734
|
return submitClaudeReview(this.host, this.coordinator, { cwd: this.cwd }, taskId, verdictInput);
|
|
6915
6735
|
}
|
|
6916
6736
|
async authorize(taskId, operation, meta, extra = {}) {
|
|
6917
|
-
if (!isPrivilegedOperation(operation) && operation !== "request_authorization")
|
|
6918
|
-
throw new Error(`unsupported privileged operation ${operation}`);
|
|
6919
|
-
this.rejectBeforePreparation(operation, { ...meta, taskId });
|
|
6920
6737
|
if (operation === "repair_authority_state") {
|
|
6921
6738
|
const authority = reconcileKernelAuthority(this.cwd, taskId);
|
|
6922
6739
|
if (authority.state !== "repairable_stale_claim" || authority.owner_task_id !== taskId) {
|
|
6923
6740
|
throw new Error(authority.diagnostic ?? "authority repair requires a repairable stale claim");
|
|
6924
6741
|
}
|
|
6925
|
-
|
|
6926
|
-
const current = reconcileKernelAuthority(this.cwd, taskId);
|
|
6927
|
-
if (current.state !== authority.state || current.owner_task_id !== authority.owner_task_id || current.revision !== authority.revision) {
|
|
6928
|
-
throw new Error("authority changed after native confirmation; repair aborted before capability issuance");
|
|
6929
|
-
}
|
|
6930
|
-
return repairKernelAuthority(this.cwd, taskId, current.revision);
|
|
6742
|
+
return repairKernelAuthority(this.cwd, taskId, authority.revision);
|
|
6931
6743
|
}
|
|
6932
|
-
|
|
6744
|
+
if (!isPrivilegedOperation(operation) && operation !== "request_authorization")
|
|
6745
|
+
throw new Error(`unsupported privileged operation ${operation}`);
|
|
6746
|
+
let op = operation;
|
|
6933
6747
|
let decisionOp;
|
|
6934
6748
|
const projection = await this.status(taskId);
|
|
6935
6749
|
if (projection.error || !projection.claim)
|
|
@@ -6943,7 +6757,7 @@ class ClaudeRuntime {
|
|
|
6943
6757
|
throw new Error(`resolve-user-decision requires exactly one open user decision; found ${open.length}`);
|
|
6944
6758
|
op = "resolve_user_decision";
|
|
6945
6759
|
decisionOp = { finding_id: open[0].id, resolution: `resume after literal-user decision: ${open[0].summary}` };
|
|
6946
|
-
} else
|
|
6760
|
+
} else {
|
|
6947
6761
|
throw new Error(readiness.blocked ?? "no unique host-derived authorization operation");
|
|
6948
6762
|
}
|
|
6949
6763
|
}
|
|
@@ -6985,16 +6799,22 @@ class ClaudeRuntime {
|
|
|
6985
6799
|
`);
|
|
6986
6800
|
execFileSync4("git", ["add", "--", priorIntent.intent_ref.path], { cwd: this.cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
6987
6801
|
const preparedRecord = await readTaskRecord(this.cwd, taskId);
|
|
6988
|
-
if (!preparedRecord.record)
|
|
6989
|
-
throw new
|
|
6802
|
+
if (!preparedRecord.record) {
|
|
6803
|
+
throw new NativeAuthorityError("workspace_changed", "TaskRecord changed before the breaking revision digest");
|
|
6804
|
+
}
|
|
6990
6805
|
preparedDiffHash = diffHashOf(this.cwd, preparedRecord.record);
|
|
6991
6806
|
}
|
|
6992
6807
|
const preparedProjection = await this.status(taskId);
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6808
|
+
try {
|
|
6809
|
+
assertProjectionBinding(projection, preparedProjection, Boolean(nextIntent));
|
|
6810
|
+
if (preparedProjection.projection.diff_hash !== preparedDiffHash) {
|
|
6811
|
+
throw new Error("workspace changed while preparing the authority digest");
|
|
6812
|
+
}
|
|
6813
|
+
} catch (error) {
|
|
6814
|
+
throw new NativeAuthorityError("workspace_changed", error instanceof Error ? error.message : String(error));
|
|
6996
6815
|
}
|
|
6997
|
-
gate = this.gate(operation, { ...meta, taskId }, {
|
|
6816
|
+
gate = await this.gate(operation, { ...meta, taskId }, {
|
|
6817
|
+
risk: projection.projection.risk,
|
|
6998
6818
|
intentRevision: nextIntent?.revision ?? projection.projection.intent_revision,
|
|
6999
6819
|
intentContentHash: nextIntentHash ?? projection.projection.intent_content_hash,
|
|
7000
6820
|
bindingDigest: `${preparedDiffHash}:${nextIntentHash ?? ""}`
|
|
@@ -7010,23 +6830,18 @@ class ClaudeRuntime {
|
|
|
7010
6830
|
}
|
|
7011
6831
|
const { registry, app } = this.authority();
|
|
7012
6832
|
const confirmation = gate.confirmation_ref;
|
|
7013
|
-
const approval = op === "record_user_approval" ? {
|
|
7014
|
-
id: `approval-user-${randomUUID5().slice(0, 8)}`,
|
|
7015
|
-
kind: "user",
|
|
7016
|
-
authority_role: "user",
|
|
7017
|
-
task_revision: projection.projection.intent_revision,
|
|
7018
|
-
intent_content_hash: projection.projection.intent_content_hash,
|
|
7019
|
-
diff_hash: projection.projection.diff_hash,
|
|
7020
|
-
actor_id: actorId,
|
|
7021
|
-
summary: "literal user approval"
|
|
7022
|
-
} : undefined;
|
|
7023
6833
|
try {
|
|
7024
6834
|
const capabilityProjection = await this.status(taskId);
|
|
7025
|
-
|
|
6835
|
+
try {
|
|
6836
|
+
assertProjectionBinding(projection, capabilityProjection, Boolean(nextIntent));
|
|
6837
|
+
} catch (error) {
|
|
6838
|
+
throw new NativeAuthorityError("workspace_changed", error instanceof Error ? error.message : String(error));
|
|
6839
|
+
}
|
|
7026
6840
|
const operationDiffHash = capabilityProjection.projection.diff_hash;
|
|
7027
6841
|
if (nextIntent && operationDiffHash !== preparedDiffHash) {
|
|
7028
|
-
throw new
|
|
6842
|
+
throw new NativeAuthorityError("workspace_changed", "workspace changed after native confirmation");
|
|
7029
6843
|
}
|
|
6844
|
+
throwIfCancelled(meta.signal);
|
|
7030
6845
|
const capability = await mintCapability(registry, {
|
|
7031
6846
|
authority_kind: "user",
|
|
7032
6847
|
task_id: taskId,
|
|
@@ -7038,11 +6853,11 @@ class ClaudeRuntime {
|
|
|
7038
6853
|
actor_id: actorId,
|
|
7039
6854
|
now,
|
|
7040
6855
|
confirmation_ref: confirmation,
|
|
7041
|
-
...approval ? { approval } : {},
|
|
7042
6856
|
...op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {},
|
|
7043
6857
|
...op === "resolve_user_decision" && decisionOp ? decisionOp : {},
|
|
7044
6858
|
...op === "stop" ? { reason: extra.reason ?? "user stop" } : {}
|
|
7045
6859
|
});
|
|
6860
|
+
throwIfCancelled(meta.signal);
|
|
7046
6861
|
const result = app.execute({
|
|
7047
6862
|
root: this.cwd,
|
|
7048
6863
|
task_id: taskId,
|
|
@@ -7050,7 +6865,6 @@ class ClaudeRuntime {
|
|
|
7050
6865
|
op,
|
|
7051
6866
|
capability,
|
|
7052
6867
|
actor_id: actorId,
|
|
7053
|
-
...approval ? { approval } : {},
|
|
7054
6868
|
...op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {},
|
|
7055
6869
|
...op === "resolve_user_decision" && decisionOp ? decisionOp : {},
|
|
7056
6870
|
...op === "stop" ? { reason: extra.reason ?? "user stop" } : {}
|
|
@@ -7186,6 +7000,7 @@ class ClaudeRuntime {
|
|
|
7186
7000
|
}
|
|
7187
7001
|
|
|
7188
7002
|
// plugins/immune-brain/runtime/claude/mcp_server.ts
|
|
7003
|
+
var MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
7189
7004
|
var TOOLS = [
|
|
7190
7005
|
{ name: "status", description: "Read the Kernel Assurance Projection for an exact task.", privileged: false },
|
|
7191
7006
|
{ name: "enroll", description: "Enroll a Git-tracked TaskIntent after native confirmation.", privileged: true },
|
|
@@ -7194,7 +7009,7 @@ var TOOLS = [
|
|
|
7194
7009
|
{ name: "request_authorization", description: "Apply exact literal-user authorization.", privileged: true },
|
|
7195
7010
|
{ name: "approve_breaking_intent_revision", description: "Approve a breaking TaskIntent revision.", privileged: true },
|
|
7196
7011
|
{ name: "stop", description: "Stop the active task with literal-user authority.", privileged: true },
|
|
7197
|
-
{ name: "repair_authority_state", description: "Repair a recoverable stale backend claim.", privileged:
|
|
7012
|
+
{ name: "repair_authority_state", description: "Repair a proven recoverable stale backend claim.", privileged: false }
|
|
7198
7013
|
];
|
|
7199
7014
|
function listMcpTools() {
|
|
7200
7015
|
return TOOLS.map((tool) => ({
|
|
@@ -7213,6 +7028,9 @@ function listMcpTools() {
|
|
|
7213
7028
|
annotations: tool.privileged ? privilegedAnnotations() : { readOnlyHint: tool.name === "status" }
|
|
7214
7029
|
}));
|
|
7215
7030
|
}
|
|
7031
|
+
function supportsElicitationProtocol(value) {
|
|
7032
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value) && value >= MCP_PROTOCOL_VERSION;
|
|
7033
|
+
}
|
|
7216
7034
|
function createMcpRuntime(options = {}) {
|
|
7217
7035
|
const host = options.host ?? new ClaudeReviewHost(new FileHookEventLog);
|
|
7218
7036
|
const runtime = new ClaudeRuntime({
|
|
@@ -7221,19 +7039,24 @@ function createMcpRuntime(options = {}) {
|
|
|
7221
7039
|
host,
|
|
7222
7040
|
ports: options.ports,
|
|
7223
7041
|
interactive: options.interactive,
|
|
7224
|
-
|
|
7042
|
+
requestConfirmation: options.requestConfirmation
|
|
7225
7043
|
});
|
|
7226
7044
|
let negotiatedVersion;
|
|
7227
7045
|
let negotiatedInteractive = false;
|
|
7046
|
+
const connectionId = randomUUID6();
|
|
7228
7047
|
return {
|
|
7229
7048
|
runtime,
|
|
7230
7049
|
host,
|
|
7050
|
+
connectionId,
|
|
7231
7051
|
listTools: listMcpTools,
|
|
7232
7052
|
bindClientHandshake(identity) {
|
|
7233
7053
|
negotiatedVersion = identity.version || undefined;
|
|
7234
|
-
negotiatedInteractive = identity.interactive;
|
|
7054
|
+
negotiatedInteractive = identity.interactive && supportsElicitationProtocol(identity.protocolVersion);
|
|
7235
7055
|
runtime.bindHostVersion(negotiatedVersion);
|
|
7236
7056
|
},
|
|
7057
|
+
bindNativeConfirmation(port) {
|
|
7058
|
+
runtime.bindNativeConfirmation(port);
|
|
7059
|
+
},
|
|
7237
7060
|
sessionInteractive: () => negotiatedInteractive,
|
|
7238
7061
|
async callTool(name, args, meta = {}) {
|
|
7239
7062
|
const taskId = String(args.task_id ?? "");
|
|
@@ -7242,22 +7065,21 @@ function createMcpRuntime(options = {}) {
|
|
|
7242
7065
|
if ("native_decision" in args)
|
|
7243
7066
|
throw new Error("native_decision cannot be supplied in tool arguments");
|
|
7244
7067
|
if (name === "status")
|
|
7245
|
-
return runtime.status(taskId);
|
|
7068
|
+
return { plugin_version: PLUGIN_VERSION, ...await runtime.status(taskId) };
|
|
7246
7069
|
if (!negotiatedVersion)
|
|
7247
|
-
throw new
|
|
7248
|
-
if (!negotiatedInteractive)
|
|
7249
|
-
throw new
|
|
7070
|
+
throw new NativeAuthorityError("unsupported_host", "Claude Code version is unavailable");
|
|
7071
|
+
if (!negotiatedInteractive && name !== "repair_authority_state") {
|
|
7072
|
+
throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
|
|
7073
|
+
}
|
|
7250
7074
|
const probe = probeHost(options.env ?? process.env, process.platform, negotiatedVersion);
|
|
7251
7075
|
if (!probe.ok)
|
|
7252
|
-
throw new
|
|
7076
|
+
throw new NativeAuthorityError("unsupported_host", probe.reason);
|
|
7253
7077
|
const toolMeta = {
|
|
7254
|
-
sessionId: meta.sessionId ??
|
|
7078
|
+
sessionId: meta.sessionId ?? connectionId,
|
|
7255
7079
|
toolCallId: meta.toolCallId ?? `call-${name}`,
|
|
7256
7080
|
taskId,
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
interactive: meta.interactive ?? options.interactive ?? false,
|
|
7260
|
-
decision: meta.decision
|
|
7081
|
+
interactive: meta.interactive ?? options.interactive ?? negotiatedInteractive,
|
|
7082
|
+
signal: meta.signal
|
|
7261
7083
|
};
|
|
7262
7084
|
if (name === "enroll")
|
|
7263
7085
|
return runtime.enroll(taskId, toolMeta);
|
|
@@ -7274,19 +7096,25 @@ function createMcpRuntime(options = {}) {
|
|
|
7274
7096
|
throw new Error(`unknown tool ${name}`);
|
|
7275
7097
|
},
|
|
7276
7098
|
observe: (event) => host.observe(event),
|
|
7277
|
-
sessionOfElicitation: (toolCallId) => host.sessionOfElicitation(toolCallId),
|
|
7278
7099
|
shutdown: () => runtime.shutdown(),
|
|
7279
7100
|
aborts: new Map
|
|
7280
7101
|
};
|
|
7281
7102
|
}
|
|
7282
|
-
function hostCallIdentity(meta
|
|
7103
|
+
function hostCallIdentity(meta) {
|
|
7283
7104
|
if (!meta)
|
|
7284
7105
|
return;
|
|
7285
7106
|
const toolCallId = meta["claudecode/toolUseId"];
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7107
|
+
return typeof toolCallId === "string" && toolCallId ? { toolCallId } : undefined;
|
|
7108
|
+
}
|
|
7109
|
+
function rpcError(error) {
|
|
7110
|
+
if (error instanceof NativeAuthorityError) {
|
|
7111
|
+
return {
|
|
7112
|
+
code: -32000,
|
|
7113
|
+
message: error.message,
|
|
7114
|
+
data: { reason_code: error.reasonCode, recovery_action: error.recoveryAction }
|
|
7115
|
+
};
|
|
7116
|
+
}
|
|
7117
|
+
return { code: -32000, message: error instanceof Error ? error.message : String(error) };
|
|
7290
7118
|
}
|
|
7291
7119
|
function encodeMessage(message) {
|
|
7292
7120
|
return Buffer.from(`${JSON.stringify(message)}
|
|
@@ -7299,14 +7127,20 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
|
|
|
7299
7127
|
const elicitation = params.capabilities?.elicitation;
|
|
7300
7128
|
mcp.bindClientHandshake({
|
|
7301
7129
|
version: trustedClient && typeof params.clientInfo?.version === "string" ? params.clientInfo.version : "",
|
|
7130
|
+
protocolVersion: typeof params.protocolVersion === "string" ? params.protocolVersion : undefined,
|
|
7302
7131
|
interactive: trustedClient && elicitation !== null && typeof elicitation === "object" && !Array.isArray(elicitation)
|
|
7303
7132
|
});
|
|
7304
7133
|
return {
|
|
7305
7134
|
jsonrpc: "2.0",
|
|
7306
7135
|
id: message.id ?? null,
|
|
7307
7136
|
result: {
|
|
7308
|
-
protocolVersion:
|
|
7309
|
-
serverInfo: {
|
|
7137
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
7138
|
+
serverInfo: {
|
|
7139
|
+
name: HOST_ID,
|
|
7140
|
+
version: PLUGIN_VERSION,
|
|
7141
|
+
contract: CORE_CONTRACT,
|
|
7142
|
+
minimumHostVersion: MIN_CLAUDE_CODE_VERSION
|
|
7143
|
+
},
|
|
7310
7144
|
capabilities: { tools: {} }
|
|
7311
7145
|
}
|
|
7312
7146
|
};
|
|
@@ -7331,13 +7165,13 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
|
|
|
7331
7165
|
const name = String(params.name ?? "");
|
|
7332
7166
|
const interactive = mcp.sessionInteractive();
|
|
7333
7167
|
if (isPrivilegedOperation(name) && !interactive) {
|
|
7334
|
-
throw new
|
|
7168
|
+
throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
|
|
7335
7169
|
}
|
|
7336
|
-
const identity = hostCallIdentity(params._meta
|
|
7170
|
+
const identity = hostCallIdentity(params._meta);
|
|
7337
7171
|
if (isPrivilegedOperation(name) && !identity) {
|
|
7338
|
-
throw new
|
|
7172
|
+
throw new NativeAuthorityError("correlation_missing", "canonical claudecode/toolUseId metadata is missing");
|
|
7339
7173
|
}
|
|
7340
|
-
const sessionId =
|
|
7174
|
+
const sessionId = mcp.connectionId;
|
|
7341
7175
|
const toolCallId = identity?.toolCallId ?? String(message.id ?? "stdio");
|
|
7342
7176
|
const result = await mcp.callTool(name, params.arguments ?? {}, {
|
|
7343
7177
|
sessionId,
|
|
@@ -7352,11 +7186,10 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
|
|
|
7352
7186
|
result: { content: [{ type: "text", text: JSON.stringify(result) }] }
|
|
7353
7187
|
};
|
|
7354
7188
|
} catch (error) {
|
|
7355
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
7356
7189
|
return {
|
|
7357
7190
|
jsonrpc: "2.0",
|
|
7358
7191
|
id: message.id ?? null,
|
|
7359
|
-
error:
|
|
7192
|
+
error: rpcError(error)
|
|
7360
7193
|
};
|
|
7361
7194
|
} finally {
|
|
7362
7195
|
mcp.aborts.delete(requestId);
|
|
@@ -7368,9 +7201,75 @@ async function handleJsonRpc(message, mcp = createMcpRuntime()) {
|
|
|
7368
7201
|
}
|
|
7369
7202
|
async function writeReply(output, reply) {
|
|
7370
7203
|
const payload = encodeMessage(reply);
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7204
|
+
await new Promise((resolve7, reject) => {
|
|
7205
|
+
if (!output.writable) {
|
|
7206
|
+
reject(new Error("output stream is not writable"));
|
|
7207
|
+
return;
|
|
7208
|
+
}
|
|
7209
|
+
let settled = false;
|
|
7210
|
+
const cleanup = () => {
|
|
7211
|
+
output.off("drain", onDrain);
|
|
7212
|
+
output.off("error", onError);
|
|
7213
|
+
output.off("close", onClose);
|
|
7214
|
+
};
|
|
7215
|
+
const onDrain = () => {
|
|
7216
|
+
if (settled)
|
|
7217
|
+
return;
|
|
7218
|
+
settled = true;
|
|
7219
|
+
cleanup();
|
|
7220
|
+
resolve7();
|
|
7221
|
+
};
|
|
7222
|
+
const onError = (error) => {
|
|
7223
|
+
if (settled)
|
|
7224
|
+
return;
|
|
7225
|
+
settled = true;
|
|
7226
|
+
cleanup();
|
|
7227
|
+
reject(error);
|
|
7228
|
+
};
|
|
7229
|
+
const onClose = () => {
|
|
7230
|
+
if (settled)
|
|
7231
|
+
return;
|
|
7232
|
+
settled = true;
|
|
7233
|
+
cleanup();
|
|
7234
|
+
reject(new Error("output stream closed before write drained"));
|
|
7235
|
+
};
|
|
7236
|
+
output.once("error", onError);
|
|
7237
|
+
output.once("close", onClose);
|
|
7238
|
+
const ok = output.write(payload, (error) => {
|
|
7239
|
+
if (settled)
|
|
7240
|
+
return;
|
|
7241
|
+
if (error) {
|
|
7242
|
+
settled = true;
|
|
7243
|
+
cleanup();
|
|
7244
|
+
reject(error);
|
|
7245
|
+
}
|
|
7246
|
+
});
|
|
7247
|
+
if (ok) {
|
|
7248
|
+
settled = true;
|
|
7249
|
+
cleanup();
|
|
7250
|
+
resolve7();
|
|
7251
|
+
return;
|
|
7252
|
+
}
|
|
7253
|
+
output.once("drain", onDrain);
|
|
7254
|
+
});
|
|
7255
|
+
}
|
|
7256
|
+
function elicitationParams(input) {
|
|
7257
|
+
const details = [
|
|
7258
|
+
`Operation: ${input.operation}`,
|
|
7259
|
+
`Task: ${input.taskId}`,
|
|
7260
|
+
input.risk ? `Risk: ${input.risk}` : null,
|
|
7261
|
+
input.intentRevision !== undefined ? `Intent revision: ${input.intentRevision}` : null,
|
|
7262
|
+
input.intentContentHash ? `Intent hash: ${input.intentContentHash}` : null,
|
|
7263
|
+
input.bindingDigest ? `Binding digest: ${input.bindingDigest}` : null
|
|
7264
|
+
].filter(Boolean);
|
|
7265
|
+
return {
|
|
7266
|
+
mode: "form",
|
|
7267
|
+
message: `Authorize this exact Immune-Brain operation?
|
|
7268
|
+
|
|
7269
|
+
${details.join(`
|
|
7270
|
+
`)}`,
|
|
7271
|
+
requestedSchema: { type: "object", properties: {} }
|
|
7272
|
+
};
|
|
7374
7273
|
}
|
|
7375
7274
|
async function serveStdio(options = {}) {
|
|
7376
7275
|
const input = options.input ?? stdin;
|
|
@@ -7381,16 +7280,88 @@ async function serveStdio(options = {}) {
|
|
|
7381
7280
|
});
|
|
7382
7281
|
let buffer = Buffer.alloc(0);
|
|
7383
7282
|
let accepting = true;
|
|
7283
|
+
let requestSequence = 0;
|
|
7384
7284
|
const inFlight = new Set;
|
|
7285
|
+
const pending = new Map;
|
|
7385
7286
|
let chain = Promise.resolve();
|
|
7287
|
+
const rejectPending = (error) => {
|
|
7288
|
+
for (const item of pending.values())
|
|
7289
|
+
item.reject(error);
|
|
7290
|
+
pending.clear();
|
|
7291
|
+
};
|
|
7292
|
+
mcp.bindNativeConfirmation(async (input2) => {
|
|
7293
|
+
if (!accepting)
|
|
7294
|
+
throw new NativeAuthorityError("interaction_not_opened", "MCP connection is closed");
|
|
7295
|
+
if (input2.signal?.aborted)
|
|
7296
|
+
throw new NativeAuthorityError("user_cancelled", "Tool call was cancelled");
|
|
7297
|
+
const requestId = `immune-brain:elicitation:${mcp.connectionId}:${++requestSequence}`;
|
|
7298
|
+
let abortListener;
|
|
7299
|
+
const response = new Promise((resolve7, reject) => {
|
|
7300
|
+
pending.set(requestId, { resolve: resolve7, reject });
|
|
7301
|
+
abortListener = () => {
|
|
7302
|
+
pending.delete(requestId);
|
|
7303
|
+
reject(new NativeAuthorityError("user_cancelled", "Tool call was cancelled"));
|
|
7304
|
+
};
|
|
7305
|
+
input2.signal?.addEventListener("abort", abortListener, { once: true });
|
|
7306
|
+
});
|
|
7307
|
+
try {
|
|
7308
|
+
await writeReply(output, {
|
|
7309
|
+
jsonrpc: "2.0",
|
|
7310
|
+
id: requestId,
|
|
7311
|
+
method: "elicitation/create",
|
|
7312
|
+
params: elicitationParams(input2)
|
|
7313
|
+
});
|
|
7314
|
+
const reply = await response;
|
|
7315
|
+
if (reply.error) {
|
|
7316
|
+
if (reply.error.code === -32601) {
|
|
7317
|
+
throw new NativeAuthorityError("unsupported_host", "Claude Code rejected MCP elicitation/create");
|
|
7318
|
+
}
|
|
7319
|
+
throw new NativeAuthorityError("correlation_missing", `MCP elicitation failed: ${reply.error.message}`);
|
|
7320
|
+
}
|
|
7321
|
+
const result = reply.result;
|
|
7322
|
+
const action = typeof result === "object" && result !== null && !Array.isArray(result) ? result.action : undefined;
|
|
7323
|
+
if (action !== "accept" && action !== "decline" && action !== "cancel") {
|
|
7324
|
+
throw new NativeAuthorityError("correlation_missing", "MCP elicitation returned an invalid action");
|
|
7325
|
+
}
|
|
7326
|
+
if (action === "accept") {
|
|
7327
|
+
const content = result.content;
|
|
7328
|
+
if (typeof content !== "object" || content === null || Array.isArray(content) || Object.keys(content).length !== 0) {
|
|
7329
|
+
throw new NativeAuthorityError("correlation_missing", "MCP elicitation accept content did not match the requested schema");
|
|
7330
|
+
}
|
|
7331
|
+
}
|
|
7332
|
+
return { decision: action, requestId };
|
|
7333
|
+
} finally {
|
|
7334
|
+
pending.delete(requestId);
|
|
7335
|
+
if (abortListener)
|
|
7336
|
+
input2.signal?.removeEventListener("abort", abortListener);
|
|
7337
|
+
}
|
|
7338
|
+
});
|
|
7386
7339
|
const runCall = (parsed) => {
|
|
7387
7340
|
const task = handleJsonRpc(parsed, mcp).then(async (reply) => {
|
|
7388
7341
|
if (reply)
|
|
7389
7342
|
await writeReply(output, reply);
|
|
7343
|
+
}).catch(() => {
|
|
7344
|
+
executeShutdown(1);
|
|
7390
7345
|
});
|
|
7391
7346
|
inFlight.add(task);
|
|
7392
7347
|
task.finally(() => inFlight.delete(task));
|
|
7393
7348
|
};
|
|
7349
|
+
const routeResponse = (obj) => {
|
|
7350
|
+
const id = typeof obj.id === "string" ? obj.id : "";
|
|
7351
|
+
const waiter = id ? pending.get(id) : undefined;
|
|
7352
|
+
if (waiter) {
|
|
7353
|
+
pending.delete(id);
|
|
7354
|
+
const hasResult = Object.hasOwn(obj, "result");
|
|
7355
|
+
const hasError = Object.hasOwn(obj, "error");
|
|
7356
|
+
if (obj.jsonrpc !== "2.0" || obj.method !== undefined || hasResult === hasError) {
|
|
7357
|
+
waiter.reject(new NativeAuthorityError("correlation_missing", "malformed MCP elicitation response"));
|
|
7358
|
+
} else {
|
|
7359
|
+
waiter.resolve(obj);
|
|
7360
|
+
}
|
|
7361
|
+
return true;
|
|
7362
|
+
}
|
|
7363
|
+
return obj.method === undefined && obj.id !== undefined;
|
|
7364
|
+
};
|
|
7394
7365
|
const drainStdio = async () => {
|
|
7395
7366
|
while (true) {
|
|
7396
7367
|
const newline = buffer.indexOf(`
|
|
@@ -7413,10 +7384,14 @@ async function serveStdio(options = {}) {
|
|
|
7413
7384
|
continue;
|
|
7414
7385
|
}
|
|
7415
7386
|
const obj = parsed;
|
|
7387
|
+
if (routeResponse(obj))
|
|
7388
|
+
continue;
|
|
7416
7389
|
if (obj.jsonrpc !== "2.0") {
|
|
7417
7390
|
await writeReply(output, { jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid Request: jsonrpc must be '2.0'" } });
|
|
7418
7391
|
continue;
|
|
7419
7392
|
}
|
|
7393
|
+
if (routeResponse(obj))
|
|
7394
|
+
continue;
|
|
7420
7395
|
if (typeof obj.method !== "string" || !obj.method) {
|
|
7421
7396
|
const id = obj.id !== undefined && (typeof obj.id === "string" || typeof obj.id === "number") ? obj.id : null;
|
|
7422
7397
|
await writeReply(output, { jsonrpc: "2.0", id, error: { code: -32600, message: "Invalid Request: missing method" } });
|
|
@@ -7433,11 +7408,15 @@ async function serveStdio(options = {}) {
|
|
|
7433
7408
|
const rpc = parsed;
|
|
7434
7409
|
if (rpc.method === "notifications/cancelled") {
|
|
7435
7410
|
const requestId = rpc.params?.requestId;
|
|
7436
|
-
if (requestId
|
|
7411
|
+
if (typeof requestId === "string" && pending.has(requestId)) {
|
|
7412
|
+
pending.get(requestId)?.reject(new NativeAuthorityError("user_cancelled", "native interaction was cancelled"));
|
|
7413
|
+
pending.delete(requestId);
|
|
7414
|
+
} else if (requestId !== undefined) {
|
|
7437
7415
|
mcp.aborts.get(requestId)?.abort(new Error("notifications/cancelled"));
|
|
7416
|
+
}
|
|
7438
7417
|
continue;
|
|
7439
7418
|
}
|
|
7440
|
-
if (
|
|
7419
|
+
if (rpc.method === "tools/call") {
|
|
7441
7420
|
if (!accepting) {
|
|
7442
7421
|
if (rpc.id !== undefined)
|
|
7443
7422
|
await writeReply(output, { jsonrpc: "2.0", id: rpc.id, error: { code: -32000, message: "stdio closed" } });
|
|
@@ -7451,6 +7430,7 @@ async function serveStdio(options = {}) {
|
|
|
7451
7430
|
await writeReply(output, reply);
|
|
7452
7431
|
}
|
|
7453
7432
|
};
|
|
7433
|
+
let resolveStdio = () => {};
|
|
7454
7434
|
let shutdownPromise = null;
|
|
7455
7435
|
const executeShutdown = (code = 0) => {
|
|
7456
7436
|
if (shutdownPromise)
|
|
@@ -7459,31 +7439,49 @@ async function serveStdio(options = {}) {
|
|
|
7459
7439
|
accepting = false;
|
|
7460
7440
|
for (const ac of mcp.aborts.values())
|
|
7461
7441
|
ac.abort(new Error("stdio closed"));
|
|
7462
|
-
|
|
7442
|
+
rejectPending(new NativeAuthorityError("user_cancelled", "MCP connection closed during native interaction"));
|
|
7443
|
+
await Promise.race([
|
|
7444
|
+
Promise.allSettled([...inFlight]),
|
|
7445
|
+
new Promise((resolve7) => setTimeout(resolve7, 200))
|
|
7446
|
+
]);
|
|
7463
7447
|
await mcp.shutdown();
|
|
7464
7448
|
if (output.writable && typeof output.end === "function") {
|
|
7465
7449
|
await new Promise((cb) => output.end(() => cb()));
|
|
7466
7450
|
}
|
|
7467
7451
|
process.exitCode = code;
|
|
7468
7452
|
exit(code);
|
|
7453
|
+
resolveStdio();
|
|
7469
7454
|
})();
|
|
7470
7455
|
return shutdownPromise;
|
|
7471
7456
|
};
|
|
7472
7457
|
await new Promise((resolve7) => {
|
|
7458
|
+
resolveStdio = resolve7;
|
|
7459
|
+
output.on("error", () => {
|
|
7460
|
+
executeShutdown(1);
|
|
7461
|
+
});
|
|
7462
|
+
output.on("close", () => {
|
|
7463
|
+
executeShutdown(0);
|
|
7464
|
+
});
|
|
7473
7465
|
input.on("data", (chunk) => {
|
|
7466
|
+
if (!accepting)
|
|
7467
|
+
return;
|
|
7474
7468
|
chain = chain.then(async () => {
|
|
7469
|
+
if (!accepting)
|
|
7470
|
+
return;
|
|
7475
7471
|
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
7476
7472
|
await drainStdio();
|
|
7473
|
+
}).catch(() => {
|
|
7474
|
+
executeShutdown(1);
|
|
7477
7475
|
});
|
|
7478
7476
|
});
|
|
7479
7477
|
input.on("end", () => {
|
|
7480
|
-
chain.then(() => executeShutdown(0))
|
|
7478
|
+
chain.then(() => executeShutdown(0));
|
|
7481
7479
|
});
|
|
7482
7480
|
input.on("close", () => {
|
|
7483
|
-
chain.then(() => executeShutdown(0))
|
|
7481
|
+
chain.then(() => executeShutdown(0));
|
|
7484
7482
|
});
|
|
7485
7483
|
input.on("error", () => {
|
|
7486
|
-
|
|
7484
|
+
executeShutdown(1);
|
|
7487
7485
|
});
|
|
7488
7486
|
});
|
|
7489
7487
|
}
|
|
@@ -7506,9 +7504,12 @@ if (entry.endsWith("mcp_server.ts") || entry.endsWith("mcp-server.mjs")) {
|
|
|
7506
7504
|
serveStdio();
|
|
7507
7505
|
}
|
|
7508
7506
|
export {
|
|
7507
|
+
supportsElicitationProtocol,
|
|
7509
7508
|
serveStdio,
|
|
7510
7509
|
listMcpTools,
|
|
7511
7510
|
handleJsonRpc,
|
|
7511
|
+
elicitationParams,
|
|
7512
7512
|
createMcpRuntime,
|
|
7513
|
-
TOOLS
|
|
7513
|
+
TOOLS,
|
|
7514
|
+
MCP_PROTOCOL_VERSION
|
|
7514
7515
|
};
|