@sema-agent/server 7.69.0 → 7.71.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/README.md +4 -1
- package/README.zh-CN.md +4 -1
- package/USAGE.md +109 -10
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +3 -2
- package/dist/approval-content-kind.d.ts +13 -7
- package/dist/approval-content-kind.js +3 -1
- package/dist/approval-reconciler.d.ts +0 -54
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.js +3 -2
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/execution-env.js +12 -2
- package/dist/boot/memory-consolidation.d.ts +2 -1
- package/dist/boot/parked-revive-gate.d.ts +2 -0
- package/dist/boot/parked-revive-gate.js +15 -10
- package/dist/boot/resolve-spec.js +8 -3
- package/dist/boot/runner-deps.d.ts +50 -46
- package/dist/boot/runner-deps.js +12 -10
- package/dist/brain.d.ts +3 -1
- package/dist/brain.js +2 -1
- package/dist/budget.js +3 -2
- package/dist/config-catalog.d.ts +31 -2
- package/dist/config-catalog.js +44 -24
- package/dist/config-types.d.ts +31 -1
- package/dist/config-types.js +1 -0
- package/dist/config.js +15 -3
- package/dist/deployment-governance.d.ts +6 -3
- package/dist/deployment-governance.js +2 -1
- package/dist/execution-lane-caps.d.ts +51 -37
- package/dist/execution-lane-caps.js +23 -0
- package/dist/fleet/fleet-bus.d.ts +22 -1
- package/dist/fleet/fleet-bus.js +2 -1
- package/dist/http/routes/approvals-assistant.js +3 -12
- package/dist/http/routes/diagnostics.js +4 -0
- package/dist/http/routes/runs.js +2 -3
- package/dist/http/routes/tasks.js +22 -18
- package/dist/http/server.js +21 -7
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +4 -3
- package/dist/main.js +6 -4
- package/dist/memory-operator-faces.d.ts +1 -1
- package/dist/memory-scope.d.ts +24 -0
- package/dist/memory-scope.js +12 -3
- package/dist/observability/metrics.d.ts +4 -6
- package/dist/observability/metrics.js +5 -1
- package/dist/observability/permission-rule-events.d.ts +20 -0
- package/dist/observability/permission-rule-events.js +28 -0
- package/dist/observability/run-terminal-log.d.ts +13 -22
- package/dist/observability/run-terminal-log.js +187 -47
- package/dist/observability/tool-trace.d.ts +12 -0
- package/dist/observability/tool-trace.js +5 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
- package/dist/orchestration/workflow-agent-session-index.js +38 -5
- package/dist/parked-decide.d.ts +40 -1
- package/dist/parked-decide.js +19 -2
- package/dist/plugins/checkpoint-store-sql.js +5 -5
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/remote-env-adb.d.ts +11 -0
- package/dist/plugins/remote-env-adb.js +2 -0
- package/dist/plugins/remote-env-device.d.ts +9 -0
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +11 -0
- package/dist/plugins/remote-env-e2b.js +2 -0
- package/dist/plugins/remote-env-host.d.ts +11 -0
- package/dist/plugins/remote-env-host.js +2 -0
- package/dist/plugins/remote-env-k8s.d.ts +11 -0
- package/dist/plugins/remote-env-k8s.js +2 -0
- package/dist/plugins/remote-env-local-docker.d.ts +11 -0
- package/dist/plugins/remote-env-local-docker.js +2 -0
- package/dist/plugins/remote-env-ssh.d.ts +11 -0
- package/dist/plugins/remote-env-ssh.js +2 -0
- package/dist/plugins/store-backend.d.ts +36 -13
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +54 -18
- package/dist/plugins/store-contracts.js +17 -30
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +3 -10
- package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
- package/dist/plugins/workflow-run-store-sql.js +28 -1
- package/dist/posture-source.d.ts +23 -3
- package/dist/posture-source.js +1 -1
- package/dist/read-face-posture.d.ts +8 -1
- package/dist/read-face-posture.js +1 -0
- package/dist/run-cancel-context.d.ts +50 -1
- package/dist/run-cancel-context.js +22 -0
- package/dist/run-local.d.ts +14 -6
- package/dist/run-local.js +4 -1
- package/dist/runs.d.ts +0 -51
- package/dist/runs.js +40 -25
- package/dist/task-a2a.js +2 -2
- package/dist/task-mcp.js +2 -2
- package/dist/task-settings.d.ts +21 -8
- package/dist/task-settings.js +2 -2
- package/dist/tool-approval.d.ts +14 -8
- package/dist/tool-approval.js +3 -3
- package/dist/trace/core-keyset-guard.d.ts +135 -11
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/ledger-events.d.ts +22 -2
- package/dist/trace/ledger-events.js +6 -1
- package/dist/trace/ledger-sink.d.ts +10 -8
- package/dist/trace/ledger-sink.js +10 -2
- package/dist/trace/project.d.ts +24 -6
- package/dist/trace/project.js +5 -3
- package/dist/trace/redact.d.ts +28 -0
- package/dist/trace/redact.js +58 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -168,7 +168,7 @@ The server is configured entirely through environment variables. The most import
|
|
|
168
168
|
| `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) |
|
|
169
169
|
| `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 |
|
|
170
170
|
| `MODEL_CONNECT_TIMEOUT_MS` | `30000` | Gateway connect timeout |
|
|
171
|
-
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `
|
|
171
|
+
| `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) |
|
|
172
172
|
| `MODEL_IDLE_TIMEOUT_MS` | `300000` | Mid-stream idle timeout (`0` = off) |
|
|
173
173
|
| `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` (structured JSON logs) |
|
|
174
174
|
|
|
@@ -191,6 +191,9 @@ stop different things at different moments (per-task ceiling / per-principal ADM
|
|
|
191
191
|
not interrupt a run already executing / deployment usage window that does stop one at a turn
|
|
192
192
|
boundary) — the side-by-side table is in `USAGE.md`, worth reading before picking one.
|
|
193
193
|
|
|
194
|
+
Web search (deployment `WEB_SEARCH_*` env, per-request `settings.webSearch`, the two-lane precedence
|
|
195
|
+
and the tool-level error forms) is documented in `USAGE.md` §9.5.
|
|
196
|
+
|
|
194
197
|
## HTTP API overview
|
|
195
198
|
|
|
196
199
|
One row per endpoint family (not exhaustive):
|
package/README.zh-CN.md
CHANGED
|
@@ -151,7 +151,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
151
151
|
| `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) |
|
|
152
152
|
| `WRITE_PROTECTED_TABLE_REPLACE` | 未设(引擎缺省表) | **整表替换**写保护名表(core 的座按契约就是整表)。只收 JSON 数组 —— 刻意不给逗号简写:一个手滑的裸串会把 51 行换成 1 行。`[]` = 显式「完全不要这张表」。替换时 boot 期发一条**响亮**日志逐名列出被丢的缺省行(`write_protection_table_replaced`;空表走 `write_protection_table_disabled`)—— 想「加两行」请用 `WRITE_PROTECTED_EXTRA`。拒启条件同姊妹键,外加:两根同写 = 一条语义面两个写者 ⇒ 拒启 |
|
|
153
153
|
| `MODEL_CONNECT_TIMEOUT_MS` | `30000` | 网关连接超时 |
|
|
154
|
-
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `
|
|
154
|
+
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `600000` | 首 token 超时(开流不吐字的唯一看门狗;`0`=关。7.71.0 起由 `120000` 提高 —— 自托管后端长 prefill 下首字合理地就要等几分钟;要旧姿态显式设 `120000`) |
|
|
155
155
|
| `MODEL_IDLE_TIMEOUT_MS` | `300000` | 流中 idle 超时(`0` 关) |
|
|
156
156
|
| `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error`(结构化 JSON 日志) |
|
|
157
157
|
|
|
@@ -170,6 +170,9 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
170
170
|
(单任务硬闸 / per-principal **进场门**——不打断已在跑的 run / 部署级治理窗——会在 turn 边界停住它);
|
|
171
171
|
选之前请先读 `USAGE.md` 里的「三道 $ 闸的分工」对照表。
|
|
172
172
|
|
|
173
|
+
Web search(部署 env `WEB_SEARCH_*` 七键、per-request `settings.webSearch`、两车道优先级、工具层错误形)
|
|
174
|
+
见 `USAGE.md` §9.5。
|
|
175
|
+
|
|
173
176
|
## HTTP API 概览
|
|
174
177
|
|
|
175
178
|
一行一个端点族(非全量):
|
package/USAGE.md
CHANGED
|
@@ -63,6 +63,8 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
|
|
|
63
63
|
# ③ 分级超时:任务级 limits.maxWalltimeMs(6.0.0 起,毫秒)之下补两段,各记为可重试的 [network] → 触发重试/断路器/failover
|
|
64
64
|
MODEL_CONNECT_TIMEOUT_MS=8000 # fetch 迟迟不返响应头(网关连不上)→ 中止
|
|
65
65
|
MODEL_FIRST_TOKEN_TIMEOUT_MS=30000 # SSE 已开但迟迟不吐第一个 delta(网关 hang);reasoning 的首个 thinking 也算首 token
|
|
66
|
+
# 缺省 600000(7.71.0 起,原 120000):自托管后端长 prefill 下首字合理地要等几分钟,
|
|
67
|
+
# 120s 会把一次正常的慢启动判成 [network] 失败去重试。快失败部署显式设回 120000;0=关
|
|
66
68
|
MODEL_IDLE_TIMEOUT_MS=20000 # (1.40.1)出过 token 后中途卡死:每个 delta 重置,静默超时→中止(补 first-token 只管首字)
|
|
67
69
|
# ④ 断路器:主网关连败 N 次即"开路"→ 快速失败,让 failover 立刻切备(不再逐个等超时)
|
|
68
70
|
MODEL_CIRCUIT_BREAKER=true MODEL_CB_FAILURE_THRESHOLD=5 MODEL_CB_COOLDOWN_MS=30000
|
|
@@ -75,14 +77,34 @@ ANTHROPIC_MAX_RETRIES=10 # 云 Anthropic 腿
|
|
|
75
77
|
- **重试上限**(2026-07-31):两个键**不设就不传给引擎** —— 引擎默认当家。以前这里是 server 侧硬编码 `2`(openai 腿甚至零配置出口),而**显式传参压过引擎默认**,于是引擎抬默认对 server 部署毫无效果;第三方限流 provider 下"重试两次就放弃"正是由此而来。现在:不设=继承引擎默认(抬默认那天自动跟上),设了=按设的走。钉在 `test/brain.test.ts` 的「网关腿重试次数」两条上。
|
|
76
78
|
- 断路器状态:配了 `SESSION_BACKEND=mysql` 时自动用**跨副本共享态**(SQL `circuit_breaker` 表,写穿+刷新最终一致),否则进程内 Map。启动日志 `breakerState` 字段回显 `shared(mysql)`/`in-process`/`off`。
|
|
77
79
|
|
|
78
|
-
**可选 — WebSearch 后端(core 只留注入口,不自带任何 provider)**
|
|
80
|
+
**可选 — WebSearch 后端(core 只留注入口,不自带任何 provider;部署 env 七键)**
|
|
79
81
|
```bash
|
|
80
|
-
WEB_SEARCH_PROVIDER=brave|tavily|searxng #
|
|
81
|
-
WEB_SEARCH_API_KEY=… # brave/tavily
|
|
82
|
-
WEB_SEARCH_ENDPOINT=https://searx.example # searxng
|
|
83
|
-
WEB_SEARCH_MAX_RESULTS=10 # 1..20
|
|
84
|
-
WEB_SEARCH_TIMEOUT_MS=10000 # 单次搜索墙钟
|
|
82
|
+
WEB_SEARCH_PROVIDER=brave|tavily|searxng # 唯一的"装配开关":合法词才挂 WebSearch 工具;缺席/非法词 = 不装配(不是挂了报错)
|
|
83
|
+
WEB_SEARCH_API_KEY=… # brave/tavily 必需(searxng 不读这一键);只进 backend 闭包,永不进模型 prompt 或工具参数
|
|
84
|
+
WEB_SEARCH_ENDPOINT=https://searx.example # searxng 必需(实例地址);brave/tavily 下是可选的 base-URL 覆盖(代理/测试)
|
|
85
|
+
WEB_SEARCH_MAX_RESULTS=10 # 1..20(夹逼);缺席/非正数/非数字 → 用 backend 默认 10;小数向下取整
|
|
86
|
+
WEB_SEARCH_TIMEOUT_MS=10000 # 单次搜索墙钟(ms),下限 1000;缺席/非正数/非数字 → 用 backend 默认 10000
|
|
87
|
+
WEB_SEARCH_SEARXNG_PARAMS="engines=bing,duckduckgo;language=zh-CN" # 仅 searxng 腿消费;`;` 分隔 k=v 对;一个都解析不出 → 键整个不铸(不产出空对象)
|
|
88
|
+
WEB_SEARCH_PROBE_ON_BOOT=true # boot 期一次性真出网探活,默认 OFF;只认 "true"/"1"(trim+小写后比较),含糊值当没开
|
|
85
89
|
```
|
|
90
|
+
|
|
91
|
+
七键逐一(`src/plugins/web-search.ts` `webSearchConfigFromEnv` §294-309;类型/默认/坏值登记见
|
|
92
|
+
`src/config-catalog.ts:654-662`):
|
|
93
|
+
|
|
94
|
+
| env 键 | 类型 | 默认 | 缺席行为 | 坏值行为 |
|
|
95
|
+
|---|---|---|---|---|
|
|
96
|
+
| `WEB_SEARCH_PROVIDER` | 闭集(`brave`\|`tavily`\|`searxng`) | 无 | WebSearch 工具整体不装配(功能缺席,无 warn) | 三词之外的任何值 = 同缺席处理,不装配、不 warn(`:296-297`) |
|
|
97
|
+
| `WEB_SEARCH_API_KEY` | secret string | 无 | brave/tavily:后端仍会装配(装配只看 `PROVIDER`),**首次真实工具调用**时抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`(`:152`/`:164`);searxng:本键无消费点 | 空字符串同缺席(`env.WEB_SEARCH_API_KEY ?` 只认真值,`:303`) |
|
|
98
|
+
| `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`(`:193`) | 不做 URL 形校验——写不成 URL 由 `new URL()` 抛出,同样落到"首次调用才现形"那条路径,不拒启 |
|
|
99
|
+
| `WEB_SEARCH_MAX_RESULTS` | number | `10` | 用默认 10 | 非数字/≤0 → 回落默认 10;是数字则向下取整;最终值再夹在 `[1,20]`(设 999 也被 clamp 到 20,`:227`) |
|
|
100
|
+
| `WEB_SEARCH_TIMEOUT_MS` | number(ms) | `10000` | 用默认 10000 | 非数字/≤0 → 回落默认;最终值下限夹到 1000ms(`Math.max(1000,…)`,`:228`) |
|
|
101
|
+
| `WEB_SEARCH_SEARXNG_PARAMS` | string(`k=v;k=v`) | 无(不铸键 = 不传 `extraParams`,交给 core adapter 自己的缺省) | 键整个不铸 | 一个 `k=v` 对都解析不出(没有 `=`,或 `k`/`v` 任一为空)→ 同缺席,整串忽略,不拒启不 warn(`:260-277`);env 侧值恒为字符串,不会触发下面 per-request 那个"数组被误当对象吸收"的边角(见 §9.5) |
|
|
102
|
+
| `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`) |
|
|
103
|
+
|
|
104
|
+
**结论:七键无一在 boot 期拒启。** 与本仓其它安全轴旋钮(`MODEL_DEGRADE_ON`、思考档三键等,拼错即拒启)
|
|
105
|
+
姿势不同——WebSearch 挂不挂是**功能面**取舍,不是安全边界,七键统一走"回落/降级",不是"fail-closed 拒启"。
|
|
106
|
+
真正的配置错误(缺 key / endpoint 错)只在**首次真实工具调用**时才现形,以 tool_result 错误文本形式回给
|
|
107
|
+
模型,不是 HTTP 层错误、不影响 boot、也不使任务整体 `failed`(细节见 §9.5)。
|
|
86
108
|
- 结果是**不可信输入**:core 会 `delimitUntrusted` 围栏并**重新施加** `allowed_domains`/`blocked_domains`
|
|
87
109
|
地板,所以 backend 遵不遵守 `opts` 是优化不是正确性要求 —— 换 provider(包括换成自建 SearXNG)
|
|
88
110
|
不会削弱域名地板。
|
|
@@ -91,6 +113,7 @@ WEB_SEARCH_TIMEOUT_MS=10000 # 单次搜索墙钟
|
|
|
91
113
|
笔记本上起的 localhost SearXNG,云端 worker 够不着 —— 所以「本地 SearXNG 兜底」这一档**只适用
|
|
92
114
|
壳自 spawn 本地引擎的同机形**;壳连接远程引擎时该档整级跳过,由运维在**引擎侧**配 `WEB_SEARCH_*`。
|
|
93
115
|
用户手填一个集中式 SearXNG 地址是合法形,照常放行。
|
|
116
|
+
- **per-request 覆盖 + 两车道优先级 + 错误形全表**:见 §9.5。
|
|
94
117
|
|
|
95
118
|
- **大工具结果落盘**(core 1.47/1.49):单条工具结果 > ~20000 字符时 core 把全文移出上下文、只留预览+ref,模型用 `read_tool_result` 按需分页回取。配了 TiDB 时自动用**durable `tool_result` 表**(跨副本 wake 仍能取回全文;否则 core 进程内默认 = 跨副本 wake 取不到→降级到预览,不崩)。`TOOL_RESULT_TTL_SEC`(默认 86400)按 TTL 回收(要 ≥ run 可恢复期)。启动日志 `toolResultStore` 回显 `shared(tidb)`/`in-process`。
|
|
96
119
|
|
|
@@ -142,7 +165,7 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
|
|
|
142
165
|
- 成本计量**自动开**:从 config 的 `model.cost`(per-1M 绝对 USD)注入 `pricing`,core 算出权威的整数 `costMicroUsd`(避免浮点累计误差)。`/metrics` 新增:`model_cost_micro_usd_total{model}`(覆盖所有 brain 调用=主任务+异步+council 子任务的总花费)、`brain_first_token_ms`(网关 hang 早警)、`brain_call_latency_ms`、`tool_calls_total{name,ok}`、`budget_exceeded_total{code}`、`cost_quota_rejected_total`、`degraded_total{reason}`(1.40 降级)。
|
|
143
166
|
- **近预算降级 vs 硬闸**:降级(`MODEL_DEGRADE_TO`,到 `atCostFraction` 切便宜模型)是**撑长**预算、任务仍完成(出口质量下降、发 `task.degraded` 事件可告警);硬闸(`maxCostUsd` 全额)仍在,切了便宜模型还超全额 → `limits.max_cost_exceeded` 停。
|
|
144
167
|
- **`METRICS_TOKEN`**(可选,只读):设了它,`GET /metrics`+`/metrics/summary` 接受**它或** `SERVICE_AUTH_TOKEN`。**全 fleet 设同一个值** → 管理端(sema-web,规划名 sema-admin)用**一个** token 拉所有 worker 的指标,**无需持有各 worker 的全权 `SERVICE_AUTH_TOKEN`**(不破坏 secret 边界)。即使外泄也只暴露指标(只读)。
|
|
145
|
-
- **`GET /metrics/summary`**(token-gated,同 `/metrics`):`/metrics` 的**精炼 JSON**——`{model, runsActive, tasks{status}, tokensTotal, costUsd, costUsdByModel, taskDurationAvgSec, brainFirstTokenAvgMs, brainCallAvgMs, cacheHitRateAvg, rateLimited, costQuotaRejected, budgetExceeded, degraded, cascade, verifications, councilRuns, toolErrors}
|
|
168
|
+
- **`GET /metrics/summary`**(token-gated,同 `/metrics`):`/metrics` 的**精炼 JSON**——`{model, runsActive, tasks{status}, tokensTotal, costUsd, costUsdByModel, taskDurationAvgSec, brainFirstTokenAvgMs, brainCallAvgMs, cacheHitRateAvg, rateLimited, costQuotaRejected, budgetExceeded, limitCeilingAfterAnswer, degraded, cascade, verifications, councilRuns, toolErrors}`(`limitCeilingAfterAnswer` 自 7.71.0,按 `origin` 分桶的「答后撞顶」计数;与 `budgetExceeded` **总体不同** —— 前者每条引擎 run 都过,后者只由 HTTP 提交腿递增 —— 不要无条件相加)。给**轻量 fleet 看板**用(管理端 sema-web 的 fleet 页按 worker 拉、渲染卡片、按轮询算速率;**不用 Prometheus/Grafana**)。counter 是累计值、histogram 报均值。
|
|
146
169
|
- 预算闸是 core 强制的:`maxCostUsd` pre-call 估算(没花钱就拒)+ 流中途取消 + turn 边界(`limits.max_cost_exceeded`);`maxTokens` 超 → `limits.max_tokens_exceeded`(core 5.8.0 起 `budget.*` 前缀退役)。**review 网关**建议设 `MAX_TASK_COST_USD` 防单任务烧掉共享云 key。
|
|
147
170
|
- 🔴 **三道 $ 闸的分工(别拿进场门当中途停机用)**:三根旋钮名字都带 cost,但**判的时刻**和**能不能打断正在烧的那张单**完全不同。按「谁触发 / 什么时刻判 / 拒或停长什么样」对表:
|
|
148
171
|
|
|
@@ -301,7 +324,7 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
|
|
|
301
324
|
| `MEMORY_ORG_UNAVAILABLE_BACKOFF_MS` | `10000`(`[500, 600000]`) | 目录取不到之后的退避窗(只用于取数失败臂,不用于负结果) |
|
|
302
325
|
| `MEMORY_DELEGATION_EVIDENCE` | 缺省(=引擎自缺省 `static-face`) | **委派臂的证据标准**(core 5.45.0 起)。`static-face`=今日行为:委派的 attestation 缺失/未知**且**静态工具面够得着外部内容 ⇒ 标记本会话记忆为已污染(可能性即暴露)。`attested-only`=**只**豁免那一条静态面标记,且**真的豁免了一次**才响亮通告(`engine_notice` 的 `memory.delegation_static_mark_waived`,每个 prepared leg **至多**一次);送达 `external` attestation 照标、非委派的污染类工具照标、委派工具自身被分类为污染的照标。⚠️ 通告**不是 leg 计数器**:不含委派的 leg、污染面没挂载的 leg、送达了 attestation 的委派都不发 —— 看不到通告的常态含义是「没有可豁免的事」,不是「配置坏了」。坏值**启动期拒**。**部署席 only**(TaskSpec 无同名键,任务/受治脚本无法据此放松部署)。⚠️ **已接受的代价**:`attested-only` 下**后台**子代的真实外部接触不标记本会话(其内容经 TaskOutput / 任务通知注入 / AgentTranscript 摘要回流,三条都不带 attestation);异常收尾的前台子代同理。已标记的会话永不回滚清洗。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.delegationEvidence`(`null`=本部署未设,引擎自缺省) |
|
|
303
326
|
| `MEMORY_PROVENANCE` | 缺省(=引擎自缺省 `carry`) | **记忆 provenance 总开关**(core 5.46.0 起)。一个会话被判为**已暴露**之后,它的记忆写怎么处置。`carry`=引擎缺省:**普通**记忆写照常提交,但带上引擎铸的 `origin` 标记(随条目走后端/同步/导出包);指令形文件(feedback / pinned / triggers / applies-when)仍被扣下隔离;派生索引的会话散文照回滚;内容扫描门原样跑(**标记不是豁免**)。`off`=此前的行为:不铸 origin 标记,已暴露会话的 harvest **一条都不收**(整体隔离候人审)。⚠️ 已提交的标记在编辑时照样带下去 —— `off` 停的是**铸**,从不抹掉已记的事实。与 `MEMORY_DELEGATION_EVIDENCE` **正交**(四种组合全合法):那一键决定**什么时候**判会话为已暴露,本键决定判了之后**对写做什么**。坏值**启动期拒**(`OFF`/`false`/`none` 这类「看起来像关掉」的写法一律不折成缺省 —— 静默回默认 = 一台自认为「暴露会话零收录」的部署其实在照常收录)。**部署席 only**(TaskSpec 无同名键,任务/受治脚本无法据此改姿态);**不冻进 checkpoint**(resume 腿跟当前部署配置走)。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.provenance`(`null`=本部署未设,引擎自缺省) |
|
|
304
|
-
| `MEMORY_CAPTURE_POLICY` | 缺省(=引擎自缺省 `open`) | **记忆采集 opt-out 的部署姿态**(core 7.0.0 起)。任务可带 `memory.capture:"off"` 声明「本会话不进长期记忆」(一次性,resume 恒不采集,无反向拼写),本键决定怎么裁那条声明:`open`=按面值生效,**只有**该 principal 的显式 `false` verdict 拒(403 `memory.capture_optout_denied`),resolver 故障**放行**+披露(隐私轴 fail-safe:错拒=记了用户明说不要记的会话,不可逆);`governed`=verdict **必答**(混合车队:部分用户强制留存),故障/缺席**拒跑**(fail-closed);`capture-required`=全体声明拒(一行配置)。verdict 源**两条**(析取):①SQL 店后端上的授权表 `memory_optout_grant`(三层折叠:per-principal 行 → 部署缺省行 → 代码缺省 allow;管理面 `GET|PUT /v1/admin/memory-optout`(PUT=部署缺省行)· `PUT|DELETE …/:principal`,operator-only,能力位 `memoryOptOutGrant`,零缓存写后即生效);②非 dry-run 的 config center 在该 principal 的 entitlement caps 里发 `allowMemoryOptOut`。🔴 **`governed` 而两源皆无 ⇒ 启动期拒启**(无源 = 每个声明都被拒且没人知道为什么;接 `DB_BACKEND=mysql|pg`、接非 dry-run center,或改姿态)。⚠️ **center-only 源的语义**(只配了 center、没有 SQL 授权表):`allowMemoryOptOut` 在 entitlement schema 里是**可选键** —— center 不发它 = 该 principal 的 verdict **缺席** = `governed` 下每次 opt-out 恒拒(403 `memory.capture_optout_denied`),而 worker **照常起动**;这一形部署级证不死(per-principal 授权只有运行期才知道),所以 server 两处响亮:启动期一行 `memory_capture_governed_center_only_source`、运行期首次真缺席一行 `memory_capture_governed_verdict_absent`(每进程一次,纯披露不改判)。要确定性 verdict 就接 SQL 授权表(它三层折叠后恒答显式 boolean)。坏值**启动期拒**(`Governed`/`required`/`off` 一律不折成缺省:两种姿态的故障极性**相反**)。**部署席 only**;**不冻进 checkpoint**。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.capturePolicy`(`null`=本部署未设)。记录载体(core 7.0.2 起):SQL 后端(`DB_BACKEND=mysql\|pg`)上记录落 `session_capture_optout` 表(跨副本存活,随中央 schema 自建零旋钮)——**远端车道(e2b/k8s)的 `capture:"off"`
|
|
327
|
+
| `MEMORY_CAPTURE_POLICY` | 缺省(=引擎自缺省 `open`) | **记忆采集 opt-out 的部署姿态**(core 7.0.0 起)。任务可带 `memory.capture:"off"` 声明「本会话不进长期记忆」(一次性,resume 恒不采集,无反向拼写),本键决定怎么裁那条声明:`open`=按面值生效,**只有**该 principal 的显式 `false` verdict 拒(403 `memory.capture_optout_denied`),resolver 故障**放行**+披露(隐私轴 fail-safe:错拒=记了用户明说不要记的会话,不可逆);`governed`=verdict **必答**(混合车队:部分用户强制留存),故障/缺席**拒跑**(fail-closed);`capture-required`=全体声明拒(一行配置)。verdict 源**两条**(析取):①SQL 店后端上的授权表 `memory_optout_grant`(三层折叠:per-principal 行 → 部署缺省行 → 代码缺省 allow;管理面 `GET|PUT /v1/admin/memory-optout`(PUT=部署缺省行)· `PUT|DELETE …/:principal`,operator-only,能力位 `memoryOptOutGrant`,零缓存写后即生效);②非 dry-run 的 config center 在该 principal 的 entitlement caps 里发 `allowMemoryOptOut`。🔴 **`governed` 而两源皆无 ⇒ 启动期拒启**(无源 = 每个声明都被拒且没人知道为什么;接 `DB_BACKEND=mysql|pg`、接非 dry-run center,或改姿态)。⚠️ **center-only 源的语义**(只配了 center、没有 SQL 授权表):`allowMemoryOptOut` 在 entitlement schema 里是**可选键** —— center 不发它 = 该 principal 的 verdict **缺席** = `governed` 下每次 opt-out 恒拒(403 `memory.capture_optout_denied`),而 worker **照常起动**;这一形部署级证不死(per-principal 授权只有运行期才知道),所以 server 两处响亮:启动期一行 `memory_capture_governed_center_only_source`、运行期首次真缺席一行 `memory_capture_governed_verdict_absent`(每进程一次,纯披露不改判)。要确定性 verdict 就接 SQL 授权表(它三层折叠后恒答显式 boolean)。坏值**启动期拒**(`Governed`/`required`/`off` 一律不折成缺省:两种姿态的故障极性**相反**)。**部署席 only**;**不冻进 checkpoint**。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.capturePolicy`(`null`=本部署未设)。记录载体(core 7.0.2 起):SQL 后端(`DB_BACKEND=mysql\|pg`)上记录落 `session_capture_optout` 表(跨副本存活,随中央 schema 自建零旋钮)——**远端车道(e2b/k8s)的 `capture:"off"` 声明自此真持久**;`DB_BACKEND=local` 自 7.71.0 起在记忆引擎接线时供 core 的控制面文件三腿(钉在本部署记忆引擎那只控制面目录上,按记忆平面取用)⇒ 单机默认部署(`CONFIG_PROVIDER=local`、`REMOTE_EXEC` 未设)上声明与中途翻转 verb 从 409 转为真受理(此前该 409 在这一形上是结构性的:core 判「远端」看的是执行环境的接口形,host 车道同被判 remote);记忆面暗的部署本席位缺席而 core 也不走采集腿。今天仍答 409 `config.memory_capture_unsupported` 的只剩把记忆引擎接了却没给任何店的自建装配形。用户面四口:提交键 `memoryCapture:"off"`(声明形)· `POST /v1/runs/:id/memory/capture-optout`(live 中途翻转,owner 门)· `POST /v1/sessions/:id/memory/erase`(所有者自助抹除,select 硬限本会话)· `GET /v1/sessions/:id/memory-status`(状态读面,owner 门,五可选键诚实缺席)。契约全文 `docs/ASSISTANT-WIRE-CONTRACT.md` §12 |
|
|
305
328
|
|
|
306
329
|
⚠️ **拒启动**:多租户 + 记忆面点亮 + `projects[].defaultScopes` 里有 org 键 + 目录源缺席 ⇒ 启动报错
|
|
307
330
|
并点名 projectId(该部署的每个此类请求都会在 prepare 期整拒,响亮拒启动比静默全拒服务诚实)。
|
|
@@ -1243,7 +1266,8 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1243
1266
|
| HTTP `200` + `terminal.kind:"completed"` | 成功 | 取 `result` |
|
|
1244
1267
|
| `terminal.kind:"blocked"` | agent 主动报卡住 | 看 `terminal.reason`,补信息再发 |
|
|
1245
1268
|
| `terminal:{kind:"failed", code:"conflict"}` | 跨实例乐观锁丢失 | 直接重试(幂等) |
|
|
1246
|
-
| `terminal:{kind:"failed", code:"limits.max_walltime_exceeded"}` | 墙钟到限(6.0.0 起 `status:"timeout"` 退役;7.64.0 起平面 `status` 键退役) | 拆小任务 / 提高 `limits.maxWalltimeMs`(毫秒) |
|
|
1269
|
+
| `terminal:{kind:"failed", code:"limits.max_walltime_exceeded"}` | 墙钟到限(6.0.0 起 `status:"timeout"` 退役;7.64.0 起平面 `status` 键退役)。🔴 **7.71.0 / 引擎 7.13.0 收窄**:上限若在最后一轮 turn **已经给出干净答案之后**才响,run 记 `completed`、答案就是 `result`,**不铸本码**——那次停机的唯一痕迹是 `engine_notice` 的 `task.interrupt_unconsumed{origin:"walltime"}`(附录 D.3)。本码此后只在**工作被真切断**时出现。⚠️ 按码计数的运维面要把 `origin ∈ walltime\|turns` 的那一帧计入才守恒(server 侧对偶计量 `limit_ceiling_after_answer_total{origin}`,与 `budget_exceeded_total{code}` **相加**才是撞顶总量) | 拆小任务 / 提高 `limits.maxWalltimeMs`(毫秒) |
|
|
1270
|
+
| `terminal:{kind:"failed", code:"limits.max_turns_exceeded"}` | 轮数到帽。🔴 **同上收窄**(引擎 7.13.0):帽子若落在**那一轮答案自己**身上 ⇒ `completed` + `task.interrupt_unconsumed{origin:"turns"}`,不铸本码 | 提高 `limits.maxTurns` / 拆小任务 |
|
|
1247
1271
|
| HTTP `401` | 缺 `Authorization` / 缺 `x-agent-principal`(要求时) | 补头 |
|
|
1248
1272
|
| HTTP `403` / `404`(session/run) | 不是该 principal 的资源 | 用正确身份 |
|
|
1249
1273
|
| HTTP `409`(`/v1/runs`) | 同 session 已有活跃 run | 等它完成 / 用返回的 `activeTaskId` |
|
|
@@ -1392,5 +1416,80 @@ GET /v1/capabilities?permissionMode=default
|
|
|
1392
1416
|
**settings 层 kill-switch(件⑥)**:请求体 `settings.permissions.disableAutoMode` 受理 **`"disable"` / `true` / `false`** 三形(两套已发布契约的拼写:CC 250 的严极词 `"disable"`,与 `@sema-agent/sdk` `SettingsPermissions.disableAutoMode?: boolean`);受理集之外的值 400 `request.field_invalid`。
|
|
1393
1417
|
`"disable"` 与 `true` **同义**:把该 run 的生效模式由 auto 折成 default(座不写 ⇒ 不武装;tighten-only,只能关不能开);`false` = 显式「不禁」= **合法且无效果**(本键没有放宽臂——它不是 auto 的开关)。`disableBypassPermissionsMode` 本批不落点。
|
|
1394
1418
|
|
|
1395
|
-
**per-run 面**:本批不在 run 记录上加 per-run `autoMode` 座——引擎侧的武装结果 / 未武装原因(
|
|
1419
|
+
**per-run 面**:本批不在 run 记录上加 per-run `autoMode` 座——引擎侧的武装结果 / 未武装原因(含只有引擎知道的那些;core 7.12.0 起熔断族已退役)
|
|
1396
1420
|
将随 core 的状态座到货,届时 server 只投影不自算;此前 `armed` 是 server 侧三项可知判据的裁决。
|
|
1421
|
+
|
|
1422
|
+
### 9.5 Web search:两车道优先级、per-request `settings.webSearch`、错误形
|
|
1423
|
+
|
|
1424
|
+
**两车道,整段整取(不是逐键 merge)**:部署 env(`WEB_SEARCH_*`,§0)与 per-request `body.settings.webSearch`
|
|
1425
|
+
是唯二两条配置来源。当 per-request 那条腿"生效"(见下面的受理门槛)时,它产出一个**全新、完全独立**的
|
|
1426
|
+
`WebSearchBackendConfig`,**整只替换**掉 env 配出来的 backend —— 不是把 per-request 给的字段一个个覆盖到
|
|
1427
|
+
env 的 config 上,env 的其余字段(尤其是 `WEB_SEARCH_TIMEOUT_MS`)**不会**被继承到 per-request 那次调用里
|
|
1428
|
+
(`src/capabilities/scenarios.ts:264-266`):
|
|
1429
|
+
|
|
1430
|
+
```
|
|
1431
|
+
const reqWebSearch = deps.requirePrincipal !== true
|
|
1432
|
+
? webSearchConfigFromSettings(req.settings?.webSearch)
|
|
1433
|
+
: undefined;
|
|
1434
|
+
const webSearch = reqWebSearch ? createWebSearchBackend(reqWebSearch) : deps.webSearch;
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
`reqWebSearch` 非 `undefined`(即 `settings.webSearch.provider` 是合法词**且**本部署是单用户车道)时,
|
|
1438
|
+
`deps.webSearch`(env 配的 backend)整个不参与这次请求 —— 谁赢是**二选一**,不是字段级合并。
|
|
1439
|
+
|
|
1440
|
+
**受理门槛(单用户车道)**:🔒 per-request `settings.webSearch` 只在 `REQUIRE_PRINCIPAL !== true`(单用户 /
|
|
1441
|
+
TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结构性够不着**——不是被拒、也不留任何
|
|
1442
|
+
`warn`/`capabilities` 位说"你发的 webSearch 被忽略了"(对比 `mcpServers` 有 `capabilities.mcpInjection` +
|
|
1443
|
+
`mcp_injection_dropped` 日志可读;`settings.webSearch` 没有对应的能力探测位,`src/http/wire-types.ts:320-323`)。
|
|
1444
|
+
原因是安全边界(与多租户能力配置隔离规则同源):per-request `endpoint`/`apiKey` 是能力配置,多租户下一个租户把 `searxng`
|
|
1445
|
+
指向内网地址就是 SSRF,所以多租户上只认部署 env 配的 backend。
|
|
1446
|
+
|
|
1447
|
+
**`settings.webSearch` 键表**(`src/plugins/web-search.ts` `webSearchConfigFromSettings` §318-335;类型契约见
|
|
1448
|
+
`@sema-agent/sdk` 9.0.0 `settings.d.ts` `SettingsWebSearch`):
|
|
1449
|
+
|
|
1450
|
+
| 键 | 类型 | 必填 | 默认 | 消费点 |
|
|
1451
|
+
|---|---|---|---|---|
|
|
1452
|
+
| `provider` | `"brave"|"tavily"|"searxng"` | 是(缺席/非三词之一 ⇒ 整个 `settings.webSearch` 被丢,回落到 env 的 backend,**不报错**) | 无 | `:321-322` |
|
|
1453
|
+
| `apiKey` | string(明文) | brave/tavily 建议带(缺了首次调用才报错,见下表);searxng 不读 | 无(不继承 env 的 `WEB_SEARCH_API_KEY`) | `:326` |
|
|
1454
|
+
| `endpoint` | string | searxng 必需(缺了首次调用才报错);brave/tavily 可选覆盖 | 无(不继承 env 的 `WEB_SEARCH_ENDPOINT`) | `:327` |
|
|
1455
|
+
| `searxngParams` | `Record<string,string>`(对象)或 `"k=v;k=v"`(字符串) | 否 | 无 | `:330-333`,解析逻辑与 env 腿共用 `parseSearxngParams`(`:260-277`)。⚠️ **未列入已发布的 `@sema-agent/sdk` 8.8.0 `SettingsWebSearch` 类型**(该接口只有 `provider`/`apiKey`/`endpoint`/`maxResults` 四键、无开放下标)——server 侧代码认这个键,但当前发布的 TS 类型接不到它;手写 JSON 请求体仍可以发,server 会照常解析(源码头注自述:字段和消费点先落地,配置录入面尚未跟上,是一笔尚未还清的既有债务) |
|
|
1456
|
+
| `maxResults` | number | 否 | 10(与 env 同一 clamp,`[1,20]`,小数向下取整) | `:323` |
|
|
1457
|
+
| *(无)* `timeoutMs` | — | — | — | **per-request 车道没有这个字段**——即便部署用 `WEB_SEARCH_TIMEOUT_MS` 配了非默认超时,per-request 生效那次调用永远退回 backend 自己的默认(10000ms,下限 1000ms),因为"整段整取"意味着 env 的 `timeoutMs` 根本不在 `reqWebSearch` 那个新对象里 |
|
|
1458
|
+
| *(无)* `fetchImpl` | — | — | — | 同上,仅测试注入用,per-request 车道不可达 |
|
|
1459
|
+
|
|
1460
|
+
**门槛以外的键**:`settings.webSearch` 本身是 `TASK_SETTINGS_KEYS` 闭集里的受理顶层键
|
|
1461
|
+
(`src/task-settings.ts:309`,集外顶层键 400 `request.body_shape`),但 `webSearch` **自己的子键没有闭集门**
|
|
1462
|
+
——不像 `settings.permissions.*` 有 `TASK_SETTINGS_PERMISSION_KEYS` 逐键拒(`src/task-settings.ts:367-403`
|
|
1463
|
+
的 `taskSettingsKeyIssue` 只扫 `settings.*` 顶层和 `settings.permissions.*` 两层)。`settings.webSearch` 下
|
|
1464
|
+
塞一个上表之外的键(拼错的 `mxResults` 之类)**不会** 400,会被 `webSearchConfigFromSettings` 静默无视
|
|
1465
|
+
——门槛之外没有"未知键"这一说。
|
|
1466
|
+
|
|
1467
|
+
**错误形**(逐条对应任务书里的四问;全部经**首次真实工具调用**才现形,均为 WebSearch 工具的
|
|
1468
|
+
`tool_result` 错误文本,回到模型的对话里,**不是** HTTP 层 `errorCode`,**不会**使 `TaskResult` 整体
|
|
1469
|
+
`failed`,也不拒启/不拒收请求本身;core `dist/tools/web.js:840-919` `createWebSearchTool` 统一兜底):
|
|
1470
|
+
|
|
1471
|
+
| 触发条件 | 现象 | 是否 retryable(core `classifySearchFailure`) |
|
|
1472
|
+
|---|---|---|
|
|
1473
|
+
| 坏 `provider`(`settings.webSearch.provider` 非三词之一,或缺席) | **不是错误** —— `webSearchConfigFromSettings` 返回 `undefined`,整段回落到 env 配的 backend(env 也没配 ⇒ WebSearch 工具不装配,模型看不到这个工具);无 warn、无日志 | 不适用 |
|
|
1474
|
+
| 缺 `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` 落最后一条默认分支) |
|
|
1475
|
+
| `searxngParams` 非对象/非字符串(如数字、布尔、`null`) | 静默丢弃——`parseSearxngParams` 落到"非 object 且非 string"分支,返回 `undefined`,键整个不铸,**不报错、不 warn** | 不适用 |
|
|
1476
|
+
| ⚠️ `searxngParams` 是**数组**(如 `["engines=bing"]`) | `typeof [] === "object"` 让它落进对象分支:`Object.entries` 按数组下标产出 `{"0":"engines=bing"}`——**不被拒绝**,但产出的键是数字字符串、值是未拆分的原始 `"k=v"` 串,传给 core adapter 的 `extraParams` 后是一组没有意义的查询参数(不是解析出 `engines=bing`)。这条边角只在 per-request 车道可达(env 值恒为字符串,不会触发);已用与源码逐字一致的独立复现脚本核验(见收车档),未改代码 | 不适用(不是异常路径) |
|
|
1477
|
+
| 搜索后端返回**非 2xx HTTP 响应**(鉴权失败/限流/服务端 5xx 等) | 三个 provider 各自的 `!res.ok` 分支抛错,消息形固定为 `"<provider> search failed (<status>): <body首 200 字符>"`(brave/tavily,`:158`/`:174`)或 `"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`——都取决于上游返回的具体措辞,不是本仓能保证的 |
|
|
1478
|
+
| `endpoint` **网络层**不可达(连接被拒/DNS 解析失败/fetch 自身抛错,尚未拿到任何 HTTP 响应) | `fetch` 抛出的传输层错误(`ECONNREFUSED`/`ENOTFOUND`/`fetch failed` 等 Node/undici 标准措辞)被同一 `catch` 接住 | `true`——这条路径的错误文本天然含 `econnrefused`/`enotfound`/`fetch failed`/`dns` 等词,`classifySearchFailure` 的网络故障正则能命中(与上一行"已拿到 HTTP 响应但非 2xx"是两条不同的失败路径,别混淆) |
|
|
1479
|
+
|
|
1480
|
+
⚠️ **"坏 provider 静默回落"这条对调用方是否可观察,取决于部署 env 有没有配 backend**:上表第一行说
|
|
1481
|
+
"env 也没配 ⇒ 工具不装配、模型看不到这个工具"——那只是**部署 env 同样缺席**这一种情形。若部署 env
|
|
1482
|
+
**已经**配了合法 backend(例如 `WEB_SEARCH_PROVIDER=brave`),调用方 per-request 传一个拼错的 `provider`
|
|
1483
|
+
(如 `"searx"`)、或带着一个本想打到自建 SearXNG 的 `endpoint`,`settings.webSearch` 整段被丢弃、静默回落
|
|
1484
|
+
到 env 的 brave backend——**WebSearch 工具照常挂载、照常可用**,查询实际发给了 env 配的 provider,不是
|
|
1485
|
+
调用方以为自己指定的那个;工具存在这一事实本身**不能**证明 per-request 的 `provider`/`endpoint` 真的
|
|
1486
|
+
生效了,两种情形(per-request 生效 / per-request 被静默丢弃回落 env)在壳侧不可判别(此条经独立复现验证,
|
|
1487
|
+
见收车档)。
|
|
1488
|
+
|
|
1489
|
+
**`apiKey` 明文与 env 槽边界**:server 收到的 `settings.webSearch.apiKey` 是**明文字符串**,没有任何服务端
|
|
1490
|
+
密钥槽位/引用间接——收到什么字符串就直接进 backend 闭包(`:326`,与 `WEB_SEARCH_API_KEY` 同一条消费路径,
|
|
1491
|
+
`src/plugins/web-search.ts:63-64`)。**server 本批不改受理面**:明文字段的形状维持原样。调用方(壳)如何在
|
|
1492
|
+
自己机器上管理这份明文是调用方的事——例如 cli 壳侧的约定是在**调用方自己的环境**里按
|
|
1493
|
+
`SEMA_WEBSEARCH_KEY_<PROVIDER>` 这样的命名空间存放每个 provider 的 key,由壳在本地读出后把明文塞进请求体;
|
|
1494
|
+
这纯粹是**客户端约定**,本仓不读取、不校验、也不感知任何这类客户端侧 env 命名(这条命名事实来自任务书,
|
|
1495
|
+
本车未读 cli 源码核实,列入收车档「未闭环」)。
|
|
@@ -34,7 +34,7 @@ export declare function canBatchTransition(from: BatchState, to: BatchState): bo
|
|
|
34
34
|
* ── `legKey`(取代数值 `leg`,一腿一凭据)─────────────────────────────────────────────────────────────
|
|
35
35
|
* = `sha256(resume checkpoint token)` 的 hex,**首腿 = 空串**。一次 park→resume 的 token 就是这条腿的
|
|
36
36
|
* 天然身份:同 token 重投 = 同一腿(幂等,正确);新 park ⇒ 新 token ⇒ 新腿 ⇒ 新 askId。
|
|
37
|
-
* 取摘要而非原始 token 是因为 token 是能力凭据(本仓有 `
|
|
37
|
+
* 取摘要而非原始 token 是因为 token 是能力凭据(本仓有 `stripResumeToken` 专门把它从可重放账本里
|
|
38
38
|
* 剥掉),而 `askId` 是 wire 可见值 ⇒ **只存/只喂摘要**。用它当轴顺带消掉了原裁需要的 `task_run` 加列
|
|
39
39
|
* 与 `markResuming` 返回加宽(后者有第二个调用点 `http/routes/runs.ts:463`,加宽会误杀已成功 resume 的
|
|
40
40
|
* running run)。残留边界:同一 run 内**不经 checkpoint token** 的重入形若未来出现会得到同一 legKey ——
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -328,8 +328,9 @@ declare const DenialLimitFallbackSchema: z.ZodObject<{
|
|
|
328
328
|
autoDenyAfterMs: z.ZodNumber;
|
|
329
329
|
}, z.core.$strict>;
|
|
330
330
|
export type DenialLimitFallback = z.infer<typeof DenialLimitFallbackSchema>;
|
|
331
|
-
/** S-185(core 7.10.0 [ref])—— **分类器为什么答不了**。`cause` 是 core
|
|
332
|
-
* (`AUTO_MODE_UNAVAILABLE_CAUSES
|
|
331
|
+
/** S-185(core 7.10.0 [ref])—— **分类器为什么答不了**。`cause` 是 core 的闭集词
|
|
332
|
+
* (`AUTO_MODE_UNAVAILABLE_CAUSES`;core 7.12.0 起 = error | timeout,`breaker_open` 随熔断族退役),
|
|
333
|
+
* 本仓**刻意不枚举**它:词表的单一
|
|
333
334
|
* 属主在 core,抄一份的形会在 core 加词那天把新词静默吞成缺席 —— 而丢的正是「这次不可用是新出现的那
|
|
334
335
|
* 一类」这条信息(与 `AskRequest.origin` 顶注同一条纪律)。判据只到「非空串」这一层形门。
|
|
335
336
|
* `.strict()` + 单成员必填:core 将来给这只对象加成员时本读面当场判假、**整键不铸**,新成员必须由人
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [ref]
|
|
2
|
+
* [ref]:`contentKind` 投影键的**单一词属主**(server 侧)。
|
|
3
3
|
*
|
|
4
4
|
* core 在 `summarizeCheckpoint`(dist/core/checkpoint-store.js)对 tool_approval 门铸
|
|
5
|
-
* `contentKind: "content_ask"`,判据一行:`tool.toolName === ASK_USER_QUESTION_TOOL_NAME
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* `contentKind: "content_ask"`,判据一行:`tool.toolName === ASK_USER_QUESTION_TOOL_NAME`。
|
|
6
|
+
* 该常量 **core 从包根导出**(`@sema-agent/core` 的 `index.d.ts`;声明在 `dist/core/ask-question.d.ts`,
|
|
7
|
+
* 值 `"AskUserQuestion"`)⇒ 本仓 **re-export 那一枚**,一个字节都不自铸。
|
|
8
|
+
* (S-213①:此处旧注曾写「该常量未从 core 包根导出」并据此在全仓手抄了 13 处同字面量 —— 那句前提
|
|
9
|
+
* 失实,而每一份手抄都是一个独立写者:core 哪天改了保留工具名,手抄的那些当场与引擎分岔且全部静默。)
|
|
8
10
|
* 三条 server 投影面(SQL `listPending` 列读 / LOCAL `listPending` blob 读 / live `listLivePending`)
|
|
9
11
|
* 全部经 {@link contentKindOf} 派生,恒与 core 的 summarize 面同词 —— 词漂移由 parity 钉看住
|
|
10
12
|
* (`test/local-checkpoint-store.test.ts` [ref] parity 格:core `summarizeCheckpoint` 对 AskUserQuestion
|
|
11
|
-
* checkpoint
|
|
13
|
+
* checkpoint 的铸值必须逐字等于本函数的答案;同文件 S-213① 格再普查「src/ 里零第二份手抄字面量」)。
|
|
12
14
|
*
|
|
13
15
|
* 🔴 与 `hasBidiControls` 的「server 不重算」纪律**不冲突**:那一位是有界扫描的结果(重算可分家),
|
|
14
16
|
* 而 contentKind 是行上 core 铸的 `toolName` 的**全函数**(一行确定性判)——派生 ≠ 重扫。
|
|
15
|
-
* 🔴 OMIT 契约与同族键一致(`boundCallId`/`gateKind`/`hasBidiControls`
|
|
17
|
+
* 🔴 OMIT 契约与同族键一致(`boundCallId`/`gateKind`/`hasBidiControls` 的纪律):
|
|
16
18
|
* `"content_ask"` 或**缺席**,恒不铸 `null`/`false`;缺席 = 不是内容问句门(普通工具门 / 非工具 park /
|
|
17
19
|
* toolName 缺席的存量行)。展示/分诊/渲染分型用,永不参与 resume / gate / CAS。
|
|
18
20
|
*/
|
|
19
|
-
export
|
|
21
|
+
export { ASK_USER_QUESTION_TOOL_NAME } from "@sema-agent/core";
|
|
22
|
+
/** 保留工具名的 **SQL 字面量**形(单引号已包)。SQL 里也不许再手抄那个词:片段从同一枚常量派生,
|
|
23
|
+
* 与 `tool-approval.ts` 的 `APPROVAL_GATE_KINDS_SQL_IN`(闭集数组 → IN 片段)同一属主纪律。
|
|
24
|
+
* 值来自 core 的常量(标识符形,无引号/反斜杠),故直接内插不产生注入面。 */
|
|
25
|
+
export declare const ASK_USER_QUESTION_SQL_LITERAL = "'AskUserQuestion'";
|
|
20
26
|
/** 三读面共用的派生:AskUserQuestion 工具门 ⇒ `"content_ask"`;其余(含 null/缺席)⇒ undefined(键缺席)。 */
|
|
21
27
|
export declare function contentKindOf(toolName: string | null | undefined): "content_ask" | undefined;
|
|
22
28
|
//# sourceMappingURL=approval-content-kind.d.ts.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ASK_USER_QUESTION_TOOL_NAME } from "@sema-agent/core";
|
|
2
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "@sema-agent/core";
|
|
3
|
+
export const ASK_USER_QUESTION_SQL_LITERAL = `'${ASK_USER_QUESTION_TOOL_NAME}'`;
|
|
2
4
|
export function contentKindOf(toolName) {
|
|
3
5
|
return toolName === ASK_USER_QUESTION_TOOL_NAME ? "content_ask" : undefined;
|
|
4
6
|
}
|
|
@@ -1,57 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 流内审批协议([ref] §3.0)的**对账收敛器** —— [ref] 车5。
|
|
3
|
-
*
|
|
4
|
-
* 职责一句话:把 `PARKING`(窗到期中选、正在转投递面)这个**唯一的非终态中间态**收敛成
|
|
5
|
-
* `PARKED | DENIED | VOID`,并在崩溃后补位那些没人打 expire 的孤儿 `STREAM_PENDING` 行。
|
|
6
|
-
*
|
|
7
|
-
* ── 判据表 v2(设计稿 §9 尾的五臂汇总,逐字落地;每臂注读口)────────────────────────────────────────
|
|
8
|
-
* ① **身份三元组 ∧ hash 双等** ⇒ `bindBatch`(判别式返回;`ok:false` ⇒ 降级续判)
|
|
9
|
-
* 身份 = `sourceTaskId` 相等 ∧ `toolCallId` 相等 ∧ **因果下界**(不是等式)`cp.createdAtMs ≥
|
|
10
|
-
* ask.createdAtMs`;再 ∧ `cp.boundInputHash === ask.boundInputHash`。三维里只有前两维是等式,把时间
|
|
11
|
-
* 那一维读成等式会让合法 park 几乎命不中。逐字实现在 {@link classifyGateMatch};判据的**唯一
|
|
12
|
-
* 属主**是那个函数的头注,这里只列纲要,细则(祖先层 fold 否决、多候选取舍)不在此复述。
|
|
13
|
-
* 🔴 两处易错,写在这里免得下一个人照旧口径改码:
|
|
14
|
-
* · 承重的第一维是 **`sourceTaskId`**([ref] 件1,黑板 [ref]③①)——`sessionId` **不进身份等式**
|
|
15
|
-
* (委派子代的 ask 落行记的是投递上下文的根会话,park 却发生在子代自己的 sessionId 上,只按
|
|
16
|
-
* session 等值会张冠李戴),它在这一层只是读口 `findCheckpointCandidatesForAsk` 的入参。
|
|
17
|
-
* ⚠️ 但别据此把它当无用键:{@link isAncestorFoldMint} 的祖先层否决判的正是
|
|
18
|
-
* `sourceTaskId !== sessionId` —— 那是内存里的承重用法,只是不属于身份等式;
|
|
19
|
-
* · hash **任一侧缺席一律不 bind**(硬相等不放宽,理由同下)。缺席的**归因**分两级,别写成一句:
|
|
20
|
-
* 身份先判 —— 同身份候选一条都没有且候选集非空 ⇒ `identity_miss`(有对家但不是这一只,或读不出);
|
|
21
|
-
* 只有在身份这一层没被判掉时,hash 缺席才落 `single_mint` 三形
|
|
22
|
-
* (`ask_only` / `checkpoint_only` / `neither`)。`single_mint` 是可观测分类、不是放宽的命中;
|
|
23
|
-
* 把「只有一侧铸过」这格结构事实混进 `identity_miss` 的噪声底,运维就读不出两者的区别。
|
|
24
|
-
* 两道等式都不许放宽的原因不变(§9 C2:同 session 内 `toolCallId` 会被网关重用,身份不严会把旧 ask
|
|
25
|
-
* PARK 到别人的 resume 坐标上,而 `PARKED` 是不可回滚的终态)。读口 = `findCheckpointCandidatesForAsk`
|
|
26
|
-
* (§9 C4 窄谓词精确查,无分页假阴性);`unparseable` 候选**视同不匹配**(单行读不出不许打断整段
|
|
27
|
-
* 扫描,§8 C-6)。
|
|
28
|
-
* ② run 终局分臂(读口 `runStore.getRun`):`status ∈ {completed, failed, blocked}`(§8 A-1 词表修正 ——
|
|
29
|
-
* `cancelled` 不是 run 状态,取消 = `failed` + `errorCode`)——
|
|
30
|
-
* - `failed ∧ errorCode === "cancelled"`,或批行已 `ABORTED` ⇒ `VOID`(取消不是路由失败,§9 C3);
|
|
31
|
-
* - 其余终局 ⇒ `DENIED(routing_failure_fail_closed)`。**全仓唯一的 DENIED 写点**(grep 钉)。
|
|
32
|
-
* ②′ **bind-once 落选者**(codex round2 R2-3 增补,排在 ② 之前判):批已 `ROUTING_BOUND` 且中选者是
|
|
33
|
-
* 兄弟 ⇒ `VOID(batch_bound_elsewhere)` —— 迟到进 PARKING 的行(`bindBatch` 的原子事务连坐不到它)
|
|
34
|
-
* 结构上再也赢不了 bind,当下即可判;不判会让它滞留到 run 终局再被 ② 误报成路由失败。
|
|
35
|
-
* ③ else(run 还在跑 / suspended / needs_review / 无行且不满足 ④)⇒ **保持 PARKING** + `deferReconcile`
|
|
36
|
-
* touch(推 `updated_at_ms` 排到队尾,配合 `listByState` 的 `ORDER BY updated_at_ms ASC` 解队头堵塞,
|
|
37
|
-
* §8 D-4 / §9 C5)。**超时永不产生终态 denial**(约束②)。
|
|
38
|
-
* ④ adhoc 双谓词(`sessionId === taskId` ∧ `getRun` 无行,§8 C-2)∧ 窗过 + `ADHOC_GRACE` ⇒ `VOID`
|
|
39
|
-
* (归因 `adhoc_leg_no_durable_domain`)。
|
|
40
|
-
* ⑤ `createdAtMs` 量的 `ORPHAN_TTL` ⇒ `VOID(orphan_ttl_exceeded)` + warn ——「任何未在 TTL 内收敛的
|
|
41
|
-
* PARKING」的最后兜底(§8 A-1 放宽形,不限「getRun 无行」),约束①(遗孤最终可判)。
|
|
42
|
-
* 🔴 TTL 一律量 immutable 的 `createdAtMs`/`expiresAtMs`,**绝不量 `updatedAtMs`**(它被 ③ 的队列
|
|
43
|
-
* 轮转每轮刷新,量它的 TTL 永不到期,§9 C5)。
|
|
44
|
-
*
|
|
45
|
-
* ── 三条铁则 ────────────────────────────────────────────────────────────────────────────────────
|
|
46
|
-
* 1. **一经发布的终态不改义**:本模块只从 `PARKING`/`STREAM_PENDING` 出发,`PARKED/DECIDED/DENIED/VOID`
|
|
47
|
-
* 的行永不再被碰(CAS 的 `WHERE state=?` 谓词是机器保证,不靠调用序自觉)。
|
|
48
|
-
* 2. **幂等可重放**:两副本同扫无害——每条转移都是带 `from` 态的 CAS,单赢者;输者本轮什么都不做。
|
|
49
|
-
* 3. **宁可不命中,绝不错配**:判别不出(hash 缺席 / 候选 `unparseable` / 列与 blob 矛盾)一律落 ②③⑤,
|
|
50
|
-
* 绝不发一张别人的 resume 凭据。
|
|
51
|
-
*
|
|
52
|
-
* 命名(CLAUDE.md 工厂命名律):`decideReconcileAction`/`selectGateCandidate` 是纯判定函数;
|
|
53
|
-
* `createApprovalReconciler` 返回带方法的活对象 ⇒ `create*`。
|
|
54
|
-
*/
|
|
55
1
|
import type { AskRow, ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
|
|
56
2
|
import type { BatchState } from "./approval-ask-machine.js";
|
|
57
3
|
import type { CheckpointAskCandidate } from "./plugins/checkpoint-store-sql.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { CANCELLED_CODE } from "./run-cancel-context.js";
|
|
1
2
|
import { isTerminalRunStatus } from "./plugins/store-contracts.js";
|
|
2
3
|
import { DENY_REASONS, VOID_REASONS } from "./approval-deny-reasons.js";
|
|
3
4
|
import { buildRevokeFrame } from "./approval-card.js";
|
|
4
5
|
import { encodeCheckpointScope } from "./security.js";
|
|
5
|
-
const CANCELLED_ERROR_CODE = "cancelled";
|
|
6
6
|
const RECONCILE_STORE_TIMEOUT_MS = 10_000;
|
|
7
7
|
const RECONCILE_SEGMENT_BUDGET_MS = 30_000;
|
|
8
8
|
function withDeadline(op, label, timeoutMs = RECONCILE_STORE_TIMEOUT_MS) {
|
|
@@ -64,7 +64,7 @@ export function decideReconcileAction(input) {
|
|
|
64
64
|
return { kind: "void", reason: VOID_REASONS.BATCH_BOUND_ELSEWHERE };
|
|
65
65
|
}
|
|
66
66
|
if (run !== null && isTerminalRunStatus(run.status)) {
|
|
67
|
-
if (run.status === "failed" && run.errorCode ===
|
|
67
|
+
if (run.status === "failed" && run.errorCode === CANCELLED_CODE) {
|
|
68
68
|
return { kind: "void", reason: VOID_REASONS.RUN_CANCELLED };
|
|
69
69
|
}
|
|
70
70
|
return { kind: "deny", reason: DENY_REASONS.ROUTING_FAILURE };
|
package/dist/approval.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "@sema-agent/core";
|
|
2
2
|
import { operatorAsk } from "./operator-ask.js";
|
|
3
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
3
4
|
import { isExplicitPosture } from "./posture-source.js";
|
|
4
5
|
export function hasOperatorGateIntent(config) {
|
|
5
6
|
return (config.approvalRequire.length > 0 ||
|
|
@@ -58,7 +59,7 @@ export function createDurableAskPolicy(opts) {
|
|
|
58
59
|
if (remEarly !== undefined && remEarly <= 0) {
|
|
59
60
|
return operatorAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
|
|
60
61
|
}
|
|
61
|
-
if (opts.exempt && toolName !==
|
|
62
|
+
if (opts.exempt && toolName !== ASK_USER_QUESTION_TOOL_NAME) {
|
|
62
63
|
return (async () => {
|
|
63
64
|
let exempted = false;
|
|
64
65
|
try {
|
|
@@ -84,7 +85,7 @@ export function createDurableAskPolicy(opts) {
|
|
|
84
85
|
if (rem !== undefined && rem <= 0) {
|
|
85
86
|
return operatorAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
|
|
86
87
|
}
|
|
87
|
-
if (toolName !==
|
|
88
|
+
if (toolName !== ASK_USER_QUESTION_TOOL_NAME && budget > 0 && autoApproved < budget) {
|
|
88
89
|
autoApproved++;
|
|
89
90
|
return { action: "allow" };
|
|
90
91
|
}
|
|
@@ -4,6 +4,7 @@ import { loadConfig } from "../../config.js";
|
|
|
4
4
|
import { createBrain } from "../../brain.js";
|
|
5
5
|
import { buildPricing } from "../../budget.js";
|
|
6
6
|
import { RemoteContainerExecutionEnv } from "../../plugins/remote-env-e2b.js";
|
|
7
|
+
import { E2B_SANDBOX_HOME } from "../../execution-lane-caps.js";
|
|
7
8
|
import { createDurableAskPolicy } from "../../approval.js";
|
|
8
9
|
import { createLeaderRunner } from "../../leader/wire.js";
|
|
9
10
|
import { runOracle as runStandaloneOracle } from "./oracle.js";
|
|
@@ -67,6 +68,7 @@ const sh = (env) => async (cmd) => {
|
|
|
67
68
|
async function provisionSandbox(rt, role, cellId) {
|
|
68
69
|
const env = new RemoteContainerExecutionEnv({
|
|
69
70
|
apiKey: rt.e2bApiKey,
|
|
71
|
+
homeDir: E2B_SANDBOX_HOME,
|
|
70
72
|
...(rt.e2bTemplate ? { template: rt.e2bTemplate } : {}),
|
|
71
73
|
timeoutMs: rt.sandboxTimeoutMs ?? 600_000,
|
|
72
74
|
metadata: { s1Cell: cellId, s1Role: role },
|
|
@@ -20,6 +20,7 @@ import { reapOrphanWorktrees, withWorktreeIsolation } from "../plugins/worktree-
|
|
|
20
20
|
import { customPkgSourceFromEnv, derivePkgSourceEnv } from "../sandbox-pkg-source.js";
|
|
21
21
|
import { effectiveHostWorkspace } from "../task-cwd.js";
|
|
22
22
|
import { isSandboxPathAdjudicationLane, sandboxPathEnvSlots, withSandboxPathEnvSlot } from "./deferred-sandbox-path-env.js";
|
|
23
|
+
import { executionLaneHomeDir } from "../execution-lane-caps.js";
|
|
23
24
|
function boundedSessionSetter(map, maxEntries) {
|
|
24
25
|
return (sid, value) => {
|
|
25
26
|
map.delete(sid);
|
|
@@ -68,6 +69,8 @@ export function createSessionScopedRegistries(maxEntries = MAX_CWD_SESSIONS) {
|
|
|
68
69
|
}
|
|
69
70
|
export function createExecutionEnv(ctx) {
|
|
70
71
|
const { config, logger, metrics, taskAttachmentStore } = ctx;
|
|
72
|
+
const laneHomeDir = executionLaneHomeDir(config);
|
|
73
|
+
const homeDirSeat = laneHomeDir !== undefined ? { homeDir: laneHomeDir } : {};
|
|
71
74
|
const perTaskImage = new PerTaskImageRegistry();
|
|
72
75
|
const sessionEnvSelection = new SessionEnvironmentSelection();
|
|
73
76
|
const sessionScoped = createSessionScopedRegistries();
|
|
@@ -78,6 +81,7 @@ export function createExecutionEnv(ctx) {
|
|
|
78
81
|
if (config.remoteExec?.provider === "e2b") {
|
|
79
82
|
executionEnvFactory = e2bExecutionEnvFactory({
|
|
80
83
|
apiKey: config.remoteExec.apiKey,
|
|
84
|
+
...homeDirSeat,
|
|
81
85
|
...(config.remoteExec.template ? { template: config.remoteExec.template } : {}),
|
|
82
86
|
...(config.remoteExec.timeoutMs != null ? { timeoutMs: config.remoteExec.timeoutMs } : {}),
|
|
83
87
|
...(config.remoteExec.livenessMs != null ? { livenessMs: config.remoteExec.livenessMs } : {}),
|
|
@@ -94,6 +98,7 @@ export function createExecutionEnv(ctx) {
|
|
|
94
98
|
else if (config.remoteExec?.provider === "k8s") {
|
|
95
99
|
const k8sCfg = {
|
|
96
100
|
image: config.remoteExec.image,
|
|
101
|
+
...homeDirSeat,
|
|
97
102
|
...(config.remoteExec.apiUrl ? { apiUrl: config.remoteExec.apiUrl } : {}),
|
|
98
103
|
...(config.remoteExec.token ? { token: config.remoteExec.token } : {}),
|
|
99
104
|
...(config.remoteExec.caCert ? { caCert: config.remoteExec.caCert } : {}),
|
|
@@ -116,6 +121,7 @@ export function createExecutionEnv(ctx) {
|
|
|
116
121
|
executionEnvFactory = sshExecutionEnvFactory({
|
|
117
122
|
host: config.remoteExec.host,
|
|
118
123
|
username: config.remoteExec.username,
|
|
124
|
+
...homeDirSeat,
|
|
119
125
|
privateKey: config.remoteExec.privateKey,
|
|
120
126
|
...(config.remoteExec.port != null ? { port: config.remoteExec.port } : {}),
|
|
121
127
|
...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
|
|
@@ -128,6 +134,7 @@ export function createExecutionEnv(ctx) {
|
|
|
128
134
|
hub: ctx.deviceLane.hub,
|
|
129
135
|
store: ctx.deviceLane.store,
|
|
130
136
|
enrollment: ctx.deviceLane.enrollment,
|
|
137
|
+
...homeDirSeat,
|
|
131
138
|
...(config.remoteExec.execTimeoutMs != null ? { execTimeoutMs: config.remoteExec.execTimeoutMs } : {}),
|
|
132
139
|
perSessionCwd,
|
|
133
140
|
perSessionShellEnv,
|
|
@@ -138,6 +145,7 @@ export function createExecutionEnv(ctx) {
|
|
|
138
145
|
else if (config.remoteExec?.provider === "adb") {
|
|
139
146
|
executionEnvFactory = adbExecutionEnvFactory({
|
|
140
147
|
serial: config.remoteExec.serial,
|
|
148
|
+
...homeDirSeat,
|
|
141
149
|
...(config.remoteExec.adbPath ? { adbPath: config.remoteExec.adbPath } : {}),
|
|
142
150
|
...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
|
|
143
151
|
});
|
|
@@ -160,6 +168,7 @@ export function createExecutionEnv(ctx) {
|
|
|
160
168
|
if (hostScheduler)
|
|
161
169
|
logger.info(`R7: self-wake scheduler enabled (host lane), store=${config.schedulerStorePath ?? defaultSchedulerStorePath()}`);
|
|
162
170
|
const hostCfg = {
|
|
171
|
+
...homeDirSeat,
|
|
163
172
|
...(config.remoteExec.workspaceBase ? { workspaceBase: config.remoteExec.workspaceBase } : {}),
|
|
164
173
|
...(config.remoteExec.commandTimeoutMs != null ? { commandTimeoutMs: config.remoteExec.commandTimeoutMs } : {}),
|
|
165
174
|
...(hostScheduler ? { scheduler: hostScheduler } : {}),
|
|
@@ -181,6 +190,7 @@ export function createExecutionEnv(ctx) {
|
|
|
181
190
|
executionEnvFactory = localDockerExecutionEnvFactory({
|
|
182
191
|
image: config.remoteExec.image,
|
|
183
192
|
...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
|
|
193
|
+
...homeDirSeat,
|
|
184
194
|
...(config.remoteExec.dockerPath ? { dockerPath: config.remoteExec.dockerPath } : {}),
|
|
185
195
|
...(config.remoteExec.dockerHost ? { dockerHost: config.remoteExec.dockerHost } : {}),
|
|
186
196
|
...(config.remoteExec.memory ? { memory: config.remoteExec.memory } : {}),
|
|
@@ -206,14 +216,14 @@ export function createExecutionEnv(ctx) {
|
|
|
206
216
|
});
|
|
207
217
|
}
|
|
208
218
|
else if (config.remoteExec?.provider === "host") {
|
|
209
|
-
const gitBaseEnv = new RemoteHostExecutionEnv({ workspaceDir: config.worktreeIsolation.repoRoot, inheritEnv: "all", backgroundShell: config.requirePrincipal !== true });
|
|
219
|
+
const gitBaseEnv = new RemoteHostExecutionEnv({ ...homeDirSeat, workspaceDir: config.worktreeIsolation.repoRoot, inheritEnv: "all", backgroundShell: config.requirePrincipal !== true });
|
|
210
220
|
executionEnvFactory = withWorktreeIsolation(executionEnvFactory, {
|
|
211
221
|
repoRoot: config.worktreeIsolation.repoRoot,
|
|
212
222
|
...(config.worktreeIsolation.allowedRoots ? { allowedRoots: config.worktreeIsolation.allowedRoots } : {}),
|
|
213
223
|
...(config.worktreeIsolation.commit ? { commit: config.worktreeIsolation.commit } : {}),
|
|
214
224
|
baseEnvForGit: gitBaseEnv,
|
|
215
225
|
rootEnvAt: (dir) => {
|
|
216
|
-
const env = new RemoteHostExecutionEnv({ workspaceDir: dir, backgroundShell: config.requirePrincipal !== true });
|
|
226
|
+
const env = new RemoteHostExecutionEnv({ ...homeDirSeat, workspaceDir: dir, backgroundShell: config.requirePrincipal !== true });
|
|
217
227
|
return lspManager ? evictLspOnDestroy(env, lspManager) : env;
|
|
218
228
|
},
|
|
219
229
|
logger,
|
|
@@ -191,7 +191,8 @@ export declare function createMemoryConsolidationFaces(store: {
|
|
|
191
191
|
* 🔴 这只引擎是四只 operator 面里**真读名册**的那只:`consolidationEligibility` 第四臂
|
|
192
192
|
* (engine.js `listCaptureOptOutSessions`)拿它筛掉 opt-out 会话的贡献条目 —— SQL 部署上漏喂
|
|
193
193
|
* = 驱动器读文件空名册 ⇒ 蒸馏器放大用户明说不要记的内容(S-5 真实失效形;全站同批注入律,
|
|
194
|
-
* 机器钉 capture-optout-lane ②)
|
|
194
|
+
* 机器钉 capture-optout-lane ②)。S-215 起 local 车道在引擎接线时也供(core 文件三腿,同一只控制面);
|
|
195
|
+
* 缺席 ⇒ 记忆面暗(引擎未接线),与 Runner 同本。 */
|
|
195
196
|
captureRecordStore?: SessionCaptureRecordStore;
|
|
196
197
|
}): MemoryConsolidationFaces;
|
|
197
198
|
//# sourceMappingURL=memory-consolidation.d.ts.map
|
|
@@ -6,6 +6,8 @@ import { type SandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
|
6
6
|
/** 本腿读的配置切面(`ServiceConfig` 结构满足)。两个构造口各自的切面 + lane 判别式读的 `remoteExec`。 */
|
|
7
7
|
export interface ParkedReviveGateConfigView extends DeploymentGovernanceConfigView, ApprovalBaselineConfigView {
|
|
8
8
|
readonly remoteExec?: ServiceConfig["remoteExec"];
|
|
9
|
+
/** S-213④:`executionLaneHomeDir` 读的运维声明(切面必须点名本腿消费的每一个字段,否则窄视图字面量会静默丢掉旋钮)。 */
|
|
10
|
+
readonly remoteExecHomeDir?: ServiceConfig["remoteExecHomeDir"];
|
|
9
11
|
/** host 腿裁决 env 的 cwd 占位根(见下方 `hostGateCwd`)。 */
|
|
10
12
|
readonly localDataRoot?: ServiceConfig["localDataRoot"];
|
|
11
13
|
}
|