@sema-agent/server 7.82.0 → 7.83.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/USAGE.md +17 -14
- package/dist/bench/s1/arms.d.ts +4 -4
- package/dist/bench/s1/arms.js +1 -2
- package/dist/bench/s1/live-deps.js +0 -1
- package/dist/bench/s1/runner-ctx.d.ts +3 -3
- package/dist/bench/s1/tasks.d.ts +8 -4
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-10-http-server.d.ts +1 -0
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/config-catalog.js +2 -3
- package/dist/config.js +1 -0
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/server.d.ts +8 -0
- package/dist/leader/endpoint.js +2 -0
- package/dist/leader/leader.d.ts +33 -6
- package/dist/leader/leader.js +54 -45
- package/dist/leader/merge.d.ts +38 -29
- package/dist/leader/merge.js +45 -38
- package/dist/leader/wire.d.ts +2 -17
- package/dist/leader/wire.js +5 -58
- package/dist/observability/fail-open.d.ts +5 -5
- package/dist/observability/fail-open.js +5 -2
- package/dist/observability/run-terminal-log.d.ts +0 -7
- package/dist/observability/run-terminal-log.js +9 -20
- package/dist/plugins/adoption-log-sql.d.ts +3 -0
- package/dist/plugins/adoption-log-sql.js +5 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
- package/dist/plugins/approval-ask-store-sql.js +9 -5
- package/dist/plugins/background-agent-store-sql.d.ts +3 -0
- package/dist/plugins/background-agent-store-sql.js +7 -2
- package/dist/plugins/device-store-sql.d.ts +3 -0
- package/dist/plugins/device-store-sql.js +9 -4
- package/dist/plugins/ensure-index.d.ts +42 -0
- package/dist/plugins/ensure-index.js +81 -0
- package/dist/plugins/ensure-schema-lock-policy.d.ts +29 -0
- package/dist/plugins/ensure-schema-lock-policy.js +31 -0
- package/dist/plugins/image-bake-store-sql.d.ts +3 -0
- package/dist/plugins/image-bake-store-sql.js +6 -2
- package/dist/plugins/image-index-sql.d.ts +3 -0
- package/dist/plugins/image-index-sql.js +8 -4
- package/dist/plugins/leader-run-store-sql.d.ts +3 -0
- package/dist/plugins/leader-run-store-sql.js +6 -2
- package/dist/plugins/mailbox-store-sql.d.ts +3 -0
- package/dist/plugins/mailbox-store-sql.js +6 -1
- package/dist/plugins/memory-engine-pg.d.ts +4 -0
- package/dist/plugins/memory-engine-pg.js +6 -6
- package/dist/plugins/memory-engine-tidb.js +5 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +5 -0
- package/dist/plugins/memory-sync-store-pg.js +9 -2
- package/dist/plugins/memory-sync-store-tidb.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +1 -1
- package/dist/plugins/one-time-migrations.js +7 -14
- package/dist/plugins/outcome-ledger-sql.d.ts +3 -0
- package/dist/plugins/outcome-ledger-sql.js +7 -3
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +9 -5
- package/dist/plugins/pg-cost-quota.d.ts +3 -0
- package/dist/plugins/pg-cost-quota.js +5 -1
- package/dist/plugins/pg-pool.d.ts +23 -2
- package/dist/plugins/pg-pool.js +22 -37
- package/dist/plugins/pg-rate-limiter.d.ts +3 -0
- package/dist/plugins/pg-rate-limiter.js +5 -1
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +8 -4
- package/dist/plugins/remote-env-adb.d.ts +62 -3
- package/dist/plugins/remote-env-adb.js +27 -15
- package/dist/plugins/remote-env-host.js +1 -1
- package/dist/plugins/remote-shell.d.ts +7 -11
- package/dist/plugins/remote-shell.js +3 -3
- package/dist/plugins/retention-store-sql.d.ts +3 -0
- package/dist/plugins/retention-store-sql.js +7 -3
- package/dist/plugins/roster-store-sql.d.ts +3 -0
- package/dist/plugins/roster-store-sql.js +6 -1
- package/dist/plugins/schema-indexes.d.ts +21 -0
- package/dist/plugins/schema-indexes.js +25 -0
- package/dist/plugins/task-attachment-store.d.ts +3 -0
- package/dist/plugins/task-attachment-store.js +7 -2
- package/dist/plugins/tidb-pool.d.ts +15 -3
- package/dist/plugins/tidb-pool.js +38 -15
- package/dist/plugins/tool-result-store-sql.d.ts +3 -0
- package/dist/plugins/tool-result-store-sql.js +5 -1
- package/dist/plugins/web-search.d.ts +50 -2
- package/dist/plugins/web-search.js +9 -2
- package/dist/trace/project.d.ts +2 -1
- package/dist/trace/redact.d.ts +6 -0
- package/package.json +2 -2
package/USAGE.md
CHANGED
|
@@ -93,12 +93,12 @@ WEB_SEARCH_PROBE_ON_BOOT=true # boot 期一次性真出网探活,
|
|
|
93
93
|
|
|
94
94
|
| env 键 | 类型 | 默认 | 缺席行为 | 坏值行为 |
|
|
95
95
|
|---|---|---|---|---|
|
|
96
|
-
| `WEB_SEARCH_PROVIDER` | 闭集(`brave`\|`tavily`\|`searxng`) | 无 | WebSearch 工具整体不装配(功能缺席,无 warn) | 三词之外的任何值 = 同缺席处理,不装配、不 warn(
|
|
97
|
-
| `WEB_SEARCH_API_KEY` | secret string | 无 | brave/tavily:后端仍会装配(装配只看 `PROVIDER`),**首次真实工具调用**时抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`(
|
|
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`(
|
|
99
|
-
| `WEB_SEARCH_MAX_RESULTS` | number | `10` | 用默认 10 | 非数字/≤0 → 回落默认 10;是数字则向下取整;最终值再夹在 `[1,20]`(设 999 也被 clamp 到 20
|
|
100
|
-
| `WEB_SEARCH_TIMEOUT_MS` | number(ms) | `10000` | 用默认 10000 | 非数字/≤0 → 回落默认;最终值下限夹到 1000ms(`Math.max(1000
|
|
101
|
-
| `WEB_SEARCH_SEARXNG_PARAMS` | string(`k=v;k=v`) | 无(不铸键 = 不传 `extraParams`,交给 core adapter 自己的缺省) | 键整个不铸 | 一个 `k=v` 对都解析不出(没有 `=`,或 `k`/`v` 任一为空)→ 同缺席,整串忽略,不拒启不 warn(
|
|
96
|
+
| `WEB_SEARCH_PROVIDER` | 闭集(`brave`\|`tavily`\|`searxng`) | 无 | WebSearch 工具整体不装配(功能缺席,无 warn) | 三词之外的任何值 = 同缺席处理,不装配、不 warn(`webSearchConfigFromEnv` 的词表守卫 `isWebSearchProvider`)。**读面(S-382 起)**:`GET /v1/capabilities` 的 `webSearch.backend` 把这一格的生效值广告成闭集词(缺席/坏值都报 `"none"` —— 两者行为本就相同);端点/密钥/配额**不上 wire**,那些仍只在 operator 面 `GET /v1/config/catalog` |
|
|
97
|
+
| `WEB_SEARCH_API_KEY` | secret string | 无 | brave/tavily:后端仍会装配(装配只看 `PROVIDER`),**首次真实工具调用**时抛 `WEB_SEARCH_API_KEY is required for the <provider> provider`(`braveSearch` / `tavilySearch` 的首行守卫);searxng:本键无消费点 | 空字符串同缺席(`env.WEB_SEARCH_API_KEY ?` 只认真值,`webSearchConfigFromEnv` 的条件展开) |
|
|
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`(`searxngSearch` 的首行守卫) | 不做 URL 形校验——写不成 URL 由 `new URL()` 抛出,同样落到"首次调用才现形"那条路径,不拒启 |
|
|
99
|
+
| `WEB_SEARCH_MAX_RESULTS` | number | `10` | 用默认 10 | 非数字/≤0 → 回落默认 10;是数字则向下取整;最终值再夹在 `[1,20]`(设 999 也被 clamp 到 20 —— `createWebSearchBackend` 里的 `maxResults` clamp) |
|
|
100
|
+
| `WEB_SEARCH_TIMEOUT_MS` | number(ms) | `10000` | 用默认 10000 | 非数字/≤0 → 回落默认;最终值下限夹到 1000ms(`createWebSearchBackend` 里的 `Math.max(1000, …)`) |
|
|
101
|
+
| `WEB_SEARCH_SEARXNG_PARAMS` | string(`k=v;k=v`) | 无(不铸键 = 不传 `extraParams`,交给 core adapter 自己的缺省) | 键整个不铸 | 一个 `k=v` 对都解析不出(没有 `=`,或 `k`/`v` 任一为空)→ 同缺席,整串忽略,不拒启不 warn(`parseSearxngParams`);env 侧值恒为字符串,不会触发下面 per-request 那个"数组被误当对象吸收"的边角(见 §9.5) |
|
|
102
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
103
|
|
|
104
104
|
**结论:七键无一在 boot 期拒启。** 与本仓其它安全轴旋钮(`MODEL_DEGRADE_ON`、思考档三键等,拼错即拒启)
|
|
@@ -1542,6 +1542,9 @@ const webSearch = reqWebSearch ? createWebSearchBackend(reqWebSearch) : deps.web
|
|
|
1542
1542
|
TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结构性够不着**——不是被拒、也不留任何
|
|
1543
1543
|
`warn`/`capabilities` 位说"你发的 webSearch 被忽略了"(对比 `mcpServers` 有 `capabilities.mcpInjection` +
|
|
1544
1544
|
`mcp_injection_dropped` 日志可读;`settings.webSearch` 没有对应的能力探测位,`src/http/wire-types.ts:320-323`)。
|
|
1545
|
+
🔴 **别把 S-382 的新位读成这个探测位**:`GET /v1/capabilities` 的 `webSearch.backend`(S-382)说的是
|
|
1546
|
+
**部署默认后端是哪一只**,**不是**「我发的 per-request `settings.webSearch` 会不会被采纳」——后者在多租户上
|
|
1547
|
+
依旧是结构性够不着、无 warn、无位,这一段的结论一字未变。
|
|
1545
1548
|
原因是安全边界(与多租户能力配置隔离规则同源):per-request `endpoint`/`apiKey` 是能力配置,多租户下一个租户把 `searxng`
|
|
1546
1549
|
指向内网地址就是 SSRF,所以多租户上只认部署 env 配的 backend。
|
|
1547
1550
|
|
|
@@ -1550,11 +1553,11 @@ TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结
|
|
|
1550
1553
|
|
|
1551
1554
|
| 键 | 类型 | 必填 | 默认 | 消费点 |
|
|
1552
1555
|
|---|---|---|---|---|
|
|
1553
|
-
| `provider` | `"brave"|"tavily"|"searxng"` | 是(缺席/非三词之一 ⇒ 整个 `settings.webSearch` 被丢,回落到 env 的 backend,**不报错**) | 无 |
|
|
1554
|
-
| `apiKey` | string(明文) | brave/tavily 建议带(缺了首次调用才报错,见下表);searxng 不读 | 无(不继承 env 的 `WEB_SEARCH_API_KEY`) |
|
|
1555
|
-
| `endpoint` | string | searxng 必需(缺了首次调用才报错);brave/tavily 可选覆盖 | 无(不继承 env 的 `WEB_SEARCH_ENDPOINT`) |
|
|
1556
|
-
| `searxngParams` | `Record<string,string>`(对象)或 `"k=v;k=v"`(字符串) | 否 | 无 |
|
|
1557
|
-
| `maxResults` | number | 否 | 10(与 env 同一 clamp,`[1,20]`,小数向下取整) |
|
|
1556
|
+
| `provider` | `"brave"|"tavily"|"searxng"` | 是(缺席/非三词之一 ⇒ 整个 `settings.webSearch` 被丢,回落到 env 的 backend,**不报错**) | 无 | `webSearchConfigFromSettings` 的词表守卫(与 env 腿**同一只** `isWebSearchProvider`,词表属主 = `WEB_SEARCH_PROVIDERS`) |
|
|
1557
|
+
| `apiKey` | string(明文) | brave/tavily 建议带(缺了首次调用才报错,见下表);searxng 不读 | 无(不继承 env 的 `WEB_SEARCH_API_KEY`) | `webSearchConfigFromSettings` 的 `apiKey` 条件展开 |
|
|
1558
|
+
| `endpoint` | string | searxng 必需(缺了首次调用才报错);brave/tavily 可选覆盖 | 无(不继承 env 的 `WEB_SEARCH_ENDPOINT`) | `webSearchConfigFromSettings` 的 `endpoint` 条件展开 |
|
|
1559
|
+
| `searxngParams` | `Record<string,string>`(对象)或 `"k=v;k=v"`(字符串) | 否 | 无 | `webSearchConfigFromSettings` 的 `searxngParams` 臂,解析逻辑与 env 腿共用 `parseSearxngParams`。⚠️ **未列入已发布的 `@sema-agent/sdk` 8.8.0 `SettingsWebSearch` 类型**(该接口只有 `provider`/`apiKey`/`endpoint`/`maxResults` 四键、无开放下标)——server 侧代码认这个键,但当前发布的 TS 类型接不到它;手写 JSON 请求体仍可以发,server 会照常解析(源码头注自述:字段和消费点先落地,配置录入面尚未跟上,是一笔尚未还清的既有债务) |
|
|
1560
|
+
| `maxResults` | number | 否 | 10(与 env 同一 clamp,`[1,20]`,小数向下取整) | `webSearchConfigFromSettings` 的 `maxResults` 归一 |
|
|
1558
1561
|
| *(无)* `timeoutMs` | — | — | — | **per-request 车道没有这个字段**——即便部署用 `WEB_SEARCH_TIMEOUT_MS` 配了非默认超时,per-request 生效那次调用永远退回 backend 自己的默认(10000ms,下限 1000ms),因为"整段整取"意味着 env 的 `timeoutMs` 根本不在 `reqWebSearch` 那个新对象里 |
|
|
1559
1562
|
| *(无)* `fetchImpl` | — | — | — | 同上,仅测试注入用,per-request 车道不可达 |
|
|
1560
1563
|
|
|
@@ -1575,7 +1578,7 @@ TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结
|
|
|
1575
1578
|
| 缺 `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` 落最后一条默认分支) |
|
|
1576
1579
|
| `searxngParams` 非对象/非字符串(如数字、布尔、`null`) | 静默丢弃——`parseSearxngParams` 落到"非 object 且非 string"分支,返回 `undefined`,键整个不铸,**不报错、不 warn** | 不适用 |
|
|
1577
1580
|
| ⚠️ `searxngParams` 是**数组**(如 `["engines=bing"]`) | `typeof [] === "object"` 让它落进对象分支:`Object.entries` 按数组下标产出 `{"0":"engines=bing"}`——**不被拒绝**,但产出的键是数字字符串、值是未拆分的原始 `"k=v"` 串,传给 core adapter 的 `extraParams` 后是一组没有意义的查询参数(不是解析出 `engines=bing`)。这条边角只在 per-request 车道可达(env 值恒为字符串,不会触发);已用与源码逐字一致的独立复现脚本核验(见收车档),未改代码 | 不适用(不是异常路径) |
|
|
1578
|
-
| 搜索后端返回**非 2xx HTTP 响应**(鉴权失败/限流/服务端 5xx 等) | 三个 provider 各自的 `!res.ok` 分支抛错,消息形固定为 `"<provider> search failed (<status>): <body首 200 字符>"`(brave/tavily
|
|
1581
|
+
| 搜索后端返回**非 2xx HTTP 响应**(鉴权失败/限流/服务端 5xx 等) | 三个 provider 各自的 `!res.ok` 分支抛错,消息形固定为 `"<provider> search failed (<status>): <body首 200 字符>"`(brave/tavily,`braveSearch` / `tavilySearch` 的 `!res.ok` 分支)或 `"SearXNG <status> <statusText> from <url>"`(searxng,core adapter) | ⚠️ **实测几乎恒为 `"unknown"`,不是按状态码分档**:`classifySearchFailure` 的状态码分支要求 `http`/`status`/`code`/`error` 四词之一紧邻数字前(`\D{0,12}`内),但本仓三个 adapter 的消息把状态码写在 `failed (…)`/`SearXNG …` 之后,不触发该分支;独立复现脚本核验(见收车档)brave/tavily/searxng 的 429/500/403/403 全部落到最后一条默认分支 `retryable:"unknown"`。**真正被分类对的只有两条兜底正则**:上游错误体文本里若真含 `"rate limit"` 才判 429 类 `true`,含 `"timed out"`/`"timeout"` 才判 408 类 `true`——都取决于上游返回的具体措辞,不是本仓能保证的 |
|
|
1579
1582
|
| `endpoint` **网络层**不可达(连接被拒/DNS 解析失败/fetch 自身抛错,尚未拿到任何 HTTP 响应) | `fetch` 抛出的传输层错误(`ECONNREFUSED`/`ENOTFOUND`/`fetch failed` 等 Node/undici 标准措辞)被同一 `catch` 接住 | `true`——这条路径的错误文本天然含 `econnrefused`/`enotfound`/`fetch failed`/`dns` 等词,`classifySearchFailure` 的网络故障正则能命中(与上一行"已拿到 HTTP 响应但非 2xx"是两条不同的失败路径,别混淆) |
|
|
1580
1583
|
|
|
1581
1584
|
⚠️ **"坏 provider 静默回落"这条对调用方是否可观察,取决于部署 env 有没有配 backend**:上表第一行说
|
|
@@ -1588,8 +1591,8 @@ TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结
|
|
|
1588
1591
|
见收车档)。
|
|
1589
1592
|
|
|
1590
1593
|
**`apiKey` 明文与 env 槽边界**:server 收到的 `settings.webSearch.apiKey` 是**明文字符串**,没有任何服务端
|
|
1591
|
-
密钥槽位/引用间接——收到什么字符串就直接进 backend 闭包(
|
|
1592
|
-
`src/plugins/web-search.ts
|
|
1594
|
+
密钥槽位/引用间接——收到什么字符串就直接进 backend 闭包(`webSearchConfigFromSettings` 的 `apiKey` 条件展开,与 `WEB_SEARCH_API_KEY` 同一条消费路径,
|
|
1595
|
+
见 `src/plugins/web-search.ts` 的 `WebSearchBackendConfig.apiKey` 字段注)。**server 本批不改受理面**:明文字段的形状维持原样。调用方(壳)如何在
|
|
1593
1596
|
自己机器上管理这份明文是调用方的事——例如 cli 壳侧的约定是在**调用方自己的环境**里按
|
|
1594
1597
|
`SEMA_WEBSEARCH_KEY_<PROVIDER>` 这样的命名空间存放每个 provider 的 key,由壳在本地读出后把明文塞进请求体;
|
|
1595
1598
|
这纯粹是**客户端约定**,本仓不读取、不校验、也不感知任何这类客户端侧 env 命名(这条命名事实来自任务书,
|
package/dist/bench/s1/arms.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* 3. WORKER-ENV: the repair leg ALWAYS passes `workerEnv` so runRepairLoop's §5.1 isolation identity check is LIVE.
|
|
23
23
|
* 4. PAIRED-SEED: `seed` is best-effort N-repeat replicate, NEVER a GO criterion; rows are distributional.
|
|
24
24
|
* 5. COMPLETE TEAM COST: TEAM stats = Σ(worker stats.costBreakdown) + (merge.repair?.costUsd) +
|
|
25
|
-
* (
|
|
25
|
+
* 两条腿(7.83.0 起:冲突解决模型已随 [ref] ④ 删除,不再有第三条 resolver 腿)。
|
|
26
26
|
*
|
|
27
27
|
* 🔴 BLOCKED-but-honest structure: the REAL firm run is clay-gated (expensive E2B+DeepSeek). This file builds the
|
|
28
28
|
* REAL arm logic against INJECTABLE profile seams (`ProfileDeps`) so the deterministic smoke drives it with a MOCK
|
|
@@ -143,9 +143,9 @@ export declare function classifyRunStatus(input: {
|
|
|
143
143
|
*/
|
|
144
144
|
export declare function captureStats(stats: TaskResult["stats"] | undefined): EmittedStats;
|
|
145
145
|
/**
|
|
146
|
-
* 🔴 Corr #5 — the COMPLETE TEAM `stats`. Σ(worker stats.costBreakdown + humanReview) PLUS the merge
|
|
147
|
-
* `repair?.costUsd
|
|
148
|
-
*
|
|
146
|
+
* 🔴 Corr #5 — the COMPLETE TEAM `stats`. Σ(worker stats.costBreakdown + humanReview) PLUS the merge leg
|
|
147
|
+
* `repair?.costUsd`. Both legs, or TEAM C1 is undercounted ([ref]'s #1 confound). costUsd (float USD) →
|
|
148
|
+
* µUSD into llmRoot. (7.83.0:第三条腿 `conflictResolverCostUsd` 随冲突解决模型删除,见下。)
|
|
149
149
|
*/
|
|
150
150
|
export declare function captureTeamStats(reports: WorkerReport[], merge: MergeResult | undefined): EmittedStats;
|
|
151
151
|
/**
|
package/dist/bench/s1/arms.js
CHANGED
|
@@ -60,8 +60,7 @@ export function captureTeamStats(reports, merge) {
|
|
|
60
60
|
acc.humanReview.gates.push(...s.humanReview.gates);
|
|
61
61
|
}
|
|
62
62
|
const repairUsd = merge?.repair?.costUsd ?? 0;
|
|
63
|
-
|
|
64
|
-
acc.costBreakdown.llmRootMicroUsd += Math.round((repairUsd + resolverUsd) * 1e6);
|
|
63
|
+
acc.costBreakdown.llmRootMicroUsd += Math.round(repairUsd * 1e6);
|
|
65
64
|
return acc;
|
|
66
65
|
}
|
|
67
66
|
export async function driveSupSuspendResume(initial, deps, implSpec, trap, advanceClock) {
|
|
@@ -362,7 +362,6 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
|
|
|
362
362
|
const targetRef = "refs/heads/main";
|
|
363
363
|
process.env.LEADER_ENABLED = "true";
|
|
364
364
|
process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
|
|
365
|
-
process.env.LEADER_CONFLICT_ROUNDS = process.env.LEADER_CONFLICT_ROUNDS ?? "2";
|
|
366
365
|
const run = createLeaderRunner({
|
|
367
366
|
brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
|
|
368
367
|
...(s1DebugGit() ? { logger: { warn: (m, x) => console.error(`[S1_DEBUG_LEADER warn] ${m}`, x ? JSON.stringify(x).slice(0, 300) : ""), info: (m, x) => console.error(`[S1_DEBUG_LEADER info] ${m}`, x ? JSON.stringify(x).slice(0, 200) : "") } } : {}),
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* `maxTokens`/`maxCostUsd` is a silently-unread dead key), and TEAM's per-worker budgets sum to ≤ the solo budget — so no
|
|
9
9
|
* arm can win on a bigger pie. `assertBudgetMatch` (below) is the build-time guard the shape test runs.
|
|
10
10
|
*
|
|
11
|
-
* 🔴 BUDGET-MATCH for TEAM is NEW WIRING
|
|
12
|
-
*
|
|
13
|
-
* live test verbatim would yield an unbounded TEAM (the confound) and an empty `humanReview` (C2 = undefined).
|
|
11
|
+
* 🔴 BUDGET-MATCH for TEAM is NEW WIRING no leader live test does: the (now-deleted, 7.83.0 / [ref] ④) conflict
|
|
12
|
+
* live test set NO budget on `SubtaskSpec.spec` and NO `now` on any Runner. The harness STAMPS both (arms.ts) —
|
|
13
|
+
* copying a live test verbatim would yield an unbounded TEAM (the confound) and an empty `humanReview` (C2 = undefined).
|
|
14
14
|
*
|
|
15
15
|
* The real-infra construction (E2B worker/grader envs + a live brain) is deferred to the live make-real proof
|
|
16
16
|
* (`test/s1-value-live.test.ts`, gated on E2B+DEEPSEEK). This module only fixes the SHAPE + the budget invariant
|
package/dist/bench/s1/tasks.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 🔴 FIRM vs DIRECTIONAL (a HIGH review correction — do NOT reproduce the false "5 LIVE-VERIFIED" claim):
|
|
5
5
|
* Only C1 / C2 / C4 have a real E2B+DeepSeek live make-real test in `test/` today
|
|
6
|
-
* (supervisor-verify-fix-live / supervisor-honest-bound-live
|
|
6
|
+
* (supervisor-verify-fix-live / supervisor-honest-bound-live;C4 的那条 `leader-conflict-resolver-live`
|
|
7
|
+
* 已随 7.83.0 / clay [ref] ④ **删除** —— 它证的是「冲突解决模型合两边」,而那条通道不存在了;C4 今天
|
|
8
|
+
* 没有 live 证明位,按 DIRECTIONAL 计,不得再算作 FIRM).
|
|
7
9
|
* C3 (honest-PARTIAL) has NO dedicated live test; C5 (safety-escalate) is ONLY a unit test
|
|
8
10
|
* (`supervisor-safety-escalate.test.ts` header: "no E2B / no DeepSeek / no DB" — it only exercises
|
|
9
11
|
* `createDurableAskPolicy` in-memory, NEVER a real boundary). Counting either as a measured datapoint is the
|
|
@@ -84,9 +86,11 @@ export declare const C1_VERIFY_FIX: TrapSpec;
|
|
|
84
86
|
*/
|
|
85
87
|
export declare const C2_HONEST_BOUND: TrapSpec;
|
|
86
88
|
/**
|
|
87
|
-
* C4 — fan-out CONFLICT (two workers overlap one file → merge conflict
|
|
88
|
-
*
|
|
89
|
-
*
|
|
89
|
+
* C4 — fan-out CONFLICT (two workers overlap one file → merge conflict).
|
|
90
|
+
* 🔴 7.83.0 / clay [ref] ④:合不上不再起解决模型,leader 把 `conflict` 段(分支 + 冲突文件)交给用户、
|
|
91
|
+
* run 状态 `needs_human` ⇒ 这一格的 TEAM 臂在冲突形上**不会自己合好**。原 FIRM 证明位
|
|
92
|
+
* (`leader-conflict-resolver-live` live 测试)随通道删除,C4 退回 DIRECTIONAL。TEAM-primary。
|
|
93
|
+
* Objective COMMAND oracle = both functions exist AND compute correctly.
|
|
90
94
|
*/
|
|
91
95
|
export declare const C4_PARALLEL: TrapSpec;
|
|
92
96
|
/**
|
|
@@ -24,6 +24,7 @@ export declare function runStage07(ctx: Awaited<ReturnType<typeof runStage06>>):
|
|
|
24
24
|
maxTurns: number | undefined;
|
|
25
25
|
};
|
|
26
26
|
taskTimeoutSec: number;
|
|
27
|
+
webSearchProvider: "brave" | "tavily" | "searxng" | undefined;
|
|
27
28
|
approvalAskAudit: import("../approval-ask-audit-store.js").FileApprovalAskAuditStore | undefined;
|
|
28
29
|
centerRuntimeCapsResolver: ((principal: string | undefined) => Promise<import("@sema-agent/core").RuntimeCaps | undefined>) | undefined;
|
|
29
30
|
checkpointStore: import("../plugins/store-backend.js").CheckpointStoreFull | undefined;
|
|
@@ -158,6 +158,6 @@ export async function runStage07(ctx) {
|
|
|
158
158
|
const builtinScenarioNames = Object.keys(scenarioDetails);
|
|
159
159
|
configCenter.applyCenterScenarios({ scenarioDeps, scenarios, scenarioDetails, builtinScenarioNames });
|
|
160
160
|
logger.info("capabilities", { skills: skills.length, scenarios: Object.keys(scenarios), repoTools: Boolean(repoClient) });
|
|
161
|
-
return { ...ctx, approvalExemptionStore, approvalNonceStore, authorize, imageBakes, imageIndex, parkedReviveInheritedGate, parkedReviveTool, rateLimiter, resumeAnchorStore, runStore, scenarioDetails, scenarios, selectEnvTool, singleUserAutoAcceptBaseline, taskLimitCaps, taskTimeoutSec };
|
|
161
|
+
return { ...ctx, approvalExemptionStore, approvalNonceStore, authorize, imageBakes, imageIndex, parkedReviveInheritedGate, parkedReviveTool, rateLimiter, resumeAnchorStore, runStore, scenarioDetails, scenarios, selectEnvTool, singleUserAutoAcceptBaseline, taskLimitCaps, taskTimeoutSec, webSearchProvider: webSearchCfg?.provider };
|
|
162
162
|
}
|
|
163
163
|
//# sourceMappingURL=stage-07-capability-layer.js.map
|
|
@@ -37,6 +37,7 @@ export declare function runStage08(ctx: Awaited<ReturnType<typeof runStage07>>):
|
|
|
37
37
|
maxTurns: number | undefined;
|
|
38
38
|
};
|
|
39
39
|
taskTimeoutSec: number;
|
|
40
|
+
webSearchProvider: "brave" | "tavily" | "searxng" | undefined;
|
|
40
41
|
approvalAskAudit: import("../approval-ask-audit-store.js").FileApprovalAskAuditStore | undefined;
|
|
41
42
|
centerRuntimeCapsResolver: ((principal: string | undefined) => Promise<import("@sema-agent/core").RuntimeCaps | undefined>) | undefined;
|
|
42
43
|
checkpointStore: import("../plugins/store-backend.js").CheckpointStoreFull | undefined;
|
|
@@ -71,6 +71,7 @@ export declare function runStage09(ctx: Awaited<ReturnType<typeof runStage08>>):
|
|
|
71
71
|
maxTurns: number | undefined;
|
|
72
72
|
};
|
|
73
73
|
taskTimeoutSec: number;
|
|
74
|
+
webSearchProvider: "brave" | "tavily" | "searxng" | undefined;
|
|
74
75
|
approvalAskAudit: import("../approval-ask-audit-store.js").FileApprovalAskAuditStore | undefined;
|
|
75
76
|
centerRuntimeCapsResolver: ((principal: string | undefined) => Promise<import("@sema-agent/core").RuntimeCaps | undefined>) | undefined;
|
|
76
77
|
checkpointStore: import("../plugins/store-backend.js").CheckpointStoreFull | undefined;
|
|
@@ -71,6 +71,7 @@ export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>):
|
|
|
71
71
|
maxTurns: number | undefined;
|
|
72
72
|
};
|
|
73
73
|
taskTimeoutSec: number;
|
|
74
|
+
webSearchProvider: "brave" | "tavily" | "searxng" | undefined;
|
|
74
75
|
approvalAskAudit: import("../approval-ask-audit-store.js").FileApprovalAskAuditStore | undefined;
|
|
75
76
|
centerRuntimeCapsResolver: ((principal: string | undefined) => Promise<import("@sema-agent/core").RuntimeCaps | undefined>) | undefined;
|
|
76
77
|
checkpointStore: import("../plugins/store-backend.js").CheckpointStoreFull | undefined;
|
|
@@ -22,7 +22,7 @@ import { installShutdownHandlers } from "./shutdown.js";
|
|
|
22
22
|
import { bindServerLateSeats } from "./late-binds.js";
|
|
23
23
|
import { serviceVersion } from "../service-version.js";
|
|
24
24
|
export async function runStage10(ctx) {
|
|
25
|
-
const { approvalAskAudit, approvalExemptionStore, approvalNonceStore, authorize, backend, backgroundAgentStore, brain, breakerState, centerRuntimeCapsResolver, checkpointStore, config, configCenter, costQuota, deploymentHooks, deviceLane, drainState, durableEnabled, elicitation, fenceSessionOwner, fileHistoryStore, fleetBus, fleetLease, fleetUsage, governanceSeams, handslessRunner, hookAgent, hookLlm, hookWakeBus, imageBakes, imageIndex, instanceId, instrumentDegenerate, leaderEndpoint, localRoot, logger, lspManager, mcpRevocations, memoryBundleFaces, memoryComplianceFaces, memoryConsolidationFaces, memoryEngine, memoryExportBackend, memoryOptOutGrantStore, memoryOriginFaces, memoryPosture, memorySyncCursors, metrics, modelUsageTracker, orgMemoryAdmission, otelExporter, outcomeSink, ownerAware, parkedReviveInheritedGate, parkedReviveTool, perSessionCwd, perTaskImage, planCacheProbe, pricing, principalCaps, promptManifestTracker, purgeSession, question, rateLimiter, reaper, registryJwtVerifier, resumeAnchorStore, retentionLaneStarted, retentionLaneStore, ruleConsent, runStore, runner, runnerDeps, runtimeCapsPeekResolver, scenarioDetails, scenarios, selectEnvTool, sendFileLedger, sendUserFileEmitter, sendUserFileToolSpec, sessionAudit, sessionEnvSelection, sessionMemoryStatusFace, sessionPolicyStore, sessionShellGateRegistry, sessionTitler, sessionWatchRegistry, setSessionCwd, setSessionShellEnv, sharedMemoryStore, sideQueryAccounting, singleUserAutoAcceptBaseline, sqlEngineFacts, sqlWorkflowRunStore, staticWiring, storeBackendDegraded, subRunner, subagentSteerRegistry, taskAttachmentStore, taskLimitCaps, taskTimeoutSec, toolApproval, toolResultStore, usageWindowStore, workflowAgentRegistry, workflowAgentSessionIndex, workflowCompletionInbox, workflowJournalStore, workflowModelAllowlistSnapshot, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, workflowRunStore, workflowsCapable } = ctx;
|
|
25
|
+
const { approvalAskAudit, approvalExemptionStore, approvalNonceStore, authorize, backend, backgroundAgentStore, brain, breakerState, centerRuntimeCapsResolver, checkpointStore, config, configCenter, costQuota, deploymentHooks, deviceLane, drainState, durableEnabled, elicitation, fenceSessionOwner, fileHistoryStore, fleetBus, fleetLease, fleetUsage, governanceSeams, handslessRunner, hookAgent, hookLlm, hookWakeBus, imageBakes, imageIndex, instanceId, instrumentDegenerate, leaderEndpoint, localRoot, logger, lspManager, mcpRevocations, memoryBundleFaces, memoryComplianceFaces, memoryConsolidationFaces, memoryEngine, memoryExportBackend, memoryOptOutGrantStore, memoryOriginFaces, memoryPosture, memorySyncCursors, metrics, modelUsageTracker, orgMemoryAdmission, otelExporter, outcomeSink, ownerAware, parkedReviveInheritedGate, parkedReviveTool, perSessionCwd, perTaskImage, planCacheProbe, pricing, principalCaps, promptManifestTracker, purgeSession, question, rateLimiter, reaper, registryJwtVerifier, resumeAnchorStore, retentionLaneStarted, retentionLaneStore, ruleConsent, runStore, runner, runnerDeps, runtimeCapsPeekResolver, scenarioDetails, scenarios, selectEnvTool, sendFileLedger, sendUserFileEmitter, sendUserFileToolSpec, sessionAudit, sessionEnvSelection, sessionMemoryStatusFace, sessionPolicyStore, sessionShellGateRegistry, sessionTitler, sessionWatchRegistry, setSessionCwd, setSessionShellEnv, sharedMemoryStore, sideQueryAccounting, singleUserAutoAcceptBaseline, sqlEngineFacts, sqlWorkflowRunStore, staticWiring, storeBackendDegraded, subRunner, subagentSteerRegistry, taskAttachmentStore, taskLimitCaps, taskTimeoutSec, toolApproval, toolResultStore, usageWindowStore, webSearchProvider, workflowAgentRegistry, workflowAgentSessionIndex, workflowCompletionInbox, workflowJournalStore, workflowModelAllowlistSnapshot, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, workflowRunStore, workflowsCapable } = ctx;
|
|
26
26
|
const stores = {
|
|
27
27
|
runStore,
|
|
28
28
|
resumeAnchorStore: resumeAnchorStore ? resumeAnchorStore : undefined,
|
|
@@ -133,6 +133,7 @@ export async function runStage10(ctx) {
|
|
|
133
133
|
memoryPosture,
|
|
134
134
|
sqlEngineFacts,
|
|
135
135
|
writeProtection: buildWriteProtectionPosture(config),
|
|
136
|
+
webSearchProvider,
|
|
136
137
|
instanceId,
|
|
137
138
|
capabilities: {
|
|
138
139
|
version: serviceVersion(),
|
package/dist/config-catalog.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } from "@sema-agent/core";
|
|
2
2
|
import { THINKING_FORMATS } from "./model-compat.js";
|
|
3
3
|
import { RESILIENCE_DEFAULTS } from "./config-types.js";
|
|
4
|
-
import { webSearchConfigFromEnv } from "./plugins/web-search.js";
|
|
4
|
+
import { webSearchConfigFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
|
|
5
5
|
import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
|
|
6
6
|
export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
|
|
7
7
|
export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
|
|
@@ -184,7 +184,6 @@ export const CONFIG_CATALOG = [
|
|
|
184
184
|
r("KUBERNETES_SERVICE_HOST", "orchestration", "string", "平台注入的 in-cluster k8s API host(K8S_API_URL 缺席时用)"),
|
|
185
185
|
r("KUBERNETES_SERVICE_PORT", "orchestration", "string", "平台注入的 in-cluster k8s API 端口(缺省 443)"),
|
|
186
186
|
r("LEADER_BUDGET_USD", "orchestration", "number", "leader replan-lite 扇出花费帽(USD)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ replan-lite 自默认" }),
|
|
187
|
-
r("LEADER_CONFLICT_ROUNDS", "orchestration", "number", "leader 合并冲突修复轮数", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 场景配置 conflictRounds(默认 0)" }),
|
|
188
187
|
r("LEADER_ENABLED", "orchestration", "boolean", "leader 端点(POST/GET /v1/leader;需隔离执行后端 + durable leader_run 店,缺则拒启)", { staticDefault: "false", configKey: "leaderEnabled" }),
|
|
189
188
|
r("LEADER_FANOUT_ENABLED", "orchestration", "boolean", "leader 扇出许可(false=钉死单路由零 router 成本)", { staticDefault: "true", danger: BILL, configKey: "leaderFanoutEnabled" }),
|
|
190
189
|
r("LEADER_MEASURE_GATES", "orchestration", "boolean", "leader 合并 push-hold-on-signal + measure-drives-repair 门(严格 'true' 才开)", { staticDefault: "false" }),
|
|
@@ -447,7 +446,7 @@ export const CONFIG_CATALOG = [
|
|
|
447
446
|
r("WEB_SEARCH_ENDPOINT", "orchestration", "url", "web search 后端 endpoint 覆写(searxng 自建面)"),
|
|
448
447
|
r("WEB_SEARCH_MAX_RESULTS", "orchestration", "number", "web search 单次结果数上限", { derivedDefaultNote: "缺席/非正 ⇒ backend 默认" }),
|
|
449
448
|
r("WEB_SEARCH_PROBE_ON_BOOT", "orchestration", "boolean", "boot 期 web search 连通性探测(只认 true/1;含糊值当没开——猜错方向是凭空出网)", { staticDefault: "false" }),
|
|
450
|
-
r("WEB_SEARCH_PROVIDER", "orchestration", "enum", "web search 工具后端(设合法词=装配 WebSearch;其它/缺席=不装配)", { enumValues:
|
|
449
|
+
r("WEB_SEARCH_PROVIDER", "orchestration", "enum", "web search 工具后端(设合法词=装配 WebSearch;其它/缺席=不装配)", { enumValues: WEB_SEARCH_PROVIDERS, danger: SEC, resolve: (_c, env) => webSearchConfigFromEnv(env)?.provider ?? null }),
|
|
451
450
|
r("WEB_SEARCH_SEARXNG_PARAMS", "orchestration", "string", "searxng 额外查询参数(k=v 对;解析不出任何一对 ⇒ 键不铸)"),
|
|
452
451
|
r("WEB_SEARCH_TIMEOUT_MS", "orchestration", "number", "web search 单次超时(ms)", { derivedDefaultNote: "缺席/非正 ⇒ backend 默认" }),
|
|
453
452
|
r("WORKFLOW_AGENTS_READONLY", "orchestration", "boolean", "workflow 子 agent 只读钳(TOB 保守旋钮;默认同权 CC parity)", { staticDefault: "false", danger: SEC, configKey: "workflowAgentsReadOnly" }),
|
package/dist/config.js
CHANGED
|
@@ -119,6 +119,7 @@ const RETIRED_ENVS = [
|
|
|
119
119
|
["OA_ISSUE_REPO", "the OA scenario was removed in 5.0.0", "removed"],
|
|
120
120
|
["OA_ISSUE_LABELS", "the OA scenario was removed in 5.0.0", "removed"],
|
|
121
121
|
["APPROVAL_POLL_MS", "the OA poll-approval lane was removed in 5.0.0; the durable checkpoint lane is the only gate", "removed"],
|
|
122
|
+
["LEADER_CONFLICT_ROUNDS", "the bounded merge-conflict-resolution agent was deleted in 7.83.0 按 C-R63 删除,合不上交用户:leader 交回 result.conflict(分支 + 冲突文件),run 状态 needs_human", "removed"],
|
|
122
123
|
];
|
|
123
124
|
function rejectRetiredEnvs() {
|
|
124
125
|
const hit = RETIRED_ENVS.filter(([o]) => process.env[o] !== undefined && process.env[o] !== "");
|
|
@@ -12,6 +12,7 @@ import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
|
12
12
|
import { projectMemoryEngineCapability } from "../../memory-posture.js";
|
|
13
13
|
import { projectWriteProtectionCapability } from "../../write-protection.js";
|
|
14
14
|
import { projectSqlEngineCapability } from "../../sql-engine-posture.js";
|
|
15
|
+
import { projectWebSearchCapability } from "../../plugins/web-search.js";
|
|
15
16
|
import { workflowModelAllowlistFor, selfOrchestrationDenial } from "../../task-workflow.js";
|
|
16
17
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
17
18
|
import { judgeAutoModeArming } from "../../auto-mode-face.js";
|
|
@@ -76,6 +77,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
76
77
|
memoryWrite: false,
|
|
77
78
|
memoryEngine: projectMemoryEngineCapability(deps.memoryPosture),
|
|
78
79
|
sql: projectSqlEngineCapability(deps.sqlEngineFacts?.()),
|
|
80
|
+
webSearch: projectWebSearchCapability(deps.webSearchProvider),
|
|
79
81
|
sessionBackgroundable: Boolean(deps.runStore),
|
|
80
82
|
runMemoryCaptureOptOut: Boolean(deps.runStore),
|
|
81
83
|
sharedMemory: Boolean(deps.sharedMemoryStore && deps.orgMemoryDirectory),
|
package/dist/http/server.d.ts
CHANGED
|
@@ -689,6 +689,14 @@ export interface ServiceDeploymentDeps {
|
|
|
689
689
|
* boot 期算一次的**存活对象**(纯 config 派生,运行期不变;core 座没有热改腿)。
|
|
690
690
|
* 缺席 = 这个进程不是由 composition root 装起来的(测试夹具形)⇒ 诊断面报 null、能力位报 null。 */
|
|
691
691
|
writeProtection?: import("../write-protection.js").WriteProtectionPosture;
|
|
692
|
+
/** S-382:**部署侧 WebSearch 后端**(`WEB_SEARCH_PROVIDER`)—— `GET /v1/capabilities` 的 `webSearch` 位的
|
|
693
|
+
* 唯一席位。值**逐字**取自 boot 期真正拿去造后端的那份 `webSearchConfigFromEnv()` 产物
|
|
694
|
+
* (`boot/stage-07-capability-layer.ts`),所以「能力位说 brave、装配里装的是 tavily」结构上不可能。
|
|
695
|
+
* 缺席 = 本部署没有搜索后端(未设 / 拼错 / 装配没喂席位)⇒ 窄投影报 `{backend:"none"}`;
|
|
696
|
+
* 折词的属主与全部判据在 `plugins/web-search.ts` 的 `projectWebSearchCapability` 头注。
|
|
697
|
+
* ⚠️ 席位只承载**部署默认**:单用户车道上每请求的 `settings.webSearch` 压过它,但那条腿**不经过本席**
|
|
698
|
+
* (它在场景装配里每请求现算),所以本位不会随调用方漂。 */
|
|
699
|
+
webSearchProvider?: import("../plugins/web-search.js").WebSearchProvider;
|
|
692
700
|
/** ① core ruling — SPLIT: `capabilities.workflows` = the ENGINE-CAN axis, boot-computed from core's own
|
|
693
701
|
* `workflowsCapability(deps)` (hardened script runner ∧ governance), NOT the `Boolean(workflowRunStore)` store
|
|
694
702
|
* proxy. Orthogonal to `workflowsList` (the durable-list axis = `workflowRunStore`). Falls back to the store
|
package/dist/leader/endpoint.js
CHANGED
|
@@ -3,6 +3,8 @@ import { redactErrorMessage, redactHead, redactLeaderResult, redactLeaderResultW
|
|
|
3
3
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
4
4
|
import { leaderIdemKey } from "../plugins/leader-run-store-sql.js";
|
|
5
5
|
function statusForLeaderResult(result) {
|
|
6
|
+
if (result.conflict)
|
|
7
|
+
return "needs_human";
|
|
6
8
|
switch (result.repairTerminal) {
|
|
7
9
|
case "candidate_only":
|
|
8
10
|
case "needs_human_oracle":
|
package/dist/leader/leader.d.ts
CHANGED
|
@@ -104,10 +104,13 @@ export interface LeaderDeps {
|
|
|
104
104
|
/** Replan-lite ([ref] §6): bounded, DETERMINISTIC reactions — no runtime DAG surgery. When set:
|
|
105
105
|
* worker failed → redispatch ×1 on a FRESH env (clean base; a crashed worker's half-written workspace is
|
|
106
106
|
* never reused) → still failed = collapse-remainder-to-single; verify FAIL (repair already bounded inside
|
|
107
|
-
* the verify loop) → collapse;
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
107
|
+
* the verify loop) → collapse; budgetUsd exceeded → stop fan-out, single continues. Multi-trigger
|
|
108
|
+
* arbitration priority (C6): suspended > budget > verify-fail > worker-failed — only the highest-priority
|
|
109
|
+
* reaction runs. (The suspended reaction itself — cancel the others, exempt the suspended worker, surface
|
|
110
|
+
* its token — is unconditional, not gated here.)
|
|
111
|
+
*
|
|
112
|
+
* 🔴 S-113 / clay [ref] ④(7.83.0):**merge-conflict 不再是一条 replan 臂**。worker 分支合不上 ⇒ 不起
|
|
113
|
+
* collapse 模型,`conflict` 段(分支 + 冲突文件)交给**用户**,见 {@link LeaderResult.conflict}。 */
|
|
111
114
|
replan?: {
|
|
112
115
|
/** Fan-out budget (USD, sum of worker costs). Exceeded → stop fan-out, collapse to single (C5). */
|
|
113
116
|
budgetUsd?: number;
|
|
@@ -154,12 +157,36 @@ export interface LeaderResult {
|
|
|
154
157
|
route?: "single" | "fanout";
|
|
155
158
|
/** Set when planning/provisioning failed before fan-out. */
|
|
156
159
|
error?: string;
|
|
157
|
-
/** Replan-lite ([ref] §6): which trigger won arbitration + what was done.
|
|
160
|
+
/** Replan-lite ([ref] §6): which trigger won arbitration + what was done.
|
|
161
|
+
* 🔴 S-113 / [ref] ④(7.83.0):闭集删掉 `"merge-conflict"`(硬 breaking,零别名——消费方的 tsc 红即通知)。 */
|
|
158
162
|
replan?: {
|
|
159
|
-
trigger: "suspended" | "budget" | "
|
|
163
|
+
trigger: "suspended" | "budget" | "verify-fail" | "worker-failed";
|
|
160
164
|
redispatched?: string[];
|
|
161
165
|
collapsed?: string;
|
|
162
166
|
};
|
|
167
|
+
/**
|
|
168
|
+
* 🔴 S-113 / clay [ref] ④(7.83.0)——**合不上 = 交给用户**(对齐 CC 形)。merge 的 `phase:"apply"` 冲突
|
|
169
|
+
* 不再起 collapse 模型:`ok=false` + 本段,用户拿 `workers[].branch` + `files` 自己 `git merge` / cherry-pick
|
|
170
|
+
* (worker 分支本就留在集成沙箱 / 远端)。run 状态走既有第三终局 `needs_human`(endpoint 的状态映射),不加
|
|
171
|
+
* 新状态词。**缺席何义** = 这一趟没有冲突,或对面是老 server。
|
|
172
|
+
*/
|
|
173
|
+
conflict?: {
|
|
174
|
+
/** 用户侧的合并基点 = leader 这一趟的 durable base。 */
|
|
175
|
+
baseSha: string;
|
|
176
|
+
/** 有冲突标记 / `.rej` 的路径(**不含内容**)。 */
|
|
177
|
+
files: string[];
|
|
178
|
+
/** 清单被截断(探测到的冲突路径多于上限)时在场;**缺席 = 这就是全部**(对抗复审 F5)。 */
|
|
179
|
+
filesTruncated?: true;
|
|
180
|
+
/** 全部 completed worker 的分支;`applied:false` = 这只 worker 的补丁没进树(冲突者本人,或排在它后面
|
|
181
|
+
* 还没轮到 apply 的——顺序 apply,首次失败即停)。 */
|
|
182
|
+
workers: Array<{
|
|
183
|
+
workerId: string;
|
|
184
|
+
branch: string;
|
|
185
|
+
applied: boolean;
|
|
186
|
+
}>;
|
|
187
|
+
/** `.rej` 头(经既有脱敏面,≤ 2 KiB)——给人看冲突长什么样。 */
|
|
188
|
+
rejHead?: string;
|
|
189
|
+
};
|
|
163
190
|
/** C4: suspended workers' resume tokens, surfaced to the caller (their checkpoints + paused envs are intact —
|
|
164
191
|
* the single-agent resume path owns them from here; orphan protection = the existing deadline reap). */
|
|
165
192
|
suspended?: Array<{
|
package/dist/leader/leader.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { fanOut } from "./fanout.js";
|
|
2
|
-
import { redactErrorMessage, redactHead
|
|
2
|
+
import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
|
|
3
3
|
import { pullDiff } from "./diffout.js";
|
|
4
4
|
import { mergeBranches } from "./merge.js";
|
|
5
|
-
import { Runner, runWithVerification, resumeWithVerification,
|
|
5
|
+
import { Runner, runWithVerification, resumeWithVerification, terminalProjection } from "@sema-agent/core";
|
|
6
6
|
import { pausedCauseOf } from "../terminal.js";
|
|
7
7
|
const MAX_RESUME_SLICES = 200;
|
|
8
8
|
export async function autoResumeOnResource(initial, signal, resume, maxSlices = MAX_RESUME_SLICES) {
|
|
@@ -152,20 +152,25 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
152
152
|
: w.diffEnv
|
|
153
153
|
? pullDiff(w.diffEnv, w.baseSha, { ref: w.branch, ...(workerRepoDir ? { cwd: workerRepoDir } : {}) })
|
|
154
154
|
: Promise.resolve({ ok: false, error: `worker ${w.workerId} has neither fetchDiff nor diffEnv` });
|
|
155
|
-
const
|
|
155
|
+
const notCompleted = (r) => r.status !== "completed";
|
|
156
|
+
const salvagePatches = async (rs) => {
|
|
156
157
|
const out = [];
|
|
158
|
+
const skipped = (workerId, why, detail) => deps.logger?.warn?.("salvage_skipped", { workerId, why, ...(detail ? { detail: redactHead(detail, 200) } : {}) });
|
|
157
159
|
await Promise.all(rs
|
|
158
|
-
.filter((r) => r.status !== "completed")
|
|
159
160
|
.map(async (r) => {
|
|
160
161
|
const w = byId.get(r.workerId);
|
|
161
162
|
if (!w)
|
|
162
|
-
return;
|
|
163
|
+
return skipped(r.workerId, "unknown_worker");
|
|
163
164
|
try {
|
|
164
165
|
const d = await workerDiff(w);
|
|
165
|
-
if (d.ok
|
|
166
|
-
|
|
166
|
+
if (!d.ok)
|
|
167
|
+
return skipped(r.workerId, "diff_failed", d.error);
|
|
168
|
+
if (d.patch.trim().length === 0)
|
|
169
|
+
return skipped(r.workerId, "empty_diff");
|
|
170
|
+
out.push({ workerId: r.workerId, sessionId: r.sessionId, patch: d.patch });
|
|
167
171
|
}
|
|
168
|
-
catch {
|
|
172
|
+
catch (e) {
|
|
173
|
+
skipped(r.workerId, "diff_threw", e instanceof Error ? e.message : String(e));
|
|
169
174
|
}
|
|
170
175
|
}));
|
|
171
176
|
return out;
|
|
@@ -226,7 +231,7 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
226
231
|
if (sus.length === 0)
|
|
227
232
|
return undefined;
|
|
228
233
|
deps.logger?.info?.("replan_suspended", { workers: sus.map((r) => r.workerId) });
|
|
229
|
-
const salvaged = await
|
|
234
|
+
const salvaged = await salvagePatches(rs.filter(notCompleted));
|
|
230
235
|
return {
|
|
231
236
|
ok: false, reports: rs, cancelled: true, route,
|
|
232
237
|
error: "worker suspended — fan-out cancelled (suspended worker exempt), resume token surfaced",
|
|
@@ -235,41 +240,26 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
235
240
|
...(salvaged.length > 0 ? { salvaged } : {}),
|
|
236
241
|
};
|
|
237
242
|
};
|
|
238
|
-
const
|
|
243
|
+
const conflictSegment = (c, rs) => ({
|
|
244
|
+
baseSha: durableBaseSha,
|
|
245
|
+
files: c.files,
|
|
246
|
+
...(c.filesTruncated ? { filesTruncated: true } : {}),
|
|
247
|
+
workers: rs
|
|
248
|
+
.filter((r) => r.status === "completed")
|
|
249
|
+
.map((r) => ({ workerId: r.workerId, branch: byId.get(r.workerId).branch, applied: c.applied.includes(r.workerId) })),
|
|
250
|
+
...(c.rejHead ? { rejHead: c.rejHead } : {}),
|
|
251
|
+
});
|
|
252
|
+
const collapseToSingle = async (trigger, reason) => {
|
|
239
253
|
const safeReason = redactHead(reason, 300);
|
|
240
254
|
deps.logger?.warn?.("replan_collapse_to_single", { trigger, reason: safeReason });
|
|
241
255
|
const replan = { trigger, collapsed: safeReason, ...(redispatched ? { redispatched } : {}) };
|
|
242
|
-
const
|
|
243
|
-
await Promise.all(reports.map(async (r) => {
|
|
244
|
-
const w = byId.get(r.workerId);
|
|
245
|
-
if (!w)
|
|
246
|
-
return;
|
|
247
|
-
try {
|
|
248
|
-
const d = await workerDiff(w);
|
|
249
|
-
if (d.ok && d.patch.trim().length > 0)
|
|
250
|
-
priorDiffs.push({ workerId: r.workerId, status: r.status, patch: d.patch });
|
|
251
|
-
}
|
|
252
|
-
catch {
|
|
253
|
-
}
|
|
254
|
-
}));
|
|
255
|
-
if (priorDiffs.length > 0)
|
|
256
|
-
deps.logger?.info?.("collapse_reuse_priors", { workers: priorDiffs.map((d) => d.workerId) });
|
|
257
|
-
const soloObjective = priorDiffs.length === 0
|
|
258
|
-
? task
|
|
259
|
-
: [
|
|
260
|
-
task,
|
|
261
|
-
``,
|
|
262
|
-
`--- PRIOR PARALLEL WORK — REUSE IT, do not re-derive from scratch ---`,
|
|
263
|
-
`${priorDiffs.length} worker(s) attempted this task in parallel; their changes could NOT be integrated cleanly (${safeReason.slice(0, 150)}). Each worker's diff over the base is below. COPY the correct, non-overlapping parts verbatim and produce ONE coherent change for the WHOLE task, resolving only the overlaps. Treat these as a strong starting point — fix anything wrong, but do not throw the work away.`,
|
|
264
|
-
...priorDiffs.map((d) => delimitUntrusted(`PRIOR WORKER '${d.workerId}' (${d.status}) DIFF (untrusted)`, redactPatchHead(d.patch, 8000))),
|
|
265
|
-
].join("\n");
|
|
266
|
-
const soloSub = { workerId: "collapse-solo", branch: "worker/collapse-solo", files: ["."], spec: { objective: soloObjective } };
|
|
256
|
+
const soloSub = { workerId: "collapse-solo", branch: "worker/collapse-solo", files: ["."], spec: { objective: task } };
|
|
267
257
|
let solo;
|
|
268
258
|
try {
|
|
269
259
|
solo = await deps.provisionWorker(soloSub);
|
|
270
260
|
}
|
|
271
261
|
catch (e) {
|
|
272
|
-
const salvaged = await
|
|
262
|
+
const salvaged = await salvagePatches(finalReports.filter(notCompleted));
|
|
273
263
|
return { ok: false, reports: finalReports, cancelled, route, error: `collapse provision failed: ${e instanceof Error ? e.message : String(e)}`, replan, ...(salvaged.length > 0 ? { salvaged } : {}) };
|
|
274
264
|
}
|
|
275
265
|
workers.push(solo);
|
|
@@ -280,10 +270,20 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
280
270
|
const sus = await surfaceSuspended(finalReports, fo.reports);
|
|
281
271
|
if (sus)
|
|
282
272
|
return { ...sus, replan: { ...replan, trigger: "suspended" }, ...(repairTerminal ? { repairTerminal } : {}), ...(candidatePatch ? { candidatePatch } : {}) };
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
273
|
+
const m = await mergeBranches(fo.reports, durableBaseSha, mergeDeps);
|
|
274
|
+
if (!m.ok && m.conflict) {
|
|
275
|
+
const cc = m.conflict;
|
|
276
|
+
deps.logger?.warn?.("merge_conflict_to_user", { conflicted: cc.conflicted, applied: cc.applied, files: cc.files.slice(0, 20) });
|
|
277
|
+
const salvagedAll = await salvagePatches(finalReports);
|
|
278
|
+
return {
|
|
279
|
+
ok: false, reports: finalReports, merge: m, cancelled: fo.cancelled, route, replan,
|
|
280
|
+
conflict: conflictSegment(cc, fo.reports),
|
|
281
|
+
...(salvagedAll.length > 0 ? { salvaged: salvagedAll } : {}),
|
|
282
|
+
...(repairTerminal ? { repairTerminal } : {}),
|
|
283
|
+
...(candidatePatch ? { candidatePatch } : {}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const salvaged = await salvagePatches(finalReports.filter(notCompleted));
|
|
287
287
|
return {
|
|
288
288
|
ok: m.ok, reports: finalReports, merge: m, cancelled: fo.cancelled, route, replan,
|
|
289
289
|
...(salvaged.length > 0 ? { salvaged } : {}),
|
|
@@ -336,18 +336,27 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
336
336
|
const sus2 = await surfaceSuspended(reports, fo2.reports);
|
|
337
337
|
if (sus2)
|
|
338
338
|
return sus2;
|
|
339
|
-
const stillFailed = fo2.reports.filter(
|
|
339
|
+
const stillFailed = fo2.reports.filter(notCompleted).map((r) => r.workerId);
|
|
340
340
|
if (stillFailed.length > 0) {
|
|
341
341
|
return collapseToSingle("worker-failed", `redispatch failed again: ${stillFailed.join(", ")}`);
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
const merge = await mergeBranches(reports, durableBaseSha, mergeDeps);
|
|
346
|
-
if (!merge.ok && merge.
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
if (!merge.ok && merge.conflict) {
|
|
347
|
+
const c = merge.conflict;
|
|
348
|
+
deps.logger?.warn?.("merge_conflict_to_user", { conflicted: c.conflicted, applied: c.applied, files: c.files.slice(0, 20) });
|
|
349
|
+
const salvagedC = await salvagePatches(reports);
|
|
350
|
+
return {
|
|
351
|
+
ok: false, reports, merge, cancelled, route,
|
|
352
|
+
conflict: conflictSegment(c, reports),
|
|
353
|
+
...(redispatched ? { replan: { trigger: "worker-failed", redispatched } } : {}),
|
|
354
|
+
...(salvagedC.length > 0 ? { salvaged: salvagedC } : {}),
|
|
355
|
+
...(repairTerminal ? { repairTerminal } : {}),
|
|
356
|
+
...(candidatePatch ? { candidatePatch } : {}),
|
|
357
|
+
};
|
|
349
358
|
}
|
|
350
|
-
const salvaged = await
|
|
359
|
+
const salvaged = await salvagePatches(reports.filter(notCompleted));
|
|
351
360
|
return {
|
|
352
361
|
ok: merge.ok, reports, merge, cancelled, route,
|
|
353
362
|
...(redispatched ? { replan: { trigger: "worker-failed", redispatched } } : {}),
|