immune-brain 4.5.0 → 5.0.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 +157 -39
- package/README.zh-CN.md +160 -40
- package/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +42 -45
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +189 -476
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +77 -102
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +7 -2
- package/plugins/immune-brain/.pi-extension/tsconfig.json +2 -2
- package/plugins/immune-brain/agents/immune-brain-advisory-reviewer.md +23 -0
- package/plugins/immune-brain/agents/immune-brain-qa.md +42 -0
- package/plugins/immune-brain/agents/immune-brain-reviewer.md +90 -1
- package/plugins/immune-brain/agents/immune-brain-ui-review.md +21 -0
- package/plugins/immune-brain/dist/BASELINE.md +4 -4
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +9924 -9654
- package/plugins/immune-brain/dist/docs/reference/planning-quality-gate.md +3 -3
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +30 -1
- package/plugins/immune-brain/dist/imm-brainstorm.md +1 -1
- package/plugins/immune-brain/dist/imm-doc-prune.md +2 -2
- package/plugins/immune-brain/dist/{imm-agent-doc-maintain.md → imm-doc-slim.md} +9 -9
- package/plugins/immune-brain/dist/imm-planner.md +60 -20
- package/plugins/immune-brain/dist/imm-pr-fix.md +1 -1
- package/plugins/immune-brain/dist/{imm-review-retro.md → imm-retro.md} +26 -13
- package/plugins/immune-brain/dist/{imm-loop.md → imm-run.md} +44 -11
- package/plugins/immune-brain/dist/registry.yaml +9 -9
- package/plugins/immune-brain/dist/role-prompts/code-review.md +7 -0
- package/plugins/immune-brain/dist/role-prompts/executor.md +35 -2
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +138 -69
- package/plugins/immune-brain/runtime/assurance/host_port.ts +20 -0
- package/plugins/immune-brain/runtime/assurance/qa.ts +2 -2
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +4 -0
- package/plugins/immune-brain/runtime/assurance/verdict_authority.ts +347 -0
- package/plugins/immune-brain/runtime/claude/interaction.ts +1 -2
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +135 -396
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +5 -4
- package/plugins/immune-brain/runtime/claude/review_host.ts +63 -16
- package/plugins/immune-brain/runtime/claude/role_agents.ts +83 -0
- package/plugins/immune-brain/runtime/commands/kernel.ts +3 -6
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +738 -677
- package/plugins/immune-brain/runtime/kernel/application.ts +7 -7
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +15 -8
- package/plugins/immune-brain/runtime/kernel/authority_port.ts +5 -14
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +30 -49
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +6 -5
- package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +0 -1
- package/plugins/immune-brain/runtime/kernel/capability_registry.ts +7 -9
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +170 -17
- package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +5 -11
- package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
- package/plugins/immune-brain/runtime/kernel/storage.ts +170 -11
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +13 -0
- package/plugins/immune-brain/runtime/kernel/types.ts +2 -1
- package/plugins/immune-brain/runtime/kernel/validation.ts +3 -1
- package/plugins/immune-brain/runtime/loop_contract.ts +22 -22
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +7 -0
- package/plugins/immune-brain/runtime/prompts/executor.md +35 -2
- package/plugins/immune-brain/runtime/role_prompt_bridge.ts +27 -0
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +66 -28
- package/plugins/immune-brain/runtime/unattended/batch_kernel_port.ts +106 -0
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +3 -9
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +132 -45
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +0 -6
- package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +40 -36
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +98 -209
- package/plugins/immune-brain/runtime/unattended/batch_state.ts +36 -14
- package/plugins/immune-brain/runtime/unattended/types.ts +0 -2
- package/plugins/immune-brain/runtime/workspace_scope.ts +78 -8
- package/plugins/immune-brain/skills/BASELINE.md +4 -4
- package/plugins/immune-brain/skills/imm-doc-prune/SKILL.md +1 -1
- package/plugins/immune-brain/skills/{imm-agent-doc-maintain → imm-doc-slim}/SKILL.md +9 -9
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +4 -2
- package/plugins/immune-brain/skills/imm-pr-fix/SKILL.md +1 -1
- package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/SKILL.md +7 -7
- package/plugins/immune-brain/skills/{imm-review-retro → imm-retro}/scripts/review_retro.ts +46 -21
- package/plugins/immune-brain/skills/{imm-loop → imm-run}/SKILL.md +7 -7
- package/plugins/immune-brain/skills/registry.yaml +9 -9
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +0 -584
- package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +0 -33
|
@@ -74,6 +74,7 @@ export interface McpRuntimeOptions {
|
|
|
74
74
|
cwd?: string;
|
|
75
75
|
env?: Record<string, string | undefined>;
|
|
76
76
|
ports?: AssuranceCoordinatorPorts;
|
|
77
|
+
authorityOverrides?: Partial<import("../assurance/verdict_authority").VerdictAuthority>;
|
|
77
78
|
host?: ClaudeReviewHost;
|
|
78
79
|
interactive?: boolean;
|
|
79
80
|
requestConfirmation?: NativeConfirmationPort;
|
|
@@ -89,6 +90,7 @@ export function createMcpRuntime(options: McpRuntimeOptions = {}) {
|
|
|
89
90
|
env: options.env ?? process.env,
|
|
90
91
|
host,
|
|
91
92
|
ports: options.ports,
|
|
93
|
+
authorityOverrides: options.authorityOverrides,
|
|
92
94
|
interactive: options.interactive,
|
|
93
95
|
requestConfirmation: options.requestConfirmation,
|
|
94
96
|
batchKernel: options.batchKernel,
|
|
@@ -160,10 +162,10 @@ export function createMcpRuntime(options: McpRuntimeOptions = {}) {
|
|
|
160
162
|
signal: meta.signal,
|
|
161
163
|
};
|
|
162
164
|
if (name === "enroll") return runtime.enroll(taskId, toolMeta);
|
|
163
|
-
if (name === "advance_assurance") return runtime.advance(taskId, toolMeta.signal);
|
|
165
|
+
if (name === "advance_assurance") return runtime.advance(taskId, toolMeta.signal, toolMeta);
|
|
164
166
|
if (name === "submit_review") {
|
|
165
167
|
if (!Object.hasOwn(args, "verdict")) throw new Error("verdict is required");
|
|
166
|
-
return runtime.submitReview(taskId, args.verdict);
|
|
168
|
+
return runtime.submitReview(taskId, args.verdict, toolMeta);
|
|
167
169
|
}
|
|
168
170
|
if (name === "revise_intent") {
|
|
169
171
|
if (!Object.hasOwn(args, "next_intent")) throw new Error("next_intent is required");
|
|
@@ -369,8 +371,7 @@ export function elicitationParams(input: NativeConfirmationInput) {
|
|
|
369
371
|
input.planDigest ? `Plan digest: ${input.planDigest}` : null,
|
|
370
372
|
b?.children ? `Ordered children (${b.children.length}):\n${b.children.map((c) => ` - ${c.task_id} (${c.slice_id}) [risk: ${c.risk ?? "unknown"}]`).join("\n")}` : null,
|
|
371
373
|
b?.excluded && b.excluded.length > 0 ? `Excluded children (${b.excluded.length}):\n${b.excluded.map((e) => ` - ${e.task_id} (${e.slice_id}): ${e.reason}`).join("\n")}` : null,
|
|
372
|
-
b?.budget ? `Budget: max_children=${b.budget.max_children},
|
|
373
|
-
b?.expires_at ? `Expires at: ${b.expires_at}` : null,
|
|
374
|
+
b?.budget ? `Budget: max_children=${b.budget.max_children}, qa_failure_limit=${b.budget.qa_failure_limit}` : null,
|
|
374
375
|
].filter(Boolean);
|
|
375
376
|
return {
|
|
376
377
|
mode: "form",
|
|
@@ -149,9 +149,9 @@ export type AsyncAgentLaunch = { agentId: string; outputFile: string };
|
|
|
149
149
|
/**
|
|
150
150
|
* Recognise the launch receipt this Claude Code build returns for `Agent`.
|
|
151
151
|
*
|
|
152
|
-
* Every `Agent` call here runs asynchronously
|
|
153
|
-
*
|
|
154
|
-
* tool result is therefore
|
|
152
|
+
* Every `Agent` call here runs asynchronously: this Host has no synchronous
|
|
153
|
+
* mode and no background switch, so the dispatch envelope carries nothing that
|
|
154
|
+
* would ask for one. The tool result is therefore
|
|
155
155
|
* `{"isAsync":true,"status":"async_launched","agentId":…,"outputFile":…}`:
|
|
156
156
|
* proof that a subagent started, never its answer. Treating it as the verdict
|
|
157
157
|
* would settle Review on a receipt for starting the reviewer, so the launch
|
|
@@ -169,12 +169,21 @@ export function parseAsyncAgentLaunch(result: string): AsyncAgentLaunch | null {
|
|
|
169
169
|
return { agentId, outputFile };
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
const HANDBACK_TOOL = "SubagentHandback";
|
|
173
|
+
|
|
172
174
|
/**
|
|
173
175
|
* Extract the reviewer's terminal message from its own transcript.
|
|
174
176
|
*
|
|
175
177
|
* Each record carries the `agentId` that wrote it, so the caller's independently
|
|
176
178
|
* observed id is matched per record rather than trusted for the file as a whole;
|
|
177
179
|
* a transcript that interleaves another agent cannot contribute its text.
|
|
180
|
+
*
|
|
181
|
+
* A subagent ends its run either with an assistant text message or, on Hosts
|
|
182
|
+
* that hand the report back through a tool, with a `SubagentHandback` call whose
|
|
183
|
+
* `input.message` is that same report and which leaves no text block behind.
|
|
184
|
+
* Both are the reviewer's own record in its own transcript, so whichever the
|
|
185
|
+
* observed agent wrote last is its result — across records and across the
|
|
186
|
+
* ordered content blocks of a single record.
|
|
178
187
|
*/
|
|
179
188
|
export function readAgentTranscriptResult(transcript: string, agentId: string): string | null {
|
|
180
189
|
let last: string | null = null;
|
|
@@ -192,6 +201,12 @@ export function readAgentTranscriptResult(transcript: string, agentId: string):
|
|
|
192
201
|
if (!block || typeof block !== "object") continue;
|
|
193
202
|
const part = block as Record<string, unknown>;
|
|
194
203
|
if (part.type === "text" && typeof part.text === "string") text += part.text;
|
|
204
|
+
if (part.type === "tool_use" && part.name === HANDBACK_TOOL && part.input && typeof part.input === "object") {
|
|
205
|
+
const report = (part.input as Record<string, unknown>).message;
|
|
206
|
+
// Blocks are ordered, so text before the report is a preamble and
|
|
207
|
+
// only text written after it can supersede it.
|
|
208
|
+
if (typeof report === "string" && report.trim()) { last = report; text = ""; }
|
|
209
|
+
}
|
|
195
210
|
}
|
|
196
211
|
if (text.trim()) last = text;
|
|
197
212
|
}
|
|
@@ -262,6 +277,12 @@ export class FileHookEventLog implements HookEventLog {
|
|
|
262
277
|
|
|
263
278
|
interface PendingReview {
|
|
264
279
|
request: ReviewRequest;
|
|
280
|
+
/**
|
|
281
|
+
* The prompt the Host asked the Parent to dispatch, marker line included.
|
|
282
|
+
* Hook evidence carries Parent-authored text, so this is the only string a
|
|
283
|
+
* dispatched prompt is ever compared against.
|
|
284
|
+
*/
|
|
285
|
+
dispatchPrompt: string;
|
|
265
286
|
initialCursors: Map<string, number>;
|
|
266
287
|
sessionCursors: Map<string, number>;
|
|
267
288
|
startEvent?: Extract<ClaudeHookEvent, { type: "SubagentStart" }>;
|
|
@@ -271,18 +292,39 @@ interface PendingReview {
|
|
|
271
292
|
error?: string;
|
|
272
293
|
}
|
|
273
294
|
|
|
295
|
+
/**
|
|
296
|
+
* The reserved dispatch prompt: the reservation marker line followed by the
|
|
297
|
+
* per-dispatch snapshot facts, byte for byte.
|
|
298
|
+
*
|
|
299
|
+
* One place builds it and one place compares against it, so the marker format
|
|
300
|
+
* cannot drift from what the binding rule accepts. The static instructions are
|
|
301
|
+
* deliberately absent: the dispatched reviewer definition carries them, and they
|
|
302
|
+
* are generated from one source by the plugin build.
|
|
303
|
+
*/
|
|
304
|
+
function reservedDispatchPrompt(
|
|
305
|
+
request: Pick<ReviewRequest, "operationId" | "taskId" | "snapshotPrompt">,
|
|
306
|
+
): string {
|
|
307
|
+
return `<!-- immune-brain:operation_id=${request.operationId} task_id=${request.taskId} -->\n${request.snapshotPrompt}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Whether hook evidence carrying a prompt belongs to this reservation.
|
|
312
|
+
*
|
|
313
|
+
* The prompt is Parent-authored text, so the marker is not an acceptance path:
|
|
314
|
+
* a dispatch that keeps the matching `operation_id` and `task_id` marker but
|
|
315
|
+
* appends, truncates or rewrites the body does not bind and cannot settle. Only
|
|
316
|
+
* the exact reserved dispatch prompt does. Evidence with no prompt at all is
|
|
317
|
+
* judged by the identifiers the hook itself supplies.
|
|
318
|
+
*/
|
|
319
|
+
function bindsPrompt(prompt: string | undefined, pending: PendingReview): boolean {
|
|
320
|
+
return prompt === undefined || prompt === pending.dispatchPrompt;
|
|
321
|
+
}
|
|
322
|
+
|
|
274
323
|
function bindsStart(event: Extract<ClaudeHookEvent, { type: "SubagentStart" }>, pending: PendingReview): boolean {
|
|
275
324
|
if (event.taskId && event.taskId !== pending.request.taskId) return false;
|
|
276
|
-
if (event.prompt
|
|
277
|
-
const match = /<!-- immune-brain:operation_id=([^\s]+)\s+task_id=([^\s]+)\s+-->/.exec(event.prompt);
|
|
278
|
-
if (match) {
|
|
279
|
-
if (match[1] !== pending.request.operationId || match[2] !== pending.request.taskId) return false;
|
|
280
|
-
} else if (event.prompt !== pending.request.prompt) {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
325
|
+
if (!bindsPrompt(event.prompt, pending)) return false;
|
|
284
326
|
if (event.operationId) return event.operationId === pending.request.operationId;
|
|
285
|
-
return event.prompt !== undefined
|
|
327
|
+
return event.prompt !== undefined;
|
|
286
328
|
}
|
|
287
329
|
|
|
288
330
|
export class ClaudeReviewHost implements AssuranceHostPort {
|
|
@@ -306,17 +348,19 @@ export class ClaudeReviewHost implements AssuranceHostPort {
|
|
|
306
348
|
}
|
|
307
349
|
this.pending.set(request.operationId, {
|
|
308
350
|
request,
|
|
351
|
+
dispatchPrompt: reservedDispatchPrompt(request),
|
|
309
352
|
initialCursors,
|
|
310
353
|
sessionCursors,
|
|
311
354
|
consumed: false,
|
|
312
355
|
});
|
|
313
356
|
return {
|
|
314
357
|
id: request.operationId,
|
|
358
|
+
// Only parameters the Claude `Agent` tool accepts. The Host has no turn
|
|
359
|
+
// cap and no background switch, so `request.maxTurns` (which the Pi port
|
|
360
|
+
// forwards natively) has no receiver here and is not projected.
|
|
315
361
|
dispatch: {
|
|
316
362
|
name: CLAUDE_REVIEWER_AGENT,
|
|
317
|
-
prompt:
|
|
318
|
-
max_turns: request.maxTurns,
|
|
319
|
-
run_in_background: false,
|
|
363
|
+
prompt: reservedDispatchPrompt(request),
|
|
320
364
|
},
|
|
321
365
|
};
|
|
322
366
|
}
|
|
@@ -419,6 +463,9 @@ export class ClaudeReviewHost implements AssuranceHostPort {
|
|
|
419
463
|
if (event.toolName !== AGENT_TOOL) return;
|
|
420
464
|
if (!event.agentId || !event.operationId || event.operationId !== state.request.operationId) return;
|
|
421
465
|
if ("taskId" in event && event.taskId && event.taskId !== state.request.taskId) return;
|
|
466
|
+
// The `Agent` tool input is where this Host actually reports the
|
|
467
|
+
// dispatched prompt, so it is bound by the same exactness rule.
|
|
468
|
+
if (!bindsPrompt(event.prompt, state)) return;
|
|
422
469
|
if (state.startEvent && event.sessionId !== state.startEvent.sessionId) return;
|
|
423
470
|
if (state.startEvent && event.agentId !== state.startEvent.agentId) return;
|
|
424
471
|
if (state.stopEvent && event.sessionId !== state.stopEvent.sessionId) return;
|
|
@@ -443,7 +490,7 @@ export class ClaudeReviewHost implements AssuranceHostPort {
|
|
|
443
490
|
}
|
|
444
491
|
if (state.stopEvent) {
|
|
445
492
|
if (state.stopEvent === event) return;
|
|
446
|
-
state.error = "duplicate SubagentStop observed for review reservation";
|
|
493
|
+
state.error = "duplicate SubagentStop observed for review reservation: a reviewer was continued after it finished; a reviewer cannot be continued and a fresh reviewer must be dispatched";
|
|
447
494
|
return;
|
|
448
495
|
}
|
|
449
496
|
state.stopEvent = event;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { InternalRole } from "../role_prompt_bridge";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude plugin agent definitions for the read-only internal roles.
|
|
5
|
+
*
|
|
6
|
+
* A native definition is the strictest boundary the Host can express for these
|
|
7
|
+
* roles: `Read, Grep, Glob` grants no write, shell or dispatch tool, while the
|
|
8
|
+
* Pi mapping leaves the same boundary as prompt text. `INTERNAL_ROLE_PROMPTS`
|
|
9
|
+
* declares `no tools` or `read-only tools` for every role here; a native
|
|
10
|
+
* definition cannot express an empty allowlist without inheriting every tool,
|
|
11
|
+
* so read-only-without-shell is what ships.
|
|
12
|
+
*
|
|
13
|
+
* The bodies are generated from the matching `runtime/prompts/` source by
|
|
14
|
+
* `scripts/build-claude-plugin.ts`, never maintained by hand.
|
|
15
|
+
*/
|
|
16
|
+
export interface ClaudeReadonlyRoleAgent {
|
|
17
|
+
/** The plugin-qualified agent type the Claude `Agent` tool accepts. */
|
|
18
|
+
readonly agent: string;
|
|
19
|
+
/** Repository-relative definition the plugin build generates. */
|
|
20
|
+
readonly definition: string;
|
|
21
|
+
/** The role prompt source the definition body is generated from. */
|
|
22
|
+
readonly prompt: string;
|
|
23
|
+
/** Frontmatter `description`: when the Host may select this agent. */
|
|
24
|
+
readonly description: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const CLAUDE_READONLY_ROLE_AGENTS = {
|
|
28
|
+
qa: {
|
|
29
|
+
agent: "immune-brain:immune-brain-qa",
|
|
30
|
+
definition: "plugins/immune-brain/agents/immune-brain-qa.md",
|
|
31
|
+
prompt: "plugins/immune-brain/runtime/prompts/qa.md",
|
|
32
|
+
description:
|
|
33
|
+
"Immune-Brain QA authority. Judges recorded execution evidence against one active target and returns one decision.",
|
|
34
|
+
},
|
|
35
|
+
"ui-review": {
|
|
36
|
+
agent: "immune-brain:immune-brain-ui-review",
|
|
37
|
+
definition: "plugins/immune-brain/agents/immune-brain-ui-review.md",
|
|
38
|
+
prompt: "plugins/immune-brain/runtime/prompts/ui-review.md",
|
|
39
|
+
description:
|
|
40
|
+
"Immune-Brain UI Review authority. Read-only review of a bounded UI change surface and its evidence.",
|
|
41
|
+
},
|
|
42
|
+
"advisory-reviewer": {
|
|
43
|
+
agent: "immune-brain:immune-brain-advisory-reviewer",
|
|
44
|
+
definition: "plugins/immune-brain/agents/immune-brain-advisory-reviewer.md",
|
|
45
|
+
prompt: "plugins/immune-brain/runtime/prompts/advisory-reviewer.md",
|
|
46
|
+
description:
|
|
47
|
+
"Immune-Brain advisory reviewer for one explicit lens. Read-only advice; synthesis stays with the coordinating Parent.",
|
|
48
|
+
},
|
|
49
|
+
} as const satisfies Record<string, ClaudeReadonlyRoleAgent>;
|
|
50
|
+
|
|
51
|
+
/** The read-only roles that ship a generated native definition. */
|
|
52
|
+
export type ClaudeReadonlyRole = keyof typeof CLAUDE_READONLY_ROLE_AGENTS;
|
|
53
|
+
|
|
54
|
+
export const CLAUDE_READONLY_ROLES = Object.keys(
|
|
55
|
+
CLAUDE_READONLY_ROLE_AGENTS,
|
|
56
|
+
) as ClaudeReadonlyRole[];
|
|
57
|
+
|
|
58
|
+
/** The tool allowlist every generated read-only definition declares. */
|
|
59
|
+
export const CLAUDE_READONLY_TOOLS = ["Read", "Grep", "Glob"] as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The read-only internal roles that ship no definition because the Host already
|
|
63
|
+
* provides the agent. `arch-explorer` is architecture discovery: Claude's own
|
|
64
|
+
* `Explore` agent is the read-only research agent, and shipping a second
|
|
65
|
+
* definition would fork a boundary the Host owns.
|
|
66
|
+
*/
|
|
67
|
+
export const CLAUDE_HOST_PROVIDED_ROLE_AGENTS = {
|
|
68
|
+
"arch-explorer": { agent: "Explore" },
|
|
69
|
+
} as const satisfies Record<string, { agent: string }>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Roles that are not dispatched as native Claude agents.
|
|
73
|
+
*
|
|
74
|
+
* `code-review` ships its own definition with a wider allowlist (it must run
|
|
75
|
+
* read-only Git commands), so it is not part of this table. The remaining roles
|
|
76
|
+
* write: `executor`, `test-fixer` and `pr-fix` mutate the workspace, and
|
|
77
|
+
* `compounder` writes learning storage. A write-capable role stays bounded by
|
|
78
|
+
* its dispatch envelope and the Parent, not by an agent definition.
|
|
79
|
+
*/
|
|
80
|
+
export const CLAUDE_NATIVE_AGENT_ROLES = new Set<InternalRole>([
|
|
81
|
+
...CLAUDE_READONLY_ROLES,
|
|
82
|
+
...(Object.keys(CLAUDE_HOST_PROVIDED_ROLE_AGENTS) as InternalRole[]),
|
|
83
|
+
]);
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { inspectSpecBinding } from "../kernel/spec_binding";
|
|
30
30
|
import { projectTask, resolveProjectedRisk } from "../kernel/completion";
|
|
31
31
|
import { deriveAssuranceAuthorization } from "../kernel/assurance_projection";
|
|
32
|
-
import {
|
|
32
|
+
import { taskDeliveryIdentity } from "../workspace_scope";
|
|
33
33
|
import type { TaskIntentV1, TaskRisk } from "../kernel/types";
|
|
34
34
|
import {
|
|
35
35
|
canonicalDescriptorBytes,
|
|
@@ -273,10 +273,7 @@ function runInspect(root: string): KernelExecution {
|
|
|
273
273
|
const workspaceState = readWorkspaceStateRaw(root);
|
|
274
274
|
let identity;
|
|
275
275
|
try {
|
|
276
|
-
identity =
|
|
277
|
-
record.contract === "assurance_kernel/task_record/v4"
|
|
278
|
-
? taskRevisionIdentity(root, intent.scope_hint, record.git_base_head, intent.task_id)
|
|
279
|
-
: taskDiffIdentity(root, intent.scope_hint, intent.task_id);
|
|
276
|
+
identity = taskDeliveryIdentity(root, record);
|
|
280
277
|
} catch (error) {
|
|
281
278
|
return sourceFailure("inspect", error);
|
|
282
279
|
}
|
|
@@ -571,7 +568,7 @@ function runIntentAuthor(args: string[], root: string): KernelExecution {
|
|
|
571
568
|
"rejected",
|
|
572
569
|
"kernel_owner_active",
|
|
573
570
|
null,
|
|
574
|
-
"Route to imm-
|
|
571
|
+
"Route to imm-run instead of authoring a new draft.",
|
|
575
572
|
),
|
|
576
573
|
};
|
|
577
574
|
if (owner.v3)
|