@sema-agent/server 7.5.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +18 -3
  3. package/README.zh-CN.md +14 -3
  4. package/USAGE.md +37 -1
  5. package/dist/approval-reconciler.d.ts +1 -1
  6. package/dist/approval-reconciler.js +6 -5
  7. package/dist/boot/config-center.js +15 -2
  8. package/dist/boot/execution-env.js +1 -1
  9. package/dist/boot/parked-revive-gate.d.ts +78 -0
  10. package/dist/boot/parked-revive-gate.js +114 -0
  11. package/dist/boot/resolve-spec.d.ts +3 -20
  12. package/dist/boot/resolve-spec.js +25 -119
  13. package/dist/budget.d.ts +1 -1
  14. package/dist/budget.js +1 -1
  15. package/dist/capabilities/repo-tools.d.ts +1 -1
  16. package/dist/capabilities/repo-tools.js +8 -2
  17. package/dist/config-provider.d.ts +1 -0
  18. package/dist/config-provider.js +23 -3
  19. package/dist/config-types.d.ts +5 -3
  20. package/dist/config.js +5 -4
  21. package/dist/deployment-governance.d.ts +168 -0
  22. package/dist/deployment-governance.js +206 -0
  23. package/dist/fleet/fleet-bus.d.ts +11 -1
  24. package/dist/fleet/fleet-bus.js +43 -0
  25. package/dist/fleet/fleet-terminal-window.d.ts +98 -0
  26. package/dist/fleet/fleet-terminal-window.js +316 -0
  27. package/dist/http/routes/approvals-assistant.js +6 -7
  28. package/dist/http/routes/fleet.js +160 -14
  29. package/dist/http/routes/runs.js +5 -1
  30. package/dist/http/routes/trace-usage.js +3 -4
  31. package/dist/http/send.d.ts +23 -0
  32. package/dist/http/send.js +23 -0
  33. package/dist/http/server.d.ts +4 -0
  34. package/dist/http/server.js +5 -2
  35. package/dist/http/sse-log.js +3 -4
  36. package/dist/leader/diffout.d.ts +10 -0
  37. package/dist/leader/diffout.js +14 -2
  38. package/dist/leader/diffup.js +3 -2
  39. package/dist/leader/planner.js +7 -0
  40. package/dist/main.js +22 -26
  41. package/dist/observability/fail-open.d.ts +4 -0
  42. package/dist/observability/fail-open.js +4 -0
  43. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  44. package/dist/orchestration/workflow-notify-journal.js +14 -34
  45. package/dist/plugins/approval-ask-store-sql.d.ts +33 -0
  46. package/dist/plugins/approval-ask-store-sql.js +66 -33
  47. package/dist/plugins/background-agent-store-sql.js +16 -16
  48. package/dist/plugins/breaker-state-sql.js +2 -2
  49. package/dist/plugins/checkpoint-store-sql.d.ts +5 -2
  50. package/dist/plugins/checkpoint-store-sql.js +5 -2
  51. package/dist/plugins/image-bake-store-sql.d.ts +1 -1
  52. package/dist/plugins/image-bake-store-sql.js +27 -27
  53. package/dist/plugins/image-index-sql.js +15 -15
  54. package/dist/plugins/mailbox-store-sql.js +3 -3
  55. package/dist/plugins/memory-engine-pg.js +9 -9
  56. package/dist/plugins/memory-engine-tidb.js +7 -7
  57. package/dist/plugins/memory-sync-store-pg.js +13 -13
  58. package/dist/plugins/memory-sync-store-tidb.js +5 -5
  59. package/dist/plugins/outcome-ledger-sql.js +7 -7
  60. package/dist/plugins/pg-cost-quota.js +3 -3
  61. package/dist/plugins/pg-pool.js +84 -75
  62. package/dist/plugins/pg-rate-limiter.js +3 -3
  63. package/dist/plugins/pg-session-storage.d.ts +1 -1
  64. package/dist/plugins/pg-session-storage.js +12 -13
  65. package/dist/plugins/remote-env-host.js +3 -1
  66. package/dist/plugins/remote-env-local-docker.js +6 -3
  67. package/dist/plugins/remote-env-ssh.d.ts +13 -1
  68. package/dist/plugins/roster-store-sql.js +8 -8
  69. package/dist/plugins/store-contracts.d.ts +19 -0
  70. package/dist/plugins/store-contracts.js +42 -0
  71. package/dist/plugins/task-attachment-store.js +5 -5
  72. package/dist/plugins/task-list-store-sql.js +1 -1
  73. package/dist/plugins/tidb-cost-quota.js +1 -1
  74. package/dist/plugins/tidb-pool.js +83 -60
  75. package/dist/plugins/tidb-rate-limiter.js +1 -1
  76. package/dist/plugins/tidb-session-store.js +2 -5
  77. package/dist/plugins/tool-result-store-sql.js +2 -2
  78. package/dist/plugins/usage-window-store-sql.js +13 -13
  79. package/dist/plugins/write-behind-counter.d.ts +10 -2
  80. package/dist/plugins/write-behind-counter.js +13 -3
  81. package/dist/resource-suspend.d.ts +3 -1
  82. package/dist/resource-suspend.js +3 -1
  83. package/dist/run-local.d.ts +73 -1
  84. package/dist/run-local.js +146 -5
  85. package/dist/runs.d.ts +3 -1
  86. package/dist/runs.js +3 -1
  87. package/dist/security.d.ts +12 -0
  88. package/dist/security.js +12 -0
  89. package/dist/session-sync-kernel.d.ts +13 -0
  90. package/dist/session-sync-kernel.js +13 -0
  91. package/dist/trace/core-keyset-guard.d.ts +1 -1
  92. package/dist/trace/project.d.ts +10 -1
  93. package/dist/trace/project.js +31 -0
  94. package/package.json +3 -3
package/LICENSE CHANGED
@@ -3,7 +3,7 @@ Business Source License 1.1
3
3
  Parameters
4
4
 
5
5
  Licensor: clay (github.com/clayboby)
6
- Licensed Work: @sema-ai/server (sema-server)
6
+ Licensed Work: @sema-agent/server (sema-server)
7
7
  The Licensed Work is (c) 2026 clay.
8
8
  Additional Use Grant: You may make production use of the Licensed Work for
9
9
  personal, educational, research, or other
package/README.md CHANGED
@@ -95,13 +95,13 @@ Requirements: Node ≥ 20 (npm path) and an OpenAI-compatible model gateway.
95
95
  ```bash
96
96
  # A) npm
97
97
  npm install @sema-agent/server
98
- MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-chat \
98
+ MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-pro \
99
99
  MODEL_API_KEY=<your-key> SERVICE_AUTH_TOKEN=<pick-one> \
100
100
  node node_modules/@sema-agent/server/dist/main.js # → :8090
101
101
 
102
102
  # B) container (zero local deps, anonymous pull)
103
103
  docker run -p 8090:8090 \
104
- -e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-chat \
104
+ -e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-pro \
105
105
  -e MODEL_API_KEY=<your-key> -e SERVICE_AUTH_TOKEN=<pick-one> \
106
106
  ghcr.io/sema-agent/sema-server:latest # or docker.io/claybobby/sema-server:latest
107
107
 
@@ -116,6 +116,21 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
116
116
  (both public; `:latest` rolling, `:<sha>` pinned).
117
117
  - **Bundled binaries**: the package ships two `bin` entries — `run-local` (single-machine local
118
118
  runner) and `sema-up` (deployment bootstrap script).
119
+ - `run-local` boots the same engine in-process, runs one task from the CLI objective, prints the
120
+ result and exits — no HTTP server, no submission auth gate.
121
+ - **The deployment governance knobs apply here too** (since 7.5.0 — before that this leg assembled its
122
+ task with the governance chain absent, so the knobs were silently inert): `AUTONOMY`,
123
+ `runtime.commandPolicy` (from `config.d/governance.json`) and `SENSITIVE_WRITE_PATTERNS` are compiled
124
+ onto the local task exactly as they are on the HTTP leg, tighten-only, anchored at the
125
+ `--workspace` directory.
126
+ - **Off switches**: `SENSITIVE_WRITE_PATTERNS=off` **or** an empty value (`SENSITIVE_WRITE_PATTERNS=`)
127
+ disables the guard set; unset `AUTONOMY` (or set it to `auto`) for no extra tightening. A guard-set
128
+ value that cannot compile (e.g. a pattern with no path segment, `/`) refuses to start with a message
129
+ naming the knob, instead of failing once per task.
130
+ - **Approvals have no durable park on this leg** — a one-shot CLI has no `/v1/approvals/:id/decide` to
131
+ resume from. A gated `ask` (e.g. `AUTONOMY=ask`, which routes every shell command through approval)
132
+ is answered inline: a `y/N` prompt when stdin is a TTY, otherwise a fail-closed **deny** with a
133
+ stderr line naming the knob that produced the gate.
119
134
  - **Full-stack, one command** (DB + object store + registry web + sandbox pool; Docker and k8s
120
135
  paths): [`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy).
121
136
  - **Sandbox package sources**: default = official upstreams (pypi/npmjs/crates.io/…). For
@@ -141,7 +156,7 @@ The server is configured entirely through environment variables. The most import
141
156
  | `CONFIG_PROVIDER` | unset | Config source: `local` (file-backed `config.d/`, single machine) / `remote` (registry control plane) |
142
157
  | `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none |
143
158
  | `SANDBOX_PKG_SOURCE` | `global` | Package sources inside sandboxes: `global` (official upstreams) / `cn` (China mirrors) / `custom` / `none` |
144
- | `SENSITIVE_WRITE_PATTERNS` | core's recommended set | Sensitive-path write deny list; comma-separated value replaces the set, `off` disables |
159
+ | `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 |
145
160
  | `MANUAL_MODE_SHELL_GATE` | unset (off) | `always`\|`classify` — tighten `Bash` into the approval chain, applied unconditionally at the governance layer (≥7.1.0: independent of client permission mode, lane, or settings presence) |
146
161
  | `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 |
147
162
  | `MODEL_CONNECT_TIMEOUT_MS` | `30000` | Gateway connect timeout |
package/README.zh-CN.md CHANGED
@@ -89,13 +89,13 @@
89
89
  ```bash
90
90
  # A) npm
91
91
  npm install @sema-agent/server
92
- MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-chat \
92
+ MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-pro \
93
93
  MODEL_API_KEY=<你的-key> SERVICE_AUTH_TOKEN=<自定> \
94
94
  node node_modules/@sema-agent/server/dist/main.js # → :8090
95
95
 
96
96
  # B) 容器(零依赖,匿名可拉)
97
97
  docker run -p 8090:8090 \
98
- -e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-chat \
98
+ -e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-pro \
99
99
  -e MODEL_API_KEY=<你的-key> -e SERVICE_AUTH_TOKEN=<自定> \
100
100
  ghcr.io/sema-agent/sema-server:latest # 或 docker.io/claybobby/sema-server:latest
101
101
 
@@ -110,6 +110,17 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
110
110
  (均 public,`:latest` 滚动 / `:<sha>` 钉版)。
111
111
  - **随包二进制**:包内带两个 `bin` —— `run-local`(单机本地 runner)与
112
112
  `sema-up`(部署引导脚本)。
113
+ - `run-local` 在进程内启同一套引擎,把命令行给的目标跑完一次、打印结果、退出——没有 HTTP 服务,
114
+ 也没有提交鉴权门。
115
+ - **部署治理旋钮在这条腿上同样生效**(7.5.0 起;此前这条腿自建任务时整条治理链缺席,旋钮静默失效):
116
+ `AUTONOMY`、`runtime.commandPolicy`(来自 `config.d/governance.json`)与 `SENSITIVE_WRITE_PATTERNS`
117
+ 与 HTTP 腿同样地 tighten-only 编译进本地任务,裁决锚在 `--workspace` 目录。
118
+ - **关闸通道**:`SENSITIVE_WRITE_PATTERNS=off` **或**留空(`SENSITIVE_WRITE_PATTERNS=`)都关掉守卫集;
119
+ `AUTONOMY` 不设(或设 `auto`)= 不额外收紧。守卫集若给了**编译不出来**的值(例如 `/` 这种不含任何
120
+ 路径段的模式),启动即拒并指名旋钮,而不是每个任务炸一次。
121
+ - **这条腿没有 durable 审批 park**:一次性 CLI 没有 `/v1/approvals/:id/decide` 可赎回。被门住的 `ask`
122
+ (例如 `AUTONOMY=ask`,它把每条 shell 命令都送进审批链)当场结算:stdin 是 TTY 就 `y/N` 问人,
123
+ 否则 fail-closed **拒绝**并在 stderr 点名是哪个旋钮产的这道门。
113
124
  - **一键全栈部署**(DB + 对象存储 + registry 网站 + 沙箱池,docker/k8s 双路径):
114
125
  [`sema-agent/sema-deploy`](https://github.com/sema-agent/sema-deploy)。
115
126
  - **沙箱装包源**:缺省 = 官方源(pypi/npmjs/crates.io/…)。中国大陆部署配
@@ -134,7 +145,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
134
145
  | `CONFIG_PROVIDER` | 未设 | 配置来源:`local`(单机文件 `config.d/`)/ `remote`(registry 控制面) |
135
146
  | `DEFAULT_SCENARIO` | `code` | 请求体未指定场景时的缺省场景 |
136
147
  | `SANDBOX_PKG_SOURCE` | `global` | 沙箱内装包源:`global`(官方源)/ `cn`(国内镜像)/ `custom` / `none` |
137
- | `SENSITIVE_WRITE_PATTERNS` | core 推荐集 | 敏感路径写拒集;逗号分隔值为整体替换,`off` 关闭 |
148
+ | `SENSITIVE_WRITE_PATTERNS` | core 推荐集 | 敏感路径写拒集;逗号分隔值为整体替换,`off` **或留空**关闭。在治理层无条件施加(与客户端权限模式/lane/settings 在场性无关),`run-local` 腿同样生效;编译不出守卫集的值(如 `/`)启动即拒 |
138
149
  | `MODEL_CONNECT_TIMEOUT_MS` | `30000` | 网关连接超时 |
139
150
  | `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `120000` | 首 token 超时 |
140
151
  | `MODEL_IDLE_TIMEOUT_MS` | `300000` | 流中 idle 超时(`0` 关) |
package/USAGE.md CHANGED
@@ -302,7 +302,7 @@ QUESTION_TTL_MS=300000
302
302
  错误文案直接给出该改成什么。所以不存在"两个名字同时设"的状态,也不存在"旧名还在悄悄生效"的状态。
303
303
 
304
304
  **`LSP_ENABLED` 一分为二**:它原来同时驱动两条腿,而且两腿缺省相反——沙箱腿缺省**关**(要烤好的
305
- `sema-code-lsp` 模板),host 腿缺省**开**(只要 PATH 上有 language server,没有就优雅退回 grep/read)。
305
+ `ai-agent-code-lsp` 模板),host 腿缺省**开**(只要 PATH 上有 language server,没有就优雅退回 grep/read)。
306
306
  现在沙箱腿仍是 `LSP_ENABLED`(opt-in),host 腿归 `LSP_HOST_ENABLED`(opt-out)。
307
307
  🪦 `LSP_ENABLED=false`(拆分前唯一的 host 腿逃生舱)自 3.0.0 起是墓碑:拒启并指路 `LSP_HOST_ENABLED`。
308
308
  `LSP_ENABLED=true` 不受影响——那是沙箱腿自己的 opt-in,语义没变。
@@ -428,6 +428,42 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
428
428
 
429
429
  > **operator 鉴权(审批队列)**:`OPERATOR_PRINCIPALS=ops:alice,ops:bob`(CSV)= 谁能当 operator——列任意 owner 待办 + 决议(批/否)。**单租户部署不设=旧行为**(握 service token 即 operator,向后兼容);设了之后,非名单 principal 列待办只看自己的、且**不能决议**(403,防"请求方批自己的高危操作"绕过 F4 闸)。⚠️ **多租户形拒启**(#157-①):`DURABLE_APPROVAL=true` + `REQUIRE_PRINCIPAL=true` 而 `OPERATOR_PRINCIPALS` 空 ⇒ 进程启动失败并点名修法——否则空名单会让任一已验证租户读到其他租户的待批队列(读面 true-for-all)。设名单,或确属单租户则不设 `REQUIRE_PRINCIPAL`。
430
430
  >
431
+ > **⚠️ 引擎 core 5.19.0 起:hook-wired 部署里,parked 后台子代赎回不了(常态,不是升级窗口)。**
432
+ > 5.19.0 让一个任务的 **PreToolUse screening 面下延管辖它委派出去的子代**,于是 hook-wired 父派出的
433
+ > 子代 park 时,checkpoint 记的祖先约束层数是 **2**(screening 席 + 父自己的策略席);而本服务的赎回腿
434
+ > 重建得出的只有 **1** 层。引擎按**层数**做 pre-CAS 校验 ⇒ 每次赎回都被响亮拒
435
+ > (`resume.parent_constraint_mismatch`,checkpoint **保持 pending 不被消费**,不静默降级成更松的链)。
436
+ >
437
+ > **判断本部署在不在射程内**(任一为真即在):① `TOOL_TRACE=true` —— 它装的诊断 tracer 自带 PreToolUse 面,
438
+ > **哪怕它一条裁决都不出**(纯观察者),引擎的判据是「席位在不在」而不是「它说了什么」;
439
+ > ② 调用方提交里带 `settings.hooks.PreToolUse`(未开 `REQUIRE_PRINCIPAL` 的部署对外开放此面)。
440
+ > 两条都不沾的缺省部署**逐字旧行为**,完全不受影响。
441
+ >
442
+ > **精确的兼容矩阵**(本服务恒供 1 层;引擎只比层数,所以下表就是全部情形。已在 5.18.1 与 5.19.0 两个
443
+ > 引擎上实测过):
444
+ >
445
+ > | 挂起的是谁 | checkpoint 是哪版铸的 | 父是否 hook-wired | 行里记的层数 | 本服务供的层数 | 结果 |
446
+ > | --- | --- | --- | --- | --- | --- |
447
+ > | **第一代**子代 | ≤ 5.18.1 | 否 | 1 | 1 | ✅ 照常赎回 |
448
+ > | **第一代**子代 | ≤ 5.18.1 | **是** | 1 | 1 | ✅ 照常赎回 —— **升级本身不会弄坏存量行** |
449
+ > | **第一代**子代 | 5.19.0 | 否 | 1 | 1 | ✅ 照常赎回 |
450
+ > | **第一代**子代 | 5.19.0 | **是** | 2 | 1 | ❌ 永久拒(见下「没有恢复路径」) |
451
+ > | **嵌套**(孙代及更深) | 任意 | 任意 | **≥2** | 1 | ❌ 永久拒(5.19.0 之前就如此,本版无变化) |
452
+ >
453
+ > ⚠️ 嵌套那一行**不是恒等于 2**:引擎的子代链是「继承来的整条 + (有 hook 就加一席) + 自己那一层」逐层
454
+ > 追加,所以嵌套与 hook 叠加时记的层数会**超过** 2(有钉实测:`test/parked-revive-e2e.test.ts` 的计数锚)。
455
+ > 对本服务而言结论一样(供 1,任何 ≥2 都拒),但**别把错误文案里的那个数字当成层深的可靠读数**。
456
+ >
457
+ > ⇒ **纠正一个容易想当然的说法**:上游 CHANGELOG 写的「升级前后跨版本 drain」对本服务**不是硬要求**——
458
+ > 旧行记的就是 1、我们供的也是 1,升级方向不产生错配(反向回滚同理)。滚版前把 `GET /v1/approvals` 排空
459
+ > 仍是好习惯(减少活过开关切换的行),但它**解决不了**下面这条。
460
+ >
461
+ > **没有恢复路径,只有预防旋钮。** 层数是 park 那一刻**写死进 checkpoint** 的:事后再批一次、事后关掉
462
+ > `TOOL_TRACE`、事后回滚引擎版本,都不改行里记的 2 也不改我们供的 1 ⇒ **已经搁浅的行赎回不回来**
463
+ > (它们保持 pending 直到 TTL/reap;那次操作只能作为**新任务**重跑)。**预防旋钮 = 关掉 `TOOL_TRACE`**
464
+ > (它本就是 default-OFF 的诊断面)**,或在开着 PreToolUse hooks 的部署上不依赖后台子代的 durable 审批**
465
+ > —— 只对**此后**新铸的行生效。
466
+ >
431
467
  > **🔧 升级到 7.5.0(引擎 core 5.17.0)前:把待决审批排空。** 5.17.0 起,park 铸行按**后端能承载的
432
468
  > 宽度**落——审批人看到的 args / 预览、盘上躺着的行、resume 真正执行的那份参数,以及运维在 `/decide`
433
469
  > 上要回显的那个不透明 `boundInputHash`,都从同一份投影铸出。**本服务的两条 checkpoint 后端
@@ -55,7 +55,7 @@
55
55
  import type { AskRow, ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
56
56
  import type { BatchState } from "./approval-ask-machine.js";
57
57
  import type { CheckpointAskCandidate } from "./plugins/checkpoint-store-sql.js";
58
- import type { RunRecord } from "./plugins/store-contracts.js";
58
+ import { type RunRecord } from "./plugins/store-contracts.js";
59
59
  import type { Logger } from "./observability/logger.js";
60
60
  import type { Metrics } from "./observability/metrics.js";
61
61
  import { type DenyReason, type VoidReason } from "./approval-deny-reasons.js";
@@ -1,10 +1,11 @@
1
+ import { isTerminalRunStatus } from "./plugins/store-contracts.js";
1
2
  import { DENY_REASONS, VOID_REASONS } from "./approval-deny-reasons.js";
2
3
  import { buildRevokeFrame } from "./approval-card.js";
3
4
  import { encodeCheckpointScope } from "./security.js";
4
- /** 判据 2 的「非 suspended 终局」词表(§8 A-1;`RunRecord["status"]` = `"running" | TaskStatus`,
5
- * TaskStatus = completed|blocked|failed|suspended|needs_review)。`suspended`/`needs_review`/`running`
6
- * 都不是终局 落判据 3 保持。 */
7
- const TERMINAL_RUN_STATUS = new Set(["completed", "failed", "blocked"]);
5
+ /* 判据 2 的「非 suspended 终局」判据 = `store-contracts.ts` {@link isTerminalRunStatus}(词表属主处的
6
+ * 穷举 switch,core 加/删 `TaskStatus` 成员当场 tsc 红)。此处**不再手抄**一份 `ReadonlySet<string>`:
7
+ * 那份的门对象是 `string`,对词表增删两向都无钉,而这张词表真变过(core 5.8.0 `"timeout"`)。
8
+ * `suspended`/`needs_review`/`running` 都不是终局 落判据 3 保持。 */
8
9
  /** core 给「被取消」的 run 打的 `errorCode`(§9 C3 的取消判别键)。 */
9
10
  const CANCELLED_ERROR_CODE = "cancelled";
10
11
  /**
@@ -140,7 +141,7 @@ export function decideReconcileAction(input) {
140
141
  return { kind: "void", reason: VOID_REASONS.BATCH_BOUND_ELSEWHERE };
141
142
  }
142
143
  // ② run 终局分臂
143
- if (run !== null && TERMINAL_RUN_STATUS.has(run.status)) {
144
+ if (run !== null && isTerminalRunStatus(run.status)) {
144
145
  if (run.status === "failed" && run.errorCode === CANCELLED_ERROR_CODE) {
145
146
  return { kind: "void", reason: VOID_REASONS.RUN_CANCELLED };
146
147
  }
@@ -450,8 +450,21 @@ export async function createConfigCenterRuntime(ctx) {
450
450
  const cfgModels = (r.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
451
451
  const cfgDir = configProvider.kind === "local" ? { dir: configProvider.root } : {};
452
452
  logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels, ...cfgDir });
453
- if (configProvider.kind === "local" && cfgModels === 0)
454
- logger.warn("config_local_no_models", { ...cfgDir, hint: "config.d/models.json 空或 CONFIG_LOCAL_DIR 指错目录(选目录旋钮是 CONFIG_LOCAL_DIR,不是 LOCAL_DATA_ROOT)— 模型继续走 env 兜底" });
453
+ if (configProvider.kind === "local" && cfgModels === 0) {
454
+ // [3073]#204④ hint 真因三臂:域判形失败(文件在场却喊「空/指错」=对排障者说谎,真因在
455
+ // config_domain_invalid 里但 hint 不指路)/ 全 enabled:false / 真空缺,三态分开点名。
456
+ const modelsDomainErr = (r.domainErrors ?? []).find((de) => de.domain === "models");
457
+ const totalModels = (r.effective.models?.models ?? []).length;
458
+ logger.warn("config_local_no_models", {
459
+ ...cfgDir,
460
+ ...(modelsDomainErr ? { domainError: modelsDomainErr.error } : {}),
461
+ hint: modelsDomainErr
462
+ ? "config.d/models.json 在场但 models 域判形失败落 default(真因见本行 domainError)— 模型继续走 env 兜底"
463
+ : totalModels > 0
464
+ ? `config.d/models.json 有 ${totalModels} 条模型但全部 enabled:false — 模型继续走 env 兜底`
465
+ : "config.d/models.json 空或 CONFIG_LOCAL_DIR 指错目录(选目录旋钮是 CONFIG_LOCAL_DIR,不是 LOCAL_DATA_ROOT)— 模型继续走 env 兜底",
466
+ });
467
+ }
455
468
  }
456
469
  }
457
470
  }
@@ -365,7 +365,7 @@ export function createExecutionEnv(ctx) {
365
365
  // LSP sidecar (design/64 §13.1, the 1.86.2 seam): ONE stateless manager on RunnerDeps — core passes each
366
366
  // task's env into sessionFor at the tool mount point, so the manager reaches the SAME sandbox the agent edits
367
367
  // (no sessionId registry / per-task construction). Opt-in (LSP_ENABLED — the SANDBOX lane's knob; the host lane
368
- // below has its own, LSP_HOST_ENABLED, because its default is the opposite); with the baked `sema-code-lsp`
368
+ // below has its own, LSP_HOST_ENABLED, because its default is the opposite); with the baked `ai-agent-code-lsp`
369
369
  // template the first call is fast, without it the language server installs on first use (slow once per sandbox).
370
370
  // k8s lane (gate#2 alignment): same manager/bridge over the pod network — `ws://podIP:port` instead of E2B's
371
371
  // public wss proxy, so it needs an IN-CLUSTER worker and a sandbox image with node + the language servers baked
@@ -0,0 +1,78 @@
1
+ import { type BackgroundAgentRecord, type ToolExecuteContext } from "@sema-agent/core";
2
+ import type { ServiceConfig } from "../config.js";
3
+ import { type ApprovalBaselineConfigView, type DeploymentGovernanceConfigView, type LiveQuestionFace } from "../deployment-governance.js";
4
+ import { type SandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
5
+ /** 本腿读的配置切面(`ServiceConfig` 结构满足)。两个构造口各自的切面 + lane 判别式读的 `remoteExec`。 */
6
+ export interface ParkedReviveGateConfigView extends DeploymentGovernanceConfigView, ApprovalBaselineConfigView {
7
+ readonly remoteExec?: ServiceConfig["remoteExec"];
8
+ /** host 腿裁决 env 的 cwd 占位根(见下方 `hostGateCwd`)。 */
9
+ readonly localDataRoot?: ServiceConfig["localDataRoot"];
10
+ }
11
+ /** 会话豁免探针店的所需切面(canonical toolName 键空间;`StoreBackend.approvalExemption()` 结构满足)。 */
12
+ export interface ApprovalExemptionProbe {
13
+ has(sessionId: string, toolName: string): Promise<boolean>;
14
+ }
15
+ export interface ParkedReviveGateDeps {
16
+ /** ⚠️ **活引用**:热改字段(autonomy/commandPolicy/守卫集/审批四旋钮)每次赎回现读,见下方「禁 memoize」。 */
17
+ readonly config: ParkedReviveGateConfigView;
18
+ /** #152:活体 AskUserQuestion 面。赎回腿今天无 ALS ctx ⇒ 判 ask,与原形同判。 */
19
+ readonly question: LiveQuestionFace | undefined;
20
+ readonly approvalExemptionStore: ApprovalExemptionProbe | undefined;
21
+ readonly logger: {
22
+ info(event: string, fields?: Record<string, unknown>): void;
23
+ warn(event: string, fields?: Record<string, unknown>): void;
24
+ };
25
+ /** host 腿 cwd 占位的落地根(main.ts 的 `localRoot`;`config.localDataRoot` 优先)。 */
26
+ readonly localRoot: string;
27
+ /** 沙箱 lane 的真 env 登记簿。缺省 = 进程内唯一那只(工厂装饰器写的同一个);测试注入自建实例。 */
28
+ readonly slots?: SandboxPathEnvSlots;
29
+ }
30
+ /**
31
+ * core 1.396 的父约束席位形(`parkedResume.inheritedGate`)—— 从**载体自己的签名位推导**,绝不手写。
32
+ *
33
+ * 🔴 手写这个形状会**藏住轴**(codex 对抗复审 R1-高2 的真回归):第一版在这里手抄了一个只有
34
+ * `parentConstraints` 的接口,于是 core 载体上那个 `shellGate` 槽在类型层面根本看不见,「shellGate 轴
35
+ * 也要交回」这件事就无声地漏了一整刀。推导形让「core 又加了一条能收紧的轴」变成本仓的编译期/钉子面
36
+ * 事件(载体键集钉见 test/parked-revive-gate-wiring.test.ts)。
37
+ */
38
+ export type RebuiltInheritedGate = NonNullable<NonNullable<NonNullable<ToolExecuteContext["reviveClaim"]>["parkedResume"]>["inheritedGate"]>;
39
+ /**
40
+ * 赎回腿的父约束链重建(design/181 件二)。
41
+ *
42
+ * 语义:**同/跨副本一致地重建「部署 ⊇ 操作员」两层完整链**——审批基线(durable question 门 + F4 高危写
43
+ * 审批门 + 会话豁免探针)作 base 的 `toolPolicy` 座,经**折叠属主** `applyRuntimeGovernance`
44
+ * (→ core `tightenTaskSpec`)叠上部署治理段(autonomy / commandPolicy / MANUAL_MODE_SHELL_GATE /
45
+ * 守卫集),取其 `toolPolicy` 装进单层 `parentConstraints`。
46
+ *
47
+ * 🔴 **禁 memoize**:构造整体在 per-row lambda **内**。`autonomy`/`commandPolicy`/守卫集/审批四旋钮都是
48
+ * 热改字段(registry 热应用换 config 引用),boot 期铸一次 = 把治理冻在启动那一刻的值上,而 resume 腿的
49
+ * 书面语义是「按**当前** config 重折」(与 resolve-spec 的审批基线读活 config 同一姿势)。
50
+ *
51
+ * 🔴 **单层链是正确的响亮拒**(design/181 §2.5 特征化格):本腿恒建 count=1。嵌套子代(孙代 park,
52
+ * count≥2)的重建本腿做不到——跨副本连「祖先各层分别是什么」都没有持久化——于是 core 的 pre-CAS
53
+ * `resume.parent_constraint_mismatch` 会响亮拒绝,checkpoint 留 pending。**不许**顺手把它补齐成静默单层:
54
+ * 那等于让孙代在一条比它挂起时更松的祖先链下复活。
55
+ *
56
+ * 交回**两条轴**(载体上本仓供得出的全部,见 {@link RebuiltInheritedGate} 的推导注):
57
+ * · `parentConstraints` —— 上面那条重建链;
58
+ * · `shellGate` —— 治理段折出来的 shell 门档位。**必须交**:core 按 rank 取 `max(live, seed)`,不交
59
+ * 等于 `live` 缺席、max 恒等于 park 时的旧种子,于是「挂起期间运维收紧 AUTONOMY/MANUAL_MODE_SHELL_GATE」
60
+ * 在赎回腿上整条失效(codex R1-高2 的真回归,红先复现)。交了之后收紧被兑现、放松不被兑现(fail-safe)。
61
+ *
62
+ * 载体上**故意不供**的三个槽(都不是本腿产得出的):`ancestorRules`(会话权限规则,属会话面)、
63
+ * `admittedOrgScopes` / `orgAdmissionGoverned`(组织记忆准入,属 memory 面)—— 交一个瞎猜的值比不交更坏
64
+ * (core 对这三个都是 seed 兜底)。
65
+ *
66
+ * 时点语义(成文进 docs/ASSISTANT-WIRE-CONTRACT.md,免运维当 bug 报):
67
+ * · toolPolicy 轴 = **当前** config(与 resume 重折一致,收紧与放松都被兑现——放松是一次显式运维动作);
68
+ * · shellGate 轴 = core 取 max(live, park 时 seed)—— 收紧兑现、放松**不**兑现(fail-safe)。
69
+ * · `onAsk` 不供,但**这不等于 auto-deny**(codex R3-中1 验真纠正):带 `durableMandate` 的继承层
70
+ * 判 `ask` 时,core 只要还看得见 checkpoint 店 + durable 审批配置就把它**重新浮回 durable 门**
71
+ * (再 park 一次,人再批一次);deny 只是「durable 设施不在场」的兜底臂。⛔ 但**不是无条件**
72
+ * (codex R4-中1):core 对挂起链计 `suspendCount`,到 `maxSuspends`(默认 5)即终止任务而不再 park。
73
+ * · 客户端每请求 settings 折出来的层(`settings.permissions` 的 allow/deny/ask、模式派生的 fs-write
74
+ * ask 门、scratchpad 豁免……整类)park 时未持久化 ⇒ 重建不出。§2.3 已披露的残余,机制面有特征化钉
75
+ * (test/parked-revive-e2e.test.ts);真 settings→park→赎回的端到端钉未建(候件,随收口件一起)。
76
+ */
77
+ export declare function createParkedReviveInheritedGate(deps: ParkedReviveGateDeps): (row: BackgroundAgentRecord) => RebuiltInheritedGate;
78
+ //# sourceMappingURL=parked-revive-gate.d.ts.map
@@ -0,0 +1,114 @@
1
+ /**
2
+ * [1596]/[1597] 跨副本父约束重供席工厂 —— durable park 的**赎回腿**(design/181 件二 / #179)。
3
+ *
4
+ * 从 main.ts 的装配体里提出来的**唯一理由不是内聚,是可测**:main.ts 顶层 `void main()`,谁 import 它
5
+ * 谁就启动一整个服务,所以那条腿的**运行期**语义在原地一格都钉不住(此前只有源码字符串钉)。提出来之后
6
+ * 「重建出来的链到底裁决成什么样」变成可断言的事实(test/parked-revive-gate-wiring.test.ts 运行期钉 +
7
+ * 治理矩阵的第三维消费腿)。装配次序契约不变:main.ts 仍在同一位置构造,构造条件逐字保持。
8
+ */
9
+ import { join } from "node:path";
10
+ import { NodeExecutionEnv } from "@sema-agent/core";
11
+ import { createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "../deployment-governance.js";
12
+ import { applyRuntimeGovernance } from "../runtime-governance.js";
13
+ import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
14
+ /**
15
+ * host 腿裁决 env 的 cwd:**从不创建**的占位目录(resolve-spec 的 `fs-write-gate-unrooted` 同族)。
16
+ *
17
+ * 赎回腿拿不到子代的工作目录(park 时未持久化,design/181 §2.2 的 v2 候件票),而 `NodeExecutionEnv`
18
+ * 的构造要求一个 cwd。占位形的语义:**绝对形写目标照常按真身裁决**(cwd 与它们无关),相对形则在
19
+ * 一个不存在的根上 canonicalize —— 段匹配照旧命中(守卫段是路径**段**判据),方向 fail-closed。
20
+ * 真正兜住相对形的是构造口内的 `RelativeTargetLexicalEnv` 词法臂(`PathAdjudication.cwd` 缺席即自动补层)。
21
+ * ⚠️ core 5.19.0(#108)后这句话的射程变窄了:守卫改按 `ToolCallRequest.cwd ?? rootPath` 解析写目标,
22
+ * 引擎盖了戳的调用**根本走不到**本占位根(戳优先),词法臂也随之让位给真身裁决(全文见
23
+ * `deployment-governance.ts` 的 `RelativeTargetLexicalEnv` 类注)。本占位根今天只在缺戳形上承重,
24
+ * 语义不变、方向仍 fail-closed,故保留不动。
25
+ * 绝不回落 `process.cwd()`:那是**服务自己的**启动目录,把它当成子代的盘是拿错基准(resolve-spec W4 同案)。
26
+ */
27
+ function hostGateCwd(config, localRoot) {
28
+ return join(config.localDataRoot ?? localRoot, "parked-revive-unrooted");
29
+ }
30
+ /**
31
+ * 赎回腿的父约束链重建(design/181 件二)。
32
+ *
33
+ * 语义:**同/跨副本一致地重建「部署 ⊇ 操作员」两层完整链**——审批基线(durable question 门 + F4 高危写
34
+ * 审批门 + 会话豁免探针)作 base 的 `toolPolicy` 座,经**折叠属主** `applyRuntimeGovernance`
35
+ * (→ core `tightenTaskSpec`)叠上部署治理段(autonomy / commandPolicy / MANUAL_MODE_SHELL_GATE /
36
+ * 守卫集),取其 `toolPolicy` 装进单层 `parentConstraints`。
37
+ *
38
+ * 🔴 **禁 memoize**:构造整体在 per-row lambda **内**。`autonomy`/`commandPolicy`/守卫集/审批四旋钮都是
39
+ * 热改字段(registry 热应用换 config 引用),boot 期铸一次 = 把治理冻在启动那一刻的值上,而 resume 腿的
40
+ * 书面语义是「按**当前** config 重折」(与 resolve-spec 的审批基线读活 config 同一姿势)。
41
+ *
42
+ * 🔴 **单层链是正确的响亮拒**(design/181 §2.5 特征化格):本腿恒建 count=1。嵌套子代(孙代 park,
43
+ * count≥2)的重建本腿做不到——跨副本连「祖先各层分别是什么」都没有持久化——于是 core 的 pre-CAS
44
+ * `resume.parent_constraint_mismatch` 会响亮拒绝,checkpoint 留 pending。**不许**顺手把它补齐成静默单层:
45
+ * 那等于让孙代在一条比它挂起时更松的祖先链下复活。
46
+ *
47
+ * 交回**两条轴**(载体上本仓供得出的全部,见 {@link RebuiltInheritedGate} 的推导注):
48
+ * · `parentConstraints` —— 上面那条重建链;
49
+ * · `shellGate` —— 治理段折出来的 shell 门档位。**必须交**:core 按 rank 取 `max(live, seed)`,不交
50
+ * 等于 `live` 缺席、max 恒等于 park 时的旧种子,于是「挂起期间运维收紧 AUTONOMY/MANUAL_MODE_SHELL_GATE」
51
+ * 在赎回腿上整条失效(codex R1-高2 的真回归,红先复现)。交了之后收紧被兑现、放松不被兑现(fail-safe)。
52
+ *
53
+ * 载体上**故意不供**的三个槽(都不是本腿产得出的):`ancestorRules`(会话权限规则,属会话面)、
54
+ * `admittedOrgScopes` / `orgAdmissionGoverned`(组织记忆准入,属 memory 面)—— 交一个瞎猜的值比不交更坏
55
+ * (core 对这三个都是 seed 兜底)。
56
+ *
57
+ * 时点语义(成文进 docs/ASSISTANT-WIRE-CONTRACT.md,免运维当 bug 报):
58
+ * · toolPolicy 轴 = **当前** config(与 resume 重折一致,收紧与放松都被兑现——放松是一次显式运维动作);
59
+ * · shellGate 轴 = core 取 max(live, park 时 seed)—— 收紧兑现、放松**不**兑现(fail-safe)。
60
+ * · `onAsk` 不供,但**这不等于 auto-deny**(codex R3-中1 验真纠正):带 `durableMandate` 的继承层
61
+ * 判 `ask` 时,core 只要还看得见 checkpoint 店 + durable 审批配置就把它**重新浮回 durable 门**
62
+ * (再 park 一次,人再批一次);deny 只是「durable 设施不在场」的兜底臂。⛔ 但**不是无条件**
63
+ * (codex R4-中1):core 对挂起链计 `suspendCount`,到 `maxSuspends`(默认 5)即终止任务而不再 park。
64
+ * · 客户端每请求 settings 折出来的层(`settings.permissions` 的 allow/deny/ask、模式派生的 fs-write
65
+ * ask 门、scratchpad 豁免……整类)park 时未持久化 ⇒ 重建不出。§2.3 已披露的残余,机制面有特征化钉
66
+ * (test/parked-revive-e2e.test.ts);真 settings→park→赎回的端到端钉未建(候件,随收口件一起)。
67
+ */
68
+ export function createParkedReviveInheritedGate(deps) {
69
+ const { config, question, approvalExemptionStore, logger, localRoot } = deps;
70
+ const slots = deps.slots ?? sandboxPathEnvSlots;
71
+ return (row) => {
72
+ // lane 分形与 resolve-spec 同一判别式(单一属主,取值处只此一个)。
73
+ // 🔑 沙箱腿的 slot 键 = **row.sessionId**(子代自己的会话,design/181 §2.2 裁定):revive 后 core 以
74
+ // 该 sessionId 铸 env 并注册 slot(subagent → prepare-task → 工厂装饰器),这个键真能绑上。
75
+ // 键 rootSessionId 跨副本恒 unbound ⇒ 每一次结构化写 fail-closed ⇒ 整条腿瘫痪。
76
+ // 未绑时转发面报错 → core 读成 unresolvedSymlink → 守卫策略 deny(成文的 fail-closed 行为)。
77
+ const pathAdjudication = isSandboxPathAdjudicationLane(config.remoteExec?.provider)
78
+ ? {
79
+ env: new DeferredSandboxPathEnv({
80
+ sessionId: row.sessionId,
81
+ slots,
82
+ logger: { warn: (event, fields) => logger.warn(event, { ...(fields ?? {}), lane: config.remoteExec?.provider, leg: "parked_revive" }) },
83
+ }),
84
+ }
85
+ : { env: createHostAdjudicationEnv(config, localRoot) };
86
+ // 审批基线:durable 轴(问答门 + F4 审批门 + 会话豁免探针)。exempt 锚 = row.rootSessionId
87
+ // ——与 /decide 的 remember grant 同键([1591] 候裁②),重建链上的豁免语义与原链一致。
88
+ const rootSessionId = row.rootSessionId;
89
+ const baseline = createApprovalBaselinePolicy(config, {
90
+ question,
91
+ ...(approvalExemptionStore && rootSessionId
92
+ ? {
93
+ exempt: (toolName) => approvalExemptionStore.has(rootSessionId, toolName),
94
+ onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: rootSessionId, toolName, rawToolName, leg: "parked_revive" }),
95
+ }
96
+ : {}),
97
+ });
98
+ // 折叠属主 = applyRuntimeGovernance → core tightenTaskSpec(本腿一条 policy 都不自己合成)。
99
+ // `objective: ""` 只是折叠所需的最小 TaskSpec 座——本腿只取回 toolPolicy,spec 的其余面不出本函数。
100
+ const governed = applyRuntimeGovernance({ objective: "", toolPolicy: baseline }, createDeploymentGovernanceInputs(config, pathAdjudication));
101
+ // 治理段无可施加时 applyRuntimeGovernance 原样返回 base ⇒ 基线本身即为链(恒非 undefined,无需断言)。
102
+ // shellGate 只在治理段真产出档位时出现在载体上(缺席 = 本次没有 live 值可交,core 用 seed 兜底)。
103
+ return {
104
+ parentConstraints: [{ policy: governed.toolPolicy ?? baseline, durableMandate: true }],
105
+ ...(governed.shellGate !== undefined ? { shellGate: governed.shellGate } : {}),
106
+ };
107
+ };
108
+ }
109
+ /** host 腿的裁决 env(真 fs;构造是纯字段赋值,不 spawn)。**每次赎回现铸**——与「禁 memoize」同因:
110
+ * cwd 占位读的是活 config 的 `localDataRoot`。 */
111
+ function createHostAdjudicationEnv(config, localRoot) {
112
+ return new NodeExecutionEnv({ cwd: hostGateCwd(config, localRoot) });
113
+ }
114
+ //# sourceMappingURL=parked-revive-gate.js.map
@@ -1,9 +1,10 @@
1
- import { type MemoryBackend, type RunnerDeps, type ToolPolicy } from "@sema-agent/core";
1
+ import { type MemoryBackend, type RunnerDeps } from "@sema-agent/core";
2
2
  import type { PromptsDomainFaces } from "../prompts-domain-validate.js";
3
3
  import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilities/select-environment-tool.js";
4
4
  import { sendUserFileTool } from "../capabilities/send-user-file-tool.js";
5
5
  import { selectScenario } from "../capabilities/scenarios.js";
6
6
  import type { ServiceConfig } from "../config.js";
7
+ import { type LiveQuestionFace } from "../deployment-governance.js";
7
8
  import { FleetEventBus } from "../fleet/fleet-bus.js";
8
9
  import { type HookLlmCall } from "../hooks/hook-runner.js";
9
10
  import type { createHookLlm } from "../hooks/hook-llm.js";
@@ -58,9 +59,7 @@ export interface ResolveSpecCtx {
58
59
  /** #152 ([2703] 案二):活体 AskUserQuestion 面(QuestionCoordinator 的判决探针切面)。在场(=
59
60
  * ASK_QUESTION_ENABLED)时 durable question 门按活流上下文分腿、spec 不再 stamp QUESTION_AWAITS_RESUME
60
61
  * (否则 spec.onQuestion 恒遮蔽 RunnerDeps.onQuestion 的活人腿);缺席时行为与旧形逐字一致。 */
61
- liveQuestionFace: {
62
- hasLiveContext(): boolean;
63
- } | undefined;
62
+ liveQuestionFace: LiveQuestionFace | undefined;
64
63
  approvalExemptionStore: ReturnType<StoreBackend["approvalExemption"]> | undefined;
65
64
  singleUserAutoAcceptBaseline: boolean;
66
65
  checkpointStore: ReturnType<NonNullable<StoreBackend["checkpoint"]>> | undefined;
@@ -69,22 +68,6 @@ export interface ResolveSpecCtx {
69
68
  perTaskImage: PerTaskImageRegistry;
70
69
  sessionEnvSelection: SessionEnvironmentSelection;
71
70
  }
72
- /** #152([2703] 案二):durable 部署上的 AskUserQuestion 门。活体面(QuestionCoordinator)缺席 ⇒ 原形
73
- * `createDurableQuestionPolicy()`(恒 ask ⇒ 恒 durable park)。在场 ⇒ **判决时**按活流上下文分腿:
74
- * 活流腿(bg/SSE,coordinator.runWithContext 包裹且投递面此刻可达,ALS 判)allow——工具执行落到
75
- * RunnerDeps.onQuestion 的 coordinator,问正在 tail 流的活人;无活流腿(sync /v1/tasks、verify/cascade、
76
- * durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留(#166 后无活流腿放行执行
77
- * 也不会产出空答:coordinator 无 ALS ctx ⇒ 冻结 `{kind:"unavailable"}`(src/question.ts),永不悬挂;
78
- * park 仍是把问题送到人面前的唯一那条腿,正当性不变、只是反事实前提换了)。
79
- * 工具名字面量与 server.ts 的 pre-CAS 守卫同源("AskUserQuestion",core 未根导出常量)。
80
- * ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
81
- * 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
82
- * 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
83
- * 自折 core 原形——该腿无活体问答面可装且 leader 不 import boot 层(分层),core 原形+sentinel 即其完整
84
- * 语义;豁免注在彼处互指,接活体面之日必须并回本工厂。 */
85
- export declare function createDurableQuestionGate(live: {
86
- hasLiveContext(): boolean;
87
- } | undefined): ToolPolicy;
88
71
  export declare function createResolveSpec(ctx: ResolveSpecCtx): ServiceDeps["resolveSpec"];
89
72
  export {};
90
73
  //# sourceMappingURL=resolve-spec.d.ts.map