@sema-agent/server 4.3.0 → 5.0.1

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.
Files changed (177) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +16 -16
  4. package/dist/approval-hmac.js +1 -1
  5. package/dist/approval.d.ts +17 -37
  6. package/dist/approval.js +26 -112
  7. package/dist/auth-bridge.d.ts +0 -1
  8. package/dist/auth-bridge.js +0 -1
  9. package/dist/auth-keys.d.ts +1 -1
  10. package/dist/auth-keys.js +1 -1
  11. package/dist/bench/s1/run-firm.js +1 -1
  12. package/dist/boot/config-center.d.ts +1 -1
  13. package/dist/boot/config-center.js +4 -3
  14. package/dist/boot/reapers.d.ts +1 -2
  15. package/dist/boot/reapers.js +4 -5
  16. package/dist/boot/resolve-spec.d.ts +1 -3
  17. package/dist/boot/resolve-spec.js +21 -35
  18. package/dist/boot/stores.js +1 -1
  19. package/dist/brain.js +3 -1
  20. package/dist/capabilities/center-prompts.d.ts +2 -3
  21. package/dist/capabilities/center-prompts.js +2 -3
  22. package/dist/capabilities/sandbox-file-send.d.ts +0 -2
  23. package/dist/capabilities/sandbox-file-send.js +2 -5
  24. package/dist/capabilities/scenarios.d.ts +2 -13
  25. package/dist/capabilities/scenarios.js +3 -22
  26. package/dist/capabilities/send-user-file-tool.js +3 -3
  27. package/dist/config-center/apply-effective.js +51 -13
  28. package/dist/config-center/types.d.ts +10 -3
  29. package/dist/config-provider.d.ts +1 -1
  30. package/dist/config-provider.js +6 -11
  31. package/dist/config-types.d.ts +15 -29
  32. package/dist/config.d.ts +28 -0
  33. package/dist/config.js +122 -68
  34. package/dist/fleet/fleet-bus.d.ts +1 -1
  35. package/dist/fleet/fleet-bus.js +25 -33
  36. package/dist/fleet-client.js +1 -1
  37. package/dist/http/active-run-conflict.d.ts +2 -0
  38. package/dist/http/active-run-conflict.js +1 -0
  39. package/dist/http/routes/approvals-assistant.d.ts +2 -1
  40. package/dist/http/routes/approvals-assistant.js +10 -96
  41. package/dist/http/routes/capabilities.js +6 -5
  42. package/dist/http/routes/fleet.js +7 -2
  43. package/dist/http/routes/images.js +10 -4
  44. package/dist/http/routes/runs.js +42 -65
  45. package/dist/http/routes/session-sync.d.ts +1 -1
  46. package/dist/http/routes/session-sync.js +2 -1
  47. package/dist/http/routes/tasks.js +7 -7
  48. package/dist/http/routes/trace-usage.js +1 -1
  49. package/dist/http/routes/workflows.js +3 -1
  50. package/dist/http/server.d.ts +1 -6
  51. package/dist/http/server.js +15 -10
  52. package/dist/http/verify-rounds.d.ts +2 -2
  53. package/dist/http/wire-types.d.ts +1 -1
  54. package/dist/images/bake-validate.d.ts +0 -22
  55. package/dist/images/bake-validate.js +0 -4
  56. package/dist/index.d.ts +4 -4
  57. package/dist/index.js +2 -5
  58. package/dist/main.js +9 -10
  59. package/dist/memory-scope.d.ts +1 -2
  60. package/dist/memory-scope.js +8 -4
  61. package/dist/orchestration/workflow-completion-inbox.d.ts +6 -4
  62. package/dist/orchestration/workflow-completion-inbox.js +6 -4
  63. package/dist/plugins/breaker-state-sql.d.ts +2 -3
  64. package/dist/plugins/file-run-store.d.ts +1 -1
  65. package/dist/plugins/file-run-store.js +1 -1
  66. package/dist/plugins/image-index-sql.d.ts +0 -1
  67. package/dist/plugins/image-index-sql.js +0 -1
  68. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  69. package/dist/plugins/local-checkpoint-store.js +1 -1
  70. package/dist/plugins/memory-engine-pg.js +2 -1
  71. package/dist/plugins/memory-engine-tidb.js +1 -1
  72. package/dist/plugins/memory-engine-vector-util.d.ts +2 -3
  73. package/dist/plugins/memory-engine-vector-util.js +2 -3
  74. package/dist/plugins/memory-run-store.d.ts +2 -2
  75. package/dist/plugins/memory-run-store.js +1 -1
  76. package/dist/plugins/memory-session-policy-store.d.ts +1 -1
  77. package/dist/plugins/memory-session-policy-store.js +1 -1
  78. package/dist/plugins/memory-sync-store-pg.js +1 -1
  79. package/dist/plugins/pg-pool.js +8 -10
  80. package/dist/plugins/pg-rate-limiter.d.ts +1 -4
  81. package/dist/plugins/pg-rate-limiter.js +0 -1
  82. package/dist/plugins/roster-store-sql.d.ts +1 -3
  83. package/dist/plugins/roster-store-sql.js +3 -5
  84. package/dist/plugins/store-backend.d.ts +7 -17
  85. package/dist/plugins/store-backend.js +14 -30
  86. package/dist/plugins/tidb-pool.js +6 -30
  87. package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
  88. package/dist/plugins/workflow-journal-store-sql.js +1 -1
  89. package/dist/principal-jwt.js +2 -2
  90. package/dist/prompts-domain-validate.d.ts +4 -3
  91. package/dist/prompts-domain-validate.js +7 -8
  92. package/dist/router/route-orchestration.js +3 -3
  93. package/dist/run-local.js +13 -10
  94. package/dist/runs.js +8 -6
  95. package/dist/runtime-caps-resolver.d.ts +3 -2
  96. package/dist/runtime-caps-resolver.js +4 -4
  97. package/dist/runtime-governance.d.ts +4 -7
  98. package/dist/runtime-governance.js +12 -17
  99. package/dist/sealed-key.d.ts +1 -1
  100. package/dist/sealed-key.js +1 -1
  101. package/dist/security.d.ts +0 -5
  102. package/dist/security.js +8 -12
  103. package/dist/session-sync.d.ts +0 -2
  104. package/dist/session-sync.js +10 -17
  105. package/dist/spec-fields.js +6 -6
  106. package/dist/task-settings.d.ts +0 -27
  107. package/dist/task-settings.js +42 -11
  108. package/dist/tool-approval.d.ts +3 -3
  109. package/dist/tool-approval.js +10 -10
  110. package/dist/trace/artifacts.d.ts +11 -0
  111. package/dist/trace/artifacts.js +8 -19
  112. package/dist/trace/ledger-sink.js +7 -1
  113. package/dist/trace/project.d.ts +17 -0
  114. package/dist/trace/project.js +58 -5
  115. package/package.json +3 -3
  116. package/dist/capabilities/oa-tools.d.ts +0 -15
  117. package/dist/capabilities/oa-tools.js +0 -54
  118. package/dist/plugins/approval-store-sql.d.ts +0 -116
  119. package/dist/plugins/approval-store-sql.js +0 -150
  120. package/dist/plugins/file-workflow-journal-store.d.ts +0 -12
  121. package/dist/plugins/file-workflow-journal-store.js +0 -12
  122. package/dist/plugins/pg-approval-store.d.ts +0 -9
  123. package/dist/plugins/pg-approval-store.js +0 -9
  124. package/dist/plugins/pg-breaker-state.d.ts +0 -8
  125. package/dist/plugins/pg-breaker-state.js +0 -8
  126. package/dist/plugins/pg-checkpoint-store.d.ts +0 -10
  127. package/dist/plugins/pg-checkpoint-store.js +0 -10
  128. package/dist/plugins/pg-file-snapshot-store.d.ts +0 -8
  129. package/dist/plugins/pg-file-snapshot-store.js +0 -8
  130. package/dist/plugins/pg-image-bake.d.ts +0 -12
  131. package/dist/plugins/pg-image-bake.js +0 -11
  132. package/dist/plugins/pg-image-index.d.ts +0 -12
  133. package/dist/plugins/pg-image-index.js +0 -11
  134. package/dist/plugins/pg-outcome-ledger.d.ts +0 -12
  135. package/dist/plugins/pg-outcome-ledger.js +0 -11
  136. package/dist/plugins/pg-resume-anchor-store.d.ts +0 -7
  137. package/dist/plugins/pg-resume-anchor-store.js +0 -7
  138. package/dist/plugins/pg-run-store.d.ts +0 -9
  139. package/dist/plugins/pg-run-store.js +0 -9
  140. package/dist/plugins/pg-session-policy-store.d.ts +0 -7
  141. package/dist/plugins/pg-session-policy-store.js +0 -7
  142. package/dist/plugins/pg-session-store.d.ts +0 -12
  143. package/dist/plugins/pg-session-store.js +0 -12
  144. package/dist/plugins/pg-tool-result-store.d.ts +0 -9
  145. package/dist/plugins/pg-tool-result-store.js +0 -9
  146. package/dist/plugins/pg-workflow-journal-store.d.ts +0 -9
  147. package/dist/plugins/pg-workflow-journal-store.js +0 -9
  148. package/dist/plugins/pg-workflow-run-store.d.ts +0 -9
  149. package/dist/plugins/pg-workflow-run-store.js +0 -9
  150. package/dist/plugins/tidb-approval-store.d.ts +0 -8
  151. package/dist/plugins/tidb-approval-store.js +0 -8
  152. package/dist/plugins/tidb-breaker-state.d.ts +0 -7
  153. package/dist/plugins/tidb-breaker-state.js +0 -7
  154. package/dist/plugins/tidb-checkpoint-store.d.ts +0 -9
  155. package/dist/plugins/tidb-checkpoint-store.js +0 -9
  156. package/dist/plugins/tidb-file-snapshot-store.d.ts +0 -8
  157. package/dist/plugins/tidb-file-snapshot-store.js +0 -8
  158. package/dist/plugins/tidb-image-bake.d.ts +0 -12
  159. package/dist/plugins/tidb-image-bake.js +0 -11
  160. package/dist/plugins/tidb-image-index.d.ts +0 -12
  161. package/dist/plugins/tidb-image-index.js +0 -11
  162. package/dist/plugins/tidb-outcome-ledger.d.ts +0 -12
  163. package/dist/plugins/tidb-outcome-ledger.js +0 -12
  164. package/dist/plugins/tidb-resume-anchor-store.d.ts +0 -7
  165. package/dist/plugins/tidb-resume-anchor-store.js +0 -7
  166. package/dist/plugins/tidb-run-store.d.ts +0 -10
  167. package/dist/plugins/tidb-run-store.js +0 -9
  168. package/dist/plugins/tidb-session-policy-store.d.ts +0 -7
  169. package/dist/plugins/tidb-session-policy-store.js +0 -7
  170. package/dist/plugins/tidb-tool-result-store.d.ts +0 -8
  171. package/dist/plugins/tidb-tool-result-store.js +0 -10
  172. package/dist/plugins/tidb-workflow-journal-store.d.ts +0 -9
  173. package/dist/plugins/tidb-workflow-journal-store.js +0 -9
  174. package/dist/plugins/tidb-workflow-run-store.d.ts +0 -10
  175. package/dist/plugins/tidb-workflow-run-store.js +0 -10
  176. package/dist/plugins/workflow-journal-limits.d.ts +0 -12
  177. package/dist/plugins/workflow-journal-limits.js +0 -12
package/dist/config.d.ts CHANGED
@@ -116,6 +116,34 @@ export declare const hostExecSpoolEnabled: () => boolean;
116
116
  * is REMOVED (a spread-inherited value from a bigger-window base model would mis-trigger a small window).
117
117
  * Shared by the env lane (main + cheap models) and the center lane (sema-registry toModel). */
118
118
  export declare function applyAutoCompactWindow(m: Model, explicit?: number): void;
119
+ /**
120
+ * 一条工具名**永不匹配任何现役工具**的三种拼法,及其修正指引。审批/权限名单是 server 侧 ToolPolicy 的
121
+ * RAW 逐字比对(canonicalToolName 折叠面已随 core 5.0.0 RB-476 退役),所以写错的名字不会报错、只会静默
122
+ * 不生效——对 deny/ask 名单而言这是**失守方向**(运维/客户端以为加了门,实际什么都没加)。
123
+ *
124
+ * 判据在此**单点成文**,三条腿共用(它们此前各自为政,拼法覆盖面不一致才是真缺口):
125
+ * ① env 腿(`APPROVAL_REQUIRE`/`DENY`/`NEVER_AUTO`,下方)—— boot 拒启;
126
+ * ② 中心下发腿(`runtime.approvalRequire`,config-center/apply-effective.ts)—— 域级拒绝+保前值+warn
127
+ * (常驻进程不 crash);
128
+ * ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts)—— 该请求 422。
129
+ *
130
+ * 三种拼法:
131
+ * - 退役名(`bash`/`Task`/`KillShell`…):core `RETIRED_TOOL_NAMES` 静态表,指引=现役名。
132
+ * - pre-prefix 短名(`figma__x`):v4 自动加前缀的折叠面已删,实挂名恒带 `mcp__`,指引=补全前缀。
133
+ * - 不完整 MCP 名(`mcp__`、`mcp__figma`):前缀对但缺段——MCP 实挂名恒是 `mcp__<server>__<tool>` 三段形,
134
+ * 少一段同样永不匹配。旧判据「含 `__` 且 protocolOf 认不出」放过了这一族(前缀恰好认得出),从另一侧
135
+ * 漏出去,失守方向与 `figma__x` 完全相同。
136
+ *
137
+ * 已接受的权衡(与退役表墓碑同一条):部署自有工具真叫 `figma__x` 的极端形会被误拒——boot 期无 roster 可
138
+ * 豁免,且失败响亮、指引明确,好过静默失守。
139
+ */
140
+ export interface UnmatchableToolName {
141
+ name: string;
142
+ guidance: string;
143
+ }
144
+ export declare function findUnmatchableToolNames(names: readonly string[]): UnmatchableToolName[];
145
+ /** {@link findUnmatchableToolNames} 的成句形——三条腿的文案同源(只有前缀/出口不同)。 */
146
+ export declare function formatUnmatchableToolNames(source: string, bad: readonly UnmatchableToolName[]): string;
119
147
  /** 组名 → 该组取景的平铺键(introspection 面:测试用它钉「每个平铺键恰好被一组取景」)。 */
120
148
  export declare const CONFIG_GROUP_KEYS: {
121
149
  readonly [G in keyof ServiceConfigGroups]: readonly (keyof ServiceConfigFlat)[];
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, RECOMMENDED_SENSITIVE_PATTERNS } from "@sema-agent/core";
4
+ import { CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES } from "@sema-agent/core";
5
5
  import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/registry-core";
6
6
  import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js"; // design/158 A4: the parser leaf — NOT security.js (base config layer must not value-import the 55KiB auth module)
7
7
  import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
@@ -60,16 +60,38 @@ function defaultSkillsDir() {
60
60
  return "skills"; // import.meta 不可用的兜底(不应发生在 ESM 构建里)
61
61
  }
62
62
  }
63
- /** Two-name env with the first set winning (MYSQL_* preferred over the TIDB_* back-compat alias); throws
64
- * when neither is set and no fallback is given — same fail-loud contract as `env`. */
65
- function env2(primary, alias, fallback) {
66
- const v = process.env[primary] || process.env[alias];
67
- if (v === undefined || v === "") {
68
- if (fallback !== undefined)
69
- return fallback;
70
- throw new Error(`missing required env: ${primary} (or ${alias})`);
63
+ const RETIRED_ENVS = [
64
+ ["TIDB_HOST", "MYSQL_HOST", "renamed"], ["TIDB_PORT", "MYSQL_PORT", "renamed"], ["TIDB_USER", "MYSQL_USER", "renamed"],
65
+ ["TIDB_PASSWORD", "MYSQL_PASSWORD", "renamed"], ["TIDB_DATABASE", "MYSQL_DATABASE", "renamed"], ["TIDB_POOL_SIZE", "MYSQL_POOL_SIZE", "renamed"],
66
+ ["CONFIG_CENTER_URL", "SEMA_REGISTRY_URL", "renamed"], ["CONFIG_CENTER_TOKEN", "SEMA_REGISTRY_TOKEN", "renamed"],
67
+ ["CONFIG_CENTER_DRY_RUN", "SEMA_REGISTRY_DRY_RUN", "renamed"], ["CONFIG_CENTER_WORKER", "SEMA_REGISTRY_WORKER", "renamed"],
68
+ ["ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL", "renamed"],
69
+ ["REGISTRY_ADMIN_TOKEN", "SEMA_REGISTRY_ADMIN_TOKEN", "renamed"], // [2373]B-1:墓碑族唯一漏网的双读(config-center.ts 发射点)
70
+ // [2354] OA 场景整体退役(clay 2026-08-01):配置本身作废,无替代名 removed 族。
71
+ ["OA_API_BASEURL", "the OA scenario was removed in 5.0.0", "removed"],
72
+ ["OA_SERVICE_TOKEN", "the OA scenario was removed in 5.0.0", "removed"],
73
+ ["OA_ISSUE_TOKEN", "the OA scenario was removed in 5.0.0", "removed"],
74
+ ["OA_ISSUE_BASEURL", "the OA scenario was removed in 5.0.0", "removed"],
75
+ ["OA_ISSUE_OWNER", "the OA scenario was removed in 5.0.0", "removed"],
76
+ ["OA_ISSUE_REPO", "the OA scenario was removed in 5.0.0", "removed"],
77
+ ["OA_ISSUE_LABELS", "the OA scenario was removed in 5.0.0", "removed"],
78
+ ["APPROVAL_POLL_MS", "the OA poll-approval lane was removed in 5.0.0; the durable checkpoint lane is the only gate", "removed"],
79
+ ];
80
+ function rejectRetiredEnvs() {
81
+ const hit = RETIRED_ENVS.filter(([o]) => process.env[o] !== undefined && process.env[o] !== "");
82
+ if (hit.length === 0)
83
+ return;
84
+ const renamed = hit.filter(([, , k]) => k === "renamed");
85
+ const removed = hit.filter(([, , k]) => k === "removed");
86
+ const parts = [];
87
+ if (renamed.length > 0) {
88
+ parts.push(`${renamed.map(([o, n]) => `${o} → use ${n}`).join("; ")} — rename the env key(s); the value semantics are unchanged`);
71
89
  }
72
- return v;
90
+ if (removed.length > 0) {
91
+ parts.push(`${removed.map(([o, n]) => `${o} (${n})`).join("; ")} — remove the env key(s): the capability itself was retired, ` +
92
+ `there is no replacement name and no rename that restores it`);
93
+ }
94
+ throw new Error(`retired env name(s) set ([2354] 兼容面全清,5.0.0 起拒启): ${parts.join(" | ")}.`);
73
95
  }
74
96
  /** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN`
75
97
  * (which e.g. slips past the `runStaleSec` liveness guard and silently disables the reaper). */
@@ -387,11 +409,11 @@ function parseStoreDomain(ctx) {
387
409
  // 在场时,dbBackend 的旧默认 "mysql" 是**引擎选择器**(auto 探测/coords 构建都 key 它),默认切
388
410
  // local 会把这些既有形错切到文件店(回归钉在 config-coverage)。
389
411
  const sqlSignal = (!!process.env.SESSION_BACKEND && process.env.SESSION_BACKEND !== "memory") ||
390
- !!(process.env.TIDB_HOST || process.env.MYSQL_HOST || process.env.PG_HOST);
412
+ !!(process.env.MYSQL_HOST || process.env.PG_HOST);
391
413
  const bareBoot = !dbBackendSet && !sqlSignal;
392
414
  const multiTenantBareBoot = bareBoot && requirePrincipal;
393
- const dbBackendRaw = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", ["mysql", "tidb", "pg", "local", "memory"]);
394
- const dbBackend = dbBackendRaw === "tidb" ? "mysql" : dbBackendRaw;
415
+ // [2354]:公名枚举不再收 "tidb"(enumEnv 对越集值自带拒启);内部逻辑不变。
416
+ const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", ["mysql", "pg", "local", "memory"]);
395
417
  if (multiTenantBareBoot) {
396
418
  CONFIG_NOTICES.push({
397
419
  event: "db_backend_default_memory_multi_tenant",
@@ -407,7 +429,7 @@ function parseStoreDomain(ctx) {
407
429
  // backend); an UNSET DB_BACKEND keeps the memory default (no surprise DB requirement on a bare boot).
408
430
  // `mysql` is accepted as an alias of the internal durable label `tidb` (see ServiceConfig.sessionBackend).
409
431
  const sqlEngineExplicit = !!process.env.DB_BACKEND && dbBackend !== "local" && dbBackend !== "memory";
410
- const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", ["memory", "mysql", "tidb", "auto"]);
432
+ const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", ["memory", "mysql", "auto"]); // [2354]:公名 "tidb" 退役
411
433
  const sessionBackend = sessionBackendRaw === "mysql" ? "tidb" : sessionBackendRaw;
412
434
  // P0.5 variant-2: the file-backed `local` backend's data root. Same tail as main.ts's `localRoot` and
413
435
  // run-local.ts's `root` (CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent), so the HTTP service + a run-local
@@ -421,7 +443,7 @@ function parseStoreDomain(ctx) {
421
443
  // `auto` builds the DB coords only when a host is actually configured — otherwise it stays memory with no
422
444
  // probe. (Explicit mysql always builds them so a missing host fails loudly.) `needsDb` is engine-agnostic;
423
445
  // the connection coords are built for whichever engine `dbBackend` selects (MYSQL_* preferred, TIDB_* alias).
424
- const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!(process.env.MYSQL_HOST || process.env.TIDB_HOST);
446
+ const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
425
447
  const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
426
448
  const needsDb = needsTidb;
427
449
  return {
@@ -433,14 +455,13 @@ function parseStoreDomain(ctx) {
433
455
  localDataRoot,
434
456
  tidb: needsDb && dbBackend === "mysql"
435
457
  ? {
436
- host: env2("MYSQL_HOST", "TIDB_HOST"),
437
- // default port follows the env family in use: MYSQL_HOST → 3306 (stock MySQL), TIDB_HOST → 4000 (TiDB)
438
- port: Number(env2("MYSQL_PORT", "TIDB_PORT", process.env.MYSQL_HOST ? "3306" : "4000")),
439
- user: env2("MYSQL_USER", "TIDB_USER"),
440
- password: env2("MYSQL_PASSWORD", "TIDB_PASSWORD", ""),
441
- database: env2("MYSQL_DATABASE", "TIDB_DATABASE"),
442
- connectionLimit: process.env.MYSQL_POOL_SIZE || process.env.TIDB_POOL_SIZE
443
- ? Number(process.env.MYSQL_POOL_SIZE || process.env.TIDB_POOL_SIZE)
458
+ host: env("MYSQL_HOST"),
459
+ port: Number(env("MYSQL_PORT", "3306")),
460
+ user: env("MYSQL_USER"),
461
+ password: env("MYSQL_PASSWORD", ""),
462
+ database: env("MYSQL_DATABASE"),
463
+ connectionLimit: process.env.MYSQL_POOL_SIZE
464
+ ? Number(process.env.MYSQL_POOL_SIZE)
444
465
  : undefined,
445
466
  }
446
467
  : undefined,
@@ -528,7 +549,7 @@ function parseModelDomain() {
528
549
  // credential alone does NOT infer (a bare ANTHROPIC_API_KEY historically only assembles the cloud route for
529
550
  // registry-declared anthropic models); neither present ⇒ "gateway", byte-identical to before.
530
551
  const explicitProvider = process.env.MODEL_PROVIDER; // env() semantics: "" = unset
531
- const inferredAnthropicBaseUrl = firstSetEnv("ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL");
552
+ const inferredAnthropicBaseUrl = firstSetEnv("ANTHROPIC_BASE_URL"); // [2354]:无下划线非标名退役(墓碑)
532
553
  const inferredAnthropicCred = firstSetEnv("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN");
533
554
  const provider = (() => {
534
555
  if (explicitProvider !== undefined && explicitProvider !== "")
@@ -702,6 +723,11 @@ function parseModelDomain() {
702
723
  ? { model: cur, systemPrompt: CODE_AGENT_PROMPT }
703
724
  : { ...cur, systemPrompt: CODE_AGENT_PROMPT };
704
725
  }
726
+ // A2:词表验证**无条件**执行(此前它挂在下面 `MODEL_DEGRADE_TO ? {...}` 三元的真臂里,TO 缺席时
727
+ // parseDegradeOn 根本不可达 ⇒ 拼错词静默无效),而 USAGE.md 的「拼错词 boot 拒启,不静默忽略」是对
728
+ // MODEL_DEGRADE_ON 本身的无条件承诺:设了就验。TO 缺席 + ON 合法 ⇒ 照旧不装配 degrade(行为不变),
729
+ // 也不报错——ON 只是把那份已在的意图写下来,不是把 degrade 打开。
730
+ const degradeOn = parseDegradeOn(csv("MODEL_DEGRADE_ON"));
705
731
  return {
706
732
  gatewayBaseUrl,
707
733
  gatewayApiKey: process.env.MODEL_API_KEY,
@@ -719,7 +745,7 @@ function parseModelDomain() {
719
745
  ? {
720
746
  apiKey: firstSetEnv("ANTHROPIC_API_KEY"),
721
747
  authToken: firstSetEnv("ANTHROPIC_AUTH_TOKEN"),
722
- baseUrl: firstSetEnv("ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL"),
748
+ baseUrl: firstSetEnv("ANTHROPIC_BASE_URL"),
723
749
  version: process.env.ANTHROPIC_VERSION,
724
750
  cacheBreakpoints: (process.env.ANTHROPIC_CACHE_BREAKPOINTS ?? "true") === "true",
725
751
  ...(process.env.ANTHROPIC_MAX_RETRIES ? { maxRetries: Number(env("ANTHROPIC_MAX_RETRIES", "2")) } : {}),
@@ -755,9 +781,10 @@ function parseModelDomain() {
755
781
  to: env("MODEL_DEGRADE_TO"),
756
782
  atCostFraction: Number(env("MODEL_DEGRADE_AT_COST_FRACTION", "0.7")),
757
783
  reactive: boolEnv("MODEL_DEGRADE_REACTIVE", false),
758
- downgradeOn: csv("MODEL_DEGRADE_ON").length
759
- ? csv("MODEL_DEGRADE_ON")
760
- : undefined,
784
+ // core 5.1.0([2390]②):词表=DegradeReason 全集;缺席时 core 默认含 server_error(默认门变宽),
785
+ // 本 env 是运维收窄/放宽的唯一旋钮。拼错词 boot 响亮拒——盲铸静默折无效成员是 TASK_TIMEOUT_SEC 同族病。
786
+ // (验词在上方无条件做掉了,见 A2 注;这里只是取用已验过的结果。)
787
+ downgradeOn: degradeOn,
761
788
  // vision precheck (adversarial-review finding): the degrade TARGET is an external gateway
762
789
  // model NOT in config.models, so resolveSpec's precheck (which only sees the picked model) can't see its
763
790
  // vision capability — a vision-capable main + text-only degrade target would let images reach the text-only
@@ -771,6 +798,43 @@ function parseModelDomain() {
771
798
  }
772
799
  /** 域:approval(审批/HITL 门)—— 审批名单与预算、durable 面、直连门开关与钥、敏感写门、elicitation。
773
800
  * 注:直连门的**激活**(directDoorActive)与 D-G §3 半开门不变量是跨域合取,留在装配层。 */
801
+ /** MODEL_DEGRADE_ON 词表=core `DegradeReason` 全集(集合用该类型铸=锁步:core 删词此处 tsc 红,加词这里
802
+ * 补一个词即可)。非法词 boot 响亮拒——盲铸静默折无效成员与 TASK_TIMEOUT_SEC 非法值折「无超时」同族。 */
803
+ const DEGRADE_ON_WORDS = new Set(["breaker_open", "rate_limit", "budget", "server_error", "last_resort"]);
804
+ function parseDegradeOn(words) {
805
+ if (words.length === 0)
806
+ return undefined;
807
+ const bad = words.filter((w) => !DEGRADE_ON_WORDS.has(w));
808
+ if (bad.length > 0) {
809
+ throw new Error(`MODEL_DEGRADE_ON has unknown trigger word(s): ${bad.join(", ")} (valid: ${[...DEGRADE_ON_WORDS].join(", ")})`);
810
+ }
811
+ return words;
812
+ }
813
+ export function findUnmatchableToolNames(names) {
814
+ const out = [];
815
+ for (const name of names) {
816
+ const retired = RETIRED_TOOL_NAMES.get(name);
817
+ if (retired !== undefined) {
818
+ out.push({ name, guidance: retired });
819
+ continue;
820
+ }
821
+ const ns = protocolOf(name);
822
+ if (ns === undefined) {
823
+ if (name.includes("__"))
824
+ out.push({ name, guidance: `pre-prefix MCP short form — the live name is mcp__${name}` });
825
+ continue;
826
+ }
827
+ if (ns.parse(name) === undefined) {
828
+ out.push({ name, guidance: `incomplete ${ns.id} name — the live form is ${ns.prefix}<server>__<tool>` });
829
+ }
830
+ }
831
+ return out;
832
+ }
833
+ /** {@link findUnmatchableToolNames} 的成句形——三条腿的文案同源(只有前缀/出口不同)。 */
834
+ export function formatUnmatchableToolNames(source, bad) {
835
+ return (`${source} names tool(s) that can never match a live tool (core 5.0.0 RB-476 — the alias/auto-prefix folds are gone): ` +
836
+ bad.map((b) => `${b.name} → ${b.guidance}`).join("; "));
837
+ }
774
838
  function parseApprovalDomain(ctx) {
775
839
  const { postureOn } = ctx; // 跨域入参②:posture 三态(single-user turnkey ⇒ HITL 面默认 ON)
776
840
  // design/80 D-G: the direct-connect approval door anchors (all must be present to ACTIVATE; fail-closed).
@@ -778,15 +842,24 @@ function parseApprovalDomain(ctx) {
778
842
  const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS);
779
843
  // DURABLE_APPROVAL 既是本域字段,也是直连门的硬前置 —— 那条前置的理由与断言在装配层的跨域不变量段。
780
844
  const durableApproval = boolEnv("DURABLE_APPROVAL", false);
845
+ // [2380] core 5.0.0 RB-476:approval 名单里「永不匹配」的名字**装配期拒启**(带修正指引)。core 自己的
846
+ // prepare 门(config.legacy_tool_name)在提交期才响——boot 期先拒,部署带失效名单不启动而不是首任务炸。
847
+ // 名单 RAW 直传(canonicalToolName 折叠面已随 core 退役,见 approval.ts);判据单点见
848
+ // {@link findUnmatchableToolNames}(env / 中心下发 / per-request 三腿共用同一条)。
849
+ const rejectUnmatchableToolNames = (envName, names) => {
850
+ const bad = findUnmatchableToolNames(names);
851
+ if (bad.length > 0)
852
+ throw new Error(formatUnmatchableToolNames(envName, bad));
853
+ return names;
854
+ };
781
855
  return {
782
- approvalRequire: csv("APPROVAL_REQUIRE"),
783
- approvalDeny: csv("APPROVAL_DENY"),
784
- approvalPollMs: Number(env("APPROVAL_POLL_MS", "1500")),
856
+ approvalRequire: rejectUnmatchableToolNames("APPROVAL_REQUIRE", csv("APPROVAL_REQUIRE")),
857
+ approvalDeny: rejectUnmatchableToolNames("APPROVAL_DENY", csv("APPROVAL_DENY")),
785
858
  durableApproval,
786
859
  resourceSuspend: boolEnv("RESOURCE_SUSPEND", false),
787
860
  resourceSuspendTtlSec: Math.max(0, numEnv("RESOURCE_SUSPEND_TTL_SEC", "0")), // 0 ⇒ core default (30d)
788
861
  approvalAutoBudget: Math.min(10000, Math.max(0, numEnv("APPROVAL_AUTO_BUDGET", "0"))), // clamp [0,10000]; 0 = off
789
- approvalNeverAuto: csv("APPROVAL_NEVER_AUTO"),
862
+ approvalNeverAuto: rejectUnmatchableToolNames("APPROVAL_NEVER_AUTO", csv("APPROVAL_NEVER_AUTO")),
790
863
  approvalHmacKeys: dgHmacKeys, // D-G: WIRED (see the field's JSDoc); empty ⇒ inactive, not unimplemented
791
864
  directApprovalDoor,
792
865
  // ③ sensitive-path write deny set: unset = core's recommended set; explicit value = full replacement
@@ -910,11 +983,10 @@ function parseAuthDomain(ctx) {
910
983
  .map((s) => [s.slice(0, s.indexOf("=")).trim(), s.slice(s.indexOf("=") + 1).trim()])
911
984
  .filter(([tok, sys]) => tok.length > 0 && sys.length > 0));
912
985
  return {
913
- // 绑址([1934]):BIND_HOST 优先,HOST 兼容(cli/桌面传的就是它);空串=未设。
914
- bindHost: process.env.BIND_HOST || process.env.HOST || undefined,
915
- // [2062]③ 来源记账:HOST 是 zsh 会替用户设的名字(HOST=机器名),{...process.env} 起底的壳会把它
916
- // 静默继承给引擎——bind 面被 shell 环境暗配而部署者不知情。main.ts "HOST" 来源打一行启动告警。
917
- bindHostSource: process.env.BIND_HOST ? "BIND_HOST" : process.env.HOST ? "HOST" : undefined,
986
+ // [2354]:HOST 读取臂已删(zsh 恒设 HOST=机器名,不可墓碑——静默不读;cli/桌面 [2053] 起已传
987
+ // BIND_HOST)。绑址唯一口=BIND_HOST;空串=未设。
988
+ bindHost: process.env.BIND_HOST || undefined,
989
+ bindHostSource: process.env.BIND_HOST ? "BIND_HOST" : undefined,
918
990
  authToken: process.env.SERVICE_AUTH_TOKEN,
919
991
  authTokens,
920
992
  allowUnauthedWrites: boolEnv("ALLOW_UNAUTHED_WRITES", false),
@@ -1210,35 +1282,16 @@ function parseIntegrationsDomain() {
1210
1282
  // 编码产品出厂态对标 CC 出厂态,通用域中性 persona 用 DEFAULT_SCENARIO=default 显式选回。
1211
1283
  defaultScenario: env("DEFAULT_SCENARIO", "code"),
1212
1284
  skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
1213
- oaApiBaseUrl: process.env.OA_API_BASEURL,
1214
- oaServiceToken: process.env.OA_SERVICE_TOKEN,
1215
- // 开 issue 工具:默认开到 OA 系统仓(AI-Only/oa-asset-system),写权限 token 走独立 env。
1216
- oaIssue: process.env.OA_ISSUE_TOKEN
1217
- ? {
1218
- // npm 卫生纪律:不再烤内网 git 主机缺省 — 开了 OA_ISSUE_TOKEN 就必须给 host(fail-loud)。
1219
- baseUrl: (() => {
1220
- const u = process.env.OA_ISSUE_BASEURL ?? process.env.GIT_API_BASEURL;
1221
- if (!u)
1222
- throw new Error("OA_ISSUE_TOKEN is set but no OA_ISSUE_BASEURL / GIT_API_BASEURL — the issue tool needs a git host");
1223
- return u;
1224
- })(),
1225
- owner: env("OA_ISSUE_OWNER", "AI-Only"),
1226
- repo: env("OA_ISSUE_REPO", "oa-asset-system"),
1227
- token: env("OA_ISSUE_TOKEN"),
1228
- defaultLabels: csv("OA_ISSUE_LABELS").map(Number).filter((n) => Number.isInteger(n)),
1229
- }
1230
- : undefined,
1285
+ // [2354] clay 令:OA 场景整体退役(oa-tools/oa 场景/OA 轮询审批车道随 5.0.0 全清)——
1286
+ // OA_* env 全族进 RETIRED_ENVS 墓碑(在场即拒启),此处零读取。
1231
1287
  // sema-registry (control plane) URL/token/worker. The orchestrator renamed the worker-injected env from
1232
- // CONFIG_CENTER_* → SEMA_REGISTRY_* (the control-plane host was renamed to match); it dual-injects
1233
- // both names at the SAME value during the transition, so we DUAL-READ here — new name preferred, old name
1234
- // fallback — and keep working after the orchestrator drops the legacy CONFIG_CENTER_* names. Pure config
1235
- // plumbing: no API/auth/task-logic change.
1236
- configCenter: (process.env.SEMA_REGISTRY_URL || process.env.CONFIG_CENTER_URL)
1288
+ // [2354]:CONFIG_CENTER_* 双读已删(墓碑在 rejectRetiredEnvs);SEMA_REGISTRY_* 唯一。
1289
+ configCenter: process.env.SEMA_REGISTRY_URL
1237
1290
  ? {
1238
- baseUrl: process.env.SEMA_REGISTRY_URL || env("CONFIG_CENTER_URL"),
1239
- token: process.env.SEMA_REGISTRY_TOKEN || process.env.CONFIG_CENTER_TOKEN || "",
1240
- dryRun: (process.env.SEMA_REGISTRY_DRY_RUN ?? process.env.CONFIG_CENTER_DRY_RUN) === "true",
1241
- worker: process.env.SEMA_REGISTRY_WORKER || process.env.CONFIG_CENTER_WORKER || undefined,
1291
+ baseUrl: env("SEMA_REGISTRY_URL"),
1292
+ token: process.env.SEMA_REGISTRY_TOKEN || "",
1293
+ dryRun: process.env.SEMA_REGISTRY_DRY_RUN === "true",
1294
+ worker: process.env.SEMA_REGISTRY_WORKER || undefined,
1242
1295
  }
1243
1296
  : undefined,
1244
1297
  configProvider: process.env.CONFIG_PROVIDER || undefined,
@@ -1254,7 +1307,7 @@ const MODEL_PLANE_GROUP_KEYS = [
1254
1307
  "modelApiKeyEnv", "modelApiKeys", "modelQuotaWeights", "tiers", "projects", "roles", "cascadeLadder", "degrade",
1255
1308
  ];
1256
1309
  const APPROVAL_GROUP_KEYS = [
1257
- "approvalRequire", "approvalDeny", "approvalPollMs", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
1310
+ "approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
1258
1311
  "approvalHmacKeys", "durableApproval", "directApprovalDoor", "directDoorActive", "resourceSuspend",
1259
1312
  "resourceSuspendTtlSec", "askQuestionEnabled", "toolApprovalEnabled", "mcpElicitation", "sensitiveWritePatterns",
1260
1313
  "manualModeShellGate",
@@ -1286,7 +1339,7 @@ const LIMITS_HTTP_GROUP_KEYS = [
1286
1339
  const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
1287
1340
  const INTEGRATIONS_GROUP_KEYS = [
1288
1341
  "pluginsAllowHosts", "mcpServers", "configBootFetchBudgetMs", "gitApiBaseUrl", "gitApiToken", "defaultScenario",
1289
- "skillsDir", "oaApiBaseUrl", "oaServiceToken", "oaIssue", "configCenter", "configProvider", "configLocalDir",
1342
+ "skillsDir", "configCenter", "configProvider", "configLocalDir",
1290
1343
  ];
1291
1344
  /** 组名 → 该组取景的平铺键(introspection 面:测试用它钉「每个平铺键恰好被一组取景」)。 */
1292
1345
  export const CONFIG_GROUP_KEYS = {
@@ -1339,6 +1392,7 @@ export function attachConfigGroups(flat) {
1339
1392
  return flat;
1340
1393
  }
1341
1394
  export function loadConfig() {
1395
+ rejectRetiredEnvs(); // [2354] 退役 env 墓碑:在场即拒启带新名指引
1342
1396
  CONFIG_WARNINGS.length = 0; // repeated loadConfig() calls (test setup) must not accumulate stale warnings
1343
1397
  CONFIG_NOTICES.length = 0;
1344
1398
  CONFIG_KNOBS.clear(); // the polarity table describes THIS load, never a previous one's env
@@ -1379,8 +1433,8 @@ export function loadConfig() {
1379
1433
  const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, authTokens } = auth;
1380
1434
  const { enabled: imageBakesEnabled, runnerPrincipal: bakeRunnerPrincipal } = orchestration.imageBakes;
1381
1435
  // 🔴 the direct-door crypto gate lives ONLY in the durable (checkpointStore) /decide branch — so DURABLE_APPROVAL
1382
- // is a HARD precondition. Without it, checkpointStore is undefined, that branch is skipped, and the LEGACY
1383
- // approvalStore /decide (trusted-header, NO crypto) becomes the live path = a full bypass (adversarial CRITICAL).
1436
+ // is a HARD precondition. Without it, checkpointStore is undefined and there is NO decide path at all
1437
+ // ([2354] A-5:legacy trusted-header /decide 车道已在 5.0.0 退役——本不变量从「防旁路」变成「防死门」,保留)
1384
1438
  const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
1385
1439
  // 🔴 boot invariant (D-G §3): a half-configured direct door is the WORST state (looks on, verifies nothing) — and
1386
1440
  // an empty OPERATOR_PRINCIPALS makes isOperator() true-for-all (= cross-tenant escalation). Refuse to start.
@@ -54,7 +54,7 @@ export interface FleetTaskRow {
54
54
  status: FleetTaskStatus;
55
55
  /** core 1.367 δ([1514]/[1515]④):launch 时刻 epoch ms(revive 行=本 revive 周期 wake 时刻)——
56
56
  * cli 的 elapsed 锚直出(比 server 端 elapsedMs 快照更稳,消费方自算不漂)。ADDITIVE / tolerate-absent
57
- * (a* 车道 spawn 即带;旧 core 帧无 startedAt 时回落 server 收帧时刻)。 */
57
+ * (a* 车道 spawn 即带;诚实缺席——回落 server 收帧时刻的臂已随 [2354] 删,floor ^4.0.0 下 startedAt 恒在)。 */
58
58
  startedAt?: number;
59
59
  elapsedMs?: number;
60
60
  tokens?: number;
@@ -21,7 +21,6 @@
21
21
  * replica-local). A cross-replica fleet roll-up is a fleet-token-gated trace-API concern (separate), not this.
22
22
  */
23
23
  import { EventEmitter } from "node:events";
24
- import { canonicalToolName } from "@sema-agent/core";
25
24
  import { redactSecrets } from "../trace/redact.js";
26
25
  /**
27
26
  * The process-local fleet aggregation bus. Holds the current active set + fans out deltas to SSE subscribers.
@@ -231,7 +230,9 @@ export function fleetRunPublisher(bus, run) {
231
230
  // `totalInputTokens`,旧核帧(尚未升级到 3.0.0、该键缺席)回落 `inputTokens`(防御性:本引擎恒 ≥3.0.0
232
231
  // 时两键并存;异常帧宁少算不 NaN)。Display-only(leader fleet 行的 token 数);billing(costMicroUsd)+
233
232
  // context/compaction 走 totalTokens,不受影响;child 行走 task_progress.usage.totalTokens(上面的早退臂)。
234
- return (u.totalInputTokens ?? u.inputTokens ?? 0) + (u.outputTokens ?? 0);
233
+ // [2354] 兼容面全清:旧核帧回落臂(?? u.inputTokens)已删——floor ^4.0.0 totalInputTokens 恒在;
234
+ // 键缺席=越契约帧,按 0 计(宁少算不借异语义键充数)。
235
+ return (u.totalInputTokens ?? 0) + (u.outputTokens ?? 0);
235
236
  };
236
237
  return {
237
238
  onStart() {
@@ -283,7 +284,7 @@ export function fleetRunPublisher(bus, run) {
283
284
  // PascalCase double-belt (core 1.176): canonicalize so a legacy snake CONTROL verb (e.g. a
284
285
  // pre-1.176 emit / a stray report_blocked) renders as its CC name (ReportBlocked) and the shell's PascalCase
285
286
  // suppression Set still hides it — never re-leaking a control verb as fleet-row activity.
286
- bus.publishTask({ id: run.runId, description: canonicalToolName(ev.toolName) });
287
+ bus.publishTask({ id: run.runId, description: ev.toolName }); // RAW(5.0.0):live 名恒 canonical
287
288
  }
288
289
  },
289
290
  onForwardEvent(ev) {
@@ -595,9 +596,10 @@ export function fleetBackgroundChildPublisher(bus, log) {
595
596
  ...(inheritParent && tomb?.resolvedParentId !== undefined ? { resolvedParentId: tomb.resolvedParentId } : {}),
596
597
  // session-scoped: registry owner IS the host sessionId (design/129 owner key); fork spawns also carry
597
598
  // the child's uuid sessionId — that is the a*↔uuid cross-system anchor, NOT the host session, so it is not used here.
598
- // [1522] LOW4:session-scoped 臂同样 root-first(另两臂 1.250 已升)——嵌套 session-scoped
599
- // owner=中间层子会话 uuid,标它=over-filter;显式 root 到货即锚 root(近死码但一处改齐)。
600
- ...(e.sessionScoped && e.owner ? { hostSessionId: e.rootSessionId ?? e.owner } : {}),
599
+ // [1522] LOW4 root-first。[2378]/[2380] RB-478 到货:三条 revive 发射腿全部无条件带
600
+ // `root ?? parent` `?? e.owner` 回落臂成纯死臂,删([2370] 补审时显式排除在全清宣称外
601
+ // 等的就是这一天)。rootSessionId 缺席=不打 host 标(over-filter 方向,durable drain )
602
+ ...(e.sessionScoped && e.owner && e.rootSessionId ? { hostSessionId: e.rootSessionId } : {}),
601
603
  // 🔴 scope DOMAIN mapping: core's registry scope is `ctx.principal ?? deployment scope` —
602
604
  // UNDEFINED on a single-user no-principal deployment — while the service fleet domain keys the
603
605
  // no-principal caller as "default" (`gatedPrincipal ?? "default"`, streamFleet's callerScope). Without
@@ -606,8 +608,9 @@ export function fleetBackgroundChildPublisher(bus, log) {
606
608
  // a principal exists, so the map is exact.
607
609
  tenantScope: e.scope ?? "default",
608
610
  sessionScoped: e.sessionScoped,
609
- // core 1.367 δ:真 launch 锚(revive=wake 时刻)优先;旧 core 帧回落收帧时刻([1338]④ 原形)
610
- spawnedAt: e.startedAt ?? Date.now(),
611
+ // [2354] 兼容面全清:旧 core 帧「回落收帧时刻」臂已删——floor ^4.0.0 startedAt 恒在;
612
+ // 缺席=诚实不发(行键 optional,消费端本就容忍缺席)
613
+ ...(e.startedAt !== undefined ? { spawnedAt: e.startedAt } : {}),
611
614
  ...(e.rootSessionId ? { rootSessionId: e.rootSessionId } : {}),
612
615
  ...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}), // P1-2(core 1.432,12 发射点全带)
613
616
  ...(e.sessionId ? { aliasUuid: e.sessionId } : {}), // codex R4(M-B):uuid↔a* 别名(fork spawn 带 forkedId)
@@ -617,15 +620,9 @@ export function fleetBackgroundChildPublisher(bus, log) {
617
620
  // core 帧的 parentSessionId(发起会话,1.350 声明字段——R2:无 runStore 的瞬态 sync 部署没有宿主
618
621
  // fleet 行,行查 fallback 必空);②宿主行 sessionId 查表(同租户)兜底。
619
622
  ...(e.workflowRunId && !(e.sessionScoped && e.owner)
620
- ? (() => {
621
- // core 1.367 δ:显式 root (spawnAttribution 把 root 传进每个 workflow-agent)——信任源第一
622
- if (e.rootSessionId)
623
- return { hostSessionId: e.rootSessionId };
624
- if (e.parentSessionId)
625
- return { hostSessionId: e.parentSessionId };
626
- const host = e.parentTaskId ? bus.snapshot().tasks.find((r) => r.id === e.parentTaskId && r.scope === (e.scope ?? "default")) : undefined;
627
- return host?.sessionId ? { hostSessionId: host.sessionId } : {};
628
- })()
623
+ // [2378]/[2380] RB-478:wa* 三级回落(root→parentSessionId→行查表)收单级——core 三条发射腿
624
+ // 已无条件带 root 锚,后两级成死臂;缺席=不打标(over-filter,durable ),不再猜。
625
+ ? (e.rootSessionId ? { hostSessionId: e.rootSessionId } : {})
629
626
  : {}),
630
627
  // [1493] cross-session isolation: a NON-session-scoped, non-wa* bg child (owner = the run taskId — the
631
628
  // norm when backgroundScope!="session", i.e. every multi-tenant deployment, and any local path where the
@@ -654,18 +651,11 @@ export function fleetBackgroundChildPublisher(bus, log) {
654
651
  // top-level/nested discriminator) on the spawn event — asked as a core seam follow-up ([1493]).
655
652
  // core 1.367 δ:[1493] fable5-R1 注里请的 root fix **到货**——spawn 帧显式带 rootSessionId
656
653
  // (固定点语义,嵌套孙代恒=root shell 会话),信任源直用;alias 反查臂降为旧 core 帧回落。
657
- ...(!(e.sessionScoped && e.owner) && !e.workflowRunId && (e.rootSessionId ?? e.parentSessionId)
658
- ? (() => {
659
- if (e.rootSessionId)
660
- return { hostSessionId: e.rootSessionId };
661
- const childScope = e.scope ?? "default";
662
- for (const pm of meta.values()) {
663
- if (pm.aliasUuid === e.parentSessionId && (pm.tenantScope ?? "default") === childScope) {
664
- return pm.hostSessionId ? { hostSessionId: pm.hostSessionId } : {}; // nested: inherit root (or stay untagged → Part B)
665
- }
666
- }
667
- return { hostSessionId: e.parentSessionId }; // top-level: parentSessionId IS the shell root session
668
- })()
654
+ // [2354] 兼容面全清:alias 反查臂(旧 core 帧无 rootSessionId 时经 meta 反查 parentSessionId)已删——
655
+ // floor ^4.0.0 下 spawn 帧恒带 rootSessionId(core 1.367 δ 固定点语义);缺席=不打 host 标,
656
+ // 失败方向仍是 over-filtering(durable inbox/stream-open drain 兜),永不 cross-delivery。
657
+ ...(!(e.sessionScoped && e.owner) && !e.workflowRunId && e.rootSessionId
658
+ ? { hostSessionId: e.rootSessionId }
669
659
  : {}),
670
660
  };
671
661
  remember(e.taskId, m);
@@ -719,9 +709,9 @@ export function fleetBackgroundChildPublisher(bus, log) {
719
709
  }
720
710
  const tokens = sumUsage(e.usage);
721
711
  const name = e.name ? redactSecrets(e.name) : undefined;
722
- // [1364]③ K-7 换位:TYPE 列取 tick 帧的行级 `agentType`(core 1.350 定谳:`name` 位=display 标签
723
- // ——description 充是历史语义非 bug,类型名在 agentType 位)。缺省回落 name( core 兼容,原行为)。
724
- const agentType = e.agentType ? redactSecrets(e.agentType) : name;
712
+ // [1364]③ K-7 换位:TYPE 列取 tick 帧的行级 `agentType`(core 1.350 定谳)。[2354] 兼容面全清:
713
+ // 「缺省回落 name」旧 core 兼容臂已删——floor ^4.0.0 下 agentType 恒在;缺席=诚实不发(行键 optional)。
714
+ const agentType = e.agentType ? redactSecrets(e.agentType) : undefined;
725
715
  // The sink tick carries core's currentAction (its only attachment point today) —
726
716
  // UNTRUSTED (tool argument head) → redact, same discipline as name. Additive; absent until a tool runs.
727
717
  const currentAction = e.currentAction ? redactSecrets(e.currentAction) : undefined;
@@ -838,7 +828,9 @@ export function fleetBackgroundChildPublisher(bus, log) {
838
828
  handleSpawn(e);
839
829
  return;
840
830
  }
841
- const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: e.scope ?? "default", spawnedAt: Date.now() }; // scope domain map holds on the lazily-minted path too([1338]④ 首见起表);F2(2026-07-30 复审):lazy-mint 曾硬编码 "default",scoped 部署下 claim 落错租户键 ⇒ 让位失效退回双生行
831
+ // [2354] 补审 F2:lazy-mint 不再铸 spawnedAt: Date.now() 假锚( spawn 臂同口径——缺 launch 锚=
832
+ // 诚实缺席,行键 optional;tick/terminal 先到=restart/丢 spawn 边缘,收帧时刻不是 launch 时刻)。
833
+ const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: e.scope ?? "default" }; // scope domain map holds on the lazily-minted path too([1338]④ 首见起表);F2(2026-07-30 复审):lazy-mint 曾硬编码 "default",scoped 部署下 claim 落错租户键 ⇒ 让位失效退回双生行
842
834
  if (!meta.has(e.taskId)) {
843
835
  remember(e.taskId, m); // tick/terminal without a seen spawn (restart/eviction edge) — row minted lazily
844
836
  dlog("bg_child_event", { kind: `${e.kind}-lazy-mint`, taskId: e.taskId, sessionScoped: e.sessionScoped });
@@ -190,7 +190,7 @@ export function startFleetClientFromEnv(cfg, opts) {
190
190
  if (!cc?.baseUrl || !cc.token || !address)
191
191
  return undefined;
192
192
  if (!cc.worker) {
193
- opts.logger?.warn("fleet_client_no_worker_scope", { note: "FLEET_ADVERTISE_ADDRESS set but CONFIG_CENTER_WORKER missing — announce needs the worker (dispatch key); fleet client not started" });
193
+ opts.logger?.warn("fleet_client_no_worker_scope", { note: "FLEET_ADVERTISE_ADDRESS set but SEMA_REGISTRY_WORKER missing — announce needs the worker (dispatch key); fleet client not started" });
194
194
  return undefined;
195
195
  }
196
196
  return startFleetClient({ ...opts, center: { baseUrl: cc.baseUrl, token: cc.token, worker: cc.worker }, advertiseAddress: address });
@@ -38,6 +38,8 @@ export declare function resumeEntryForGate(kind: string, ids: {
38
38
  */
39
39
  export interface ActiveRunConflictDoneResult {
40
40
  status: "failed";
41
+ /** [2373]C-1:与 409 体同一机器码——流腿客户端凭它认拒绝,禁按 byte-frozen 文案匹配。 */
42
+ errorCode: "conflict.session_active_run";
41
43
  errorMessage: string;
42
44
  activeTaskId: string | null;
43
45
  activeTaskStatus?: string;
@@ -36,6 +36,7 @@ export function resumeEntryForGate(kind, ids) {
36
36
  export function toDoneFrameResult(body) {
37
37
  return {
38
38
  status: "failed",
39
+ errorCode: body.errorCode,
39
40
  errorMessage: body.error,
40
41
  activeTaskId: body.activeTaskId,
41
42
  ...(body.activeTaskStatus !== undefined ? { activeTaskStatus: body.activeTaskStatus } : {}),
@@ -4,7 +4,8 @@
4
4
  * 路由(前缀 `/v1/approvals` 与 `/v1/assistant`):
5
5
  * · durable 腿(接了 checkpointStore 时优先):list / stream(SSE 原生推送)/ decide|answer / preempt /
6
6
  * resume / plan_review;
7
- * · legacy 腿( approvalStore):list / by-id / decide —— 直连门部署下**必须**打不到(fail closed)。
7
+ * · legacy 腿(approvalStore 轮询车道)已于 5.0.0 整体退役([2354]/[2356] A-5)——无 checkpointStore
8
+ * `/v1/approvals` 落 404(诚实缺席),不再有 trusted-header 决裁面。
8
9
  *
9
10
  * 所有会**烧模型**的续跑都经 `ctx.legs.*`(resumeCheckpoint / resumePreempted / resumePlanReview)走 resume
10
11
  * 家族的同一条腿:lease admission、markResuming CAS、写 run log、发 fleet 帧都在腿里,本域只做入站鉴权/形状门。