@sema-agent/server 7.106.0 → 7.108.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 +52 -0
- package/README.md +2 -2
- package/USAGE.md +40 -4
- package/dist/approval-ask-audit-store.d.ts +62 -4
- package/dist/approval-ask-audit-store.js +67 -11
- package/dist/approval.js +16 -13
- package/dist/boot/coordinators.d.ts +4 -0
- package/dist/boot/coordinators.js +3 -2
- 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/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/config-catalog.d.ts +6 -1
- package/dist/config-catalog.js +34 -17
- package/dist/config-center/apply-effective.js +2 -0
- package/dist/config-center/types.d.ts +7 -0
- 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/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/routes/capabilities.js +0 -2
- 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/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 +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/run-terminal.d.ts +8 -5
- package/dist/outbound-dispatcher.d.ts +69 -0
- package/dist/outbound-dispatcher.js +123 -3
- package/dist/plugins/park-commit-watch.d.ts +48 -0
- package/dist/plugins/park-commit-watch.js +87 -0
- package/dist/router/route-orchestration.js +7 -2
- package/dist/runs.js +3 -2
- package/dist/runtime-governance.js +3 -2
- package/dist/task-settings.d.ts +30 -17
- package/dist/task-settings.js +15 -4
- package/dist/tool-approval.d.ts +62 -0
- package/dist/tool-approval.js +67 -3
- 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 +13 -1
- 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 +6 -3
- package/dist/trace/self-minted-wire.d.ts +71 -0
- package/dist/trace/self-minted-wire.js +28 -0
- package/package.json +2 -2
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,58 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 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
|
+
|
|
12
|
+
- **🔴 安全轴(S-919):本仓收紧方向的按名策略臂(`APPROVAL_DENY` / `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / commandPolicy ask 表 / 治理 shell 标 / durable 问题门 /
|
|
13
|
+
supervisor 危险门 / workflow 问与 plan 拒)读 core `ruleNamesOf(req)` 全集,任一名命中即算命中。**只影响**经 `resolveSpec` 席挂引擎名别名的嵌入方工具(`MyShell{aliases:["Bash"]}`):
|
|
14
|
+
修前模型按别名调它一个门都不拦,修后照拦照问。**迁移**:要让某只工具不被某条名单够到,别给它挂那条名单里的引擎名作别名;规则归因 `matchedAskRule` 写命中的那个名。
|
|
15
|
+
- **wire 值(S-929):`GET /v1/approvals` 的 `crashConverged[].cause` 从单值 `"crashed_before_park"` 变闭集 `"crashed_before_park" | "shutdown_before_park"`**(有序关停宽限窗满仍未闭的已批腿;
|
|
16
|
+
`orphanState` / `resumeSafe` 判据同形,`shutdown_before_park` 常态 `decided`、`pending` 形只在故障下出现 —— 持久 ask 店 claim 挂住,或 park 路由的停车行到宽限窗满仍未提交)。
|
|
17
|
+
**迁移**:消费方按 `orphanState` / `resumeSafe` 分桶,不按 `cause` 单值判。**消费方版本约束**:client-core ≤0.82.7 的行校验 `cause !== 'crashed_before_park'` 把新词行计进
|
|
18
|
+
`dropped`(cli 渲「could not be read」,fail-safe、不自动续跑);配套 = client-core 认领 CC-218 的那一版(放宽为两值)。审计账本 settle 行 additive `cause:"shutdown_before_park"` + 第六个记录种类 `interrupted_by_shutdown`。
|
|
19
|
+
**回滚约束**:回滚到 ≤7.107.0 后,本版写下的 `interrupted_by_shutdown` 行在旧二进制上计 `corruptLines`、boot 那一行 `approval_ask_audit_crash_converged` warn 会响
|
|
20
|
+
(无害:旧读者跳过坏行,settle 行多出的 `cause` 键被忽略)。**park 政策下**:有序关停时握着未决卡的 run 落 `done(suspended)`(修前进程退出把它截断在 running,
|
|
21
|
+
由下一次 boot 收成崩溃孤儿)—— 轮询 run 终态的消费方会看到 suspended,可经 `/v1/approvals/:sessionId/decide` 事后决议。
|
|
22
|
+
**有序关停中 park 路由的卡以观察到 core 已处置这次调用为结清**:settle 行只在**这次调用自己的**停车行提交被观察到(执行会话 × 调用 id 相等)、或承载它的流式腿退场之后才写
|
|
23
|
+
(委派 / 后台子代的卡只认它自己的停车行 —— 宿主腿退场、宿主停车行提交都不结清它);宽限窗满仍两者皆无 ⇒ 不写 + 运维 warn `approval_park_uncommitted_at_shutdown`,
|
|
24
|
+
下一次 boot 照样列成 pending 孤儿(与崩溃同路;子代(未被追踪腿)卡的腿闭证据不由宿主腿提供 —— 重放的腿证据只对 `legTracked` 行作数;`backend.close()` 排在店的在飞 `put` 静默之后)。
|
|
25
|
+
**`UNATTENDED_APPROVAL_POLICY=deny` 的部署**:有序重启时未决卡当场按部署政策拒、腿带着拒绝再跑至多 10s(模型可能把 run 收成 `completed`),该卡不再出现在
|
|
26
|
+
`crashConverged`(修前是 `crashed_before_park` / pending / `resumeSafe:true`)—— 要事后决议就改 `park` 政策,或在排空窗内答完。
|
|
27
|
+
- **wire 值(S-930):`POST /v1/sessions/:id/memory/erase` 的四种 core 失败形从 500 `internal.error` 改为与 operator 面同码同状态**(409 `memory.erasure_selector_mismatch` /
|
|
28
|
+
`memory.erasure_evidence_unavailable` / `memory.erasure_census_incomplete`;500 `internal.memory_control_plane_corrupt` 固定句)。**迁移**:按「自助抹除失败恒 500」写死的调用方改按 `errorCode` 分支;
|
|
29
|
+
sdk 15.0.0 的 🚧 live 表征钉随 15.0.1 翻 409。
|
|
30
|
+
- **目录条目 +1 键(S-931):`models[].headers`**(闭集白名单,今天只 `anthropic-beta`、只在 `api: "anthropic-messages"` 条目;表外 / 线路不读 / 值不合形 ⇒ 条目不可用
|
|
31
|
+
`[config.model_entry_invalid]` fields `headers.<名>`)。**⚠️ 本版半场**:settings-schema 6.3.0 读腿剥未知键 ⇒ 经生产配置通道写的 `headers` 到不了目录(只有 LKG / 手写 effective 到得了),
|
|
32
|
+
settings-schema 6.4.0 + server 7.109.0 打通(S-935)。**迁移**:零动作;要今天就用,走 effective 直写通道并接受读腿告警。
|
|
33
|
+
- **通告码 +1(S-925):user 受众 `server.model_unreachable`**(`engine_notice` 帧 / 账本行,先于 `done`;`detail = { code, targetHost, proxyInUse?, remedy, sessionId }`;每腿每目标一次;
|
|
34
|
+
腿里的辅助模型调用同判 —— 帧说的是端点够不着,不是 run 失败)。**迁移**:按 `code` 渲染的消费方零动作;经 client-core 按受众分发的端在 client-core 镜像 +1 行(0.86.x)前看不到它(S-933,core 7.38.0 入册后两边镜像退役)。
|
|
35
|
+
运维面另 +1 日志行 `outbound_connect_failed`(代理只报变量名)与 `GET /v1/diagnostics/wiring` 的 `outbound` 段。
|
|
36
|
+
- **wire 值(S-922):`POST /v1/side-query` 200 体 `errorMessage` 过 `redactErrorMessage`**(键不变;服务商错误体里的密钥形换 `«redacted»`)。**迁移**:按原样错误体做字符串匹配的调用方改比错误码。
|
|
37
|
+
- **零动作(S-921 / S-920)**:披露改真与明拒行保鲜钉,无行为变化。
|
|
38
|
+
|
|
39
|
+
## 7.107.0(车SE,S-907 / S-890;车SF)—— `@sema-agent/core` 7.36.0:post 钩子收 wire 名 · 双引擎名声明拒 · 工具调用 id 越界折叠(服务商拿回原串)· 两只恒真能力位删
|
|
40
|
+
|
|
41
|
+
- **🔴 hooks:PostToolUse / PostToolUseFailure 的 matcher 与 payload `tool_name` 改为调用派发到的工具的 wire 名**(此前是模型拼写;PreToolUse 本来就是 wire 名)。
|
|
42
|
+
按工具**别名**写的 post matcher(及条目 `if` 条件的名部)不再命中(只影响执行后面,不放宽任何决定)。**迁移**:matcher 写 wire 名;滚动升级期两名都写(本服务不做双读)。
|
|
43
|
+
PostToolBatch 不变(条目 `tool_name` 仍是拼写,matcher 本就被忽略)。hook 故障通告的 `toolName` 在 post 两事件上同步变为 wire 名。
|
|
44
|
+
- **🔴 声明:一只工具的名字(正名 + 别名,按解析后计)落到两只引擎工具上 ⇒ run 在准备阶段被拒 `config.tool_face_invalid`**(零轮;同步口 200 + failed 体)。
|
|
45
|
+
只影响经 `resolveSpec` 席自带工具声明的嵌入方。**迁移**:一只工具只答一只引擎工具的名。
|
|
46
|
+
- **行为面(收紧)**:调用方工具声明低不可逆档又挂 `Bash` / `Monitor` 别名 ⇒ 座取最严的名(`default` 多一张卡、`dontAsk` 从跑变拒)。**迁移**:去掉那个别名,或接受 shell 座。
|
|
47
|
+
- **wire 值:服务商给的工具调用 id 越界(控制字节 / 空格 / 非 ASCII / `~` / 129 字符以上)时,本栈各面的 `toolCallId` 是引擎的折叠形** `<前 32 码元>~<32 hex>`
|
|
48
|
+
(恒在形、确定、单射;在形 id 逐字不变)。服务商自己的那串在转录 `ToolCall.wireId` / side-query `toolCalls[].wireId`,引擎回给服务商的调用 / 结果恒用它
|
|
49
|
+
(⚠️ PostgreSQL 会话后端:原串含 NUL / 孤立代理码元时经落店重载会被清洗成 U+FFFD —— 既有限制,7.106.0 同病落在调用 id 本身)。
|
|
50
|
+
**零动作**(join 照旧成立);与服务商日志对账的运维读 `wireId`,或服务端结构化日志 `engine_notice` `route.tool_call_id_folded`(每 run 一行,operator 受众,不上会话流)。
|
|
51
|
+
升级前已 park 的行以原串为键,新版 resume 不再折叠(askId 不变、卡照旧可答)。
|
|
52
|
+
- **耐久停车行 `pendingAction.names` 的序改「拼写先」**(门判定那张脸)。本服务任何 wire 面都不投 `names`;只影响直读店行(SQL `checkpoint` 列 / local 检查点文件)的工具 —— 按 `names[0]` 猜 wire 名的改读 `toolName`。
|
|
53
|
+
- **🔴 wire(能力面,车SF S-899):`GET /v1/capabilities` 的 `modeShellGateTranslation` 与 `permissionModeAuto.intentArming` 删**(两位 7.106.0 起恒 `true`,零信息)。
|
|
54
|
+
**迁移**:壳不再按 `modeShellGateTranslation` 决定是否注 `MANUAL_MODE_SHELL_GATE`(缺席不是「老 server」;直接不注);`intentArming` 的读者改读 `armed` / `reason`。**真实后果**:已发布 cli ≤1.0.139 的引擎附着门把缺席读成旧引擎 ⇒ 同机多安装下 headless `-p` 拒复用 ≥7.107.0 引擎、交互车道误报一行(fail-closed 有出路:停那台引擎或升级较旧的 cli 安装 ≥1.0.140);sdk 14.0.0 仍把 `intentArming` 写成必填(门 6 红在围栏头校准 7.106.0),随 sdk 14.1.0 删两位 + 抬围栏头。
|
|
55
|
+
- **请求面模式词表(S-902)**:五词受理集不变、`dontAsk` 仍 400 `request.field_invalid`;三处 400 拒句里的词序改随 core `PERMISSION_MODES` 序(文案值变、码不变)—— 按字节比对拒句的测试改比码。
|
|
56
|
+
|
|
57
|
+
### 升级须知补三条(S-923 + 合并复审)
|
|
58
|
+
- **工具名帧的拼写**:fresh 腿 `tool_start` / `tool_end.toolName` 是模型拼写(与 7.106.0 同);升级前 park 的行续跑仍给 wire 名(`names` wire 名在先);**只有 7.107.0 起按别名 park 的调用**续跑帧改给拼写。壳按 `toolCallId` join,不按帧的 `toolName` 归一;要 wire 名读 PreToolUse / post 钩子上下文的 `toolName`、`wiring_manifest.tools` 名册或停车行 `pendingAction.toolName`(帧不能反推)。
|
|
59
|
+
- **把引擎手工具名挂成别名的嵌入方**(`aliases:["Read"]` 一类):中断调用按较不安全效应判、效应面按较不安全效应记(只在该名挂载不重登效应时)、结果预算不再豁免、微压缩清理不再达它(上下文可能变大)—— 升级前排空这类声明下的 park,或接受更保守判定;一只名字属于两只引擎工具的声明在 prepare 被拒(`config.tool_face_invalid`),已 park 的调用续跑不动,改声明后重试。
|
|
60
|
+
- **持久面反向不兼容边界(回滚 / 共享存储混版)**:7.107.0(core 7.36.0 #1207)起,服务商给的**越界** tool-call id(字符集外或 >128)在转录 / 检查点里落**折叠形**,原串只在同行 `wireId`;≤7.106.0 的实例(core 7.35.2)不读 `wireId`,续跑这类会话时会把折叠 id 发回网关 ⇒ 要求回传原 id 的网关拒绝后续请求(在形 id 零差异,不受影响;7.35.2 的 pre-CAS 复核按 `names` 先读拼写、取座可能不同)。**约束**:同一存储上不要让 ≤7.106.0 实例接管 7.107.0 已写过折叠 id 的会话;回滚前排空这类会话(会话 `messages[]` 里出现 `wireId` 的行即受影响),或接受重开。
|
|
61
|
+
|
|
10
62
|
## 7.106.0(车QY,S-877 / S-881)—— `@sema-agent/core` 7.35.2(7.35.0 的全部变更 + #1206 新会话锁 + 看门狗 30s 等待披露):一只权限模式 · `approverPosture` / `taskApproverPosture` 删 · `MANUAL_MODE_SHELL_GATE=classify` 拒启 · 宿主子进程 env 按 CC 形剥(git 配置族与代理族原样到达)
|
|
11
63
|
|
|
12
64
|
- **🔴 wire:提交体 `approverPosture` 删,零别名。** 仍带它 ⇒ 400 `request.body_shape`(`unknownKeys:["approverPosture"]`)。**迁移**:删掉这个键;要「宿主自动答卡」的效果用
|
package/README.md
CHANGED
|
@@ -186,8 +186,8 @@ The server is configured entirely through environment variables. The most import
|
|
|
186
186
|
| `SENSITIVE_WRITE_PATTERNS` | core's recommended set | Sensitive-path write deny list; comma-separated value replaces the set, `off` **or an empty value** disables. Applied unconditionally at the governance layer (independent of client permission mode, lane or settings presence) — including on the `run-local` leg. A value that cannot compile into a guard set (e.g. `/`, a pattern with no path segment) refuses to start |
|
|
187
187
|
| `WRITE_PROTECTED_EXTRA` | unset (engine default table) | **Adds** rows to core's write-protection table (the literal-name table whose hit demotes a surviving `allow` to `ask` on Write/Edit/NotebookEdit). Comma-separated bare names (a bare name matches ANY path segment; a name containing `/` matches a consecutive segment run) or a JSON array of `"name"` strings / `{name, kind}` rows (`kind`: `basename` \| `segment` \| `segment-run`). The value is composed as `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`, so **no default row can be lost**. Unset = no seat is wired = the engine's default table is in force (this server never copies that table). Form discrimination is by content, not by first character: a value containing any JSON structural character (`[ ] { } "`) is parsed as JSON and **must** be a top-level array (a missing pair of brackets refuses to start instead of being split into junk bare names). An empty value, a malformed entry (glob metacharacter, unknown kind, kind/name mismatch) or setting this together with `WRITE_PROTECTED_TABLE_REPLACE` **refuses to start**. Read faces: `GET /v1/capabilities` → `writeProtection.{armed,rows,replaced}`; `GET /v1/diagnostics/wiring` → `writeProtection.{rows,source,droppedDefaultRows}` (operator-only) |
|
|
188
188
|
| `WRITE_PROTECTED_TABLE_REPLACE` | unset (engine default table) | **Replaces the whole** write-protection table (core's seat is whole-table by contract). JSON array only — deliberately no comma shorthand, because a slipped bare string would swap 51 default rows for one. `[]` = the explicit "no write-protection table at all" posture. Replacing logs one **loud** boot line naming every default row you dropped (`write_protection_table_replaced`; the empty posture logs `write_protection_table_disabled`) — use `WRITE_PROTECTED_EXTRA` when you meant to ADD. Same refuse-to-start conditions as its sibling, plus: both knobs set = two writers on one surface = refuses to start |
|
|
189
|
-
| `MANUAL_MODE_SHELL_GATE` | unset | `always
|
|
190
|
-
| `PERMISSIONS_DISABLE_AUTO_MODE` | `false` | Local mirror of CC `permissions.disableAutoMode` — the **org deny** bit for `permissionMode:"auto"` (paired with core's intent-arming rule). **Tighten-only**: `true` folds every principal's `runtimeCaps.autoMode` to `false` (a center grant cannot flip it back); unset leaves caps untouched, so on a center-less box a shell asking for `auto` **arms** the classifier once the paired core (intent-arming rule "requested ∧ classifier seat ∧ `autoMode !== false`" — absence is not a deny) is installed;
|
|
189
|
+
| `MANUAL_MODE_SHELL_GATE` | unset | `always` — the operator floor: keep every `Bash` call under the engine's shape-tier approval question regardless of the client's permission mode, lane or settings (applied at the governance layer). **Unset is not "off"** and `off` is a no-op (the knob only tightens); `classify` was retired in 7.106.0 (core 7.34.0 folds the classifier into the engine's own mode arbitration) and now **refuses to boot** (the boot error names the retirement and the two remaining words; no error code — it is a config parse refusal, not a wire code). The catalog row (`GET /v1/config/catalog`) lists `enumValues: ["always","off"]`, `echoWords: ["always"]`. |
|
|
190
|
+
| `PERMISSIONS_DISABLE_AUTO_MODE` | `false` | Local mirror of CC `permissions.disableAutoMode` — the **org deny** bit for `permissionMode:"auto"` (paired with core's intent-arming rule). **Tighten-only**: `true` folds every principal's `runtimeCaps.autoMode` to `false` (a center grant cannot flip it back); unset leaves caps untouched, so on a center-less box a shell asking for `auto` **arms** the classifier once the paired core (intent-arming rule "requested ∧ classifier seat ∧ `autoMode !== false`" — absence is not a deny) is installed; the old "org grant" rule is gone since server 7.106.0 (core 7.34.0: one arming polarity; the `permissionModeAuto.intentArming` member that used to report it was removed in 7.107.0). Boolean word table; any other value refuses to start. Self-check: `GET /v1/capabilities?permissionMode=auto` → `permissionModeAuto.{armed, reason, model}` (USAGE §9.4) |
|
|
191
191
|
| `SCRATCHPAD_SWEEP_TTL_MS` | 7 days | Idle-reap window for per-session scratchpad dirs (by dir mtime; `0` disables). The scratchpad is **ephemeral by contract**: replica-local disk, NOT part of the durable-suspend persistence set — a resume on a different replica, or after a sweep, starts with an empty dir (same two-track posture as the Agent SDK hosting doc: conversation persists, working-directory artifacts don't). Raise/disable only on single-replica deployments that park approvals for longer than the window |
|
|
192
192
|
| `MODEL_FIRST_BYTE_TIMEOUT_MS` | unset (core's own default) | The wait from `fetch()` to the response headers, ms (7.103.0; replaces the retired `MODEL_CONNECT_TIMEOUT_MS`, which now **refuses to start** — the TCP + TLS connect is the transport's own clock since core 7.33.0). Unset = no key is passed, core's default applies (plus an upload allowance by request size, and one widened retry of its own). `0` = no engine clock on that segment and the transport's header / body clocks off too (the TCP + TLS connect keeps the transport's own clock). A whole number in `[0, 2147483647]`, anything else refuses to start. With `MODEL_GATEWAY_FALLBACK_URLS` set every gateway route likewise gets the first window plus one widened retry before failover (the server does not pass core's `firstByteRetries`: a caller can turn failover off per request, and that call has no backup behind it). A failover topology must set this knob explicitly: unset, a primary that accepts the connection but never answers is cut over only after core's first-byte lane (default window plus the widened retry) runs out, and a call with a shorter time budget (code-review council lenses, 180 s) is cancelled before the backup is tried; at boot such a deployment logs one `failover_first_byte_lane` warn |
|
|
193
193
|
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `600000` | First-token timeout (the only watchdog for a stream that opens and never emits; `0` = no engine clock on that segment, the transport's body clock off too. Raised from `120000` in 7.71.0 — a self-hosted backend's long prefill legitimately takes minutes to the first byte; set `120000` to restore the old posture) |
|
package/USAGE.md
CHANGED
|
@@ -332,6 +332,26 @@ 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)**
|
|
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.108.0 的已知边界**:`@sema-agent/settings-schema` 6.3.0 的 `ModelEntry` 还没有 `headers` 键,`config.d` 与配置中心两条读腿会
|
|
353
|
+
**剥掉**它(读告警点名未知键)—— server 这一半已就位,schema 补键并提货之后才真正可用(候裁;届时本段删掉这一条)。
|
|
354
|
+
|
|
335
355
|
**可选 — OTLP/HTTP 指标导出(core 1.37 可观测)**
|
|
336
356
|
```bash
|
|
337
357
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 # 设了才开;周期把指标 POST 到 <endpoint>/v1/metrics
|
|
@@ -1933,11 +1953,11 @@ armed ⟺ 本次请求 permissionMode 是 "auto" ∧ 本部署装配了分类
|
|
|
1933
1953
|
| `CROSS_SESSION_DIALOG_EXPIRY` | 未设(⇒ 引擎缺省 `5m`) | 同族:CC settings 键名 `dialogExpiry` 的部署层 —— 被 hold 的跨会话消息等待人审多久后按**安全缺省**结算(过期丢弃,**带回执**告知发送方,不静默吞)。词表 `60s`/`5m`/`10m`/`never`(`never` = 不设期限);未设 ⇒ 本仓**不铸键**,由引擎落它自己的缺省(不复制上游缺省值)。词表外拒启。⚠️ 生效前提同上。另注:CC 的同名键还有第二个消费面(转发到远端客户端的审批对话框停靠时长),本旋钮**今天不驱动**那一面 |
|
|
1934
1954
|
|
|
1935
1955
|
**自查读面**(`GET /v1/capabilities.permissionModeAuto`,壳的 `sema doctor permissions` 消费;server 7.106.0 起引擎只剩一种武装极性,
|
|
1936
|
-
`intentArming`
|
|
1956
|
+
7.107.0 起成员 `intentArming` 删除,见该条):
|
|
1937
1957
|
|
|
1938
1958
|
```
|
|
1939
1959
|
GET /v1/capabilities?permissionMode=auto
|
|
1940
|
-
→ permissionModeAuto: { accepted:true, classifierSeat:true, entitlementSource:false,
|
|
1960
|
+
→ permissionModeAuto: { accepted:true, classifierSeat:true, entitlementSource:false, armed:true, model:"default" }
|
|
1941
1961
|
GET /v1/capabilities?permissionMode=default
|
|
1942
1962
|
→ permissionModeAuto: { …, armed:false, reason:"mode_not_auto" }
|
|
1943
1963
|
```
|
|
@@ -1949,8 +1969,8 @@ GET /v1/capabilities?permissionMode=default
|
|
|
1949
1969
|
center 硬失败 fail-closed 拒项)/ `local_denied`(本地旋钮 `PERMISSIONS_DISABLE_AUTO_MODE=true`,改本机 env)/ `settings_denied`
|
|
1950
1970
|
(settings 层 `permissions.disableAutoMode:"disable"`——本面不产,归 run 级面)/ `resolver_fault`(per-principal resolver 抛错,
|
|
1951
1971
|
引擎同形全拒)。
|
|
1952
|
-
- `intentArming
|
|
1953
|
-
|
|
1972
|
+
- `intentArming`:**7.107.0 删除**(S-899)。7.57.0–7.105.0 由安装包探针答(`false` = 旧式「组织授予」极性),7.106.0 恒 `true`(引擎只剩意图武装式
|
|
1973
|
+
一种极性:无 center 也能 auto)—— 不携带信息;「会不会武装」读 `armed` / `reason`。
|
|
1954
1974
|
- `model`:分类器**配置**路由(role 回落序 `classifier → summarize → default` 的 catalog 名),不是本次真用的模型。
|
|
1955
1975
|
- `entitlementSource` 仍只说「center 源在不在」;新武装式下它为 `false` **不再**意味着 auto 不武装。
|
|
1956
1976
|
- 读面按调用方身份解 per-principal caps(与引擎同一只 resolver,per-principal 缓存 60s;center 硬失败时受 8s
|
|
@@ -2149,6 +2169,22 @@ server 进程(HTTP 服务与 `run-local`)**所有走 undici 的出网跳**都认
|
|
|
2149
2169
|
- **启动日志**:恰一行 `outbound_proxy`,`{httpProxy, httpsProxy, noProxy, loopbackDirect, runtime}`(代理值 display-safe;`httpsProxy` 是 https
|
|
2150
2170
|
目标**实际**走的那只,含回落)。配置目录 `GET /v1/config/catalog` 有 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` 三行(`limitsHttp` 组,回显同一份
|
|
2151
2171
|
display-safe 投影)。`/health` 与 fleet 公告**不**带代理信息。
|
|
2172
|
+
- **建连失败时怎么读(7.108.0 / S-925;Node 运行时)**:任何一条出网请求在**建连时**就失败(码闭集:`UND_ERR_CONNECT_TIMEOUT` —— TCP + TLS 没在
|
|
2173
|
+
传输层的建连时限内完成 / `ECONNREFUSED` / `ENOTFOUND` / `EAI_AGAIN` / `EHOSTUNREACH` / `ENETUNREACH`;可能出现在已建立连接上的
|
|
2174
|
+
`ECONNRESET` / `ETIMEDOUT` 刻意不算)⇒ 三件,同一份事实:
|
|
2175
|
+
① **运维日志** 一行 `outbound_connect_failed { targetHost, code, proxyInUse, proxyReadAtMs, sinceBootMs, hint }` —— `proxyInUse` = 这一跳走的代理
|
|
2176
|
+
**变量名**(`HTTP_PROXY` / `HTTPS_PROXY`,不打值;直连 ⇒ `null`),`proxyReadAtMs` = 代理变量被读进来的时刻(= 起服那一拍),`sinceBootMs` = 进程已跑多久
|
|
2177
|
+
—— 两者一对照就知道「起服之后 VPN / 代理是不是换过」;全进程的出网都算(模型、中心、工具 …),每条失败的请求一行(重试各算一次)。
|
|
2178
|
+
② **诊断面** `GET /v1/diagnostics/wiring` 的 `outbound` 段:`{ proxy: { http, https }, noProxy, loopbackDirect, proxyReadAtMs, lastConnectFailure }` ——
|
|
2179
|
+
代理只报**名字**(哪只变量供的;`https` 在 `HTTPS_PROXY` 缺席时回落 `HTTP_PROXY`)、`NO_PROXY` 只报在不在,地址一个字节都不上本段;
|
|
2180
|
+
`lastConnectFailure` = 本进程最近一次建连失败 `{ targetHost, code, proxyInUse, atMs }`(没有过 ⇒ `null`)。
|
|
2181
|
+
③ **用户面**:一次**模型调用**因建连失败而终态(它的最后一条请求在建连时失败;5xx / 中途断流 / 取消都不算;腿里的辅助调用 ——
|
|
2182
|
+
WebFetch 摘要、压实、提示建议、分类器 —— 同判:说的是端点够不着,不是 run 失败了)⇒ 这条 run 的流 / 账本上一帧
|
|
2183
|
+
`engine_notice` `server.model_unreachable`,`detail = { code, targetHost, proxyInUse?, remedy: "check VPN / proxy; restart if the proxy changed since boot", sessionId }`
|
|
2184
|
+
(契约附录 D.3;每腿每目标一次;流式 / 后台 / 续跑三条腿有,同步非流式腿没有投递口 ⇒ 只有 ① ②)。**出路**:查 VPN / 代理;代理或 VPN 在起服之后
|
|
2185
|
+
变过 ⇒ 重启 server(代理变量只在启动时读)。没有旋钮。**会被它打扰的部署形**:辅助端点(auto 分类器 / WebFetch 摘要 / 提示建议 / 压实)单独部署且挂掉、
|
|
2186
|
+
主模型正常 —— 用户每腿每目标会看到一帧、出路句是泛化的;读 `detail.targetHost` 分清是哪一个端点(它是部署自己声明的模型端点,对用户是新披露的事实;
|
|
2187
|
+
代理地址不上),运维按同一目标对 `outbound_connect_failed` 对账。Bun 运行时三件都没有(它的原生 fetch 不经 undici 诊断通道)。
|
|
2152
2188
|
- **Bun 运行时(发布镜像 `CMD ["bun", …]` 与 `bun build --compile` 二进制)**:Bun 的 fetch 不读 undici 的全局 dispatcher,它**自己**读这三只变量
|
|
2153
2189
|
(代理与 `NO_PROXY` 都认),但**回环不豁免**。启动行因此报 `loopbackDirect:false`,配了代理时升 warn —— 在 Bun 上请把 `localhost,127.0.0.1,::1`
|
|
2154
2190
|
写进 `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) {
|
package/dist/approval.js
CHANGED
|
@@ -3,6 +3,7 @@ import { operatorAlwaysHumanAsk, operatorAsk } from "./operator-ask.js";
|
|
|
3
3
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
4
4
|
import { isExplicitPosture } from "./posture-source.js";
|
|
5
5
|
import { handsOnWorkerFilePlane } from "./execution-lane-caps.js";
|
|
6
|
+
import { heldRuleName } from "./tool-rule-names.js";
|
|
6
7
|
export function hasApprovalListIntent(config) {
|
|
7
8
|
return (config.approvalRequire.length > 0 ||
|
|
8
9
|
config.approvalDeny.length > 0 ||
|
|
@@ -79,49 +80,51 @@ export function createDurableAskPolicy(opts) {
|
|
|
79
80
|
const neverAuto = createNameSetMatcher(opts.neverAuto ?? []);
|
|
80
81
|
const budget = Math.max(0, Math.floor(opts.autoBudget ?? 0));
|
|
81
82
|
let autoApproved = 0;
|
|
83
|
+
const isQuestionTool = (name) => name === ASK_USER_QUESTION_TOOL_NAME;
|
|
82
84
|
return {
|
|
83
85
|
check: (req) => {
|
|
84
|
-
|
|
85
|
-
if (deny(toolName))
|
|
86
|
+
if (heldRuleName(req, deny) !== undefined)
|
|
86
87
|
return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
const neverAutoHit = heldRuleName(req, neverAuto);
|
|
89
|
+
if (neverAutoHit !== undefined)
|
|
90
|
+
return operatorAlwaysHumanAsk(`APPROVAL_NEVER_AUTO(${neverAutoHit})`, `human approval required to run "${req.toolName}"`);
|
|
91
|
+
const requireHit = heldRuleName(req, require);
|
|
92
|
+
if (requireHit !== undefined) {
|
|
90
93
|
const remEarly = req.budget?.resourceRemainingMicroUsd;
|
|
91
94
|
if (remEarly !== undefined && remEarly <= 0) {
|
|
92
95
|
return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
|
|
93
96
|
}
|
|
94
|
-
if (opts.exempt &&
|
|
97
|
+
if (opts.exempt && heldRuleName(req, isQuestionTool) === undefined) {
|
|
95
98
|
return (async () => {
|
|
96
99
|
let exempted = false;
|
|
97
100
|
try {
|
|
98
|
-
exempted = (await opts.exempt(toolName)) === true;
|
|
101
|
+
exempted = (await opts.exempt(req.toolName)) === true;
|
|
99
102
|
}
|
|
100
103
|
catch {
|
|
101
104
|
exempted = false;
|
|
102
105
|
}
|
|
103
106
|
if (exempted) {
|
|
104
|
-
opts.onExempted?.(toolName, req.toolName);
|
|
107
|
+
opts.onExempted?.(req.toolName, req.toolName);
|
|
105
108
|
return { action: "allow" };
|
|
106
109
|
}
|
|
107
|
-
return decideRequired(req,
|
|
110
|
+
return decideRequired(req, requireHit);
|
|
108
111
|
})();
|
|
109
112
|
}
|
|
110
|
-
return decideRequired(req,
|
|
113
|
+
return decideRequired(req, requireHit);
|
|
111
114
|
}
|
|
112
115
|
return { action: "allow" };
|
|
113
116
|
},
|
|
114
117
|
};
|
|
115
|
-
function decideRequired(req,
|
|
118
|
+
function decideRequired(req, requireHit) {
|
|
116
119
|
const rem = req.budget?.resourceRemainingMicroUsd;
|
|
117
120
|
if (rem !== undefined && rem <= 0) {
|
|
118
121
|
return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
|
|
119
122
|
}
|
|
120
|
-
if (
|
|
123
|
+
if (heldRuleName(req, isQuestionTool) === undefined && budget > 0 && autoApproved < budget) {
|
|
121
124
|
autoApproved++;
|
|
122
125
|
return { action: "allow" };
|
|
123
126
|
}
|
|
124
|
-
return operatorAsk(`APPROVAL_REQUIRE(${
|
|
127
|
+
return operatorAsk(`APPROVAL_REQUIRE(${requireHit})`, `approval required to run "${req.toolName}"`);
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
//# sourceMappingURL=approval.js.map
|
|
@@ -13,6 +13,7 @@ import type { Logger } from "../observability/logger.js";
|
|
|
13
13
|
import { QuestionCoordinator } from "../question.js";
|
|
14
14
|
import { ToolApprovalCoordinator, type ParkedAskRedeem } from "../tool-approval.js";
|
|
15
15
|
import { FileApprovalAskAuditStore } from "../approval-ask-audit-store.js";
|
|
16
|
+
import type { ParkCommitWatch } from "../plugins/park-commit-watch.js";
|
|
16
17
|
import { SendUserFileEmitter } from "../capabilities/send-user-file-tool.js";
|
|
17
18
|
import { TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
|
|
18
19
|
import type { StoreBackend } from "../plugins/store-backend.js";
|
|
@@ -32,6 +33,9 @@ export interface LiveCoordinatorsCtx {
|
|
|
32
33
|
* 必须排在它之前;同款 holder 先例 = reaper 的 `getRunDenySweep`)。取到 undefined ⇒ 那条臂逐字回落
|
|
33
34
|
* 修前 404(partial 部署安全)。缺席(不传本键)⇒ 协调器整条迟到腿的 PARKED 臂恒 404。 */
|
|
34
35
|
getParkedRedeem?: () => ParkedAskRedeem | undefined;
|
|
36
|
+
/** S-929 F1:checkpoint 店的 park 提交观察层(stage-06 与店同一处铸;在场 ⇔ 有 checkpoint 店)。协调器据它判「有序关停下的 park 路由
|
|
37
|
+
* 提交了没有」—— 审计 settle 行等 park 提交落定才写。缺席 = 没有 park 设施(core 在 ask 回调处就收场,没有提交这一步)。 */
|
|
38
|
+
parkCommits?: ParkCommitWatch;
|
|
35
39
|
}
|
|
36
40
|
export declare function createLiveCoordinators(ctx: LiveCoordinatorsCtx): {
|
|
37
41
|
elicitation: ElicitationCoordinator | undefined;
|
|
@@ -10,7 +10,7 @@ import { basename, resolve } from "node:path";
|
|
|
10
10
|
import { toolsRunOnThisHost } from "../task-cwd.js";
|
|
11
11
|
import { stat as fsStat, readFile as fsReadFile } from "node:fs/promises";
|
|
12
12
|
export function createLiveCoordinators(ctx) {
|
|
13
|
-
const { config, logger, backend, sendUserFileTaskEnvs, ruleConsent, ruleScopeRootFor, getParkedRedeem } = ctx;
|
|
13
|
+
const { config, logger, backend, sendUserFileTaskEnvs, ruleConsent, ruleScopeRootFor, getParkedRedeem, parkCommits } = ctx;
|
|
14
14
|
const elicitation = config.mcpElicitation.enabled ? new ElicitationCoordinator(config.mcpElicitation.throttle) : undefined;
|
|
15
15
|
const question = config.askQuestionEnabled ? new QuestionCoordinator(config.questionThrottle) : undefined;
|
|
16
16
|
const durableEnabled = backend?.checkpoint !== undefined && config.durableApproval === true;
|
|
@@ -51,7 +51,7 @@ export function createLiveCoordinators(ctx) {
|
|
|
51
51
|
decided: converged.decided,
|
|
52
52
|
inconsistent: converged.inconsistent,
|
|
53
53
|
corruptLines: converged.corruptLines,
|
|
54
|
-
note: "orphan approval asks from
|
|
54
|
+
note: "orphan approval asks from the previous process were converged (cause crashed_before_park = it crashed; cause shutdown_before_park = an orderly shutdown cut the leg off before it closed or before its park committed); see GET /v1/approvals crashConverged",
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -71,6 +71,7 @@ export function createLiveCoordinators(ctx) {
|
|
|
71
71
|
...(getParkedRedeem !== undefined ? { parkedRedeem: getParkedRedeem } : {}),
|
|
72
72
|
...(approvalAskAudit !== undefined ? { askAudit: approvalAskAudit } : {}),
|
|
73
73
|
...(approvalExemptionStore !== undefined ? { sessionGrants: approvalExemptionStore } : {}),
|
|
74
|
+
...(parkCommits !== undefined ? { parkCommits } : {}),
|
|
74
75
|
})
|
|
75
76
|
: undefined;
|
|
76
77
|
const sendUserFileEmitter = (() => {
|