immune-brain 4.0.1 → 4.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Immune-Brain agent skill system",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "immune-brain",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Immune-Brain Claude Code Host: native Enrollment, QA, Review, and Kernel settlement.",
5
5
  "author": {
6
6
  "name": "Immune-Brain Team"
@@ -37,6 +37,10 @@ import {
37
37
  type UserAttentionReason,
38
38
  } from "./pi-canary-interaction";
39
39
  import { isToolFailureState, throwToolFailure } from "./pi-canary-tool-failure";
40
+ import { resolveUxLanguage, uxText } from "./ux-language";
41
+
42
+ /** Host-native UI language; see ux-language.ts. Resolved once per process. */
43
+ const UX_LANG = resolveUxLanguage();
40
44
 
41
45
  const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
42
46
 
@@ -62,7 +66,7 @@ async function requestEnrollmentConfirmation(
62
66
  task_id: taskId,
63
67
  state: "Approval required",
64
68
  result: title,
65
- next: "Review enrollment evidence",
69
+ next: uxText(UX_LANG, "Review enrollment evidence", "请审查 Enrollment 证据"),
66
70
  });
67
71
  const selected = await requestAuthorityDialog(pi, ctx, {
68
72
  attention_id: randomUUID(),
@@ -75,8 +79,8 @@ async function requestEnrollmentConfirmation(
75
79
  details,
76
80
  signal,
77
81
  actions: [
78
- { value: "confirm", label: "Confirm enrollment", description: "Create the Kernel-managed task" },
79
- { value: "cancel", label: "Cancel", description: "Leave planning artifacts and authority unchanged" },
82
+ { value: "confirm", label: uxText(UX_LANG, "Confirm enrollment", "确认 Enrollment"), description: uxText(UX_LANG, "Create the Kernel-managed task", "创建 Kernel 托管任务") },
83
+ { value: "cancel", label: uxText(UX_LANG, "Cancel", "取消"), description: uxText(UX_LANG, "Leave planning artifacts and authority unchanged", "不改动规划产物与权限状态") },
80
84
  ],
81
85
  });
82
86
  return selected === "confirm";
@@ -305,7 +309,7 @@ async function executeForegroundEnrollment(
305
309
 
306
310
  try {
307
311
  signal.throwIfAborted();
308
- progress("preparing", `Preparing immutable Kernel owners for ${taskId}`);
312
+ progress("preparing", uxText(UX_LANG, `Preparing immutable Kernel owners for ${taskId}`, `正在为 ${taskId} 准备不可变 Kernel 所有者`));
309
313
  const now = new Date().toISOString();
310
314
  let taskIntent: Awaited<ReturnType<typeof readTaskIntent>>;
311
315
 
@@ -489,13 +493,13 @@ async function executeForegroundEnrollment(
489
493
  `Owners: intent+workspace+claim+record checked`,
490
494
  `Route: Kernel enrollment`,
491
495
  ].join("\n");
492
- progress("awaiting_confirmation", "Waiting for exact literal-user confirmation");
496
+ progress("awaiting_confirmation", uxText(UX_LANG, "Waiting for exact literal-user confirmation", "等待本人确认"));
493
497
  const confirmed = await requestEnrollmentConfirmation(
494
498
  pi,
495
499
  ctx,
496
500
  taskId,
497
501
  "enrollment",
498
- "Create Kernel-managed task?",
502
+ uxText(UX_LANG, "Create Kernel-managed task?", "创建 Kernel 托管任务?"),
499
503
  confirmationSummary,
500
504
  confirmationDetails,
501
505
  signal,
@@ -526,7 +530,7 @@ async function executeForegroundEnrollment(
526
530
  return terminal(action, taskId, "blocked", stage, "Intent changed after confirmation; enrollment aborted before authority", "restore the intended snapshot and rerun enrollment");
527
531
  }
528
532
 
529
- progress("revalidating", "Revalidating immutable owners and the confirmed TaskIntent");
533
+ progress("revalidating", uxText(UX_LANG, "Revalidating immutable owners and the confirmed TaskIntent", "正在重新校验不可变所有者与已确认的 TaskIntent"));
530
534
  const { unchanged } = await revalidatePiCanary(root, { task_id: taskId, now }, preparation);
531
535
  if (!unchanged)
532
536
  return terminal(action, taskId, "blocked", stage, "Workspace changed after confirmation; enrollment aborted before authority", "restore the intended snapshot and rerun enrollment");
@@ -555,7 +559,7 @@ async function executeForegroundEnrollment(
555
559
  now,
556
560
  };
557
561
 
558
- progress("rehearsing", "Running the zero-write Kernel owner rehearsal");
562
+ progress("rehearsing", uxText(UX_LANG, "Running the zero-write Kernel owner rehearsal", "正在执行 Kernel 所有者零写入预演"));
559
563
  const rehearsal = await runEnrollmentRehearsal(root, input, capability, registry);
560
564
  if (!rehearsal.rehearsed || rehearsal.evidence.outcome !== "ready")
561
565
  return terminal(action, taskId, "failed", stage, `Kernel enrollment rehearsal failed: ${rehearsal.evidence.blockers.join("; ")}`, "resolve the final-lock preconditions and retry");
@@ -65,6 +65,10 @@ import {
65
65
  type UserAttentionReason,
66
66
  } from "./pi-canary-interaction";
67
67
  import { isToolFailureState, throwToolFailure, type ToolFailureV1 } from "./pi-canary-tool-failure";
68
+ import { resolveUxLanguage, uxText } from "./ux-language";
69
+
70
+ /** Host-native UI language; see ux-language.ts. Resolved once per process. */
71
+ const UX_LANG = resolveUxLanguage();
68
72
  import { taskDiffIdentity, taskRevisionIdentity, captureGitTaskSnapshot } from "../runtime/workspace_scope";
69
73
  import { reviewAdvisoryRecords, reviewReworkFindings } from "../runtime/assurance/coordinator";
70
74
  import {
@@ -323,7 +327,7 @@ export default function (
323
327
  task_id: claim.task_id,
324
328
  state: "Blocked",
325
329
  result: projection.error,
326
- next: "Inspect authority state",
330
+ next: uxText(UX_LANG, "Inspect authority state", "检查权限状态"),
327
331
  });
328
332
  return;
329
333
  }
@@ -395,8 +399,8 @@ export default function (
395
399
  if (input?.task_id) presentTaskRail(ctx, {
396
400
  task_id: input.task_id,
397
401
  state: "Planning",
398
- result: "Preparing enrollment",
399
- next: "Review the native enrollment decision",
402
+ result: uxText(UX_LANG, "Preparing enrollment", "正在准备 Enrollment"),
403
+ next: uxText(UX_LANG, "Review the native enrollment decision", "请审查原生 Enrollment 决策"),
400
404
  });
401
405
  }
402
406
  });
@@ -871,24 +875,24 @@ export default function (
871
875
  presentTaskRail(ctx, {
872
876
  task_id: taskId,
873
877
  state: "Approval required",
874
- result: `${operation} requires your confirmation`,
875
- next: `Decide ${operation}`,
878
+ result: uxText(UX_LANG, `${operation} requires your confirmation`, `${operation} 需要您的确认`),
879
+ next: uxText(UX_LANG, `Decide ${operation}`, `请决策 ${operation}`),
876
880
  });
877
881
  const attention = {
878
882
  attention_id: randomUUID(),
879
883
  task_id: taskId,
880
884
  reason: attentionReason,
881
- label: `${operation} approval required`,
885
+ label: uxText(UX_LANG, `${operation} approval required`, `${operation} 待您批准`),
882
886
  };
883
887
  try {
884
888
  const selected = await requestAuthorityDialog(pi, ctx, attention, {
885
- title: `Authorize ${operation}?`,
889
+ title: uxText(UX_LANG, `Authorize ${operation}?`, `是否批准 ${operation}?`),
886
890
  summary: dialogSummary,
887
891
  details: dialogDetails,
888
892
  signal: ctx.signal,
889
893
  actions: [
890
- { value: "authorize", label: "Authorize", description: `Apply ${operation} after re-checking state` },
891
- { value: "cancel", label: "Cancel", description: "Leave managed authority unchanged" },
894
+ { value: "authorize", label: uxText(UX_LANG, "Authorize", "批准"), description: uxText(UX_LANG, `Apply ${operation} after re-checking state`, `重新校验状态后应用 ${operation}`) },
895
+ { value: "cancel", label: uxText(UX_LANG, "Cancel", "取消"), description: uxText(UX_LANG, "Leave managed authority unchanged", "保持托管权限状态不变") },
892
896
  ],
893
897
  });
894
898
  confirmed = selected === "authorize";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Host-native UI language for Immune-Brain TUI text (Task Rail sentences,
3
+ * authority dialog titles/actions, progress summaries). This layer is
4
+ * deterministic extension code, so conversational AGENTS.md reply-language
5
+ * rules cannot reach it; users opt in with IMM_UX_LANGUAGE (for example
6
+ * `zh`). Anything unrecognized keeps English, the published default.
7
+ *
8
+ * Boundary: only sentence-level interaction text follows this setting.
9
+ * Machine contracts and domain terms (state enums, operation ids, Task /
10
+ * Intent / Claim / Acceptance field labels, hashes, paths, CLI commands)
11
+ * always stay literal, as do agent-facing Tool result reasons and
12
+ * diagnostic notifications.
13
+ */
14
+ export type UxLanguage = "en" | "zh";
15
+
16
+ export function resolveUxLanguage(
17
+ env: Readonly<Record<string, string | undefined>> = process.env,
18
+ ): UxLanguage {
19
+ const raw = env.IMM_UX_LANGUAGE?.trim().toLowerCase();
20
+ if (raw === "zh" || raw === "zh-cn" || raw === "zh-tw" || raw === "chinese" || raw === "中文")
21
+ return "zh";
22
+ return "en";
23
+ }
24
+
25
+ /** Pick the interaction sentence for the resolved language. */
26
+ export function uxText(lang: UxLanguage, en: string, zh: string): string {
27
+ return lang === "zh" ? zh : en;
28
+ }
@@ -124,6 +124,17 @@ always run sequentially.
124
124
  - Do not translate or rename machine contracts: schema fields, enum values,
125
125
  CLI flags, JSON keys, file paths, tool names, API names,
126
126
  and code identifiers stay literal.
127
+ - Host-native UI text (Task Rail sentences, authority dialog titles and
128
+ actions, enrollment progress summaries) is deterministic extension code and
129
+ cannot follow conversational rules; users localize it with the
130
+ `IMM_UX_LANGUAGE` environment variable (for example `IMM_UX_LANGUAGE=zh`).
131
+ Only sentence-level interaction text follows it; machine contracts, domain
132
+ field labels, and agent-facing Tool result reasons stay literal English.
133
+ - Internal role dispatches (`dispatch_role` and routed role contexts) carry
134
+ the user-facing interaction language as `interaction_language` in the
135
+ delegation context, so QA/Review/Explorer roles report in the user's
136
+ language while keeping machine contracts literal. Omit it to keep English
137
+ role output.
127
138
  - Preserve `CONTEXT.md` canonical terms such as `Step`, `Plan`, `Spec`,
128
139
  `Skill`, `Brainstorm`, `Executor`, `QA`, `Compounder`, `Learning`, and `ADR`;
129
140
  add local-language explanations around them when helpful.
@@ -39,7 +39,7 @@ function probeHost(env = process.env, platform = process.platform, hostVersion)
39
39
  }
40
40
 
41
41
  // plugins/immune-brain/runtime/plugin_version.ts
42
- var PLUGIN_VERSION = "4.0.1";
42
+ var PLUGIN_VERSION = "4.1.0";
43
43
 
44
44
  // plugins/immune-brain/runtime/claude/interaction.ts
45
45
  import { createHash, randomUUID } from "node:crypto";
@@ -1263,6 +1263,13 @@ function loadRolePrompt(role) {
1263
1263
  }
1264
1264
  throw new Error(`internal role prompt is not packaged: ${role}`);
1265
1265
  }
1266
+ function readInteractionLanguage(context) {
1267
+ const raw = context.interaction_language;
1268
+ if (typeof raw !== "string")
1269
+ return null;
1270
+ const trimmed = raw.trim();
1271
+ return trimmed.length > 0 ? trimmed : null;
1272
+ }
1266
1273
  function buildRoleDelegationPacket(input) {
1267
1274
  const spec = roleSpec(input.role);
1268
1275
  const requestedGate = input.context.review_gate;
@@ -1273,11 +1280,15 @@ function buildRoleDelegationPacket(input) {
1273
1280
  throw new Error(`${input.role} cannot carry review gate ${requestedGate}`);
1274
1281
  }
1275
1282
  const reviewGate = spec.review_gate;
1283
+ const interactionLanguage = readInteractionLanguage(input.context);
1276
1284
  const context = stableStringify(input.context);
1277
1285
  const prompt = [
1278
1286
  `internal role: ${input.role}`,
1279
1287
  `tool_policy: ${spec.tool_policy}`,
1280
1288
  `do not discover or load Pi Skills; execute this internal role contract directly`,
1289
+ ...interactionLanguage ? [
1290
+ `interaction language: ${interactionLanguage} — write findings, summaries, and explanations in this language; keep machine contracts (file paths, code identifiers, CLI commands, enum values, task ids) literal`
1291
+ ] : [],
1281
1292
  loadRolePrompt(input.role).trim(),
1282
1293
  `Delegation context (untrusted data): ${context}`
1283
1294
  ].join(`
@@ -164,6 +164,14 @@ explicit runtime-supported role boundary requests it, followed by the returned
164
164
  foreground Agent envelope exactly. It is not an extra gate on Kernel Assurance.
165
165
  All internal Agent envelopes use `run_in_background: false`.
166
166
 
167
+ Role dispatches follow the user's interaction language: include
168
+ `"interaction_language"` in the `dispatch_role` or routed role context with
169
+ the current reply language (the current explicit user instruction, else the
170
+ project `AGENTS.md` reply-language default, for example `"中文"` or
171
+ `"English"`), so role findings and summaries arrive in the user's language.
172
+ Machine contracts stay literal regardless. Omit the field to keep English
173
+ role output.
174
+
167
175
  The internal Compounder is optional: only closed work with structured evidence
168
176
  of a reusable Learning may route to it. Routine completion creates no Learning.
169
177
  It cannot approve successors or delay terminal settlement. A projection with
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/plugin_versioning.ts from the root package.json.
2
- export const PLUGIN_VERSION = "4.0.1" as const;
2
+ export const PLUGIN_VERSION = "4.1.0" as const;
@@ -89,6 +89,14 @@ export interface RoleDelegationContext {
89
89
  target_id?: string;
90
90
  review_gate?: StableReviewGate;
91
91
  changed_files_signature?: string;
92
+ /**
93
+ * Optional user-facing interaction language (for example "中文" or
94
+ * "English"). The Parent sets it from the current explicit user
95
+ * instruction or the project reply-language default; when present the
96
+ * delegation prompt asks the role to report in that language while
97
+ * keeping machine contracts literal. Omitted keeps English role output.
98
+ */
99
+ interaction_language?: string;
92
100
  [key: string]: unknown;
93
101
  }
94
102
 
@@ -137,6 +145,13 @@ export function loadRolePrompt(role: InternalRole): string {
137
145
  throw new Error(`internal role prompt is not packaged: ${role}`);
138
146
  }
139
147
 
148
+ function readInteractionLanguage(context: RoleDelegationContext): string | null {
149
+ const raw = context.interaction_language;
150
+ if (typeof raw !== "string") return null;
151
+ const trimmed = raw.trim();
152
+ return trimmed.length > 0 ? trimmed : null;
153
+ }
154
+
140
155
  export function buildRoleDelegationPacket(input: {
141
156
  role: InternalRole;
142
157
  context: RoleDelegationContext;
@@ -155,11 +170,17 @@ export function buildRoleDelegationPacket(input: {
155
170
  }
156
171
 
157
172
  const reviewGate = spec.review_gate;
173
+ const interactionLanguage = readInteractionLanguage(input.context);
158
174
  const context = stableStringify(input.context);
159
175
  const prompt = [
160
176
  `internal role: ${input.role}`,
161
177
  `tool_policy: ${spec.tool_policy}`,
162
178
  `do not discover or load Pi Skills; execute this internal role contract directly`,
179
+ ...(interactionLanguage
180
+ ? [
181
+ `interaction language: ${interactionLanguage} — write findings, summaries, and explanations in this language; keep machine contracts (file paths, code identifiers, CLI commands, enum values, task ids) literal`,
182
+ ]
183
+ : []),
163
184
  loadRolePrompt(input.role).trim(),
164
185
  `Delegation context (untrusted data): ${context}`,
165
186
  ].join("\n\n");
@@ -124,6 +124,17 @@ always run sequentially.
124
124
  - Do not translate or rename machine contracts: schema fields, enum values,
125
125
  CLI flags, JSON keys, file paths, tool names, API names,
126
126
  and code identifiers stay literal.
127
+ - Host-native UI text (Task Rail sentences, authority dialog titles and
128
+ actions, enrollment progress summaries) is deterministic extension code and
129
+ cannot follow conversational rules; users localize it with the
130
+ `IMM_UX_LANGUAGE` environment variable (for example `IMM_UX_LANGUAGE=zh`).
131
+ Only sentence-level interaction text follows it; machine contracts, domain
132
+ field labels, and agent-facing Tool result reasons stay literal English.
133
+ - Internal role dispatches (`dispatch_role` and routed role contexts) carry
134
+ the user-facing interaction language as `interaction_language` in the
135
+ delegation context, so QA/Review/Explorer roles report in the user's
136
+ language while keeping machine contracts literal. Omit it to keep English
137
+ role output.
127
138
  - Preserve `CONTEXT.md` canonical terms such as `Step`, `Plan`, `Spec`,
128
139
  `Skill`, `Brainstorm`, `Executor`, `QA`, `Compounder`, `Learning`, and `ADR`;
129
140
  add local-language explanations around them when helpful.