@sema-agent/server 7.102.0 → 7.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +61 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/USAGE.md +99 -6
- package/dist/background-session-reap.d.ts +4 -1
- package/dist/bake-runner/main.d.ts +21 -3
- package/dist/bake-runner/main.js +18 -11
- package/dist/boot/config-center.d.ts +7 -2
- package/dist/boot/config-center.js +18 -4
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-10-http-server.js +6 -1
- package/dist/brain.d.ts +28 -8
- package/dist/brain.js +20 -4
- package/dist/capabilities/center-plugins.d.ts +9 -3
- package/dist/capabilities/center-plugins.js +5 -10
- package/dist/config-catalog.js +9 -5
- package/dist/config-center/apply-effective.d.ts +16 -0
- package/dist/config-center/apply-effective.js +23 -11
- package/dist/config-center/mcp-revocation.d.ts +2 -2
- package/dist/config-center/mcp-revocation.js +1 -1
- package/dist/config-center/restart-signal.d.ts +0 -10
- package/dist/config-center/restart-signal.js +2 -12
- package/dist/config-types.d.ts +35 -17
- package/dist/config-types.js +3 -3
- package/dist/config.d.ts +7 -0
- package/dist/config.js +43 -17
- package/dist/env-name-allowlist-knobs.d.ts +21 -7
- package/dist/env-name-allowlist-knobs.js +7 -2
- package/dist/fleet-client.d.ts +9 -3
- package/dist/fleet-client.js +2 -0
- package/dist/host-lsp-manager.d.ts +4 -3
- package/dist/host-lsp-manager.js +2 -6
- package/dist/http/dispatch.js +4 -0
- package/dist/http/server.d.ts +4 -0
- package/dist/leader/wire.d.ts +8 -1
- package/dist/leader/wire.js +4 -3
- package/dist/model-entry-refusal.d.ts +31 -9
- package/dist/model-entry-refusal.js +22 -10
- package/dist/model-route-endpoint.d.ts +10 -0
- package/dist/model-route-endpoint.js +6 -2
- package/dist/observability/secret-env-scrub.d.ts +12 -0
- package/dist/observability/secret-env-scrub.js +12 -0
- package/dist/outbound-dispatcher.d.ts +125 -0
- package/dist/outbound-dispatcher.js +211 -0
- package/dist/plugins/host-platform.d.ts +6 -1
- package/dist/plugins/remote-env-host.js +3 -11
- package/dist/project-memory.js +3 -6
- package/dist/run-local.js +2 -0
- package/dist/server-secret-env.d.ts +97 -24
- package/dist/server-secret-env.js +37 -1
- package/dist/trace/core-keyset-guard.d.ts +6 -1
- package/dist/trace/project.js +13 -0
- package/dist/trace/redact.d.ts +11 -0
- package/dist/trace/redact.js +1 -0
- package/package.json +4 -3
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,67 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.103.0(车OY 部分)—— server 进程出网认 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`(S-776)
|
|
11
|
+
|
|
12
|
+
- **出网跟着代理变量走**:server 进程(HTTP 服务与 `run-local`)的出网 —— 引擎 → 网关、中心拉取 / 公告 / 凭证续签、WebSearch、对象存储、OTLP … ——
|
|
13
|
+
自本版起在 **Node 运行时**按 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`(小写同认且优先)走;回环目标恒直连。`NO_PROXY` 不支持 CIDR。**谁受伤**:env 里因别的原因带着 `HTTP(S)_PROXY`、此前
|
|
14
|
+
靠 server 无视它的部署 —— 不在 `NO_PROXY` 里的内网目标现在会被送进代理。**迁移**:把内网目标写进 `NO_PROXY`,或只给 server 进程去掉这几只变量。
|
|
15
|
+
- **无 scheme 的代理值拒启(Node 运行时)**:`proxy.corp:3128` 这类 curl 式写法(以及 `ftp:`、带路径 / 查询)undici 用不了,自本版起拒启,码
|
|
16
|
+
`[config.outbound_proxy_invalid]`(此前 Node 下被无视)。**迁移**:写成 `http://proxy.corp:3128`。Bun 运行时(发布镜像)不校验,且 Bun 的 fetch
|
|
17
|
+
此前就读这几只变量 —— 镜像形上本条与上一条都不是行为变化,只需把 `localhost,127.0.0.1,::1` 写进 `NO_PROXY`(Bun 不豁免回环)。
|
|
18
|
+
- **启动日志多一行 `outbound_proxy`**(info;Bun 运行时配了代理时是 warn);配置目录多三行 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`。
|
|
19
|
+
`dependencies` 多一只 `undici`(= core 的同版精确钉)。
|
|
20
|
+
|
|
21
|
+
## 7.103.0(车OR 部分)—— core 7.33.1 提货:模型调用超时按段重排(`MODEL_CONNECT_TIMEOUT_MS` 退役 / `MODEL_FIRST_BYTE_TIMEOUT_MS` / failover 拓扑首字节 lane / 看门狗 `0` 义 / `wiring_manifest.hands`)
|
|
22
|
+
|
|
23
|
+
- **`MODEL_CONNECT_TIMEOUT_MS` 删除,设了即拒启**(S-753;任何值,含 `0`;空串 = 未设):拒句带码 `[config.env_key_retired]`,点名替代键。
|
|
24
|
+
它名义「建连」、实际计到**响应头**:本仓自带的 30 000 缺省把自托管网关的 prefill(发生在响应头之前)在 30 s 处掐断、同 body 重发、
|
|
25
|
+
报「connection lost」。core 7.33.0 起建连(TCP + TLS)是传输层自己的钟,不再有旋钮。**迁移**:删掉这个键;要给响应头等待设上限,
|
|
26
|
+
改设下一条的新键(值语义变了,别照抄旧值)。
|
|
27
|
+
- **替代键 `MODEL_FIRST_BYTE_TIMEOUT_MS`(`fetch()` → 响应头的等待,毫秒)**:**不设 = core 缺省**(本仓不抄那个数;数值与上传宽限、
|
|
28
|
+
加宽重试见 `@sema-agent/core` 7.33.x 的 MIGRATION),而不是旧键的 30 s。单路由部署:到点 core 自有一次加宽重试(不吃
|
|
29
|
+
`GATEWAY_MAX_RETRIES`),期间流上一帧 `status {phase:"reconnecting", errClass:"stall"}`,终局句点名 `firstByteTimeoutMs`。合法域
|
|
30
|
+
`[0, 2147483647]` 整数,其余拒启。
|
|
31
|
+
- **failover 拓扑(`MODEL_GATEWAY_FALLBACK_URLS`)的首字节 lane = core 缺省**:每条网关路由首窗到点后都有 core 缺省的一次加宽重试,之后才
|
|
32
|
+
交给 failover。server **不铸** core 7.33.1 #1128 的 `firstByteRetries` 座(任何拓扑;S-753 车PA):它只在构造期,而调用方可按请求写
|
|
33
|
+
`resilience.allowFailover: false` 关掉 failover(那一次只用主网关、身后没有备)—— 按拓扑铸 0 会让那一次少一次加宽重试。**谁受伤**:主网关接了
|
|
34
|
+
TCP 却不回响应头时,缺省下要等完 core 的首字节 lane(缺省首窗 + 一次加宽重试;以 core 7.33.x 缺省计约 15 分钟;core 改缺省即变)才切备,
|
|
35
|
+
**时间预算短于那一段的调用切不到备**(code-review council 每个镜头 180 s 与 team discuss 600 s:lane 没走完就被取消,备网关一次都没被尝试 ——
|
|
36
|
+
600 s 在主网关的加宽重试窗里到点;codex 复审 C1 / 车PA r1 实测)。这类部署**升级前**显式设首字节窗(如 `MODEL_FIRST_BYTE_TIMEOUT_MS=30000`,约 30 s + 宽限、再 60 s + 宽限
|
|
37
|
+
之后切备),并让调用预算容得下「两窗 + 上传宽限 + 备网关作答」;一只只是慢(排队 + prefill 超过窗与加宽窗)的主网关也会被切走。没设的
|
|
38
|
+
failover 部署起服时打一条 `failover_first_byte_lane` warn。
|
|
39
|
+
- **`MODEL_FIRST_BYTE_TIMEOUT_MS` / `MODEL_FIRST_TOKEN_TIMEOUT_MS` / `MODEL_IDLE_TIMEOUT_MS` 设 `0` = 该段不设引擎钟,传输层的响应头 / 响应体钟也关**
|
|
40
|
+
(core 7.33.0 每次请求把传输层的 headers / body 钟置 0;此前 `0` 只关引擎的钟,传输层 300 s 仍会切;TCP + TLS 建连仍归传输层自己的钟)。
|
|
41
|
+
failover 拓扑上首字节段设 `0` ⇒ 挂住的主网关**永不**被首字节 lane 切走(只剩 run 级时限收尾)。依赖「设 0 也还有 300 s 兜底」的部署改设
|
|
42
|
+
一个大数。启动日志 `listening` 行 `brain` 段:`connectTimeoutMs` 键删、`firstByteTimeoutMs` 键加(`null` = core 缺省),三只看门狗键设 `0` 时
|
|
43
|
+
显示 `0`(此前显示 `null`)。
|
|
44
|
+
- **`wiring_manifest` 帧多一段 `hands: { mounted, reason? }`**(core 7.33.1 #1039,租户可见,与 `lsp` 同形):`mounted: false` +
|
|
45
|
+
`reason: "no_execution_env"` = **这条腿**没有手(按腿的正面陈述 —— 同一台部署上 scan / council / discuss 这类无手车道也读 false);
|
|
46
|
+
`mounted: true` ≠ shell 可达。operator 面的 `configFingerprint` **一次性变值**
|
|
47
|
+
(同装配各腿仍相等)—— 持久化 / 钉了指纹的读者会见一次跳变。
|
|
48
|
+
- **`GIT_CONFIG_KEY_<i>` 不再被剥**(core 7.33.1 #1129):宿主子进程(host shell / git 腿 / 语言服务器 / bake-runner)里,普通的 git env
|
|
49
|
+
配置组原样到达;只有某个 KEY 的值带 `<scheme>://<userinfo>@`(令牌 insteadOf)时该组整组扣下(7.103.0 组不变量)。🔴 **安全轴披露(core
|
|
50
|
+
KL-1129a)**:`GIT_CONFIG_VALUE_<i>` **不在** core 的凭据形状判据里 —— 组里 KEY 是 `http.extraHeader`、VALUE 是 `Authorization: Bearer …`
|
|
51
|
+
这一形会**原样**进宿主子进程(含模型能驱动的 host shell),并在 `isolateConfig` 的 git 腿上作为命令级配置**生效**(`GIT_CONFIG_GLOBAL /
|
|
52
|
+
SYSTEM=/dev/null` 挡不住 env 这一层)。**迁移**:不要把凭据放进服务进程 env 的 git 配置组;要给 git 带凭据,走 credential helper / ssh key /
|
|
53
|
+
写在 `durableRemote` 里(见 DEPLOY-PREREQS)。⤷ **7.103.0 已随 core 7.33.2 修掉**(车PA 提货):core 给凭据侧加了**值族表**,
|
|
54
|
+
`GIT_CONFIG_VALUE_<i>` 的值是 `Authorization:` / `Proxy-Authorization:` / `Cookie:` 头(任一行、含折行续写)、带 userinfo 的 URL
|
|
55
|
+
(`<scheme>:/+<user>[:<secret>]@…`),或 proxy 地址键(`http.proxy` / `http.<url>.proxy` / `remote.<name>.proxy`)下的 `user[:secret]@host`
|
|
56
|
+
⇒ 按凭据剥(遥测 `secret_env_scrubbed_total{kind="family-rule"}`),本仓组不变量随即把残组**整组**扣下(与 7.32.1 同姿态;该组里别的配置
|
|
57
|
+
一并不到子进程)。值族表之外的形(裸令牌写在不叫头 / URL / proxy 的配置值里)仍会原样到达 —— 迁移句不变:凭据别放进服务进程 env 的 git 配置组。
|
|
58
|
+
⤷ **core 7.33.4(7.103.0 提货;经 7.33.3,车PB / 车PC)**:KEY 与 VALUE 共用的那只 URL 判据(7.33.2 起共用)不再解析 authority —— 值里有 `:/`、
|
|
59
|
+
其后任意位置有 `@` 或 `%40` 即剥;proxy 地址键下值里有 `@` / `%40` 即剥(密码含未转义 `/`、括号形、单斜杠 `https:/u:t@h`、`ssh://t%40h/…` 等此前
|
|
60
|
+
漏过的写法一并剥 ⇒ 整组扣)。**谁受伤**:git env 配置组的键或值里 `:/` 之后带 `@` / `%40` 的(含 `@scope` 包路径写法、`%40` 编码形;不限 URL ——
|
|
61
|
+
scp 绝对路径 `host:/repo@v2.git`、`http.extraHeader = Referer: https://…/@scope`、`includeIf.gitdir:/home/a@corp/.path` 同)整组不到宿主子进程
|
|
62
|
+
(fail-closed 多剥,core KL-1129b)。**出路**(只对不带凭据的这类配置;凭据照旧走 credential helper / ssh key / `durableRemote`,`~/.gitconfig`
|
|
63
|
+
模型可读):不隔离全局配置的腿(host shell / 语言服务器 / bake-runner / leader 推送)读宿主全局 git 配置,写进 `~/.gitconfig` 即可;center-plugins
|
|
64
|
+
克隆腿每次在新目录克隆、且隔离全局 / 系统配置,这类配置在该腿上**没有替代通道**,只能改写成 `:/` 之后不带 `@` / `%40` 的形,或候 core 收窄
|
|
65
|
+
KL-1129b;project-memory 只跑 `git log` / `git status`,不读这类配置。不受影响:值里没有 `:/` 的 scp 相对路径(`git@host:org/repo.git`)与 `:/`
|
|
66
|
+
之后没有 `@` / `%40` 的值。
|
|
67
|
+
🔴 **另一条 git env 通道不在剥密族表里(既有缺口 S-783,本版不修码)**:`GIT_CONFIG_PARAMETERS`(git 的命令级配置 env,形如
|
|
68
|
+
`'http.extraheader'='Authorization: Bearer …'`)不被 core 与本仓任何一层剥 —— 值**原样**进宿主子进程(含模型能驱动的 host shell),并在
|
|
69
|
+
隔离了全局 / 系统 git 配置的腿上照样**生效**(它与 `git -c` 同层)。**不要**把凭据放进它;core 族表纳入它之后再收。
|
|
70
|
+
|
|
10
71
|
## 7.102.0(车OI 部分)—— core 7.32.0 提货带来的四条(后台 shell 退出硬收 / Esc 不收后台 / 读根授权与 artifact store / parked 继承工具新失败形)
|
|
11
72
|
|
|
12
73
|
- **后台 shell 在进程退出与会话删除时被硬收(core #1107,行为面)**:
|
package/README.md
CHANGED
|
@@ -184,9 +184,9 @@ The server is configured entirely through environment variables. The most import
|
|
|
184
184
|
| `MANUAL_MODE_SHELL_GATE` | unset | `always`\|`classify` — tighten `Bash` into the approval chain, applied unconditionally at the governance layer (≥7.1.0: independent of client permission mode, lane, or settings presence). **Unset is not "off"**: since 7.12.0 the caller's explicit `permissionMode` supplies the baseline this knob tightens from (`bypassPermissions` → `off`, `auto`/`default`/`acceptEdits`/`plan` → `classify`; **no** mode stated → core's `off` default). Since 7.73.0 (core 7.15.0) `off` no longer means "no gate at all": the READ BOUNDARY (built-in read-deny tiers + workspace containment) is judged under **every** doctrine, and `shellGate` governs only the RESIDUAL shell risk. Since 7.92.0 (core 7.25.0) that boundary is ONE read station judged before approval, and its three outcomes differ: a **deny-listed read is REFUSED** on the `off` lane as on every other face — no card, no stored rule and no approver's yes can release it (`tool_end.errorCode`/`structured` = `read_path_denied` with the matched `target`/`pattern`, `gate.disposition.deniedBy = "read_boundary"`); a **recursive read form is ENUMERATED** — it runs with zero asks when no deny row sits under the tree, is refused naming that row when one does, and still raises ONE mandated approval when the tree is too large to enumerate; an **out-of-root read** is unchanged and still raises exactly ONE mandated approval (no stored rule and no auto-mode classifier can clear it). Non-readers (`rm`/`curl`/`git`/`npm`) and ordinary in-workspace reads stay unasked on that lane. This knob only ever raises that baseline — it has no relax half, so `off` is accepted as an explicit **no-op** (a boot line says so; not symmetric with `SENSITIVE_WRITE_PATTERNS=off`, which really does clear a set). **Any other value refuses to start** (7.12.0, BREAKING for a deployment that had a typo: it was previously treated as unset, i.e. silently no gate at all) |
|
|
185
185
|
| `PERMISSIONS_DISABLE_AUTO_MODE` | `false` | Local mirror of CC `permissions.disableAutoMode` — the **org deny** bit for `permissionMode:"auto"` (paired with core's intent-arming rule). **Tighten-only**: `true` folds every principal's `runtimeCaps.autoMode` to `false` (a center grant cannot flip it back); unset leaves caps untouched, so on a center-less box a shell asking for `auto` **arms** the classifier once the paired core (intent-arming rule "requested ∧ classifier seat ∧ `autoMode !== false`" — absence is not a deny) is installed; on core 7.2.0 the engine still uses the old "org grant" rule (`permissionModeAuto.intentArming:false`), so the self-check answers `armed:true` only for a center-granted principal and `deployment_incapable` on a center-less box. Boolean word table; any other value refuses to start. Self-check: `GET /v1/capabilities?permissionMode=auto` → `permissionModeAuto.{armed, reason, model}` (USAGE §9.4) |
|
|
186
186
|
| `SCRATCHPAD_SWEEP_TTL_MS` | 7 days | Idle-reap window for per-session scratchpad dirs (by dir mtime; `0` disables). The scratchpad is **ephemeral by contract**: replica-local disk, NOT part of the durable-suspend persistence set — a resume on a different replica, or after a sweep, starts with an empty dir (same two-track posture as the Agent SDK hosting doc: conversation persists, working-directory artifacts don't). Raise/disable only on single-replica deployments that park approvals for longer than the window |
|
|
187
|
-
| `
|
|
188
|
-
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `600000` | First-token timeout (the only watchdog for a stream that opens and never emits; `0` = off. Raised from `120000` in 7.71.0 — a self-hosted backend's long prefill legitimately takes minutes to the first byte; set `120000` to restore the old posture) |
|
|
189
|
-
| `MODEL_IDLE_TIMEOUT_MS` | `300000` | Mid-stream idle timeout (`0` = off) |
|
|
187
|
+
| `MODEL_FIRST_BYTE_TIMEOUT_MS` | unset (core's own default) | The wait from `fetch()` to the response headers, ms (7.103.0; replaces the retired `MODEL_CONNECT_TIMEOUT_MS`, which now **refuses to start** — the TCP + TLS connect is the transport's own clock since core 7.33.0). Unset = no key is passed, core's default applies (plus an upload allowance by request size, and one widened retry of its own). `0` = no engine clock on that segment and the transport's header / body clocks off too (the TCP + TLS connect keeps the transport's own clock). A whole number in `[0, 2147483647]`, anything else refuses to start. With `MODEL_GATEWAY_FALLBACK_URLS` set every gateway route likewise gets the first window plus one widened retry before failover (the server does not pass core's `firstByteRetries`: a caller can turn failover off per request, and that call has no backup behind it). A failover topology must set this knob explicitly: unset, a primary that accepts the connection but never answers is cut over only after core's first-byte lane (default window plus the widened retry) runs out, and a call with a shorter time budget (code-review council lenses, 180 s) is cancelled before the backup is tried; at boot such a deployment logs one `failover_first_byte_lane` warn |
|
|
188
|
+
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `600000` | First-token timeout (the only watchdog for a stream that opens and never emits; `0` = no engine clock on that segment, the transport's body clock off too. Raised from `120000` in 7.71.0 — a self-hosted backend's long prefill legitimately takes minutes to the first byte; set `120000` to restore the old posture) |
|
|
189
|
+
| `MODEL_IDLE_TIMEOUT_MS` | `300000` | Mid-stream idle timeout (`0` = no engine clock on that segment, the transport's body clock off too) |
|
|
190
190
|
| `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` (structured JSON logs) |
|
|
191
191
|
|
|
192
192
|
**Boolean knobs** accept `true`/`false`/`1`/`0`/`yes`/`no`/`on`/`off` (case-insensitive; word table widened
|
package/README.zh-CN.md
CHANGED
|
@@ -158,9 +158,9 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
158
158
|
| `SENSITIVE_WRITE_PATTERNS` | core 推荐集 | 敏感路径写拒集;逗号分隔值为整体替换,`off` **或留空**关闭。在治理层无条件施加(与客户端权限模式/lane/settings 在场性无关),`run-local` 腿同样生效;编译不出守卫集的值(如 `/`)启动即拒 |
|
|
159
159
|
| `WRITE_PROTECTED_EXTRA` | 未设(引擎缺省表) | 给 core 的**写保护名表**加行(字面名表:命中即把幸存的 `allow` 降级成 `ask`,作用于 Write/Edit/NotebookEdit)。逗号分隔裸名(裸名匹配**任意路径段**,含 `/` 的名匹配连续段)或 JSON 数组(`"name"` 串 / `{name, kind}` 行,`kind`:`basename` \| `segment` \| `segment-run`)。值按 `[...core 缺省表, …]` 组合,**丢不掉任何缺省行**。未设 = 不铸座 = 引擎缺省表在岗(本仓从不复制那张表)。两形按**内容**判而不是猜首字符:值里出现 JSON 结构字符(`[ ] { } "`)即按 JSON 解析,且顶层**必须是数组**(少写一对方括号 ⇒ 拒启,而不是被拆成垃圾裸名静默收下)。空值 / 坏值(通配符、未知 kind、kind 与名字段数矛盾)/ 与 `WRITE_PROTECTED_TABLE_REPLACE` 同时设置 ⇒ **启动即拒**。读面:`GET /v1/capabilities` 的 `writeProtection.{armed,rows,replaced}`;`GET /v1/diagnostics/wiring` 的 `writeProtection.{rows,source,droppedDefaultRows}`(operator-only) |
|
|
160
160
|
| `WRITE_PROTECTED_TABLE_REPLACE` | 未设(引擎缺省表) | **整表替换**写保护名表(core 的座按契约就是整表)。只收 JSON 数组 —— 刻意不给逗号简写:一个手滑的裸串会把 51 行换成 1 行。`[]` = 显式「完全不要这张表」。替换时 boot 期发一条**响亮**日志逐名列出被丢的缺省行(`write_protection_table_replaced`;空表走 `write_protection_table_disabled`)—— 想「加两行」请用 `WRITE_PROTECTED_EXTRA`。拒启条件同姊妹键,外加:两根同写 = 一条语义面两个写者 ⇒ 拒启 |
|
|
161
|
-
| `
|
|
162
|
-
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `600000` | 首 token 超时(开流不吐字的唯一看门狗;`0
|
|
163
|
-
| `MODEL_IDLE_TIMEOUT_MS` | `300000` | 流中 idle 超时(`0`
|
|
161
|
+
| `MODEL_FIRST_BYTE_TIMEOUT_MS` | 未设(core 缺省) | `fetch()` → 响应头的等待,毫秒(7.103.0 起;取代退役的 `MODEL_CONNECT_TIMEOUT_MS`,设了旧键**拒启** —— core 7.33.0 起建连(TCP + TLS)是传输层自己的钟)。未设 = 不传键,core 缺省当家(另加按请求体大小的上传宽限,到点 core 自有一次加宽重试)。`0` = 该段不设引擎钟、传输层的响应头 / 响应体钟也关(TCP + TLS 建连仍归传输层自己的钟)。须为 `[0, 2147483647]` 内整数,其余拒启。配了 `MODEL_GATEWAY_FALLBACK_URLS` 时每条网关路由同样是首窗 + 一次加宽重试之后才切备(server 不铸 core `firstByteRetries`:调用方可按请求关 failover,那一次身后没有备)。failover 拓扑须显式设:未设时接了连接却不回头的主网关要等 core 的首字节 lane(缺省窗 + 加宽重试)走完才切备,预算更短的调用(code-review council 镜头 180 s)在备网关被尝试之前就被取消;这类部署起服时打一条 `failover_first_byte_lane` warn |
|
|
162
|
+
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `600000` | 首 token 超时(开流不吐字的唯一看门狗;`0` = 该段不设引擎钟、传输层响应体钟也关。7.71.0 起由 `120000` 提高 —— 自托管后端长 prefill 下首字合理地就要等几分钟;要旧姿态显式设 `120000`) |
|
|
163
|
+
| `MODEL_IDLE_TIMEOUT_MS` | `300000` | 流中 idle 超时(`0` = 该段不设引擎钟、传输层响应体钟也关) |
|
|
164
164
|
| `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error`(结构化 JSON 日志) |
|
|
165
165
|
|
|
166
166
|
**布尔旋钮收 `true`/`false`/`1`/`0`/`yes`/`no`/`on`/`off`**(大小写不敏感;词表自 7.16.0 起放宽)。
|
package/USAGE.md
CHANGED
|
@@ -96,12 +96,17 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
|
|
|
96
96
|
|
|
97
97
|
**可选 — 韧性栈:分级超时 + 断路器(core 1.38,叠在 failover 之下)**
|
|
98
98
|
```bash
|
|
99
|
-
# ③
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
# ③ 分级超时(core 7.33.0 按一次调用的段重排;任务级 limits.maxWalltimeMs 之下):建连(TCP + TLS)是传输层自己的钟,不是旋钮。
|
|
100
|
+
# 三个旋钮的 0 = 该段不设引擎钟,**传输层的响应头 / 响应体钟也关**(7.103.0 起;此前 0 只关引擎的钟,传输层 300 s 仍在;
|
|
101
|
+
# TCP + TLS 建连仍归传输层自己的钟)。
|
|
102
|
+
MODEL_FIRST_BYTE_TIMEOUT_MS=8000 # fetch() → 响应头的等待(7.103.0 起;取代退役的 MODEL_CONNECT_TIMEOUT_MS —— 设了旧键即拒启)。
|
|
103
|
+
# 不设 = core 缺省(本仓不抄那个数,见 core 7.33.x MIGRATION);窗上另加 core 按 body 大小给的上传宽限,
|
|
104
|
+
# 到点 core 自有一次加宽重试(不吃重试上限;配了备用网关也一样,之后才切备 —— server 不铸 firstByteRetries)、终局句点名本键。自托管慢网关(prefill 在响应头之前)
|
|
105
|
+
# 按排队 + prefill 留足;failover 拓扑须显式设(未设时预算短的调用切不到备;见 DEPLOY-PREREQS)
|
|
106
|
+
MODEL_FIRST_TOKEN_TIMEOUT_MS=30000 # 响应头之后迟迟不吐第一个 delta(网关 hang);reasoning 的首个 thinking 也算首 token
|
|
102
107
|
# 缺省 600000(7.71.0 起,原 120000):自托管后端长 prefill 下首字合理地要等几分钟,
|
|
103
|
-
# 120s
|
|
104
|
-
MODEL_IDLE_TIMEOUT_MS=20000 # (1.40.1)出过 token 后中途卡死:每个 delta 重置,静默超时→中止(补 first-token 只管首字)
|
|
108
|
+
# 120s 会把一次正常的慢启动判成失败去重试。快失败部署显式设回 120000
|
|
109
|
+
MODEL_IDLE_TIMEOUT_MS=20000 # (1.40.1)出过 token 后中途卡死:每个 delta 重置,静默超时→中止(补 first-token 只管首字);缺省 300000
|
|
105
110
|
# ④ 断路器:主网关连败 N 次即"开路"→ 快速失败,让 failover 立刻切备(不再逐个等超时)
|
|
106
111
|
MODEL_CIRCUIT_BREAKER=true MODEL_CB_FAILURE_THRESHOLD=5 MODEL_CB_COOLDOWN_MS=30000
|
|
107
112
|
# ⑤ 每次调用的重试上限(0–20)。**不设 = 用引擎默认**(server 不再写死它)
|
|
@@ -418,6 +423,11 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
|
|
|
418
423
|
与 `/health.restart` 同一只取值口、逐字同一个对象(形 = settings-schema `RestartSignal`:`reasons` / `version` / `since`;
|
|
419
424
|
在场即「须重启才能应用配置」,缺席即不需要 —— 中心整行替换);重启信号出现 / 消失 / 变化的当刻即再报一次,不等下一拍心跳。
|
|
420
425
|
钉:`test/health-readyz.test.ts` / `test/health-readiness-single-source.test.ts`。
|
|
426
|
+
- 模型拒因(**7.103.0+**,S-768):`/health.modelRefusals` 与 fleet 心跳 `modelRefusals` 同一只取值口、同一个对象(形 = settings-schema
|
|
427
|
+
6.2.0 `ModelRefusalsSignal`:`entries` = 目录条目名 → 拒因码,`plane` = 模型面组拒码;值只码)。描述最近一次被判的中心候选目录;
|
|
428
|
+
没有可报的拒 ⇒ 键缺席。名字是秘密形 / 空串 / `__proto__` 的不可用条目不上这两处(出口过名筛;引擎内部照旧拒它)。消费方读 `version`
|
|
429
|
+
判「缺席 = 没有拒」还是「老引擎、不可知」;读值用 settings-schema `readModelRefusals`。契约段:`docs/ASSISTANT-WIRE-CONTRACT.md`「模型拒因」。
|
|
430
|
+
钉:`test/model-refusals-s768.test.ts`。
|
|
421
431
|
- 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 **durable(local)**
|
|
422
432
|
(1.292+ 裸 boot 默认 durable;/health 的 `sessionBackend` 报 `durable(local)`)——session 行落盘在
|
|
423
433
|
数据根下,清数据/隐私预期要按「sessions 在 engine-data 里」来做,不要按「只在内存」。
|
|
@@ -1337,7 +1347,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1337
1347
|
| `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`,空名单=谁都不是)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,domainExists,managedNow,effectiveNow}}`;顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 路径 404 + 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(`off`/未设 ⇒ `null`)而不是 env 原文。 |
|
|
1338
1348
|
| `POST /v1/admin/config/refresh` | **配置手动刷新**(operator-only,与上一行同门:空 `OPERATOR_PRINCIPALS` ⇒ 恒 403)。改完中心配置 / `config.d` 之后不想等 60s 轮询就调它:触发**一次既有**的 refresh 拍(在飞则汇入那一拍,绝不并发双拍),返回时该拍已落地。200 体 `{triggered:true,targetVersion,appliedVersion}` —— 两个世代号与 `/health` 的 `configTargetVersion`/`configAppliedVersion` 同源。本部署没有配置管道(纯 env worker:既没配 `CONFIG_CENTER`、也不是 `CONFIG_PROVIDER=local`)⇒ 409 `config.refresh_unavailable` 指路,不会回一个「触发了但什么都没发生」的 200。 |
|
|
1339
1349
|
| `GET /health` | 健康(无需鉴权)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
|
|
1340
|
-
| `GET /v1/sessions/:id/background` · `POST /v1/sessions/:id/background/stop` `{"includeRetained":true\|false}` | **会话后台任务的读口与停止口**(**7.102.0+**,S-745;能力位 `background.listFace` / `background.exitFaces`,老 server 404)。读口列**本会话**、**本副本**上的后台任务行(`{ rows: [{ id, description?, type, status, createdAt, sessionScoped, retained? }] }`,`retained:true` = 声明了保留的活服务,正常退出不会停它);停止口「软 → 等一次 `HOST_BG_KILL_GRACE_MS` → 硬」收掉本会话的后台 shell(SIGTERM 处理器先有一次宽限跑完,之后组 SIGKILL,屏蔽 SIGTERM 的循环也活不过去;有后台行时应答慢一次宽限)并逐字返回受据 `{ receipts: [{ id, outcome }] }`:`killed` / `already_gone` / `retained_skipped`(`includeRetained:false` 留下的活服务)/ `no_pgid`(远端 / 沙箱车道没有本机进程组可发,已派普通 kill
|
|
1350
|
+
| `GET /v1/sessions/:id/background` · `POST /v1/sessions/:id/background/stop` `{"includeRetained":true\|false}` | **会话后台任务的读口与停止口**(**7.102.0+**,S-745;能力位 `background.listFace` / `background.exitFaces`,老 server 404)。读口列**本会话**、**本副本**上的后台任务行(`{ rows: [{ id, description?, type, status, createdAt, sessionScoped, retained? }] }`,`retained:true` = 声明了保留的活服务,正常退出不会停它);停止口「软 → 等一次 `HOST_BG_KILL_GRACE_MS` → 硬」收掉本会话的后台 shell(SIGTERM 处理器先有一次宽限跑完,之后组 SIGKILL,屏蔽 SIGTERM 的循环也活不过去;有后台行时应答慢一次宽限)并逐字返回受据 `{ receipts: [{ id, outcome }] }`:`killed` / `already_gone` / `retained_skipped`(`includeRetained:false` 留下的活服务)/ `no_pgid`(远端 / 沙箱车道没有本机进程组可发,已派普通 kill,「可能还在跑」;host 车道的 `already_gone` 只由观察得出 —— 引擎已收割组长或内核答组已空,7.103.0 起,S-769;darwin 上梯子那一枪先落地、引擎还没收割的那一拍如实答 `no_pgid`,判「收掉没有」读进程)。`includeRetained` 必填:`true` = 连活服务一起停(壳的「Exit and stop tasks」),`false` = 活服务照留(`sema kill`)。鉴权 = 会话属主或显式 operator(未知与他人同 404);停止口在无凭据写门内(没配 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` ⇒ 503 `auth.service_token_required`)。`DELETE /v1/sessions/:id` 走同一只收敛(`includeRetained:false`);run 取消(Esc)**不**收后台 shell。契约 §16 |
|
|
1341
1351
|
| `GET /readyz` | **就绪探针**(**7.102.0+**,无需鉴权)。就绪 ⇒ `200 {"ready":true}`;未就绪 ⇒ `503 {"ready":false,"reasons":[…]}` + `Retry-After: 5`,`reasons` ⊆ {`draining`, `model_roster_pending`}(按优先序)。与 `/health` 的 `ready` / `readyReason` / `reasons` 同一只判;库连不上**不算**未就绪(`storeLive` 只在 `/health` 披露)。给只认状态码的**摘流**探针用(k8s `readinessProbe.httpGet`、LB 健康检查;重启语义的 `HEALTHCHECK` / `livenessProbe` 留在 `/health`),见 §0「探针」 |
|
|
1342
1352
|
| `GET /metrics` | Prometheus 指标(有 token 时需带) |
|
|
1343
1353
|
|
|
@@ -1903,3 +1913,86 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
|
|
|
1903
1913
|
不同,即验证「脱敏只发生在持久化/展示面,不发生在出站面」这句话为真——**若代理本身继续把请求转发到外部
|
|
1904
1914
|
模型服务,即便截获成功,也不满足「内容不出机器」这个更高的目标,截获只证明了脱敏的射程,不证明数据没有
|
|
1905
1915
|
离开你控制的边界**。
|
|
1916
|
+
|
|
1917
|
+
### 9.7 bake-runner(构建宿主守护进程)的 env(7.103.0 起成文)
|
|
1918
|
+
|
|
1919
|
+
`bake-runner`(`npm run start:bake-runner` = `node dist/bake-runner/main.js`)是跑在构建宿主上的**独立进程**:它向 image-api
|
|
1920
|
+
认领 bake、起 `build.sh`、推镜像。它**不读**服务进程的配置(配置目录 `GET /v1/config/catalog` 里的 `BAKE_*` 行是服务侧 bake API 的
|
|
1921
|
+
旋钮,与本表无关)。在此之前 runner 的 env 表只写在 `src/bake-runner/main.ts` 的 `loadEnv` 头注里,本节是它的成文处(两处同文)。
|
|
1922
|
+
|
|
1923
|
+
| env | 缺省 | 说明 |
|
|
1924
|
+
|---|---|---|
|
|
1925
|
+
| `IMAGE_API_BASE` | 必填 | image-api 根(尾斜杠去掉) |
|
|
1926
|
+
| `BAKE_RUNNER_TOKEN` | 必填 | claim / heartbeat 的运维主体 bearer。不进日志,**7.103.0 起也不进任何子进程** |
|
|
1927
|
+
| `BAKE_RUNNER_ID` | `bake-runner-<HOSTNAME>` | 认领时报给 image-api 的 runner 名 |
|
|
1928
|
+
| `RECIPE_REF` | `HEAD` | 配方 checkout 的 ref(`HEAD` = 用现成 checkout,不 fetch) |
|
|
1929
|
+
| `RECIPE_DIR` | `/opt/recipes` | 配方仓 checkout 根(build.sh 的 cwd) |
|
|
1930
|
+
| `BUILD_SH_PATH` | `e2b-template/dev-sandbox/build.sh` | 相对 `RECIPE_DIR` 的 build.sh 路径 |
|
|
1931
|
+
| `BAKE_DATA_DIR` | `/data` | 数据盘(磁盘门读它的 df) |
|
|
1932
|
+
| `BAKE_HEARTBEAT_MS` / `BAKE_IDLE_POLL_MS` / `IMAGE_BAKE_MIN_FREE_GB` / `BAKE_SHUTDOWN_GRACE_MS` | 30000 / 5000 / 20 / 5000 | 数值;非数 / 负数**拒启** |
|
|
1933
|
+
| `BAKE_ENV_ALLOW` | 空 | 子进程 env 的点名放行名单(见下) |
|
|
1934
|
+
| `LOG_LEVEL` | `info` | 日志档位 |
|
|
1935
|
+
|
|
1936
|
+
**子进程 env(7.103.0,S-684 残余)。** runner 起的每一只子进程组 —— `build.sh`,以及主机命令 `git fetch` / `docker push` /
|
|
1937
|
+
`docker image inspect` / `df` —— 拿到的 env = 本进程 env **剥密**:core 形状规则(`*_KEY` / `*_TOKEN` / `*_SECRET` / `*_PASSWORD` …)∘
|
|
1938
|
+
服务配置目录里的 secret 名 ∘ 名单型旋钮原文。runner 自己的 `BAKE_RUNNER_TOKEN` 在内。剥了什么在第一次起组时记一行
|
|
1939
|
+
`secret_env_scrubbed`(warn,site = `server.bake-runner.spawn-env`,带 `allowed` 与出路句;只键名,不带值)。
|
|
1940
|
+
`PATH` / `HOME` / `NO_PROXY` / `DOCKER_CONFIG` / `BAKE_IMAGE_REGISTRY` / `RECIPE_GITSHA` 这类非密钥键照常传。
|
|
1941
|
+
docker 推送的鉴权走构建宿主上的 docker 凭据存储(`docker login`),不走 env,不受影响。
|
|
1942
|
+
git 的 env 配置组(`GIT_CONFIG_COUNT` + `GIT_CONFIG_KEY_<i>` / `GIT_CONFIG_VALUE_<i>`)要么整组到达、要么整组不到:core 剥掉其中任一成员
|
|
1943
|
+
(KEY 或 VALUE 的值里 `:/` 之后带 `@` / `%40`、VALUE 是鉴权头等)之后剩下的残组会让 git 起手退 128,所以残组整组扣下(随组扣下的成员在同一行
|
|
1944
|
+
`secret_env_scrubbed` 里,`kind` = `git-config-group`)。要这组配置到达 ⇒ 把全部 `GIT_CONFIG_KEY_<i>` 与 `GIT_CONFIG_VALUE_<i>` 写进
|
|
1945
|
+
`BAKE_ENV_ALLOW`(两边都可能被剥,只放回 KEY 仍是残组),或改用文件型 git 配置(`~/.gitconfig`,不放凭据)。
|
|
1946
|
+
|
|
1947
|
+
**`BAKE_ENV_ALLOW=NAME,NAME`。** 配方真要一枚密钥形 env(例如 build.sh 里自己做 registry 登录要 `REGISTRY_PASSWORD`,或
|
|
1948
|
+
`git fetch` 的 askpass 读 `GIT_TOKEN`)⇒ 点名它,runner 把该键从本进程 env **原样**放回给每一只子进程组(部署方显式承担)。
|
|
1949
|
+
与服务侧 `LSP_ENV_ALLOW` 同一只解析器、同一只放行谓词:
|
|
1950
|
+
- 形错(空段 / 尾逗号 / 非变量名形,如 `$NPM_TOKEN`、`a b`、`NAME=value` 整段贴入 / 点名任一只名单型旋钮)⇒ runner **拒启**,
|
|
1951
|
+
`bake_runner_fatal` 那一行点名旋钮与第几段,**不回显值**;
|
|
1952
|
+
- 本进程环境里没有的名 ⇒ 起服一行 `config_env_entry_rejected`(只报序号)、不放行;
|
|
1953
|
+
- 旋钮原文本身按凭据类对待,不进子进程;
|
|
1954
|
+
- 生效名单(点到且环境里真有的名)见起服行 `bake_runner_start.envAllow`(只键名);
|
|
1955
|
+
- 没有多租户姿态(构建宿主守护进程没有主体 / 租户),`REQUIRE_PRINCIPAL` 与它无关。
|
|
1956
|
+
|
|
1957
|
+
### 9.8 出网代理:`HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`(7.103.0 起)
|
|
1958
|
+
|
|
1959
|
+
server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认这三只平台标准变量:引擎 → 模型网关(含 https 网关经 `CONNECT` 隧道)、
|
|
1960
|
+
配置中心拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP、hook 模型调用、core 的 MCP streamable-http / A2A / WebFetch 工具 …
|
|
1961
|
+
实现是**一只**进程级 dispatcher,在启动时装一次;没有另外的开关 —— 这三只变量本身就是配置通道。
|
|
1962
|
+
|
|
1963
|
+
- **读法**(与 undici 读 env 逐字同序):小写 `http_proxy` / `https_proxy` / `no_proxy` 同认**且优先**;https 目标在 `HTTPS_PROXY` 缺席 / 空时
|
|
1964
|
+
走 `HTTP_PROXY`;两者都没有 ⇒ 直连。**启动时读一次**(boot 冻结,运行中改 env 不生效)。
|
|
1965
|
+
- **代理 URL 形**:`http://host:port` 或 `https://host:port`(undici 7.30 构造也接受 `socks5://`,本版未实测转发);userinfo(`http://user:pass@host:port`)被译成
|
|
1966
|
+
`Proxy-Authorization: Basic …`(密码里的 `/` `#` `?` `@` 要百分号编码)。**Node 运行时**:无 scheme(curl 式 `proxy.corp:3128`)、`ftp:`、
|
|
1967
|
+
带路径 / 查询的值 undici 用不了 ⇒ **拒启**,拒句带码 `[config.outbound_proxy_invalid]`(带 `scheme://` 却在 host:port 之后还有路径 / 点段 / 查询 /
|
|
1968
|
+
片段的值,本仓先于 undici 拒 —— 这一道各运行时同,Bun 下也拒);回显只出 `scheme//host[:port]`,形不干净(有路径 /
|
|
1969
|
+
查询 / 片段 —— 未编码的特殊字符会让密码落进这几段)整只占位 `«redacted:url»`。Bun 运行时不校验(见下)。
|
|
1970
|
+
- **`NO_PROXY` 语法 = undici 的**:逗号或空白分隔;`host` 精确匹配**且含其子域**;`.host` / `*.host` 后缀;`host:port` 限端口;整串 `*` = 全直连;
|
|
1971
|
+
IPv6 写方括号形 `[fd00::1]`。**不支持 CIDR / IP 段**(`10.0.0.0/8`、`10.*` 都不生效):按 IP 访问的目标逐个列(可带 `:port`)。
|
|
1972
|
+
- 🔴 **回环恒直连,不看 `NO_PROXY`**:目标是 `localhost` / `127.0.0.0/8` / `::1` / `::ffff:127.x` / `0.0.0.0` / `::` 时一律直连 —— 本机网关、本机中心
|
|
1973
|
+
不会被送进公司代理(代理够不到本进程的回环)。这是无条件规则,没有关掉它的旋钮;它是对 undici 缺省的收窄(undici 自己只按 `NO_PROXY` 判)。
|
|
1974
|
+
内网目标(对象存储 / 中心 / 自托管网关 / OTLP collector)**不是**回环,要直连就写进 `NO_PROXY`。
|
|
1975
|
+
- **企业代理的 CA**:给 server 进程设 `NODE_EXTRA_CA_CERTS=<pem 文件>`(Node 启动时读进缺省信任库,undici 的 TLS 用的就是它 —— 隧道里目标的
|
|
1976
|
+
TLS 已实测;`https://` 代理本身那一段同一个信任库,未单独实测)。不要关证书校验。
|
|
1977
|
+
- **Node 24 `NODE_USE_ENV_PROXY`**:它让 Node 在启动时自己装一只读这三只变量的 dispatcher;server 随后装的这一只**覆盖**它(fetch 那一半)——
|
|
1978
|
+
行为一致,只多回环直连。不设它也一样生效。
|
|
1979
|
+
- **代理那一跳失败时怎么读**(Node 运行时):连代理的每一次拨号 —— 拒连、解析不了、超时、`https://` 代理自身的 TLS、收下连接就关、CONNECT 回
|
|
1980
|
+
非 200 —— 失败时,引擎的终局句(与 `run_failed` 日志行)形如
|
|
1981
|
+
`fetch failed (via outbound proxy (from HTTPS_PROXY) ← connect ECONNREFUSED 10.0.0.9:3128)` / `… ← proxy answered CONNECT with HTTP 403)` —— 这句会进
|
|
1982
|
+
租户可见的失败原因:**包装句**(`via outbound proxy (from <变量>)`)点明「经代理 + 哪只变量」、不带代理地址;`←` 之后是根因原文(undici 的底层网络
|
|
1983
|
+
错误原样,拒连 / 解析不了时它本身会写出代理的 host:port —— 本仓不改写上游错误;代理地址对照启动日志 `outbound_proxy` 行)。代理拨号有一只
|
|
1984
|
+
**从拨号起计的绝对期限** 10 s(= undici 连接阶段的缺省时限;连代理的 TCP / TLS 与 CONNECT 交换都算在内,代理中途吐字节也不续命)—— 到点
|
|
1985
|
+
CONNECT 还没回完 ⇒ 根因 `proxy did not answer CONNECT within 10000 ms`、那条连接随之关掉;调用方先取消时请求当场结束,挂着的那次拨号同样
|
|
1986
|
+
在期限内收口、不留连接。请求当场失败,不重连(代理收下连接就关也一样)。隧道建好之后的失败(目标的 TLS、
|
|
1987
|
+
目标的响应)不带这段前缀 —— 那是目标本身的事。
|
|
1988
|
+
- **启动日志**:恰一行 `outbound_proxy`,`{httpProxy, httpsProxy, noProxy, loopbackDirect, runtime}`(代理值 display-safe;`httpsProxy` 是 https
|
|
1989
|
+
目标**实际**走的那只,含回落)。配置目录 `GET /v1/config/catalog` 有 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` 三行(`limitsHttp` 组,回显同一份
|
|
1990
|
+
display-safe 投影)。`/health` 与 fleet 公告**不**带代理信息。
|
|
1991
|
+
- **Bun 运行时(发布镜像 `CMD ["bun", …]` 与 `bun build --compile` 二进制)**:Bun 的 fetch 不读 undici 的全局 dispatcher,它**自己**读这三只变量
|
|
1992
|
+
(代理与 `NO_PROXY` 都认),但**回环不豁免**。启动行因此报 `loopbackDirect:false`,配了代理时升 warn —— 在 Bun 上请把 `localhost,127.0.0.1,::1`
|
|
1993
|
+
写进 `NO_PROXY`。Bun 下代理值**不拒启**(本仓的 undici 在 Bun 里是垫片、不校验;Bun 自己把无 scheme 的值当 http 用,`ftp:` 到请求时才报错),
|
|
1994
|
+
上面那条「代理那一跳失败时怎么读」的前缀也只在 Node 下有;e2b SDK 在 Bun 下走全局 fetch,**认**代理变量。
|
|
1995
|
+
- **不走这只 dispatcher 的出网**(本版如实列出,不在本版改):e2b SDK(Node 下自带 dispatcher,只认它自己的 `proxy` 选项,server 不传)、k8s 车道
|
|
1996
|
+
(`node:https` + `ws`)、LSP 的 `ws` 传输、ssh 车道、MySQL / PG 驱动(TCP)、`bake-runner`(独立进程)。宿主上起的 host shell 与 git 子进程 env 里
|
|
1997
|
+
照常带着代理变量(不被剥名),由 git / curl 这类工具自己认它们;**MCP stdio 服务器不继承**(MCP SDK 只给子进程 `HOME` / `LOGNAME` / `PATH` /
|
|
1998
|
+
`SHELL` / `TERM` / `USER` 加上该服务器声明的 env)—— 要它走代理,在它的 env 声明里写上代理变量。
|
|
@@ -37,7 +37,10 @@ export interface SessionHardReap {
|
|
|
37
37
|
* 不需要按会话单飞;
|
|
38
38
|
* ③ 硬面:`killAllBackgroundShellsNow({ sessionId, scope, includeRetained, reason })`(会话过滤 = 会话锚自己的键:本会话的会话驻留行
|
|
39
39
|
* + run 的 task id 就是会话 id 的 run 行;同一个 id 在别的 scope 是别的会话;别的会话的行永远不碰)—— 宽限里跑完处理器的进程答
|
|
40
|
-
* `already_gone`,屏蔽 / 没跑完的在这一枪组 SIGKILL(与 host 梯子自己到点的那一枪同拍,两枪都是组 SIGKILL,幂等)
|
|
40
|
+
* `already_gone`,屏蔽 / 没跑完的在这一枪组 SIGKILL(与 host 梯子自己到点的那一枪同拍,两枪都是组 SIGKILL,幂等)。同拍是设计内的:
|
|
41
|
+
* 两枪谁先是同一毫秒的定时器次序。硬面先打 ⇒ `killed`;梯子先打、引擎还没收割组长 ⇒ 硬面那一枪落在只剩僵尸的组上:darwin EPERM ⇒
|
|
42
|
+
* `no_pgid`(未确认)、linux ⇒ `killed`;已收割 ⇒ `already_gone`。受据按观察答(S-769 车PA 删了「已硬收过」闩:组信号任一成员收到即
|
|
43
|
+
* 成功,闩在部分送达时把活着读成没了),本函数也不为「让硬面先打」挪任何一拍。
|
|
41
44
|
*
|
|
42
45
|
* `scope` = 会话行属主 `owner ?? "default"`(与 core 注册铸值 `spec.principal ?? "default"` 同源 —— 缺席 scope 在 core 的
|
|
43
46
|
* `canAccess` 上 fail-closed、一行都收不到,[ref] 钉过这一病)。
|
|
@@ -11,7 +11,24 @@ interface RunnerEnv {
|
|
|
11
11
|
idlePollMs: number;
|
|
12
12
|
minFreeGb: number;
|
|
13
13
|
shutdownGraceMs: number;
|
|
14
|
+
/** `BAKE_ENV_ALLOW` 的生效名单(点到、且本进程环境里真有的名;缺省空 = 子进程 env 全剥)。 */
|
|
15
|
+
envAllow: readonly string[];
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* bake-runner 的 **env 契约**(本进程只读这些;成文另见 USAGE.md §9.7):
|
|
19
|
+
* · 必填:`IMAGE_API_BASE`(image-api 根,尾斜杠去掉)、`BAKE_RUNNER_TOKEN`(claim / heartbeat 的运维主体 bearer;不进日志、不进子进程)。
|
|
20
|
+
* · `BAKE_RUNNER_ID`(缺省 `bake-runner-<HOSTNAME>`)、`RECIPE_REF`(缺省 `HEAD` = 用现成 checkout)、`RECIPE_DIR`(缺省 `/opt/recipes`)、
|
|
21
|
+
* `BUILD_SH_PATH`(缺省 `e2b-template/dev-sandbox/build.sh`)、`BAKE_DATA_DIR`(缺省 `/data`)、`LOG_LEVEL`(缺省 info)。
|
|
22
|
+
* · 数值四键(`parseNumOrFailNonNegative`,非数 / 负数拒启):`BAKE_HEARTBEAT_MS`(30000)、`BAKE_IDLE_POLL_MS`(5000)、
|
|
23
|
+
* `IMAGE_BAKE_MIN_FREE_GB`(20)、`BAKE_SHUTDOWN_GRACE_MS`(5000)。
|
|
24
|
+
* · `BAKE_ENV_ALLOW=NAME,NAME`(7.103.0,S-684 残余):本进程 spawn 的每一只子进程组(build.sh 与 git fetch / docker push /
|
|
25
|
+
* docker image inspect / df)的 env 缺省 = 本进程 env 剥密(runner 自己的 `BAKE_RUNNER_TOKEN` 在内);名单点到的键从本进程 env **原样**
|
|
26
|
+
* 放回(部署方显式承担 —— 例如配方脚本真要一枚 `*_PASSWORD` 做 registry 登录)。与服务侧 `LSP_ENV_ALLOW` **同一只**解析器
|
|
27
|
+
* (`config.ts` `parseEnvNameAllowlist`)与同一只放行谓词:形错(空段 / 尾逗号 / 非变量名形 / 点名任一只名单型旋钮)⇒ **拒启**,拒句点名
|
|
28
|
+
* 旋钮与段号、不回显值;本进程环境里没有的名 ⇒ 起服 `config_env_entry_rejected` 只报序号、不放行;旋钮原文本身按凭据类,不进子进程。
|
|
29
|
+
* 没有多租户姿态(构建宿主守护进程没有主体 / 租户);生效名单见起服行 `bake_runner_start.envAllow`(只键名)。
|
|
30
|
+
* ⚠️ 这不是服务配置目录里的键(目录 `BAKE_*` 行是服务侧 bake API 的旋钮),服务进程不读它。
|
|
31
|
+
*/
|
|
15
32
|
declare function loadEnv(): RunnerEnv;
|
|
16
33
|
/** The real image-api HTTP client. Authenticates claim/heartbeat with the runner bearer; ingest additionally
|
|
17
34
|
* carries the per-bake ingest secret header (so image-api can reject a stale/rogue runner — §P2.4e). */
|
|
@@ -33,8 +50,9 @@ declare function makeApiClient(env: RunnerEnv, log: BakeRunnerLogger): ImageApiC
|
|
|
33
50
|
declare const liveGroupPgids: Set<number>;
|
|
34
51
|
declare function killLiveGroupsSync(): void;
|
|
35
52
|
/** The real child spawner: `setsid`-style detached process GROUP so a cancel/deadline kill signals the whole tree
|
|
36
|
-
* (channel-close / a plain kill of the immediate child does NOT stop a detached build — §P2.11).
|
|
37
|
-
|
|
53
|
+
* (channel-close / a plain kill of the immediate child does NOT stop a detached build — §P2.11).
|
|
54
|
+
* `envAllow` = `BAKE_ENV_ALLOW`'s effective list (the build.sh group's env = scrubbed ∪ these names). */
|
|
55
|
+
declare function makeSpawner(envAllow: readonly string[]): ChildSpawner;
|
|
38
56
|
/**
|
|
39
57
|
* 有界、整组击杀、进簿的 `sh -c` —— **本进程唯一的 host exec 面**(C5)。
|
|
40
58
|
*
|
|
@@ -42,7 +60,7 @@ declare function makeSpawner(): ChildSpawner;
|
|
|
42
60
|
* 成功 ⇒ `{ stdout }`。多出来的两件:① `timeoutMs` 到点整组 SIGKILL 并 reject;② 它的组在 {@link liveGroupPgids}
|
|
43
61
|
* 里,于是关机梯子的 ④(`process.exit`)经退出边界收得到它 —— 旧形下一只挂死的 `docker push` 是看不见也杀不死的。
|
|
44
62
|
*/
|
|
45
|
-
export declare function execGroup(cmd: string, timeoutMs: number): Promise<{
|
|
63
|
+
export declare function execGroup(cmd: string, timeoutMs: number, envAllow: readonly string[]): Promise<{
|
|
46
64
|
stdout: string;
|
|
47
65
|
}>;
|
|
48
66
|
/** The real host ops: fs manifest read, df /data, recipe checkout, and the thin docker-push big-blob retry. */
|
package/dist/bake-runner/main.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { createLogger } from "../observability/logger.js";
|
|
4
|
-
import { parseNumOrFailNonNegative } from "../config.js";
|
|
4
|
+
import { logConfigDiagnostics, parseEnvNameAllowlist, parseNumOrFailNonNegative } from "../config.js";
|
|
5
|
+
import { installSecretEnvScrubRecorder } from "../observability/secret-env-scrub.js";
|
|
6
|
+
import { hostSubprocessEnv } from "../server-secret-env.js";
|
|
5
7
|
import { shellQuote as shellSafe } from "../plugins/remote-shell.js";
|
|
6
8
|
import { BakeRunner, } from "./runner.js";
|
|
7
9
|
const BAKE_CLAIM_INGEST_FETCH_TIMEOUT_MS = 30_000;
|
|
@@ -29,6 +31,7 @@ function loadEnv() {
|
|
|
29
31
|
idlePollMs: parseNumOrFailNonNegative("BAKE_IDLE_POLL_MS", process.env.BAKE_IDLE_POLL_MS || "5000"),
|
|
30
32
|
minFreeGb: parseNumOrFailNonNegative("IMAGE_BAKE_MIN_FREE_GB", process.env.IMAGE_BAKE_MIN_FREE_GB || "20"),
|
|
31
33
|
shutdownGraceMs: parseNumOrFailNonNegative("BAKE_SHUTDOWN_GRACE_MS", process.env.BAKE_SHUTDOWN_GRACE_MS || "5000"),
|
|
34
|
+
envAllow: parseEnvNameAllowlist("BAKE_ENV_ALLOW") ?? [],
|
|
32
35
|
};
|
|
33
36
|
}
|
|
34
37
|
function makeApiClient(env, log) {
|
|
@@ -119,7 +122,7 @@ function spawnGroupChild(opts) {
|
|
|
119
122
|
cwd: opts.cwd,
|
|
120
123
|
detached: true,
|
|
121
124
|
stdio: ["ignore", "pipe", "pipe"],
|
|
122
|
-
env:
|
|
125
|
+
env: hostSubprocessEnv({ site: "server.bake-runner.spawn-env", allow: opts.envAllow }),
|
|
123
126
|
});
|
|
124
127
|
const pgid = child.pid ?? -1;
|
|
125
128
|
pruneEmptyGroups();
|
|
@@ -158,15 +161,16 @@ function groupHasMembers(pgid) {
|
|
|
158
161
|
return err.code === "EPERM";
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
|
-
function makeSpawner() {
|
|
162
|
-
return { spawn: (opts) => spawnGroupChild(opts) };
|
|
164
|
+
function makeSpawner(envAllow) {
|
|
165
|
+
return { spawn: (opts) => spawnGroupChild({ ...opts, envAllow }) };
|
|
163
166
|
}
|
|
164
|
-
export async function execGroup(cmd, timeoutMs) {
|
|
167
|
+
export async function execGroup(cmd, timeoutMs, envAllow) {
|
|
165
168
|
let stdout = "";
|
|
166
169
|
let stderr = "";
|
|
167
170
|
const append = (buf, line) => (buf.length >= BAKE_HOST_EXEC_OUTPUT_CAP ? buf : `${buf}${line}\n`);
|
|
168
171
|
const child = spawnGroupChild({
|
|
169
172
|
argv: ["/bin/sh", "-c", cmd],
|
|
173
|
+
envAllow,
|
|
170
174
|
onStdoutLine: (l) => {
|
|
171
175
|
stdout = append(stdout, l);
|
|
172
176
|
},
|
|
@@ -216,7 +220,7 @@ function makeHostOps(env) {
|
|
|
216
220
|
return JSON.parse(await readFile(path, "utf8"));
|
|
217
221
|
},
|
|
218
222
|
async dfData() {
|
|
219
|
-
const { stdout } = await execGroup(`df -P -BG ${shellSafe(env.dataDir)}`, BAKE_HOST_PROBE_TIMEOUT_MS);
|
|
223
|
+
const { stdout } = await execGroup(`df -P -BG ${shellSafe(env.dataDir)}`, BAKE_HOST_PROBE_TIMEOUT_MS, env.envAllow);
|
|
220
224
|
return stdout;
|
|
221
225
|
},
|
|
222
226
|
async checkoutRecipe(ref) {
|
|
@@ -224,10 +228,10 @@ function makeHostOps(env) {
|
|
|
224
228
|
const dir = shellSafe(env.recipeDir);
|
|
225
229
|
try {
|
|
226
230
|
await execGroup(`git -C ${dir} fetch --depth 1 origin ${shellSafe(ref)} && ` +
|
|
227
|
-
`git -C ${dir} checkout --quiet --detach FETCH_HEAD`, BAKE_RECIPE_CHECKOUT_TIMEOUT_MS);
|
|
231
|
+
`git -C ${dir} checkout --quiet --detach FETCH_HEAD`, BAKE_RECIPE_CHECKOUT_TIMEOUT_MS, env.envAllow);
|
|
228
232
|
}
|
|
229
233
|
catch {
|
|
230
|
-
await execGroup(`git -C ${dir} fetch origin && git -C ${dir} checkout --quiet --detach ${shellSafe(ref)}`, BAKE_RECIPE_CHECKOUT_TIMEOUT_MS);
|
|
234
|
+
await execGroup(`git -C ${dir} fetch origin && git -C ${dir} checkout --quiet --detach ${shellSafe(ref)}`, BAKE_RECIPE_CHECKOUT_TIMEOUT_MS, env.envAllow);
|
|
231
235
|
}
|
|
232
236
|
}
|
|
233
237
|
return env.recipeDir;
|
|
@@ -235,8 +239,8 @@ function makeHostOps(env) {
|
|
|
235
239
|
async dockerPushRetry(tag, attempts) {
|
|
236
240
|
for (let i = 0; i < attempts; i++) {
|
|
237
241
|
try {
|
|
238
|
-
await execGroup(`docker push ${shellSafe(tag)}`, BAKE_DOCKER_PUSH_TIMEOUT_MS);
|
|
239
|
-
const { stdout } = await execGroup(`docker image inspect ${shellSafe(tag)} --format '{{index .RepoDigests 0}}'`, BAKE_HOST_PROBE_TIMEOUT_MS);
|
|
242
|
+
await execGroup(`docker push ${shellSafe(tag)}`, BAKE_DOCKER_PUSH_TIMEOUT_MS, env.envAllow);
|
|
243
|
+
const { stdout } = await execGroup(`docker image inspect ${shellSafe(tag)} --format '{{index .RepoDigests 0}}'`, BAKE_HOST_PROBE_TIMEOUT_MS, env.envAllow);
|
|
240
244
|
const repoDigest = stdout.trim();
|
|
241
245
|
const digest = repoDigest.includes("@") ? repoDigest.slice(repoDigest.indexOf("@") + 1) : null;
|
|
242
246
|
return { ok: true, digest };
|
|
@@ -259,16 +263,19 @@ function makeClock() {
|
|
|
259
263
|
async function main() {
|
|
260
264
|
const env = loadEnv();
|
|
261
265
|
const log = createLogger(process.env.LOG_LEVEL || "info");
|
|
266
|
+
logConfigDiagnostics(log);
|
|
267
|
+
installSecretEnvScrubRecorder({ logger: log });
|
|
262
268
|
log.info("bake_runner_start", {
|
|
263
269
|
imageApiBase: env.imageApiBase,
|
|
264
270
|
runnerId: env.runnerId,
|
|
265
271
|
recipeRef: env.recipeRef,
|
|
266
272
|
heartbeatMs: env.heartbeatMs,
|
|
267
273
|
minFreeGb: env.minFreeGb,
|
|
274
|
+
envAllow: env.envAllow,
|
|
268
275
|
});
|
|
269
276
|
const runner = new BakeRunner({
|
|
270
277
|
api: makeApiClient(env, log),
|
|
271
|
-
spawner: makeSpawner(),
|
|
278
|
+
spawner: makeSpawner(env.envAllow),
|
|
272
279
|
host: makeHostOps(env),
|
|
273
280
|
clock: makeClock(),
|
|
274
281
|
log,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ModelRefusalsSignal } from "@sema-agent/settings-schema";
|
|
1
2
|
import { CenterPromptSource, type SwappableDeps } from "@sema-agent/core";
|
|
2
3
|
import { buildPricing } from "../budget.js";
|
|
3
4
|
import { type PromptsDomainFaces } from "../prompts-domain-validate.js";
|
|
@@ -28,8 +29,8 @@ export interface CenterScenarioOverlayTargets {
|
|
|
28
29
|
/** overlay 前捕获的六内建名(centerScenarioDetails 的 shadow 判据)。 */
|
|
29
30
|
builtinScenarioNames: string[];
|
|
30
31
|
}
|
|
31
|
-
/** S-740 / S-741:config-center
|
|
32
|
-
export type SelfReportChange = "roster_landed" | "restart_signal";
|
|
32
|
+
/** S-740 / S-741 / S-768:config-center 持有的三样自报状态(fleet 心跳读它们)—— 闭集:加一样 = 写口与订阅方一起表态。 */
|
|
33
|
+
export type SelfReportChange = "roster_landed" | "restart_signal" | "model_refusals";
|
|
33
34
|
/** 跨段可变状态的**唯一**对外读口 + 三个原位动作。字段语义见各方法上的注释。 */
|
|
34
35
|
export interface ConfigCenterRuntime {
|
|
35
36
|
/** 配置源形态;`undefined` = 纯 env 部署(本运行时的所有分支不存在)。 */
|
|
@@ -97,6 +98,10 @@ export interface ConfigCenterRuntime {
|
|
|
97
98
|
onSelfReportChange(listener: (changed: SelfReportChange) => void): void;
|
|
98
99
|
/** /health.restart 与 fleet 心跳 `restart` 的**同一只**取值口(orchestrator 自动重启信号;settings-schema `RestartSignal` 形)。 */
|
|
99
100
|
restartState(): EngineRestartSignal | undefined;
|
|
101
|
+
/** S-768:`/health.modelRefusals` 与 fleet 心跳 `modelRefusals` 的**同一只**取值口(settings-schema 6.2.0 `ModelRefusalsSignal` 形:
|
|
102
|
+
* 名 → 码 + 组拒码)。描述**最近一次被判的那份中心候选目录**(每次候选 apply 的报告 `ApplyReport.modelRefusals` 投影;组拒时就是被拒
|
|
103
|
+
* 的那份 —— 引擎自己的拒因表不换代)。没有可报的拒 / 没有配置管道 / 还没判过候选 ⇒ `undefined`(键缺席)。值变了 ⇒ 通知自报订阅者。 */
|
|
104
|
+
modelRefusalsState(): ModelRefusalsSignal | undefined;
|
|
100
105
|
/** /health.modelPlaneDeferred:被扣着、还没有 next-boot handoff 的候选(codex R18 起的状态机)。S-668 波 0 起
|
|
101
106
|
* 模型面不再 defer,键名保留,今天承载的是 durable LKG 发布债与 skills 债(见 `planeDeferredNoHandoff`)。 */
|
|
102
107
|
planeDeferredState(): {
|
|
@@ -4,6 +4,8 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { loadRemoteExec } from "@sema-agent/settings-schema/node";
|
|
6
6
|
import { CENTER_CREDENTIAL_ENV, skillContentHash } from "@sema-agent/settings-schema";
|
|
7
|
+
import { stableStringify } from "@sema-agent/settings-schema/hash";
|
|
8
|
+
import { buildModelRefusalsSignal } from "../model-entry-refusal.js";
|
|
7
9
|
import { CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, } from "@sema-agent/core";
|
|
8
10
|
import { buildPricing } from "../budget.js";
|
|
9
11
|
import { applyCenterPlugins } from "../capabilities/center-plugins.js";
|
|
@@ -62,6 +64,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
62
64
|
let planeDeferredNoHandoff;
|
|
63
65
|
let bootSkillStale = false;
|
|
64
66
|
let pendingRestart;
|
|
67
|
+
let modelRefusals;
|
|
65
68
|
const cc = config.configCenter;
|
|
66
69
|
const bootCredential = cc?.credential.read();
|
|
67
70
|
const bootVerdict = bootCredential === undefined ? undefined : judgeCenterCredentialAtBoot(bootCredential);
|
|
@@ -162,6 +165,16 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
162
165
|
if (!same)
|
|
163
166
|
notifySelfReport("restart_signal");
|
|
164
167
|
};
|
|
168
|
+
const publishModelRefusals = (next) => {
|
|
169
|
+
const same = stableStringify(modelRefusals ?? null) === stableStringify(next ?? null);
|
|
170
|
+
modelRefusals = next;
|
|
171
|
+
if (!same)
|
|
172
|
+
notifySelfReport("model_refusals");
|
|
173
|
+
};
|
|
174
|
+
const recordApply = (rep) => {
|
|
175
|
+
applyLedger.recordApply(rep);
|
|
176
|
+
publishModelRefusals(buildModelRefusalsSignal(rep.modelRefusals));
|
|
177
|
+
};
|
|
165
178
|
let centerPrompts;
|
|
166
179
|
let pendingCatalogFaces;
|
|
167
180
|
const retryPendingCatalog = async (phase) => {
|
|
@@ -402,7 +415,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
402
415
|
generation: lkgGen,
|
|
403
416
|
report: (rep) => {
|
|
404
417
|
lkgReport = rep;
|
|
405
|
-
|
|
418
|
+
recordApply(rep);
|
|
406
419
|
},
|
|
407
420
|
});
|
|
408
421
|
applyLedger.applied(lkgGen, lkgR.effective.version);
|
|
@@ -471,7 +484,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
471
484
|
generation: bootGen,
|
|
472
485
|
report: (rep) => {
|
|
473
486
|
bootReport = rep;
|
|
474
|
-
|
|
487
|
+
recordApply(rep);
|
|
475
488
|
},
|
|
476
489
|
});
|
|
477
490
|
applyLedger.applied(bootGen, r.effective.version);
|
|
@@ -592,6 +605,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
592
605
|
selfReportListeners.push(listener);
|
|
593
606
|
},
|
|
594
607
|
restartState: () => pendingRestart,
|
|
608
|
+
modelRefusalsState: () => modelRefusals,
|
|
595
609
|
planeDeferredState: () => planeDeferredNoHandoff,
|
|
596
610
|
configApplyState: () => (configProvider ? applyLedger.view() : undefined),
|
|
597
611
|
centerManagedKeys: () => (configProvider ? centerManagedConfigKeys(latestEffective) : undefined),
|
|
@@ -800,7 +814,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
800
814
|
generation: tickGen,
|
|
801
815
|
report: (rep) => {
|
|
802
816
|
applyReport = rep;
|
|
803
|
-
|
|
817
|
+
recordApply(rep);
|
|
804
818
|
},
|
|
805
819
|
...(planeChanged ? { swapPlane: swapRunnerDeps } : {}),
|
|
806
820
|
});
|
|
@@ -932,7 +946,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
932
946
|
generation: lateGen,
|
|
933
947
|
report: (rep) => {
|
|
934
948
|
applyReportLate = rep;
|
|
935
|
-
|
|
949
|
+
recordApply(rep);
|
|
936
950
|
},
|
|
937
951
|
...(planeChangedLate ? { swapPlane: swapRunnerDeps } : {}),
|
|
938
952
|
});
|
|
@@ -12,6 +12,7 @@ import { installFileHistoryFailureRecorder } from "../file-history-disclosure.js
|
|
|
12
12
|
import { installSecretEnvScrubRecorder } from "../observability/secret-env-scrub.js";
|
|
13
13
|
import { setRunTerminalLogObserver } from "../observability/run-terminal.js";
|
|
14
14
|
import { setRedactionObserver } from "../trace/redact.js";
|
|
15
|
+
import { installOutboundDispatcher } from "../outbound-dispatcher.js";
|
|
15
16
|
export function runStage01() {
|
|
16
17
|
const config = loadConfig();
|
|
17
18
|
const runStaleFailure = crossInvariant("run-stale-heartbeat").check({ live: config, next: config });
|
|
@@ -56,6 +57,7 @@ export function runStage01() {
|
|
|
56
57
|
note: "LOCAL_DATA_ROOT moved the local backend's DATA root, but config.d / remote-exec.json are still read from the config root (LOCAL_DATA_ROOT is not consulted there). If you meant to move both, set CONFIG_LOCAL_DIR to the same directory.",
|
|
57
58
|
});
|
|
58
59
|
}
|
|
60
|
+
installOutboundDispatcher(logger);
|
|
59
61
|
return { config, localRoot, logger, metrics };
|
|
60
62
|
}
|
|
61
63
|
//# sourceMappingURL=stage-01-config.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { autoRunOnRecommendationEffective, defaultTaskRegistry } from "@sema-agent/core";
|
|
2
2
|
import { posIntEnv } from "../session-watch.js";
|
|
3
|
-
import { brainSummary } from "../brain.js";
|
|
3
|
+
import { brainSummary, buildFailoverFirstByteNotice } from "../brain.js";
|
|
4
4
|
import { resolveBindHost } from "../config.js";
|
|
5
5
|
import { buildWriteProtectionPosture } from "../write-protection.js";
|
|
6
6
|
import { isPricingConfigured } from "../budget.js";
|
|
@@ -151,6 +151,7 @@ export async function runStage10(ctx) {
|
|
|
151
151
|
planeDeferredState: () => configCenter.planeDeferredState(),
|
|
152
152
|
configApplyState: () => configCenter.configApplyState(),
|
|
153
153
|
centerCredentialState: () => configCenter.centerCredentialState(),
|
|
154
|
+
modelRefusalsState: () => configCenter.modelRefusalsState(),
|
|
154
155
|
mcpRevocations,
|
|
155
156
|
mcpRevocationState: () => configCenter.mcpRevocationState(),
|
|
156
157
|
configRefreshNow: () => configCenter.refreshNow(),
|
|
@@ -241,12 +242,16 @@ export async function runStage10(ctx) {
|
|
|
241
242
|
version: serviceVersion(),
|
|
242
243
|
readiness: () => readinessOf(deployment),
|
|
243
244
|
restart: () => deployment.restartState?.(),
|
|
245
|
+
modelRefusals: () => deployment.modelRefusalsState?.(),
|
|
244
246
|
...(fleetUsage ? { usage: fleetUsage } : {}),
|
|
245
247
|
logger: { info: (m, d) => logger.info(m, d ?? {}), warn: (m, d) => logger.warn(m, d ?? {}) },
|
|
246
248
|
});
|
|
247
249
|
configCenter.onSelfReportChange(() => void fleetClient?.announceNow());
|
|
248
250
|
if (fleetClient)
|
|
249
251
|
logger.info("fleet_client_started", { worker: config.configCenter?.worker, address: buildDisplaySafeUrl(process.env.FLEET_ADVERTISE_ADDRESS ?? "") });
|
|
252
|
+
const failoverFirstByte = buildFailoverFirstByteNotice(config);
|
|
253
|
+
if (failoverFirstByte)
|
|
254
|
+
logger.warn("failover_first_byte_lane", failoverFirstByte);
|
|
250
255
|
logger.info("listening", {
|
|
251
256
|
port: config.port,
|
|
252
257
|
bindHost: bindHost ?? "0.0.0.0/::(all interfaces)",
|