@sema-agent/server 4.3.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.
Files changed (166) hide show
  1. package/USAGE.md +5 -3
  2. package/dist/approval-hmac.js +1 -1
  3. package/dist/approval.d.ts +5 -37
  4. package/dist/approval.js +12 -112
  5. package/dist/auth-bridge.d.ts +0 -1
  6. package/dist/auth-bridge.js +0 -1
  7. package/dist/auth-keys.d.ts +1 -1
  8. package/dist/auth-keys.js +1 -1
  9. package/dist/bench/s1/run-firm.js +1 -1
  10. package/dist/boot/config-center.d.ts +1 -1
  11. package/dist/boot/config-center.js +3 -2
  12. package/dist/boot/reapers.d.ts +1 -2
  13. package/dist/boot/reapers.js +4 -5
  14. package/dist/boot/resolve-spec.d.ts +1 -3
  15. package/dist/boot/resolve-spec.js +21 -35
  16. package/dist/boot/stores.js +1 -1
  17. package/dist/brain.js +3 -1
  18. package/dist/capabilities/center-prompts.d.ts +2 -3
  19. package/dist/capabilities/center-prompts.js +2 -3
  20. package/dist/capabilities/sandbox-file-send.d.ts +0 -2
  21. package/dist/capabilities/sandbox-file-send.js +2 -5
  22. package/dist/capabilities/scenarios.d.ts +0 -9
  23. package/dist/capabilities/scenarios.js +1 -20
  24. package/dist/capabilities/send-user-file-tool.js +3 -3
  25. package/dist/config-center/apply-effective.js +8 -8
  26. package/dist/config-center/types.d.ts +10 -3
  27. package/dist/config-provider.js +6 -11
  28. package/dist/config-types.d.ts +12 -26
  29. package/dist/config.js +95 -68
  30. package/dist/fleet/fleet-bus.js +25 -33
  31. package/dist/fleet-client.js +1 -1
  32. package/dist/http/active-run-conflict.d.ts +2 -0
  33. package/dist/http/active-run-conflict.js +1 -0
  34. package/dist/http/routes/approvals-assistant.d.ts +2 -1
  35. package/dist/http/routes/approvals-assistant.js +10 -96
  36. package/dist/http/routes/capabilities.js +6 -5
  37. package/dist/http/routes/fleet.js +4 -1
  38. package/dist/http/routes/images.js +10 -4
  39. package/dist/http/routes/runs.js +34 -49
  40. package/dist/http/routes/session-sync.d.ts +1 -1
  41. package/dist/http/routes/session-sync.js +2 -1
  42. package/dist/http/routes/tasks.js +7 -7
  43. package/dist/http/routes/trace-usage.js +1 -1
  44. package/dist/http/server.d.ts +1 -6
  45. package/dist/http/server.js +15 -10
  46. package/dist/http/verify-rounds.d.ts +2 -2
  47. package/dist/http/wire-types.d.ts +1 -1
  48. package/dist/images/bake-validate.d.ts +0 -22
  49. package/dist/images/bake-validate.js +0 -4
  50. package/dist/index.d.ts +4 -4
  51. package/dist/index.js +2 -5
  52. package/dist/main.js +5 -9
  53. package/dist/memory-scope.js +2 -2
  54. package/dist/orchestration/workflow-completion-inbox.d.ts +6 -4
  55. package/dist/orchestration/workflow-completion-inbox.js +6 -4
  56. package/dist/plugins/breaker-state-sql.d.ts +2 -3
  57. package/dist/plugins/image-index-sql.d.ts +0 -1
  58. package/dist/plugins/image-index-sql.js +0 -1
  59. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  60. package/dist/plugins/local-checkpoint-store.js +1 -1
  61. package/dist/plugins/memory-engine-pg.js +2 -1
  62. package/dist/plugins/memory-engine-tidb.js +1 -1
  63. package/dist/plugins/memory-engine-vector-util.d.ts +2 -3
  64. package/dist/plugins/memory-engine-vector-util.js +2 -3
  65. package/dist/plugins/memory-run-store.d.ts +1 -1
  66. package/dist/plugins/memory-session-policy-store.d.ts +1 -1
  67. package/dist/plugins/memory-session-policy-store.js +1 -1
  68. package/dist/plugins/memory-sync-store-pg.js +1 -1
  69. package/dist/plugins/pg-pool.js +8 -10
  70. package/dist/plugins/pg-rate-limiter.d.ts +1 -4
  71. package/dist/plugins/pg-rate-limiter.js +0 -1
  72. package/dist/plugins/roster-store-sql.d.ts +1 -3
  73. package/dist/plugins/roster-store-sql.js +3 -5
  74. package/dist/plugins/store-backend.d.ts +7 -17
  75. package/dist/plugins/store-backend.js +14 -30
  76. package/dist/plugins/tidb-pool.js +4 -28
  77. package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
  78. package/dist/plugins/workflow-journal-store-sql.js +1 -1
  79. package/dist/principal-jwt.js +2 -2
  80. package/dist/prompts-domain-validate.d.ts +4 -3
  81. package/dist/prompts-domain-validate.js +7 -8
  82. package/dist/router/route-orchestration.js +3 -3
  83. package/dist/run-local.js +5 -4
  84. package/dist/runs.js +8 -6
  85. package/dist/runtime-caps-resolver.d.ts +3 -2
  86. package/dist/runtime-caps-resolver.js +4 -4
  87. package/dist/runtime-governance.d.ts +4 -7
  88. package/dist/runtime-governance.js +12 -17
  89. package/dist/sealed-key.d.ts +1 -1
  90. package/dist/sealed-key.js +1 -1
  91. package/dist/security.d.ts +0 -5
  92. package/dist/security.js +8 -12
  93. package/dist/session-sync.d.ts +0 -2
  94. package/dist/session-sync.js +10 -17
  95. package/dist/spec-fields.js +6 -6
  96. package/dist/task-settings.js +7 -7
  97. package/dist/tool-approval.d.ts +3 -3
  98. package/dist/tool-approval.js +10 -10
  99. package/dist/trace/artifacts.d.ts +11 -0
  100. package/dist/trace/artifacts.js +8 -19
  101. package/dist/trace/ledger-sink.js +7 -1
  102. package/dist/trace/project.d.ts +17 -0
  103. package/dist/trace/project.js +55 -5
  104. package/package.json +2 -2
  105. package/dist/capabilities/oa-tools.d.ts +0 -15
  106. package/dist/capabilities/oa-tools.js +0 -54
  107. package/dist/plugins/approval-store-sql.d.ts +0 -116
  108. package/dist/plugins/approval-store-sql.js +0 -150
  109. package/dist/plugins/file-workflow-journal-store.d.ts +0 -12
  110. package/dist/plugins/file-workflow-journal-store.js +0 -12
  111. package/dist/plugins/pg-approval-store.d.ts +0 -9
  112. package/dist/plugins/pg-approval-store.js +0 -9
  113. package/dist/plugins/pg-breaker-state.d.ts +0 -8
  114. package/dist/plugins/pg-breaker-state.js +0 -8
  115. package/dist/plugins/pg-checkpoint-store.d.ts +0 -10
  116. package/dist/plugins/pg-checkpoint-store.js +0 -10
  117. package/dist/plugins/pg-file-snapshot-store.d.ts +0 -8
  118. package/dist/plugins/pg-file-snapshot-store.js +0 -8
  119. package/dist/plugins/pg-image-bake.d.ts +0 -12
  120. package/dist/plugins/pg-image-bake.js +0 -11
  121. package/dist/plugins/pg-image-index.d.ts +0 -12
  122. package/dist/plugins/pg-image-index.js +0 -11
  123. package/dist/plugins/pg-outcome-ledger.d.ts +0 -12
  124. package/dist/plugins/pg-outcome-ledger.js +0 -11
  125. package/dist/plugins/pg-resume-anchor-store.d.ts +0 -7
  126. package/dist/plugins/pg-resume-anchor-store.js +0 -7
  127. package/dist/plugins/pg-run-store.d.ts +0 -9
  128. package/dist/plugins/pg-run-store.js +0 -9
  129. package/dist/plugins/pg-session-policy-store.d.ts +0 -7
  130. package/dist/plugins/pg-session-policy-store.js +0 -7
  131. package/dist/plugins/pg-session-store.d.ts +0 -12
  132. package/dist/plugins/pg-session-store.js +0 -12
  133. package/dist/plugins/pg-tool-result-store.d.ts +0 -9
  134. package/dist/plugins/pg-tool-result-store.js +0 -9
  135. package/dist/plugins/pg-workflow-journal-store.d.ts +0 -9
  136. package/dist/plugins/pg-workflow-journal-store.js +0 -9
  137. package/dist/plugins/pg-workflow-run-store.d.ts +0 -9
  138. package/dist/plugins/pg-workflow-run-store.js +0 -9
  139. package/dist/plugins/tidb-approval-store.d.ts +0 -8
  140. package/dist/plugins/tidb-approval-store.js +0 -8
  141. package/dist/plugins/tidb-breaker-state.d.ts +0 -7
  142. package/dist/plugins/tidb-breaker-state.js +0 -7
  143. package/dist/plugins/tidb-checkpoint-store.d.ts +0 -9
  144. package/dist/plugins/tidb-checkpoint-store.js +0 -9
  145. package/dist/plugins/tidb-file-snapshot-store.d.ts +0 -8
  146. package/dist/plugins/tidb-file-snapshot-store.js +0 -8
  147. package/dist/plugins/tidb-image-bake.d.ts +0 -12
  148. package/dist/plugins/tidb-image-bake.js +0 -11
  149. package/dist/plugins/tidb-image-index.d.ts +0 -12
  150. package/dist/plugins/tidb-image-index.js +0 -11
  151. package/dist/plugins/tidb-outcome-ledger.d.ts +0 -12
  152. package/dist/plugins/tidb-outcome-ledger.js +0 -12
  153. package/dist/plugins/tidb-resume-anchor-store.d.ts +0 -7
  154. package/dist/plugins/tidb-resume-anchor-store.js +0 -7
  155. package/dist/plugins/tidb-run-store.d.ts +0 -10
  156. package/dist/plugins/tidb-run-store.js +0 -9
  157. package/dist/plugins/tidb-session-policy-store.d.ts +0 -7
  158. package/dist/plugins/tidb-session-policy-store.js +0 -7
  159. package/dist/plugins/tidb-tool-result-store.d.ts +0 -8
  160. package/dist/plugins/tidb-tool-result-store.js +0 -10
  161. package/dist/plugins/tidb-workflow-journal-store.d.ts +0 -9
  162. package/dist/plugins/tidb-workflow-journal-store.js +0 -9
  163. package/dist/plugins/tidb-workflow-run-store.d.ts +0 -10
  164. package/dist/plugins/tidb-workflow-run-store.js +0 -10
  165. package/dist/plugins/workflow-journal-limits.d.ts +0 -12
  166. package/dist/plugins/workflow-journal-limits.js +0 -12
@@ -1,11 +1,10 @@
1
- import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, assembleCodeTools, createGiteaIssueTool, defaultTaskRegistry } from "@sema-agent/core";
1
+ import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
2
2
  import { HttpError } from "../security.js";
3
3
  import { nowTool } from "./builtin-tools.js";
4
4
  import { GiteaClient, parseRepo, repoToolsFor } from "./repo-tools.js";
5
5
  import { createCouncilTool, clampRounds } from "./code-review-council.js";
6
6
  import { stablePrompt } from "./prompt.js";
7
7
  import { createTeamTool, teamCoordinatorPrompt, getTeam, teamNames } from "./team.js";
8
- import { oaTools, oaPrompt } from "./oa-tools.js";
9
8
  import { createWebSearchBackend, webSearchConfigFromSettings } from "../plugins/web-search.js";
10
9
  import { skillsForScenario } from "./skills.js";
11
10
  /**
@@ -205,23 +204,6 @@ export function buildScenarios(deps) {
205
204
  skills: skillsForScenario(deps.skills, "scan"),
206
205
  };
207
206
  },
208
- oa: (req, principal) => {
209
- if (!deps.oaApiBaseUrl || !deps.oaServiceToken)
210
- throw new HttpError(501, "oa scenario needs OA_API_BASEURL + OA_SERVICE_TOKEN configured");
211
- // OA enforces the END USER's RBAC from the forwarded principal — without one, every OA call would
212
- // run unattributed. Refuse rather than silently send a null principal.
213
- if (!principal)
214
- throw new HttpError(401, "oa scenario requires an authenticated principal (set REQUIRE_PRINCIPAL + x-agent-principal)");
215
- // 写权限的"开 issue"工具仅当配置了 OA_ISSUE_* 时注入;token/repo 固定,模型只填 title/body。
216
- const issueTool = deps.oaIssue
217
- ? [createGiteaIssueTool({ ...deps.oaIssue, name: "open_gitea_issue", description: "把用户的 bug/需求开成 Gitea issue 报给老板。只填 title(简洁)/body(现象、影响、复现/建议)。仓库与标签由部署固定。" })]
218
- : [];
219
- return {
220
- tools: [...oaTools({ baseUrl: deps.oaApiBaseUrl, token: deps.oaServiceToken, principal }), ...issueTool],
221
- skills: skillsForScenario(deps.skills, "oa"),
222
- promptProvider: oaPrompt,
223
- };
224
- },
225
207
  // Multi-agent team: pick a template, the consumer (OA) injects business context, we run it.
226
208
  // Templates resolve from the live registry (`getTeam`) = BUILTIN_TEAMS overridden/extended by the
227
209
  // sema registry at runtime (applyEffective → registerTeams), so a team is config-driven; built-ins
@@ -270,7 +252,6 @@ export function builtinScenarioDetails(scenarios, deps) {
270
252
  : "core 编码完全体提示词(= code 场景;finalVerification 固定开)"),
271
253
  "code-review": mk("code-review", "代码评审(clone-free 只读仓库工具;council=true 升多镜头评审团)", "repo-readonly", probe("code-review", { repo: "probe/probe" }, undefined, REPO_TOOLS), "评审员/评审团协调提示词(镜头分工+产出契约)"),
272
254
  scan: mk("scan", "中性只读仓库扫描(输出契约由 objective+skills 决定)", "repo-readonly", probe("scan", { repo: "probe/probe" }, undefined, REPO_TOOLS), "无场景附加提示词(objective+center skills 拥有输出契约)"),
273
- oa: mk("oa", "OA 资产运营助手(以最终用户 principal 走 OA API,RBAC+审计在 OA 侧)", "oa-api", probe("oa", {}, "probe:meta", ["oa_schema", "oa_alerts", "oa_list", "oa_get", "oa_lookup", "oa_create", "oa_update", "oa_transition"]), "OA 运营助手提示词(先 schema 后操作、写走受控流转)"),
274
255
  team: mk("team", "多智能体团队圆桌(模板成员+synthesizer,模板可由 registry 配置)", "team", probe("team", {}, undefined, ["run_team"]), "团队协调者提示词(圆桌纪律+合成流程)"),
275
256
  };
276
257
  }
@@ -24,7 +24,7 @@
24
24
  */
25
25
  import { AsyncLocalStorage } from "node:async_hooks";
26
26
  import { Type } from "typebox";
27
- import { builtinAgentDefinitions, canonicalToolName } from "@sema-agent/core";
27
+ import { builtinAgentDefinitions } from "@sema-agent/core";
28
28
  /** Per-run ALS carrier (QuestionCoordinator pattern): run legs wrap their body via runWithContext; the tool
29
29
  * recovers the ambient emit at execute time. No pending state, no respond face — emission is one-way. */
30
30
  export class SendUserFileEmitter {
@@ -155,7 +155,7 @@ const READONLY_BUILTIN_AGENT_TYPES = new Set(builtinAgentDefinitions().map((d) =
155
155
  * definition's name (core subagent.js); membership in core's builtin catalog ⇒ inject nothing for that spawn.
156
156
  */
157
157
  export function subagentSendUserFileExtraTools(deps) {
158
- const canonical = canonicalToolName(SEND_USER_FILE_TOOL_NAME);
158
+ const canonical = SEND_USER_FILE_TOOL_NAME; // RAW(5.0.0):本名恒 canonical,折叠面退役
159
159
  return (ctx) => {
160
160
  // F2: read-only builtin child (Explore/Plan) — never inject.
161
161
  if (ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType))
@@ -163,7 +163,7 @@ export function subagentSendUserFileExtraTools(deps) {
163
163
  // F1: deployment policy gates SendUserFile (deny/ask) — withhold from ALL children (fail-closed stopgap
164
164
  // until core propagates the parent toolPolicy into child specs). Canonical-space match (S8 discipline).
165
165
  const gate = deps.approvalGate();
166
- const gated = [...(gate.deny ?? []), ...(gate.require ?? []), ...(gate.neverAuto ?? [])].some((t) => canonicalToolName(t) === canonical);
166
+ const gated = [...(gate.deny ?? []), ...(gate.require ?? []), ...(gate.neverAuto ?? [])].some((t) => t === canonical);
167
167
  return gated ? [] : [deps.spec];
168
168
  };
169
169
  }
@@ -292,8 +292,8 @@ function stageEffective(config, eff, logger, opts) {
292
292
  // → undefined = INERT). Values are catalog NAME strings (open, not enum'd); dangling bindings were already
293
293
  // culled center-side. mutateInPlace keeps the reference RunnerDeps.tiers holds — the expansion itself happens
294
294
  // at Runner CONSTRUCTION (core 1.265 expandTiers), so a refresh-time change is restart-to-apply like models.
295
- // NOTE: the wire face (this module's EffectiveConfig) carries the JSON body verbatim, so tierGroups ride
296
- // eff.models even though CenterModel doesn't type themresolveActiveTiers reads them structurally.
295
+ // [2373]B-6:EffectiveConfig.models 已声明 tierGroups/activeTierGroup(types.ts)——旧「CenterModel
296
+ // doesn't type them」注随宽铸一起退役;resolveActiveTiers registry-core 形,窄化一步即可。
297
297
  const activeTiers = resolveActiveTiers(eff.models) ?? {};
298
298
  // [865]①(生产实锤:面板上选了 A 模型,实际跑的是目录里的另一个)显式默认解析——enabled[0] 会被任何改变 pool 顺序的写
299
299
  // 静默翻转,default 必须消费 wire 里已有的显式意图。解析单源 = resolveDefaultModelName(dry-run 的
@@ -360,13 +360,13 @@ function stageEffective(config, eff, logger, opts) {
360
360
  // 142-S4 projects 域(registry-core 0.10.0):center 项目登记簿 → config.projects(键=
361
361
  // projectId)。消费是 per-request 查表(memoryScope 派生 + defaultScopes 种子,security.ts/main.ts),
362
362
  // 所以 boot+refresh 双腿都 mutateInPlace 热应用(整表替换,非粘——center 停发即清空);env lane 恒空。
363
- // 宽读:域包裹 `{projects:{...}}`(registry-core ProjectsConfig 真形状)与裸 Record 都认(projectId=UUID,
364
- // 永不撞 "projects" 字面键);防御:非 object 条目跳过,字段逐个验型(wire verbatim,不信任形状)。
363
+ // [2373]B-8:裸 Record 臂删——registry-core 0.13.0 ProjectsConfig 只有包裹形 `{projects:{...}}`
364
+ // (types.d.ts:3599,且 :12231 钉进 EffectiveConfig),裸形无 producer( Q15 declared 双形容忍同族)。
365
+ // 防御保留:非 object 条目跳过,字段逐个验型(wire verbatim,不信任形状)。
365
366
  const projDomain = eff.projects;
366
- const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain)
367
- ? "projects" in projDomain && typeof projDomain.projects === "object"
368
- ? (projDomain.projects ?? {})
369
- : projDomain
367
+ const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain) &&
368
+ "projects" in projDomain && typeof projDomain.projects === "object"
369
+ ? (projDomain.projects ?? {})
370
370
  : {}) ?? {};
371
371
  const projects = {};
372
372
  for (const [pid, reg] of Object.entries(rawProjects)) {
@@ -126,10 +126,17 @@ export interface CenterMcpServer {
126
126
  export interface EffectiveConfig {
127
127
  version: number;
128
128
  updatedAt: string;
129
+ /** [2373]B-6:default/tierGroups/activeTierGroup 收进类型面(registry-core 0.13.0 已声明,
130
+ * types.d.ts:641/679/724)——此前 wire 面靠「JSON verbatim + 消费方结构读」逃逸,投影/消费两侧
131
+ * 各自 as 宽铸。tierGroups 上游是 ZodDefault(合规 center 恒发),但本类型描述的是**到货 JSON**
132
+ * (含非合规/旧存量 center),故仍标 optional——消费方 resolveActiveTiers 自身容缺。 */
129
133
  models: {
130
134
  models: CenterModel[];
131
135
  roles: Record<string, CenterRoleTarget>;
132
136
  atModelAllowlist?: string[];
137
+ default?: string;
138
+ tierGroups?: unknown;
139
+ activeTierGroup?: string;
133
140
  };
134
141
  /** OPTIONAL since registry-core 0.7 deleted the teams domain: a 0.7+ center /effective payload
135
142
  * has NO teams key (collab is its successor). Absent/empty ⇒ registerTeams resets to BUILTIN_TEAMS (the
@@ -180,12 +187,12 @@ export interface EffectiveConfig {
180
187
  };
181
188
  /** 142-S4(registry-core 0.10.0 第 15 域):center 项目登记簿。wire 面 verbatim JSON —
182
189
  * registry-core `ProjectsConfig` 的形状是 `{projects: Record<projectId, ProjectRegistration>}`(skills/mcp
183
- * 同款域包裹),但本 interface 宽读两种形态(包裹/裸 Record——projectId UUID,永远不会撞 "projects"
184
- * 字面键,判包裹无歧义)。消费=applyEffective → config.projects(displayName/gitRemotes/defaultScopes
190
+ * 同款域包裹)。[2373]B-8:裸 Record 联合臂随 apply-effective 的宽读一起删(0.13.0 只有包裹形,
191
+ * 裸形无 producer)。消费=applyEffective → config.projects(displayName/gitRemotes/defaultScopes
185
192
  * 三个 runtime 字段;审计字段不进 ServiceConfig)。 */
186
193
  projects?: {
187
194
  projects?: Record<string, unknown>;
188
- } | Record<string, unknown>;
195
+ };
189
196
  /** registry-core 0.6:治理三件(autonomy/commandPolicy/approvalRequire)拆新 governance 域=真值;
190
197
  * 过渡期 effective 在 runtime 位双写镜像。读取优先 governance 新位、runtime 旧位 fallback(顺序契约:
191
198
  * 消费端先切换、确认后 center 才撤双写,治理闸零 fail-open 窗口)。 */
@@ -210,16 +210,9 @@ export function mapToServiceEffective(eff, version) {
210
210
  ...(m.contextWindow !== undefined ? { contextWindow: m.contextWindow } : {}),
211
211
  ...(m.maxTokens !== undefined ? { maxTokens: m.maxTokens } : {}),
212
212
  // codex F5 ([818]① 1M dual-window): an EXPLICIT `autoCompactTokens` must survive this CLOSED
213
- // projection the remote lane ships the roster JSON verbatim and toModel reads the field
214
- // structurally, but the local lane funnels through this mapper, which silently ATE the explicit
215
- // value (it then fell back to the contextWindow derivation). Structural read (registry-core's
216
- // ModelEntry doesn't declare it — same passthrough posture as tierGroups below). NOTE (honest
217
- // gap): registry-core's zod ModelEntry is "strip"-mode, so a value in a config.d/models.json read
218
- // through FileConfigStore is ALSO dropped upstream of this mapper — that half needs a registry-core
219
- // schema addition; this fix removes OUR projection's drop for any caller of this seam.
220
- ...(typeof m.autoCompactTokens === "number"
221
- ? { autoCompactTokens: m.autoCompactTokens }
222
- : {}),
213
+ // projection. [2373]B-6d:registry-core 0.13.0 已把它收进 ModelEntry schema(types.d.ts:157/212)
214
+ // ——旧注的「strip-mode 会在上游丢值」的 honest gap 已随那次 schema addition 关闭,结构读改类型直读。
215
+ ...(typeof m.autoCompactTokens === "number" ? { autoCompactTokens: m.autoCompactTokens } : {}),
223
216
  ...(m.cost !== undefined ? { cost: m.cost } : {}),
224
217
  ...(m.extraBody !== undefined ? { extraBody: m.extraBody } : {}),
225
218
  // registry-core 0.10.13 promptGuidance ([998]⑤c): the CLOSED projection must pass it through or
@@ -232,10 +225,12 @@ export function mapToServiceEffective(eff, version) {
232
225
  // 本 mapper 是 CLOSED 投影——不直通则本地 lane 静默丢键(autoCompactTokens 同类病),applyEffective 的
233
226
  // resolveDefaultModelName 第一优先级在本地 lane 永远点不亮。写面悬空 ref 由 registry-core superRefine
234
227
  // fail-loud;存量/未经 schema 的路径仍由消费方 warn 降级(config-center/facade.ts resolveDefaultModelName)。
235
- ...(eff.models.default !== undefined ? { default: eff.models.default } : {}),
228
+ ...(eff.models.default !== undefined ? { default: eff.models.default } : {}), // [2373]B-6c:0.13.0 类型直读(types.d.ts:641)
236
229
  ...(eff.models.atModelAllowlist !== undefined ? { atModelAllowlist: eff.models.atModelAllowlist } : {}),
237
230
  // registry-core 0.9.0 档位组: verbatim passthrough — applyEffective's resolveActiveTiers reads
238
231
  // these structurally off eff.models (the remote lane ships them JSON-verbatim; the local lane must too).
232
+ // [2373]B-6b:0.13.0 类型直读(EffectiveConfig.models 面已声明四键,宽铸删)。本地 lane 的
233
+ // eff 来自 FileConfigStore 原始 JSON(非 zod 输出),tierGroups 的 ZodDefault 恒在场性不适用——门保留。
239
234
  ...(eff.models.tierGroups !== undefined ? { tierGroups: eff.models.tierGroups } : {}),
240
235
  ...(eff.models.activeTierGroup !== undefined ? { activeTierGroup: eff.models.activeTierGroup } : {}),
241
236
  },
@@ -299,13 +299,13 @@ export interface ServiceConfigFlat {
299
299
  database: string;
300
300
  connectionLimit?: number;
301
301
  };
302
- /** Which SQL engine the durable stores run on (clay 2026-06-21 PG migration; 2026-07-06 wording: canonical
303
- * value is `mysql` = any MySQL-protocol server — MySQL, TiDB, MariaDB — over the same mysql2 pool; the env
304
- * value `tidb` is a back-compat alias normalized to `mysql` at parse). `mysql` reads the `tidb`-named
305
- * connection coords above (MYSQL_* env preferred, TIDB_* accepted); `pg` (PostgreSQL dialect) reads `pg`
306
- * below. A subsystem that selects a DB backend (sessionBackend === "tidb", read as "the SQL DB") then
307
- * runs on the engine `dbBackend` picks. TiDB is fully supported (any MySQL-protocol engine works —
308
- * memory no longer needs a VECTOR column since design/138 moved it onto the file engine). Env: DB_BACKEND. */
302
+ /** Which SQL engine the durable stores run on (clay 2026-06-21 PG migration). Canonical value is `mysql`
303
+ * = any MySQL-protocol server — MySQL, TiDB, MariaDB — over the same mysql2 pool; `pg` (PostgreSQL
304
+ * dialect) reads `pg` below. [2354]/[2373]B-3(5.0.0):the env value `tidb` and the TIDB_* coord names
305
+ * are RETIRED `tidb` is rejected by the enum and any TIDB_* env in place refuses to start with the
306
+ * MYSQL_* replacement named (see RETIRED_ENVS in config.ts). `mysql` reads MYSQL_* coords only.
307
+ * TiDB itself stays fully supported (any MySQL-protocol engine works — memory no longer needs a VECTOR
308
+ * column since design/138 moved it onto the file engine). Env: DB_BACKEND. */
309
309
  dbBackend: "mysql" | "pg" | "local" | "memory";
310
310
  /** clay 拍(2026-07-27):operator 是否显式设了 DB_BACKEND。main.ts 的降级判据——**默认推导**的
311
311
  * local(裸 boot)在 mkdir/ensure 失败时降级 memory + warn + gauge(auto-DB 不可达先例);
@@ -468,11 +468,10 @@ export interface ServiceConfigFlat {
468
468
  operatorPrincipals: string[];
469
469
  /** Tool names that are always denied. */
470
470
  approvalDeny: string[];
471
- /** Poll interval (ms) for an approval decision. */
472
- approvalPollMs: number;
473
471
  /** design/45 durable-checkpoint (core 1.67): when true (+ a TiDB pool), F4 uses durable **suspend/resume**
474
472
  * instead of poll-blocking — a gated tool `ask` suspends the task (status:"suspended" + checkpoint), an
475
- * operator resumes it on any replica. Default OFF unchanged poll-policy. `DURABLE_APPROVAL=true`. */
473
+ * operator resumes it on any replica. Default OFF([2354] A-5:轮询车道已退役,OFF+门意图 core UNGATED
474
+ * 告警,不再有回退门形). `DURABLE_APPROVAL=true`. */
476
475
  durableApproval: boolean;
477
476
  /** design/80 seam #2 (preemption) / design/74 ("失败≠没做完" third state): when true (+ durable mode), the
478
477
  * /v1/runs durable path opts every task into core `resourceSuspend` — a budget/turns/walltime limit OR a
@@ -797,7 +796,7 @@ export interface ServiceConfigFlat {
797
796
  to: string;
798
797
  atCostFraction: number;
799
798
  reactive: boolean;
800
- downgradeOn?: ("rate_limit" | "breaker_open")[];
799
+ downgradeOn?: import("@sema-agent/core").DegradeReason[];
801
800
  toSupportsImages: boolean;
802
801
  };
803
802
  /** Quality-gate cascade ladder (core 1.45 `runCascade`, opt-in via `cascade:true`): catalog model
@@ -847,19 +846,6 @@ export interface ServiceConfigFlat {
847
846
  defaultScenario: string;
848
847
  /** Directory of baked-in skill Markdown files, loaded once at startup. */
849
848
  skillsDir: string;
850
- /** OA backend base URL for the `oa` scenario tools (read /api/v1, write /api/assets). */
851
- oaApiBaseUrl?: string;
852
- /** Shared trust token forwarded to OA (= OA AGENT_SERVICE_TOKEN); OA enforces per-principal RBAC. */
853
- oaServiceToken?: string;
854
- /** `oa` 场景的"开 Gitea issue"工具配置(OA AI 请求#5);仅当 OA_ISSUE_TOKEN 配置时启用。token 来自 env,不入模型/日志。
855
- * 上线建议:OA_ISSUE_TOKEN 用**最小权限**(仅 issue:write 的 scope),别用全权 token。 */
856
- oaIssue?: {
857
- baseUrl: string;
858
- owner: string;
859
- repo: string;
860
- token: string;
861
- defaultLabels?: number[];
862
- };
863
849
  /** Central sema registry (sema-registry). When set, the service pulls the effective config
864
850
  * on boot (and refreshes) and applies it OVER the env defaults — env is the fallback. Logical config
865
851
  * only (model roster/roles/teams); secrets/gateways stay in this service's env.
@@ -886,7 +872,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
886
872
  export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "cascadeLadder" | "degrade">;
887
873
  /** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
888
874
  * 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
889
- export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalPollMs" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "toolApprovalEnabled" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
875
+ export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "toolApprovalEnabled" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
890
876
  /** 组:memory(记忆面 + TOC 同步腿)。 */
891
877
  export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memorySync" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
892
878
  /** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
@@ -899,7 +885,7 @@ export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachme
899
885
  /** 组:observability(可观测)。 */
900
886
  export type ServiceObservabilityConfig = Pick<ServiceConfigFlat, "metricsToken" | "traceToken" | "toolTrace" | "traceThinking" | "logLevel" | "otel">;
901
887
  /** 组:integrations(外部集成)。`mcpServers` 由 sema-registry 适配器填(无 env 腿),归本组。 */
902
- export type ServiceIntegrationsConfig = Pick<ServiceConfigFlat, "pluginsAllowHosts" | "mcpServers" | "configBootFetchBudgetMs" | "gitApiBaseUrl" | "gitApiToken" | "defaultScenario" | "skillsDir" | "oaApiBaseUrl" | "oaServiceToken" | "oaIssue" | "configCenter" | "configProvider" | "configLocalDir">;
888
+ export type ServiceIntegrationsConfig = Pick<ServiceConfigFlat, "pluginsAllowHosts" | "mcpServers" | "configBootFetchBudgetMs" | "gitApiBaseUrl" | "gitApiToken" | "defaultScenario" | "skillsDir" | "configCenter" | "configProvider" | "configLocalDir">;
903
889
  /** 九个组槽。每组恒在场(`loadConfig` / `attachConfigGroups` 装好才交出配置),故不可选——新代码写
904
890
  * `config.modelPlane.model` 不需要 `?.`(可选组会把 `Model` 污染成 `Model | undefined`)。 */
905
891
  export interface ServiceConfigGroups {
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,32 @@ 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
+ /** [2354] 兼容面全清:退役 env 名墓碑——老名**在场即拒启**带新名指引(boolEnvWithLegacyNegated 3.0.0
64
+ * 负名墓碑同形)。静默忽略=部署带着失效配置无感跑;双读=兼容层吸收下游冒烟。都不要。 */
65
+ const RETIRED_ENVS = [
66
+ ["TIDB_HOST", "MYSQL_HOST"], ["TIDB_PORT", "MYSQL_PORT"], ["TIDB_USER", "MYSQL_USER"],
67
+ ["TIDB_PASSWORD", "MYSQL_PASSWORD"], ["TIDB_DATABASE", "MYSQL_DATABASE"], ["TIDB_POOL_SIZE", "MYSQL_POOL_SIZE"],
68
+ ["CONFIG_CENTER_URL", "SEMA_REGISTRY_URL"], ["CONFIG_CENTER_TOKEN", "SEMA_REGISTRY_TOKEN"],
69
+ ["CONFIG_CENTER_DRY_RUN", "SEMA_REGISTRY_DRY_RUN"], ["CONFIG_CENTER_WORKER", "SEMA_REGISTRY_WORKER"],
70
+ ["ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL"],
71
+ ["REGISTRY_ADMIN_TOKEN", "SEMA_REGISTRY_ADMIN_TOKEN"], // [2373]B-1:墓碑族唯一漏网的双读(config-center.ts 发射点)
72
+ // [2354] OA 场景整体退役(clay 令 2026-08-01):配置本身作废,无替代名。
73
+ ["OA_API_BASEURL", "(retired — OA scenario removed in 5.0.0)"],
74
+ ["OA_SERVICE_TOKEN", "(retired — OA scenario removed in 5.0.0)"],
75
+ ["OA_ISSUE_TOKEN", "(retired — OA scenario removed in 5.0.0)"],
76
+ ["OA_ISSUE_BASEURL", "(retired — OA scenario removed in 5.0.0)"],
77
+ ["OA_ISSUE_OWNER", "(retired — OA scenario removed in 5.0.0)"],
78
+ ["OA_ISSUE_REPO", "(retired — OA scenario removed in 5.0.0)"],
79
+ ["OA_ISSUE_LABELS", "(retired — OA scenario removed in 5.0.0)"],
80
+ ["APPROVAL_POLL_MS", "(retired — OA poll-approval lane removed in 5.0.0; durable checkpoint lane is the only gate)"],
81
+ ];
82
+ function rejectRetiredEnvs() {
83
+ const hit = RETIRED_ENVS.filter(([o]) => process.env[o] !== undefined && process.env[o] !== "");
84
+ if (hit.length > 0) {
85
+ throw new Error(`retired env name(s) set ([2354] 兼容面全清,5.0.0 起拒启): ` +
86
+ hit.map(([o, n]) => `${o} → use ${n}`).join("; ") +
87
+ ` — rename the env key(s); the value semantics are unchanged.`);
71
88
  }
72
- return v;
73
89
  }
74
90
  /** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN`
75
91
  * (which e.g. slips past the `runStaleSec` liveness guard and silently disables the reaper). */
@@ -387,11 +403,11 @@ function parseStoreDomain(ctx) {
387
403
  // 在场时,dbBackend 的旧默认 "mysql" 是**引擎选择器**(auto 探测/coords 构建都 key 它),默认切
388
404
  // local 会把这些既有形错切到文件店(回归钉在 config-coverage)。
389
405
  const sqlSignal = (!!process.env.SESSION_BACKEND && process.env.SESSION_BACKEND !== "memory") ||
390
- !!(process.env.TIDB_HOST || process.env.MYSQL_HOST || process.env.PG_HOST);
406
+ !!(process.env.MYSQL_HOST || process.env.PG_HOST);
391
407
  const bareBoot = !dbBackendSet && !sqlSignal;
392
408
  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;
409
+ // [2354]:公名枚举不再收 "tidb"(enumEnv 对越集值自带拒启);内部逻辑不变。
410
+ const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", ["mysql", "pg", "local", "memory"]);
395
411
  if (multiTenantBareBoot) {
396
412
  CONFIG_NOTICES.push({
397
413
  event: "db_backend_default_memory_multi_tenant",
@@ -407,7 +423,7 @@ function parseStoreDomain(ctx) {
407
423
  // backend); an UNSET DB_BACKEND keeps the memory default (no surprise DB requirement on a bare boot).
408
424
  // `mysql` is accepted as an alias of the internal durable label `tidb` (see ServiceConfig.sessionBackend).
409
425
  const sqlEngineExplicit = !!process.env.DB_BACKEND && dbBackend !== "local" && dbBackend !== "memory";
410
- const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", ["memory", "mysql", "tidb", "auto"]);
426
+ const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", ["memory", "mysql", "auto"]); // [2354]:公名 "tidb" 退役
411
427
  const sessionBackend = sessionBackendRaw === "mysql" ? "tidb" : sessionBackendRaw;
412
428
  // P0.5 variant-2: the file-backed `local` backend's data root. Same tail as main.ts's `localRoot` and
413
429
  // run-local.ts's `root` (CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent), so the HTTP service + a run-local
@@ -421,7 +437,7 @@ function parseStoreDomain(ctx) {
421
437
  // `auto` builds the DB coords only when a host is actually configured — otherwise it stays memory with no
422
438
  // probe. (Explicit mysql always builds them so a missing host fails loudly.) `needsDb` is engine-agnostic;
423
439
  // 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);
440
+ const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
425
441
  const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
426
442
  const needsDb = needsTidb;
427
443
  return {
@@ -433,14 +449,13 @@ function parseStoreDomain(ctx) {
433
449
  localDataRoot,
434
450
  tidb: needsDb && dbBackend === "mysql"
435
451
  ? {
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)
452
+ host: env("MYSQL_HOST"),
453
+ port: Number(env("MYSQL_PORT", "3306")),
454
+ user: env("MYSQL_USER"),
455
+ password: env("MYSQL_PASSWORD", ""),
456
+ database: env("MYSQL_DATABASE"),
457
+ connectionLimit: process.env.MYSQL_POOL_SIZE
458
+ ? Number(process.env.MYSQL_POOL_SIZE)
444
459
  : undefined,
445
460
  }
446
461
  : undefined,
@@ -528,7 +543,7 @@ function parseModelDomain() {
528
543
  // credential alone does NOT infer (a bare ANTHROPIC_API_KEY historically only assembles the cloud route for
529
544
  // registry-declared anthropic models); neither present ⇒ "gateway", byte-identical to before.
530
545
  const explicitProvider = process.env.MODEL_PROVIDER; // env() semantics: "" = unset
531
- const inferredAnthropicBaseUrl = firstSetEnv("ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL");
546
+ const inferredAnthropicBaseUrl = firstSetEnv("ANTHROPIC_BASE_URL"); // [2354]:无下划线非标名退役(墓碑)
532
547
  const inferredAnthropicCred = firstSetEnv("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN");
533
548
  const provider = (() => {
534
549
  if (explicitProvider !== undefined && explicitProvider !== "")
@@ -719,7 +734,7 @@ function parseModelDomain() {
719
734
  ? {
720
735
  apiKey: firstSetEnv("ANTHROPIC_API_KEY"),
721
736
  authToken: firstSetEnv("ANTHROPIC_AUTH_TOKEN"),
722
- baseUrl: firstSetEnv("ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL"),
737
+ baseUrl: firstSetEnv("ANTHROPIC_BASE_URL"),
723
738
  version: process.env.ANTHROPIC_VERSION,
724
739
  cacheBreakpoints: (process.env.ANTHROPIC_CACHE_BREAKPOINTS ?? "true") === "true",
725
740
  ...(process.env.ANTHROPIC_MAX_RETRIES ? { maxRetries: Number(env("ANTHROPIC_MAX_RETRIES", "2")) } : {}),
@@ -755,9 +770,9 @@ function parseModelDomain() {
755
770
  to: env("MODEL_DEGRADE_TO"),
756
771
  atCostFraction: Number(env("MODEL_DEGRADE_AT_COST_FRACTION", "0.7")),
757
772
  reactive: boolEnv("MODEL_DEGRADE_REACTIVE", false),
758
- downgradeOn: csv("MODEL_DEGRADE_ON").length
759
- ? csv("MODEL_DEGRADE_ON")
760
- : undefined,
773
+ // core 5.1.0([2390]②):词表=DegradeReason 全集;缺席时 core 默认含 server_error(默认门变宽),
774
+ // 本 env 是运维收窄/放宽的唯一旋钮。拼错词 boot 响亮拒——盲铸静默折无效成员是 TASK_TIMEOUT_SEC 同族病。
775
+ downgradeOn: parseDegradeOn(csv("MODEL_DEGRADE_ON")),
761
776
  // vision precheck (adversarial-review finding): the degrade TARGET is an external gateway
762
777
  // model NOT in config.models, so resolveSpec's precheck (which only sees the picked model) can't see its
763
778
  // vision capability — a vision-capable main + text-only degrade target would let images reach the text-only
@@ -771,6 +786,18 @@ function parseModelDomain() {
771
786
  }
772
787
  /** 域:approval(审批/HITL 门)—— 审批名单与预算、durable 面、直连门开关与钥、敏感写门、elicitation。
773
788
  * 注:直连门的**激活**(directDoorActive)与 D-G §3 半开门不变量是跨域合取,留在装配层。 */
789
+ /** MODEL_DEGRADE_ON 词表=core `DegradeReason` 全集(集合用该类型铸=锁步:core 删词此处 tsc 红,加词这里
790
+ * 补一个词即可)。非法词 boot 响亮拒——盲铸静默折无效成员与 TASK_TIMEOUT_SEC 非法值折「无超时」同族。 */
791
+ const DEGRADE_ON_WORDS = new Set(["breaker_open", "rate_limit", "budget", "server_error", "last_resort"]);
792
+ function parseDegradeOn(words) {
793
+ if (words.length === 0)
794
+ return undefined;
795
+ const bad = words.filter((w) => !DEGRADE_ON_WORDS.has(w));
796
+ if (bad.length > 0) {
797
+ throw new Error(`MODEL_DEGRADE_ON has unknown trigger word(s): ${bad.join(", ")} (valid: ${[...DEGRADE_ON_WORDS].join(", ")})`);
798
+ }
799
+ return words;
800
+ }
774
801
  function parseApprovalDomain(ctx) {
775
802
  const { postureOn } = ctx; // 跨域入参②:posture 三态(single-user turnkey ⇒ HITL 面默认 ON)
776
803
  // design/80 D-G: the direct-connect approval door anchors (all must be present to ACTIVATE; fail-closed).
@@ -778,15 +805,34 @@ function parseApprovalDomain(ctx) {
778
805
  const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS);
779
806
  // DURABLE_APPROVAL 既是本域字段,也是直连门的硬前置 —— 那条前置的理由与断言在装配层的跨域不变量段。
780
807
  const durableApproval = boolEnv("DURABLE_APPROVAL", false);
808
+ // [2380] core 5.0.0 RB-476:approval 名单里的退役工具名**装配期拒启**(带现役名指引)。core 自己的
809
+ // prepare 门(config.legacy_tool_name)在提交期才响——boot 期先拒,部署带失效名单不启动而不是首任务炸。
810
+ // 名单 RAW 直传(canonicalToolName 折叠面已随 core 退役,见 approval.ts)。
811
+ const rejectRetiredToolNames = (envName, names) => {
812
+ const hit = names.filter((n) => RETIRED_TOOL_NAMES.has(n));
813
+ if (hit.length > 0) {
814
+ throw new Error(`${envName} names retired tool(s) (core 5.0.0 RB-476 — the alias fold is gone, these never match): ` +
815
+ hit.map((n) => `${n} → ${RETIRED_TOOL_NAMES.get(n)}`).join("; "));
816
+ }
817
+ // [2382]① core 5.1.0 判别式:pre-prefix 短名(figma__x 形)静态表拦不住,而这些名单是 server 侧
818
+ // ToolPolicy 原样比对——core 的 prepare 硬臂(G-3)只管 core 侧 rules,管不到这里;放过=deny 静默
819
+ // 永不匹配(失守方向)。名含 `__` 且协议前缀不认识 ⇒ boot 拒。部署自有工具真叫 `figma__x` 的极端形
820
+ // 会被误拒(boot 无 roster 可豁免)——与上面退役表墓碑同一个已接受权衡,失败响亮且指引明确。
821
+ const prePrefix = names.filter((n) => n.includes("__") && protocolOf(n) === undefined);
822
+ if (prePrefix.length > 0) {
823
+ throw new Error(`${envName} names look like pre-prefix MCP short forms (the v4 auto-prefix fold is gone — these never match a live tool): ` +
824
+ prePrefix.map((n) => `${n} → mcp__${n}`).join("; "));
825
+ }
826
+ return names;
827
+ };
781
828
  return {
782
- approvalRequire: csv("APPROVAL_REQUIRE"),
783
- approvalDeny: csv("APPROVAL_DENY"),
784
- approvalPollMs: Number(env("APPROVAL_POLL_MS", "1500")),
829
+ approvalRequire: rejectRetiredToolNames("APPROVAL_REQUIRE", csv("APPROVAL_REQUIRE")),
830
+ approvalDeny: rejectRetiredToolNames("APPROVAL_DENY", csv("APPROVAL_DENY")),
785
831
  durableApproval,
786
832
  resourceSuspend: boolEnv("RESOURCE_SUSPEND", false),
787
833
  resourceSuspendTtlSec: Math.max(0, numEnv("RESOURCE_SUSPEND_TTL_SEC", "0")), // 0 ⇒ core default (30d)
788
834
  approvalAutoBudget: Math.min(10000, Math.max(0, numEnv("APPROVAL_AUTO_BUDGET", "0"))), // clamp [0,10000]; 0 = off
789
- approvalNeverAuto: csv("APPROVAL_NEVER_AUTO"),
835
+ approvalNeverAuto: rejectRetiredToolNames("APPROVAL_NEVER_AUTO", csv("APPROVAL_NEVER_AUTO")),
790
836
  approvalHmacKeys: dgHmacKeys, // D-G: WIRED (see the field's JSDoc); empty ⇒ inactive, not unimplemented
791
837
  directApprovalDoor,
792
838
  // ③ sensitive-path write deny set: unset = core's recommended set; explicit value = full replacement
@@ -910,11 +956,10 @@ function parseAuthDomain(ctx) {
910
956
  .map((s) => [s.slice(0, s.indexOf("=")).trim(), s.slice(s.indexOf("=") + 1).trim()])
911
957
  .filter(([tok, sys]) => tok.length > 0 && sys.length > 0));
912
958
  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,
959
+ // [2354]:HOST 读取臂已删(zsh 恒设 HOST=机器名,不可墓碑——静默不读;cli/桌面 [2053] 起已传
960
+ // BIND_HOST)。绑址唯一口=BIND_HOST;空串=未设。
961
+ bindHost: process.env.BIND_HOST || undefined,
962
+ bindHostSource: process.env.BIND_HOST ? "BIND_HOST" : undefined,
918
963
  authToken: process.env.SERVICE_AUTH_TOKEN,
919
964
  authTokens,
920
965
  allowUnauthedWrites: boolEnv("ALLOW_UNAUTHED_WRITES", false),
@@ -1210,35 +1255,16 @@ function parseIntegrationsDomain() {
1210
1255
  // 编码产品出厂态对标 CC 出厂态,通用域中性 persona 用 DEFAULT_SCENARIO=default 显式选回。
1211
1256
  defaultScenario: env("DEFAULT_SCENARIO", "code"),
1212
1257
  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,
1258
+ // [2354] clay 令:OA 场景整体退役(oa-tools/oa 场景/OA 轮询审批车道随 5.0.0 全清)——
1259
+ // OA_* env 全族进 RETIRED_ENVS 墓碑(在场即拒启),此处零读取。
1231
1260
  // 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)
1261
+ // [2354]:CONFIG_CENTER_* 双读已删(墓碑在 rejectRetiredEnvs);SEMA_REGISTRY_* 唯一。
1262
+ configCenter: process.env.SEMA_REGISTRY_URL
1237
1263
  ? {
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,
1264
+ baseUrl: env("SEMA_REGISTRY_URL"),
1265
+ token: process.env.SEMA_REGISTRY_TOKEN || "",
1266
+ dryRun: process.env.SEMA_REGISTRY_DRY_RUN === "true",
1267
+ worker: process.env.SEMA_REGISTRY_WORKER || undefined,
1242
1268
  }
1243
1269
  : undefined,
1244
1270
  configProvider: process.env.CONFIG_PROVIDER || undefined,
@@ -1254,7 +1280,7 @@ const MODEL_PLANE_GROUP_KEYS = [
1254
1280
  "modelApiKeyEnv", "modelApiKeys", "modelQuotaWeights", "tiers", "projects", "roles", "cascadeLadder", "degrade",
1255
1281
  ];
1256
1282
  const APPROVAL_GROUP_KEYS = [
1257
- "approvalRequire", "approvalDeny", "approvalPollMs", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
1283
+ "approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
1258
1284
  "approvalHmacKeys", "durableApproval", "directApprovalDoor", "directDoorActive", "resourceSuspend",
1259
1285
  "resourceSuspendTtlSec", "askQuestionEnabled", "toolApprovalEnabled", "mcpElicitation", "sensitiveWritePatterns",
1260
1286
  "manualModeShellGate",
@@ -1286,7 +1312,7 @@ const LIMITS_HTTP_GROUP_KEYS = [
1286
1312
  const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
1287
1313
  const INTEGRATIONS_GROUP_KEYS = [
1288
1314
  "pluginsAllowHosts", "mcpServers", "configBootFetchBudgetMs", "gitApiBaseUrl", "gitApiToken", "defaultScenario",
1289
- "skillsDir", "oaApiBaseUrl", "oaServiceToken", "oaIssue", "configCenter", "configProvider", "configLocalDir",
1315
+ "skillsDir", "configCenter", "configProvider", "configLocalDir",
1290
1316
  ];
1291
1317
  /** 组名 → 该组取景的平铺键(introspection 面:测试用它钉「每个平铺键恰好被一组取景」)。 */
1292
1318
  export const CONFIG_GROUP_KEYS = {
@@ -1339,6 +1365,7 @@ export function attachConfigGroups(flat) {
1339
1365
  return flat;
1340
1366
  }
1341
1367
  export function loadConfig() {
1368
+ rejectRetiredEnvs(); // [2354] 退役 env 墓碑:在场即拒启带新名指引
1342
1369
  CONFIG_WARNINGS.length = 0; // repeated loadConfig() calls (test setup) must not accumulate stale warnings
1343
1370
  CONFIG_NOTICES.length = 0;
1344
1371
  CONFIG_KNOBS.clear(); // the polarity table describes THIS load, never a previous one's env
@@ -1379,8 +1406,8 @@ export function loadConfig() {
1379
1406
  const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, authTokens } = auth;
1380
1407
  const { enabled: imageBakesEnabled, runnerPrincipal: bakeRunnerPrincipal } = orchestration.imageBakes;
1381
1408
  // 🔴 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).
1409
+ // is a HARD precondition. Without it, checkpointStore is undefined and there is NO decide path at all
1410
+ // ([2354] A-5:legacy trusted-header /decide 车道已在 5.0.0 退役——本不变量从「防旁路」变成「防死门」,保留)
1384
1411
  const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
1385
1412
  // 🔴 boot invariant (D-G §3): a half-configured direct door is the WORST state (looks on, verifies nothing) — and
1386
1413
  // an empty OPERATOR_PRINCIPALS makes isOperator() true-for-all (= cross-tenant escalation). Refuse to start.