@sema-agent/server 7.107.0 → 7.109.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 +72 -1
- package/USAGE.md +51 -25
- package/dist/approval-ask-audit-store.d.ts +62 -4
- package/dist/approval-ask-audit-store.js +67 -11
- package/dist/approval-ask-machine.d.ts +9 -25
- package/dist/approval-ask-machine.js +5 -2
- package/dist/approval-card.d.ts +32 -3
- package/dist/approval-card.js +29 -2
- package/dist/approval.js +16 -13
- package/dist/bench/s1/live-deps.d.ts +4 -5
- package/dist/bench/s1/live-deps.js +9 -10
- package/dist/boot/coordinators.d.ts +4 -0
- package/dist/boot/coordinators.js +3 -2
- package/dist/boot/leader.js +0 -2
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +3 -8
- package/dist/boot/shutdown.d.ts +8 -0
- package/dist/boot/shutdown.js +31 -8
- package/dist/boot/stage-06-runners.d.ts +1 -0
- package/dist/boot/stage-06-runners.js +6 -3
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-10-http-server.d.ts +1 -0
- package/dist/boot/stage-10-http-server.js +3 -1
- package/dist/brain.js +2 -1
- package/dist/capabilities/scenarios.d.ts +3 -2
- package/dist/config-catalog.js +1 -3
- package/dist/config-center/apply-effective.js +2 -0
- package/dist/config-center/types.d.ts +7 -0
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +2 -9
- package/dist/config.js +2 -5
- package/dist/deployment-governance.js +2 -1
- package/dist/fleet-client.d.ts +10 -2
- package/dist/fleet-client.js +14 -8
- package/dist/http/admission.js +8 -2
- package/dist/http/memory-compliance-failure-reply.d.ts +41 -0
- package/dist/http/memory-compliance-failure-reply.js +73 -0
- package/dist/http/memory-store-refusal-reply.d.ts +2 -2
- package/dist/http/resume-legs.js +3 -2
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/memory-compliance.js +2 -34
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/sessions.js +3 -11
- package/dist/http/routes/side-query.js +7 -2
- package/dist/http/routes/tasks.js +2 -1
- package/dist/http/server.d.ts +2 -2
- package/dist/http/wire-types.d.ts +8 -0
- package/dist/leader/diffup.d.ts +53 -0
- package/dist/leader/diffup.js +95 -1
- package/dist/leader/leader.d.ts +0 -6
- package/dist/leader/leader.js +1 -1
- package/dist/leader/repair-wire.js +1 -1
- package/dist/leader/wire.d.ts +3 -10
- package/dist/leader/wire.js +39 -13
- package/dist/memory-store-refusal.d.ts +2 -1
- package/dist/model-entry-headers.d.ts +41 -0
- package/dist/model-entry-headers.js +61 -0
- package/dist/model-entry-refusal.d.ts +6 -0
- package/dist/model-entry-refusal.js +4 -0
- package/dist/model-reach-scope.d.ts +55 -0
- package/dist/model-reach-scope.js +16 -0
- package/dist/model-unreachable-notice.d.ts +38 -0
- package/dist/model-unreachable-notice.js +72 -0
- package/dist/observability/fail-open.d.ts +5 -5
- package/dist/observability/fail-open.js +5 -5
- package/dist/observability/run-terminal.d.ts +2 -1
- package/dist/outbound-dispatcher.d.ts +69 -0
- package/dist/outbound-dispatcher.js +123 -3
- package/dist/plugins/approval-ask-store-file.d.ts +2 -1
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
- package/dist/plugins/approval-ask-store-memory.js +7 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +18 -0
- package/dist/plugins/approval-ask-store-sql.js +7 -0
- package/dist/plugins/park-commit-watch.d.ts +48 -0
- package/dist/plugins/park-commit-watch.js +87 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/router/route-orchestration.js +7 -2
- package/dist/run-local.js +2 -3
- package/dist/runs.js +3 -2
- package/dist/runtime-governance.d.ts +3 -3
- package/dist/runtime-governance.js +3 -2
- package/dist/task-settings.js +4 -2
- package/dist/tool-approval.d.ts +99 -15
- package/dist/tool-approval.js +75 -10
- package/dist/tool-rule-names.d.ts +25 -0
- package/dist/tool-rule-names.js +5 -0
- package/dist/trace/core-keyset-guard.d.ts +14 -2
- package/dist/trace/engine-notice-wire.d.ts +3 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/project.d.ts +3 -1
- package/dist/trace/project.js +2 -3
- package/dist/trace/self-minted-wire.d.ts +71 -0
- package/dist/trace/self-minted-wire.js +28 -0
- package/dist/unattended-permission-mode.d.ts +54 -93
- package/dist/unattended-permission-mode.js +8 -61
- package/package.json +3 -3
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,77 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 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 · 策略决定对象的普通对象律 · 迁移窗口
|
|
11
|
+
|
|
12
|
+
- **🔴 env:`UNATTENDED_PERMISSION_MODE` 设了即拒启**(任何值,含 7.106.0 的缺省 `bypassPermissions`;拒因带 `config.env_key_retired` 并点名键)。
|
|
13
|
+
**迁移**:从 `.env` / 编排清单里删掉它。leader worker / leader 修复腿 / `sema` 一次性 CLI(run-local)及 leader worker park 的 `/decide` 续跑,
|
|
14
|
+
自本版起恒按 `default` 跑,审批席**按类作答**:机器只答引擎盖了 `ordinary` 类的问(普通写、非只读 shell 的形状层问 —— 照常落地)。
|
|
15
|
+
**写面与 `ordinary` 的边界**:`roots` 只约束 Write / Edit 这类结构化文件工具;shell 的根外写与 Read 工具的根外读属 core `ordinary`,无人值守腿上由机器答(与 ≤7.108.0 的 bypass 放行同一落地结果,记法不同)。
|
|
16
|
+
**审计读者**:机器按类放行在 core 门记录里写成 `settlement.kind:"human_allowed"` / `who.party:"person"`(core 没有「机器放行」归属词,S-953 转 core)—— 无人值守腿上读作机器放行;无席且无 durable 块的 leader 腿被拒的问(run-local 有本地席,不走这一词)结算词 `no_approver` → `approver_unavailable`,按词告警的运维改比码。
|
|
17
|
+
- **🔴 行为面(安全轴收紧)**:这些腿上此前被缺省 `bypassPermissions` 放掉的六类问 —— shell 读会话目录外(`probe`)、MCP / A2A 工具标记、
|
|
18
|
+
transcript 写、规则店读不出(`rule_unreadable`)、共享制品读(`artifact_shared_read`)—— 改为**不答**:run-local 有终端时问你 y/N、无终端当场拒;
|
|
19
|
+
leader 各腿配了 durable 审批块 ⇒ park 到 `/v1/approvals/:id/decide`,没配 ⇒ 当场拒。写保护表目标从「策略层路径拒行拒(人在场也答不了)」改成一问
|
|
20
|
+
(有席问人 / 无席 park 或拒);`cp <写保护目标> <普通目标>` 这类只读写保护源的命令不再被过拒。**迁移**:靠无人值守 worker 读工作区外文件 /
|
|
21
|
+
调带标记 MCP 工具的流程,给 leader 配 durable 审批块让人答,或把要读的东西放进工作区。
|
|
22
|
+
- **wire(additive)**:live `tool_approval` 帧、`approval_request` 呈卡帧的 `card`(= `card_json` = 断连重放帧)、`GET /v1/approvals` 队列行与 `/stream`
|
|
23
|
+
的 `pending` 帧新增两只可选键 `askClass?` / `classes?`(core 盖的类对,两键同在同缺;缺席 = 老引擎 / 老行 / 不盖类的站,**不是**「普通」)。
|
|
24
|
+
零动作;按类渲卡 / 按类答的壳读**整只** `classes`。回滚窗:`card_json` 是 `.strict()` 形 ⇒ 回滚到 ≤7.108.0 的副本读不动带这两键的新卡行
|
|
25
|
+
(重放跳过 + 一次 warn,幂等重入走 park —— 与既往 additive 卡键同一代价),整队滚。
|
|
26
|
+
- **🔴 行为面(core 7.37.0,策略 / hook 作者)**:引擎对策略 / hook / 祖先返回的决定对象**只读一次**,只收**普通对象**:`action` 必须是
|
|
27
|
+
`allow` / `deny` / `ask` 之一;契约成员必须是对象**自有可枚举**成员(不放原型上、不靠 getter、不靠 Proxy);改写(`updatedInput` 等)只放可克隆纯数据。
|
|
28
|
+
不合 ⇒ 接手即拒(工具不执行)。本服务自身的策略 / hook 全是字面对象或展开(亲核),部署自写的 `ToolPolicy`(嵌入方)按此自查。
|
|
29
|
+
**委派**:祖先层一次「是」只覆盖它答过的类 —— 子代门查到更严的类(写保护、根外读)会再出一张卡;带出网 / 不可逆标记的工具在委派下首卡即
|
|
30
|
+
`operator_forced`(`bypassPermissions` 不放它)。持久 ask 店车道(流内审批协议上场的部署)上第二张卡同样出卡(S-944,见下一条)。
|
|
31
|
+
- **🔴 安全轴(S-944):持久 ask 店车道上的 askId 身份含这一问的类集**。委派下同一调用的第二张卡(类集不同)自本版是**新问**:新行、出自己的卡、
|
|
32
|
+
等人答这一问 —— 此前它与首卡同 askId,被首卡的「是」回放。同一调用同类集的重试照旧幂等。**行为面(收紧)**:会话授权(`allow_session` /
|
|
33
|
+
耐久 `remember:"session"`)不再代答「这次调用在这条腿上已出过卡」的后续问(同一调用的另一类集问)—— 出卡,人答。**wire 值变更**:不透明 `askId`(呈卡帧、
|
|
34
|
+
`POST /v1/tasks/:taskId/asks/:askId/decision`)对同一调用的取值与 ≤7.108.0 不同(形不变,64 位 hex);持久 `approval_ask.ask_id` 主键的派生同变,
|
|
35
|
+
**零 DDL、零迁移、零兼容读**。**升级影响(亲核)**:升级前写的行按旧派生,行上存的 askId 原样可答 / 可重放 / 可对账(本服务没有任何一处从行或
|
|
36
|
+
停车行反算 askId);跨 park 续跑的腿本来就换 `legKey` ⇒ 新派生,与旧行无交集 ⇒ 无影响。**同一条腿跨版本重入**(同 run、同 `legKey`:重试 /
|
|
37
|
+
闭包再入 / 跨副本 failover / 同一 resume token 重投)同一调用时,新版派生的新 id 不命中旧行 ⇒ 这一问出卡再问(人再答一次,fail-closed);
|
|
38
|
+
会话授权(live `allow_session` / 耐久 `remember:"session"`)**不会**替它代答 —— 一揽子放行的复核按**调用**读(这次调用在这条腿上有任何行,
|
|
39
|
+
含旧派生写的人拒 / 挂着的卡 ⇒ 不短路),与 ask 身份无关。**迁移**:自建 `StoreBackend`(不走 `createStoreBackend`)的嵌入方须实现 `ApprovalAskStore.listByCall`(类型面硬破坏,tsc 红即通知);部署零 DDL、零数据迁移。**滚动升级**:混版窗内旧版副本接管本版写过的腿读不到新派生行(反向),
|
|
40
|
+
与 7.107.0 段「持久面反向不兼容边界」同一处置:同一存储上不要让旧版实例接管本版在跑的腿。
|
|
41
|
+
- **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 超界残余窗)。
|
|
42
|
+
- **目录条目 `headers` 两条读腿打通(S-935,settings-schema 6.4.0)**:`config.d/models.json` 与配置中心写的 `models[].headers` 自本版起到达目录(7.108.0 被读腿剥掉)。
|
|
43
|
+
**行为面**:秘密形的头值(形似令牌 / 密钥)在 schema 自由形安全闸即让条目不可用(拒因字段整键 `headers`)。**迁移**:零动作。
|
|
44
|
+
- **🔴 迁移窗口(core #1213;core 7.38.0 发车帖把它改期到 **7.39.0**)**:core 7.39.0 删掉旧检查点行的一次性迁移、并**拒续跑**仍带旧模式键(`autoModeRequested` / `approverPosture` /
|
|
45
|
+
doctrine `shellGate`)的行。这类行只可能是 **server ≤7.105.0(core ≤7.33.8)时期停放、至今仍 pending** 的行 —— server 7.106.0(core 7.35.2)起写的行
|
|
46
|
+
与在 7.106.0+ 上续跑过一次的行都已是新形。**在升到提货 core 7.39.0 的 server 版本之前**(7.110.0 提货 core 7.38.0 仍迁移、不拒),把这些行答完或放弃:
|
|
47
|
+
列表 = `GET /v1/approvals`(运维队列;`createdAt` 早于本部署升到 server 7.106.0 那一刻的行)与 `GET /v1/assistant/inbox`;
|
|
48
|
+
决定口 = 工具审批 `POST /v1/approvals/:id/decide`(`:id` = sessionId,体 `{ "decision": "approve" | "deny" }`)、计划审阅
|
|
49
|
+
`POST /v1/assistant/tasks/:id/plan_review`、资源停车续跑 `POST /v1/assistant/tasks/:id/resume`。本版(core 7.37.x)上续跑这类行仍会迁移一次再继续。
|
|
50
|
+
本服务不读、也不判这几个旧键(那是 core 的私有迁移面),只按停放时间给出区间。
|
|
51
|
+
## 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` 脱敏
|
|
52
|
+
|
|
53
|
+
- **🔴 安全轴(S-919):本仓收紧方向的按名策略臂(`APPROVAL_DENY` / `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / commandPolicy ask 表 / 治理 shell 标 / durable 问题门 /
|
|
54
|
+
supervisor 危险门 / workflow 问与 plan 拒)读 core `ruleNamesOf(req)` 全集,任一名命中即算命中。**只影响**经 `resolveSpec` 席挂引擎名别名的嵌入方工具(`MyShell{aliases:["Bash"]}`):
|
|
55
|
+
修前模型按别名调它一个门都不拦,修后照拦照问。**迁移**:要让某只工具不被某条名单够到,别给它挂那条名单里的引擎名作别名;规则归因 `matchedAskRule` 写命中的那个名。
|
|
56
|
+
- **wire 值(S-929):`GET /v1/approvals` 的 `crashConverged[].cause` 从单值 `"crashed_before_park"` 变闭集 `"crashed_before_park" | "shutdown_before_park"`**(有序关停宽限窗满仍未闭的已批腿;
|
|
57
|
+
`orphanState` / `resumeSafe` 判据同形,`shutdown_before_park` 常态 `decided`、`pending` 形只在故障下出现 —— 持久 ask 店 claim 挂住,或 park 路由的停车行到宽限窗满仍未提交)。
|
|
58
|
+
**迁移**:消费方按 `orphanState` / `resumeSafe` 分桶,不按 `cause` 单值判。**消费方版本约束**:client-core ≤0.82.7 的行校验 `cause !== 'crashed_before_park'` 把新词行计进
|
|
59
|
+
`dropped`(cli 渲「could not be read」,fail-safe、不自动续跑);配套 = client-core 认领 CC-218 的那一版(放宽为两值)。审计账本 settle 行 additive `cause:"shutdown_before_park"` + 第六个记录种类 `interrupted_by_shutdown`。
|
|
60
|
+
**回滚约束**:回滚到 ≤7.107.0 后,本版写下的 `interrupted_by_shutdown` 行在旧二进制上计 `corruptLines`、boot 那一行 `approval_ask_audit_crash_converged` warn 会响
|
|
61
|
+
(无害:旧读者跳过坏行,settle 行多出的 `cause` 键被忽略)。**park 政策下**:有序关停时握着未决卡的 run 落 `done(suspended)`(修前进程退出把它截断在 running,
|
|
62
|
+
由下一次 boot 收成崩溃孤儿)—— 轮询 run 终态的消费方会看到 suspended,可经 `/v1/approvals/:sessionId/decide` 事后决议。
|
|
63
|
+
**有序关停中 park 路由的卡以观察到 core 已处置这次调用为结清**:settle 行只在**这次调用自己的**停车行提交被观察到(执行会话 × 调用 id 相等)、或承载它的流式腿退场之后才写
|
|
64
|
+
(委派 / 后台子代的卡只认它自己的停车行 —— 宿主腿退场、宿主停车行提交都不结清它);宽限窗满仍两者皆无 ⇒ 不写 + 运维 warn `approval_park_uncommitted_at_shutdown`,
|
|
65
|
+
下一次 boot 照样列成 pending 孤儿(与崩溃同路;子代(未被追踪腿)卡的腿闭证据不由宿主腿提供 —— 重放的腿证据只对 `legTracked` 行作数;`backend.close()` 排在店的在飞 `put` 静默之后)。
|
|
66
|
+
**`UNATTENDED_APPROVAL_POLICY=deny` 的部署**:有序重启时未决卡当场按部署政策拒、腿带着拒绝再跑至多 10s(模型可能把 run 收成 `completed`),该卡不再出现在
|
|
67
|
+
`crashConverged`(修前是 `crashed_before_park` / pending / `resumeSafe:true`)—— 要事后决议就改 `park` 政策,或在排空窗内答完。
|
|
68
|
+
- **wire 值(S-930):`POST /v1/sessions/:id/memory/erase` 的四种 core 失败形从 500 `internal.error` 改为与 operator 面同码同状态**(409 `memory.erasure_selector_mismatch` /
|
|
69
|
+
`memory.erasure_evidence_unavailable` / `memory.erasure_census_incomplete`;500 `internal.memory_control_plane_corrupt` 固定句)。**迁移**:按「自助抹除失败恒 500」写死的调用方改按 `errorCode` 分支;
|
|
70
|
+
sdk 15.0.0 的 🚧 live 表征钉随 15.0.1 翻 409。
|
|
71
|
+
- **目录条目 +1 键(S-931):`models[].headers`**(闭集白名单,今天只 `anthropic-beta`、只在 `api: "anthropic-messages"` 条目;表外 / 线路不读 / 值不合形 ⇒ 条目不可用
|
|
72
|
+
`[config.model_entry_invalid]` fields `headers.<名>`)。**⚠️ 本版半场**:settings-schema 6.3.0 读腿剥未知键 ⇒ 经生产配置通道写的 `headers` 到不了目录(只有 LKG / 手写 effective 到得了),
|
|
73
|
+
settings-schema 6.4.0 + server 7.109.0 打通(S-935)。**迁移**:零动作;要今天就用,走 effective 直写通道并接受读腿告警。
|
|
74
|
+
- **通告码 +1(S-925):user 受众 `server.model_unreachable`**(`engine_notice` 帧 / 账本行,先于 `done`;`detail = { code, targetHost, proxyInUse?, remedy, sessionId }`;每腿每目标一次;
|
|
75
|
+
腿里的辅助模型调用同判 —— 帧说的是端点够不着,不是 run 失败)。**迁移**:按 `code` 渲染的消费方零动作;经 client-core 按受众分发的端在 client-core 镜像 +1 行(0.86.x)前看不到它(S-933,core 7.38.0 入册后两边镜像退役)。
|
|
76
|
+
运维面另 +1 日志行 `outbound_connect_failed`(代理只报变量名)与 `GET /v1/diagnostics/wiring` 的 `outbound` 段。
|
|
77
|
+
- **wire 值(S-922):`POST /v1/side-query` 200 体 `errorMessage` 过 `redactErrorMessage`**(键不变;服务商错误体里的密钥形换 `«redacted»`)。**迁移**:按原样错误体做字符串匹配的调用方改比错误码。
|
|
78
|
+
- **零动作(S-921 / S-920)**:披露改真与明拒行保鲜钉,无行为变化。
|
|
79
|
+
|
|
80
|
+
|
|
10
81
|
## 7.107.0(车SE,S-907 / S-890;车SF)—— `@sema-agent/core` 7.36.0:post 钩子收 wire 名 · 双引擎名声明拒 · 工具调用 id 越界折叠(服务商拿回原串)· 两只恒真能力位删
|
|
11
82
|
|
|
12
83
|
- **🔴 hooks:PostToolUse / PostToolUseFailure 的 matcher 与 payload `tool_name` 改为调用派发到的工具的 wire 名**(此前是模型拼写;PreToolUse 本来就是 wire 名)。
|
|
@@ -45,7 +116,7 @@
|
|
|
45
116
|
也被拒(core 路径拒座把 `cp` 的源也按写目标判,与 CC 同形),升级前照跑 —— 补偿 = 旋钮 `acceptEdits|default` + 审批席,或用读工具取内容。
|
|
46
117
|
🔴 **宽于升级前的六类**:根外读探针(`probe`)、MCP / A2A 工具标记、transcript 写、`rule_unreadable`(**规则店读不出时照放** —— 安全轴 fail-open 方向)、
|
|
47
118
|
`artifact_shared_read` —— 升级前这些腿上照问(无人答 ⇒ 拒),缺省旋钮下被释放。**补偿**:`UNATTENDED_PERMISSION_MODE=acceptEdits|default`,或给腿配审批席。
|
|
48
|
-
想更严设 `acceptEdits` / `default` / `dontAsk
|
|
119
|
+
想更严设 `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。
|
|
49
120
|
- **会话授权(`remember:"session"` / `allow_session`)对引擎的问也生效**:同会话下一次同工具不再出卡(Write / 非只读 Bash 在 `default` 下同此;
|
|
50
121
|
需要审批席 —— `TOOL_APPROVAL_ENABLED` 开,不要活卡再配 `STREAM_ASK_WINDOW_MS=0`)。`APPROVAL_NEVER_AUTO` / 耐久预算耗尽 / **治理层**
|
|
51
122
|
(`runtime.commandPolicy` 的 ask、`AUTONOMY` 折出的门、敏感写守卫集)的卡改成强制卡(`mandated:true`、`ruleOffersAbsence:"mandated"`,此前 `"shadowed"`),
|
package/USAGE.md
CHANGED
|
@@ -332,6 +332,27 @@ 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;两条读腿打通 = S-935,7.109.0)**
|
|
336
|
+
```jsonc
|
|
337
|
+
// config.d/models.json(或配置中心 models 域)的一条条目:
|
|
338
|
+
{ "name": "claude-1m", "id": "<上游模型 id>", "provider": "anthropic", "api": "anthropic-messages",
|
|
339
|
+
"headers": { "anthropic-beta": "context-1m-2025-08-07" } }
|
|
340
|
+
```
|
|
341
|
+
- **三问**:谁需要 = 要开供应商 beta(1M 上下文等)的部署(壳的 `--betas` / `ANTHROPIC_BETAS` 进不了引擎发出的请求,这里是它的部署级座);
|
|
342
|
+
谁受伤 = 无(缺席 ⇒ 请求逐字节同 7.107.0);补偿 = n/a(按请求变化 ⇒ 目录写两条不同名的条目,走既有的模型选择)。
|
|
343
|
+
- **白名单是闭集**,每个名字带「读它的线路」:今天只有 `anthropic-beta`(只在 `api: "anthropic-messages"` 的条目上)。表外的名字(含
|
|
344
|
+
`authorization` / `x-api-key` —— 凭据只走 `sealedApiKey` / `apiKeyEnv`)、大小写变体(只认小写规范拼写)、写在不读它的线路上、值不是
|
|
345
|
+
可见 ASCII(或含换行)⇒ **这条条目不可用**:不进可选目录,warn `sema_registry_model_entry_invalid` 点名条目与 `headers.<名>`(不回显值),
|
|
346
|
+
显式选中它 ⇒ 400 `request.model_unavailable` `[config.model_entry_invalid]`;其余条目照常服务。
|
|
347
|
+
- **per-model**:头铸进这一只模型(core `Model.headers`),同一条 anthropic 路由上别的条目不受影响;值逐字 —— core 会把它自己要开的 beta
|
|
348
|
+
并进同一个 `anthropic-beta` 头(core anthropic 腿的 beta 头合并律),不会互相覆盖。模型面热应用:改了下一次请求即生效(降级壳焊住的目标除外,那一只按
|
|
349
|
+
`degrade-route` 重启片走)。
|
|
350
|
+
- **请求面没有 `betas`**:头是部署 ↔ 供应商的契约(计费 / 配额 / 兼容性由运维承担),不是租户可表态的位 —— `POST /v1/tasks` 带 `betas` ⇒
|
|
351
|
+
400 `request.body_shape`(键闭集)。
|
|
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`,上面那几条)。
|
|
355
|
+
|
|
335
356
|
**可选 — OTLP/HTTP 指标导出(core 1.37 可观测)**
|
|
336
357
|
```bash
|
|
337
358
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 # 设了才开;周期把指标 POST 到 <endpoint>/v1/metrics
|
|
@@ -843,31 +864,20 @@ UNATTENDED_APPROVAL_POLICY=park
|
|
|
843
864
|
- 代答不到的:`APPROVAL_DENY` / 敏感路径守卫(是拒,不是问);`APPROVAL_NEVER_AUTO` 与耐久预算耗尽的问(「恒问人」,回执 `rememberApplied:false`);
|
|
844
865
|
委派子代经父链继承到祖先席上的引擎问(引擎盖强制位,席依约不代答)。
|
|
845
866
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
-
|
|
857
|
-
|
|
858
|
-
(
|
|
859
|
-
|
|
860
|
-
普通问句不问。本旋钮**不增不减**这些腿上已经接着的运维门:运维写下的问(审批名单、治理层的问、hook 的问)在
|
|
861
|
-
`bypassPermissions` 下照问(没人答照旧 park 或拒),拒照拒 —— 它只决定引擎自己的「普通」问句由谁答。
|
|
862
|
-
- 🔴 写面不随它打开:这三类腿的写面恒是工作区根内(`roots`,与 7.105.0 同)—— `bypassPermissions` 在引擎模式表里自带的「根外也能写」
|
|
863
|
-
**不**随本旋钮进来。
|
|
864
|
-
- 想让这些腿更严:设 `acceptEdits`(根内写不问、非只读命令仍问 ⇒ 没人答即拒)、`default`(写与非只读命令都问)或 `dontAsk`
|
|
865
|
-
(凡是会问的一律拒)。run-local 有 TTY 时 `default` 会在终端里问你。
|
|
866
|
-
- `auto` 需要引擎的 auto 分类器席;leader / run-local 的 Runner 不挂它 ⇒ 引擎按「auto 不可用」折回 `default` 并发一条
|
|
867
|
-
`config.permission_mode_unavailable` 通告。中心下发的 `RuntimeCaps.bypassPermissions === false` 同样会把 `bypassPermissions`
|
|
868
|
-
折回 `default`(组织级拒优先)。
|
|
869
|
-
- 纯部署级:不看任何客户端表态,也不参与托管姿态判定(这些腿不是请求)。启动日志一行 `unattended_permission_mode`
|
|
870
|
-
(`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 一句)。
|
|
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「审计读者」)。
|
|
871
881
|
|
|
872
882
|
**可选 — 写保护名表(S-138;缺席=引擎缺省表在岗)**
|
|
873
883
|
|
|
@@ -2149,6 +2159,22 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
|
|
|
2149
2159
|
- **启动日志**:恰一行 `outbound_proxy`,`{httpProxy, httpsProxy, noProxy, loopbackDirect, runtime}`(代理值 display-safe;`httpsProxy` 是 https
|
|
2150
2160
|
目标**实际**走的那只,含回落)。配置目录 `GET /v1/config/catalog` 有 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` 三行(`limitsHttp` 组,回显同一份
|
|
2151
2161
|
display-safe 投影)。`/health` 与 fleet 公告**不**带代理信息。
|
|
2162
|
+
- **建连失败时怎么读(7.108.0 / S-925;Node 运行时)**:任何一条出网请求在**建连时**就失败(码闭集:`UND_ERR_CONNECT_TIMEOUT` —— TCP + TLS 没在
|
|
2163
|
+
传输层的建连时限内完成 / `ECONNREFUSED` / `ENOTFOUND` / `EAI_AGAIN` / `EHOSTUNREACH` / `ENETUNREACH`;可能出现在已建立连接上的
|
|
2164
|
+
`ECONNRESET` / `ETIMEDOUT` 刻意不算)⇒ 三件,同一份事实:
|
|
2165
|
+
① **运维日志** 一行 `outbound_connect_failed { targetHost, code, proxyInUse, proxyReadAtMs, sinceBootMs, hint }` —— `proxyInUse` = 这一跳走的代理
|
|
2166
|
+
**变量名**(`HTTP_PROXY` / `HTTPS_PROXY`,不打值;直连 ⇒ `null`),`proxyReadAtMs` = 代理变量被读进来的时刻(= 起服那一拍),`sinceBootMs` = 进程已跑多久
|
|
2167
|
+
—— 两者一对照就知道「起服之后 VPN / 代理是不是换过」;全进程的出网都算(模型、中心、工具 …),每条失败的请求一行(重试各算一次)。
|
|
2168
|
+
② **诊断面** `GET /v1/diagnostics/wiring` 的 `outbound` 段:`{ proxy: { http, https }, noProxy, loopbackDirect, proxyReadAtMs, lastConnectFailure }` ——
|
|
2169
|
+
代理只报**名字**(哪只变量供的;`https` 在 `HTTPS_PROXY` 缺席时回落 `HTTP_PROXY`)、`NO_PROXY` 只报在不在,地址一个字节都不上本段;
|
|
2170
|
+
`lastConnectFailure` = 本进程最近一次建连失败 `{ targetHost, code, proxyInUse, atMs }`(没有过 ⇒ `null`)。
|
|
2171
|
+
③ **用户面**:一次**模型调用**因建连失败而终态(它的最后一条请求在建连时失败;5xx / 中途断流 / 取消都不算;腿里的辅助调用 ——
|
|
2172
|
+
WebFetch 摘要、压实、提示建议、分类器 —— 同判:说的是端点够不着,不是 run 失败了)⇒ 这条 run 的流 / 账本上一帧
|
|
2173
|
+
`engine_notice` `server.model_unreachable`,`detail = { code, targetHost, proxyInUse?, remedy: "check VPN / proxy; restart if the proxy changed since boot", sessionId }`
|
|
2174
|
+
(契约附录 D.3;每腿每目标一次;流式 / 后台 / 续跑三条腿有,同步非流式腿没有投递口 ⇒ 只有 ① ②)。**出路**:查 VPN / 代理;代理或 VPN 在起服之后
|
|
2175
|
+
变过 ⇒ 重启 server(代理变量只在启动时读)。没有旋钮。**会被它打扰的部署形**:辅助端点(auto 分类器 / WebFetch 摘要 / 提示建议 / 压实)单独部署且挂掉、
|
|
2176
|
+
主模型正常 —— 用户每腿每目标会看到一帧、出路句是泛化的;读 `detail.targetHost` 分清是哪一个端点(它是部署自己声明的模型端点,对用户是新披露的事实;
|
|
2177
|
+
代理地址不上),运维按同一目标对 `outbound_connect_failed` 对账。Bun 运行时三件都没有(它的原生 fetch 不经 undici 诊断通道)。
|
|
2152
2178
|
- **Bun 运行时(发布镜像 `CMD ["bun", …]` 与 `bun build --compile` 二进制)**:Bun 的 fetch 不读 undici 的全局 dispatcher,它**自己**读这三只变量
|
|
2153
2179
|
(代理与 `NO_PROXY` 都认),但**回环不豁免**。启动行因此报 `loopbackDirect:false`,配了代理时升 warn —— 在 Bun 上请把 `localhost,127.0.0.1,::1`
|
|
2154
2180
|
写进 `NO_PROXY`。Bun 下代理值**不拒启**(本仓的 undici 在 Bun 里是垫片、不校验;Bun 自己把无 scheme 的值当 http 用,`ftp:` 到请求时才报错),
|
|
@@ -34,10 +34,15 @@
|
|
|
34
34
|
* 臂的收敛前提,见下)/ 窗死线 / 铸造时刻。
|
|
35
35
|
* `settle` 决议点(`settle()` 公共咽喉,五臂全过):outcome(allowed/denied/expired)+
|
|
36
36
|
* `parkRouted`(窗到期/断连等 park 路由臂如实标注 —— 该行的续命在 durable park 面,
|
|
37
|
-
* 收敛器**不**把它当孤儿)+ 宿主自报(在场才带)
|
|
37
|
+
* 收敛器**不**把它当孤儿)+ 宿主自报(在场才带)+ `cause:"shutdown_before_park"`(S-929:这次等待是被**有序关停**
|
|
38
|
+
* 结束的 —— `drainForShutdown` 按断连同路收场的活卡;park 路由的那几行**等观察到 core 已处置这次调用才落**(F1:停车行提交 / 承载它的腿退场,后者恒在 `leg_closed` 之前),两者皆无就退出 ⇒ 无此行;闭集 {@link AuditShutdownCause},词表外 ⇒ 计坏行、结清照认)。
|
|
38
39
|
* `leg_closed` 腿闭点(`runWithContext` finally;只为铸过审计行的 taskId 落,防账本膨胀):
|
|
39
40
|
* 「决议之后腿有没有闭」是 decided 臂唯一的机械证据面 —— 没有它,「批完跑完」与
|
|
40
41
|
* 「批了、执行窗内崩」在账本字节上同形,收敛器只能二选一地撒谎。
|
|
42
|
+
* `interrupted_by_shutdown` **有序关停记号**(S-929;`drainForShutdown` 在关停宽限窗满时落,只为铸过审计行、腿仍未闭的 taskId):
|
|
43
|
+
* 「这条腿是被**有序关停**截断的,不是进程崩了」。它**不是** `leg_closed`(腿没闭:工具可能正执行到一半),所以
|
|
44
|
+
* 不改变收敛判据,只改变收敛行的 `cause`(`"shutdown_before_park"` 而不是 `"crashed_before_park"`)—— `resumeSafe:false`
|
|
45
|
+
* 那句「确认上次操作再续跑」的提醒不许因为关停是有序的就丢掉。
|
|
41
46
|
* `crash_converged` 收敛记录(boot 收敛器落,幂等锚):orphanState 两值分臂。
|
|
42
47
|
* `late_settle` **迟到人工结算点**([ref];两条 HTTP decide 腿在**权威受理之后**落,受理谓词 =
|
|
43
48
|
* `HTTP 200 ∧ errorCode ≠ "cancelled"`,[ref] 件②起 —— 见下):
|
|
@@ -84,12 +89,21 @@
|
|
|
84
89
|
* **decided 臂**:批了、执行窗内崩,工具可能有半截副作用 ⇒ `resumeSafe:false`(留人工确认)。
|
|
85
90
|
* 判据方向刻意**保守**:腿闭是「本进程对该腿的守护结束」,晚于工具真完成 —— 误差方向是「把可能
|
|
86
91
|
* 干净的批注成待人工确认」,绝不反向。
|
|
87
|
-
* -
|
|
92
|
+
* - **腿证据只对被追踪行作数**(S-945,[ref]):`leg_closed` 与 `interrupted_by_shutdown` 都只按 taskId 落,只对 `ask.legTracked` 的行作证据 ——
|
|
93
|
+
* 委派 / 后台子代 ask 行的 taskId 是宿主 run,宿主腿的记录不证明子代那次调用的任何事(下面三条判据都经这一口读)。
|
|
94
|
+
* - **腿闭反证门**(codex R1-F1 部分采):有 `ask`(被追踪)、无 `settle`、却有本 taskId 的后继 `leg_closed` ⇒
|
|
88
95
|
* 这不是崩溃孤儿(settle 恒先于 ask() 返回、腿闭恒在其后),只能是「settle 行写丢在一条跑完了的
|
|
89
96
|
* 腿上」的审计断档组 —— **不收敛**(标 pending = 把可能已执行的批注成 resumeSafe:true,方向反;
|
|
90
97
|
* 标 decided = 凭空断言人批了),`inconsistent` 计数进 boot 日志。
|
|
98
|
+
* - **收敛行的 `cause`**(S-929,两臂同一条):该组最后一个事件行(settle 行,缺席则 ask 行)之后、它的收敛行(重投影时)
|
|
99
|
+
* 之前,有本 taskId 的 `interrupted_by_shutdown` 记号(被追踪行才认)⇒ `"shutdown_before_park"`(腿被有序关停截断);否则 `"crashed_before_park"`。
|
|
100
|
+
* 记号**只改成因注,不改收敛判据**:有序关停下已批而腿未闭,工具照样可能半截副作用(decided 臂 `resumeSafe:false` 照留)。
|
|
101
|
+
* 有序关停下的**未决**活卡由 `drainForShutdown` 先落 settle(`cause:"shutdown_before_park"`)⇒ 不再是 pending 孤儿(本就不收敛);
|
|
102
|
+
* park 路由的卡须等**观察到 core 已处置这次调用**(停车行提交 / 承载它的腿退场)才落(F1)—— 宽限窗满仍两者皆无 ⇒ 无 settle 行 ⇒ 照样按 pending 孤儿收敛(有序关停中未提交的 park
|
|
103
|
+
* 与崩溃同路,证据不丢;腿被打过记号 ⇒ `shutdown_before_park`,零活流 / 子代腿无记号 ⇒ `crashed_before_park`)。
|
|
91
104
|
* - 其余(denied / expired / park 路由 / 已收敛)⇒ 不收敛。三条成文残留(fail-safe 方向,宁漏不误):
|
|
92
|
-
* ① `legTracked:false` 的 decided 行拿不到腿闭信号 ⇒ decided 臂对它不收(pending 臂不受此限
|
|
105
|
+
* ① `legTracked:false` 的 decided 行拿不到腿闭信号 ⇒ decided 臂对它不收(pending 臂不受此限 —— 但也因此没有腿闭反证门护着:
|
|
106
|
+
* 子代卡照列 pending 孤儿但 `resumeSafe:false` —— 没有腿证据就不作「零执行」正断言(S-945 ⑧,车 SO codex r2 [high];修前 true = 方向反))。
|
|
93
107
|
* 两形同标 false:bg 腿(出处腿不在本协调器 runWithContext 视野)与**委派/bg 子代**(codex
|
|
94
108
|
* R1-F2:继承链闭包携宿主身份,origin.taskId = 宿主 run,宿主腿退场的 leg_closed 会把「批了、
|
|
95
109
|
* 子代执行中崩」误反证成「跑完了」—— 铸造点 `!child` 合取如实标 false);
|
|
@@ -158,12 +172,32 @@ export interface AuditAskInput {
|
|
|
158
172
|
expiresAtMs: number;
|
|
159
173
|
mintedAtMs: number;
|
|
160
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* S-929:审计账本上「这件事因**有序关停**而结束」的成因词(闭集,今天一个词)。settle 行的 `cause` 与收敛行的 `cause`
|
|
177
|
+
* 共用这一个词。读盘词表 = {@link AUDIT_SHUTDOWN_CAUSE_WORDS}(编译期穷举)。词表外的值(新版本写的、本二进制不认识的成因 / 手改)⇒
|
|
178
|
+
* **计坏行**(`corruptLines`,坏值响亮律 —— 不静默读成「没写成因」),但**不丢该 settle 行的结清**(F4:outcome / parkRouted 照认 ——
|
|
179
|
+
* 因为一个成因词把整条结清行丢掉,会把一张已结清的卡收成 pending 孤儿,方向比「少认一个成因」更坏)。
|
|
180
|
+
*/
|
|
181
|
+
export type AuditShutdownCause = "shutdown_before_park";
|
|
182
|
+
/**
|
|
183
|
+
* 成因词的读盘词表(F4:`satisfies Record<AuditShutdownCause, true>` ⇒ 闭集加一个词而本表没跟 = **编译红**;判定走 `Object.hasOwn`,
|
|
184
|
+
* 原型链上的名字不算词 —— CLAUDE.md 词表律)。唯一读者 = {@link readShutdownCause}。
|
|
185
|
+
*/
|
|
186
|
+
export declare const AUDIT_SHUTDOWN_CAUSE_WORDS: {
|
|
187
|
+
readonly shutdown_before_park: true;
|
|
188
|
+
};
|
|
189
|
+
/** 收敛行 `cause` 的闭集(`GET /v1/approvals` `crashConverged[].cause`):崩溃孤儿 / 有序关停截断的腿(S-929 +1;与 `crashed_before_park` 同形)。
|
|
190
|
+
* wire 值闭集登记在 `trace/self-minted-wire.ts` 的 `crashConvergedCause`(`satisfies Record<CrashConvergedCause, …>` 双向绑定 ⇒ 加词不登记 = tsc 红,
|
|
191
|
+
* wire 变更清单从那张表 diff —— F3)。 */
|
|
192
|
+
export type CrashConvergedCause = "crashed_before_park" | AuditShutdownCause;
|
|
161
193
|
/** 决议点入参(`settle()` 公共咽喉挂线;五臂全过,不漏窗到期/断连/取消)。 */
|
|
162
194
|
export interface AuditSettleInput {
|
|
163
195
|
approvalId: string;
|
|
164
196
|
outcome: "allowed" | "denied" | "expired";
|
|
165
197
|
/** 走了 park 路由(windowRouteUnavailable)—— 该行的续命在 durable park 面,收敛器不当孤儿。 */
|
|
166
198
|
parkRouted?: boolean;
|
|
199
|
+
/** S-929:这次等待是被有序关停结束的(`ToolApprovalCoordinator.drainForShutdown`);缺席 = 别的臂。 */
|
|
200
|
+
cause?: AuditShutdownCause;
|
|
167
201
|
/** 宿主自报来源(HostSettledBy;在场才带)。收敛器不消费,纯审计。 */
|
|
168
202
|
settledBy?: string;
|
|
169
203
|
settledAtMs: number;
|
|
@@ -208,6 +242,9 @@ export interface ApprovalAskAuditSink {
|
|
|
208
242
|
recordSettle(input: AuditSettleInput): void;
|
|
209
243
|
/** 腿闭点(`runWithContext` finally)。只为铸过审计行的 taskId 真落行(店内去重)。 */
|
|
210
244
|
noteLegClosed(taskId: string): void;
|
|
245
|
+
/** S-929 有序关停记号点(`drainForShutdown` 宽限窗满时调一次):铸过审计行、腿仍未闭的每个 taskId 各落一条
|
|
246
|
+
* `interrupted_by_shutdown`。 */
|
|
247
|
+
noteLegsInterruptedByShutdown(): void;
|
|
211
248
|
/** [ref] 迟到人工结算点(HTTP decide 两腿)。**纯观察**:自吞一切失败,不返回受否,绝不改变结算面。 */
|
|
212
249
|
recordLateSettle(input: AuditLateSettleInput): void;
|
|
213
250
|
}
|
|
@@ -237,7 +274,9 @@ export interface CrashConvergedRow {
|
|
|
237
274
|
expiresAtMs: number;
|
|
238
275
|
/** 终态 DENIED 同码(稿 §2 (c) 施工形;SQL 车道收敛器的 fail-closed DENIED 同向)。 */
|
|
239
276
|
decision: "denied";
|
|
240
|
-
|
|
277
|
+
/** 成因注(闭集 {@link CrashConvergedCause}):`crashed_before_park` = 进程崩了;`shutdown_before_park`(S-929)= 腿被**有序关停**截断
|
|
278
|
+
* (判据见头注「收敛行的 `cause`」)。两者的 `orphanState` / `resumeSafe` 判据相同。 */
|
|
279
|
+
cause: CrashConvergedCause;
|
|
241
280
|
orphanState: CrashOrphanState;
|
|
242
281
|
/** decided 臂:人当时批的是 approve(这句话不许被终态同码吞掉)。 */
|
|
243
282
|
originalDecision?: "approve";
|
|
@@ -275,10 +314,14 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
275
314
|
private readonly replayed;
|
|
276
315
|
/** 前世账本里的腿闭事件(taskId + 行序;decided 臂按「决议行之后」判)。 */
|
|
277
316
|
private readonly replayedLegClosed;
|
|
317
|
+
/** S-929:前世账本里的有序关停记号(taskId + 行序;收敛行 `cause` 的判据,见头注)。 */
|
|
318
|
+
private readonly replayedShutdownMarks;
|
|
278
319
|
/** 已收敛行(读面数据源;含前世已收敛 + 本次 boot 新收敛)。owner 只做 scope 门,不上 wire。 */
|
|
279
320
|
private readonly convergedRows;
|
|
280
321
|
/** 本进程铸过审计行、且腿闭信号可达的 taskId(腿闭去重 + 防无关腿刷账本)。 */
|
|
281
322
|
private readonly openLegTasks;
|
|
323
|
+
/** S-929:本进程已落过有序关停记号的 taskId(记号去重;与腿追踪集分开 —— 记号之后的真实腿闭照落)。 */
|
|
324
|
+
private readonly shutdownMarkedLegs;
|
|
282
325
|
private convergedOnce;
|
|
283
326
|
/**
|
|
284
327
|
* [ref] 反查索引 ①:**checkpoint 锚 → 铸造行身份**。与 {@link replayed} 刻意分家 —— 那张表是
|
|
@@ -336,6 +379,11 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
336
379
|
recordAsk(input: AuditAskInput): void;
|
|
337
380
|
recordSettle(input: AuditSettleInput): void;
|
|
338
381
|
noteLegClosed(taskId: string): void;
|
|
382
|
+
/** S-929:有序关停宽限窗满时仍未闭的腿各落一条记号。只为铸过审计行、腿仍开着的 taskId 落;记号有**自己的**去重集 ——
|
|
383
|
+
* 腿追踪集 `openLegTasks` **不动**:进程在记号之后仍可能活几秒(server / backend 收尾),期间真跑完的腿照常落 `leg_closed`
|
|
384
|
+
* (codex r1 F1 [high]:清空追踪集会吞掉这份真实证据,而「settle 写丢 + 腿跑完」时正是它让腿闭反证门判 inconsistent,
|
|
385
|
+
* 吞掉就把一次已执行的批投影成 pending / `resumeSafe:true`)。迟到的腿闭落在记号之后 ⇒ 收敛判据照常认它(腿跑完了,不是孤儿)。 */
|
|
386
|
+
noteLegsInterruptedByShutdown(): void;
|
|
339
387
|
/**
|
|
340
388
|
* [ref] **迟到人工结算点**(判据的唯一属主;两条 HTTP decide 腿只交锚,不自己推)。
|
|
341
389
|
*
|
|
@@ -369,6 +417,16 @@ export declare class FileApprovalAskAuditStore implements ApprovalAskAuditSink,
|
|
|
369
417
|
inconsistent: number;
|
|
370
418
|
corruptLines: number;
|
|
371
419
|
};
|
|
420
|
+
/** S-929 收敛行的成因注(判据见头注「收敛行的 `cause`」):组的最后一个事件行之后、收敛行(若已收敛)之前,有本 taskId 的
|
|
421
|
+
* 有序关停记号 ⇒ `shutdown_before_park`;否则 `crashed_before_park`。 */
|
|
422
|
+
private causeOf;
|
|
423
|
+
/**
|
|
424
|
+
* 🔴 **S-945([ref])—— 腿证据只对被追踪的 ask 行作数**:一处成文,三处读(pending 臂的腿闭反证门 / decided 臂 / 收敛行成因注)。
|
|
425
|
+
* 腿闭行与有序关停记号都只按 `taskId` 落;委派 / 后台子代 ask 行的 `taskId` 是**宿主** run(出处),承载它的不是宿主腿(铸造时
|
|
426
|
+
* `legTracked:false`)⇒ 宿主腿的这两类记录对它什么都不证明(修前反证门不看 `legTracked`:宿主腿闭把子代未结清卡判成 inconsistent、不列孤儿)。
|
|
427
|
+
* 返回 `undefined` = 这一行没有可作证的腿;否则 = 本 taskId 在 `(afterLine, beforeLine)` 之间有没有该类记录。
|
|
428
|
+
*/
|
|
429
|
+
private legEvidence;
|
|
372
430
|
private projectRow;
|
|
373
431
|
/** scope 门与 `listLivePending` 逐字同律:`undefined` = operator 全量(含 owner null 行);串 = 只见
|
|
374
432
|
* `owner === scope` 的行(null-owner 行对 principal 不可见,fail-closed 宁少列不超范围;`"__none__"`
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
5
|
export const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
|
|
6
6
|
export const CRASH_CONVERGED_LIST_MAX = 200;
|
|
7
|
+
export const AUDIT_SHUTDOWN_CAUSE_WORDS = { shutdown_before_park: true };
|
|
7
8
|
export function buildCheckpointAnchorHash(token) {
|
|
8
9
|
return createHash("sha256").update(token, "utf8").digest("hex").slice(0, 16);
|
|
9
10
|
}
|
|
@@ -12,7 +13,17 @@ const HOST_DECIDED_BY = new Set(HOST_DECIDED_BY_WORDS);
|
|
|
12
13
|
function isHostDecidedBy(v) {
|
|
13
14
|
return typeof v === "string" && HOST_DECIDED_BY.has(v);
|
|
14
15
|
}
|
|
15
|
-
function
|
|
16
|
+
function isAuditShutdownCause(v) {
|
|
17
|
+
return typeof v === "string" && Object.hasOwn(AUDIT_SHUTDOWN_CAUSE_WORDS, v);
|
|
18
|
+
}
|
|
19
|
+
function readShutdownCause(v) {
|
|
20
|
+
if (v === undefined)
|
|
21
|
+
return { cause: undefined, outOfSet: false };
|
|
22
|
+
if (isAuditShutdownCause(v))
|
|
23
|
+
return { cause: v, outOfSet: false };
|
|
24
|
+
return { cause: undefined, outOfSet: true };
|
|
25
|
+
}
|
|
26
|
+
function readLedgerRecord(u, noteOutOfSetSeat) {
|
|
16
27
|
if (u === null || typeof u !== "object" || Array.isArray(u))
|
|
17
28
|
return undefined;
|
|
18
29
|
const o = u;
|
|
@@ -66,7 +77,18 @@ function readLedgerRecord(u) {
|
|
|
66
77
|
if (approvalId === undefined || settledAtMs === undefined || outcome === undefined)
|
|
67
78
|
return undefined;
|
|
68
79
|
const settledBy = s("settledBy");
|
|
69
|
-
|
|
80
|
+
const cause = readShutdownCause(o.cause);
|
|
81
|
+
if (cause.outOfSet)
|
|
82
|
+
noteOutOfSetSeat();
|
|
83
|
+
return {
|
|
84
|
+
kind: "settle",
|
|
85
|
+
approvalId,
|
|
86
|
+
outcome,
|
|
87
|
+
settledAtMs,
|
|
88
|
+
...(bool("parkRouted") === true ? { parkRouted: true } : {}),
|
|
89
|
+
...(settledBy !== undefined ? { settledBy } : {}),
|
|
90
|
+
...(cause.cause !== undefined ? { cause: cause.cause } : {}),
|
|
91
|
+
};
|
|
70
92
|
}
|
|
71
93
|
case "leg_closed": {
|
|
72
94
|
const taskId = s("taskId");
|
|
@@ -75,6 +97,13 @@ function readLedgerRecord(u) {
|
|
|
75
97
|
return undefined;
|
|
76
98
|
return { kind: "leg_closed", taskId, closedAtMs };
|
|
77
99
|
}
|
|
100
|
+
case "interrupted_by_shutdown": {
|
|
101
|
+
const taskId = s("taskId");
|
|
102
|
+
const atMs = num("atMs");
|
|
103
|
+
if (taskId === undefined || atMs === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
return { kind: "interrupted_by_shutdown", taskId, atMs };
|
|
106
|
+
}
|
|
78
107
|
case "crash_converged": {
|
|
79
108
|
const approvalId = s("approvalId");
|
|
80
109
|
const convergedAtMs = num("convergedAtMs");
|
|
@@ -138,8 +167,10 @@ export class FileApprovalAskAuditStore {
|
|
|
138
167
|
writeBytes;
|
|
139
168
|
replayed = new Map();
|
|
140
169
|
replayedLegClosed = [];
|
|
170
|
+
replayedShutdownMarks = [];
|
|
141
171
|
convergedRows = [];
|
|
142
172
|
openLegTasks = new Set();
|
|
173
|
+
shutdownMarkedLegs = new Set();
|
|
143
174
|
convergedOnce = false;
|
|
144
175
|
askByCallAnchor = new Map();
|
|
145
176
|
askBySessionOnly = new Map();
|
|
@@ -177,7 +208,9 @@ export class FileApprovalAskAuditStore {
|
|
|
177
208
|
this.corruptLines += 1;
|
|
178
209
|
continue;
|
|
179
210
|
}
|
|
180
|
-
const rec = readLedgerRecord(parsed)
|
|
211
|
+
const rec = readLedgerRecord(parsed, () => {
|
|
212
|
+
this.corruptLines += 1;
|
|
213
|
+
});
|
|
181
214
|
if (rec === undefined) {
|
|
182
215
|
this.corruptLines += 1;
|
|
183
216
|
continue;
|
|
@@ -206,9 +239,13 @@ export class FileApprovalAskAuditStore {
|
|
|
206
239
|
case "leg_closed":
|
|
207
240
|
this.replayedLegClosed.push({ taskId: rec.taskId, line });
|
|
208
241
|
break;
|
|
242
|
+
case "interrupted_by_shutdown":
|
|
243
|
+
this.replayedShutdownMarks.push({ taskId: rec.taskId, line });
|
|
244
|
+
break;
|
|
209
245
|
case "crash_converged": {
|
|
210
246
|
const g = this.group(rec.approvalId, line);
|
|
211
247
|
g.converged = rec;
|
|
248
|
+
g.convergedLine = line;
|
|
212
249
|
break;
|
|
213
250
|
}
|
|
214
251
|
default: {
|
|
@@ -292,6 +329,15 @@ export class FileApprovalAskAuditStore {
|
|
|
292
329
|
this.openLegTasks.delete(taskId);
|
|
293
330
|
this.append({ kind: "leg_closed", taskId, closedAtMs: Date.now() });
|
|
294
331
|
}
|
|
332
|
+
noteLegsInterruptedByShutdown() {
|
|
333
|
+
const atMs = Date.now();
|
|
334
|
+
for (const taskId of this.openLegTasks) {
|
|
335
|
+
if (this.shutdownMarkedLegs.has(taskId))
|
|
336
|
+
continue;
|
|
337
|
+
this.shutdownMarkedLegs.add(taskId);
|
|
338
|
+
this.append({ kind: "interrupted_by_shutdown", taskId, atMs });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
295
341
|
recordLateSettle(input) {
|
|
296
342
|
const candidates = input.toolCallId !== undefined
|
|
297
343
|
? (this.askByCallAnchor.get(FileApprovalAskAuditStore.callAnchorKey(input.sessionId, input.toolCallId)) ?? new Set())
|
|
@@ -327,27 +373,26 @@ export class FileApprovalAskAuditStore {
|
|
|
327
373
|
if (g.ask === undefined)
|
|
328
374
|
continue;
|
|
329
375
|
if (g.converged !== undefined) {
|
|
330
|
-
this.convergedRows.push({ row: this.projectRow(g, g.converged.orphanState, g.converged.convergedAtMs), owner: g.ask.owner });
|
|
376
|
+
this.convergedRows.push({ row: this.projectRow(g, g.converged.orphanState, g.converged.convergedAtMs, this.causeOf(g)), owner: g.ask.owner });
|
|
331
377
|
continue;
|
|
332
378
|
}
|
|
333
379
|
let orphanState;
|
|
334
380
|
if (g.settle === undefined) {
|
|
335
|
-
if (this.
|
|
381
|
+
if (this.legEvidence(g, this.replayedLegClosed, g.askLine ?? 0) === true) {
|
|
336
382
|
inconsistent += 1;
|
|
337
383
|
continue;
|
|
338
384
|
}
|
|
339
385
|
orphanState = "pending";
|
|
340
386
|
}
|
|
341
387
|
else if (g.settle.outcome === "allowed" &&
|
|
342
|
-
g.
|
|
343
|
-
!this.replayedLegClosed.some((l) => l.taskId === g.ask.taskId && l.line > (g.settleLine ?? 0))) {
|
|
388
|
+
this.legEvidence(g, this.replayedLegClosed, g.settleLine ?? 0) === false) {
|
|
344
389
|
orphanState = "decided";
|
|
345
390
|
}
|
|
346
391
|
if (orphanState === undefined)
|
|
347
392
|
continue;
|
|
348
393
|
const convergedAtMs = Date.now();
|
|
349
394
|
this.append({ kind: "crash_converged", approvalId, orphanState, convergedAtMs });
|
|
350
|
-
this.convergedRows.push({ row: this.projectRow(g, orphanState, convergedAtMs), owner: g.ask.owner });
|
|
395
|
+
this.convergedRows.push({ row: this.projectRow(g, orphanState, convergedAtMs, this.causeOf(g)), owner: g.ask.owner });
|
|
351
396
|
if (orphanState === "pending")
|
|
352
397
|
pending += 1;
|
|
353
398
|
else
|
|
@@ -357,7 +402,18 @@ export class FileApprovalAskAuditStore {
|
|
|
357
402
|
this.convergedRows.length = Math.min(this.convergedRows.length, CRASH_CONVERGED_LIST_MAX);
|
|
358
403
|
return { pending, decided, inconsistent, corruptLines: this.corruptLines };
|
|
359
404
|
}
|
|
360
|
-
|
|
405
|
+
causeOf(g) {
|
|
406
|
+
const after = g.settleLine ?? g.askLine ?? 0;
|
|
407
|
+
const before = g.convergedLine ?? Number.POSITIVE_INFINITY;
|
|
408
|
+
return this.legEvidence(g, this.replayedShutdownMarks, after, before) === true ? "shutdown_before_park" : "crashed_before_park";
|
|
409
|
+
}
|
|
410
|
+
legEvidence(g, records, afterLine, beforeLine = Number.POSITIVE_INFINITY) {
|
|
411
|
+
const ask = g.ask;
|
|
412
|
+
if (!ask.legTracked)
|
|
413
|
+
return undefined;
|
|
414
|
+
return records.some((r) => r.taskId === ask.taskId && r.line > afterLine && r.line < beforeLine);
|
|
415
|
+
}
|
|
416
|
+
projectRow(g, orphanState, convergedAtMs, cause) {
|
|
361
417
|
const ask = g.ask;
|
|
362
418
|
return {
|
|
363
419
|
approvalId: ask.approvalId,
|
|
@@ -367,11 +423,11 @@ export class FileApprovalAskAuditStore {
|
|
|
367
423
|
ts: ask.mintedAtMs,
|
|
368
424
|
expiresAtMs: ask.expiresAtMs,
|
|
369
425
|
decision: "denied",
|
|
370
|
-
cause
|
|
426
|
+
cause,
|
|
371
427
|
orphanState,
|
|
372
428
|
...(orphanState === "decided" ? { originalDecision: "approve", ...(g.settle !== undefined ? { decidedAtMs: g.settle.settledAtMs } : {}) } : {}),
|
|
373
429
|
convergedAtMs,
|
|
374
|
-
resumeSafe: orphanState === "pending",
|
|
430
|
+
resumeSafe: orphanState === "pending" && this.legEvidence(g, this.replayedLegClosed, g.askLine ?? 0) === false,
|
|
375
431
|
};
|
|
376
432
|
}
|
|
377
433
|
listCrashConverged(scope) {
|
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
return
|
|
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]);
|