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
|
@@ -47,7 +47,14 @@ import { reconcileKernelAuthority, repairKernelAuthority } from "../kernel/stora
|
|
|
47
47
|
import { preparePiCanary, revalidatePiCanary } from "../kernel/pi_canary_prepare";
|
|
48
48
|
import { qaFindingId } from "../assurance/qa_findings";
|
|
49
49
|
import { taskDiffIdentity, taskRevisionIdentity } from "../workspace_scope";
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
confirmationRef,
|
|
52
|
+
enrollmentNonce,
|
|
53
|
+
evaluateNativeGate,
|
|
54
|
+
isPrivilegedOperation,
|
|
55
|
+
NativeAuthorityError,
|
|
56
|
+
type NativeConfirmationPort,
|
|
57
|
+
} from "./interaction";
|
|
51
58
|
import { ClaudeReviewHost, FileHookEventLog, type ClaudeHookEvent } from "./review_host";
|
|
52
59
|
import { probeHost, type PermissionMode } from "./capability";
|
|
53
60
|
|
|
@@ -311,6 +318,10 @@ async function mintCapability(
|
|
|
311
318
|
return registry.issue(binding);
|
|
312
319
|
}
|
|
313
320
|
|
|
321
|
+
function throwIfCancelled(signal?: AbortSignal): void {
|
|
322
|
+
if (signal?.aborted) throw new NativeAuthorityError("user_cancelled", "Tool call was cancelled");
|
|
323
|
+
}
|
|
324
|
+
|
|
314
325
|
export interface ClaudeRuntimeOptions {
|
|
315
326
|
cwd: string;
|
|
316
327
|
env?: Record<string, string | undefined>;
|
|
@@ -318,7 +329,7 @@ export interface ClaudeRuntimeOptions {
|
|
|
318
329
|
ports?: AssuranceCoordinatorPorts;
|
|
319
330
|
interactive?: boolean;
|
|
320
331
|
permissionMode?: PermissionMode;
|
|
321
|
-
|
|
332
|
+
requestConfirmation?: NativeConfirmationPort;
|
|
322
333
|
}
|
|
323
334
|
|
|
324
335
|
export class ClaudeRuntime {
|
|
@@ -327,8 +338,7 @@ export class ClaudeRuntime {
|
|
|
327
338
|
private readonly cwd: string;
|
|
328
339
|
private readonly env: Record<string, string | undefined>;
|
|
329
340
|
private readonly interactive: boolean;
|
|
330
|
-
private
|
|
331
|
-
private readonly decisions: Map<string, NativeDecision>;
|
|
341
|
+
private requestConfirmation?: NativeConfirmationPort;
|
|
332
342
|
private hostVersion: string | undefined;
|
|
333
343
|
private mutationRegistry: MutationAuthorityRegistry | null = null;
|
|
334
344
|
private enrollmentRegistry = createEnrollmentAuthorityRegistry();
|
|
@@ -338,8 +348,7 @@ export class ClaudeRuntime {
|
|
|
338
348
|
this.cwd = options.cwd;
|
|
339
349
|
this.env = options.env ?? process.env;
|
|
340
350
|
this.interactive = options.interactive ?? true;
|
|
341
|
-
this.
|
|
342
|
-
this.decisions = options.decisions ?? new Map();
|
|
351
|
+
this.requestConfirmation = options.requestConfirmation;
|
|
343
352
|
this.host = options.host ?? new ClaudeReviewHost(new FileHookEventLog());
|
|
344
353
|
if (options.ports) {
|
|
345
354
|
this.coordinator = new AssuranceCoordinator({ ...options.ports, host: this.host });
|
|
@@ -357,6 +366,10 @@ export class ClaudeRuntime {
|
|
|
357
366
|
this.hostVersion = version || undefined;
|
|
358
367
|
}
|
|
359
368
|
|
|
369
|
+
bindNativeConfirmation(port: NativeConfirmationPort): void {
|
|
370
|
+
this.requestConfirmation = port;
|
|
371
|
+
}
|
|
372
|
+
|
|
360
373
|
async shutdown(): Promise<void> {
|
|
361
374
|
await this.coordinator.onSessionShutdown();
|
|
362
375
|
}
|
|
@@ -393,45 +406,28 @@ export class ClaudeRuntime {
|
|
|
393
406
|
return { registry: this.mutationRegistry, app: this.app };
|
|
394
407
|
}
|
|
395
408
|
|
|
396
|
-
private
|
|
397
|
-
|
|
398
|
-
// any workspace preparation. The live accept elicitation is requested
|
|
399
|
-
// and consumed only after preparation computes the binding digests, so
|
|
400
|
-
// native confirmation is always bound to hashes computed before it.
|
|
401
|
-
const configured = this.decisions.get(operation) ?? meta.decision;
|
|
402
|
-
if (configured === "deny" || configured === "cancel") this.gate(operation, meta);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private gate(operation: string, meta: ToolMeta, binding: {
|
|
409
|
+
private async gate(operation: string, meta: ToolMeta, binding: {
|
|
410
|
+
risk?: string;
|
|
406
411
|
intentRevision?: number;
|
|
407
412
|
intentContentHash?: string;
|
|
408
413
|
bindingDigest?: string;
|
|
409
|
-
} = {}): { confirmation_ref: string } {
|
|
414
|
+
} = {}): Promise<{ confirmation_ref: string }> {
|
|
415
|
+
throwIfCancelled(meta.signal);
|
|
410
416
|
const probe = probeHost(this.env, process.platform, this.hostVersion);
|
|
411
|
-
if (!probe.ok) throw new
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
? this.host.takeConfirmation(meta.sessionId, meta.toolCallId)
|
|
421
|
-
: undefined
|
|
422
|
-
);
|
|
423
|
-
const gate = evaluateNativeGate({
|
|
424
|
-
operation,
|
|
425
|
-
permissionMode,
|
|
426
|
-
requiresUserInteraction,
|
|
427
|
-
interactive: meta.interactive ?? this.interactive,
|
|
428
|
-
decision,
|
|
429
|
-
});
|
|
430
|
-
if (!gate.ok) throw new Error(gate.reason);
|
|
417
|
+
if (!probe.ok) throw new NativeAuthorityError("unsupported_host", probe.reason);
|
|
418
|
+
const interactive = meta.interactive ?? this.interactive;
|
|
419
|
+
if (!interactive) throw new NativeAuthorityError("unsupported_host", "interactive MCP elicitation is unavailable");
|
|
420
|
+
if (!isPrivilegedOperation(operation)) throw new Error(`unsupported native operation ${operation}`);
|
|
421
|
+
if (!this.requestConfirmation) throw new NativeAuthorityError("interaction_not_opened", "native confirmation port is unavailable");
|
|
422
|
+
const result = await this.requestConfirmation({ operation, taskId: meta.taskId, toolCallId: meta.toolCallId, signal: meta.signal, ...binding });
|
|
423
|
+
throwIfCancelled(meta.signal);
|
|
424
|
+
const gate = evaluateNativeGate({ operation, interactive, decision: result.decision });
|
|
425
|
+
if (!gate.ok) throw gate.error;
|
|
431
426
|
return {
|
|
432
427
|
confirmation_ref: confirmationRef({
|
|
433
|
-
|
|
428
|
+
connectionId: meta.sessionId,
|
|
434
429
|
toolCallId: meta.toolCallId,
|
|
430
|
+
requestId: result.requestId,
|
|
435
431
|
operation,
|
|
436
432
|
taskId: meta.taskId,
|
|
437
433
|
...binding,
|
|
@@ -444,17 +440,19 @@ export class ClaudeRuntime {
|
|
|
444
440
|
}
|
|
445
441
|
|
|
446
442
|
async enroll(taskId: string, meta: ToolMeta) {
|
|
447
|
-
this.rejectBeforePreparation("enroll", { ...meta, taskId });
|
|
448
443
|
const now = new Date().toISOString();
|
|
449
444
|
const preparation = await preparePiCanary(this.cwd, { task_id: taskId, now });
|
|
450
|
-
const
|
|
445
|
+
const intent = await readTaskIntent(this.cwd, taskId);
|
|
446
|
+
const gate = await this.gate("enroll", { ...meta, taskId }, {
|
|
447
|
+
risk: intent.intent.risk,
|
|
451
448
|
intentRevision: preparation.intent?.revision,
|
|
452
449
|
intentContentHash: preparation.intent?.content_hash,
|
|
453
450
|
bindingDigest: preparation.digest,
|
|
454
451
|
});
|
|
455
452
|
const { unchanged } = await revalidatePiCanary(this.cwd, { task_id: taskId, now }, preparation);
|
|
456
|
-
if (!unchanged) throw new
|
|
453
|
+
if (!unchanged) throw new NativeAuthorityError("workspace_changed", "workspace changed after native confirmation");
|
|
457
454
|
if (!preparation.intent) throw new Error("enrollment requires a readable TaskIntent");
|
|
455
|
+
throwIfCancelled(meta.signal);
|
|
458
456
|
const nonce = enrollmentNonce();
|
|
459
457
|
const binding: EnrollmentCapabilityBinding = {
|
|
460
458
|
task_id: taskId,
|
|
@@ -493,21 +491,15 @@ export class ClaudeRuntime {
|
|
|
493
491
|
}
|
|
494
492
|
|
|
495
493
|
async authorize(taskId: string, operation: string, meta: ToolMeta, extra: Record<string, unknown> = {}) {
|
|
496
|
-
if (!isPrivilegedOperation(operation) && operation !== "request_authorization") throw new Error(`unsupported privileged operation ${operation}`);
|
|
497
|
-
this.rejectBeforePreparation(operation, { ...meta, taskId });
|
|
498
494
|
if (operation === "repair_authority_state") {
|
|
499
495
|
const authority = reconcileKernelAuthority(this.cwd, taskId);
|
|
500
496
|
if (authority.state !== "repairable_stale_claim" || authority.owner_task_id !== taskId) {
|
|
501
497
|
throw new Error(authority.diagnostic ?? "authority repair requires a repairable stale claim");
|
|
502
498
|
}
|
|
503
|
-
|
|
504
|
-
const current = reconcileKernelAuthority(this.cwd, taskId);
|
|
505
|
-
if (current.state !== authority.state || current.owner_task_id !== authority.owner_task_id || current.revision !== authority.revision) {
|
|
506
|
-
throw new Error("authority changed after native confirmation; repair aborted before capability issuance");
|
|
507
|
-
}
|
|
508
|
-
return repairKernelAuthority(this.cwd, taskId, current.revision);
|
|
499
|
+
return repairKernelAuthority(this.cwd, taskId, authority.revision);
|
|
509
500
|
}
|
|
510
|
-
|
|
501
|
+
if (!isPrivilegedOperation(operation) && operation !== "request_authorization") throw new Error(`unsupported privileged operation ${operation}`);
|
|
502
|
+
let op = operation;
|
|
511
503
|
let decisionOp: { finding_id: string; resolution: string } | undefined;
|
|
512
504
|
const projection = await this.status(taskId);
|
|
513
505
|
if (projection.error || !projection.claim) throw new Error(projection.error ?? "no active backend claim");
|
|
@@ -524,7 +516,7 @@ export class ClaudeRuntime {
|
|
|
524
516
|
if (open.length !== 1) throw new Error(`resolve-user-decision requires exactly one open user decision; found ${open.length}`);
|
|
525
517
|
op = "resolve_user_decision";
|
|
526
518
|
decisionOp = { finding_id: open[0].id, resolution: `resume after literal-user decision: ${open[0].summary}` };
|
|
527
|
-
} else
|
|
519
|
+
} else {
|
|
528
520
|
throw new Error(readiness.blocked ?? "no unique host-derived authorization operation");
|
|
529
521
|
}
|
|
530
522
|
}
|
|
@@ -567,15 +559,22 @@ export class ClaudeRuntime {
|
|
|
567
559
|
writeFileSync(sidecar, `${JSON.stringify(nextIntent, null, 2)}\n`);
|
|
568
560
|
execFileSync("git", ["add", "--", priorIntent.intent_ref.path], { cwd: this.cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
569
561
|
const preparedRecord = await readTaskRecord(this.cwd, taskId);
|
|
570
|
-
if (!preparedRecord.record)
|
|
562
|
+
if (!preparedRecord.record) {
|
|
563
|
+
throw new NativeAuthorityError("workspace_changed", "TaskRecord changed before the breaking revision digest");
|
|
564
|
+
}
|
|
571
565
|
preparedDiffHash = diffHashOf(this.cwd, preparedRecord.record);
|
|
572
566
|
}
|
|
573
567
|
const preparedProjection = await this.status(taskId);
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
568
|
+
try {
|
|
569
|
+
assertProjectionBinding(projection, preparedProjection, Boolean(nextIntent));
|
|
570
|
+
if (preparedProjection.projection.diff_hash !== preparedDiffHash) {
|
|
571
|
+
throw new Error("workspace changed while preparing the authority digest");
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
throw new NativeAuthorityError("workspace_changed", error instanceof Error ? error.message : String(error));
|
|
577
575
|
}
|
|
578
|
-
gate = this.gate(operation, { ...meta, taskId }, {
|
|
576
|
+
gate = await this.gate(operation, { ...meta, taskId }, {
|
|
577
|
+
risk: projection.projection.risk,
|
|
579
578
|
intentRevision: nextIntent?.revision ?? projection.projection.intent_revision,
|
|
580
579
|
intentContentHash: nextIntentHash ?? projection.projection.intent_content_hash,
|
|
581
580
|
bindingDigest: `${preparedDiffHash}:${nextIntentHash ?? ""}`,
|
|
@@ -591,25 +590,18 @@ export class ClaudeRuntime {
|
|
|
591
590
|
}
|
|
592
591
|
const { registry, app } = this.authority();
|
|
593
592
|
const confirmation = gate.confirmation_ref;
|
|
594
|
-
const approval = op === "record_user_approval"
|
|
595
|
-
? {
|
|
596
|
-
id: `approval-user-${randomUUID().slice(0, 8)}`,
|
|
597
|
-
kind: "user" as const,
|
|
598
|
-
authority_role: "user" as const,
|
|
599
|
-
task_revision: projection.projection.intent_revision,
|
|
600
|
-
intent_content_hash: projection.projection.intent_content_hash,
|
|
601
|
-
diff_hash: projection.projection.diff_hash,
|
|
602
|
-
actor_id: actorId,
|
|
603
|
-
summary: "literal user approval",
|
|
604
|
-
}
|
|
605
|
-
: undefined;
|
|
606
593
|
try {
|
|
607
594
|
const capabilityProjection = await this.status(taskId);
|
|
608
|
-
|
|
595
|
+
try {
|
|
596
|
+
assertProjectionBinding(projection, capabilityProjection, Boolean(nextIntent));
|
|
597
|
+
} catch (error) {
|
|
598
|
+
throw new NativeAuthorityError("workspace_changed", error instanceof Error ? error.message : String(error));
|
|
599
|
+
}
|
|
609
600
|
const operationDiffHash = capabilityProjection.projection.diff_hash;
|
|
610
601
|
if (nextIntent && operationDiffHash !== preparedDiffHash) {
|
|
611
|
-
throw new
|
|
602
|
+
throw new NativeAuthorityError("workspace_changed", "workspace changed after native confirmation");
|
|
612
603
|
}
|
|
604
|
+
throwIfCancelled(meta.signal);
|
|
613
605
|
const capability = await mintCapability(registry, {
|
|
614
606
|
authority_kind: "user",
|
|
615
607
|
task_id: taskId,
|
|
@@ -621,11 +613,11 @@ export class ClaudeRuntime {
|
|
|
621
613
|
actor_id: actorId,
|
|
622
614
|
now,
|
|
623
615
|
confirmation_ref: confirmation,
|
|
624
|
-
...(approval ? { approval } : {}),
|
|
625
616
|
...(op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {}),
|
|
626
617
|
...(op === "resolve_user_decision" && decisionOp ? decisionOp : {}),
|
|
627
618
|
...(op === "stop" ? { reason: extra.reason ?? "user stop" } : {}),
|
|
628
619
|
});
|
|
620
|
+
throwIfCancelled(meta.signal);
|
|
629
621
|
const result = app.execute({
|
|
630
622
|
root: this.cwd,
|
|
631
623
|
task_id: taskId,
|
|
@@ -633,7 +625,6 @@ export class ClaudeRuntime {
|
|
|
633
625
|
op,
|
|
634
626
|
capability,
|
|
635
627
|
actor_id: actorId,
|
|
636
|
-
...(approval ? { approval } : {}),
|
|
637
628
|
...(op === "approve_breaking_intent_revision" ? { next_intent: nextIntent, next_intent_ref: nextIntentRef } : {}),
|
|
638
629
|
...(op === "resolve_user_decision" && decisionOp ? decisionOp : {}),
|
|
639
630
|
...(op === "stop" ? { reason: extra.reason ?? "user stop" } : {}),
|
|
@@ -784,6 +775,5 @@ export interface ToolMeta {
|
|
|
784
775
|
requiresUserInteraction?: boolean;
|
|
785
776
|
permissionMode?: PermissionMode;
|
|
786
777
|
interactive?: boolean;
|
|
787
|
-
decision?: NativeDecision;
|
|
788
778
|
signal?: AbortSignal;
|
|
789
779
|
}
|