@sema-agent/server 7.108.0 → 7.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/MIGRATION.md +60 -1
  2. package/README.md +8 -0
  3. package/README.zh-CN.md +5 -0
  4. package/USAGE.md +19 -29
  5. package/dist/approval-ask-machine.d.ts +9 -25
  6. package/dist/approval-ask-machine.js +5 -2
  7. package/dist/approval-card.d.ts +33 -3
  8. package/dist/approval-card.js +26 -2
  9. package/dist/bench/s1/live-deps.d.ts +4 -5
  10. package/dist/bench/s1/live-deps.js +9 -10
  11. package/dist/boot/leader.js +0 -2
  12. package/dist/boot/reapers.d.ts +3 -0
  13. package/dist/boot/reapers.js +3 -2
  14. package/dist/boot/resolve-spec.d.ts +1 -1
  15. package/dist/boot/resolve-spec.js +6 -12
  16. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  17. package/dist/boot/stage-07-capability-layer.js +17 -3
  18. package/dist/boot/stage-08-reapers.d.ts +1 -0
  19. package/dist/boot/stage-08-reapers.js +2 -2
  20. package/dist/boot/stage-09-leader.d.ts +1 -0
  21. package/dist/boot/stage-10-http-server.d.ts +1 -0
  22. package/dist/boot/stage-10-http-server.js +2 -1
  23. package/dist/capabilities/scenarios.d.ts +3 -2
  24. package/dist/config-catalog.js +2 -4
  25. package/dist/config-provider.js +1 -0
  26. package/dist/config-types.d.ts +5 -11
  27. package/dist/config.js +21 -12
  28. package/dist/deployment-governance.d.ts +8 -2
  29. package/dist/deployment-governance.js +9 -8
  30. package/dist/hooks/hook-runner.d.ts +2 -2
  31. package/dist/hooks/hook-runner.js +40 -26
  32. package/dist/http/admission.js +8 -2
  33. package/dist/http/durable-run-leg.js +1 -1
  34. package/dist/http/resume-legs.js +6 -1
  35. package/dist/http/route-ctx.d.ts +3 -3
  36. package/dist/http/routes/capabilities.js +1 -0
  37. package/dist/http/routes/runs.d.ts +0 -1
  38. package/dist/http/routes/runs.js +22 -0
  39. package/dist/http/routes/sessions.js +12 -2
  40. package/dist/http/routes/tasks.js +5 -1
  41. package/dist/http/server.d.ts +7 -2
  42. package/dist/http/wire-types.d.ts +8 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.js +1 -0
  45. package/dist/instruction-source-notice.d.ts +3 -3
  46. package/dist/leader/diffup.d.ts +53 -0
  47. package/dist/leader/diffup.js +95 -1
  48. package/dist/leader/leader.d.ts +0 -6
  49. package/dist/leader/leader.js +1 -1
  50. package/dist/leader/repair-wire.js +1 -1
  51. package/dist/leader/wire.d.ts +3 -10
  52. package/dist/leader/wire.js +39 -13
  53. package/dist/memory-layer-legacy-lock.d.ts +3 -3
  54. package/dist/model-entry-headers.d.ts +5 -2
  55. package/dist/model-entry-headers.js +4 -4
  56. package/dist/model-unreachable-notice.d.ts +2 -2
  57. package/dist/observability/corrupt-read-seat.d.ts +6 -0
  58. package/dist/observability/corrupt-read-seat.js +6 -0
  59. package/dist/observability/fail-open.d.ts +13 -5
  60. package/dist/observability/fail-open.js +13 -5
  61. package/dist/observability/metrics.js +2 -1
  62. package/dist/observability/run-terminal.js +10 -0
  63. package/dist/operator-ask.d.ts +9 -8
  64. package/dist/operator-ask.js +9 -1
  65. package/dist/plugins/approval-ask-store-file.d.ts +2 -1
  66. package/dist/plugins/approval-ask-store-file.js +3 -0
  67. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  68. package/dist/plugins/approval-ask-store-memory.js +7 -1
  69. package/dist/plugins/approval-ask-store-sql.d.ts +18 -0
  70. package/dist/plugins/approval-ask-store-sql.js +7 -0
  71. package/dist/plugins/file-run-store.js +2 -2
  72. package/dist/plugins/memory-run-store.d.ts +1 -1
  73. package/dist/plugins/memory-run-store.js +2 -1
  74. package/dist/plugins/pg-pool.js +15 -0
  75. package/dist/plugins/run-store-sql.d.ts +1 -1
  76. package/dist/plugins/run-store-sql.js +3 -2
  77. package/dist/plugins/schema-indexes.js +2 -0
  78. package/dist/plugins/store-backend.d.ts +4 -0
  79. package/dist/plugins/store-backend.js +8 -0
  80. package/dist/plugins/store-contracts.d.ts +7 -0
  81. package/dist/plugins/store-contracts.js +1 -0
  82. package/dist/plugins/tidb-pool.js +17 -0
  83. package/dist/plugins/user-input-ledger-file.d.ts +33 -0
  84. package/dist/plugins/user-input-ledger-file.js +98 -0
  85. package/dist/plugins/user-input-ledger-sql.d.ts +44 -0
  86. package/dist/plugins/user-input-ledger-sql.js +101 -0
  87. package/dist/request-key-closure.js +1 -1
  88. package/dist/run-local.d.ts +18 -5
  89. package/dist/run-local.js +26 -12
  90. package/dist/runs.d.ts +8 -1
  91. package/dist/runs.js +6 -2
  92. package/dist/runtime-governance.d.ts +23 -11
  93. package/dist/runtime-governance.js +7 -12
  94. package/dist/session-records-skipped-notice.d.ts +4 -3
  95. package/dist/task-settings.d.ts +0 -6
  96. package/dist/task-settings.js +0 -1
  97. package/dist/tool-approval.d.ts +44 -15
  98. package/dist/tool-approval.js +12 -8
  99. package/dist/trace/core-keyset-guard.d.ts +17 -2
  100. package/dist/trace/engine-notice-wire.d.ts +30 -35
  101. package/dist/trace/engine-notice-wire.js +23 -23
  102. package/dist/trace/project.js +2 -3
  103. package/dist/trace/self-minted-wire.d.ts +14 -0
  104. package/dist/trace/self-minted-wire.js +6 -0
  105. package/dist/unattended-permission-mode.d.ts +54 -93
  106. package/dist/unattended-permission-mode.js +8 -61
  107. package/dist/user-input-ledger.d.ts +296 -0
  108. package/dist/user-input-ledger.js +337 -0
  109. package/package.json +4 -4
package/MIGRATION.md CHANGED
@@ -7,6 +7,64 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.110.0(车ST,S-955;小车 SV,S-956;车SX,S-958 / S-959;小车 SA,S-962 / S-963;主会话 S-964;车SW,S-928)—— `@sema-agent/core` 7.38.0(minor,core 硬破坏 0):通告码受众 / 铸者改读 core 表 · `terminal.failed.detail` 过境 · hook 载荷 `permission_mode` = 腿实时模式 · 类对判据走 core 屏 · run-local 无 TTY 拒记部署政策名下 · 复合体读不出词的 PreToolUse 结果交引擎屏 · 🔴 `MEMORY_SYNC_SCOPE` 缺省不再从非 v2 `MEMORY_SCOPE` 派生(拒启) · 崩溃丢掉的 200-applied steer 下一条 run 告知(用户输入受理账本)
11
+
12
+ - **🔴 记忆同步 env(S-964,配置面硬破坏)**:单用户 file 引擎上设了 `MEMORY_SYNC_URL` / `MEMORY_SYNC_TOKEN`、**没设** `MEMORY_SYNC_SCOPE`、且 `MEMORY_SCOPE` 不是 typed 键(`user:` / `org:` / `proj:` / `userproj:`;含缺省 `local`)的部署**升级后拒启**。此前这一形派生同步层 `user:<MEMORY_SCOPE>` —— 一个本部署从不写的层(写家是裸 `MEMORY_SCOPE`,core 同步按同一个名字取条目)⇒ 同步腿自 142-S2.5-W1 起每轮推 0 条。出路二选一(错误句原文点名):`MEMORY_SYNC_SCOPE=<要复制的层>`,或把 `MEMORY_SCOPE` 设成中心期待的 typed 键(如 `user:<tenant>`,缺省同步层随之同名)。显式 `MEMORY_SYNC_SCOPE` 的部署零变化。
13
+
14
+ - **hook 作者(stdin 载荷契约)**:`permission_mode` 的值源从「请求上的模式词」改为**腿的实时模式**(core 回调上下文 `permissionMode`,plan 腿续跑 / 模式释放后的真实值);PreCompact / PostCompact / StopFailure 三事件与上下文缺席时**不带**该键(此前恒带请求词)。按旧值做分支的 hook 改读实时词、对缺席作「无模式语义」处理。**接手屏**(core 7.38.0):PreToolUse hook 结果越界 `action` / 字串布尔 / `null` 成员 / 不可克隆改写 ⇒ 引擎拒(工具不跑、模型读接手句、`permissionDenied` 观察者响);无 `action` 仍是无异议 —— 本服务映射核在约内,部署自写 hook 自查。经本服务 hooks 复合体(部署基线恒挂,挂了 hooks 的 run 都经过它)的结果同判:读不出词的结果(集外 `action`、非对象 —— 含 `0` / `""` / `NaN`;无异议只认 `undefined` / `null` / `false`)原样交引擎屏 ⇒ 引擎拒(S-959;修前复合体把它读作无异议、照跑,带 `additionalContext` 的还被译成 allow)。
15
+ - **嵌入方(类型面,不在包导出面)**:`SERVER_NOTICE_AUDIENCE` / `ServerNoticeCode` / `isServerNoticeCode` / `HookRunnerContext.permissionMode` / `ENGINE_ABSENT_PERMISSION_MODE` 仓内硬删 —— 四枚本服务铸码的受众 / 铸者改读 core `NOTICE_AUDIENCE` / `NOTICE_MINTER`;client-core 镜像行可退役(core 包已含四码)。
16
+ - **结果面(additive)**:`terminal.failed.detail? { kind:"provider_rejected", status, code? }` 过境(同步 200 体 / `done.result` / `GET /v1/runs/:id` / `done` 账本行);判「可改重发」看 `status`(401 / 403 / 429 也带)。`engine_notice` D.3 白名单 +`task.tool_turn_incomplete`(user);`memory.layer_locked_legacy` 明拒(operator)。`/metrics` `brain_echo_dropped_total{field}` 可出现 `field="apiErrorCode"`。
17
+ - **审批面(值层,键不变)**:`askClass` / `classes` 读写两面统一走 core `screenAskClassMembers`:投规范序副本;重复员 / 非规范序首员不符形改判(只在 core 自己不写出的形上);`card_json` 存量卡零影响。
18
+ - **🔴 迁移窗口(core #1213)**:本版提货 core 7.38.0 **仍迁移旧模式键检查点行、不拒**;排空窗到提货 core 7.39.0 的 server 版前(7.109.0 段原句)。
19
+ - **run-local(S-956)**:无 TTY 被拒的 run 不再带 `haltedOnUserRejection`。退出码仍为 1;非 `--json` 停因行从 `task stopped (haltedOnUserRejection) …` 改为 `task incomplete (approval unanswered) …`;门记录 `settlement.kind` `human_refused` → `policy_refused`、`who.party` `person` → `none`;模型读 core 策略句 + 拒因。按 7.101.0 迁移建议读 `--json` 的 `haltedOnUserRejection` 判拒的脚本,改看退出码 / stderr 停因行。同一拍的兄弟调用不再被 `gate.batch_halted` 连坐,各自过门。委派子代里无 TTY 的拒也计数 ⇒ CLI 退 1(更严;TTY 下子代人答 N 仍退 0)。
20
+ - **嵌入方(类型面硬破坏,S-928)**:`StoreBackend`(包根导出型)新增**必选** `userInputLedger(): UserInputLedgerStore`;自建 StoreBackend 的嵌入方 tsc 红即通知(7.109.0 `listByCall` 同形);返回型 `UserInputLedgerStore` 及行型 `UserInputAcceptance` / `NewUserInputAcceptance` / `SettleHow` / `OpenScanCursor` / `WriterGoneEvidence` 自本版从包根导出。
21
+ - **用户输入受理账本(S-928;零动作)**:live 腿 steer 在回 200 之前先落一行受理(local:`<数据根>/user-input-ledger/ledger.jsonl`;SQL:新表 `user_input_acceptance`,
22
+ 中央 `ensureSchema` 以 `CREATE TABLE IF NOT EXISTS` 自建,两方言 + 两条索引,无 ALTER、无回填)。账本写不进 ⇒ steer 回 **503 `steering.ledger_unavailable`**(此前 200;引擎零字节)——
23
+ local 车道数据根只读 / 盘满、SQL 车道库不可达时出现,其余路由不受影响。进程崩溃时丢掉的受理在该会话下一条 run 首拍告知 `task.user_steer_undrained{cause:"crashed"}`,
24
+ `GET /v1/sessions/:id` 多一个可选键 `userInputUndrained`(缺席 = 零条)。**回滚**:回到 ≤7.109.x 时账本文件 / 表留着无害(旧二进制不读);滚回之后的崩溃丢失恢复修前的静默。
25
+ - **零数据迁移**;DDL 只有上一条的新表(自建)。
26
+
27
+ ## 7.109.0(车SK,S-934)—— `@sema-agent/core` 7.37.1(7.37.0 的全部变更 + #1224 工具回合律请求修复,运维通告 `context.tool_turn_repaired` 不上会话流):无人值守腿按类作答(`UNATTENDED_PERMISSION_MODE` 退役,设了即拒启)· ask 类对 `askClass` / `classes` 上 wire · 策略决定对象的普通对象律 · 迁移窗口
28
+
29
+ - **🔴 env:`UNATTENDED_PERMISSION_MODE` 设了即拒启**(任何值,含 7.106.0 的缺省 `bypassPermissions`;拒因带 `config.env_key_retired` 并点名键)。
30
+ **迁移**:从 `.env` / 编排清单里删掉它。leader worker / leader 修复腿 / `sema` 一次性 CLI(run-local)及 leader worker park 的 `/decide` 续跑,
31
+ 自本版起恒按 `default` 跑,审批席**按类作答**:机器只答引擎盖了 `ordinary` 类的问(普通写、非只读 shell 的形状层问 —— 照常落地)。
32
+ **写面与 `ordinary` 的边界**:`roots` 只约束 Write / Edit 这类结构化文件工具;shell 的根外写与 Read 工具的根外读属 core `ordinary`,无人值守腿上由机器答(与 ≤7.108.0 的 bypass 放行同一落地结果,记法不同)。
33
+ **审计读者**:机器按类放行在 core 门记录里写成 `settlement.kind:"human_allowed"` / `who.party:"person"`(core 没有「机器放行」归属词,S-953 转 core)—— 无人值守腿上读作机器放行;无席且无 durable 块的 leader 腿被拒的问(run-local 有本地席,不走这一词)结算词 `no_approver` → `approver_unavailable`,按词告警的运维改比码。
34
+ - **🔴 行为面(安全轴收紧)**:这些腿上此前被缺省 `bypassPermissions` 放掉的六类问 —— shell 读会话目录外(`probe`)、MCP / A2A 工具标记、
35
+ transcript 写、规则店读不出(`rule_unreadable`)、共享制品读(`artifact_shared_read`)—— 改为**不答**:run-local 有终端时问你 y/N、无终端当场拒;
36
+ leader 各腿配了 durable 审批块 ⇒ park 到 `/v1/approvals/:id/decide`,没配 ⇒ 当场拒。写保护表目标从「策略层路径拒行拒(人在场也答不了)」改成一问
37
+ (有席问人 / 无席 park 或拒);`cp <写保护目标> <普通目标>` 这类只读写保护源的命令不再被过拒。**迁移**:靠无人值守 worker 读工作区外文件 /
38
+ 调带标记 MCP 工具的流程,给 leader 配 durable 审批块让人答,或把要读的东西放进工作区。
39
+ - **wire(additive)**:live `tool_approval` 帧、`approval_request` 呈卡帧的 `card`(= `card_json` = 断连重放帧)、`GET /v1/approvals` 队列行与 `/stream`
40
+ 的 `pending` 帧新增两只可选键 `askClass?` / `classes?`(core 盖的类对,两键同在同缺;缺席 = 老引擎 / 老行 / 不盖类的站,**不是**「普通」)。
41
+ 零动作;按类渲卡 / 按类答的壳读**整只** `classes`。回滚窗:`card_json` 是 `.strict()` 形 ⇒ 回滚到 ≤7.108.0 的副本读不动带这两键的新卡行
42
+ (重放跳过 + 一次 warn,幂等重入走 park —— 与既往 additive 卡键同一代价),整队滚。
43
+ - **🔴 行为面(core 7.37.0,策略 / hook 作者)**:引擎对策略 / hook / 祖先返回的决定对象**只读一次**,只收**普通对象**:`action` 必须是
44
+ `allow` / `deny` / `ask` 之一;契约成员必须是对象**自有可枚举**成员(不放原型上、不靠 getter、不靠 Proxy);改写(`updatedInput` 等)只放可克隆纯数据。
45
+ 不合 ⇒ 接手即拒(工具不执行)。本服务自身的策略 / hook 全是字面对象或展开(亲核),部署自写的 `ToolPolicy`(嵌入方)按此自查。
46
+ **委派**:祖先层一次「是」只覆盖它答过的类 —— 子代门查到更严的类(写保护、根外读)会再出一张卡;带出网 / 不可逆标记的工具在委派下首卡即
47
+ `operator_forced`(`bypassPermissions` 不放它)。持久 ask 店车道(流内审批协议上场的部署)上第二张卡同样出卡(S-944,见下一条)。
48
+ - **🔴 安全轴(S-944):持久 ask 店车道上的 askId 身份含这一问的类集**。委派下同一调用的第二张卡(类集不同)自本版是**新问**:新行、出自己的卡、
49
+ 等人答这一问 —— 此前它与首卡同 askId,被首卡的「是」回放。同一调用同类集的重试照旧幂等。**行为面(收紧)**:会话授权(`allow_session` /
50
+ 耐久 `remember:"session"`)不再代答「这次调用在这条腿上已出过卡」的后续问(同一调用的另一类集问)—— 出卡,人答。**wire 值变更**:不透明 `askId`(呈卡帧、
51
+ `POST /v1/tasks/:taskId/asks/:askId/decision`)对同一调用的取值与 ≤7.108.0 不同(形不变,64 位 hex);持久 `approval_ask.ask_id` 主键的派生同变,
52
+ **零 DDL、零迁移、零兼容读**。**升级影响(亲核)**:升级前写的行按旧派生,行上存的 askId 原样可答 / 可重放 / 可对账(本服务没有任何一处从行或
53
+ 停车行反算 askId);跨 park 续跑的腿本来就换 `legKey` ⇒ 新派生,与旧行无交集 ⇒ 无影响。**同一条腿跨版本重入**(同 run、同 `legKey`:重试 /
54
+ 闭包再入 / 跨副本 failover / 同一 resume token 重投)同一调用时,新版派生的新 id 不命中旧行 ⇒ 这一问出卡再问(人再答一次,fail-closed);
55
+ 会话授权(live `allow_session` / 耐久 `remember:"session"`)**不会**替它代答 —— 一揽子放行的复核按**调用**读(这次调用在这条腿上有任何行,
56
+ 含旧派生写的人拒 / 挂着的卡 ⇒ 不短路),与 ask 身份无关。**迁移**:自建 `StoreBackend`(不走 `createStoreBackend`)的嵌入方须实现 `ApprovalAskStore.listByCall`(类型面硬破坏,tsc 红即通知);部署零 DDL、零数据迁移。**滚动升级**:混版窗内旧版副本接管本版写过的腿读不到新派生行(反向),
57
+ 与 7.107.0 段「持久面反向不兼容边界」同一处置:同一存储上不要让旧版实例接管本版在跑的腿。
58
+ - **leader worker 的增量上传循环随 env 收尾(S-949,P1;嵌入方 / 夹具可见)**:worker `executionEnvFactory` 交出的 env 若**没有 `destroy()`**,工厂当场拒收(与集成沙箱 / grader 同一道闸;生产三车道 e2b / k8s / host 都有);stage 末步起循环、finalize 首步停循环(先于 belt 与真 destroy,等每一代退出 ≤30 s),循环的每次上传带 `curl --max-time 25`;工作区多出 `.leader-upload-loop.sh` / `.gen` / `.seq` / `.pid.<代>`(无凭据)。**迁移**:自建 worker env 的嵌入方补 `destroy()`;依赖「循环与 belt 并跑」时序的无;收尾每 worker 常态多 ≤1–2 s(等循环下一拍退出),上界 = stop 等待 30 s(循环正卡在一次上传上时)。已知边界:S-950(e2b 车道循环是否真活着未核)/ S-951(stop 超界残余窗)。
59
+ - **目录条目 `headers` 两条读腿打通(S-935,settings-schema 6.4.0)**:`config.d/models.json` 与配置中心写的 `models[].headers` 自本版起到达目录(7.108.0 被读腿剥掉)。
60
+ **行为面**:秘密形的头值(形似令牌 / 密钥)在 schema 自由形安全闸即让条目不可用(拒因字段整键 `headers`)。**迁移**:零动作。
61
+ - **🔴 迁移窗口(core #1213;core 7.38.0 发车帖把它改期到 **7.39.0**)**:core 7.39.0 删掉旧检查点行的一次性迁移、并**拒续跑**仍带旧模式键(`autoModeRequested` / `approverPosture` /
62
+ doctrine `shellGate`)的行。这类行只可能是 **server ≤7.105.0(core ≤7.33.8)时期停放、至今仍 pending** 的行 —— server 7.106.0(core 7.35.2)起写的行
63
+ 与在 7.106.0+ 上续跑过一次的行都已是新形。**在升到提货 core 7.39.0 的 server 版本之前**(7.110.0 提货 core 7.38.0 仍迁移、不拒),把这些行答完或放弃:
64
+ 列表 = `GET /v1/approvals`(运维队列;`createdAt` 早于本部署升到 server 7.106.0 那一刻的行)与 `GET /v1/assistant/inbox`;
65
+ 决定口 = 工具审批 `POST /v1/approvals/:id/decide`(`:id` = sessionId,体 `{ "decision": "approve" | "deny" }`)、计划审阅
66
+ `POST /v1/assistant/tasks/:id/plan_review`、资源停车续跑 `POST /v1/assistant/tasks/:id/resume`。本版(core 7.37.x)上续跑这类行仍会迁移一次再继续。
67
+ 本服务不读、也不判这几个旧键(那是 core 的私有迁移面),只按停放时间给出区间。
10
68
  ## 7.108.0(车SI,S-919 / S-929 / S-930 / S-920;车SJ,S-931 / S-925 / S-922 / S-921)—— `@sema-agent/core` 7.36.0 不变:按名策略臂读 core 名字全集 · 有序关停 settle 活卡 · 自助抹除口码表补齐 · 目录条目 `headers` · 出站建连失败通告 · side-query `errorMessage` 脱敏
11
69
 
12
70
  - **🔴 安全轴(S-919):本仓收紧方向的按名策略臂(`APPROVAL_DENY` / `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / commandPolicy ask 表 / 治理 shell 标 / durable 问题门 /
@@ -36,6 +94,7 @@
36
94
  - **wire 值(S-922):`POST /v1/side-query` 200 体 `errorMessage` 过 `redactErrorMessage`**(键不变;服务商错误体里的密钥形换 `«redacted»`)。**迁移**:按原样错误体做字符串匹配的调用方改比错误码。
37
95
  - **零动作(S-921 / S-920)**:披露改真与明拒行保鲜钉,无行为变化。
38
96
 
97
+
39
98
  ## 7.107.0(车SE,S-907 / S-890;车SF)—— `@sema-agent/core` 7.36.0:post 钩子收 wire 名 · 双引擎名声明拒 · 工具调用 id 越界折叠(服务商拿回原串)· 两只恒真能力位删
40
99
 
41
100
  - **🔴 hooks:PostToolUse / PostToolUseFailure 的 matcher 与 payload `tool_name` 改为调用派发到的工具的 wire 名**(此前是模型拼写;PreToolUse 本来就是 wire 名)。
@@ -74,7 +133,7 @@
74
133
  也被拒(core 路径拒座把 `cp` 的源也按写目标判,与 CC 同形),升级前照跑 —— 补偿 = 旋钮 `acceptEdits|default` + 审批席,或用读工具取内容。
75
134
  🔴 **宽于升级前的六类**:根外读探针(`probe`)、MCP / A2A 工具标记、transcript 写、`rule_unreadable`(**规则店读不出时照放** —— 安全轴 fail-open 方向)、
76
135
  `artifact_shared_read` —— 升级前这些腿上照问(无人答 ⇒ 拒),缺省旋钮下被释放。**补偿**:`UNATTENDED_PERMISSION_MODE=acceptEdits|default`,或给腿配审批席。
77
- 想更严设 `acceptEdits` / `default` / `dontAsk`,拼错即拒启。**过渡形**:`UNATTENDED_PERMISSION_MODE` 与写保护派生 deny 行是 7.106.0 过渡形;core 7.37.0 到货后(S-906,`AskRequest.askClass?` / `classes?`)退成 `default` + onAsk 按类作答,届时硬删旋钮(拒启点名)。客户端 `bypassPermissions` 不再放掉部署治理层(commandPolicy / 敏感写守卫)的 ask。`config.tool_mount_denied` 的 HTTP 档仍 400。
136
+ 想更严设 `acceptEdits` / `default` / `dontAsk`,拼错即拒启。⚰️ **7.109.0 起旋钮退役(设了即拒启),这些腿改按类作答 —— 见 7.109.0 段。** **过渡形**:`UNATTENDED_PERMISSION_MODE` 与写保护派生 deny 行是 7.106.0 过渡形;core 7.37.0 到货后(S-906,`AskRequest.askClass?` / `classes?`)退成 `default` + onAsk 按类作答,并硬删旋钮(拒启点名)—— 已于 7.109.0 兑现。客户端 `bypassPermissions` 不再放掉部署治理层(commandPolicy / 敏感写守卫)的 ask。`config.tool_mount_denied` 的 HTTP 档仍 400。
78
137
  - **会话授权(`remember:"session"` / `allow_session`)对引擎的问也生效**:同会话下一次同工具不再出卡(Write / 非只读 Bash 在 `default` 下同此;
79
138
  需要审批席 —— `TOOL_APPROVAL_ENABLED` 开,不要活卡再配 `STREAM_ASK_WINDOW_MS=0`)。`APPROVAL_NEVER_AUTO` / 耐久预算耗尽 / **治理层**
80
139
  (`runtime.commandPolicy` 的 ask、`AUTONOMY` 折出的门、敏感写守卫集)的卡改成强制卡(`mandated:true`、`ruleOffersAbsence:"mandated"`,此前 `"shadowed"`),
package/README.md CHANGED
@@ -128,6 +128,9 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
128
128
  7.104.0 the execution-lane start refusals (`LEADER_ENABLED` on the host lane, a multi-tenant posture on a
129
129
  shared lane, the device lane without its durable stores) are judged there, not in `loadConfig()`;
130
130
  `assertExecutionLaneInvariants(config)` is the same judgment without the `config.d/remote-exec.json` override.
131
+ Build-time tools (e.g. a catalog editor) read two allowlists from the package root instead of hand-copying them: `RETIRED_ENVS` (the retired-env
132
+ tombstones) and, since 7.110.0, `CATALOG_ENTRY_HEADER_LANES` / `CATALOG_ENTRY_HEADER_NAMES` (+ type `CatalogEntryHeaderName`) — the closed allowlist of
133
+ model-catalog entry `headers` and the API line that reads each name. Both are frozen, the very objects the server judges with.
131
134
  - **Bundled binaries**: the package ships two `bin` entries — `run-local` (single-machine local
132
135
  runner) and `sema-up` (deployment bootstrap script).
133
136
  - `run-local` boots the same engine in-process, runs one task from the CLI objective, prints the
@@ -156,6 +159,11 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
156
159
  - **A run stopped by a refusal exits 1** (since 7.101.0; it used to exit 0), `--json` included: whatever the
157
160
  model wrote before the refusal still goes to stdout, and without `--json` stderr names the stop
158
161
  (`task stopped (haltedOnUserRejection) …`).
162
+ - **A refusal nobody was asked for is the deployment's policy, not yours** (since 7.110.0): with no TTY (or
163
+ arguments that cannot be displayed) the gate records `policy_refused` / `who.party: "none"`, the model reads
164
+ the engine's policy sentence instead of "the user doesn't want to proceed", and it carries on with the parts
165
+ that need no approval. The run still exits 1 — that call never ran — and stderr names the stop
166
+ (`task incomplete (approval unanswered) …`).
159
167
  - **Full-stack, one command** (DB + object store + registry web + sandbox pool; Docker and k8s
160
168
  paths): [`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy).
161
169
  - **Sandbox package sources**: default = official upstreams (pypi/npmjs/crates.io/…). For
package/README.zh-CN.md CHANGED
@@ -112,6 +112,8 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
112
112
  `composeExecutionLane(config, ctx)` —— HTTP 服务与 `run-local` 都这么做。7.104.0 起执行车道的几条拒启(host 车道上开
113
113
  `LEADER_ENABLED`、多租户姿态落在共享车道、device 车道缺耐久店)判在这一步,不在 `loadConfig()` 里;
114
114
  `assertExecutionLaneInvariants(config)` 是同一份判据,不做 `config.d/remote-exec.json` 覆写。
115
+ 构建期工具(如目录编辑页)从包根取两张表、不手抄:`RETIRED_ENVS`(退役 env 墓碑)与 7.110.0 起的 `CATALOG_ENTRY_HEADER_LANES` /
116
+ `CATALOG_ENTRY_HEADER_NAMES`(+ 型 `CatalogEntryHeaderName`)—— 模型目录条目 `headers` 的闭集白名单及读每个名字的线路。两者都冻结,就是服务端自己判据用的那一只。
115
117
  - **随包二进制**:包内带两个 `bin` —— `run-local`(单机本地 runner)与
116
118
  `sema-up`(部署引导脚本)。
117
119
  - `run-local` 在进程内启同一套引擎,把命令行给的目标跑完一次、打印结果、退出——没有 HTTP 服务,
@@ -133,6 +135,9 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
133
135
  一问 —— 主任务与子代的并发询问依次排队。
134
136
  - **因拒答而停下的 run 退 1**(7.101.0 起,`--json` 也一样;此前退 0):模型在拒答前写的文字照打 stdout,
135
137
  非 `--json` 形 stderr 一行点名停因(`task stopped (haltedOnUserRejection) …`)。
138
+ - **没有人被问的拒记在部署政策名下,不记在你名下**(7.110.0 起):无 TTY(或参数展示不出)时门记录是
139
+ `policy_refused` / `who.party: "none"`,模型读引擎的策略句而不是「用户不想继续」,接着做不需要审批的部分;
140
+ 那次调用没执行 ⇒ run 照样退 1,stderr 停因行 `task incomplete (approval unanswered) …`。
136
141
  - **一键全栈部署**(DB + 对象存储 + registry 网站 + 沙箱池,docker/k8s 双路径):
137
142
  [`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy)。
138
143
  - **沙箱装包源**:缺省 = 官方源(pypi/npmjs/crates.io/…)。中国大陆部署配
package/USAGE.md CHANGED
@@ -332,7 +332,7 @@ MODEL_EXTRA_BODY='{"top_k":40}' # JSON 逃生口(top_k / logit_bias 等;penalt
332
332
  🔴 **调用方读法(core 官方配方,别拿 `terminal.kind` 三元代替)**:`const out = r.result || r.salvagedOutput || "";`
333
333
  —— 先读 `result`,空了才回落。理由:`salvagedOutput` 只在**一张七员闭集**的失败终局上在场(逐字取自 core 的 `SALVAGE_ELIGIBLE_TERMINALS`:`output.degenerate`、`limits.max_tokens_exceeded`、`limits.max_cost_exceeded`、`limits.max_turns_exceeded`、`limits.max_walltime_exceeded`、`env.lifetime_expired`、`usage.window_exhausted`),**闭集之外**的失败终局(模型/供应商错 `provider.error`、`conflict`、未铸码的失败……)它**整键缺席**,而最终助手文本仍在 `result` 里;闭集**之内**它又与 `result` 取自**同一条** final message 文本。两头都指向同一条读法:先 `result`。只读 `salvagedOutput` 会在闭集外的每一条失败路径上把真产出当成"没有输出"丢掉。penalty 是预防、这是兜底。
334
334
 
335
- **可选 — 目录条目的供应商附加请求头(`headers`,S-931,7.108.0)**
335
+ **可选 — 目录条目的供应商附加请求头(`headers`,S-931,7.108.0;两条读腿打通 = S-935,7.109.0)**
336
336
  ```jsonc
337
337
  // config.d/models.json(或配置中心 models 域)的一条条目:
338
338
  { "name": "claude-1m", "id": "<上游模型 id>", "provider": "anthropic", "api": "anthropic-messages",
@@ -349,8 +349,9 @@ MODEL_EXTRA_BODY='{"top_k":40}' # JSON 逃生口(top_k / logit_bias 等;penalt
349
349
  `degrade-route` 重启片走)。
350
350
  - **请求面没有 `betas`**:头是部署 ↔ 供应商的契约(计费 / 配额 / 兼容性由运维承担),不是租户可表态的位 —— `POST /v1/tasks` 带 `betas` ⇒
351
351
  400 `request.body_shape`(键闭集)。
352
- - ⚠️ **7.108.0 的已知边界**:`@sema-agent/settings-schema` 6.3.0 的 `ModelEntry` 还没有 `headers` 键,`config.d` 与配置中心两条读腿会
353
- **剥掉**它(读告警点名未知键)—— server 这一半已就位,schema 补键并提货之后才真正可用(候裁;届时本段删掉这一条)。
352
+ - **读腿**(7.109.0 起,S-935):`config.d/models.json` 与配置中心两条读腿逐字交出 `headers`。`@sema-agent/settings-schema` 6.4.0 的 `ModelEntry.headers`
353
+ 是解析透明键 —— **契约包只做自由文本安全判**(与 `compat` / `extraBody` 同一只闸:**秘密形的值**(形似令牌 / 密钥)在那一步就让条目不可用,拒因字段是
354
+ 整键 `headers`、不回显值),**形(头名白名单 × 线路 × 值形)由本服务判**(`src/model-entry-headers.ts`,上面那几条)。
354
355
 
355
356
  **可选 — OTLP/HTTP 指标导出(core 1.37 可观测)**
356
357
  ```bash
@@ -689,7 +690,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
689
690
  | 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次;中心改了 roles / 档位 / 目录条目后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报) |
690
691
 
691
692
  生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
692
- - **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 点名三张名单键;`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,拒答而停下的 run 退 1。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
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:"none"}}`,模型读到的是引擎的策略句(「部署政策拒了、此处没有交互审批,别重试,先做不需要审批的部分」)加一句拒因,而不是「用户不想继续,停下来等他」—— 不停批,模型接着做别的部分;那只要人批的动作没执行 ⇒ run 照样退 1,非 `--json` 形 stderr 停因行是 `task incomplete (approval unanswered): …`。参数展示不出而不问就拒的那一形(TTY 在场也一样)同此。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
693
694
  - **仅 `/v1/tasks`(同步)+ `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多次尝试非单流,400)。与 `verify` **互斥**(同时给 → 400)。
694
695
  - 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `discuss`(广度交叉)而非级联(深度阶梯)。
695
696
 
@@ -863,31 +864,20 @@ UNATTENDED_APPROVAL_POLICY=park
863
864
  - 代答不到的:`APPROVAL_DENY` / 敏感路径守卫(是拒,不是问);`APPROVAL_NEVER_AUTO` 与耐久预算耗尽的问(「恒问人」,回执 `rememberApplied:false`);
864
865
  委派子代经父链继承到祖先席上的引擎问(引擎盖强制位,席依约不代答)。
865
866
 
866
- **无人值守腿的权限模式(`UNATTENDED_PERMISSION_MODE`,7.106.0 起)**
867
-
868
- ```bash
869
- # 词表 = 引擎的六个权限模式:default | acceptEdits | plan | auto | bypassPermissions | dontAsk。缺省 bypassPermissions。
870
- # 拼错词**启动期响亮拒**(拒因列出六个合法词;不折大小写)
871
- UNATTENDED_PERMISSION_MODE=bypassPermissions
872
- ```
873
- - 管的是本服务**自己铸** spec、**没有调用方**能在请求上说模式、也**没有人**能答问句的三类腿:leader 的 worker、leader 的
874
- 修复腿(集成沙箱修复 / 模块门修复 / 单代理修复环)、`sema` 一次性 CLI(run-local)。`POST /v1/tasks|runs` **不读**本旋钮 ——
875
- 那里模式在请求上(`permissionMode` / `settings.permissions.defaultMode`),不说 = 引擎的 `default`。
876
- - 为什么要有它:引擎(core 7.34.0)起「不说模式」= `default` —— 每一次写、每一条它证不了只读的 shell 命令先问人。上面三类腿
877
- 没有人可问 ⇒ 那一问当场拒,leader worker 会「跑完了,一个字都没写」。缺省 `bypassPermissions` 让写门 / 形状层回到 7.105.0 的样子
878
- (写保护表在这些腿上从问改成拒,不随 bypass 放开;🔴 **根外读探针、MCP / A2A 工具标记、transcript 写、`rule_unreadable`、`artifact_shared_read` 六类会被放掉 —— 7.105.0 上它们照问**;
879
- 过渡形:core 7.37.0 到货后本旋钮退成 `default` + 按类作答的审批席并硬删):
880
- 普通问句不问。本旋钮**不增不减**这些腿上已经接着的运维门:运维写下的问(审批名单、治理层的问、hook 的问)在
881
- `bypassPermissions` 下照问(没人答照旧 park 或拒),拒照拒 —— 它只决定引擎自己的「普通」问句由谁答。
882
- - 🔴 写面不随它打开:这三类腿的写面恒是工作区根内(`roots`,与 7.105.0 同)—— `bypassPermissions` 在引擎模式表里自带的「根外也能写」
883
- **不**随本旋钮进来。
884
- - 想让这些腿更严:设 `acceptEdits`(根内写不问、非只读命令仍问 ⇒ 没人答即拒)、`default`(写与非只读命令都问)或 `dontAsk`
885
- (凡是会问的一律拒)。run-local 有 TTY 时 `default` 会在终端里问你。
886
- - `auto` 需要引擎的 auto 分类器席;leader / run-local 的 Runner 不挂它 ⇒ 引擎按「auto 不可用」折回 `default` 并发一条
887
- `config.permission_mode_unavailable` 通告。中心下发的 `RuntimeCaps.bypassPermissions === false` 同样会把 `bypassPermissions`
888
- 折回 `default`(组织级拒优先)。
889
- - 纯部署级:不看任何客户端表态,也不参与托管姿态判定(这些腿不是请求)。启动日志一行 `unattended_permission_mode`
890
- (`value` + `source: env|default`)。
867
+ **无人值守腿:`default` 模式 + 按类作答的审批席(7.109.0 起;S-934 / S-906)—— 没有旋钮**
868
+
869
+ - 管的是本服务**自己铸** spec、**没有调用方**能在请求上说模式的腿:leader 的 worker、leader 的修复腿(集成沙箱修复 / 模块门修复 /
870
+ 单代理修复环)、`sema` 一次性 CLI(run-local),以及 leader worker 的 park 经 `/decide` 续跑时重挂的同一条腿。`POST /v1/tasks|runs`
871
+ 不走这一条 —— 那里模式在请求上(`permissionMode` / `settings.permissions.defaultMode`),不说 = 引擎的 `default`。
872
+ - 这些腿恒按 `default` 跑,审批席是一只**按类作答**的席:引擎给每一问盖上它属于哪几类(`AskRequest.classes`),机器只答**全部**属于
873
+ `ordinary` 的问 —— 模式写门对普通写的问、形状层对非只读 shell 的问(写与命令照常落地,与 7.105.0 同)。其余每一类都不答:
874
+ 写保护表目标(`write_protection`)、shell 读会话目录外(`probe`)、MCP / A2A 工具标记、transcript 写、规则店读不出、共享制品读、
875
+ plan 的写问(`mode`),以及运维 / hook / 规则 / 组织为人提的问(审批名单、治理层、`APPROVAL_NEVER_AUTO` …)。不答的问:
876
+ - **有席的腿交给席**:run-local 在终端里跑 ⇒ 问你 y/N;没有 TTY ⇒ 当场拒(stderr 一句;门记录记在部署政策名下 `policy_refused` / `who.party:"none"`,7.110.0 起,S-956)。
877
+ - **无席的腿**(leader 各腿):配了 durable 审批块的部署 ⇒ park,等人经 `/decide` 答(与 7.105.0 同形);没配 ⇒ 当场拒(fail-closed)。
878
+ - 答集是常量(只 `ordinary`,core 推荐形),**不是旋钮**。7.106.0–7.108.0 的 `UNATTENDED_PERMISSION_MODE` 已退役:**设了即拒启**
879
+ (`config.env_key_retired`,任何值都拒);从 `.env` 里删掉它。要让机器多答某一类问,先立案,不开旋钮。
880
+ - 写面:`default` 模式不带写面 ⇒ 工作区根内(`roots`,与 7.105.0 同)。 `roots` 只约束 Write / Edit 这类结构化文件工具;shell 的根外写、Read 工具的根外读属 core `ordinary`,这些腿上由机器答(落地结果与 ≤7.108.0 的 bypass 放行同,门记录归属词见 MIGRATION 7.109.0「审计读者」)。
891
881
 
892
882
  **可选 — 写保护名表(S-138;缺席=引擎缺省表在岗)**
893
883
 
@@ -1,3 +1,4 @@
1
+ import type { AskRequest } from "@sema-agent/core";
1
2
  export type AskState = "STREAM_PENDING" | "DECIDED" | "PARKING" | "PARKED" | "DENIED" | "VOID";
2
3
  export type BatchState = "OPEN" | "ROUTING_UNBOUND" | "ROUTING_BOUND" | "ABORTED";
3
4
  /** ask 侧终态集合(无出边的 4 态)——store 层 CAS 谓词与 reap/对账扫描复用这份闭集,不许各自重列。 */
@@ -18,31 +19,14 @@ export declare const MAX_DECISION_NOTE_CHARS = 2048;
18
19
  export declare function canAskTransition(from: AskState, to: AskState): boolean;
19
20
  /** 批转移合法性,同形。 */
20
21
  export declare function canBatchTransition(from: BatchState, to: BatchState): boolean;
21
- /**
22
- * 🔴 身份元组的两条轴(车3 设计稿 §3.2′,2026-08-06 换轴,原轴 `(sourceTaskId, toolCallId, leg:number)`
23
- * 被推翻)—— 这段注释是「为什么是这五个部件」的唯一书面记录:
24
- *
25
- * ── `runId`(新增轴,封死跨 run 复用)────────────────────────────────────────────────────────────────
26
- * core 给 `AskRequest.sourceTaskId` 填的是 **sessionId**(`prepare-task.js` 的 `askSourceIdentity`),
27
- * 不是 wire run id。而原轴给每条新 run 的首腿都重置 `leg=0` ⇒ 同一会话的**下一次全新提交**若撞上同一
28
- * `toolCallId`([ref] 定稿 §3.1 记录了部分网关按**请求编号**而非全局编号铸它)会整组重现,
29
- * `ensureAsk` 的幂等 upsert 于是命中上一 run 已 `DECIDED(approve)` 的旧行并把它回放成 `true` ——
30
- * 静默复用一次早已过去的人类同意。这不是低概率碰撞,是可确定复现的路径(§10 钉 C-1)。
31
- * `runId` = 本 run 的 wire id(durable 腿 = 真 run uuid;adhoc 腿 = 该腿一次性 uuid):每次全新提交必是
32
- * 新值,同一腿内恒定(重试/failover/闭包再入不变)⇒ 幂等仍成立。
33
- *
34
- * ── `legKey`(取代数值 `leg`,一腿一凭据)─────────────────────────────────────────────────────────────
35
- * = `sha256(resume checkpoint token)` 的 hex,**首腿 = 空串**。一次 park→resume 的 token 就是这条腿的
36
- * 天然身份:同 token 重投 = 同一腿(幂等,正确);新 park ⇒ 新 token ⇒ 新腿 ⇒ 新 askId。
37
- * 取摘要而非原始 token 是因为 token 是能力凭据(本仓有 `sealResultForExit` 专门把它从可重放账本里
38
- * 剥掉),而 `askId` 是 wire 可见值 ⇒ **只存/只喂摘要**。用它当轴顺带消掉了原裁需要的 `task_run` 加列
39
- * 与 `markResuming` 返回加宽(后者有第二个调用点 `http/routes/runs.ts:463`,加宽会误杀已成功 resume 的
40
- * running run)。残留边界:同一 run 内**不经 checkpoint token** 的重入形若未来出现会得到同一 legKey ——
41
- * 今天不存在这种腿(run 内每次重入都必须经 checkpoint resolve),故不设兜底。
42
- */
43
- /** ask_id = sha256('ask' ⊕ sourceTaskId ⊕ runId ⊕ toolCallId ⊕ legKey ⊕ parentToolCallId).slice(0,64)。
44
- * 确定性:同参数元组(含重试/failover/闭包再入)永远派生同一个 id——这是 ensureAsk 幂等 upsert 成立的前提。 */
45
- export declare function deriveAskId(sourceTaskId: string, runId: string, toolCallId: string, legKey: string, parentToolCallId?: string): string;
22
+ /** 一只 ask 的请求侧身份素材 = core `AskRequest` 上的三格(型从 core 派生,不手抄;整只 `AskRequest` 可直接交)。 */
23
+ export type AskIdentityRequest = Pick<AskRequest, "toolCallId" | "classes"> & {
24
+ readonly delegation?: Pick<NonNullable<AskRequest["delegation"]>, "parentToolCallId">;
25
+ };
26
+ /** ask_id = sha256('ask' ⊕ sourceTaskId ⊕ runId ⊕ toolCallId ⊕ legKey ⊕ parentToolCallId ⊕ 类集规范形).slice(0,64)。
27
+ * 确定性:同参数元组(含重试/failover/闭包再入)永远派生同一个 id——这是 ensureAsk 幂等 upsert 成立的前提;
28
+ * 同调用不同类集 ⇒ 不同 id(S-944)。 */
29
+ export declare function deriveAskId(sourceTaskId: string, runId: string, legKey: string, request: AskIdentityRequest): string;
46
30
  /** batch_id = sha256('batch' ⊕ sourceTaskId ⊕ runId ⊕ legKey).slice(0,64)。同一 (sourceTaskId, runId,
47
31
  * legKey) 恒派生同一批——批 = 同一决策点的兄弟 ask 集合,故不掺 toolCallId。 */
48
32
  export declare function deriveBatchId(sourceTaskId: string, runId: string, legKey: string): string;
@@ -27,8 +27,11 @@ function deterministicId(prefix, parts) {
27
27
  const joined = [prefix, ...parts.map((p) => p ?? "")].join(ID_SEPARATOR);
28
28
  return createHash("sha256").update(joined, "utf8").digest("hex").slice(0, 64);
29
29
  }
30
- export function deriveAskId(sourceTaskId, runId, toolCallId, legKey, parentToolCallId) {
31
- return deterministicId("ask", [sourceTaskId, runId, toolCallId, legKey, parentToolCallId]);
30
+ function askClassSetKey(classes) {
31
+ return classes === undefined ? "" : [...new Set(classes)].sort().join(ID_SEPARATOR);
32
+ }
33
+ export function deriveAskId(sourceTaskId, runId, legKey, request) {
34
+ return deterministicId("ask", [sourceTaskId, runId, request.toolCallId, legKey, request.delegation?.parentToolCallId, askClassSetKey(request.classes)]);
32
35
  }
33
36
  export function deriveBatchId(sourceTaskId, runId, legKey) {
34
37
  return deterministicId("batch", [sourceTaskId, runId, legKey]);
@@ -21,7 +21,7 @@
21
21
  * 没有方法、没有捕获的行为。
22
22
  */
23
23
  import { z } from "zod";
24
- import { READ_ROOT_CANDIDATE_DIR_MAX, type AskEvidenceAbsence, type AskRequest as CoreAskRequest, type PendingAction as CorePendingAction, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
24
+ import { READ_ROOT_CANDIDATE_DIR_MAX, type AskClass, type AskEvidenceAbsence, type AskRequest as CoreAskRequest, type PendingAction as CorePendingAction, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
25
25
  import type { AskRow } from "./plugins/approval-ask-store-sql.js";
26
26
  /** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把
27
27
  * 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本,
@@ -429,6 +429,26 @@ export declare function readClosedWordSeat<S extends ClosedWordSeat>(carrier: ob
429
429
  type ClosedWordSeat = "mandate" | "origin";
430
430
  /** 一座的值型 = core 寄存行上那一座的型(去掉可选位的 `undefined`)。 */
431
431
  type ClosedWordOf<S extends ClosedWordSeat> = NonNullable<ToolApprovalPark[S]>;
432
+ /**
433
+ * 🔴 **S-934 ②(core 7.37.0 #1214)—— ask 类对的唯一窄读**:`askClass`(一问的类 = `classes` 的首员)与 `classes`(这一问属于的全部类,core
434
+ * `ASK_CLASSES` 序)。三个载体上同名:`AskRequest`(活卡帧 / 呈卡帧的卡读它)、寄存行 `PendingAction.tool_approval`(运维队列行经
435
+ * {@link parkRowFacts} 读它);`CheckpointSummary` 不回显(core 原句「read the row」)⇒ inbox 行不带。
436
+ *
437
+ * 判据(display,不是裁决输入 —— 裁决在 core 的仲裁点,本仓无人值守腿的按类作答读的是 core 交来的 `AskRequest.classes` 本身,不经本函数):
438
+ * · **两键都缺席 ⇒ 缺席**(不计数):老引擎(core 7.37.0 之前)、老行(续跑不补,KL-1214-b)、不盖 `origin` 的站(KL-1214-a)。缺席从不编默认类;
439
+ * · **在场 ⇒ 读法就是 core 的屏** `screenAskClassMembers`(S-955 ⑥,core 7.38.0 #1249 导出;引擎在写停车行前用的同一只):`askClass` 在集、`classes`
440
+ * 非空数组且每员在集、无重复、按 `ASK_CLASSES` 规范序取首员 === `askClass`;屏收 ⇒ 投屏给的规范序副本。屏拒(半对 / 集外词 / 首员不符 /
441
+ * 重复员)⇒ **两键都不投**(卡上少一行类 ≪ 卡上出现生词或半只对)+ 计一次 `server.approval-card.closed-word-out-of-set`(S-249 族,detail 前缀
442
+ * `askClass=` / `classes=` 判别是哪一格读不出 —— 前缀只是诊断,判据只有屏一条)。
443
+ * · 本仓**零自写对判**(修前这里与 {@link ApprovalCardSchema} 的 superRefine 各写一份「`classes[0] === askClass`」,按输入首员比、收重复员,与引擎的
444
+ * 屏是两条规则)。投出去的 `classes` 是新数组(帧不与载体共享可变引用;屏给的是冻结数组)。
445
+ */
446
+ export declare function readAskClassPair(carrier: object): AskClassPair | undefined;
447
+ /** 类对的投影形(帧 / 卡 / 队列行同一形):两键同在同缺。值型取 core 寄存行上的座(core 改型 ⇒ 下面的围栏编译红)。 */
448
+ export interface AskClassPair {
449
+ askClass: NonNullable<ToolApprovalPark["askClass"]>;
450
+ classes: Array<NonNullable<ToolApprovalPark["classes"]>[number]>;
451
+ }
432
452
  /**
433
453
  * 🔴 S-700 P1(client-core CC-180 / cli L-652 的根子;板 [ref] 订正 → [ref] 立案)—— **park 行事实**:
434
454
  * 运维队列行(`GET /v1/approvals` + `/stream`)顶层展开的那几位。型**从 core 寄存行派生,不手抄**(S-249 同一条
@@ -446,8 +466,10 @@ type ClosedWordOf<S extends ClosedWordSeat> = NonNullable<ToolApprovalPark[S]>;
446
466
  * S-755(cli L-695 / [ref],client-core [ref])第五位 `origin`:谁提的这一问(core `ASK_ORIGINS` 闭集一词,经 {@link readClosedWordSeat};
447
467
  * 与活卡帧 `origin` / inbox 行 `origin` 同名同义同值)。此前只有活卡帧与 inbox 行带它 ⇒ 耐久重开 / 重连时壳从队列行读不到出身词,
448
468
  * bypass 姿态下按 `ask_rule` / `hook` 照问的那条壳规则无从落地。缺席 = 老行(core 7.5.0 之前 park 的)/ 集外词(计 S-249 tag)。
469
+ * S-934 ②(core 7.37.0 #1214)第六 / 七位 `askClass` / `classes`:这一问的类对(经 {@link readAskClassPair};两位同在同缺,与活卡帧 / 呈卡帧的卡
470
+ * 同名同义同值)—— durable 审批席按类答的依据。缺席 = 老行(core 7.37.0 之前 park 的,续跑不补)/ 读不出(计 S-249 tag)。
449
471
  */
450
- export type ParkRowFacts = Pick<ToolApprovalPark, "requiresRealApproval" | "ruleOffersAbsence" | "mandated" | "mandate" | "origin">;
472
+ export type ParkRowFacts = Pick<ToolApprovalPark, "requiresRealApproval" | "ruleOffersAbsence" | "mandated" | "mandate" | "origin" | "askClass" | "classes">;
451
473
  /**
452
474
  * S-700 —— park 行事实的**唯一投影**(`pendingAction` 是寄存行上 core 铸的那一只,读自持久层 ⇒ `unknown`)。
453
475
  *
@@ -629,6 +651,8 @@ export declare const ApprovalCardSchema: z.ZodObject<{
629
651
  }, z.core.$strict>>>;
630
652
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
631
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">>>;
632
656
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
633
657
  sourceTaskId: z.ZodOptional<z.ZodString>;
634
658
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -742,6 +766,8 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
742
766
  }, z.core.$strict>>>;
743
767
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
744
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">>>;
745
771
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
746
772
  sourceTaskId: z.ZodOptional<z.ZodString>;
747
773
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -787,6 +813,10 @@ export interface ApprovalCardSource {
787
813
  * `ApprovalCardSchema.ruleOffers`)。与 wire 帧**同一份素材** ⇒ live 帧 / `card_json` / 重放帧
788
814
  * 三面同源。 */
789
815
  ruleOffers?: readonly RuleOffer[];
816
+ /** S-934 ②:类对 —— 与 wire 帧**同一份素材**(发帧点经 `readAskClassPair` 窄读定;`ToolApprovalFrame` 结构上满足本接口)⇒ live 帧 /
817
+ * `approval_request` / `card_json` / 重放帧四面同源。两键同在同缺。 */
818
+ askClass?: AskClass;
819
+ classes?: readonly AskClass[];
790
820
  fromSubagent?: true;
791
821
  sourceTaskId?: string;
792
822
  /** 已 redactSecrets。 */
@@ -838,7 +868,7 @@ export interface ApprovalRequestFrame {
838
868
  /** = SSE 具名事件名(与 `tool_approval` 同约定)。 */
839
869
  type: "approval_request";
840
870
  schemaVersion: 1;
841
- /** `deriveAskId` 派生(持久层主键;五元组见 approval-ask-machine.ts)。 */
871
+ /** `deriveAskId` 派生(持久层主键;六元身份元组 —— S-944 / 7.109.0 起含这一问的类集 —— 见 approval-ask-machine.ts)。 */
842
872
  askId: string;
843
873
  /** wire run id(= 协调器 ctx 的 taskId)。 */
844
874
  taskId: string;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, isAskOrigin, isPersistedRuleMandate } from "@sema-agent/core";
2
+ import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, isAskClass, isAskOrigin, isPersistedRuleMandate, screenAskClassMembers } from "@sema-agent/core";
3
3
  import { redactSecrets } from "./trace/redact.js";
4
4
  import { recordFailOpen } from "./observability/fail-open.js";
5
5
  import { PERSISTED_RULE_MATCHES, UNCOVERED_SEGMENT_REASONS } from "./permission-rule-vocab.js";
@@ -284,18 +284,31 @@ const CLOSED_WORD_SEAT_GUARDS = {
284
284
  mandate: isPersistedRuleMandate,
285
285
  origin: isAskOrigin,
286
286
  };
287
+ export function readAskClassPair(carrier) {
288
+ const askClass = "askClass" in carrier ? carrier.askClass : undefined;
289
+ const classes = "classes" in carrier ? carrier.classes : undefined;
290
+ if (askClass === undefined && classes === undefined)
291
+ return undefined;
292
+ const screened = screenAskClassMembers(askClass, classes);
293
+ if (screened !== undefined)
294
+ return { askClass: screened.askClass, classes: [...screened.classes] };
295
+ recordFailOpen("server.approval-card.closed-word-out-of-set", isAskClass(askClass) ? `classes=${outOfSetDetail(classes)}` : `askClass=${outOfSetDetail(askClass)}`);
296
+ return undefined;
297
+ }
287
298
  export function parkRowFacts(pendingAction) {
288
299
  if (typeof pendingAction !== "object" || pendingAction === null || !("kind" in pendingAction) || pendingAction.kind !== "tool_approval")
289
300
  return {};
290
301
  const ruleOffersAbsence = readRuleOffersAbsence(pendingAction);
291
302
  const mandate = readClosedWordSeat(pendingAction, "mandate");
292
303
  const origin = readClosedWordSeat(pendingAction, "origin");
304
+ const askClassPair = readAskClassPair(pendingAction);
293
305
  return {
294
306
  ...(readParkRowTrueSeat(pendingAction, "requiresRealApproval") ? { requiresRealApproval: true } : {}),
295
307
  ...(readParkRowTrueSeat(pendingAction, "mandated") ? { mandated: true } : {}),
296
308
  ...(mandate !== undefined ? { mandate } : {}),
297
309
  ...(origin !== undefined ? { origin } : {}),
298
310
  ...(ruleOffersAbsence !== undefined ? { ruleOffersAbsence } : {}),
311
+ ...(askClassPair ?? {}),
299
312
  };
300
313
  }
301
314
  const DenialLimitFallbackSchema = z
@@ -337,6 +350,8 @@ export const ApprovalCardSchema = z
337
350
  classifierUnavailable: ClassifierUnavailableSchema.optional(),
338
351
  probeCause: ProbeCauseSchema.optional(),
339
352
  ruleEvidence: RuleEvidenceSchema.optional(),
353
+ askClass: z.custom((v) => isAskClass(v)).optional(),
354
+ classes: z.array(z.custom((v) => isAskClass(v))).min(1).optional(),
340
355
  fromSubagent: z.literal(true).optional(),
341
356
  sourceTaskId: z.string().max(MAX_IDENT).optional(),
342
357
  sourceAgentName: z.string().max(MAX_AGENT_NAME).optional(),
@@ -348,7 +363,14 @@ export const ApprovalCardSchema = z
348
363
  })
349
364
  .optional(),
350
365
  })
351
- .strict();
366
+ .strict()
367
+ .superRefine((card, ctx) => {
368
+ if (card.askClass === undefined && card.classes === undefined)
369
+ return;
370
+ if (screenAskClassMembers(card.askClass, card.classes) === undefined) {
371
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["classes"], message: "askClass / classes must be a pair the engine's own screen (screenAskClassMembers) admits" });
372
+ }
373
+ });
352
374
  export const ApprovalCardEnvelopeSchema = z
353
375
  .object({
354
376
  schemaVersion: z.literal(1),
@@ -377,6 +399,7 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
377
399
  const ruleEvidence = readRuleEvidence(req);
378
400
  const ruleOffersAbsence = readRuleOffersAbsence(req);
379
401
  const denialLimitFallback = readDenialLimitFallback(req);
402
+ const askClassPair = readAskClassPair(source);
380
403
  const toolCallId = clip(source.toolCallId, MAX_IDENT);
381
404
  const sourceTaskId = clip(source.sourceTaskId, MAX_IDENT);
382
405
  const sourceAgentName = clip(source.sourceAgentName, MAX_AGENT_NAME);
@@ -399,6 +422,7 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
399
422
  ...(source.ruleOffers !== undefined && source.ruleOffers.length > 0 ? { ruleOffers: source.ruleOffers.map(copyRuleOffer) } : {}),
400
423
  ...(ruleOffersAbsence !== undefined ? { ruleOffersAbsence } : {}),
401
424
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
425
+ ...(askClassPair !== undefined ? { askClass: askClassPair.askClass, classes: askClassPair.classes } : {}),
402
426
  ...(source.fromSubagent === true ? { fromSubagent: true } : {}),
403
427
  ...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
404
428
  ...(sourceAgentName !== undefined ? { sourceAgentName } : {}),
@@ -35,7 +35,6 @@
35
35
  * `infraFailed` (EXCLUDED) — NEVER a fabricated uniform delivered=false loss for the whole TEAM column.
36
36
  */
37
37
  import { type TaskSpec } from "@sema-agent/core";
38
- import { type UnattendedLeg } from "../../unattended-permission-mode.js";
39
38
  import type { ProfileDeps, SoloImplSpec, SupImplSpec } from "./arms.js";
40
39
  import type { TrapSpec } from "./tasks.js";
41
40
  /** Live runtime config — the SAME base model + budget every arm runs on (the §3.2 fairness root). */
@@ -96,11 +95,11 @@ export declare function benchBaseSpec(rt: LiveRuntimeConfig, cellId: string): (o
96
95
  /**
97
96
  * Project a `SoloImplSpec` (budget already stamped by the harness, all keys in `limits`) onto the bench base TaskSpec.
98
97
  * 🔴 7.106.0 合并复审 X3:SOLO 是**无人值守腿**(verify→fix 跑在一只不接 `onAsk` 的 Runner 上,没有审批席)⇒ 经 `withUnattendedLeg` 接
99
- * 与 TEAM 的 leader worker 同一条腿(模式 = `UNATTENDED_PERMISSION_MODE`、写面 roots、写保护拒层)。修前裸 spec ⇒ core 7.34.0「缺席 =
100
- * default」让写与非只读命令 headless 拒、终态照旧 completed —— SOLO 与 TEAM 的比较混进了权限差异。SUP 不走这里:它有耐久审批席
101
- * (checkpoint suspend),它的问要人答,bypass 会把 SUP 的门放掉。
98
+ * 与 TEAM 的 leader worker 同一只合并口(S-934 ①:`default` + 按类作答的 onAsk,无席)。修前裸 spec ⇒ core 7.34.0「缺席 =
99
+ * default」且无席 ⇒ 写与非只读命令 headless 拒、终态照旧 completed —— SOLO 与 TEAM 的比较混进了权限差异。SUP 不走这里:它有耐久审批席
100
+ * (checkpoint suspend),它的问要人答。
102
101
  */
103
- export declare function toTaskSpec(base: (objective: string) => TaskSpec, impl: SoloImplSpec, leg: UnattendedLeg): TaskSpec;
102
+ export declare function toTaskSpec(base: (objective: string) => TaskSpec, impl: SoloImplSpec): TaskSpec;
104
103
  /** Project a `SupImplSpec` onto the bench base TaskSpec PLUS the durable wiring (checkpointStore + ask policy). */
105
104
  export declare function toSupTaskSpec(base: (objective: string) => TaskSpec, impl: SupImplSpec, trap: TrapSpec): TaskSpec;
106
105
  //# sourceMappingURL=live-deps.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { outwardPlane } from "../../terminal.js";
2
2
  import { Runner, runWithVerification, resumeWithVerification, runRepairLoop, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, } from "@sema-agent/core";
3
3
  import { loadConfig } from "../../config.js";
4
- import { createUnattendedLeg, unattendedPermissionMode, withUnattendedLeg } from "../../unattended-permission-mode.js";
4
+ import { withUnattendedLeg } from "../../unattended-permission-mode.js";
5
5
  import { createBrain } from "../../brain.js";
6
6
  import { buildPricing } from "../../budget.js";
7
7
  import { RemoteContainerExecutionEnv } from "../../plugins/remote-env-e2b.js";
@@ -58,7 +58,7 @@ function buildBrainIngredients(rt) {
58
58
  if (!p || ((p.inputPer1M ?? 0) === 0 && (p.outputPer1M ?? 0) === 0)) {
59
59
  throw new Error(`s1 live: zero pricing for ${rt.modelId} — C1 cost axis would be a fabricated $0 (set MODEL_COST_INPUT/OUTPUT)`);
60
60
  }
61
- return { brain, models: config.models, roles: config.roles, pricing, unattendedPermissionMode: unattendedPermissionMode(config) };
61
+ return { brain, models: config.models, roles: config.roles, pricing };
62
62
  }
63
63
  const sh = (env) => async (cmd) => {
64
64
  const r = await env.exec(`bash -lc ${JSON.stringify(cmd)}`);
@@ -168,9 +168,8 @@ function graderTransport(grader) {
168
168
  };
169
169
  }
170
170
  export function buildLiveDeps(rt, trap, seed, cellId) {
171
- const { brain, models, roles, pricing, unattendedPermissionMode: leaderMode } = buildBrainIngredients(rt);
171
+ const { brain, models, roles, pricing } = buildBrainIngredients(rt);
172
172
  const baseSpec = benchBaseSpec(rt, cellId);
173
- const soloLeg = createUnattendedLeg(leaderMode, {});
174
173
  const state = {};
175
174
  const ensureWorker = async () => {
176
175
  if (!state.worker) {
@@ -208,7 +207,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
208
207
  const deps = {
209
208
  runWithVerification: async (implSpec) => {
210
209
  const { runner } = await ensureWorker();
211
- return runWithVerification(runner, toTaskSpec(baseSpec, implSpec, soloLeg), { maxRounds: rt.maxRounds ?? 2 });
210
+ return runWithVerification(runner, toTaskSpec(baseSpec, implSpec), { maxRounds: rt.maxRounds ?? 2 });
212
211
  },
213
212
  runWithVerificationSup: async (implSpec) => {
214
213
  const { runner } = await ensureWorker();
@@ -284,7 +283,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
284
283
  rmSync(tmp, { recursive: true, force: true });
285
284
  }
286
285
  };
287
- return runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, trap, cellId, captureIntegratedTreeToBundle);
286
+ return runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId, captureIntegratedTreeToBundle);
288
287
  },
289
288
  runOracle: () => runOracle(false),
290
289
  };
@@ -303,8 +302,8 @@ export function benchBaseSpec(rt, cellId) {
303
302
  function projectImplBudget(base, impl) {
304
303
  return { ...base(impl.objective), limits: { ...impl.limits } };
305
304
  }
306
- export function toTaskSpec(base, impl, leg) {
307
- return withUnattendedLeg(projectImplBudget(base, impl), leg);
305
+ export function toTaskSpec(base, impl) {
306
+ return withUnattendedLeg(projectImplBudget(base, impl));
308
307
  }
309
308
  export function toSupTaskSpec(base, impl, trap) {
310
309
  const requireApproval = trap.gatedTools ?? ["Bash"];
@@ -317,7 +316,7 @@ export function toSupTaskSpec(base, impl, trap) {
317
316
  onQuestion: QUESTION_AWAITS_RESUME,
318
317
  };
319
318
  }
320
- async function runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, trap, cellId, importIntegratedTreeToWorker) {
319
+ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId, importIntegratedTreeToWorker) {
321
320
  const { mkdtempSync, rmSync, writeFileSync, mkdirSync } = await import("node:fs");
322
321
  const { execFileSync, spawn } = await import("node:child_process");
323
322
  const { tmpdir } = await import("node:os");
@@ -370,7 +369,7 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, t
370
369
  process.env.LEADER_ENABLED = "true";
371
370
  process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
372
371
  const run = createLeaderRunner({
373
- brain, models, roles, pricing, unattendedPermissionMode: leaderMode, e2bApiKey: rt.e2bApiKey, workspace: W,
372
+ brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
374
373
  ...(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) : "") } } : {}),
375
374
  });
376
375
  const teamSeedWrite = (trap.seedFiles ?? [])
@@ -2,7 +2,6 @@ import { createLeaderEndpoint } from "../leader/endpoint.js";
2
2
  import { createLeaderRunner } from "../leader/wire.js";
3
3
  import { buildDeploymentPostureSeats } from "./runner-deps.js";
4
4
  import { hasApprovalListIntent } from "../approval.js";
5
- import { unattendedPermissionMode } from "../unattended-permission-mode.js";
6
5
  export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
7
6
  if (!(leaderEnabled && leaderProvider === "k8s" && !hasS3))
8
7
  return null;
@@ -59,7 +58,6 @@ export function createLeaderFace(ctx) {
59
58
  : {}),
60
59
  ...(ctx.promptSource ? { promptSource: ctx.promptSource } : {}),
61
60
  ...(ctx.onError ? { onError: ctx.onError } : {}),
62
- unattendedPermissionMode: unattendedPermissionMode(config),
63
61
  fanoutEnabled: config.leaderFanoutEnabled,
64
62
  ...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
65
63
  ? { routerModel: (process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID) }
@@ -16,6 +16,7 @@ import { Runner, type RunnerDeps, type WorkflowRunStore } from "@sema-agent/core
16
16
  import type { ServiceConfig } from "../config.js";
17
17
  import { CostQuota } from "../observability/cost-quota.js";
18
18
  import type { RateLimiter } from "../observability/rate-limit.js";
19
+ import type { UserInputLedger } from "../user-input-ledger.js";
19
20
  import type { Logger } from "../observability/logger.js";
20
21
  import type { Metrics } from "../observability/metrics.js";
21
22
  import type { WorkflowNotifyGate, WorkflowNotifyJournalStore } from "../orchestration/workflow-notify-journal.js";
@@ -98,6 +99,8 @@ export interface ReapersCtx {
98
99
  * (`boot/permission-rules-audit.ts` 的 `DormantRuleCounter` 是同一条判据的先例)。
99
100
  * 生产传的仍是整只束(结构上满足这个窄口)。 */
100
101
  permissionRuleStores: PermissionRuleRetentionSweeper | undefined;
102
+ /** S-928 用户输入受理账本(stage-07 装配;缺席 = 无 backend)。每拍在 reapStale **之后**收敛一次(同一条腿串行,理由见 tick 里那一行)。 */
103
+ userInputLedger: Pick<UserInputLedger, "converge"> | undefined;
101
104
  /** S-280 直连门 nonce 的消费记录店 —— 本 tick 只用它的**到期清扫**一手。行至多活 ~150 秒(= 证明还能被
102
105
  * 受理的最后一刻),所以存量是「到达率 × TTL」而不是历史总量,清扫无需分批。无 backend 的 env-only
103
106
  * 部署 ⇒ undefined ⇒ 本腿零调用(那种部署也没有 durable 审批门)。 */