@sema-agent/server 7.99.0 → 7.101.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 (174) hide show
  1. package/MIGRATION.md +35 -1
  2. package/README.md +12 -1
  3. package/README.zh-CN.md +9 -1
  4. package/USAGE.md +150 -56
  5. package/dist/approval-ask-machine.d.ts +10 -0
  6. package/dist/approval-ask-machine.js +3 -0
  7. package/dist/approval-card.d.ts +86 -1
  8. package/dist/approval-card.js +42 -8
  9. package/dist/approval-policy-settlement.d.ts +31 -0
  10. package/dist/approval-policy-settlement.js +16 -0
  11. package/dist/approval-reconciler.d.ts +8 -1
  12. package/dist/approval-reconciler.js +5 -4
  13. package/dist/approval.d.ts +44 -20
  14. package/dist/approval.js +14 -6
  15. package/dist/boot/config-center.d.ts +16 -8
  16. package/dist/boot/config-center.js +114 -90
  17. package/dist/boot/execution-env.d.ts +1 -2
  18. package/dist/boot/execution-env.js +21 -3
  19. package/dist/boot/leader.js +12 -9
  20. package/dist/boot/memory-consolidation.d.ts +38 -0
  21. package/dist/boot/memory-consolidation.js +35 -0
  22. package/dist/boot/parked-revive-gate.js +1 -0
  23. package/dist/boot/reapers.js +1 -0
  24. package/dist/boot/resolve-spec.d.ts +0 -1
  25. package/dist/boot/resolve-spec.js +28 -10
  26. package/dist/boot/runner-deps.d.ts +2 -2
  27. package/dist/boot/runner-deps.js +9 -3
  28. package/dist/boot/stage-02-registry-pull.js +9 -0
  29. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  30. package/dist/boot/stage-06-runners.d.ts +2 -2
  31. package/dist/boot/stage-06-runners.js +3 -3
  32. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  33. package/dist/boot/stage-07-capability-layer.js +6 -6
  34. package/dist/boot/stage-08-reapers.d.ts +3 -4
  35. package/dist/boot/stage-08-reapers.js +2 -2
  36. package/dist/boot/stage-09-leader.d.ts +2 -3
  37. package/dist/boot/stage-10-http-server.d.ts +2 -3
  38. package/dist/boot/stage-10-http-server.js +2 -2
  39. package/dist/boot/stores.js +4 -0
  40. package/dist/capabilities/center-plugins.d.ts +14 -2
  41. package/dist/capabilities/center-plugins.js +13 -5
  42. package/dist/capabilities/hands-lane.js +2 -1
  43. package/dist/capabilities/scenarios.js +18 -5
  44. package/dist/capabilities/tool-defer.d.ts +2 -2
  45. package/dist/config-catalog.d.ts +2 -2
  46. package/dist/config-catalog.js +16 -15
  47. package/dist/config-center/apply-effective.d.ts +59 -39
  48. package/dist/config-center/apply-effective.js +231 -120
  49. package/dist/config-center/apply-ledger.d.ts +45 -16
  50. package/dist/config-center/apply-ledger.js +28 -18
  51. package/dist/config-center/facade.d.ts +12 -6
  52. package/dist/config-center/facade.js +2 -2
  53. package/dist/config-center/hot-keys-registry.d.ts +11 -4
  54. package/dist/config-center/hot-keys-registry.js +11 -7
  55. package/dist/config-center/http-client.d.ts +4 -1
  56. package/dist/config-center/http-client.js +15 -10
  57. package/dist/config-center/read-warnings.d.ts +115 -6
  58. package/dist/config-center/read-warnings.js +115 -5
  59. package/dist/config-center/restart-signal.d.ts +21 -14
  60. package/dist/config-center/restart-signal.js +16 -22
  61. package/dist/config-center/types.d.ts +9 -3
  62. package/dist/config-invariants.d.ts +2 -2
  63. package/dist/config-invariants.js +15 -0
  64. package/dist/config-provider.d.ts +4 -0
  65. package/dist/config-provider.js +64 -9
  66. package/dist/config-types.d.ts +57 -13
  67. package/dist/config.js +87 -22
  68. package/dist/cross-session-settings.js +1 -1
  69. package/dist/deployment-governance.d.ts +45 -12
  70. package/dist/deployment-governance.js +37 -9
  71. package/dist/env-name-allowlist-knobs.d.ts +37 -0
  72. package/dist/env-name-allowlist-knobs.js +24 -0
  73. package/dist/fleet-client.d.ts +2 -1
  74. package/dist/hooks/hook-llm.js +13 -4
  75. package/dist/host-lsp-manager.d.ts +41 -0
  76. package/dist/host-lsp-manager.js +15 -0
  77. package/dist/http/active-run-conflict.d.ts +2 -12
  78. package/dist/http/active-run-conflict.js +1 -6
  79. package/dist/http/admission.js +30 -7
  80. package/dist/http/memory-store-refusal-reply.d.ts +24 -0
  81. package/dist/http/memory-store-refusal-reply.js +10 -0
  82. package/dist/http/resume-legs.d.ts +1 -1
  83. package/dist/http/resume-legs.js +14 -10
  84. package/dist/http/route-ctx.d.ts +2 -2
  85. package/dist/http/routes/approvals-assistant.d.ts +1 -1
  86. package/dist/http/routes/approvals-assistant.js +3 -2
  87. package/dist/http/routes/capabilities.js +3 -3
  88. package/dist/http/routes/memory-bundle.js +3 -0
  89. package/dist/http/routes/memory-compliance.js +3 -0
  90. package/dist/http/routes/memory-origin.js +3 -0
  91. package/dist/http/routes/sessions.js +7 -0
  92. package/dist/http/routes/side-query.js +10 -4
  93. package/dist/http/routes/tasks.js +7 -6
  94. package/dist/http/server.d.ts +6 -5
  95. package/dist/http/wire-types.d.ts +6 -3
  96. package/dist/instruction-source-notice.d.ts +97 -0
  97. package/dist/instruction-source-notice.js +78 -0
  98. package/dist/leader/wire.d.ts +20 -12
  99. package/dist/leader/wire.js +7 -6
  100. package/dist/memory-layer-legacy-lock.d.ts +14 -0
  101. package/dist/memory-layer-legacy-lock.js +74 -0
  102. package/dist/memory-store-refusal.d.ts +37 -0
  103. package/dist/memory-store-refusal.js +14 -0
  104. package/dist/model-entry-refusal.d.ts +130 -0
  105. package/dist/model-entry-refusal.js +157 -0
  106. package/dist/model-provider.d.ts +9 -3
  107. package/dist/model-route-endpoint.d.ts +1 -1
  108. package/dist/model-select.d.ts +49 -4
  109. package/dist/model-select.js +48 -17
  110. package/dist/observability/corrupt-read-seat.d.ts +21 -4
  111. package/dist/observability/corrupt-read-seat.js +31 -0
  112. package/dist/observability/fail-open.d.ts +16 -4
  113. package/dist/observability/fail-open.js +16 -4
  114. package/dist/observability/metrics.js +1 -0
  115. package/dist/observability/run-terminal.js +1 -0
  116. package/dist/observability/secret-env-scrub.d.ts +27 -3
  117. package/dist/observability/secret-env-scrub.js +22 -6
  118. package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
  119. package/dist/orchestration/workflow-completion-inbox.js +8 -3
  120. package/dist/plugins/approval-ask-store-file.d.ts +20 -2
  121. package/dist/plugins/approval-ask-store-file.js +21 -3
  122. package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
  123. package/dist/plugins/approval-ask-store-memory.js +39 -13
  124. package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
  125. package/dist/plugins/approval-ask-store-sql.js +27 -3
  126. package/dist/plugins/caching-session-store.d.ts +6 -0
  127. package/dist/plugins/caching-session-store.js +6 -0
  128. package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
  129. package/dist/plugins/checkpoint-store-sql.js +19 -2
  130. package/dist/plugins/local-checkpoint-store.js +2 -0
  131. package/dist/plugins/local-session-store.d.ts +20 -25
  132. package/dist/plugins/local-session-store.js +111 -64
  133. package/dist/plugins/mailbox-store-sql.d.ts +7 -5
  134. package/dist/plugins/mailbox-store-sql.js +6 -4
  135. package/dist/plugins/permission-rule-store-file.js +3 -2
  136. package/dist/plugins/pg-session-storage.d.ts +7 -0
  137. package/dist/plugins/pg-session-storage.js +19 -0
  138. package/dist/plugins/remote-env-host.d.ts +3 -1
  139. package/dist/plugins/remote-env-host.js +3 -6
  140. package/dist/plugins/sql-json-column.d.ts +16 -3
  141. package/dist/plugins/sql-json-column.js +15 -4
  142. package/dist/plugins/tidb-pool.js +5 -0
  143. package/dist/plugins/tidb-session-store.d.ts +13 -0
  144. package/dist/plugins/tidb-session-store.js +14 -0
  145. package/dist/plugins/web-search.d.ts +44 -23
  146. package/dist/plugins/web-search.js +161 -50
  147. package/dist/project-memory.d.ts +10 -0
  148. package/dist/project-memory.js +28 -14
  149. package/dist/run-local.d.ts +17 -15
  150. package/dist/run-local.js +119 -18
  151. package/dist/runs.js +6 -5
  152. package/dist/runtime-governance.d.ts +50 -3
  153. package/dist/runtime-governance.js +5 -0
  154. package/dist/sealed-key.d.ts +5 -2
  155. package/dist/server-secret-env.d.ts +26 -2
  156. package/dist/server-secret-env.js +9 -0
  157. package/dist/task-settings.d.ts +1 -1
  158. package/dist/tool-approval.d.ts +150 -28
  159. package/dist/tool-approval.js +150 -54
  160. package/dist/trace/core-keyset-guard.d.ts +30 -8
  161. package/dist/trace/engine-notice-wire.d.ts +27 -1
  162. package/dist/trace/engine-notice-wire.js +8 -0
  163. package/dist/trace/ledger-sink.js +2 -2
  164. package/dist/trace/project.d.ts +13 -0
  165. package/dist/trace/project.js +22 -5
  166. package/dist/trace/projection-drop.d.ts +2 -0
  167. package/dist/trace/projection-drop.js +6 -0
  168. package/dist/trace/sema-provenance.d.ts +4 -1
  169. package/dist/trace/sema-provenance.js +1 -1
  170. package/dist/trace/task-notification-facets.d.ts +65 -0
  171. package/dist/trace/task-notification-facets.js +31 -0
  172. package/dist/trace/wire-projection-faces.d.ts +3 -3
  173. package/dist/trace/wire-projection-faces.js +3 -3
  174. package/package.json +3 -3
package/MIGRATION.md CHANGED
@@ -7,13 +7,44 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.101.0 —— 十四条(`run-local` 执法三张审批名单 / 人停下的 run 退 1 / 模型目录 fail-closed 三条 + 类型面 `ServiceConfig.modelRefusals` · `ServiceConfig.lspEnvAllow` / 记忆抹除面 409 撤 + 存量只读位一次性 `chmod`(滚动窗内会被旧副本再锁,起服 warn 点名)/ settings 层 `crossSessionInbound: accept` 改答 hold / 子进程剥名收窄 / SQL `session_meta` 新列 / 沙箱名单形错拒启 + 空串值照注入 / `WEB_SEARCH_ENDPOINT` 带 userinfo 拒启 / 配置目录两行 `csv` → `secret` / 被拒名撞档位别名 ⇒ 模型面组拒;S-690 / S-694 / S-699 / S-704 / S-705 / S-712 / S-713 / S-716 / S-723)
11
+
12
+ - **`run-local` 上 `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` 生效**(改前:这条一次性 CLI 腿不读三张名单,名单工具照跑,只有启动日志一条 `tool_policy_only_sensitive_baseline` warn)。改后与 HTTP 腿同一只策略:`APPROVAL_DENY` 命中 ⇒ 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人(问句多一行这次调用的参数),否则当场拒并在 stderr 点名三张名单键(`APPROVAL_NEVER_AUTO` 不吃 `APPROVAL_AUTO_BUDGET` 预算)。**谁受伤**:无 TTY(CI)跑 `run-local`、而 `.env` 与服务共用并带着名单的脚本 —— 名单工具从「照跑」变「当场拒」。**迁移**:给 CLI 环境单独一份 `.env`(不带名单),或设 `APPROVAL_AUTO_BUDGET=<N>` 让 require 名单前 N 次自动批,或在终端里跑(TTY 问人)。`tool_policy_only_sensitive_baseline` 在 `run-local` 上不再出现(名单已执法)。
13
+ - **`run-local`:人拒掉一次工具调用而停下的 run 退 1**(改前退 0)。这类 run 的终局是 completed + `haltedOnUserRejection`(无 TTY 当场拒、TTY 答 N 都是这一形),意思是「停在等人指示」而不是「干完了活」;改后退 1(`--json` 形同样退 1),stdout 仍是模型停下前产出的文字(常为空);非 `--json` 形 stderr 多一行 `task stopped (haltedOnUserRejection): … continue it with --session <id>`。**谁受伤**:靠「`AUTONOMY=ask` / 名单被拒后仍退 0」继续往下跑的脚本。**迁移**:要把「被拒」当成功的脚本改看 stderr 停因行或 `--json` 输出的 `haltedOnUserRejection`。
14
+ - **本部署服务不了的目录条目不可用(S-704 ①,行为面)**:`provider:"anthropic"` 的目录条目在没配 `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` 的部署上不进可选目录(`GET /v1/models` 不列、不当缺省),显式选中 ⇒ 400 `request.model_unavailable`(拒因码 `config.model_no_brain`);此前它落网关 brain、带着条目自己的钥匙。不合 settings-schema `ModelEntry` 形的条目同律(`config.model_entry_invalid`;坏 `baseUrl` 此前被退回部署网关)。目录里每条都不可用 ⇒ 那一代模型面整拒(上一代继续服务)。**迁移**:配 anthropic 凭据,或把条目改成 `gateway` / `vllm` 并给它自己的 `baseUrl`;按 `sema_registry_model_no_brain` / `sema_registry_model_entry_invalid` 日志修条目。
15
+ - **一条坏条目只丢它自己(S-704 ②,行为面,所有目录型域)**:schema 逐条丢掉的条目不再让 refresh / 迟到 boot 整拒候选;其余条目照用,坏条目每条一行 `config_entry_dropped`(坐标 + 字段诊断)。**谁受伤**:依赖「一条坏条目冻结整份候选、上一代继续服务」的部署 —— 现在新一代(少那一条)生效。**迁移**:按日志坐标修那一条。
16
+ - **被拒条目的名字撞档位词 / CC 别名 ⇒ 这份候选的模型面整组被拒(S-712,行为面)**:目录里一条不可用(本机无 brain / 形坏被读腿丢掉)的条目恰好叫 `sonnet` / `opus` / `haiku` / `pro` / `flash` / `best` 之类,而档位表会铸出这个词 ⇒ 模型面停在上一代(码 `config.model_refused_name_minted`,warn `sema_registry_model_plane_refused` 点名「被拒名 → 目标条目」);此前(7.101.0 开发期)这个词被静默铸到另一条条目上,显式点它 / 绑它的角色跑在别人的端点与账号上。**迁移**:改名、修好那条条目,或改绑铸出这个词的档位绑定;多台 worker 共用一份中心目录、只有部分 worker 配了 anthropic 凭据(目录里 anthropic 条目叫 `opus` / `sonnet` / `haiku` 且激活了档位组)时,无凭据的 worker 上这一形**每一代**都会出现 —— 在中心按 worker 的 roster(`SEMA_REGISTRY_WORKER`)里把这些条目排出那些 worker,或给它们配凭据,或至少设 `MODEL_ID` 让它们起得来(DEBTS S-749)。没有字符串名的被丢行不触发本条(没有可保留的名字,与停用同处置)。同批:模型面三条不变量(缺省模型 / 路由端点 / 本条)不成立时只拒**模型面这一组**(目录,以及带模型引用、随它同代的 collab 模板 —— boot 按组回落盘上 LKG 时同样整组取 LKG 那一代),同候选的审批名单 / 限额照落(此前整份候选不落,boot 腿日志还写成 `config_source_unreachable_using_env`;拉取成功而应用失败现在是 `config_apply_refused`)。
17
+ - **`MODEL_ID` 不再是 loadConfig 期的硬要求(S-705)**:目录有可用条目即可省;目录应用之后仍没有缺省模型 ⇒ 拒启(`config.default_model_missing`,句首保留 `MODEL_ID is required`)。中心超出 `CONFIG_BOOT_FETCH_BUDGET_MS` 且无 LKG 时 boot 那一刻没有目录:没写 `MODEL_ID` 就拒启 —— 写一个 `MODEL_ID` 兜底或调大预算。
18
+ - **类型面**:`ServiceConfig.modelRefusals`(新必填席;手铸 `ServiceConfig` 的消费方 tsc 即红 —— env 形写 `{}`)与 `ServiceConfig.lspEnvAllow`(S-699 新必填席 `readonly string[]`,同样 tsc 即红 —— 缺省形写 `[]`);`SealedKeyPoisonReason` 删 `unsupported_alg`(形错的 sealed 密文现在让整条不可用,到不了毒丸臂);`FoldedReadWarning` 带 `scope` 判别位、`FetchEffectiveResult` 新键 `droppedEntries`。
19
+ - **记忆抹除面的 409 `memory.layer_unwritable` 撤销(S-697,随 core 7.31.0 #1076 提货;wire 码删除 + 行为面)**:`POST /v1/memory/erase`、`POST /v1/memory/origin/entries/:entryId/clear`、`POST /v1/sessions/:id/memory/erase` 不再在引擎之前做盘上可写预检,这个码不再出现(7.100.0 那条「按 409 体 `chmod u+w <dir>` 后重试」的指路随之撤)—— core 7.31.0 的挂载**一个模式位都不改**(只读层是会话策略:往会话不写的层里写在写点被拒,绕过工具写进去的字节永不被采纳)。7.100.0 上被 409 盖住的三种答复回到 core 自己的读数(非法选择子 400 `config.memory_erasure_request`、清标空理由 422 `memory.origin_clear_invalid`、已提交抹除同 `requestId` 重放的 200 收执),锁住的是别的层时抹除可写层条目照常 200。**升级(一次性)**:存量已锁目录在**最后一个 ≤7.30.x 会话结束后**(= 7.100.0 及更早的 server 副本全部停掉之后)执行 `chmod -R u+w <memoryDir>`(file 记忆引擎的 `<数据根>/memory`)—— 混跑期旧引擎每次只读挂载都会再锁一次,7.31 不替你解。**谁受伤**:没做这一步、又去删一条住在锁住目录里的条目的部署 ⇒ `500`(日志带 core 的同一条 `chmod -R u+w` 补救句),journal 留盘、下次起服失败,照补救句做完即自愈(`docs/DEPLOY-PREREQS.md` 同名段)。**滚动窗内这一形会反复出现**:同一数据根上还有 7.100.0 及更早的副本在跑时,它每次只读挂载一层都会把那一层再锁一次 —— 窗内做过的 `chmod` 会被撤回,7.101.0 副本在那一层上删条目照样 `500` + journal 卡盘。7.101.0 起服时对记忆挂载面探一次:有锁住的层 ⇒ 一条 warn(`engine_notice`,码 `memory.layer_locked_legacy`,`detail.layers` = 层名、`detail.remedy` = 同一句 `chmod -R u+w '<memoryDir>'`),**照常起服**(不拒启:窗内每次 boot 都可能刚被旧副本锁过);看到这一行就先别在那几层上抹除 / 清标,等旧副本全停后执行那一句。按码分支 `memory.layer_unwritable` 的消费方(sdk 错误码目录)删掉那一臂。
20
+ - **settings 层里残留的 `crossSessionInbound: "accept"` ⇒ 跨会话消息 hold(core 7.31.0 #1078,行为面)—— 本服务不受影响(只 env 层接线)**:本服务三层来源里只有 env 管理层 `CROSS_SESSION_INBOUND` 接线(user / repo 两层不读),而 env 写 `accept` 自 7.100.0 起就拒启 ⇒ 下面这一形只波及**自持 settings 层的嵌入方**。7.30.x 把这个退役词读成 `next-turn` 并每腿通告一次(7.100.0 本节那句「由引擎自己读成 `next-turn`」随之过期);7.31.0 起它与任何未知词同路 —— 该层进 `invalidLayers`、fail-closed 落 `hold`(每条跨会话消息以 `invalid-setting` 因由扣住,`peer.inbound_disposition` 披露 `held`)。env `CROSS_SESSION_INBOUND=accept` 自 7.100.0 起就拒启,不受影响;`@sema-agent/settings-schema` 6.0.0 的文件层本就拒这个词,所以只有绕过 schema 写进去的 settings 文件会撞上。**迁移**(仅嵌入方):把每一层的 `accept` 改写成 `next-turn`。
21
+ - **子进程剥名收窄(core 7.31.0 #1081,运行面;7.100.0 本节「子进程按名剥凭据形变量」一条的修正)**:十个预算名(`MAX_NEW_TOKENS` `MAX_COMPLETION_TOKENS` `AIDER_MAP_TOKENS` `AIDER_MAX_CHAT_HISTORY_TOKENS` `MAX_BATCH_PREFILL_TOKENS` `MAX_WAITING_TOKENS` `MAX_TOP_N_TOKENS` `RATE_LIMIT_TOKENS` `PROMPT_TOKENS` `CONTEXT_TOKENS`)与两个开关(`DB_FOREIGN_KEYS` `JSON_SORT_KEYS`)按**精确名**豁免,重新到得了工具子 shell、git 子进程与本地语言服务器;装饰过的变体(`USERS_MAX_NEW_TOKENS`、`MAX_NEW_TOKENS_2`)仍剥。为这五名改过名或用 `inheritEnv:[names]` 点名放行的部署可以撤回那一手(留着也无害)。
22
+ - **SQL 后端:`session_meta` 新列 `instruction_source`(S-690,存储面)**:存量 MySQL 协议 / PostgreSQL 库升级后**拒启**(`config.schema_mismatch`,拒因里逐字给出 `ALTER TABLE session_meta ADD COLUMN instruction_source VARCHAR(64) NULL;`,PG 为 `… VARCHAR(64) COLLATE "C";`),照它加列或删库重建;滚动序恒为「**先升库、再升副本**」(加列之后,旧副本重启会把它看成幽灵列而拒启)。`DB_BACKEND=local` 与内存形零动作。见上方「SQL 存储面 BREAKING」表。
23
+ - **沙箱名单形错拒启 + 空串值照注入(S-713,行为面)**:`E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 写了空段、尾逗号(`A,`)、带 `-` / `.` 的键名(`MY-TOKEN`)或点名名单型旋钮(自己 / 另一只沙箱名单 / `LSP_ENV_ALLOW`)⇒ **拒启**(拒句点名键与第几段,不回显原值;修前静默吞或照转发)。点到的键值为空串(compose `X=${X:-}` 在宿主没设时的常态)⇒ **照注入空串**(修前不注入):local-docker 上 `-e X=` 盖掉镜像里的同名缺省值,e2b 上盖掉 `SANDBOX_PKG_SOURCE` 推导出的同名包源变量。**迁移**:按拒句修名单;不想盖缺省值就把那个名从名单里删掉。
24
+ - **`WEB_SEARCH_ENDPOINT` 带 userinfo ⇒ 拒启(S-716,行为面)**:`WEB_SEARCH_PROVIDER` 点名了后端、`WEB_SEARCH_ENDPOINT` 形如 `https://user:pw@host/…`(或只带 `user@`)的部署升级后拒启(`config.web_search_invalid`);每请求 `settings.webSearch.endpoint` 带这一形从 202 变 `400 request.field_invalid`(修前这一形本来就用不了:fetch 每次都抛,错误文本还带出口令)。**迁移**:brave / tavily 的凭据走 `WEB_SEARCH_API_KEY`(或 `settings.webSearch.apiKey`);带 basic-auth 的 SearXNG 在前面放一层注入鉴权的反代。
25
+ - **配置目录两行 `csv` → `secret`(S-713,wire 形变)**:`GET /v1/config/catalog` 的 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 两行 `type` `csv` → `secret`、`valueClass` `plain` → `secret`、`effectiveValue` 从名字数组变 `{ present }`,`summary.secret` 计数 +2。读这两行名单内容的消费方改读起服行 `remote_exec_enabled.sandboxEnvKeys`(只键名);这两只旋钮的原文从此也不进 host shell / git 子进程 / 语言服务器(在这些子进程里读它们的脚本收不到了)。
26
+
27
+
28
+ ## 7.100.0 —— 十条 BREAKING(部署面 `CROSS_SESSION_INBOUND=accept` 拒启 / 类型面 `MailboxStore.ack` / WebSearch 配置形错响亮 / 请求面 `settings.webSearch` / WebSearch 报错句换形 / deny 部署的「无人可答」记部署政策 / 记忆抹除面新 409 / 审批名单热应用 / 子进程剥名 / local 车道回滚前收敛 ask 账本)
29
+
30
+ - **`CROSS_SESSION_INBOUND=accept` ⇒ 拒启**(core 7.30.0 把 `accept` 从 `crossSessionInbound` 闭集删掉,拆成 `wake`(立刻唤醒收件方)/ `next-turn`(排到收件方下一轮 = 旧 `accept` 的那一档);本仓词表直接取 core 常量,零别名 —— 拒启句列出新词表)。**迁移**:env 写 `next-turn`(原义)或 `wake`;`settings.json` 里的 `accept` 由引擎自己读成 `next-turn` 并通告一次,但 `@sema-agent/settings-schema` 6.0.0 的文件层对 `accept` 响亮拒 ⇒ 一并改写。本版精确钉 core 7.30.1 + settings-schema 6.0.0(锁步序:core → settings-schema → server → 宿主才可写 `wake`)。
31
+ - **`MailboxStore.ack` 返回 `Promise<{ acked: boolean }>`**(core 7.30.0):树外手铸的 `MailboxStore` 实现升级即 tsc 红 —— 围栏拒(盒不在 / 无人持租 / 非持租者)答 `{ acked: false }`,放行答 `{ acked: true }`(删 0 行也是 true)。本仓 SQL 两只孪生已改。
32
+ - **部署 env:WebSearch 旋钮形错 ⇒ 拒启**。`WEB_SEARCH_PROVIDER` 点名了后端时,`WEB_SEARCH_ENDPOINT`(不是 `scheme://host[:port][/path]` 形的绝对 URL)、`WEB_SEARCH_SEARXNG_PARAMS`(任一段不是 `name=value`、名不是参数名、同名两次、把数组 / 对象的 JSON 文本填进来)、`WEB_SEARCH_MAX_RESULTS` / `WEB_SEARCH_TIMEOUT_MS`(非数或 < 1,含 `30s` 这类带单位的)任一形错,进程起不来,stderr 首屏带键名与机读码 `config.web_search_invalid`。改前这些被静默吸收或落默认。**空串 / 未设不受影响;没配 `WEB_SEARCH_PROVIDER` 的部署不读这四键。** **迁移**:按拒启句把那一键改对或删掉。
33
+ - **每请求 `settings.webSearch` 形错 ⇒ `400 request.field_invalid`**(不分车道,请求面 BREAKING),错误句点名字段。**`provider` 缺席或不在词表里同样 400**(改前:整段丢弃、静默改用部署后端)—— 从 `settings.json` 带着拼错 provider(或只写了 `apiKey` / `endpoint`、没写 provider,或一个空的 `webSearch: {}`)的壳,修后提交即 400。**迁移**:按错误句改请求体(`provider` 写 `brave` / `tavily` / `searxng` 之一,不需要 per-request 搜索就整段删掉);`searxngParams` 用对象 `{ name: value }` 或串 `name=value;name=value`。
34
+ - **brave / tavily 非 2xx 报错句换形**:`<provider> search failed: HTTP <status> — <响应体摘录>`(改前 `<provider> search failed (<status>): …`)。按旧句形匹配的消费方要改。
35
+ - **`UNATTENDED_APPROVAL_POLICY=deny` 下「无人可答」记在部署政策名下(S-689,行为面;点名 cli)**:没人能答的受门调用,`tool_end.gate.settlement` 从 `{kind:"human_refused", who:{party:"person"}}` 变 `{kind:"policy_refused", who:{party:"none"}}`;模型读到引擎的策略句,run **不再**以 `haltedOnUserRejection:true` 收束、接着跑(auto 模式否决限回落那一问被拒时改为 `failed` / `classifier.denial_limit`);持久 ask 行当场落 DECIDED(deny) + `settled_by="policy"`,重入回放同一个政策拒。**谁受伤**:按 `haltedOnUserRejection` 或 `settlement.kind === "human_refused"` 判「用户拒了」的壳。**迁移**:改读 `policy_refused`;模型多跑的拍数由部署的 `maxTurns` / 墙钟兜顶。
36
+ - **记忆抹除面:三个既有端点新答 409 `memory.layer_unwritable`(S-693,行为面)**:`POST /v1/memory/erase`、`POST /v1/memory/origin/entries/:entryId/clear`、`POST /v1/sessions/:id/memory/erase`。锁住且有条目的记忆层在场时(默认路径 core 7.30.1 每次项目会话都会锁 `memory/local`),operator / 属主抹除与清标先答 409 `memory.layer_unwritable`(7.99.0:项目层条目 200、`local` 条目 500 + journal 卡盘、下次起服 fatal);同一门序让这种层在场时的非法选择子(400 `config.memory_erasure_request`)、清标空理由(422 `memory.origin_clear_invalid`)与已提交抹除同 `requestId` 重放的 200 收执也先答 409。挂载面里有指向树外目录的软链 ⇒ 未分类 500(判不了,日志点名软链)。**迁移**:按 409 体点名的目录 `chmod u+w <dir>` 后**用同一个 `requestId`** 重试(得到原答复);软链形把软链移出记忆层。core 7.31.0(#1076)提货版撤销本预检与这个码。
37
+ - **审批名单热应用(S-668 波 2,行为面)**:`governance.approvalRequire` 从「改了要重启」变热,`/health.restart.reasons` 与 `GET /v1/config/catalog` 的 `restartSlices` 不再有 `runtime-gates`(同版波 0:`models-tiers` 出、`memory-consolidation` 进 ⇒ 闭集 7 词)。三处行为变化:① 中心下发的名单形错(非数组 / `null` / 非字符串项)或拼法匹配不到 ⇒ 审批组**整组拒**:活配置零变化、候选不成为 LKG、每拍重判直到修好(改前只 warn、候选照样成为 LKG);没有 checkpoint 店的部署收到非空名单同样整组拒(改前重启后拒启 = 崩溃环);② 中心撤掉这一键 ⇒ 立刻回 env 底 `APPROVAL_REQUIRE`(改前保留到下次重启);③ 开了 leader 端点、有 checkpoint 店、只配 `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` 而 require 为空的部署,leader worker **开始执行**这两张表(改前一张都不执行;收紧向)。**迁移**:按 `sema_registry_approval_require_invalid` 日志修名单;依赖「leader 不执行 deny / never-auto」的部署核对名单。
38
+ - **回滚到 7.99.0(local 车道 + `UNATTENDED_APPROVAL_POLICY=deny`)要先收敛 / 压实 ask 账本(NP-9,持久数据面)**:7.100.0 起部署政策拒当场落 ask 行,File 形账本(`<数据根>/approval-asks/asks.jsonl`)把它记成新动词 `claimTerminalRefuse`。账本里只要还留着这种记录,7.99.0 **启动即拒启**并点名(「verb outside this build's closed set (a DOWNGRADE …)」)—— 不会静默把政策拒读成 VOID,但也起不来。**回滚步骤**:①停止接新任务,等所有 run 收敛(`GET /v1/approvals` 的 `pending` / `livePending` 都空,没有停驻或在飞的审批);②再二选一:让 7.100.0 继续跑到账本到阈压实(压实后账本只剩行快照,7.99.0 读得动,政策结算原样保留),或停机把 `asks.jsonl` 移开(已收敛的部署上只丢已决历史 —— 与 `docs/DEPLOY-PREREQS.md`「要清就停机删文件」同一动作);③再启 7.99.0。SQL 车道(`mysql` / `pg`)不受影响:政策结算是行上的列,7.99.0 原样读得动。
39
+ - **子进程按名剥凭据形变量(core 7.30.1 #1072,运行面)**:工具子 shell、git 子进程与本地语言服务器收不到名字以 `KEYS` / `TOKENS` / `PASSWORDS` 结尾的宿主变量;计数豁免只看紧挨结尾词的那一个词 ⇒ `MAX_NEW_TOKENS` / `MAX_COMPLETION_TOKENS` / `MAX_BATCH_PREFILL_TOKENS` / `DB_FOREIGN_KEYS` / `JSON_SORT_KEYS` 这五个 7.30.0 不剥的非密钥名也被剥(core 收紧过头,归 core 修)。**迁移**:host shell 用 `inheritEnv:[names]` 点名放行,或给变量改个不以这三形结尾的名字;规则全文见 `docs/DEPLOY-PREREQS.md`。
40
+
10
41
  ## 7.99.0 —— 三条 BREAKING(行为面 / 类型面 / 运行时下限)
11
42
 
12
43
  - **显式签发方不再算托管证据(7.99.0,S-671 / B-10;按 clay 裁定「显式签发方不算托管证据」)**:`PRINCIPAL_JWT_*` / `AUTH_BRIDGE_ISSUER` 在场而 `REQUIRE_PRINCIPAL` 未设的部署,7.95–7.98 拒启,7.99.0 起服;托管 ⇔ 运维显式声明 `REQUIRE_PRINCIPAL=true`。**谁受伤**:靠这条推断顶着没设 `REQUIRE_PRINCIPAL` 的多租机器,升级后不再被拦(召回缺口如实登记)。**迁移**:多租机器自己设 `REQUIRE_PRINCIPAL=true`(+ `OPERATOR_PRINCIPALS`),见 `docs/DEPLOY-PREREQS.md` 托管形前置。
13
44
  - **`ServiceConfig.configCenter.token` 删除,换成 `credential`(7.99.0,S-651 波 A-1,类型面)**:凭证改为一只每次请求取值的活对象;新旋钮 `SEMA_REGISTRY_TOKEN_FILE`(与 `SEMA_REGISTRY_TOKEN` 二选一,都设 ⇒ 拒启)。手铸 `ServiceConfig` 的消费方 tsc 即红。**盘上数据**:旧 LKG 文件(`config-lkg-<worker>.json`,formatVersion 1)按凭证身份分箱后一次性迁移 —— 此刻凭证是该 worker 的拉取令牌且新箱无文件 ⇒ 迁进新箱、旧文件改名 `.migrated-v1`;其它形响亮拒一行(点名旧路径与出路);迁移代码到期删除(S-683,10-31)。旧 prompt 活动状态目录暂不迁移(S-685)。
14
45
  - **Node 运行时下限改成实测值 `^20.19.0 || >=22.12.0`(7.99.0)**:此前 `>=20` 是假话 —— 7.98.0 在 Node 20.0–20.18 与 22.0–22.11 上本就起不来(依赖 e2b 的 CJS 入口撞 chalk@5 纯 ESM,S-682);开了 engine-strict 的安装在范围外会失败。**迁移**:Node 升到 20.19+ 或 22.12+。
15
46
 
16
- ## SQL 存储面 BREAKING(3.0.0 之后的三个窗)
47
+ ## SQL 存储面 BREAKING(3.0.0 之后;列增批自 7.101.0 起入表)
17
48
 
18
49
  **常设口径**:本仓**不出 `ALTER TABLE` 增量迁移**(成文裁定:预生产期零存量用户窗口,schema 变更
19
50
  一律删表/删库重建);boot 对旧形 schema **拒启并带恢复动作文案**,不会静默跑在错形表上。只用
@@ -24,6 +55,9 @@ file/local 存储形(未配 SQL 后端)的部署不受本节任何条目影响
24
55
  | **7.6.0**(2026-08-08) | SQL 双端归一化第一刀:隔离键排序规则钉死(MySQL 腿表级 `COLLATE utf8mb4_bin`/PG 腿逐列 `COLLATE "C"`)+索引名 36 条+列名/宽度收窄 | **删库重建**(两方言) |
25
56
  | **7.8.0**(2026-08-09) | SQL 命名三轴归一化第二刀:9 张表名单数化、epoch 毫秒列补 `_ms` 后缀 5 列、approval 两表 `version→rev`;wire 面零变化 | **删库重建**(两方言) |
26
57
  | **7.14.0**(2026-08-12) | `tool_result` 换代(随 core 5.26.0 的 ref 格式换代):ref 四段单射形、主键 `VARCHAR(190)→518`、新增出处两列 `owner_session_id`/`owner_task_id` | 升级前两方言 **`DROP TABLE tool_result`**(不删=boot 拒启;offload 产物是可恢复窗缓存,转录内联预览不受影响) |
58
+ | **7.101.0** | `session_meta` 新列 `instruction_source VARCHAR(64) NULL`(PG `COLLATE "C"`;S-690 项目指令换源通告的基线) | 两方言按拒启句里给出的 `ALTER TABLE session_meta ADD COLUMN …` 加列,或删库重建;**先升库、再升副本**(加列后旧副本重启见幽灵列拒启) |
59
+
60
+ > 列增批此前只记在 CHANGELOG / `docs/DEPLOY-PREREQS.md`(7.92.0 `session_policy`、7.93.4 `approval_ask.settled_by` 各加过一列),自 7.101.0 起同样入本表。7.101.0 这一列的拒启句自带可粘贴的 `ALTER`,本仓仍不替你执行。
27
61
 
28
62
  ## server 3.0.0 —— BREAKING 四条(2026-07-31)
29
63
 
package/README.md CHANGED
@@ -140,6 +140,17 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
140
140
  resume from. A gated `ask` (e.g. `AUTONOMY=ask`, which routes every shell command through approval)
141
141
  is answered inline: a `y/N` prompt when stdin is a TTY, otherwise a fail-closed **deny** with a
142
142
  stderr line naming the knob that produced the gate.
143
+ - **The three approval lists are enforced here too** (since 7.101.0 — before that they were silently
144
+ inert on this leg): `APPROVAL_DENY` refuses outright; `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` ask
145
+ through that same inline answer (TTY `y/N`, otherwise deny with the list keys named on stderr);
146
+ `APPROVAL_AUTO_BUDGET` auto-approves the first N `APPROVAL_REQUIRE` calls (never-auto tools always ask).
147
+ A CI script that shares a server's `.env` should get its own `.env`, or run in a terminal.
148
+ - **The `y/N` prompt shows the call's full arguments** (JSON, terminal-escaped; a call whose arguments cannot
149
+ be displayed is refused instead of asked), and one terminal answers one question at a time — concurrent asks
150
+ from the main task and its sub-agents queue behind each other.
151
+ - **A run stopped by a refusal exits 1** (since 7.101.0; it used to exit 0), `--json` included: whatever the
152
+ model wrote before the refusal still goes to stdout, and without `--json` stderr names the stop
153
+ (`task stopped (haltedOnUserRejection) …`).
143
154
  - **Full-stack, one command** (DB + object store + registry web + sandbox pool; Docker and k8s
144
155
  paths): [`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy).
145
156
  - **Sandbox package sources**: default = official upstreams (pypi/npmjs/crates.io/…). For
@@ -156,7 +167,7 @@ The server is configured entirely through environment variables. The most import
156
167
  | `PORT` | `8090` | HTTP listen port |
157
168
  | `BIND_HOST` (alias `HOST`) | see note | Listen address. An explicit `BIND_HOST` **always wins**. Default: `127.0.0.1` when the write face is unauthenticated (`ALLOW_UNAUTHED_WRITES=true` **and** no service token configured), otherwise all interfaces — deployments with a token are unaffected. Since 3.15.0 the `HOST` alias is **not** fully equivalent: shells commonly set `HOST` to the machine name without the operator knowing, so when the narrowing condition above holds it wins over an inherited `HOST` (logged as `bind_host_from_HOST_env_overridden`). Set `BIND_HOST` explicitly, or configure a service token, to expose the write face. |
158
169
  | `MODEL_GATEWAY_BASEURL` | **no default** | OpenAI-compatible gateway base URL (without `/chat/completions`). **No factory default** (S-623): every route that rides the openai-compatible lane must resolve to an explicit endpoint — the model's own catalog `baseUrl`, or this knob — else the server refuses to boot, naming the models and printing the explicit local spelling (`MODEL_GATEWAY_BASEURL=http://127.0.0.1:8000/v1`). An Anthropic-only deployment may leave it unset. Setting `MODEL_GATEWAY_FALLBACK_URLS` without it is refused too |
159
- | `MODEL_ID` | **required** | Default model id — **no factory default since 3.0.0**. Unset ⇒ the server refuses to boot with a message naming the knob (the old baked-in default was an internal-only model name, so every external deployment failed later and further from the cause: a gateway `400` plus a cascade of title-hook warnings). Set it to whatever model name your gateway serves, or supply the catalog via the config-center control plane |
170
+ | `MODEL_ID` | **required unless a catalog supplies the default** | Default model id — **no factory default since 3.0.0**. Since 7.101.0 (S-705) the check runs **after** the catalog is applied: `MODEL_ID` **or** a usable catalog default (`config.d/models.json` / the config center) is enough; with neither the server refuses to boot (`config.default_model_missing`, naming both ways out). The old baked-in default was an internal-only model name, so every external deployment failed later and further from the cause: a gateway `400` plus a cascade of title-hook warnings |
160
171
  | `MODEL_API_KEY` | — | Gateway API key (optional). Pairs only with `MODEL_GATEWAY_BASEURL`: with no gateway it is not loaded and sent nowhere |
161
172
  | `SERVICE_AUTH_TOKEN` | — | Callers must send `Authorization: Bearer <token>` |
162
173
  | `DB_BACKEND` | `local`* | `mysql` (any MySQL-protocol DB: MySQL/TiDB/MariaDB) / `pg` (PostgreSQL) / `local` (file-backed, no DB) / `memory` (explicit in-memory: nothing survives a restart, durable-runs faces 501). *Bare boot (no DB env at all) defaults to `local` so a single-user machine keeps its runs across restarts; any SQL signal (`SESSION_BACKEND` or `MYSQL_/PG_HOST`) keeps the `mysql` engine default, and `REQUIRE_PRINCIPAL=true` bare boots stay `memory`. (`TIDB_HOST`/`_PORT`/`_USER`/`_PASSWORD`/`_DATABASE`/`_POOL_SIZE` are retired names, not an alias — since 5.0.0 setting any of them refuses to boot, pointing at the `MYSQL_*` replacement; TiDB itself connects fine through `MYSQL_*`, since it speaks the MySQL protocol.) (the local file store has no tenant isolation — a warning says so). A DEFAULT-derived `local` that cannot create its data root degrades to memory with a warning + the `store_backend_degraded` gauge; an EXPLICIT `DB_BACKEND=local` fails loud instead. Setting `mysql`/`pg` explicitly also switches sessions to durable |
package/README.zh-CN.md CHANGED
@@ -121,6 +121,14 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
121
121
  - **这条腿没有 durable 审批 park**:一次性 CLI 没有 `/v1/approvals/:id/decide` 可赎回。被门住的 `ask`
122
122
  (例如 `AUTONOMY=ask`,它把每条 shell 命令都送进审批链)当场结算:stdin 是 TTY 就 `y/N` 问人,
123
123
  否则 fail-closed **拒绝**并在 stderr 点名是哪个旋钮产的这道门。
124
+ - **三张审批名单在这条腿上同样执法**(7.101.0 起;此前在本腿静默不生效):`APPROVAL_DENY` 当场拒;
125
+ `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 走上面那条当场结算(TTY `y/N`,否则拒并在 stderr 点名名单键);
126
+ `APPROVAL_AUTO_BUDGET` 让前 N 次 `APPROVAL_REQUIRE` 自动批(never-auto 工具恒问人)。与服务共用 `.env`
127
+ 的 CI 脚本请给它单独一份 `.env`,或在终端里跑。
128
+ - **`y/N` 问句带上这次调用的完整参数**(JSON、终端安全化;参数展示不出的调用不问、当场拒);一个终端一次只答
129
+ 一问 —— 主任务与子代的并发询问依次排队。
130
+ - **因拒答而停下的 run 退 1**(7.101.0 起,`--json` 也一样;此前退 0):模型在拒答前写的文字照打 stdout,
131
+ 非 `--json` 形 stderr 一行点名停因(`task stopped (haltedOnUserRejection) …`)。
124
132
  - **一键全栈部署**(DB + 对象存储 + registry 网站 + 沙箱池,docker/k8s 双路径):
125
133
  [`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy)。
126
134
  - **沙箱装包源**:缺省 = 官方源(pypi/npmjs/crates.io/…)。中国大陆部署配
@@ -136,7 +144,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
136
144
  | `PORT` | `8090` | HTTP 监听端口 |
137
145
  | `BIND_HOST`(别名 `HOST`) | 见说明 | 监听地址。显式 `BIND_HOST` **恒生效**。缺省:写面无鉴权时(`ALLOW_UNAUTHED_WRITES=true` **且**未配任何 service token)= `127.0.0.1`,否则全接口——配了 token 的部署不受影响。自 3.15.0 起 `HOST` 别名**不再完全等效**:shell 常把 `HOST` 设成机器名而 operator 并不知情,于是上述收窄条件成立时,收窄压过继承来的 `HOST`(告警事件 `bind_host_from_HOST_env_overridden`)。要暴露写面,显式设 `BIND_HOST`,或配一个 service token。 |
138
146
  | `MODEL_GATEWAY_BASEURL` | **无缺省** | OpenAI 兼容网关地址(不带 `/chat/completions`)。**无出厂缺省**(S-623):每一条落 openai 兼容腿的路由都必须解析到显式端点 —— 目录条目自己的 `baseUrl`,或本键 —— 否则拒启,拒因点名模型并给出本机写法(`MODEL_GATEWAY_BASEURL=http://127.0.0.1:8000/v1`)。anthropic 单路由部署可不设。只设 `MODEL_GATEWAY_FALLBACK_URLS` 不设本键同拒 |
139
- | `MODEL_ID` | **必填** | 缺省模型 id ——**3.0.0 起无出厂缺省**。未设 = 启动即失败并指路该旋钮(旧的烤死缺省是内网模型名,外部部署必炸且炸在离根因最远处:网关 `400` + 标题 hook 连环告警)。填你的网关真正提供的模型名,或改由配置控制面下发目录 |
147
+ | `MODEL_ID` | **必填(目录给出缺省时可省)** | 缺省模型 id ——**3.0.0 起无出厂缺省**。7.101.0 起(S-705)判据挪到目录应用**之后**:`MODEL_ID` **或**目录里一条可用的缺省(`config.d/models.json` / 配置控制面)任一在场即可;两者皆无 = 拒启(码 `config.default_model_missing`,指路二选一)。旧的烤死缺省是内网模型名,外部部署必炸且炸在离根因最远处:网关 `400` + 标题 hook 连环告警 |
140
148
  | `MODEL_API_KEY` | — | 网关 key(可选)。只与 `MODEL_GATEWAY_BASEURL` 配对:没设网关时不装载、不发往任何路由 |
141
149
  | `SERVICE_AUTH_TOKEN` | — | 调用方需带 `Authorization: Bearer <token>` |
142
150
  | `DB_BACKEND` | `local`* | SQL 引擎:`mysql`(任何 MySQL 协议库:MySQL/TiDB/MariaDB —— TiDB 走这个值,**没有** `tidb` 别名,写它启动即拒)/ `pg`(PostgreSQL)/ `local`(免 DB 文件持久化)/ `memory`(显式纯内存)。显式设置 `mysql`/`pg` 时 session 自动转 durable。*单租户裸 boot 缺省 `local`;`REQUIRE_PRINCIPAL=true` 的多租户裸 boot 缺省 `memory`(local 与多租户互斥) |
package/USAGE.md CHANGED
@@ -61,7 +61,16 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
61
61
  (码 `config.model_route_endpoint_missing`)点名解析不到的模型并给出本机写法 `MODEL_GATEWAY_BASEURL=http://127.0.0.1:8000/v1`。
62
62
  只设 `MODEL_GATEWAY_FALLBACK_URLS` 不设主网关同拒(`config.gateway_fallback_without_primary`)。只走 Anthropic 路由的部署
63
63
  不设网关合法;此时 `MODEL_API_KEY`(网关的凭据)不装载、不发往任何路由(启动通知 `model_gateway_key_unpaired`)。
64
- 中心 / config.d 下发的候选目录里有解析不到的条目 ⇒ 候选整批拒、上一代继续服务。
64
+ 中心 / config.d 下发的候选目录里有解析不到的条目 ⇒ 候选的模型面这一组被拒、上一代模型面继续服务(同候选其余组照落)。
65
+ - 🔴 **目录里的 anthropic 条目要本机真有 anthropic 路由**(S-704,7.101.0 起):`provider:"anthropic"` 的目录条目只在本服务配了
66
+ `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` 时可用;没配 ⇒ 该条目**不进可选目录**(`GET /v1/models` 不列、不当缺省,缺省顺延到下一条
67
+ 可用条目),启动 / 刷新日志一行 `sema_registry_model_no_brain`(带码 `config.model_no_brain` 与出路),显式点它(`body.model` /
68
+ `@名` / `settings.model` / `compactionModel` / side-query)⇒ 400 `request.model_unavailable`。**7.100.0 及以前**这种条目会落到网关
69
+ brain、带着**它自己的** `apiKeyEnv` 钥匙打到网关。不合 settings-schema 形的条目同律(码 `config.model_entry_invalid`,如非 http(s)
70
+ 的 `baseUrl` —— 此前被「退回部署网关」;读腿逐条丢掉的条目同样留名字,点它照样 400);目录里**每条**都不可用 ⇒ 那一代模型面这一组被拒。
71
+ 🔴 **被拒条目的名字不许被档位 / 别名铸到别的条目上**(S-712):不可用条目恰好叫 `sonnet` / `opus` / `pro` / `best` 这类档位词或 CC 别名、
72
+ 而档位表会铸出这个词时,这份候选的模型面整组被拒(码 `config.model_refused_name_minted`,warn `sema_registry_model_plane_refused` 点名
73
+ 「被拒名 → 目标条目」)—— 否则显式点它、或绑它的角色,会静默跑在另一条条目的端点与账号上;出路:改名 / 修好条目,或改绑那条档位绑定。
65
74
  - **failover ≠ Anthropic↔vLLM**:failover 给所有 brain 发**同一个 model**(同协议同 id 的冗余);云↔本地是**按 `model.provider` 路由**的选择,不是故障转移(两者 model id/参数不同,不能透明互切)。设 `MODEL_PROVIDER=anthropic` + `MODEL_ID=claude-…` 让整个服务走 Anthropic。
66
75
  - **缺省推断**:`MODEL_PROVIDER` **未设**、但显式配了 Anthropic 协议 base URL(`ANTHROPIC_BASE_URL`)**和**对应凭证(`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)时,缺省自动判 `anthropic` 并打一行启动 warn(`model_provider_inferred`)——纯净机直连 Anthropic 兼容上游不再需要显式第七个键。显式 `MODEL_PROVIDER` 恒赢;只给 base URL 或只给凭证不推断;两者皆无 = `gateway`,与以前逐字节一致。
67
76
  - 启动日志的 `brain` 字段会回显当前组合(`failover` / `anthropicRoute` / `anthropicCacheBreakpoints`)。
@@ -89,30 +98,39 @@ ANTHROPIC_MAX_RETRIES=10 # 云 Anthropic 腿
89
98
  ```bash
90
99
  WEB_SEARCH_PROVIDER=brave|tavily|searxng # 唯一的"装配开关":合法词才挂 WebSearch 工具;缺席/非法词 = 不装配(不是挂了报错)
91
100
  WEB_SEARCH_API_KEY=… # brave/tavily 必需(searxng 不读这一键);只进 backend 闭包,永不进模型 prompt 或工具参数
92
- WEB_SEARCH_ENDPOINT=https://searx.example # searxng 必需(实例地址);brave/tavily 下是可选的 base-URL 覆盖(代理/测试)
93
- WEB_SEARCH_MAX_RESULTS=10 # 1..20(夹逼);缺席/非正数/非数字 → 用 backend 默认 10;小数向下取整
94
- WEB_SEARCH_TIMEOUT_MS=10000 # 单次搜索墙钟(ms),下限 1000;缺席/非正数/非数字 → 用 backend 默认 10000
95
- WEB_SEARCH_SEARXNG_PARAMS="engines=bing,duckduckgo;language=zh-CN" # 仅 searxng 腿消费;`;` 分隔 k=v 对;一个都解析不出 → 键整个不铸(不产出空对象)
101
+ WEB_SEARCH_ENDPOINT=https://searx.example # searxng 必需(实例地址);brave/tavily 下是可选的 base-URL 覆盖(代理/测试/**Brave 兼容网关**)
102
+ # Brave 兼容网关(S-669,2026-09-24 实跑):自托管的 Nólë 网关暴露与 Brave 同形的 `GET …/res/v1/web/search`,
103
+ # 认 `X-Subscription-Token`,返回 `web.results[{title,url,description,…}]` —— 用 brave 词 + endpoint 覆写即可,零新词:
104
+ # WEB_SEARCH_PROVIDER=brave
105
+ # WEB_SEARCH_ENDPOINT=https://<gateway-host>:<port>/res/v1/web/search # 完整搜索 URL(不是 base)
106
+ # WEB_SEARCH_API_KEY=<网关给本部署的独立钥匙> # 从部署密钥存放处注入,不进仓、不进日志
107
+ # `GET /v1/capabilities` 的 `webSearch.backend` 仍报 `"brave"`(闭集不变);网关侧按钥匙限速 / 吊销 / 记 client。
108
+ WEB_SEARCH_MAX_RESULTS=10 # ≥1 的数,>20 夹到 20,小数向下取整;缺席/空 → backend 默认 10;非数字 / <1 → 拒启
109
+ WEB_SEARCH_TIMEOUT_MS=10000 # 单次搜索墙钟(ms),<1000 抬到 1000;缺席/空 → backend 默认 10000;非数字(含 "30s" 这类带单位的)/ <1 → 拒启
110
+ WEB_SEARCH_SEARXNG_PARAMS="engines=bing,duckduckgo;language=zh-CN" # 仅 searxng 腿消费;`;` 分隔 name=value;空 → 不铸;任一段不成形 → 拒启
96
111
  WEB_SEARCH_PROBE_ON_BOOT=true # boot 期一次性真出网探活,默认 OFF;只认 "true"/"1"(trim+小写后比较),含糊值当没开
97
112
  ```
98
113
 
99
- 七键逐一(`src/plugins/web-search.ts` `webSearchConfigFromEnv` §294-309;类型/默认/坏值登记见
100
- `src/config-catalog.ts:654-662`):
114
+ 七键逐一(属主 `src/plugins/web-search.ts` `webSearchConfigFromEnv`,每个字段一只判官、与每请求 `settings.webSearch`
115
+ 共用;类型/默认/坏值登记见 `src/config-catalog.ts` 的 `WEB_SEARCH_*` 行)。**只有 `WEB_SEARCH_PROVIDER` 点名了后端时其余
116
+ 旋钮才被读**——没配后端时它们不装配、也不判:
101
117
 
102
118
  | env 键 | 类型 | 默认 | 缺席行为 | 坏值行为 |
103
119
  |---|---|---|---|---|
104
120
  | `WEB_SEARCH_PROVIDER` | 闭集(`brave`\|`tavily`\|`searxng`) | 无 | WebSearch 工具整体不装配(功能缺席,无 warn) | 三词之外的任何值 = 同缺席处理,不装配、不 warn(`webSearchConfigFromEnv` 的词表守卫 `isWebSearchProvider`)。**读面(S-382 起)**:`GET /v1/capabilities` 的 `webSearch.backend` 把这一格的生效值广告成闭集词(缺席/坏值都报 `"none"` —— 两者行为本就相同);端点/密钥/配额**不上 wire**,那些仍只在 operator 面 `GET /v1/config/catalog` |
105
- | `WEB_SEARCH_API_KEY` | secret string | 无 | brave/tavily:后端仍会装配(装配只看 `PROVIDER`),**首次真实工具调用**时抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`(`braveSearch` / `tavilySearch` 的首行守卫);searxng:本键无消费点 | 空字符串同缺席(`env.WEB_SEARCH_API_KEY ?` 只认真值,`webSearchConfigFromEnv` 的条件展开) |
106
- | `WEB_SEARCH_ENDPOINT` | url string | brave/tavily → 各自官方 API;searxng → 无默认 | brave/tavily:落官方 endpoint;searxng:**首次真实工具调用**时抛 `WEB_SEARCH_ENDPOINT (the SearXNG instance URL) is required for the searxng provider`(`searxngSearch` 的首行守卫) | 不做 URL 形校验——写不成 URL 由 `new URL()` 抛出,同样落到"首次调用才现形"那条路径,不拒启 |
107
- | `WEB_SEARCH_MAX_RESULTS` | number | `10` | 用默认 10 | 非数字/≤0 → 回落默认 10;是数字则向下取整;最终值再夹在 `[1,20]`(设 999 也被 clamp 到 20 —— `createWebSearchBackend` 里的 `maxResults` clamp) |
108
- | `WEB_SEARCH_TIMEOUT_MS` | number(ms) | `10000` | 用默认 10000 | 非数字/≤0 → 回落默认;最终值下限夹到 1000ms(`createWebSearchBackend` 里的 `Math.max(1000, …)`) |
109
- | `WEB_SEARCH_SEARXNG_PARAMS` | string(`k=v;k=v`) | 无(不铸键 = 不传 `extraParams`,交给 core adapter 自己的缺省) | 键整个不铸 | 一个 `k=v` 对都解析不出(没有 `=`,或 `k`/`v` 任一为空)→ 同缺席,整串忽略,不拒启不 warn(`parseSearxngParams`);env 侧值恒为字符串,不会触发下面 per-request 那个"数组被误当对象吸收"的边角(见 §9.5) |
121
+ | `WEB_SEARCH_API_KEY` | secret string | 无 | brave/tavily:后端仍会装配(装配只看 `PROVIDER`),**首次真实工具调用**时抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`(`braveSearch` / `tavilySearch` 的首行守卫);searxng:本键无消费点 | 空串 / 纯空白同缺席;内容不设形(它是凭据) |
122
+ | `WEB_SEARCH_ENDPOINT` | url string(brave 下 = 完整搜索 URL,可指向 Brave 兼容网关如 Nólë) | brave/tavily → 各自官方 API;searxng → 无默认 | brave/tavily:落官方 endpoint;searxng:**首次真实工具调用**时抛 `WEB_SEARCH_ENDPOINT (the SearXNG instance URL) is required for the searxng provider`(`searxngSearch` 的首行守卫) | 空串同缺席(compose 模板 `${WEB_SEARCH_ENDPOINT:-}` 铸的就是空串)。**不是 `scheme://host[:port][/path]` 形的绝对 URL ⇒ 拒启**(`searx.internal`、`localhost:8888` 这类少了 `scheme://` 的形都算;判据与模型路由 base URL 拒启同一只)。**带 userinfo ⇒ 拒启**(7.101.0 起,S-716:`https://user:pw@host/…`、只带用户名的 `https://user@host/…` 都算;判据 = WHATWG 解析后用户名或口令任一非空,与运行时 fetch 拒收含凭据 URL 的约束同一条,所以修前这一形起得来、每次搜索必抛;brave / tavily 的凭据走 `WEB_SEARCH_API_KEY`,searxng 腿没有凭据通道,带鉴权的实例需在前面放一层注入鉴权的反代)。拒启句带键名与机读码 `config.web_search_invalid`,**不回显值**(这一格可能带凭据) |
123
+ | `WEB_SEARCH_MAX_RESULTS` | number | `10` | 用默认 10 | 空串同缺席;**非数字或 < 1 ⇒ 拒启**(`config.web_search_invalid`;改前静默落默认 10);是数则向下取整,再夹到 `[1,20]`(设 999 被夹到 20 —— 夹紧不是形错) |
124
+ | `WEB_SEARCH_TIMEOUT_MS` | number(ms) | `10000` | 用默认 10000 | 空串同缺席;**非数字(`"30s"` 这类带单位的也算)或 < 1 ⇒ 拒启**(`config.web_search_invalid`;改前静默落默认 10000 —— 写了 30s 的运维实际拿到 10s);是数则向下取整,再抬到 ≥ 1000ms |
125
+ | `WEB_SEARCH_SEARXNG_PARAMS` | string(`k=v;k=v`) | 无(不铸键 = 不传 `extraParams`,交给 core adapter 自己的缺省) | 键整个不铸 | 空串 / 纯空白 / 只有分号 = 缺席。文法与每请求 `settings.webSearch.searxngParams` 同一套:`name=value` 用 `;` 分隔(尾分号可有可无),名是参数名(字母 / 数字 / `_` / `-` / `.`),值非空,同名只许一次。**任一段不成形 ⇒ 拒启**(`config.web_search_invalid`,句里只报第几项 `entry #N`,**不回显名或值** —— 7.101.0 起,S-716;改前句里回显参数名):把数组或对象的 JSON 文本(`["engines=bing"]`、`{"engines":"bing"}`)填进来、一段没有 `=`、名为空、同名两次。改前这些被静默跳过或吸成垃圾参数发给实例 |
110
126
  | `WEB_SEARCH_PROBE_ON_BOOT` | boolean(仅认 `"true"`/`"1"`) | `false`(OFF) | 不探活 | trim+小写后不等于 `"true"`/`"1"` 的任何拼法(含 `"yes"`/`"on"`)一律当 `false`;探活失败(网络不通/后端拒绝)只 `warn`(`web_search_probe_failed`),**不拒启**——功能型能力缺席走降级,不是保护型旋钮(`main.ts:1028-1034`) |
111
127
 
112
- **结论:七键无一在 boot 期拒启。** 与本仓其它安全轴旋钮(`MODEL_DEGRADE_ON`、思考档三键等,拼错即拒启)
113
- 姿势不同——WebSearch 挂不挂是**功能面**取舍,不是安全边界,七键统一走"回落/降级",不是"fail-closed 拒启"。
114
- 真正的配置错误(缺 key / endpoint 错)只在**首次真实工具调用**时才现形,以 tool_result 错误文本形式回给
115
- 模型,不是 HTTP 层错误、不影响 boot、也不使任务整体 `failed`(细节见 §9.5)。
128
+ **结论:缺席与形错分两件事。** 缺席(未设 / 空串,以及 `WEB_SEARCH_PROVIDER` 不是三词之一)一律走「不装配 / 用默认」,
129
+ 不 warn —— WebSearch 挂不挂是**功能面**取舍,不是安全边界。**形错**(写了但写不成该有的形:`WEB_SEARCH_ENDPOINT`、
130
+ `WEB_SEARCH_SEARXNG_PARAMS`、`WEB_SEARCH_MAX_RESULTS`、`WEB_SEARCH_TIMEOUT_MS` 四键)**拒启**,stderr 首屏一句带键名 +
131
+ 机读码 `config.web_search_invalid`:形错此前被静默吸收(垃圾参数发给实例)或静默落到一个运维没选过的默认值,
132
+ 与「配对了」在外面同形。仍只在**首次真实工具调用**时现形的只剩「形对但不通」一类(缺 key、地址对但连不上),
133
+ 以 tool_result 错误文本回给模型,不是 HTTP 层错误、不使任务整体 `failed`(细节见 §9.5)。
116
134
  - 结果是**不可信输入**:core 会 `delimitUntrusted` 围栏并**重新施加** `allowed_domains`/`blocked_domains`
117
135
  地板,所以 backend 遵不遵守 `opts` 是优化不是正确性要求 —— 换 provider(包括换成自建 SearXNG)
118
136
  不会削弱域名地板。
@@ -272,7 +290,7 @@ OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer xxx # 逗号分隔的 k=v
272
290
  # 便宜模型分层(同网关、换 id):council 的 6 个 lens / 上下文压缩走便宜档,主任务与仲裁走主模型。
273
291
  MODEL_ID=<your-model-name> MODEL_CHEAP_ID=<your-cheap-model-name>
274
292
  ```
275
- - 调用方可在 `objective` 里 `@<模型名>` 选模型,**只认已配置的名单**(`MODEL_ID` / `MODEL_CHEAP_ID`)——注入不了 `baseUrl`/`apiKey`;无 `@` → 默认模型。
293
+ - 调用方可在 `objective` 里 `@<模型名>` 选模型,**只认已配置的名单**(`MODEL_ID` / `MODEL_CHEAP_ID`,或目录条目)——注入不了 `baseUrl`/`apiKey`;无 `@` → 默认模型;`@` 点中一条已发布但本部署不可用的目录条目 ⇒ 400 `request.model_unavailable`(S-704)。
276
294
  - **`GET /v1/models`**(带 Bearer)→ `{models:[{name,id,provider,reasoning,vision}],default}`,只含名字/能力、**不含密钥** —— 消费方(如 OA)拿它做 `@` 自动补全下拉的数据源。
277
295
  - 不设 `MODEL_CHEAP_ID` → 所有角色 = 主模型(行为不变)。
278
296
  - **1M 双窗**(`autoCompactTokens`):`967000` 只发给 **claude-sonnet-5 系 id ∧ 窗 ≥1M**(CC 的 per-model 值,不是窗宽几何)。其他 ≥1M 模型要双窗触发点请显式配 —— 主槽 `MODEL_AUTO_COMPACT_TOKENS=<tokens>`、cheap 槽 `MODEL_CHEAP_AUTO_COMPACT_TOKENS=<tokens>`(**两槽各配各的**:主槽的值按主模型 id/窗声明,不会外溢到 cheap)。不配 → core 按整窗的 `W-33000` 平几何触发,boot 期发 `auto_compact_window_not_derived_1m` warn 点名槽位(`slot=main|cheap`)。⚠️ cheap 槽不设 `MODEL_CHEAP_CONTEXT_WINDOW` 时**继承主模型的窗**,主模型是 1M 则 cheap 也按 1M 计。取值范围:**必须低于该槽的物理窗**(CC 的形是 `窗-33000`)—— 高于物理窗时 **core 在取数处把触发窗夹回物理窗**(`resolveTriggerWindow`,自 core 5.60.0 起),即**这根旋钮变成 no-op**、几何回落成没配它的样子,并由引擎逐任务铸一条 `config.autocompact_window_clamped` 通告(受众 operator)。server 侧**不夹也不拒**(夹紧归 core 的取数处,一处一次),但会在 **boot** 就发 `auto_compact_tokens_above_context_window` warn 点名槽位、越窗值与 **`clampedTo`(core 会夹到的那个数)**—— 不必先派一条任务才知道旋钮白配了。⚠️ 7.57.0 之前这一段写的是「autocompact 事实上关掉、改由破坏性 guard 砍历史」,那是 core 5.60.0 之前的行为,已订正。
@@ -493,7 +511,10 @@ SEND_USER_FILE_SANDBOX_PUT_ENDPOINT=… # 可选:沙箱直传 PUT 的端
493
511
  SEMA_REGISTRY_URL=http://<config-center-host>:3100 # 启动拉 GET /api/config/effective(Bearer+ETag),覆盖 env 兜底
494
512
  SEMA_REGISTRY_TOKEN=<SERVICE_PULL_TOKEN 的值> # 取自配置控制面主机 .env;只读拉取令牌
495
513
  # SEMA_REGISTRY_TOKEN_FILE=/path/to/token # 或:凭证文件(与上一行二选一,都设=拒启)。每条中心请求都重读 ⇒ 轮换 / 换账号改写文件即可,
496
- # 不重启;文件不可读 = error 级 center_credential_unreadable,不当作无凭证。
514
+ # 中心请求面不重启;文件不可读 = error 级 center_credential_unreadable,不当作无凭证。
515
+ # ⚠️ 换账号(凭证身份变化)后,只在启动期物化的面 —— 技能 / MCP / A2A / 场景 —— 仍是上一个
516
+ # 身份的,直到重启(`/health` 报 restartRequired,reasons 含 skills);热面(models / roles /
517
+ # limits / 审批名单 …)在新身份第一份候选到达那一拍换代。「换账号即降档 + 当场拉取」候 S-667(不在 7.100.0)。
497
518
  # 用户 JWT 形(CLI 登录)⇒ skill 正文 / prompt 产物走 /api/v1/me/…;盘上 LKG 与 prompt 状态按「worker + 凭证身份」分箱。
498
519
  SEMA_REGISTRY_DRY_RUN=true # 安全灰度:只 LOG 中心配置 vs env 推导的差异,不 apply
499
520
  SEMA_REGISTRY_WORKER=<worker名> # 可选:拉取 /effective?worker=<名> 取该 worker 的 roster(reconciler 按 worker 注);不设=全局 roster(向后兼容)
@@ -502,6 +523,13 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启 flee
502
523
  # (此前静默每拍 announce 400,worker 永不注册)。
503
524
  ```
504
525
  - 中心**空/未发布** → `applyEffective` 回落 env + 内建 teams 并 warn `config_center_unpublished`,**不影响在跑的服务**(接了也安全)。
526
+ - **逐条容错**(S-704 ②,7.101.0 起):目录里一条条目不合形(如 `contextWindow:"abc"`)⇒ **只丢那一条**(warn `config_entry_dropped`,
527
+ 点名坐标 `models.<下标>` 与字段诊断),其余条目照用;refresh 拍 / 迟到的 boot 拉取**不再因此整拒候选**(此前一条坏条目让整份目录停在
528
+ 上一代 —— 上一代若还是 env,env 模型就一直服务)。颗粒更粗的失效(整个域读不懂、拼错键、坏文件)仍整拒候选、保 LKG。
529
+ - **没有 `MODEL_ID` 也能起**(S-705,7.101.0 起):目录(`config.d/models.json` 或中心)有可用条目时 env 可以不写 `MODEL_ID` —— 缺省模型取
530
+ 目录缺省。判据在目录应用**之后**:env `MODEL_ID` 或目录缺省任一在场即过;两者皆无 ⇒ 拒启(码 `config.default_model_missing`,指路二选一)。
531
+ 中心拉取超出 `CONFIG_BOOT_FETCH_BUDGET_MS` 又没有 LKG 时,boot 那一刻没有目录 ⇒ 没有 `MODEL_ID` 兜底就拒启(调大预算让 boot 等目录,
532
+ 或写一个 `MODEL_ID` 兜底)。
505
533
  - **灰度姿势**(配置控制面 AI 建议):先 `SEMA_REGISTRY_DRY_RUN=true` 起一轮,看日志 `sema_registry_dry_run`(中心给的 models/roles/teams + 会否覆盖 default、per-model apiKeyEnv)对得上 env 再去掉该 flag 真正 apply。
506
534
  - 拉取**只读、只取逻辑配置**(模型名册/角色/团队);密钥/网关仍在本服务 env(中心只发 env-**名** 引用,不发密钥值)。回滚=去掉 `SEMA_REGISTRY_URL` 即纯 env。
507
535
  - **配置热更新真表(7.38+ 现行)**——refresh 拍(60s 轮询,或 `POST /v1/admin/config/refresh` 手动触发,见 API 表)对各域的生效方式:
@@ -510,9 +538,14 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启 flee
510
538
  |---|---|---|
511
539
  | models / roles / default 模型 | **热**(下一 refresh 拍) | 全 boot Runner **原子换代**(swap 失败=候选整拒,活配置零触碰);此前「改 models 需重启」的时代已随 Runner swap 腿落地终结 |
512
540
  | pricing / keys(env-名引用)/ prompts / teams | **热** | 值换代即生效;cost 族限额座(rate limit / cost quota)同热(限额=纯比较参数,窗内累计不动;窗长换代=记账周期重开) |
513
- | **tier 变更**(models-tiers plane 与 tier-frozen 基线不一致) | **defer 到重启**(唯一 defer 臂) | 候选 durable 落地(LKG,`CONFIG_LKG_DURABLE`)后 `/health` 报 `restartRequired`;无 durable handoff ⇒ `/health` 报 `modelPlaneDeferred{version,since,blockedReasons}` 候运维(不强制重启,plane 保持未应用) |
541
+ | tier 变更(档位组 / 档位绑定) | **热**(下一 refresh 拍) | 与 models 同一条换代腿:tier 表随模型面在 commit 前原子换进全部 boot Runner,下一任务按新档位路由,`/health` 不报 `restartRequired`(7.100.0 起 `models-tiers` 重启理由退役——它在生产上从未发出过) |
542
+ | plugins(插件引用) | 重启生效 | 插件只在启动期物化;改了之后 `/health` 报 `restartRequired`,`restart.reasons` 含 `skills`(7.100.0 前这一改动**不报**,要等一次无关的重启) |
543
+ | skills / mcp / a2a / scenarios(技能 / MCP / A2A / 场景) | 重启生效 | 只在启动期物化;中心改了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 各含其名。**换账号**(凭证身份变化)同理:这些面仍是上一身份的直到重启(候 S-667) |
544
+ | 审批名单(`governance.approvalRequire`) | **热**(下一 refresh 拍) | 7.100.0 起:下一个任务调名单里的工具即停车等审批;撤键回 env 底(`APPROVAL_REQUIRE`)。**没有 checkpoint 店**的部署(`DURABLE_APPROVAL` 关 / 后端无 checkpoint 面)发非空名单 ⇒ 审批组**整组拒**(旧名单继续服务、`configAppliedVersion` 停旧代、拒因见日志 `sema_registry_approval_require_invalid` 与诊断端点 `configApply.groups.approvalGate`)—— 与启动期「名单已配却无 durable 门 ⇒ 拒启」同一条判据。此前 `/health` 报 `restartRequired`(`runtime-gates`,该理由退役) |
545
+ | 记忆整理驱动席(仅 `MEMORY_CONSOLIDATION_DRIVER=on`) | 重启生效 | 整理跑用的模型在启动期解析一次;中心改了 roles / 档位 / 该条目录后 `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;此前不报) |
514
546
 
515
547
  生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
548
+ - **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,拒答而停下的 run 退 1。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
516
549
  - **仅 `/v1/tasks`(同步)+ `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多次尝试非单流,400)。与 `verify` **互斥**(同时给 → 400)。
517
550
  - 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `discuss`(广度交叉)而非级联(深度阶梯)。
518
551
 
@@ -657,10 +690,21 @@ UNATTENDED_APPROVAL_POLICY=park
657
690
  没有 park 设施的部署由引擎 fail-closed 拒绝并告诉模型「没有可停靠的 durable 审批门」。
658
691
  - `deny` ⇒ **真无人值守/headless 部署的显式声明**:这类 ask 当场拒绝,让模型自己改道,不积压一堆
659
692
  等不到人的挂起 run。它是**收紧**方向(不放行任何东西)。
693
+ 这一拒记在**部署政策**名下,不记在人名下(7.100.0 起):那次调用的 `tool_end.gate.settlement` 是
694
+ `{kind:"policy_refused", who:{party:"none"}}`;模型读到的是「部署政策拒了、此处没有交互审批,别重试,
695
+ 先做不需要审批的部分」,run 接着跑(auto 模式下分类器连拒后回落的那一问被拒时,引擎按「无人可判」停下
696
+ run:终局 `failed` / `classifier.denial_limit`)。此前记成 `human_refused` / `who.party:"person"`,模型被告知
697
+ 「用户不想继续,停下来等他」,run 以 `haltedOnUserRejection` 收束 —— 而这台部署自己声明了没人会来。
698
+ 少数情形记的是 `approval_window_expired`,同样不在人名下:活卡已送达、本服务自己的窗走完没人点,**且**没有持久 ask 店(或 ask 店
699
+ 当时报错、结束等待的只能说是本服务的窗)。有 ask 店时窗走完、终局 claim 赢下的那一支记的是 `policy_refused`(行上同落 `settled_by`)。
700
+ 店抖动兜底(持久行不可用 / 落行身份不定)与崩溃恢复(收敛器代打崩溃遗留的过期 ask)同判(7.101.0 起):同样记
701
+ `policy_refused`,行上同落政策结算(证不出是本次插下的行不当场动,由收敛器在窗过 + 宽限后落),同一只 ask 重入
702
+ 回放同一个政策拒(此前这三形分别是 durable park、durable park、重入记成 `human_refused`)。
660
703
  - **7.34.0 的行为变化(park 侧)**:此前「窗走完没人答」是**当场拒绝 + 一条 error 回模型**(模型往往就
661
704
  绕开了那次治理);现在它与其余「无人可答」的情形一样走 park。要恢复旧结局请显式配 `deny`。
662
705
  - 纯部署级:**不看**客户端的任何表态/权限模式。协调器整体关掉(`TOOL_APPROVAL_ENABLED` 关)时这个旋钮
663
- 没有施加对象;`APPROVAL_REQUIRE` 名单里的工具走的是 durable 审批门,不受它影响。
706
+ 没有施加对象。`APPROVAL_REQUIRE` 名单只决定「这只工具要不要审批」,与本旋钮无关;协调器在场时,名单工具的
707
+ 审批与其余 ask 走同一条路、同一个政策(`deny` 部署上同样当场拒)。
664
708
  - 🔴 **与 `STREAM_APPROVAL_ENABLED` 正交**:把流内协议开关关掉**不会**回滚 R-13 —— 它只是不发
665
709
  `approval_request`、不落 ask 行、不起收敛器腿,活卡窗到期照样走 park(park 的承载是引擎的 checkpoint,
666
710
  不是 server 的 ask 行,「有 durable 设施但没开协议」正是 R-13 要救的那类部署)。要回到旧的「窗满即拒」
@@ -1033,6 +1077,56 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1033
1077
  🪦 `LSP_ENABLED=false`(拆分前唯一的 host 腿逃生舱)自 3.0.0 起是墓碑:拒启并指路 `LSP_HOST_ENABLED`。
1034
1078
  `LSP_ENABLED=true` 不受影响——那是沙箱腿自己的 opt-in,语义没变。
1035
1079
 
1080
+ **host 腿语言服务器拿到的环境 + 点名放行 `LSP_ENV_ALLOW`(7.101.0 起)**:host 腿的语言服务器是本机子进程,它拿到的环境
1081
+ = 服务进程环境**剥掉凭据形的键**(与模型可驱动的 host shell 同一只剥除:配置目录 `secret` 型键按名剥 ∘ 引擎按键名形状剥
1082
+ `*_KEY` / `*_TOKEN` / `*_SECRET` / `*_PASSWORD` …),`PATH` / `HOME` / locale 这类照常在。代价是语言服务器自己要用的令牌也被
1083
+ 剥:`.npmrc` 里 `${NPM_TOKEN}` 插值的私有 registry(TS 自动装类型)、rust-analyzer `cargo metadata` 走私有 registry 的
1084
+ `CARGO_REGISTRIES_<名>_TOKEN` —— 症状是补全 / 诊断静默变差。剥了哪些键,起服时一行 `warn` 告诉你(见下),出路是点名放行:
1085
+
1086
+ | 键 | 缺省 | 写法 | 行为 |
1087
+ |---|---|---|---|
1088
+ | `LSP_ENV_ALLOW` | 未设 = 空名单 = 全剥(7.100.0 行为) | 逗号分隔的**裸变量名**,如 `LSP_ENV_ALLOW=NPM_TOKEN,CARGO_REGISTRIES_MY_REG_TOKEN` | 点到的键从服务进程环境**原样**交给语言服务器(密钥形也照交 —— 点名即你显式承担把这个值交给仓里代码的后果);没点到的照旧剥,`SERVICE_AUTH_TOKENS` 这类不会因为别的键被放行而跟着进去。空串 = 未设。**形错拒启**:空段(`A,,B`、尾逗号)、非变量名形(`1BAD`、带空格、`NAME=value` 整段贴进来)⇒ 起服失败,拒句点名键与第几段,**不回显原值**。形对但本进程环境里**没有**这个名的段 ⇒ 不放行,起服一行 `config_env_entry_rejected` 只报第几段(常见成因是 `LSP_ENV_ALLOW=$NPM_TOKEN` 这种把**值**展开进来的手滑 —— `npm_` 令牌恰好是合法变量名形,所以名单原文在任何日志 / 配置目录回显里都不出现)。旋钮**原文**按凭据类对待:配置目录只回在场位,`LSP_ENV_ALLOW` 这个键本身不进语言服务器 / host shell / git 子进程(点名它自己,或点名两只沙箱名单 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` ⇒ 拒启);生效名单看起服那行的 `allowed`。`REQUIRE_PRINCIPAL=true`(多租户 / 托管)⇒ 忽略名单、仍全剥,起服一行 `config_env_set_but_ineffective`(host 腿语言服务器在这一姿态下本来就不起)。只作用于 host 腿;沙箱腿的语言服务器跑在沙箱里,环境由沙箱车道自己的旋钮决定 |
1089
+
1090
+ **「剥了什么」这一行**:起服(host 腿语言服务器管理器构造时)一行 `warn` `secret_env_scrubbed`,`site:"server.lsp.spawn-env"`,
1091
+ 字段 `keys`(这次剥掉的键名)· `allowed`(当前放行名单)· `hint`(出路句,点名 `LSP_ENV_ALLOW=NAME,NAME`)· `summary`;**值永不进日志**。
1092
+ 放回的键不算剥除(不在 `keys` 里、不进计数 `secret_env_scrubbed_total`);同一个键每进程只报一次;没有剥除 ⇒ 不发。
1093
+ 另两条剥除现场(模型可驱动的 host shell、project-memory 的 git 子进程)没有部署级放行旋钮,它们的同名行照旧是 `info`、不带 `allowed` / `hint`。
1094
+
1095
+ **形状规则的精确名豁免(7.101.0 起,core 7.31.0 #1081)**:十个预算名(`MAX_NEW_TOKENS` `MAX_COMPLETION_TOKENS` `AIDER_MAP_TOKENS`
1096
+ `AIDER_MAX_CHAT_HISTORY_TOKENS` `MAX_BATCH_PREFILL_TOKENS` `MAX_WAITING_TOKENS` `MAX_TOP_N_TOKENS` `RATE_LIMIT_TOKENS` `PROMPT_TOKENS`
1097
+ `CONTEXT_TOKENS`)与两个开关(`DB_FOREIGN_KEYS` `JSON_SORT_KEYS`)不再被剥,三条子进程腿同时生效(7.100.0 上这五个 7.30.1 误剥的名
1098
+ `MAX_NEW_TOKENS` / `MAX_COMPLETION_TOKENS` / `MAX_BATCH_PREFILL_TOKENS` / `DB_FOREIGN_KEYS` / `JSON_SORT_KEYS` 需要点名放行,现在不用)。
1099
+ **精确名,不是形**:`USERS_MAX_NEW_TOKENS`、`MAX_NEW_TOKENS_2` 这类装饰过的变体照剥。引擎自己的「剥了什么」通告码
1100
+ `config.secret_env_scrubbed` 只在引擎**自己**剥的子进程上发;本服务三条腿都交本服务剥好的环境,所以运维面看的是上面这行
1101
+ `secret_env_scrubbed`,不是那只通告码。
1102
+
1103
+ **「点名放行」只有一种意思**:本服务里凡是「一张名单点到哪些环境变量键」的地方(这里的 `LSP_ENV_ALLOW`、沙箱车道的
1104
+ `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV`(见下表),与嵌入方构造 host shell 时的 `inheritEnv:[names]`)用的是同一只谓词 ——
1105
+ 点到的键从服务进程环境原样交出去(空串值照交),密钥形也照交;源里没有的名什么也不产生。三只名单型旋钮(`LSP_ENV_ALLOW` /
1106
+ `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV`)另走同一只名单解析:形错拒启点名第几段、不回显原值,点名任一只名单型旋钮拒启,
1107
+ 本进程环境里没有的名按序号一行 `config_env_entry_rejected`,旋钮原文按凭据类对待(配置目录只回在场位、不进任何子进程)。
1108
+ 几处只在**没点到的键**上不同:语言服务器在缺省剥除的结果之上放回点名键(没点到的照剥,非密钥键照常在);host shell 的
1109
+ `inheritEnv:[names]` 与两只沙箱名单是 hermetic 的(没点到的一律不交,与引擎 `NodeExecutionEnv` 同名选项同义)。
1110
+ 两处都不向对方看齐:host shell 改成「剥后放回」等于在模型可驱动的 shell 上放宽,语言服务器改成 hermetic 会让它当场找不到自己的工具链。
1111
+
1112
+ **沙箱带外 env 名单(`E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV`,7.101.0 / S-713 起同上一段的解析与谓词)**:沙箱里的工具要用本服务
1113
+ 进程环境里的凭据(如 git 走 `$GIT_TOKEN`),又不想让它进提示词、命令串或事件日志,就把**名字**写进名单,值只在服务进程环境里:
1114
+
1115
+ | 键 | 车道 | 写法 | 行为 |
1116
+ |---|---|---|---|
1117
+ | `E2B_SANDBOX_ENV` | `REMOTE_EXEC=e2b` | 逗号分隔的**裸变量名**,如 `E2B_SANDBOX_ENV=GIT_TOKEN,NPM_TOKEN` | 点到的键从服务进程环境**原样**注入沙箱(e2b 沙箱级 env;与 `SANDBOX_PKG_SOURCE` 推导出的包源变量同名时名单赢) |
1118
+ | `DOCKER_SANDBOX_ENV` | `REMOTE_EXEC=local-docker` | 同上 | 点到的键以 `docker exec -e K=V` 注入每条命令(与单条命令自带的 env 同名时,命令自带的赢) |
1119
+
1120
+ 两只旋钮只在**自己的车道被选中时**才求值(别的车道上它们是无关配置,不校验、不告警)。未设 / 空串 = 不注入。**形错拒启**:空段
1121
+ (`A,,B`、尾逗号)、非变量名形(`1BAD`、带空格、`NAME=value` 整段贴进来)、点名名单型旋钮(`E2B_SANDBOX_ENV=E2B_SANDBOX_ENV`,
1122
+ 或 `LSP_ENV_ALLOW` / 另一只沙箱名单)⇒ 起服失败,拒句点名键与第几段,**不回显原值**。形对但服务进程环境里**没有**这个名 ⇒ 不注入,
1123
+ 起服一行 `config_env_entry_rejected` 只报第几段。点到的键值为空串 ⇒ 照样注入一个空串(7.101.0 前跳过,见 CHANGELOG)。
1124
+ `REQUIRE_PRINCIPAL=true` 下照常注入(与 `LSP_ENV_ALLOW` 不同:沙箱 env 是部署方给隔离沙箱显式配的)。
1125
+ **起服可见面(7.101.0 / S-715)**:两条车道都在 `remote_exec_enabled` 行带 `sandboxEnvKeys` —— 名单点到、且服务进程环境里真有(因而真被转发)的键名,字典序,只键名不带值
1126
+ (名单没配或点到的名全不在 ⇒ 不带该字段;e2b 腿不含 `SANDBOX_PKG_SOURCE` 推导出的包源变量);`REQUIRE_PRINCIPAL=true` 下另有一行 warn
1127
+ `config_env_forwarded_multi_tenant`(`env` = 旋钮名、`entries` = 名单段数、`note` = 姿态句:这张 worker 级名单进每个租户的沙箱),
1128
+ 不回显名单原文;单用户部署不出这一行。
1129
+
1036
1130
  **`CONFIG_REQUIRE_ROSTER=true`(3.1.0,可选,缺省关)**:远程 registry 非 dryRun 车道的 fleet worker
1037
1131
  可布防「roster 落地前拒接计费提交(503)」——3.0.0 起 MODEL_ID 必填,每个 worker 都带 boot 模型起服,
1038
1132
  旧的「无 env 模型即等 roster」推断失效;要那个保护语义现在需显式声明。
@@ -1055,7 +1149,7 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1055
1149
 
1056
1150
  > ⚠️ **Bearer 是"每个请求",不只是 POST。** 配了 `SERVICE_AUTH_TOKEN` 后,**`GET /v1/runs/:id`、`GET /v1/runs/:id/events`(SSE)、`/metrics`** 等所有非 `/health` 路由都要带 `Authorization: Bearer`——漏带一律 `401`。下面示例为简洁**省略了 Bearer**,真实调用请逐个补上。
1057
1151
 
1058
- **请求体字段(你能传的全部):**
1152
+ **请求体字段(常用;受理**闭集**以 `docs/ASSISTANT-WIRE-CONTRACT.md` §1 为准 —— 本表不再重抄一份,S-710):**
1059
1153
  | 字段 | 必填 | 说明 |
1060
1154
  |---|---|---|
1061
1155
  | `objective` | ✅ | 要做什么(自然语言)。**唯一必填。** |
@@ -1064,7 +1158,8 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1064
1158
  | `attachmentIds` | — | D-1 通用文件上传(1.289+):先 `POST /v1/attachments?name=…`(raw body,content-type=mime)拿句柄,提交时引用 ≤16 个;文件物化到执行环境工作目录 `attachments/` 下,objective 尾部自动追加文件清单(内容不进会话流)。单文件缺省 ≤32 MiB(`ATTACHMENT_MAX_BYTES`);可配 mime 白名单(`ATTACHMENT_MIME_ALLOWLIST` CSV,缺省不限);上传后未引用的按 `ATTACHMENT_UNBOUND_TTL_MS`(缺省 24h)回收。**云形态(tidb/pg)字节本体存对象存储——MinIO 必配**(`MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY`,与快照 lane 同一组变量),未配则附件面 501;local 形走本地文件店。 |
1065
1159
  | `scenario` | — | 省略时取本部署的 `DEFAULT_SCENARIO`(工厂缺省场景名 = **`code`**——CC 编码 persona 蒸馏版,default 全量工具面,终验旋钮独立;`default` 场景**已非出厂缺省**,要用它须显式传 `scenario:"default"`,或部署方显式设 `DEFAULT_SCENARIO=default`)。可选内建名:`default`(通用/助理场景,域中性 persona,CC agent loop 全量工具面)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review`/`discuss` 同属 clone-free 无执行环境场景,见 §5 末)/ **配置控制面可声明任意新场景**(`{name, toolset: none\|repo-readonly, prompt?}`,组合即配置、能力写死在部署;restart-to-apply;center 可覆盖内建名,boot 日志 `config_center_scenarios.shadowsBuiltin` 可审计) |
1066
1160
  | `repo` / `council` / `debate` | — | `repo` 为 `code-review`/`scan` 必填;`council`/`debate` 仅 `code-review`,见 §5 |
1067
- | ~~model / tools / prompt~~ | 🚫 | **不接受**——服务端注入 |
1161
+ | `systemPrompt` / `appendSystemPrompt` | — | 调用方自带的系统提示 / 追加段(各 ≤16384 字符,超限 400 `request.field_invalid`;`appendSystemPrompt` 必须非空串)—— 任务自带时优先于角色 / 全局缺省(见 §模型角色) |
1162
+ | ~~model / tools~~ | 🚫 | **不接受**——服务端注入(模型走目录 / 角色,工具走场景装配;按请求只能**减**:`excludeTools` / `settings.permissions`) |
1068
1163
 
1069
1164
  **`TaskResult`(同步 / `done` 事件里拿到的;7.64.0 起终局是**一条** `terminal` 因由,不再有 `status`/`errorCode`/`errorMessage`/`blockedReason` 四个平面键):**
1070
1165
  ```json
@@ -1605,7 +1700,7 @@ armed ⟺ 本次请求 permissionMode 是 "auto" ∧ 本部署装配了分类
1605
1700
  | 旋钮 | 缺省 | 语义 |
1606
1701
  |---|---|---|
1607
1702
  | `PERMISSIONS_DISABLE_AUTO_MODE` | `false`(opt-in) | CC/center/settings 键名 `permissions.disableAutoMode` 的 **env 腿**(config catalog 登记 center 键名 `permissions.disableAutoMode`,settings-schema 现无 `permissions` 域 ⇒ 目录行 `domainExists:false`):**tighten-only** 棘轮 —— `true` ⇒ 本部署每个 principal 的 `caps.autoMode` 折成 `false`(center 授予 `true` **翻不回**);未设 ⇒ 不动 caps。布尔词表(`true/false/1/0/yes/no/on/off`),词表外的值**拒启**(安全轴旋钮不许静默回默认)。目录行见 `GET /v1/config/catalog`(approval 域,security 轴) |
1608
- | `CROSS_SESSION_INBOUND` | 未设 | 跨终端会话设计线:CC settings 键名 `crossSessionInbound` 的**部署/组织层**(引擎层形的 `managed` 层,CC `policySettings` 对位)—— 本部署对**入站跨会话消息**的治理三态:`accept` 投递 / `hold` 停在收件会话的待审队列(模型看不见、不能据此行动)/ `refuse` 本部署整体退出这条车道。🔴 **未设 ≠ `accept`**:未设 = 这一层不表态,由其余层与引擎的 mode-parity 判定决定。词表外的值**拒启**。⚠️ **生效前提**:引擎的 peer 目录席(`RunnerDeps.peerDirectory`)在场——本版**未接**该席,故本旋钮今天「就位待命」不生效(目录席到货那天零改动即生效);目录行见 `GET /v1/config/catalog`(approval 域,security 轴) |
1703
+ | `CROSS_SESSION_INBOUND` | 未设 | 跨终端会话设计线:CC settings 键名 `crossSessionInbound` 的**部署/组织层**(引擎层形的 `managed` 层,CC `policySettings` 对位)—— 本部署对**入站跨会话消息**的治理四态(词表取自引擎闭集,core 7.30.0 起):`wake` 投递且**宿主**可在收件箱增长时起一个 turn / `next-turn` 在下一个 turn 边界投递 / `hold` 停在收件会话的待审队列(模型看不见、不能据此行动)/ `refuse` 本部署整体退出这条车道。⚠️ 本服务宿主**不起 turn**,所以在本服务上 `wake` 按引擎契约的降级臂读作 `next-turn`。🔴 **旧词 `accept` 自 7.100.0(core 7.30.0 提货)起拒启** —— 它的原义就是 `next-turn`,改成那个词即可(零别名,拒启即通告)。🔴 **未设 ≠ `next-turn`**:未设 = 这一层不表态,由其余层与引擎的 mode-parity 判定决定。词表外的值**拒启**。⚠️ **生效前提**:引擎的 peer 目录席(`RunnerDeps.peerDirectory`)在场 —— 7.91.0 起已接线(会话枚举面 ∧ mailbox 店 ∧ 收件人生命周期面三合取),缺任一合取项的部署上本旋钮仍「就位待命」;目录行见 `GET /v1/config/catalog`(approval 域,security 轴) |
1609
1704
  | `CROSS_SESSION_DIALOG_EXPIRY` | 未设(⇒ 引擎缺省 `5m`) | 同族:CC settings 键名 `dialogExpiry` 的部署层 —— 被 hold 的跨会话消息等待人审多久后按**安全缺省**结算(过期丢弃,**带回执**告知发送方,不静默吞)。词表 `60s`/`5m`/`10m`/`never`(`never` = 不设期限);未设 ⇒ 本仓**不铸键**,由引擎落它自己的缺省(不复制上游缺省值)。词表外拒启。⚠️ 生效前提同上。另注:CC 的同名键还有第二个消费面(转发到远端客户端的审批对话框停靠时长),本旋钮**今天不驱动**那一面 |
1610
1705
 
1611
1706
  **自查读面**(`GET /v1/capabilities.permissionModeAuto`,壳的 `sema doctor permissions` 消费;下例第一行是
@@ -1660,20 +1755,22 @@ GET /v1/capabilities?permissionMode=default
1660
1755
  是唯二两条配置来源。当 per-request 那条腿"生效"(见下面的受理门槛)时,它产出一个**全新、完全独立**的
1661
1756
  `WebSearchBackendConfig`,**整只替换**掉 env 配出来的 backend —— 不是把 per-request 给的字段一个个覆盖到
1662
1757
  env 的 config 上,env 的其余字段(尤其是 `WEB_SEARCH_TIMEOUT_MS`)**不会**被继承到 per-request 那次调用里
1663
- (`src/capabilities/scenarios.ts:264-266`):
1758
+ (`src/capabilities/scenarios.ts` 的 `requestWebSearchBackend`,判决来自 `src/plugins/web-search.ts` 的
1759
+ `judgeRequestWebSearch`):
1664
1760
 
1665
1761
  ```
1666
- const reqWebSearch = deps.requirePrincipal !== true
1667
- ? webSearchConfigFromSettings(req.settings?.webSearch)
1668
- : undefined;
1669
- const webSearch = reqWebSearch ? createWebSearchBackend(reqWebSearch) : deps.webSearch;
1762
+ judgeRequestWebSearch(req.settings?.webSearch) // 单用户车道才问;多租车道恒答 absent
1763
+ absent ⇒ 部署后端(env 那只;env 也没配 ⇒ 工具不装配)
1764
+ honored ⇒ 这次调用用 per-request 那只(整段整取)
1765
+ malformed ⇒ 新鲜提交已在受理面 400(含 provider 缺席 / 词表外);只有续跑重放会走到这里 ⇒ 这一次不装配
1766
+ WebSearch(不拿部署后端顶替调用方点名的目的地)+ 一行 warn `web_search_settings_unusable` 点名字段
1670
1767
  ```
1671
1768
 
1672
- `reqWebSearch` 非 `undefined`(即 `settings.webSearch.provider` 是合法词**且**本部署是单用户车道)时,
1769
+ 判决为 `honored`(即 `settings.webSearch` 形对、`provider` 是合法词,**且**本部署是单用户车道)时,
1673
1770
  `deps.webSearch`(env 配的 backend)整个不参与这次请求 —— 谁赢是**二选一**,不是字段级合并。
1674
1771
 
1675
1772
  **受理门槛(单用户车道)**:🔒 per-request `settings.webSearch` 只在 `REQUIRE_PRINCIPAL !== true`(单用户 /
1676
- TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结构性够不着**——不是被拒、也不留任何
1773
+ TOC 本地形)时被**采纳**;`REQUIRE_PRINCIPAL=true`(多租户)上一段**形对**的配置**结构性够不着**——不是被拒、也不留任何
1677
1774
  `warn`/`capabilities` 位说"你发的 webSearch 被忽略了"(对比 `mcpServers` 有 `capabilities.mcpInjection` +
1678
1775
  `mcp_injection_dropped` 日志可读;`settings.webSearch` 没有对应的能力探测位,`src/http/wire-types.ts:320-323`)。
1679
1776
  🔴 **别把 S-382 的新位读成这个探测位**:`GET /v1/capabilities` 的 `webSearch.backend`(S-382)说的是
@@ -1681,17 +1778,20 @@ TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结
1681
1778
  依旧是结构性够不着、无 warn、无位,这一段的结论一字未变。
1682
1779
  原因是安全边界(与多租户能力配置隔离规则同源):per-request `endpoint`/`apiKey` 是能力配置,多租户下一个租户把 `searxng`
1683
1780
  指向内网地址就是 SSRF,所以多租户上只认部署 env 配的 backend。
1781
+ **形判不分车道**:一段**形错**的 `settings.webSearch`(见下表「形错」列)在任何车道上都在提交当场答
1782
+ `400 request.field_invalid`,错误句逐字点名字段(`settings.webSearch.<字段> must be …`)——与 `settings.hooks`
1783
+ 同姿势:形在受理面判,采不采纳由单用户闸决定。
1684
1784
 
1685
- **`settings.webSearch` 键表**(`src/plugins/web-search.ts` `webSearchConfigFromSettings` §318-335;类型契约见
1686
- `@sema-agent/sdk` 9.0.0 `settings.d.ts` `SettingsWebSearch`):
1785
+ **`settings.webSearch` 键表**(`src/plugins/web-search.ts` `judgeRequestWebSearch`,每个字段一只判官、与部署 env 的
1786
+ 同名旋钮共用;类型契约见 `@sema-agent/sdk` 9.0.0 `settings.d.ts` `SettingsWebSearch`)。整段本身必须是对象(`null` = 缺席):
1687
1787
 
1688
- | 键 | 类型 | 必填 | 默认 | 消费点 |
1788
+ | 键 | 类型 | 必填 | 默认 | 形错(⇒ 400) |
1689
1789
  |---|---|---|---|---|
1690
- | `provider` | `"brave"|"tavily"|"searxng"` | 是(缺席/非三词之一 ⇒ 整个 `settings.webSearch` 被丢,回落到 env 的 backend,**不报错**) | 无 | `webSearchConfigFromSettings` 的词表守卫(与 env 腿**同一只** `isWebSearchProvider`,词表属主 = `WEB_SEARCH_PROVIDERS`) |
1691
- | `apiKey` | string(明文) | brave/tavily 建议带(缺了首次调用才报错,见下表);searxng 不读 | 无(不继承 env 的 `WEB_SEARCH_API_KEY`) | `webSearchConfigFromSettings` 的 `apiKey` 条件展开 |
1692
- | `endpoint` | string | searxng 必需(缺了首次调用才报错);brave/tavily 可选覆盖 | 无(不继承 env 的 `WEB_SEARCH_ENDPOINT`) | `webSearchConfigFromSettings` 的 `endpoint` 条件展开 |
1693
- | `searxngParams` | `Record<string,string>`(对象)或 `"k=v;k=v"`(字符串) | 否 | 无 | `webSearchConfigFromSettings` 的 `searxngParams` 臂,解析逻辑与 env 腿共用 `parseSearxngParams`。⚠️ **未列入已发布的 `@sema-agent/sdk` 8.8.0 `SettingsWebSearch` 类型**(该接口只有 `provider`/`apiKey`/`endpoint`/`maxResults` 四键、无开放下标)——server 侧代码认这个键,但当前发布的 TS 类型接不到它;手写 JSON 请求体仍可以发,server 会照常解析(源码头注自述:字段和消费点先落地,配置录入面尚未跟上,是一笔尚未还清的既有债务) |
1694
- | `maxResults` | number | 否 | 10(与 env 同一 clamp,`[1,20]`,小数向下取整) | `webSearchConfigFromSettings` 的 `maxResults` 归一 |
1790
+ | `provider` | `"brave"|"tavily"|"searxng"` | **是** | 无 | 缺席、不是字符串、或不是三词之一(大小写 / 首尾空白照旧归一)——一段 `settings.webSearch` 就是在点名目的地,认不出就 400 点名 `settings.webSearch.provider`(括号里只说哪一形:`it is missing` / `it is not a string` / `it is not one of them`,**不回显**收到的串 —— 7.101.0 起,S-716),**不**静默换成部署 env 的 backend(改前:整段丢弃、回落 env backend、不报错)。词表守卫与 env 腿**同一只** `isWebSearchProvider`;env 腿词表外是「不装配」(部署自己的缺席) |
1791
+ | `apiKey` | string(明文) | brave/tavily 建议带(缺了首次调用才报错,见下表);searxng 不读 | 无(不继承 env 的 `WEB_SEARCH_API_KEY`);空串同缺席 | 不是字符串 |
1792
+ | `endpoint` | string | searxng 必需(缺了首次调用才报错);brave/tavily 可选覆盖 | 无(不继承 env 的 `WEB_SEARCH_ENDPOINT`);空串同缺席 | 不是字符串,或不是 `scheme://host[:port][/path]` 形的绝对 URL(`localhost:8888` 这类少了 `scheme://` 的也算),或带 userinfo(`user:pw@` / 只带 `user@`;7.101.0 起,S-716,句 `settings.webSearch.endpoint must not carry userinfo (user or user:password before the host)`,不回显值) |
1793
+ | `searxngParams` | `Record<string,string>`(对象)或 `"name=value;name=value"`(字符串) | 否 | 无;空串 / 空对象同缺席 | 与 env `WEB_SEARCH_SEARXNG_PARAMS` 同一套文法:数组、数字、一段不是 `name=value`、名不是参数名(字母 / 数字 / `_` / `-` / `.`)、值为空或不是字符串、同名两次(句里只报第几项 `entry #N`,不回显名或值 —— 7.101.0 起,S-716)。⚠️ **未列入已发布的 `@sema-agent/sdk` 8.8.0 `SettingsWebSearch` 类型**(该接口只有 `provider`/`apiKey`/`endpoint`/`maxResults` 四键、无开放下标)——server 侧代码认这个键,但当前发布的 TS 类型接不到它;手写 JSON 请求体仍可以发,server 会照常解析(源码头注自述:字段和消费点先落地,配置录入面尚未跟上,是一笔尚未还清的既有债务) |
1794
+ | `maxResults` | number | 否 | 10(与 env 同一 clamp,`[1,20]`,小数向下取整;999 夹到 20 不是形错) | 不是数,或 < 1(`"10"` 这种字符串也算) |
1695
1795
  | *(无)* `timeoutMs` | — | — | — | **per-request 车道没有这个字段**——即便部署用 `WEB_SEARCH_TIMEOUT_MS` 配了非默认超时,per-request 生效那次调用永远退回 backend 自己的默认(10000ms,下限 1000ms),因为"整段整取"意味着 env 的 `timeoutMs` 根本不在 `reqWebSearch` 那个新对象里 |
1696
1796
  | *(无)* `fetchImpl` | — | — | — | 同上,仅测试注入用,per-request 车道不可达 |
1697
1797
 
@@ -1699,33 +1799,27 @@ TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结
1699
1799
  (`src/task-settings.ts:309`,集外顶层键 400 `request.body_shape`),但 `webSearch` **自己的子键没有闭集门**
1700
1800
  ——不像 `settings.permissions.*` 有 `TASK_SETTINGS_PERMISSION_KEYS` 逐键拒(`src/task-settings.ts:367-403`
1701
1801
  的 `taskSettingsKeyIssue` 只扫 `settings.*` 顶层和 `settings.permissions.*` 两层)。`settings.webSearch` 下
1702
- 塞一个上表之外的键(拼错的 `mxResults` 之类)**不会** 400,会被 `webSearchConfigFromSettings` 静默无视
1703
- ——门槛之外没有"未知键"这一说。
1802
+ 塞一个上表之外的键(拼错的 `mxResults` 之类)**不会** 400,会被判官静默无视
1803
+ ——门槛之外没有"未知键"这一说(子键闭集是另一件事,未随本版做)。
1704
1804
 
1705
- **错误形**(逐条对应任务书里的四问;全部经**首次真实工具调用**才现形,均为 WebSearch 工具的
1706
- `tool_result` 错误文本,回到模型的对话里,**不是** HTTP 层 `errorCode`,**不会**使 `TaskResult` 整体
1707
- `failed`,也不拒启/不拒收请求本身;core `dist/tools/web.js:840-919` `createWebSearchTool` 统一兜底):
1805
+ **错误形**(形错已在上表:提交当场 400。下表是**形对**之后的失败,全部经**首次真实工具调用**才现形,均为
1806
+ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是** HTTP 层 `errorCode`,**不会**使 `TaskResult` 整体
1807
+ `failed`;core `dist/tools/web.js` `createWebSearchTool` 统一兜底,失败卡上的 `retryable` 由 core 从错误文本分档):
1708
1808
 
1709
1809
  | 触发条件 | 现象 | 是否 retryable(core `classifySearchFailure`) |
1710
1810
  |---|---|---|
1711
- | 坏 `provider`(`settings.webSearch.provider` 非三词之一,或缺席) | **不是错误** —— `webSearchConfigFromSettings` 返回 `undefined`,整段回落到 env 配的 backend(env 也没配 ⇒ WebSearch 工具不装配,模型看不到这个工具);无 warn、无日志 | 不适用 |
1712
1811
  | 缺 `apiKey`(brave/tavily,env 或 per-request 均未给) | 首次调用抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`,core 接住转成 `Error (WebSearch): the search backend failed. …` 文本回模型 | `"unknown"`(消息里没有 HTTP 状态码模式,`classifySearchFailure` 落最后一条默认分支) |
1713
- | `searxngParams` 非对象/非字符串(如数字、布尔、`null`) | 静默丢弃——`parseSearxngParams` 落到"非 object 且非 string"分支,返回 `undefined`,键整个不铸,**不报错、不 warn** | 不适用 |
1714
- | ⚠️ `searxngParams` 是**数组**(如 `["engines=bing"]`) | `typeof [] === "object"` 让它落进对象分支:`Object.entries` 按数组下标产出 `{"0":"engines=bing"}`——**不被拒绝**,但产出的键是数字字符串、值是未拆分的原始 `"k=v"` 串,传给 core adapter 的 `extraParams` 后是一组没有意义的查询参数(不是解析出 `engines=bing`)。这条边角只在 per-request 车道可达(env 值恒为字符串,不会触发);已用与源码逐字一致的独立复现脚本核验(见收车档),未改代码 | 不适用(不是异常路径) |
1715
- | 搜索后端返回**非 2xx HTTP 响应**(鉴权失败/限流/服务端 5xx 等) | 三个 provider 各自的 `!res.ok` 分支抛错,消息形固定为 `"<provider> search failed (<status>): <body首 200 字符>"`(brave/tavily,`braveSearch` / `tavilySearch` 的 `!res.ok` 分支)或 `"SearXNG <status> <statusText> from <url>"`(searxng,core adapter) | ⚠️ **实测几乎恒为 `"unknown"`,不是按状态码分档**:`classifySearchFailure` 的状态码分支要求 `http`/`status`/`code`/`error` 四词之一紧邻数字前(`\D{0,12}`内),但本仓三个 adapter 的消息把状态码写在 `failed (…)`/`SearXNG …` 之后,不触发该分支;独立复现脚本核验(见收车档)brave/tavily/searxng 的 429/500/403/403 全部落到最后一条默认分支 `retryable:"unknown"`。**真正被分类对的只有两条兜底正则**:上游错误体文本里若真含 `"rate limit"` 才判 429 类 `true`,含 `"timed out"`/`"timeout"` 才判 408 类 `true`——都取决于上游返回的具体措辞,不是本仓能保证的 |
1812
+ | 搜索后端返回**非 2xx HTTP 响应**(鉴权失败/限流/服务端 5xx 等) | brave / tavily:一句形 `"<provider> search failed: HTTP <status> — <响应体首 200 字符>"`(`src/plugins/web-search.ts` 的 `httpFailure`,两腿唯一铸点);searxng:`"SearXNG <status> <statusText> from <url>"`(core adapter 自己抛,本仓不改写) | brave / tavily:**按状态码分档** —— 429 与 5xx ⇒ `true`,其余 4xx ⇒ `false`(句里带 `HTTP <status>` 令牌,core 的分档认得出)。⚠️ searxng 腿**仍是 `"unknown"`**:core adapter 那句话的状态码前没有状态词,分档认不出 —— 归 core 修(源头),修前只有上游错误体里碰巧写着 `rate limit` / `timed out` 时才分得对 |
1716
1813
  | `endpoint` **网络层**不可达(连接被拒/DNS 解析失败/fetch 自身抛错,尚未拿到任何 HTTP 响应) | `fetch` 抛出的传输层错误(`ECONNREFUSED`/`ENOTFOUND`/`fetch failed` 等 Node/undici 标准措辞)被同一 `catch` 接住 | `true`——这条路径的错误文本天然含 `econnrefused`/`enotfound`/`fetch failed`/`dns` 等词,`classifySearchFailure` 的网络故障正则能命中(与上一行"已拿到 HTTP 响应但非 2xx"是两条不同的失败路径,别混淆) |
1717
1814
 
1718
- ⚠️ **"坏 provider 静默回落"这条对调用方是否可观察,取决于部署 env 有没有配 backend**:上表第一行说
1719
- "env 也没配 ⇒ 工具不装配、模型看不到这个工具"——那只是**部署 env 同样缺席**这一种情形。若部署 env
1720
- **已经**配了合法 backend(例如 `WEB_SEARCH_PROVIDER=brave`),调用方 per-request 传一个拼错的 `provider`
1721
- (如 `"searx"`)、或带着一个本想打到自建 SearXNG 的 `endpoint`,`settings.webSearch` 整段被丢弃、静默回落
1722
- 到 env 的 brave backend——**WebSearch 工具照常挂载、照常可用**,查询实际发给了 env 配的 provider,不是
1723
- 调用方以为自己指定的那个;工具存在这一事实本身**不能**证明 per-request 的 `provider`/`endpoint` 真的
1724
- 生效了,两种情形(per-request 生效 / per-request 被静默丢弃回落 env)在壳侧不可判别(此条经独立复现验证,
1725
- 见收车档)。
1815
+ ✅ **"坏 provider 静默回落"这一形自 7.100.0 起没有了**:改前部署 env 配了合法 backend(例如 `WEB_SEARCH_PROVIDER=brave`)
1816
+ 时,调用方 per-request 传一个拼错的 `provider`(如 `"searx"`)、或带着一个本想打到自建 SearXNG 的 `endpoint`,
1817
+ `settings.webSearch` 整段被丢弃、静默回落到 env 的 brave backend —— 工具照常挂载,查询发给了 env 配的 provider,
1818
+ 壳侧判别不了。现在同一请求在提交当场答 `400 request.field_invalid` 点名 `settings.webSearch.provider`;WebSearch
1819
+ 工具在场 ⇔ 调用方的 per-request 配置被采纳(单用户车道)或调用方根本没带(用部署默认)。
1726
1820
 
1727
1821
  **`apiKey` 明文与 env 槽边界**:server 收到的 `settings.webSearch.apiKey` 是**明文字符串**,没有任何服务端
1728
- 密钥槽位/引用间接——收到什么字符串就直接进 backend 闭包(`webSearchConfigFromSettings` 的 `apiKey` 条件展开,与 `WEB_SEARCH_API_KEY` 同一条消费路径,
1822
+ 密钥槽位/引用间接——收到什么字符串就直接进 backend 闭包(判官 `judgeRequestWebSearch` 只判「是不是字符串」,与 `WEB_SEARCH_API_KEY` 同一只判官、同一条消费路径,
1729
1823
  见 `src/plugins/web-search.ts` 的 `WebSearchBackendConfig.apiKey` 字段注)。**server 本批不改受理面**:明文字段的形状维持原样。调用方(壳)如何在
1730
1824
  自己机器上管理这份明文是调用方的事——例如 cli 壳侧的约定是在**调用方自己的环境**里按
1731
1825
  `SEMA_WEBSEARCH_KEY_<PROVIDER>` 这样的命名空间存放每个 provider 的 key,由壳在本地读出后把明文塞进请求体;
@@ -46,4 +46,14 @@ export declare function deriveAskId(sourceTaskId: string, runId: string, toolCal
46
46
  /** batch_id = sha256('batch' ⊕ sourceTaskId ⊕ runId ⊕ legKey).slice(0,64)。同一 (sourceTaskId, runId,
47
47
  * legKey) 恒派生同一批——批 = 同一决策点的兄弟 ask 集合,故不掺 toolCallId。 */
48
48
  export declare function deriveBatchId(sourceTaskId: string, runId: string, legKey: string): string;
49
+ /**
50
+ * 🔴 S-689 codex R1-F1:**单 ask 批** —— batch_id = sha256('ask-batch' ⊕ askId).slice(0,64)。
51
+ *
52
+ * 批是 **park 路由的单位**:窗到期赢家把整批转投递面(OPEN → ROUTING_UNBOUND)、兄弟连坐 VOID,core 随后 park,
53
+ * 这条腿就此结束 —— 所以「整腿一批」成立。不走 park 路由的部署(`UNATTENDED_APPROVAL_POLICY=deny`)上这个前提
54
+ * 不在:core 拿到的是部署政策拒,腿**继续跑**。整腿一批时,首只到期就把批关了,同腿后续每一只卡 `decideAsk`
55
+ * 恒 `batch_closed`(卡看得见、答不了),兄弟还被连坐成 VOID。⇒ 没有 park 路由时,批退化成 ask 自己。
56
+ * 选用处 = 协调器的 `batchIdFor`(唯一消费者);前缀与腿批不同(`ask-batch` 一元组 vs `batch` 三元组),两族 id 不相撞。
57
+ */
58
+ export declare function deriveAskScopedBatchId(askId: string): string;
49
59
  //# sourceMappingURL=approval-ask-machine.d.ts.map
@@ -33,4 +33,7 @@ export function deriveAskId(sourceTaskId, runId, toolCallId, legKey, parentToolC
33
33
  export function deriveBatchId(sourceTaskId, runId, legKey) {
34
34
  return deterministicId("batch", [sourceTaskId, runId, legKey]);
35
35
  }
36
+ export function deriveAskScopedBatchId(askId) {
37
+ return deterministicId("ask-batch", [askId]);
38
+ }
36
39
  //# sourceMappingURL=approval-ask-machine.js.map