@sema-agent/server 7.111.0 → 7.113.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +68 -0
- package/USAGE.md +3 -3
- package/dist/approval-ask-audit-store.d.ts +3 -4
- package/dist/approval-ask-audit-store.js +3 -3
- package/dist/approval-card.d.ts +4 -4
- package/dist/boot/coordinators.js +1 -1
- package/dist/boot/stores.js +4 -6
- package/dist/boot/workflow-orchestration.js +4 -4
- package/dist/config-types.d.ts +1 -1
- package/dist/config.js +2 -1
- package/dist/hooks/hook-runner.d.ts +7 -2
- package/dist/hooks/hook-runner.js +48 -4
- package/dist/host-decision.d.ts +5 -3
- package/dist/http/admission.js +12 -3
- package/dist/http/request-guards.js +2 -2
- package/dist/http/resume-legs.d.ts +14 -24
- package/dist/http/resume-legs.js +91 -29
- package/dist/http/route-ctx.d.ts +64 -17
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/background.js +2 -2
- package/dist/http/routes/mcp-reconnect.js +2 -2
- package/dist/http/routes/notify-wake.js +3 -3
- package/dist/http/routes/runs.js +22 -19
- package/dist/http/routes/session-sync.js +6 -6
- package/dist/http/routes/sessions.js +20 -20
- package/dist/http/routes/tasks.d.ts +3 -2
- package/dist/http/routes/tasks.js +38 -45
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +1 -1
- package/dist/http/server.js +1 -2
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/local-data-root.d.ts +14 -0
- package/dist/local-data-root.js +5 -0
- package/dist/observability/corrupt-read-seat.d.ts +46 -66
- package/dist/observability/corrupt-read-seat.js +74 -59
- package/dist/observability/fail-open.d.ts +29 -1
- package/dist/observability/fail-open.js +26 -1
- package/dist/observability/run-terminal.js +39 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +1 -5
- package/dist/orchestration/workflow-agent-session-index.js +2 -2
- package/dist/orchestration/workflow-notify-journal.d.ts +1 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -2
- package/dist/plugins/approval-ask-store-file.d.ts +1 -4
- package/dist/plugins/approval-ask-store-file.js +2 -2
- package/dist/plugins/file-resume-anchor-store.d.ts +1 -6
- package/dist/plugins/file-resume-anchor-store.js +2 -4
- package/dist/plugins/file-run-store.d.ts +0 -4
- package/dist/plugins/file-run-store.js +1 -3
- package/dist/plugins/send-file-ledger.d.ts +1 -5
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/store-backend.js +8 -9
- package/dist/plugins/store-contracts.d.ts +40 -7
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/user-input-ledger-file.d.ts +1 -5
- package/dist/plugins/user-input-ledger-file.js +2 -2
- package/dist/run-cancel-context.d.ts +59 -7
- package/dist/run-cancel-context.js +26 -0
- package/dist/run-local.d.ts +1 -1
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +7 -5
- package/dist/runs.js +18 -13
- package/dist/spec-fields.js +0 -1
- package/dist/tool-approval.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.js +2 -3
- package/dist/trace/redact.d.ts +18 -0
- package/dist/trace/redact.js +4 -0
- package/dist/trace/self-minted-wire.d.ts +22 -0
- package/dist/trace/self-minted-wire.js +8 -0
- package/dist/unattended-permission-mode.d.ts +9 -2
- package/dist/unattended-permission-mode.js +5 -1
- package/dist/user-input-ledger.d.ts +24 -5
- package/dist/user-input-ledger.js +46 -11
- package/package.json +4 -3
- package/scripts/retired-mode-seed-drain-local.mjs +317 -0
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,74 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.113.0(车UP)—— `@sema-agent/core` 7.38.3 → 7.39.1(core 7.39.0 硬破坏六条 + 7.39.1 清单披露补全):`attachments.planModeReminder` 退役 · 停放行排空前提 · 策略结算记在策略名下 · 受理账本收消费证据
|
|
11
|
+
|
|
12
|
+
- **🔴 请求面硬破坏:`attachments.planModeReminder` 退役(响亮拒,无兼容形)**。① *谁受影响*:提交体 `attachments` 里带 `planModeReminder`(任何值,含 `false`)的调用方
|
|
13
|
+
(SDK 的 `TaskRequest.attachments.planModeReminder`、自建客户端)。② *怎么改*:删掉这个键 —— 引擎(core 7.39.0)在 `permissionMode:"plan"` 的腿上自己陈述
|
|
14
|
+
plan 姿态(首请求 / 周期提醒 / 退出帧);要 plan 姿态就发 `permissionMode:"plan"`。③ *不改会怎样*:该提交 `400 request.field_invalid`(错误句点名退役与出路)——
|
|
15
|
+
不再是「受理、引擎在 prepare 拒」。升级前就停放、存量体里带这个键的任务续跑时**丢键照跑**(它要的提醒今天由引擎在 plan 模式下自己铸,不丢意图)。
|
|
16
|
+
- **🔴 升级前排空 ≤ core 7.33.x 时期停放的 checkpoint 行**(core 7.39.0 删掉一次性迁移):`inheritedGate` 仍带退役模式种子(`autoModeRequested` / `approverPosture` /
|
|
17
|
+
非 `"always"` 的 `shellGate`)的 pending 行 —— 只可能是 server ≤7.105.0 时期停放、至今仍 pending 的行 —— 升级后续跑被拒 `409 checkpoint.unsupported_version`
|
|
18
|
+
(行字节不动、仍 pending)。**在还跑 ≤7.112.0 的部署上**用 `docs/DEPLOY-PREREQS.md` 同名节的只读 SQL 数行(期望 0),非 0 就答完或取消那些 run 再换钉;
|
|
19
|
+
漏排的行升级后可 `POST /v1/runs/:id/cancel` 放弃,或一次性回滚到 7.112.x 续跑完再升回来。
|
|
20
|
+
- **`DB_BACKEND=local`(File 店)车道的排空**:没有 SQL 可跑 —— 用随包的只读脚本 `scripts/retired-mode-seed-drain-local.mjs`(7.113.0 起在 npm 包里)。
|
|
21
|
+
**升级前你装的还是 ≤7.112.0,脚本不在已装的包里,但不必先升级服务**:`d=$(mktemp -d) && (cd "$d" && npm pack @sema-agent/server@7.113.0 && tar xzf sema-agent-server-7.113.0.tgz) && node "$d/package/scripts/retired-mode-seed-drain-local.mjs"`
|
|
22
|
+
(已升级的,直接 `node <安装目录>/node_modules/@sema-agent/server/scripts/retired-mode-seed-drain-local.mjs`)。
|
|
23
|
+
**读哪个数据根**:不带参时 = 服务自己的那条解析 —— `LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`(脚本取的是服务 `loadConfig` 调的同一只函数,不是它自抄的一份),
|
|
24
|
+
所以请在**与服务同一份 env** 下跑;也可以把数据根当唯一参数递进去。末行 `RETIRED-MODE-SEED rows=… root=<实际读的数据根>` 回显读了哪个目录。
|
|
25
|
+
只读、不取锁;它把 `checkpoints/checkpoints.snapshot.jsonl`(压实后的行)与 `checkpoints/checkpoints.jsonl`(之后的事件)折成当前状态再判 pending 行(不要用 grep 代替:压实后老行只在快照里)。
|
|
26
|
+
行形与事件词的判据与 core 的 File 店逐条同(判据坐标写在脚本头注里)。**服务运行中也能跑**:脚本自己判读到的是不是一致的快照 + 账本 ——
|
|
27
|
+
读前读后各取两只文件的身份(压实会原子换新快照再清空账本,追加会改大小),变了就重读(至多 3 次);仍读不稳就报 rc 2「服务在写,停写或重跑」,不会在半态下报一个假的「干净」。
|
|
28
|
+
**rc 0** = 零命中,可换钉;**rc 1** = 有命中(逐行 `RETIRED-ROW token=… sessionId=… member=…`,按 sessionId 答完或取消再跑);
|
|
29
|
+
**rc 2** = 不给结论 —— 账本里有它不认的事件词、不合形的记录(含坏行、`put` 的行体不合形、缺 `scope` 的快照行;命中的旧卡照列),
|
|
30
|
+
或**数据根目录不存在、根下没有 `checkpoints/`**(路径打错、env 没对上服务,读到的是一个什么都没有的目录:缺席不等于干净),或读期间文件一直在变、读文件出错。
|
|
31
|
+
- **新镜像码 `checkpoint.config_refused`(409,`retriable:true`)**:resume 的配置门在 claim 前拒,卡仍 pending、run 行停回 `suspended`;体带 `field` ——
|
|
32
|
+
`taskConfig` = 改配置后同一张卡再决,`inheritedGate` = 行记下的模式种子坏了、只能取消这条 run。本服务自己的配置面产不出它(来路只剩嵌入方自写 `resolveSpec`)。
|
|
33
|
+
- **两个 409 码的体多一个 `retriable:true`(additive;落点改判)**:`checkpoint.walltime_axis_retired` 与 `/wake` 口上的 `steering.blocked_by_hook`。键此前缺席;码、状态、`error` 报文不变。
|
|
34
|
+
读法与本节 `checkpoint.config_refused` / `seat_retired` 同:这一族里 `retriable:true` = 「卡仍 pending(core 在 claim 之前拒)」,**不是**「原样重试会成功」。
|
|
35
|
+
- **`checkpoint.walltime_axis_retired`(`POST /v1/approvals/:session/decide`;账本带已退役的跨片限时配额 `resourceLedger.totalWalltimeSec` 的存量 pending 行)**。
|
|
36
|
+
**Before(7.112.0)**:这个码落终局 —— run 行被驱成 `failed`、会话锁释放,卡仍 pending 却再也没有可续跑的行(孤儿)。
|
|
37
|
+
**After(7.113.0)**:run 行**留 `suspended`、会话锁保持**,卡仍 pending。这条行在 ≥7.39 的引擎上**永远续不了**(同一份字节每次都被同码拒)。
|
|
38
|
+
core 报文建议的「另开新任务」在**同一会话**上会 `409 conflict.session_active_run`(会话锁还在这条行手里),所以出路是:**先 `POST /v1/runs/:id/cancel` 放弃这条 run**(卡 expire、会话释放),再在该会话上另开新任务;
|
|
39
|
+
或在 core 报文所说的「previous release」的 worker 上续完它 —— 注意 7.112.x 带的 core 7.38.3 已经同样拒这个码,**回滚到 7.112.x 救不了**,要续只能用限时轴退役之前的 core 版本。
|
|
40
|
+
- **`steering.blocked_by_hook` 的 `/wake` 口(带 `message` 的 wake 撞上部署的 `userPromptSubmit` hook 崩 / 超时 / 拒;core 在 claim 之前拒,卡仍 pending)**。
|
|
41
|
+
**Before(7.112.0)**:409 体不带 `retriable`,`/wake` 为这次尝试铸的合成 run 行以无码的终局(`result:null`)落终局 —— 一次零执行的拒绝被记成普通失败。
|
|
42
|
+
**After(7.113.0)**:409 体带 `retriable:true`;合成行以带 `steering.blocked_by_hook` 码的结构化终局落终局,**不占会话**;hook 恢复后**同一张卡**可再 wake。live `steer` / `interrupt` 口上的同名码(422)不变。
|
|
43
|
+
- **审计面(门记录 `tool_end.gate.settlement`,值变更)**:① 无人值守腿(leader worker / 修复腿 / `sema` CLI / 它们的续跑)上机器按类放行的那一问,结算从
|
|
44
|
+
`human_allowed` / `who.party:"person"` 改为 **`policy_allowed` / `who.party:"policy"` / `who.approver:"unattended_class_answer"`**(7.109.0 起登记的「读作机器放行」
|
|
45
|
+
这条读法退役);② 一切**策略拒**(`UNATTENDED_APPROVAL_POLICY=deny` 的无人可答、run-local 无 TTY 的拒、`settledBy:"policy"` 的决裁)的 `policy_refused`
|
|
46
|
+
`who.party` 从 `"none"` 改为 **`"policy"`**。按 party 词分桶的审计读者改读新词。**混版 / 回滚**:7.112.x 的读面不认 party `policy`,读到 7.113.0 写下的这两种记录会把整条 `gate` 扣下(读面没有 `gate`、计一次投影缺陷)。
|
|
47
|
+
- **结果面(additive)**:`blocked` 终局多一个可选 `terminal.detail { kind:"prompt_blocked_by_hook", cause, hookReason? }`(主提交被部署 `userPromptSubmit` hook 拦下时;
|
|
48
|
+
同步 200 体 / 流 `done.result` / `GET /v1/runs/:id` / 账本 `done` 行四面同形),`hookReason` 与 `reason` 同一只脱敏。按封闭 schema 校验结果的消费方加这一可选键。
|
|
49
|
+
- **hook 作者 / 嵌入方**:本服务的 hooks 复合体对每一槽的 PreToolUse 结果先过引擎自己的 hook 席收据 —— 契约成员放在原型上 / 不可枚举 / 访问器上的结果、Proxy 载体,
|
|
50
|
+
经复合体与直接交给引擎**同答**:引擎拒(工具不跑、`permissionDenied` 响)。此前复合体会把原型上的 `deny` 读丢、把 Proxy 的 `allow` 折成普通 allow。本服务自己的
|
|
51
|
+
`createTaskHooks` 只铸普通对象,不受影响;自写裸 `Hooks` 的嵌入方按引擎契约自查。
|
|
52
|
+
- **用户输入受理账本(零 DDL、零动作)**:引擎确认消费一条 live steer(core 7.39.0 起的 `human_input{delivery:"applied"}`)⇒ 受理结清成新词 `consumed`
|
|
53
|
+
(local:`ledger.jsonl` 的 `settled` 行 `how:"consumed"`;SQL:`user_input_acceptance.settle_how = 'consumed'`,列宽够、无 ALTER);确认消费过的输入崩溃时**不再**告知。
|
|
54
|
+
`task.user_steer_undrained{cause:"crashed"}` 的 `message` 事实句改为「…ended before the engine confirmed consuming them」(`detail` 不变;按文本匹配的脚本改读 `detail.cause`)。
|
|
55
|
+
**wire 值变更**:不带 `Idempotency-Key` 的 live steer,其 `human_input.inputId` 自本版等于 steer 回执上的 `messageId`(此前引擎自铸)。
|
|
56
|
+
**回滚到 ≤7.112.x**:local 车道 —— 旧二进制不认 `consumed` 行,启动重放计一条坏行并跳过 ⇒ 那几条受理在它眼里回到「未结清」,若其写者已不在会被按崩溃告知一次
|
|
57
|
+
(宁多说一遍);要干净回滚:停服后删 `<数据根>/user-input-ledger/ledger.jsonl`(代价 = 丢掉尚未告知的崩溃丢失提醒)。SQL 车道 —— 结清行的 `state` 列仍是 `settled`,
|
|
58
|
+
旧二进制的收敛只扫 `state = 'open'`,不受影响。
|
|
59
|
+
- **嵌入方(类型面)**:包根导出的 `SettleHow` 多一个词 `consumed`(对它穷举的 switch tsc 红即通知);`TaskSpec.outputStyle`(core 7.39.0 新席)本服务不接、请求面不收。
|
|
60
|
+
|
|
61
|
+
### 运维日志事件名退役一族(车 UQ S-990 / 车 UR S-1000 / S-569)—— 按事件名配告警 / 数行的运维要改配
|
|
62
|
+
- **退役(不再逐记录发 warn)**:`session_repo_corrupt_read` · `workflow_journal_corrupt_read` · `workflow_run_corrupt_read` · `roster_corrupt_read` · `background_agent_corrupt_read`(该座已删,披露归 backend 的 `file_store_corrupt_read`)· `mailbox_corrupt_read`(S-990 六座);`approval_ask_journal_corrupt_read` · `run_store_corrupt_read` · `resume_anchor_corrupt_read` · `send_file_ledger_corrupt_read` · `approval_ask_audit_corrupt_read` · `workflow_notify_journal_corrupt_read` · `workflow_agent_session_ledger_line_rejected` · `user_input_ledger_corrupt_read`(S-1000 八座)。
|
|
63
|
+
- **替代读法(F 类座纪律,同一只记录器)**:每座一个 tag —— 进程内**一次** `fail_open` warn(首条明细 + 指路)、`fail_open_total{tag}` 计数每命中 +1、`SEMA_FAILOPEN_PROBE=1` 时每命中一行探针(`kind=record path= line= form= reason=`);tag 名见 `docs/FAIL-OPEN-CENSUS.md`(第 110–113 / 115 / 117 行与 S-582 / S-928 / S-967 / S-977 既有行)。
|
|
64
|
+
- **保持逐命中响亮(安全轴混座,不退役)**:`file_store_corrupt_read`;其 session-policy 脸的坏读今天仍 fail-open(core #1299 修后改拒),server 7.113.0 起同时计 `server.session-policy.corrupt-read-fail-open`(P-DEBT)。
|
|
65
|
+
- **run-local 车道**(前台 CLI 进程)的会话 repo 仍走混座逐命中 warn,不降。
|
|
66
|
+
|
|
67
|
+
### 会话读三口的非属主 404(车 UQ S-989;wire 值变更)
|
|
68
|
+
- `GET /v1/sessions/:id`(detail / head / events)对**非属主**调用方的反枚举 404,码 / 句子从 run 拼法(`not_found.run` + run 长文)改为会话拼法(`not_found.session` + `session not found`),与「不存在」臂同串。读 `not_found.run` 判「会话读被拒」的旧端要改读 `not_found.session`;属主 200 不变;run 面(`/v1/runs/:id`)不变。
|
|
69
|
+
|
|
70
|
+
## 7.112.0(车UG,S-924)—— `settings.hooks` 里按工具别名写的 matcher 提交当场 400(此前静默不命中)
|
|
71
|
+
|
|
72
|
+
- **🔴 settings 面硬破坏(响亮拒,无迁移代码、无兼容形)**。① *谁受影响*:提交体 `settings.hooks` 里 PreToolUse / PostToolUse / PostToolUseFailure 的 `matcher`
|
|
73
|
+
是**引擎工具别名**的(今天只有 `ListPeers` → `ListAgents`;别名表读 core 静态工具目录)—— 它们**早就不命中**(post 两事件自 7.107.0 / core 7.36.0,PreToolUse 一直),只是没人告诉写的人。
|
|
74
|
+
② *怎么改*:把 matcher 改成**正名**(错误句直接给出);`正名|别名` 的滚动期写法不受影响(命中正名)。③ *不改会怎样*:该**新提交** `400 request.field_invalid`(错误句点名别名与正名)——
|
|
75
|
+
不再是「接受、hook 永不跑」。耐久 resume 重放**不判**(会砖 parked 任务):升级前就 parked 的任务,其存量 body 里的别名 matcher 在续跑腿照旧永不命中、同一份 hooks 里其余钩子照常生效(与 7.111.0 逐字同)。
|
|
76
|
+
嵌入方经 `resolveSpec` 席挂的工具别名在受理面看不见,不在本门射程。
|
|
77
|
+
|
|
10
78
|
## 7.111.0(车TA,S-961 / S-972)—— `wiring_manifest` 帧 +`instructions` 段(additive,零动作)
|
|
11
79
|
|
|
12
80
|
- **wire(additive,零动作)**:`wiring_manifest` 帧(四条腿:流式 SSE / 账本行 / 非流式 `wiringManifest` 回执 / 续跑腿)多一段本服务自铸的 `instructions: { selected: string[] }` = 这条腿选中的项目指令文件(相对 cwd 的链成员);`[]` = 没有,缺席 = 无陈述(契约附录 G.21)。按封闭 schema 校验该帧的消费方加这一可选键;不要它的部署 `PROJECT_MEMORY_ENABLED=false`(连同项目记忆注入一起关)。`task.user_steer_undrained{cause:"crashed"}` 的 `message` 改句序(行动句在前、< 400 字符),`detail` 不变 —— 按文本匹配的脚本改读 `detail.cause`(S-972)。
|
package/USAGE.md
CHANGED
|
@@ -690,7 +690,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
|
|
|
690
690
|
| 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次;中心改了 roles / 档位 / 目录条目后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报) |
|
|
691
691
|
|
|
692
692
|
生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
|
|
693
|
-
- **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,人答 N 而停下的 run 退 1(停因 `haltedOnUserRejection`)。**无 TTY 的拒记在部署政策名下**(7.110.0 起,S-956;此前记成人拒):该次调用 `tool_end.gate.settlement` = `{kind:"policy_refused", who:{party:"
|
|
693
|
+
- **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,人答 N 而停下的 run 退 1(停因 `haltedOnUserRejection`)。**无 TTY 的拒记在部署政策名下**(7.110.0 起,S-956;此前记成人拒):该次调用 `tool_end.gate.settlement` = `{kind:"policy_refused", who:{party:"policy"}}`(7.113.0 起;7.110.x–7.112.x 为 `party:"none"`),模型读到的是引擎的策略句(「部署政策拒了、此处没有交互审批,别重试,先做不需要审批的部分」)加一句拒因,而不是「用户不想继续,停下来等他」—— 不停批,模型接着做别的部分;那只要人批的动作没执行 ⇒ run 照样退 1,非 `--json` 形 stderr 停因行是 `task incomplete (approval unanswered): …`。参数展示不出而不问就拒的那一形(TTY 在场也一样)同此。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
|
|
694
694
|
- **仅 `/v1/tasks`(同步)+ `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多次尝试非单流,400)。与 `verify` **互斥**(同时给 → 400)。
|
|
695
695
|
- 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `discuss`(广度交叉)而非级联(深度阶梯)。
|
|
696
696
|
|
|
@@ -836,7 +836,7 @@ UNATTENDED_APPROVAL_POLICY=park
|
|
|
836
836
|
- `deny` ⇒ **真无人值守/headless 部署的显式声明**:这类 ask 当场拒绝,让模型自己改道,不积压一堆
|
|
837
837
|
等不到人的挂起 run。它是**收紧**方向(不放行任何东西)。
|
|
838
838
|
这一拒记在**部署政策**名下,不记在人名下(7.100.0 起):那次调用的 `tool_end.gate.settlement` 是
|
|
839
|
-
`{kind:"policy_refused", who:{party:"
|
|
839
|
+
`{kind:"policy_refused", who:{party:"policy"}}`(7.113.0 起;此前 `party:"none"`);模型读到的是「部署政策拒了、此处没有交互审批,别重试,
|
|
840
840
|
先做不需要审批的部分」,run 接着跑(auto 模式下分类器连拒后回落的那一问被拒时,引擎按「无人可判」停下
|
|
841
841
|
run:终局 `failed` / `classifier.denial_limit`)。此前记成 `human_refused` / `who.party:"person"`,模型被告知
|
|
842
842
|
「用户不想继续,停下来等他」,run 以 `haltedOnUserRejection` 收束 —— 而这台部署自己声明了没人会来。
|
|
@@ -873,7 +873,7 @@ UNATTENDED_APPROVAL_POLICY=park
|
|
|
873
873
|
`ordinary` 的问 —— 模式写门对普通写的问、形状层对非只读 shell 的问(写与命令照常落地,与 7.105.0 同)。其余每一类都不答:
|
|
874
874
|
写保护表目标(`write_protection`)、shell 读会话目录外(`probe`)、MCP / A2A 工具标记、transcript 写、规则店读不出、共享制品读、
|
|
875
875
|
plan 的写问(`mode`),以及运维 / hook / 规则 / 组织为人提的问(审批名单、治理层、`APPROVAL_NEVER_AUTO` …)。不答的问:
|
|
876
|
-
- **有席的腿交给席**:run-local 在终端里跑 ⇒ 问你 y/N;没有 TTY ⇒ 当场拒(stderr 一句;门记录记在部署政策名下 `policy_refused` / `who.party:"none"
|
|
876
|
+
- **有席的腿交给席**:run-local 在终端里跑 ⇒ 问你 y/N;没有 TTY ⇒ 当场拒(stderr 一句;门记录记在部署政策名下 `policy_refused` / `who.party:"policy"`(7.113.0 起;7.110.x–7.112.x 为 `"none"`),7.110.0 起,S-956)。
|
|
877
877
|
- **无席的腿**(leader 各腿):配了 durable 审批块的部署 ⇒ park,等人经 `/decide` 答(与 7.105.0 同形);没配 ⇒ 当场拒(fail-closed)。
|
|
878
878
|
- 答集是常量(只 `ordinary`,core 推荐形),**不是旋钮**。7.106.0–7.108.0 的 `UNATTENDED_PERMISSION_MODE` 已退役:**设了即拒启**
|
|
879
879
|
(`config.env_key_retired`,任何值都拒);从 `.env` 里删掉它。要让机器多答某一类问,先立案,不开旋钮。
|
|
@@ -165,7 +165,6 @@
|
|
|
165
165
|
*/
|
|
166
166
|
import type { HostDecision } from "./host-decision.js";
|
|
167
167
|
import { AppendLog } from "@sema-agent/core";
|
|
168
|
-
import type { Logger } from "./observability/logger.js";
|
|
169
168
|
/** 数据子目录名(挂在 local 数据根下,与 `workflow-notify/` 同层同形)。 */
|
|
170
169
|
export declare const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
171
170
|
/** 崩溃收敛行的读面上限(新前旧后截断;有界 wire —— 读面不许随月份线性长)。 */
|
|
@@ -358,15 +357,15 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
358
357
|
* (撕尾不进重放、完整末行进重放,修前修后读数同);
|
|
359
358
|
* 目录已在而盘后来满 / 变只读 ⇒ boot 照常,append 走 `recordFailOpen`。
|
|
360
359
|
* @param internals.appendLog 测试注入席(见 {@link AuditAppendPort})。
|
|
361
|
-
*
|
|
360
|
+
*
|
|
361
|
+
* 坏行披露 = F 类座纪律(S-1000:无 warn 出口)—— 每条跳过的行 `recordFailOpen` 计数 + 探针行,warn 每进程每 tag 一次。
|
|
362
362
|
*/
|
|
363
363
|
constructor(root: string, internals?: {
|
|
364
364
|
appendLog?: (path: string) => AuditAppendPort;
|
|
365
|
-
warn?: Logger["warn"];
|
|
366
365
|
});
|
|
367
366
|
/** 重放的坏值计数(`convergeCrashedAsks().corruptLines`):重放属主跳过的坏行(撕尾不计)+ 成因词表外的行(行在、结清照认,坏值照样计数)。 */
|
|
368
367
|
private corruptLines;
|
|
369
|
-
/** 重放前世账本(共用口 `replayJsonlSkipping`):撕尾 = 未落完的写,静默跳过;坏行跳过 +
|
|
368
|
+
/** 重放前世账本(共用口 `replayJsonlSkipping`):撕尾 = 未落完的写,静默跳过;坏行跳过 + 逐条计数 / 探针;坏行数另暴露给收敛日志。 */
|
|
370
369
|
private replay;
|
|
371
370
|
private group;
|
|
372
371
|
/** append + fsync(逐行落盘:进程猝死不丢已记的行 —— 本店的全部价值就在崩溃时刻;写口 = 共用的 `AppendLog`:单次 `writeSync` 一行、末段在打开与每次追加前修(撕尾截、完整末行补换行)、失败的写截回开写处)。
|
|
@@ -191,11 +191,11 @@ export class FileApprovalAskAuditStore {
|
|
|
191
191
|
const dir = join(root, APPROVAL_ASK_AUDIT_DIR);
|
|
192
192
|
ensureDir(dir);
|
|
193
193
|
this.ledgerPath = join(dir, "ledger.jsonl");
|
|
194
|
-
this.replay(
|
|
194
|
+
this.replay();
|
|
195
195
|
}
|
|
196
196
|
corruptLines = 0;
|
|
197
|
-
replay(
|
|
198
|
-
const { records, skipped } = replayJsonlSkipping(this.ledgerPath, "approval-ask-audit", LEDGER_SHAPE
|
|
197
|
+
replay() {
|
|
198
|
+
const { records, skipped } = replayJsonlSkipping(this.ledgerPath, "approval-ask-audit", LEDGER_SHAPE);
|
|
199
199
|
this.corruptLines = skipped;
|
|
200
200
|
let line = 0;
|
|
201
201
|
for (const { rec, outOfSetCause } of records) {
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -651,8 +651,8 @@ export declare const ApprovalCardSchema: z.ZodObject<{
|
|
|
651
651
|
}, z.core.$strict>>>;
|
|
652
652
|
personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
653
653
|
}, z.core.$strict>>;
|
|
654
|
-
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
|
|
655
|
-
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
|
|
654
|
+
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
|
|
655
|
+
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
|
|
656
656
|
fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
|
|
657
657
|
sourceTaskId: z.ZodOptional<z.ZodString>;
|
|
658
658
|
sourceAgentName: z.ZodOptional<z.ZodString>;
|
|
@@ -766,8 +766,8 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
|
|
|
766
766
|
}, z.core.$strict>>>;
|
|
767
767
|
personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
768
768
|
}, z.core.$strict>>;
|
|
769
|
-
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
|
|
770
|
-
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
|
|
769
|
+
askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
|
|
770
|
+
classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
|
|
771
771
|
fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
|
|
772
772
|
sourceTaskId: z.ZodOptional<z.ZodString>;
|
|
773
773
|
sourceAgentName: z.ZodOptional<z.ZodString>;
|
|
@@ -41,7 +41,7 @@ export function createLiveCoordinators(ctx) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
const approvalAskAudit = config.toolApprovalEnabled && backend?.kind === "local" && config.localDataRoot !== undefined
|
|
44
|
-
? new FileApprovalAskAuditStore(config.localDataRoot
|
|
44
|
+
? new FileApprovalAskAuditStore(config.localDataRoot)
|
|
45
45
|
: undefined;
|
|
46
46
|
if (approvalAskAudit) {
|
|
47
47
|
const converged = approvalAskAudit.convergeCrashedAsks();
|
package/dist/boot/stores.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { FileBackgroundAgentStore, FileMailboxStore, FileRosterStore, FileUsageWindowStore, InMemoryUsageWindowStore, mailboxCrossProcessMountVerdict } from "@sema-agent/core";
|
|
3
|
-
import {
|
|
3
|
+
import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
|
|
4
4
|
import { createMemorySyncRunner, createMemorySyncTransport } from "../memory-sync-client.js";
|
|
5
5
|
import { memoryEmbedderFor } from "../plugins/memory-embedder.js";
|
|
6
6
|
import { ensurePgMemoryEmbedderMetaSchema, reconcileEmbedderFingerprint } from "../plugins/memory-embedder-fingerprint.js";
|
|
@@ -232,7 +232,7 @@ export async function openStores(ctx) {
|
|
|
232
232
|
}
|
|
233
233
|
else if (backend?.kind === "local") {
|
|
234
234
|
rosterStore = new FileRosterStore(join(config.localDataRoot ?? localRoot, "roster.json"), {
|
|
235
|
-
onCorruptRead:
|
|
235
|
+
onCorruptRead: createCorruptReadFailOpenSeat("server.roster.corrupt-record-skipped"),
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
238
|
if (rosterStore)
|
|
@@ -252,9 +252,7 @@ export async function openStores(ctx) {
|
|
|
252
252
|
backgroundAgentStore = new TiDBBackgroundAgentStore(mysqlPool);
|
|
253
253
|
}
|
|
254
254
|
else if (backend?.kind === "local") {
|
|
255
|
-
backgroundAgentStore = new FileBackgroundAgentStore(config.localDataRoot ?? localRoot
|
|
256
|
-
onCorruptRead: createCorruptReadSeat(logger.warn.bind(logger), "background_agent_corrupt_read"),
|
|
257
|
-
});
|
|
255
|
+
backgroundAgentStore = new FileBackgroundAgentStore(config.localDataRoot ?? localRoot);
|
|
258
256
|
}
|
|
259
257
|
if (backgroundAgentStore)
|
|
260
258
|
logger.info("background_agent_store_enabled", { backend: pgPool ? "pg" : mysqlPool ? "tidb" : "file" });
|
|
@@ -308,7 +306,7 @@ export async function openStores(ctx) {
|
|
|
308
306
|
}
|
|
309
307
|
else if (backend?.kind === "local") {
|
|
310
308
|
mailboxStore = new FileMailboxStore(config.localDataRoot ?? localRoot, {
|
|
311
|
-
onCorruptRead:
|
|
309
|
+
onCorruptRead: createCorruptReadFailOpenSeat("server.mailbox.corrupt-record-skipped"),
|
|
312
310
|
});
|
|
313
311
|
mailboxRecipientLifecycle = createDropOnlyRecipientLifecycle(mailboxStore);
|
|
314
312
|
}
|
|
@@ -2,7 +2,7 @@ import { FileWorkflowRunStore, InMemoryWorkflowRunStore } from "@sema-agent/core
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { FleetEventBus } from "../fleet/fleet-bus.js";
|
|
4
4
|
import { LocalWorkflowAgentSessionIndex } from "../orchestration/workflow-agent-session-index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
|
|
6
6
|
import { FileWorkflowCompletionInbox, InMemoryWorkflowCompletionInbox, resolveCompletionRoute } from "../orchestration/workflow-completion-inbox.js";
|
|
7
7
|
import { FileWorkflowNotifyJournalStore, JournalingWorkflowRunStore, WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
|
|
8
8
|
import { SubagentSteerRegistry } from "../orchestration/subagent-steer.js";
|
|
@@ -16,11 +16,11 @@ export function createWorkflowOrchestration(ctx) {
|
|
|
16
16
|
? new InMemoryWorkflowRunStore()
|
|
17
17
|
: (sqlWorkflowRunStore ??
|
|
18
18
|
new FileWorkflowRunStore(join(config.localDataRoot ?? localRoot, "workflows"), {
|
|
19
|
-
onCorruptRead:
|
|
19
|
+
onCorruptRead: createCorruptReadFailOpenSeat("server.workflow-run.corrupt-record-skipped"),
|
|
20
20
|
}))
|
|
21
21
|
: undefined;
|
|
22
22
|
const workflowNotifyJournal = config.selfOrchestrationEnabled && config.workflowRunStoreBackend !== "memory"
|
|
23
|
-
? (sqlWorkflowRunStore ? backend.notifyJournal() : new FileWorkflowNotifyJournalStore(join(config.localDataRoot ?? localRoot, "workflows"), undefined
|
|
23
|
+
? (sqlWorkflowRunStore ? backend.notifyJournal() : new FileWorkflowNotifyJournalStore(join(config.localDataRoot ?? localRoot, "workflows"), undefined))
|
|
24
24
|
: undefined;
|
|
25
25
|
const workflowCompletionInbox = config.selfOrchestrationEnabled
|
|
26
26
|
? config.workflowRunStoreBackend === "memory"
|
|
@@ -64,7 +64,7 @@ export function createWorkflowOrchestration(ctx) {
|
|
|
64
64
|
? new LocalWorkflowAgentSessionIndex()
|
|
65
65
|
: (sqlWorkflowRunStore
|
|
66
66
|
? backend.workflowAgentSessionIndex()
|
|
67
|
-
: new LocalWorkflowAgentSessionIndex(join(config.localDataRoot ?? localRoot, "workflows"), undefined
|
|
67
|
+
: new LocalWorkflowAgentSessionIndex(join(config.localDataRoot ?? localRoot, "workflows"), undefined))
|
|
68
68
|
: undefined;
|
|
69
69
|
const workflowRunStore = baseWorkflowRunStore
|
|
70
70
|
? new JournalingWorkflowRunStore(baseWorkflowRunStore, workflowNotifyGate, fleetBus, workflowAgentSessionIndex)
|
package/dist/config-types.d.ts
CHANGED
|
@@ -1884,7 +1884,7 @@ export interface ServiceConfigFlat {
|
|
|
1884
1884
|
* 结束等待的确实是本仓的窗;其余三臂结束等待的是容量/连接/装配,自报恒缺席。
|
|
1885
1885
|
*
|
|
1886
1886
|
* 🔴 **`deny` 交给引擎的形(S-689)**:部署政策拒对象臂 `{allow:false, settledBy:"policy", reason}` ⇒ 引擎结算
|
|
1887
|
-
* `policy_refused`(`who.party:"none"`)、模型读策略句(「部署政策拒了、此处没有交互审批,别重试、做不需审批的
|
|
1887
|
+
* `policy_refused`(`who.party:"policy"` —— core 7.39.0 #1265 起;此前 `"none"`)、模型读策略句(「部署政策拒了、此处没有交互审批,别重试、做不需审批的
|
|
1888
1888
|
* 部分」)、run 不以「用户拒绝」收束而是继续。修前是裸 `false` —— 引擎契约里那就是「一个人拒了」
|
|
1889
1889
|
* (`human_refused` / `haltedOnUserRejection`),与本键「没有人会来」的自声明正好相反。(b)(c) 两臂照旧带
|
|
1890
1890
|
* `"timeout"` 自报(`approval_window_expired`)。
|
package/dist/config.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isV2ScopeKey, memoryReplicationCoverage, replicationCovers } from "./me
|
|
|
11
11
|
import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
|
|
12
12
|
import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
|
|
13
13
|
import { CONFIG_PROVIDER_WORDS, DB_BACKEND_WORDS, RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS } from "./config-types.js";
|
|
14
|
+
import { resolveLocalDataRoot } from "./local-data-root.js";
|
|
14
15
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
15
16
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
16
17
|
import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-posture.js";
|
|
@@ -874,7 +875,7 @@ function parseStoreDomain(ctx) {
|
|
|
874
875
|
const sqlEngineExplicit = !!process.env.DB_BACKEND && dbBackend !== "local" && dbBackend !== "memory";
|
|
875
876
|
const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", ["memory", "mysql", "auto"]);
|
|
876
877
|
const sessionBackend = sessionBackendRaw === "mysql" ? "tidb" : sessionBackendRaw;
|
|
877
|
-
const localDataRoot = process.env
|
|
878
|
+
const localDataRoot = resolveLocalDataRoot(process.env, homedir());
|
|
878
879
|
const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
|
|
879
880
|
const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
|
|
880
881
|
const needsDb = needsTidb;
|
|
@@ -35,6 +35,11 @@ export interface ParseHooksConfigResult {
|
|
|
35
35
|
/** 人可读的第一条校验错误(HTTP 层用它 400 fail-loud;resume 防御路径用它 warn)。 */
|
|
36
36
|
error?: string;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* fresh 提交面专用判据(S-924):配置里第一条「永远打不中」的工具事件 matcher 的拒句;没有 ⇒ undefined。
|
|
40
|
+
* 入参是 {@link parseHooksConfig} 已校验过的配置。**只有 `admission.ts` 的 fresh 提交门调它** —— resume 重放不调(理由见上面的头注)。
|
|
41
|
+
*/
|
|
42
|
+
export declare function deadToolMatcherIssue(config: HooksConfig): string | undefined;
|
|
38
43
|
/**
|
|
39
44
|
* 把 wire 上的 untrusted `settings.hooks` 校验成契约 {@link HooksConfig} + 服务侧上限。
|
|
40
45
|
* 错误返回 error 字符串而不 throw——submit 路径 400 fail-loud、resume 防御路径 warn-drop,由调用方选。
|
|
@@ -174,8 +179,8 @@ export declare function createTaskHooks(config: HooksConfig, ctx: HookRunnerCont
|
|
|
174
179
|
* 把部署级 hooks 基线(deps.hooks,如 TOOL_TRACE 观测)折进 task 级 hooks——core 是整槽覆盖
|
|
175
180
|
* (`spec.hooks ?? deps.hooks`),不折叠就静默 shadow 部署观测(runtask gateBaseline 注释点名的坑)。
|
|
176
181
|
* 语义:部署槽先跑(观测看到原始输入/输出),task 槽后跑;结果合并:
|
|
177
|
-
* - preToolUse
|
|
178
|
-
* (
|
|
182
|
+
* - preToolUse:每一槽的结果**先过 core 的 hook 席收据** `receiveHookResult`(S-971,core 7.39.0 #1274)—— 引擎拒的载体 ⇒ 交回引擎的拒绝体
|
|
183
|
+
* (短路,引擎按自己的 fail-closed 拒处置);收下的 ⇒ 折它的冻结副本:deny > ask > allow 折叠;updatedInput 串行线程(部署重写喂给 task 槽);context 拼接。
|
|
179
184
|
* - postToolUse/postToolUseFailure/postToolBatch:双跑,additionalContext 拼接,updatedOutput 取
|
|
180
185
|
* 后者(task 槽)优先。
|
|
181
186
|
* - userPromptSubmit/stop:部署 block 优先短路(不再跑 task 槽),否则双跑、context 拼接、task block 生效。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { hostShell, resolveHostShell, adoptHostForegroundChild } from "../plugins/host-platform.js";
|
|
3
|
-
import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, } from "@sema-agent/settings-schema/hooks";
|
|
3
|
+
import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, HOOK_EVENT_MATCHER_FIELD, } from "@sema-agent/settings-schema/hooks";
|
|
4
|
+
import { describeToolCatalog, isEngineRefusal, receiveHookResult } from "@sema-agent/core";
|
|
4
5
|
import { boundedNoticeText } from "../fleet/fleet-bus.js";
|
|
5
6
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
6
7
|
import { redactSecrets, buildDisplaySafeUrl } from "../trace/redact.js";
|
|
@@ -56,6 +57,49 @@ function toAssistantTurnError(errorKind) {
|
|
|
56
57
|
return "unknown";
|
|
57
58
|
return Object.prototype.hasOwnProperty.call(BRAIN_TO_TURN_ERROR, errorKind) ? (BRAIN_TO_TURN_ERROR[errorKind] ?? "unknown") : "unknown";
|
|
58
59
|
}
|
|
60
|
+
const NAME_ALTERNATION = /^[A-Za-z0-9_-]+(?:\|[A-Za-z0-9_-]+)*$/;
|
|
61
|
+
function matcherReadsWireToolName(event) {
|
|
62
|
+
return HOOK_EVENT_OWNER[event] === "engine" && HOOK_EVENT_MATCHER_FIELD[event] === "tool_name";
|
|
63
|
+
}
|
|
64
|
+
let engineToolAliasTable;
|
|
65
|
+
function engineToolAliases() {
|
|
66
|
+
if (engineToolAliasTable !== undefined)
|
|
67
|
+
return engineToolAliasTable;
|
|
68
|
+
const entries = describeToolCatalog();
|
|
69
|
+
const wireNames = new Set(entries.map((e) => e.name));
|
|
70
|
+
const table = new Map();
|
|
71
|
+
for (const e of entries) {
|
|
72
|
+
for (const alias of e.face.aliases ?? []) {
|
|
73
|
+
if (!wireNames.has(alias) && !table.has(alias))
|
|
74
|
+
table.set(alias, e.name);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
engineToolAliasTable = table;
|
|
78
|
+
return table;
|
|
79
|
+
}
|
|
80
|
+
function deadAliasInMatcher(event, matcher) {
|
|
81
|
+
if (matcher === undefined || !matcherReadsWireToolName(event) || !NAME_ALTERNATION.test(matcher))
|
|
82
|
+
return undefined;
|
|
83
|
+
const branches = matcher.split("|");
|
|
84
|
+
const aliases = engineToolAliases();
|
|
85
|
+
for (const branch of branches) {
|
|
86
|
+
const canonical = aliases.get(branch);
|
|
87
|
+
if (canonical !== undefined && !branches.includes(canonical))
|
|
88
|
+
return { alias: branch, canonical };
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
export function deadToolMatcherIssue(config) {
|
|
93
|
+
for (const [event, groups] of Object.entries(config)) {
|
|
94
|
+
for (const group of groups ?? []) {
|
|
95
|
+
const dead = deadAliasInMatcher(event, group.matcher);
|
|
96
|
+
if (dead) {
|
|
97
|
+
return `settings.hooks.${event}: matcher "${dead.alias}" is an alias; tool-event matchers match the tool's wire name (PostToolUse / PostToolUseFailure since 7.107.0) — use "${dead.canonical}"`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
59
103
|
export function parseHooksConfig(raw) {
|
|
60
104
|
const parsed = HooksConfig.safeParse(raw);
|
|
61
105
|
if (!parsed.success) {
|
|
@@ -1190,10 +1234,10 @@ export function composeHooks(deployment, task) {
|
|
|
1190
1234
|
for (const slot of [d.preToolUse?.bind(d), t.preToolUse?.bind(t)]) {
|
|
1191
1235
|
if (!slot)
|
|
1192
1236
|
continue;
|
|
1193
|
-
const r = await slot(toolName, current, ctx);
|
|
1194
|
-
if (r === undefined ||
|
|
1237
|
+
const r = receiveHookResult(await slot(toolName, current, ctx));
|
|
1238
|
+
if (r === undefined || r === null || r === false)
|
|
1195
1239
|
continue;
|
|
1196
|
-
if (
|
|
1240
|
+
if (isEngineRefusal(r))
|
|
1197
1241
|
return r;
|
|
1198
1242
|
if (r.additionalContext)
|
|
1199
1243
|
contexts.push(r.additionalContext);
|
package/dist/host-decision.d.ts
CHANGED
|
@@ -22,7 +22,9 @@ import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
|
|
|
22
22
|
* 而 sweep 结构上不能 `allow`)。
|
|
23
23
|
* · `decidedBy: "policy"`(core 7.26.2 / #1016)—— **这台部署自己的策略**拒的:答话的是机器不是人。调用方
|
|
24
24
|
* 经决裁体的 `settledBy` 自报;core 据它给模型铸一句**策略句**(而不是「用户不同意,停下来等他」),
|
|
25
|
-
* 并把结算记成 `policy_refused`(`who.party:"none"`)。`allow`
|
|
25
|
+
* 并把结算记成 `policy_refused`(`who.party:"policy"`,core 7.39.0 #1265 起;此前 `"none"`)。`allow` 侧:core 7.39.0 起引擎收策略的
|
|
26
|
+
* 放行(`policy_allowed`),但本仓的两条 decide 口**不开**这一形(`tool-approval.ts` 的 `SETTLED_BY_ONLY_ON_DENY` 与店列的同一条拒仍在 ——
|
|
27
|
+
* 耐久车道上的机器放行是一件没裁过的新能力,不在 7.113.0 提货批内,候登)。
|
|
26
28
|
* · `approver` —— 通道自报的结算方标识串。**缺席永不读成**「没人批」或「有人批」,只意味着通道没报名字。
|
|
27
29
|
*/
|
|
28
30
|
/**
|
|
@@ -74,8 +76,8 @@ export type HostDecidedBy = HostDecision["decidedBy"];
|
|
|
74
76
|
* 被改造过的串):归属是转录,不是可以「修一修」的东西 —— 修出来的名字既不是通道报的那个,也不是
|
|
75
77
|
* 「通道没报」,两头都不真。
|
|
76
78
|
*
|
|
77
|
-
* 🔴 归属**不按词分岔**:core 的 `policy_refused`
|
|
78
|
-
*
|
|
79
|
+
* 🔴 归属**不按词分岔**:core 7.38.x 的 `policy_refused` 结算记录上没有归属席(`who.party:"none"`),7.39.0 #1265 起有了
|
|
80
|
+
* (`who.party:"policy"`,`approver?`)—— 无论 core 的记录怎么写,**本服务观察到的事实**不变:送出这次请求的那把已验证身份是真的,本仓的
|
|
79
81
|
* 迟到结算账本(`approval-ask-audit-store.ts`)记的就是它。按词删一格 = 给账本挖一个只在某个词上出现的洞。
|
|
80
82
|
*
|
|
81
83
|
* 🔴 **入参是 `string`,不是 `HostDecidedBy` —— 这一格刻意让类型说真话**(wire 边界强类型宪法):两条
|
package/dist/http/admission.js
CHANGED
|
@@ -7,7 +7,7 @@ import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
|
7
7
|
import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
8
8
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
9
9
|
import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
|
|
10
|
-
import { parseHooksConfig } from "../hooks/hook-runner.js";
|
|
10
|
+
import { deadToolMatcherIssue, parseHooksConfig } from "../hooks/hook-runner.js";
|
|
11
11
|
import { judgeRequestWebSearch } from "../plugins/web-search.js";
|
|
12
12
|
import { normalizeApproachNotice, unknownTaskAgentModelRef, validateTaskAgents } from "../spec-fields.js";
|
|
13
13
|
import { isValidCwd, MAX_ADDITIONAL_DIRS, MAX_CWD_CHARS } from "../task-cwd.js";
|
|
@@ -200,6 +200,11 @@ export function createAdmission(ctx) {
|
|
|
200
200
|
sendError(res, 400, "request.field_invalid", hooksParsed.error ?? "settings.hooks: invalid shape");
|
|
201
201
|
return null;
|
|
202
202
|
}
|
|
203
|
+
const deadMatcher = deadToolMatcherIssue(hooksParsed.config);
|
|
204
|
+
if (deadMatcher !== undefined) {
|
|
205
|
+
sendError(res, 400, "request.field_invalid", deadMatcher);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
203
208
|
}
|
|
204
209
|
{
|
|
205
210
|
const webSearchVerdict = judgeRequestWebSearch(st.webSearch);
|
|
@@ -545,11 +550,15 @@ export function createAdmission(ctx) {
|
|
|
545
550
|
if (body.attachments !== undefined) {
|
|
546
551
|
const a = body.attachments;
|
|
547
552
|
if (typeof a !== "object" || a === null || Array.isArray(a)) {
|
|
548
|
-
sendError(res, 400, "request.field_invalid", "attachments must be an object { todoReminder?, todoReminderMode?, changedFiles?,
|
|
553
|
+
sendError(res, 400, "request.field_invalid", "attachments must be an object { todoReminder?, todoReminderMode?, changedFiles?, budgetUsd?, backgroundTasks?, toolsDelta?, agentListing?, skillsListing?, mcpInstructions? }");
|
|
549
554
|
return null;
|
|
550
555
|
}
|
|
551
556
|
const rec = a;
|
|
552
|
-
|
|
557
|
+
if (rec.planModeReminder !== undefined) {
|
|
558
|
+
sendError(res, 400, "request.field_invalid", 'attachments.planModeReminder was retired in core 7.39.0 — the engine now states plan posture itself whenever the permission mode is "plan" (first request, periodic reminders, exit frame); remove the key (send permissionMode "plan" for plan posture)');
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
for (const k of ["todoReminder", "budgetUsd", "backgroundTasks", "toolsDelta", "agentListing", "skillsListing", "mcpInstructions"]) {
|
|
553
562
|
if (rec[k] !== undefined && typeof rec[k] !== "boolean") {
|
|
554
563
|
sendError(res, 400, "request.field_invalid", `attachments.${k} must be a boolean`);
|
|
555
564
|
return null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GLOBAL_USAGE_KEY, usageRetryAfterMs } from "@sema-agent/core";
|
|
2
2
|
import { HttpError, principalFrom } from "../security.js";
|
|
3
|
-
import { RUN_NOT_FOUND_MESSAGE } from "./route-ctx.js";
|
|
3
|
+
import { RUN_NOT_FOUND_MESSAGE, SESSION_NOT_FOUND_MESSAGE } from "./route-ctx.js";
|
|
4
4
|
import { sendError } from "./send.js";
|
|
5
5
|
import { authorized, systemFor, gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal } from "./principal-gate.js";
|
|
6
6
|
const MAX_BODY = 8 * 1024 * 1024;
|
|
@@ -165,7 +165,7 @@ export function createRequestGuards(ctx) {
|
|
|
165
165
|
}
|
|
166
166
|
if (!runOwnerReadable(principal, owner, deps.config)) {
|
|
167
167
|
if (notFoundForm === "session")
|
|
168
|
-
sendError(res, 404, "not_found.session",
|
|
168
|
+
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
169
169
|
else
|
|
170
170
|
sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
|
|
171
171
|
return false;
|
|
@@ -7,21 +7,26 @@
|
|
|
7
7
|
* 字节不变)。同居是第三条,也是唯一一条既保住静态序又保住 move-only 的。序由 `const` 的 TDZ 亲自执法:
|
|
8
8
|
* 腿是提升的函数声明、ctx 是文件尾的 `const`,谁也不会在工厂执行期读到对方。
|
|
9
9
|
*
|
|
10
|
-
* ctx 字段
|
|
11
|
-
*
|
|
10
|
+
* ctx 字段 27 只(registry 6 + helpers 14 + deps/runnerFor/prepareSpec/finalizeTaskResult + 三只配额/租约/用量窗
|
|
11
|
+
* 判据;S-947 删 `cancelledViaVerb` 旁表后 28 → 27,与 `test/split-ctx-parity.test.ts` 的读数同)—— 这是**如实披露**的耦合读数(设计稿 §9:把它变小是波 3 的事,本刀不承诺)。型一律从既有型派生
|
|
12
12
|
* (`RunRegistry` / `RouteHelpers` / `RouteLegs` / `ReturnType<typeof createRequestGuards>`),不手抄签名 ⇒ 不会漂。
|
|
13
13
|
*/
|
|
14
14
|
import type { IncomingMessage } from "node:http";
|
|
15
|
-
import { type QuestionAnswer, type CheckpointToken } from "@sema-agent/core";
|
|
15
|
+
import { CheckpointError, type QuestionAnswer, type CheckpointToken } from "@sema-agent/core";
|
|
16
16
|
import type { DecideBinding } from "./wire-types.js";
|
|
17
17
|
import type { CheckpointStoreFull } from "../plugins/store-backend.js";
|
|
18
18
|
import { type HostDecision } from "../host-decision.js";
|
|
19
19
|
import { type PlanApprovePermissionModeAfter } from "../task-settings.js";
|
|
20
|
-
import type { RouteCtxBase, DriveResumeArgs, PreparedPlanReview } from "./route-ctx.js";
|
|
20
|
+
import type { RouteCtxBase, DriveResumeArgs, HostLegOutcome, PreparedPlanReview, ResumeLegOutcome } from "./route-ctx.js";
|
|
21
21
|
import { type TrustedRunInternals } from "./durable-run-leg.js";
|
|
22
22
|
import type { RunRegistry, RouteHelpers, RouteLegs } from "./route-ctx.js";
|
|
23
23
|
import type { createRequestGuards } from "./request-guards.js";
|
|
24
24
|
import type { FlatServiceDeps } from "./server.js";
|
|
25
|
+
/**
|
|
26
|
+
* {@link CHECKPOINT_ERROR_DISPOSITION} 的读口(纯函数,resume 腿的 catch 臂与钉共用)。运行期遇到表外的码(装树 core 比本二进制多一个码的版本偏斜 ——
|
|
27
|
+
* 型面上拼不出来)⇒ `"terminal"`:与 `by_reason` 认不得的词同一条 fail-closed 方向(不宽容默认成可重试),也与修前手列串的缺省逐字相同。
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkpointErrorDisposition(e: CheckpointError): "cancelled" | "retriable" | "terminal";
|
|
25
30
|
export declare function createResumeLegs(ctx: {
|
|
26
31
|
deps: FlatServiceDeps;
|
|
27
32
|
runnerFor: FlatServiceDeps["runnerFor"];
|
|
@@ -33,21 +38,12 @@ export declare function createResumeLegs(ctx: {
|
|
|
33
38
|
}) => Promise<void>, acceptEarly?: boolean, decider?: {
|
|
34
39
|
principal?: string;
|
|
35
40
|
explicitOperator: boolean;
|
|
36
|
-
}) => Promise<
|
|
37
|
-
|
|
38
|
-
body: object;
|
|
39
|
-
}>;
|
|
40
|
-
driveResumeIntoRunLog: (args: DriveResumeArgs) => Promise<{
|
|
41
|
-
status: number;
|
|
42
|
-
body: object;
|
|
43
|
-
}>;
|
|
41
|
+
}) => Promise<ResumeLegOutcome>;
|
|
42
|
+
driveResumeIntoRunLog: (args: DriveResumeArgs) => Promise<ResumeLegOutcome>;
|
|
44
43
|
driveResumeLeg: (args: DriveResumeArgs, onAccepted?: (receipt: {
|
|
45
44
|
status: number;
|
|
46
45
|
body: object;
|
|
47
|
-
}) => void) => Promise<
|
|
48
|
-
status: number;
|
|
49
|
-
body: object;
|
|
50
|
-
}>;
|
|
46
|
+
}) => void) => Promise<ResumeLegOutcome>;
|
|
51
47
|
mayDiscloseRowError: (cs: CheckpointStoreFull, token: CheckpointToken, decider: {
|
|
52
48
|
principal?: string;
|
|
53
49
|
explicitOperator: boolean;
|
|
@@ -59,14 +55,8 @@ export declare function createResumeLegs(ctx: {
|
|
|
59
55
|
status: number;
|
|
60
56
|
body: object;
|
|
61
57
|
}>;
|
|
62
|
-
startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, trustedInternals: TrustedRunInternals, runId: string) => Promise<
|
|
63
|
-
|
|
64
|
-
body: object;
|
|
65
|
-
}>;
|
|
66
|
-
resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowParkedResume?: DriveResumeArgs["workflowParkedResume"], ownerLeaseAdmittedFor?: string) => Promise<{
|
|
67
|
-
status: number;
|
|
68
|
-
body: object;
|
|
69
|
-
}>;
|
|
58
|
+
startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, trustedInternals: TrustedRunInternals, runId: string) => Promise<HostLegOutcome>;
|
|
59
|
+
resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowParkedResume?: DriveResumeArgs["workflowParkedResume"], ownerLeaseAdmittedFor?: string) => Promise<HostLegOutcome>;
|
|
70
60
|
preparePlanReview: (sessionId: string, log?: {
|
|
71
61
|
taskId?: string;
|
|
72
62
|
principalPresent?: boolean;
|