@sema-agent/server 7.142.0 → 7.144.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 (301) hide show
  1. package/MIGRATION.md +608 -0
  2. package/USAGE.md +63 -36
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +7 -5
  5. package/dist/approval-ask-audit-store.js +12 -6
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +155 -39
  9. package/dist/approval-card.js +111 -31
  10. package/dist/approval-queue-kind.d.ts +27 -0
  11. package/dist/approval-queue-kind.js +6 -0
  12. package/dist/approval-reconciler.d.ts +8 -8
  13. package/dist/approval-reconciler.js +3 -3
  14. package/dist/audit.js +4 -2
  15. package/dist/boot/budget-tracing.d.ts +1 -1
  16. package/dist/boot/execution-env.js +1 -1
  17. package/dist/boot/governance-seams.d.ts +26 -41
  18. package/dist/boot/governance-seams.js +61 -28
  19. package/dist/boot/late-binds.d.ts +7 -4
  20. package/dist/boot/late-binds.js +3 -1
  21. package/dist/boot/leader.js +2 -4
  22. package/dist/boot/memory-consolidation.d.ts +0 -8
  23. package/dist/boot/memory-consolidation.js +3 -3
  24. package/dist/boot/reapers.js +5 -4
  25. package/dist/boot/resolve-spec.d.ts +1 -1
  26. package/dist/boot/resolve-spec.js +8 -8
  27. package/dist/boot/runner-deps.d.ts +14 -6
  28. package/dist/boot/runner-deps.js +6 -17
  29. package/dist/boot/shutdown.d.ts +9 -0
  30. package/dist/boot/shutdown.js +18 -2
  31. package/dist/boot/stage-01-config.js +2 -0
  32. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  33. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  34. package/dist/boot/stage-03-store-backend.js +1 -2
  35. package/dist/boot/stage-04-stores.d.ts +5 -2
  36. package/dist/boot/stage-04-stores.js +3 -5
  37. package/dist/boot/stage-05-execution-env.d.ts +5 -2
  38. package/dist/boot/stage-06-runners.d.ts +5 -2
  39. package/dist/boot/stage-06-runners.js +1 -3
  40. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  41. package/dist/boot/stage-07-capability-layer.js +6 -2
  42. package/dist/boot/stage-08-reapers.d.ts +5 -2
  43. package/dist/boot/stage-09-leader.d.ts +5 -2
  44. package/dist/boot/stage-10-http-server.d.ts +5 -2
  45. package/dist/boot/stage-10-http-server.js +4 -1
  46. package/dist/boot/stores.d.ts +5 -2
  47. package/dist/boot/stores.js +7 -7
  48. package/dist/boot/web-admin.d.ts +5 -1
  49. package/dist/boot/web-admin.js +31 -19
  50. package/dist/capabilities/hands-lane.d.ts +9 -39
  51. package/dist/capabilities/hands-lane.js +8 -13
  52. package/dist/capabilities/mcp-probe.d.ts +3 -2
  53. package/dist/capabilities/scenarios.d.ts +23 -17
  54. package/dist/capabilities/scenarios.js +2 -2
  55. package/dist/capabilities/skills.d.ts +1 -17
  56. package/dist/capabilities/skills.js +0 -8
  57. package/dist/capability-facts.d.ts +8 -8
  58. package/dist/capability-facts.js +4 -4
  59. package/dist/card-void.d.ts +6 -9
  60. package/dist/config-catalog.js +1 -1
  61. package/dist/config-d-migration.d.ts +2 -0
  62. package/dist/config-dir-trust.js +58 -29
  63. package/dist/config-invariants.d.ts +7 -1
  64. package/dist/config-invariants.js +7 -0
  65. package/dist/config-provider.d.ts +10 -0
  66. package/dist/config-provider.js +15 -4
  67. package/dist/config-root-migration.d.ts +13 -0
  68. package/dist/config-root-migration.js +99 -0
  69. package/dist/config-types.d.ts +5 -5
  70. package/dist/config.d.ts +4 -3
  71. package/dist/config.js +1 -1
  72. package/dist/delegated-session-owner.d.ts +35 -151
  73. package/dist/delegated-session-owner.js +1 -83
  74. package/dist/device-ws-protocol.d.ts +1 -0
  75. package/dist/device-ws-protocol.js +2 -1
  76. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  77. package/dist/env-model-table-gate.d.ts +53 -0
  78. package/dist/env-model-table-gate.js +53 -0
  79. package/dist/execution-placement.d.ts +7 -4
  80. package/dist/fleet/fleet-bus.d.ts +28 -52
  81. package/dist/fleet/fleet-bus.js +35 -100
  82. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  83. package/dist/fleet/fleet-reconciler.js +1 -1
  84. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  85. package/dist/fleet/subagent-tail-bus.js +15 -28
  86. package/dist/hooks/classic-stations.d.ts +3 -1
  87. package/dist/hooks/classic-stations.js +2 -0
  88. package/dist/hooks/hook-runner.js +28 -7
  89. package/dist/host-decision.d.ts +54 -20
  90. package/dist/host-decision.js +42 -14
  91. package/dist/http/admission.d.ts +1 -1
  92. package/dist/http/admission.js +7 -3
  93. package/dist/http/approval-sweeps.js +11 -8
  94. package/dist/http/caller-trust.d.ts +1 -1
  95. package/dist/http/durable-run-leg.js +2 -2
  96. package/dist/http/engine-refusal.d.ts +16 -0
  97. package/dist/http/engine-refusal.js +17 -0
  98. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  99. package/dist/http/memory-compliance-failure-reply.js +4 -6
  100. package/dist/http/owner-access.d.ts +11 -0
  101. package/dist/http/owner-access.js +13 -0
  102. package/dist/http/principal-gate.d.ts +1 -1
  103. package/dist/http/refusal-body.d.ts +7 -3
  104. package/dist/http/refusal-body.js +1 -0
  105. package/dist/http/resume-legs.d.ts +21 -7
  106. package/dist/http/resume-legs.js +82 -53
  107. package/dist/http/resume-restatement.d.ts +39 -0
  108. package/dist/http/resume-restatement.js +25 -0
  109. package/dist/http/route-ctx.d.ts +35 -10
  110. package/dist/http/route-ctx.js +1 -1
  111. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  112. package/dist/http/routes/approvals-assistant.js +143 -31
  113. package/dist/http/routes/capabilities.js +11 -2
  114. package/dist/http/routes/memory-policy.d.ts +2 -2
  115. package/dist/http/routes/memory-policy.js +1 -1
  116. package/dist/http/routes/notify-wake.js +23 -25
  117. package/dist/http/routes/rules.js +48 -13
  118. package/dist/http/routes/runs.js +9 -8
  119. package/dist/http/routes/session-memory-sync.d.ts +34 -6
  120. package/dist/http/routes/session-memory-sync.js +27 -10
  121. package/dist/http/routes/session-sync.js +13 -20
  122. package/dist/http/routes/sessions.d.ts +2 -2
  123. package/dist/http/routes/sessions.js +22 -23
  124. package/dist/http/routes/tasks.js +27 -49
  125. package/dist/http/server.d.ts +13 -7
  126. package/dist/http/server.js +2 -2
  127. package/dist/http/stored-body-replay.js +15 -4
  128. package/dist/http/wire-types.d.ts +17 -7
  129. package/dist/index.d.ts +1 -0
  130. package/dist/index.js +1 -0
  131. package/dist/lane-home-probe.d.ts +15 -0
  132. package/dist/lane-home-probe.js +4 -0
  133. package/dist/leader/leader.js +5 -2
  134. package/dist/leader/wire.d.ts +7 -1
  135. package/dist/leader/wire.js +6 -5
  136. package/dist/leg-facts.d.ts +37 -0
  137. package/dist/leg-facts.js +15 -0
  138. package/dist/memory-bundle-engine.d.ts +1 -4
  139. package/dist/memory-bundle-engine.js +2 -2
  140. package/dist/memory-operator-faces.d.ts +30 -66
  141. package/dist/memory-operator-faces.js +17 -88
  142. package/dist/memory-scope-stores.d.ts +38 -40
  143. package/dist/memory-scope-stores.js +51 -36
  144. package/dist/memory-scope.d.ts +0 -9
  145. package/dist/memory-scope.js +0 -9
  146. package/dist/memory-sync-client.js +14 -3
  147. package/dist/memory-sync.d.ts +20 -4
  148. package/dist/memory-sync.js +23 -4
  149. package/dist/model-plane-face.d.ts +13 -7
  150. package/dist/model-plane-face.js +2 -1
  151. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  152. package/dist/observability/corrupt-read-seat.js +1 -2
  153. package/dist/observability/fail-open.d.ts +30 -10
  154. package/dist/observability/fail-open.js +27 -10
  155. package/dist/observability/run-terminal.d.ts +1 -1
  156. package/dist/observability/run-terminal.js +6 -2
  157. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  158. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  159. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  160. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  161. package/dist/orchestration/workflow-park-decision.js +5 -3
  162. package/dist/org-delivery-gate.d.ts +11 -0
  163. package/dist/org-delivery-gate.js +19 -1
  164. package/dist/parked-decide.d.ts +13 -9
  165. package/dist/parked-decide.js +7 -7
  166. package/dist/parked-revive-surface.d.ts +6 -7
  167. package/dist/parked-revive-surface.js +1 -1
  168. package/dist/peer-inbound-face.d.ts +122 -0
  169. package/dist/peer-inbound-face.js +123 -0
  170. package/dist/permission-rule-list.d.ts +13 -9
  171. package/dist/permission-rule-list.js +7 -3
  172. package/dist/plugins/approval-ask-store-file.js +18 -6
  173. package/dist/plugins/approval-ask-store-memory.js +8 -6
  174. package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
  175. package/dist/plugins/approval-ask-store-sql.js +30 -11
  176. package/dist/plugins/caching-session-store.d.ts +0 -3
  177. package/dist/plugins/caching-session-store.js +0 -3
  178. package/dist/plugins/checkpoint-store-sql.d.ts +45 -38
  179. package/dist/plugins/checkpoint-store-sql.js +108 -45
  180. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  181. package/dist/plugins/file-history-store-sql.js +24 -19
  182. package/dist/plugins/file-run-store.d.ts +29 -0
  183. package/dist/plugins/file-run-store.js +97 -8
  184. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  185. package/dist/plugins/fork-routing-session-store.js +6 -0
  186. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  187. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  188. package/dist/plugins/local-checkpoint-store.d.ts +6 -8
  189. package/dist/plugins/local-checkpoint-store.js +5 -4
  190. package/dist/plugins/local-session-store.d.ts +69 -30
  191. package/dist/plugins/local-session-store.js +133 -57
  192. package/dist/plugins/local-session-window.d.ts +4 -2
  193. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  194. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  195. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  196. package/dist/plugins/mailbox-store-sql.js +76 -7
  197. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  198. package/dist/plugins/memory-sync-store-pg.js +21 -0
  199. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  200. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  201. package/dist/plugins/one-time-migrations.d.ts +44 -14
  202. package/dist/plugins/one-time-migrations.js +118 -15
  203. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  204. package/dist/plugins/org-delivery-quota-store.js +2 -12
  205. package/dist/plugins/park-commit-watch.d.ts +1 -1
  206. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  207. package/dist/plugins/permission-rule-store-file.js +15 -2
  208. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  209. package/dist/plugins/permission-rule-store-sql.js +35 -2
  210. package/dist/plugins/pg-session-storage.d.ts +1 -9
  211. package/dist/plugins/pg-session-storage.js +2 -19
  212. package/dist/plugins/remote-env-device.d.ts +8 -11
  213. package/dist/plugins/remote-env-device.js +6 -3
  214. package/dist/plugins/remote-env-host.d.ts +8 -4
  215. package/dist/plugins/remote-env-host.js +7 -9
  216. package/dist/plugins/remote-env-k8s.d.ts +37 -24
  217. package/dist/plugins/remote-env-k8s.js +50 -17
  218. package/dist/plugins/remote-shell.js +12 -2
  219. package/dist/plugins/retention-store-sql.d.ts +2 -2
  220. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  221. package/dist/plugins/sandbox-read-floor.js +2 -7
  222. package/dist/plugins/schema-consistency.d.ts +5 -1
  223. package/dist/plugins/session-fork.d.ts +1 -1
  224. package/dist/plugins/session-ownership.d.ts +9 -0
  225. package/dist/plugins/session-ownership.js +6 -0
  226. package/dist/plugins/session-store.d.ts +13 -16
  227. package/dist/plugins/session-store.js +9 -10
  228. package/dist/plugins/sql-errors.d.ts +10 -1
  229. package/dist/plugins/sql-errors.js +13 -0
  230. package/dist/plugins/sql-json-column.d.ts +24 -2
  231. package/dist/plugins/sql-json-column.js +38 -3
  232. package/dist/plugins/store-backend.d.ts +23 -28
  233. package/dist/plugins/store-backend.js +12 -3
  234. package/dist/plugins/store-contracts.d.ts +29 -5
  235. package/dist/plugins/store-contracts.js +4 -1
  236. package/dist/plugins/task-attachment-store.d.ts +2 -2
  237. package/dist/plugins/task-attachment-store.js +37 -52
  238. package/dist/plugins/tidb-session-store.d.ts +2 -14
  239. package/dist/plugins/tidb-session-store.js +2 -20
  240. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  241. package/dist/plugins/tool-result-store-sql.js +23 -2
  242. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  243. package/dist/plugins/wiring-section-codec.js +55 -0
  244. package/dist/plugins/workspace-fill.d.ts +1 -1
  245. package/dist/rule-entry-list.d.ts +3 -2
  246. package/dist/rule-entry-list.js +5 -1
  247. package/dist/rules-consent.d.ts +28 -5
  248. package/dist/rules-consent.js +40 -28
  249. package/dist/run-local.js +11 -6
  250. package/dist/runs.d.ts +12 -8
  251. package/dist/runs.js +10 -7
  252. package/dist/runtime-governance.d.ts +1 -1
  253. package/dist/security.d.ts +0 -4
  254. package/dist/server-secret-env.d.ts +4 -0
  255. package/dist/session-error-wire.d.ts +34 -23
  256. package/dist/session-error-wire.js +8 -3
  257. package/dist/session-incarnation.d.ts +3 -3
  258. package/dist/session-notify-door.d.ts +106 -0
  259. package/dist/session-notify-door.js +97 -0
  260. package/dist/session-sync.d.ts +9 -14
  261. package/dist/session-sync.js +5 -5
  262. package/dist/session-workspace.d.ts +3 -13
  263. package/dist/session-workspace.js +0 -9
  264. package/dist/standing-answer-grant.d.ts +5 -0
  265. package/dist/standing-answer-grant.js +10 -0
  266. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  267. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  268. package/dist/task-mcp.d.ts +1 -1
  269. package/dist/task-settings.d.ts +11 -8
  270. package/dist/task-settings.js +31 -11
  271. package/dist/tool-approval.d.ts +55 -35
  272. package/dist/tool-approval.js +112 -74
  273. package/dist/trace/core-keyset-guard.d.ts +12 -9
  274. package/dist/trace/engine-notice-wire.d.ts +1 -1
  275. package/dist/trace/engine-notice-wire.js +4 -2
  276. package/dist/trace/exit-seat.d.ts +7 -1
  277. package/dist/trace/exit-seat.js +1 -0
  278. package/dist/trace/ledger-sink.d.ts +2 -2
  279. package/dist/trace/ledger-sink.js +12 -4
  280. package/dist/trace/model-segment.d.ts +74 -25
  281. package/dist/trace/model-segment.js +141 -32
  282. package/dist/trace/project.d.ts +15 -6
  283. package/dist/trace/project.js +12 -4
  284. package/dist/trace/redact.d.ts +72 -9
  285. package/dist/trace/redact.js +1509 -471
  286. package/dist/trace/self-minted-wire.d.ts +598 -47
  287. package/dist/trace/self-minted-wire.js +190 -20
  288. package/dist/trace/wire-projection-faces.d.ts +1 -1
  289. package/dist/trace/wire-projection-faces.js +1 -1
  290. package/dist/verifier-pause-card.d.ts +33 -5
  291. package/dist/verifier-pause-card.js +15 -10
  292. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  293. package/dist/web-admin-client/apply-effective.js +90 -10
  294. package/dist/workspace-space.d.ts +19 -3
  295. package/dist/workspace-space.js +8 -4
  296. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  297. package/package.json +3 -2
  298. package/wire-changes/server@7.143.0.json +1167 -0
  299. package/wire-changes/server@7.144.0.json +291 -0
  300. package/dist/session-records-skipped-notice.d.ts +0 -21
  301. package/dist/session-records-skipped-notice.js +0 -27
@@ -18,6 +18,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
18
18
  import type { HostDecision } from "../host-decision.js";
19
19
  import type { EntryProblem } from "../entry-problems.js";
20
20
  import type { SessionOwnerCell } from "../security.js";
21
+ import type { SessionNotifyDoor } from "../session-notify-door.js";
21
22
  import type { TaskStream, TaskSpec, TaskResult, QuestionAnswer, Checkpoint, CheckpointToken, ResumeOutcome, RunInternals, Runner, EngineNotice } from "@sema-agent/core";
22
23
  import type { FlatServiceDeps, RequestAuth } from "./server.js";
23
24
  import type { TaskRequestBody, DecideBinding } from "./wire-types.js";
@@ -34,6 +35,7 @@ import type { RunIdentity } from "../identity-source.js";
34
35
  import type { PeerLease } from "../peer-seat.js";
35
36
  import type { RootedTaskSpec } from "./execution-context.js";
36
37
  import type { GateCommitment, GateSettlementEvidence } from "../gate-settlement.js";
38
+ import type { ResumeRestatement } from "./resume-restatement.js";
37
39
  /** 跨域可变状态 = lens1 §C2 实测的那 8 条「运行期登记簿」,一个对象。
38
40
  * [ref]:`cancelledViaVerb`(按 taskId 键控的「这次是取消动词」旁表)删除 —— 中止成因改挂在每条腿自己的
39
41
  * {@link RunAbort} 句柄上(`inflightRuns` 的值),同寿同删,不再有一张与腿不同寿的旁表。 */
@@ -123,6 +125,11 @@ export interface PreparedTaskSubmission {
123
125
  /** [ref]:本次提交的执行 Runner —— 场景 hands=none 时是无手孪生(不挂 executionEnvFactory)。
124
126
  * 两个消费域一律用它,不再直接摸 `deps.runner`(那正是「场景表态了、执行点没听」的缝)。 */
125
127
  runner: Runner;
128
+ /**
129
+ * 这条腿的**部署事实**(执行车道 · run 技能名册 · 受理见证;`leg-facts.ts`):漏斗在复制之前按装配输出读出的那一份,此后作普通值走 ——
130
+ * 不在 spec 上(引擎的 TaskSpec 一读门拒任何非引擎成员)。缺席 = 嵌入方自带的装配器没铸(名册段 / 会话行记录座随之缺席;车道已在选路时响亮拒)。
131
+ */
132
+ legFacts: import("../leg-facts.js").LegFacts | undefined;
126
133
  /** authorizer 交出的身份与根(必填席,恒在;续跑家族的内部车道交它从 checkpoint 重建的那一只)。 */
127
134
  auth: RequestAuth;
128
135
  verify?: VerifyRoundsSpec;
@@ -257,9 +264,10 @@ export interface DriveResumeArgs {
257
264
  *
258
265
  * 🔴 写失败**响亮但不致命**(codex r1 两条 [high] 验真后定):core 的 `resumeStream` 是 **EAGER** 的 ——
259
266
  * 写点这一拍 run 已经在执行,裸抛会把一条活腿判成 failed + 释放 claim 而执行还在继续。⇒ 一个臂、
260
- * 四条腿同守:计数 `resume_ctx_not_persisted_total` + 一行 warn,本次续跑照常。方向论证(不是 [ref] 的
261
- * 静默放宽):写失败 ⇒ 耐久体保持**上一次**的值,而这四条腿里只有 `drivePlanReview` 改写这个体、
262
- * 且只会**放宽** ⇒ 落盘的恒不比本次续跑更宽。旧的 `plan_approve_mode_not_persisted`(plan 专用)随本批删除,
267
+ * 四条腿同守:计数 `resume_ctx_not_persisted_total` + 一行 warn,本次续跑照常。写失败 ⇒ 耐久体保持**上一次**的值:
268
+ * 模式词那一格(`drivePlanReview` 只会放宽)落盘的恒不比本次续跑更宽;续跑请求的重述键(`resume-restatement.ts`)
269
+ * 可能收窄对端表,写失败时落盘的仍是上一份表 ⇒ 下一次**不带重述**的续跑按上一份重建 —— 这一形是收不紧的,所以
270
+ * 那一行 warn 如实点名它(响亮,不静默)。旧的 `plan_approve_mode_not_persisted`(plan 专用)随 [ref] 删除,
263
271
  * 由上面这只家族级的取代。全文见 `resume-legs.ts` 的 `commitResumeCtx` 顶注。
264
272
  */
265
273
  resumeCtx: {
@@ -274,6 +282,8 @@ export interface DriveResumeArgs {
274
282
  /** [ref]:续跑腿的执行 Runner。resume 会用同一条 resolveSpec 重解析场景,故与首跑选同一只 —— 否则
275
283
  * 一次续跑就把手带 band 重新 mount 回来(收窄只在首跑成立 = 没成立)。 */
276
284
  runner: Runner;
285
+ /** 这条腿的部署事实(`leg-facts.ts`):续跑漏斗在复制之前读出的那一份(与 {@link PreparedTaskSubmission.legFacts} 同一律)。读者 = 本腿的 `wiring_manifest.skills` 段与会话行记录座。 */
286
+ legFacts: import("../leg-facts.js").LegFacts | undefined;
277
287
  sessionId: string;
278
288
  principal: string | undefined;
279
289
  fleetScope: string;
@@ -418,7 +428,7 @@ export interface RouteLegs {
418
428
  /** [ref] 件C:**腿身份**(不是能力表态)。`liveStream: true` 只由 `POST /v1/tasks/stream` 的装配点按
419
429
  * URL 传 —— 那是全树唯一挂 HITL 活流投递面的腿,逐腿 `interactionPosture` 判据的第一条合取项。
420
430
  * 缺席 ⇒ 无人腿(bg / A2A / resume),行为字节不变。 */
421
- legFacts?: {
431
+ legRoute?: {
422
432
  liveStream?: boolean;
423
433
  }): Promise<PreparedTaskSubmission | null>;
424
434
  /** 同步腿的终局记账(计费/配额/指标),tasks 域用。 */
@@ -429,7 +439,10 @@ export interface RouteLegs {
429
439
  * 「漏报出处」变成编译错,不是一条下游把「没人答」渲染成「有人拒」的静默假出处。
430
440
  * 🔴 [ref](core 7.6.0 S6-A):宿主报事实、core 铸词 —— server 不再传结算词(旧形传 core 的三词之一,
431
441
  * 于是「哪个词配哪个 decision」这条相容规则要 server 自己记,而记错的方向恰是上面那句要防的)。 */
432
- resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding,
442
+ resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, answer: QuestionAnswer | undefined,
443
+ /** 这次决定落在哪一个动作上:调用方点名它看到的那一对(`boundCallId` + `boundInputHash`);缺任一 ⇒ 400 `decide_requires_binding`
444
+ * (判在门型守卫之后,判官 `decidedActionOf`)。本函数从不拿当前停驻行补。 */
445
+ binding: DecideBinding,
433
446
  /** [ref] codex R4(验真后修):remember 豁免的 grant 挂点。**工具名不再由路由自己读**(那是与本函数
434
447
  * 载入行分歧的第三次 scope-less 读,三读分歧下会给他人工具落豁免=提权)——由本函数把**已属主校验的
435
448
  * 载入 cp** 的可记工具名(`rememberableToolName`,非 tool_approval / AskUserQuestion / legacy null / [ref] 强制卡 ⇒ `null`
@@ -446,7 +459,9 @@ export interface RouteLegs {
446
459
  decider?: {
447
460
  principal?: string;
448
461
  explicitOperator: boolean;
449
- }): Promise<ResumeLegOutcome>;
462
+ },
463
+ /** 续跑请求的重述键(`mcpServers` / `a2aPeers`),门读出并判过的那一份;缺席 = 冻结读法。语义属主 = `resume-restatement.ts` 顶注。 */
464
+ restatement?: ResumeRestatement): Promise<ResumeLegOutcome>;
450
465
  /** 全 resume 家族的共同下半场(lease admission + 写 run log + fleet 发布)。 */
451
466
  driveResumeIntoRunLog(args: DriveResumeArgs): Promise<ResumeLegOutcome>;
452
467
  /** assistant 抢占腿的复位(gate-kind 守卫 + 行级属主复核 + 驱动)。 */
@@ -458,7 +473,9 @@ export interface RouteLegs {
458
473
  decider?: {
459
474
  principal?: string;
460
475
  explicitOperator: boolean;
461
- }): Promise<ResumeLegOutcome>;
476
+ },
477
+ /** 续跑请求的重述键(同 {@link RouteLegs.resumeCheckpoint} 末参)。 */
478
+ restatement?: ResumeRestatement): Promise<ResumeLegOutcome>;
462
479
  /** notify-wake 域:唤醒一条 parked checkpoint。 */
463
480
  resumeWake(sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined,
464
481
  /** [ref] 件 S-1:透传给 {@link DriveResumeArgs.acceptEarly}(200 受理语义)。HTTP `/wake` 腿传 `true`
@@ -466,7 +483,11 @@ export interface RouteLegs {
466
483
  acceptEarly?: boolean,
467
484
  /** 透传给 {@link DriveResumeArgs.workflowDecision}。HTTP `/wake` 腿**不设**此位 —— 它是 `/decide` 第三条车道投递腿的专用席,
468
485
  * 而两条腿共用这一条驱动路径正是 `notify-wake.ts` 那条「wake 会烧模型,所以复用 resume 家族的同一条腿」的成文纪律。 */
469
- workflowDecision?: WorkflowDecisionDelivery): Promise<ResumeLegOutcome>;
486
+ workflowDecision?: WorkflowDecisionDelivery,
487
+ /** 付费方身份(`/decide` workflow 车道专用;HTTP `/wake` 腿不设)。语义属主 = `resume-legs.ts` 同名形参顶注。 */
488
+ ownerLeaseAdmittedFor?: string,
489
+ /** 续跑请求的重述键(同 {@link RouteLegs.resumeCheckpoint} 末参)。 */
490
+ restatement?: ResumeRestatement): Promise<ResumeLegOutcome>;
470
491
  /**
471
492
  * assistant plan_review 三态腿的**纯读段**([ref],7.97.0):选卡 → 属主轻读 → 载入 → 行级 scope → 门类 → 这条决断
472
493
  * 唯一的一次 ctx 读。任何一步拒 ⇒ `{ok:false, answer}`(不改任何状态;端点在消费一次性 nonce **之前**把它答出去);
@@ -503,7 +524,11 @@ export interface RouteLegs {
503
524
  acceptEarly?: boolean,
504
525
  /** [ref]:批准之后这条会话继续用的 permission mode(闭集 `PLAN_APPROVE_PERMISSION_MODES_AFTER`;只在把计划带出 plan 的决定
505
526
  * —— `approve` / `edit` —— 上有意义,缺席 ⇒ `"default"`)。语义与判据的唯一属主 = `resume-legs.ts` 的同名形参顶注。 */
506
- permissionModeAfter?: PlanApprovePermissionModeAfter): Promise<ResumeLegOutcome>;
527
+ permissionModeAfter?: PlanApprovePermissionModeAfter,
528
+ /** 续跑请求的重述键(同 {@link RouteLegs.resumeCheckpoint} 末参)。 */
529
+ restatement?: ResumeRestatement): Promise<ResumeLegOutcome>;
530
+ /** 会话通知门(`POST /v1/sessions/:id/notify` 与 Runner 之外的后台子代完成共用的那一只;活流登记簿是本服务的,门随服务建一次)。 */
531
+ sessionNotify: SessionNotifyDoor;
507
532
  }
508
533
  /**
509
534
  * [ref] 件 S-2 —— resume 族**受理点的断连留痕**(裁定:维持 detach,补观察面)。
@@ -729,7 +754,7 @@ export declare const RUN_NOT_FOUND_MESSAGE = "run not found \u2014 the id belong
729
754
  * 走一只常量 + `runOwnerOk(..., "session")` 的会话拼法,「改一处」就是改全域。
730
755
  */
731
756
  export declare const SESSION_NOT_FOUND_MESSAGE = "session not found";
732
- /** [ref] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给 TaskOutput/journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
757
+ /** [ref] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给任务句柄读腿 / journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
733
758
  export declare function runNotFoundMessage(id: string): string;
734
759
  export {};
735
760
  //# sourceMappingURL=route-ctx.d.ts.map
@@ -33,7 +33,7 @@ export const SESSION_NOT_FOUND_MESSAGE = "session not found";
33
33
  export function runNotFoundMessage(id) {
34
34
  const aStar = /^(?:a|wa)[0-9a-f]{4,}$/i.test(id);
35
35
  return aStar
36
- ? `run not found — "${id}" looks like an agent handle (a*/wa* domain), which is never in the run store; use the TaskOutput tool (or the workflow journal / subagents surface) to read it, and the run's durable taskId (X-Task-Id) for this endpoint`
36
+ ? `run not found — "${id}" looks like an agent handle (a*/wa* domain), which is never in the run store; read it through its run's task-handle surface (GET /v1/runs/:id/tasks/:handle/output, or the workflow journal / subagents surface), and use the run's durable taskId (X-Task-Id) for this endpoint`
37
37
  : RUN_NOT_FOUND_MESSAGE;
38
38
  }
39
39
  //# sourceMappingURL=route-ctx.js.map
@@ -6,6 +6,7 @@ import { type GovernancePosture } from "../../runtime-governance.js";
6
6
  import { type GateIntentView } from "../../approval.js";
7
7
  import { type RouteCtx, type RouteDecl, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
8
8
  import { type KeyClosureIssue } from "../../request-key-closure.js";
9
+ import { type PeerInboundPendingRow } from "../../peer-inbound-face.js";
9
10
  /** 族的成员 = 路由表行 id(改名 / 删行 ⇒ 下表的键当场编译红)。 */
10
11
  export type DecisionDoorId = Extract<ApprovalsAssistantRouteId, "approval-decide" | "assistant-task-plan-review">;
11
12
  /** 一道门的静态描述 —— 只写**这道门自己**的东西;路径、路径段、别门的键一概不在这里写(从来源读)。 */
@@ -137,7 +138,14 @@ export declare function streamApprovals(req: IncomingMessage, res: ServerRespons
137
138
  * 缺席,流的字节与 [ref] 之前逐字相同(能力缺席 = 零变化,不是空数组)。**位置在 `pollMs` 之前**
138
139
  * 与 `governance` 同一条理由:`pollMs` 只有测试传,而本参数是每个生产调用点都必须表态的那一个。
139
140
  */
140
- live: Pick<ToolApprovalCoordinator, "listLivePending"> | undefined, scope: string | undefined,
141
+ live: Pick<ToolApprovalCoordinator, "listLivePending"> | undefined,
142
+ /**
143
+ * 🔴 [ref]:`pending` 车道的**第二个半场** —— 被扣的跨会话来信(`peer_inbound` 行),与 `GET /v1/approvals` 的 `pending[]`
144
+ * **同一只**闭包({@link peerInboundRowsFor},已绑调用方自己的信箱 scope)。与停驻行同一条 delta 车道、同一对事件名
145
+ * (`pending` / `resolved`):两族同一扇决断口,帧上的 `kind` 判别。`undefined` = 本腿没有这一半场(流字节只差每帧的 `kind`)。
146
+ * 位置在 `scope` 之前、与 `live` 并列:每个生产调用点都必须表态。
147
+ */
148
+ peerInbound: (() => readonly PeerInboundPendingRow[]) | undefined, scope: string | undefined,
141
149
  /** [ref]②:出身归因的部署侧一半 + 三张审批名单(问的属主)。缺席 ⇒ 归不出治理出身(键缺席,与 best-effort 同方向)、没有一行读作
142
150
  * 部署名单的问(记不住,方向是多问)。**位置在 `pollMs` 之前**是刻意的:`pollMs` 只有测试传,而本参数是每个生产调用点都必须给的那一个。 */
143
151
  governance?: PendingProjectionDeployment, pollMs?: number): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { screenedGate } from "../../trace/project.js";
2
2
  import { skillCardFacts } from "../../approval-card.js";
3
- import { hostDecisionFacts, readDecidedBy, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, DECIDED_BY_OUTSIDE_CLOSED_SET } from "../../host-decision.js";
3
+ import { hostDecisionFacts, readDecider, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF } from "../../host-decision.js";
4
4
  import { sameExecutionRecord } from "@sema-agent/core";
5
5
  import { findParkedAgentForCheckpoint } from "../../parked-decide.js";
6
6
  import { DECIDED_REPLAY_SCAN_CAP, approvalPayloadFingerprint } from "../../plugins/checkpoint-store-sql.js";
@@ -32,16 +32,29 @@ import { MAX_CHECKPOINT_ID_CHARS, durableRowIdentity } from "../../checkpoint-id
32
32
  import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "../../request-key-closure.js";
33
33
  import { fieldConflictFacts } from "../../field-conflict.js";
34
34
  import { buildSettlementEvidence, decisionCommittedOf } from "../../gate-settlement.js";
35
+ import { seatText } from "../../trace/exit-seat.js";
36
+ import { approvalQueueKindOfDoorId } from "../../approval-queue-kind.js";
37
+ import { defaultPeerInboundFace, parsePeerInboundId, peerBoxScope } from "../../peer-inbound-face.js";
38
+ import { RESUME_RESTATABLE_KEYS, readResumeRestatement } from "../resume-restatement.js";
35
39
  const DECIDE_BODY_DECLARED_KEYS = [
36
- "answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decision", "feedback", "reason", "remember", "updatedInput",
40
+ "answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decidedVia", "decision", "feedback", "reason", "remember", "updatedInput",
41
+ ...RESUME_RESTATABLE_KEYS,
37
42
  ];
38
43
  const DECIDE_PROOF_COVERED_KEYS = ["answer", "boundCallId", "boundInputHash", "decision", "reason"];
39
- const DECIDE_NOT_IN_PROOF_KEYS = ["checkpointToken", "decidedBy", "feedback", "remember", "updatedInput"];
44
+ const DECIDE_NOT_IN_PROOF_KEYS = ["checkpointToken", "decidedBy", "decidedVia", "feedback", "remember", "updatedInput", ...RESUME_RESTATABLE_KEYS];
45
+ function readDecisionDoorRestatement(res, body, directDoorActive) {
46
+ if (directDoorActive && RESUME_RESTATABLE_KEYS.some((key) => body[key] !== undefined)) {
47
+ sendError(res, 400, "restatement_not_in_proof", "mcpServers / a2aPeers (the resume request's restatement of its MCP / A2A peer tables) are not supported on a direct-door worker (not covered by the decision proof)");
48
+ return null;
49
+ }
50
+ return { restatement: readResumeRestatement(body) };
51
+ }
40
52
  const PLAN_REVIEW_BODY_DECLARED_KEYS = [
41
- "boundCallId", "checkpointId", "decidedBy", "decision", "editedPlan", "permissionModeAfter", "reason",
53
+ "boundCallId", "checkpointId", "decidedBy", "decidedVia", "decision", "editedPlan", "permissionModeAfter", "reason",
54
+ ...RESUME_RESTATABLE_KEYS,
42
55
  ];
43
56
  const PLAN_REVIEW_PROOF_COVERED_KEYS = ["boundCallId", "checkpointId", "decision", "editedPlan", "permissionModeAfter", "reason"];
44
- const PLAN_REVIEW_NOT_IN_PROOF_KEYS = ["decidedBy"];
57
+ const PLAN_REVIEW_NOT_IN_PROOF_KEYS = ["decidedBy", "decidedVia", ...RESUME_RESTATABLE_KEYS];
45
58
  const PROOF_SLOT_OF = {
46
59
  answer: "answer",
47
60
  boundCallId: "boundCallId",
@@ -230,7 +243,7 @@ async function decideIdempotentReplay(cs, args) {
230
243
  return undefined;
231
244
  }
232
245
  }
233
- if (binding.boundInputHash !== undefined && row.boundInputHash !== binding.boundInputHash)
246
+ if (binding.boundInputHash === undefined || row.boundInputHash !== binding.boundInputHash)
234
247
  return undefined;
235
248
  if (row.decision !== decision)
236
249
  return undefined;
@@ -300,15 +313,15 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
300
313
  const operator = explicitOperatorOk(principal, deps.config);
301
314
  const ownScope = principal ?? CHECKPOINT_PUBLIC_SCOPE;
302
315
  if (match.decl.id === "approvals-stream") {
303
- const scope = operator
304
- ? (new URLSearchParams(match.search).get("owner") ?? undefined)
305
- : ownScope;
306
- await streamApprovals(req, res, cs, deps.toolApproval, scope, deps.config);
316
+ const ownerParam = operator ? (new URLSearchParams(match.search).get("owner") ?? undefined) : undefined;
317
+ const scope = operator ? ownerParam : ownScope;
318
+ await streamApprovals(req, res, cs, deps.toolApproval, peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam), scope, deps.config);
307
319
  return;
308
320
  }
309
321
  if (match.decl.id === "approvals-list") {
322
+ const ownerParam = operator ? (new URLSearchParams(match.search).get("owner") ?? undefined) : undefined;
310
323
  const scope = operator
311
- ? (new URLSearchParams(match.search).get("owner") ?? undefined)
324
+ ? ownerParam
312
325
  : ownScope;
313
326
  let durableRows;
314
327
  try {
@@ -318,8 +331,9 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
318
331
  sendApprovalsReadFailure(req, res, deps.logger, err);
319
332
  return;
320
333
  }
334
+ const pending = [...durableRows.map((r) => projectPendingForWire(r, deps.config)), ...peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam)()];
321
335
  sendJson(res, 200, {
322
- pending: durableRows.map((r) => projectPendingForWire(r, deps.config)),
336
+ pending,
323
337
  serverNowMs: Date.now(),
324
338
  ...(deps.toolApproval ? { livePending: deps.toolApproval.listLivePending(scope) } : {}),
325
339
  ...(deps.approvalAskAudit ? { crashConverged: deps.approvalAskAudit.listCrashConverged(scope) } : {}),
@@ -398,6 +412,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
398
412
  ...durableRowIdentity("assistant-inbox", s.checkpointId, s.token),
399
413
  scope: s.scope,
400
414
  createdAt: s.createdAt,
415
+ rowVerdict: s.rowVerdict,
401
416
  ...(s.gateKind !== undefined ? { gateKind: s.gateKind } : {}),
402
417
  ...(s.severity !== undefined ? { severity: s.severity } : {}),
403
418
  ...(s.spentMicroUsd !== undefined ? { spentMicroUsd: s.spentMicroUsd } : {}),
@@ -528,6 +543,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
528
543
  sendPrincipalRefusal(res, req, deps.config);
529
544
  return;
530
545
  }
546
+ const restatement = readResumeRestatement(await readJson(req));
531
547
  const taskId = match.params.taskId;
532
548
  const run = await runStore.getRun(taskId);
533
549
  if (!run) {
@@ -539,7 +555,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
539
555
  sendError(res, 404, "not_found.run", "task not found");
540
556
  return;
541
557
  }
542
- const out = await resumePreempted(run.sessionId, req, true, { principal, explicitOperator });
558
+ const out = await resumePreempted(run.sessionId, req, true, { principal, explicitOperator }, restatement);
543
559
  sendResumeOutcome(res, out, deps.logger);
544
560
  return;
545
561
  }
@@ -574,13 +590,13 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
574
590
  sendError(res, 400, "request.field_conflict", "decision 'edit' requires a non-empty editedPlan (the operator's revised plan)", fieldConflictFacts("plan_edit", ["decision", "editedPlan"]));
575
591
  return;
576
592
  }
577
- const decidedByReading = readDecidedBy(body.decidedBy, deps.config.directDoorActive);
593
+ const decidedByReading = readDecider(body, deps.config.directDoorActive);
578
594
  switch (decidedByReading.kind) {
579
595
  case "not_in_proof":
580
596
  sendError(res, 400, "decided_by_not_in_proof", DECIDED_BY_NOT_IN_PROOF);
581
597
  return;
582
598
  case "outside_closed_set":
583
- sendError(res, 400, "request.field_invalid", DECIDED_BY_OUTSIDE_CLOSED_SET);
599
+ sendError(res, 400, "request.field_invalid", decidedByReading.message);
584
600
  return;
585
601
  case "accepted":
586
602
  break;
@@ -589,6 +605,9 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
589
605
  throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
590
606
  }
591
607
  }
608
+ const restated = readDecisionDoorRestatement(res, body, deps.config.directDoorActive);
609
+ if (restated === null)
610
+ return;
592
611
  if (decision === "edit" && !isWellFormedUnicode(body.editedPlan)) {
593
612
  sendError(res, 400, "request.field_invalid", "editedPlan must be well-formed Unicode text (a lone surrogate cannot be signed unambiguously)");
594
613
  return;
@@ -704,7 +723,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
704
723
  return;
705
724
  }
706
725
  }
707
- const out = await drivePlanReview(plan.prepared, decision, decision === "edit" ? body.editedPlan : undefined, typeof body.reason === "string" ? body.reason : undefined, hostDecisionFacts(decidedByReading.decidedBy, deciderPrincipal), req, true, isPlanApprovePermissionModeAfter(body.permissionModeAfter) ? body.permissionModeAfter : undefined);
726
+ const out = await drivePlanReview(plan.prepared, decision, decision === "edit" ? body.editedPlan : undefined, typeof body.reason === "string" ? body.reason : undefined, hostDecisionFacts(decidedByReading.decider, deciderPrincipal), req, true, isPlanApprovePermissionModeAfter(body.permissionModeAfter) ? body.permissionModeAfter : undefined, restated.restatement);
708
727
  sendResumeOutcome(res, out, deps.logger);
709
728
  return;
710
729
  }
@@ -716,10 +735,22 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
716
735
  sendError(res, 400, "request.path_malformed", "malformed approval id (invalid percent-encoding)");
717
736
  return;
718
737
  }
738
+ const doorKind = approvalQueueKindOfDoorId(sessionId);
739
+ switch (doorKind) {
740
+ case "peer_inbound":
741
+ await decidePeerInbound(req, res, sessionId, { readJson, config: deps.config, logger: deps.logger, principal, face: defaultPeerInboundFace });
742
+ return;
743
+ case "checkpoint":
744
+ break;
745
+ default: {
746
+ const unreachable = doorKind;
747
+ throw new Error(`approval decide: door id of an unknown kind ${String(unreachable)}`);
748
+ }
749
+ }
719
750
  const body = (await readJson(req));
720
751
  const decision = body?.decision === "approve" ? "approve" : body?.decision === "deny" ? "deny" : undefined;
721
752
  if (!decision) {
722
- sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny', reason?, answer?, checkpointToken?, boundCallId?, boundInputHash?, updatedInput?, remember?, decidedBy?, feedback? }", retiredDecisionKeyFacts(body)?.extra);
753
+ sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny', boundCallId, boundInputHash, reason?, answer?, checkpointToken?, updatedInput?, remember?, decidedBy?, decidedVia?, feedback? }", retiredDecisionKeyFacts(body)?.extra);
723
754
  return;
724
755
  }
725
756
  {
@@ -756,10 +787,6 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
756
787
  sendError(res, 501, "capability.store_required", "approval exemptions need a store backend (DB_BACKEND=mysql|pg|local)");
757
788
  return;
758
789
  }
759
- if (!body.checkpointToken && !body.boundCallId) {
760
- sendError(res, 400, "remember_requires_binding", "remember requires the decision binding (echo checkpointToken and/or boundCallId from the pending approval)");
761
- return;
762
- }
763
790
  remember = true;
764
791
  }
765
792
  let answer;
@@ -799,13 +826,13 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
799
826
  sendError(res, 400, "feedback_not_in_proof", "feedback is not supported on a direct-door worker (not covered by the decision proof)");
800
827
  return;
801
828
  }
802
- const decidedByReading = readDecidedBy(body.decidedBy, deps.config.directDoorActive);
829
+ const decidedByReading = readDecider(body, deps.config.directDoorActive);
803
830
  switch (decidedByReading.kind) {
804
831
  case "not_in_proof":
805
832
  sendError(res, 400, "decided_by_not_in_proof", DECIDED_BY_NOT_IN_PROOF);
806
833
  return;
807
834
  case "outside_closed_set":
808
- sendError(res, 400, "request.field_invalid", DECIDED_BY_OUTSIDE_CLOSED_SET);
835
+ sendError(res, 400, "request.field_invalid", decidedByReading.message);
809
836
  return;
810
837
  case "accepted":
811
838
  break;
@@ -814,12 +841,15 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
814
841
  throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
815
842
  }
816
843
  }
817
- const decidedBy = decidedByReading.decidedBy;
844
+ const decider = decidedByReading.decider;
845
+ const restated = readDecisionDoorRestatement(res, body, deps.config.directDoorActive);
846
+ if (restated === null)
847
+ return;
818
848
  if (body.feedback !== undefined && decision !== "approve") {
819
849
  sendError(res, 400, "request.field_conflict", "feedback is only valid with decision 'approve' — the words of a denial are its reason", fieldConflictFacts("approval_feedback", ["feedback", "decision"]));
820
850
  return;
821
851
  }
822
- if (body.feedback !== undefined && decidedBy !== "person") {
852
+ if (body.feedback !== undefined && decider.decidedBy !== "person") {
823
853
  sendError(res, 400, "request.field_conflict", "feedback is only valid on a person's decision — the agent reads it as the user's own words, which a policy decision cannot carry", fieldConflictFacts("approval_feedback", ["feedback", "decidedBy"]));
824
854
  return;
825
855
  }
@@ -871,7 +901,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
871
901
  pendingGrant = { sessionId: grant.overrideSessionId ?? sessionId, toolName: canonical };
872
902
  }
873
903
  : undefined;
874
- const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, hostDecisionFacts(decidedBy, deciderPrincipal), req, answer, binding, grantOnCommit, true, { principal: deciderPrincipal, explicitOperator });
904
+ const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, hostDecisionFacts(decider, deciderPrincipal), req, answer, binding, grantOnCommit, true, { principal: deciderPrincipal, explicitOperator }, restated.restatement);
875
905
  if (pendingGrant !== undefined && decisionCommittedOf(out) && deps.approvalExemptionStore) {
876
906
  try {
877
907
  await deps.approvalExemptionStore.grant(pendingGrant.sessionId, pendingGrant.toolName, deciderPrincipal ?? null);
@@ -907,6 +937,60 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
907
937
  }
908
938
  return assertNever(match.decl.id);
909
939
  }
940
+ async function decidePeerInbound(req, res, id, env) {
941
+ const body = (await env.readJson(req));
942
+ const decision = body?.decision === "approve" ? "approve" : body?.decision === "deny" ? "deny" : undefined;
943
+ if (!decision) {
944
+ sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny' }", retiredDecisionKeyFacts(body)?.extra);
945
+ return;
946
+ }
947
+ {
948
+ const keyIssue = decisionBodyKeyIssue("approval-decide", body);
949
+ if (keyIssue) {
950
+ const retired = retiredDecisionKeyFacts(body);
951
+ sendError(res, 400, "request.body_shape", retired?.error ?? keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys, ...retired?.extra });
952
+ return;
953
+ }
954
+ }
955
+ const notForThisKind = Object.keys(body).filter((k) => k !== "decision").sort();
956
+ if (notForThisKind.length > 0) {
957
+ sendError(res, 400, "request.body_shape", `body: key(s) ${notForThisKind.join(", ")} do not apply to a held cross-session message (kind peer_inbound) — refused rather than dropped, because a dropped key reads as "in effect" to the caller. This kind takes { decision: 'approve' | 'deny' } only`, { unknownKeys: [], unsupportedKeys: notForThisKind.slice(0, MAX_KEY_ECHO) });
958
+ return;
959
+ }
960
+ let decider = env.principal;
961
+ if (env.config.directDoorActive) {
962
+ const hdr = (n) => {
963
+ const h = req.headers[n];
964
+ return Array.isArray(h) ? h[0] : h;
965
+ };
966
+ const proof = verifyDirectDoorProof({ jwt: hdr(PRINCIPAL_TOKEN_HEADER), mac: hdr(APPROVAL_MAC_HEADER), kid: hdr(APPROVAL_MAC_KID_HEADER) }, buildApprovalEnvelope(id, DECIDE_PROOF_COVERED_KEYS, { answer: undefined, boundCallId: undefined, boundInputHash: undefined, decision, reason: null }), env.config);
967
+ if (!proof.ok) {
968
+ sendError(res, proof.status, proof.errorCode, proof.error);
969
+ return;
970
+ }
971
+ decider = proof.principal;
972
+ }
973
+ const coord = parsePeerInboundId(id);
974
+ const outcome = coord === undefined ? { kind: "not_found" } : await env.face.decide(peerBoxScope(decider), coord, decision);
975
+ switch (outcome.kind) {
976
+ case "not_found":
977
+ sendError(res, 404, "not_found.approval", "approval not found");
978
+ return;
979
+ case "unrecorded":
980
+ env.logger?.warn("peer_inbound_decision_unrecorded", { id, decision, err: seatText("operator-log-text", String(outcome.error)) });
981
+ sendError(res, 503, "state.peer_decision_unrecorded", "the decision on this held cross-session message could not be recorded (the mailbox store refused the move) — nothing was decided; retry the same request");
982
+ return;
983
+ case "settled": {
984
+ const reply = { kind: "peer_inbound", id, outcome: outcome.outcome };
985
+ sendJson(res, 200, reply);
986
+ return;
987
+ }
988
+ default: {
989
+ const unreachable = outcome;
990
+ throw new Error(`peer inbound decide: unhandled result ${JSON.stringify(unreachable)}`);
991
+ }
992
+ }
993
+ }
910
994
  const APPROVALS_STREAM_POLL_MS = 3000;
911
995
  function approvalsReadFailureOf(err) {
912
996
  const fault = storeFaultClassOf(err);
@@ -946,6 +1030,12 @@ function redactPendingDisclosures(row) {
946
1030
  const next = redactRiskDescriptor(rd);
947
1031
  return next === rd ? row : { ...row, riskDescriptor: next };
948
1032
  }
1033
+ function peerInboundRowsFor(face, principal, operatorOwnerParam) {
1034
+ if (operatorOwnerParam !== undefined && operatorOwnerParam !== principal)
1035
+ return () => [];
1036
+ const scope = peerBoxScope(principal);
1037
+ return () => face.list(scope);
1038
+ }
949
1039
  function projectPendingForWire(row, deployment) {
950
1040
  const governance = deployment;
951
1041
  const redacted = redactPendingDisclosures(row);
@@ -959,7 +1049,7 @@ function projectPendingForWire(row, deployment) {
959
1049
  classes: row.classes,
960
1050
  deploymentListQuestion,
961
1051
  });
962
- return { ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}) };
1052
+ return { kind: "checkpoint", ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}) };
963
1053
  }
964
1054
  function createSseDeltaLane(res, spec) {
965
1055
  let prev = new Map();
@@ -978,8 +1068,19 @@ function createSseDeltaLane(res, spec) {
978
1068
  },
979
1069
  };
980
1070
  }
981
- export async function streamApprovals(req, res, cs, live, scope, governance, pollMs = APPROVALS_STREAM_POLL_MS) {
982
- const keyOf = (p) => JSON.stringify([p.sessionId, p.toolCallId ?? null]);
1071
+ export async function streamApprovals(req, res, cs, live, peerInbound, scope, governance, pollMs = APPROVALS_STREAM_POLL_MS) {
1072
+ const keyOf = (p) => {
1073
+ switch (p.kind) {
1074
+ case "checkpoint":
1075
+ return JSON.stringify([p.kind, p.sessionId, p.toolCallId ?? null]);
1076
+ case "peer_inbound":
1077
+ return JSON.stringify([p.kind, p.id]);
1078
+ default: {
1079
+ const unreachable = p;
1080
+ throw new Error(`approvals stream: pending row of an unknown kind ${JSON.stringify(unreachable)}`);
1081
+ }
1082
+ }
1083
+ };
983
1084
  sseHeaders(res);
984
1085
  res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, mode: "approvals-delta", pollMs: APPROVALS_STREAM_POLL_MS })}\n\n`);
985
1086
  let closed = false;
@@ -992,7 +1093,18 @@ export async function streamApprovals(req, res, cs, live, scope, governance, pol
992
1093
  keyOf,
993
1094
  bytesOf: (p) => JSON.stringify({ type: "pending", ...p }),
994
1095
  upsertData: (p, nowMs) => ({ type: "pending", ...p, serverNowMs: nowMs }),
995
- exitData: (p, nowMs) => ({ type: "resolved", sessionId: p.sessionId, toolCallId: p.toolCallId, serverNowMs: nowMs }),
1096
+ exitData: (p, nowMs) => {
1097
+ switch (p.kind) {
1098
+ case "checkpoint":
1099
+ return { type: "resolved", kind: p.kind, sessionId: p.sessionId, toolCallId: p.toolCallId, serverNowMs: nowMs };
1100
+ case "peer_inbound":
1101
+ return { type: "resolved", kind: p.kind, id: p.id, serverNowMs: nowMs };
1102
+ default: {
1103
+ const unreachable = p;
1104
+ throw new Error(`approvals stream: pending row of an unknown kind ${JSON.stringify(unreachable)}`);
1105
+ }
1106
+ }
1107
+ },
996
1108
  });
997
1109
  const liveLane = createSseDeltaLane(res, {
998
1110
  upsertEvent: "live_pending",
@@ -1006,7 +1118,7 @@ export async function streamApprovals(req, res, cs, live, scope, governance, pol
1006
1118
  while (!closed) {
1007
1119
  let snapshot;
1008
1120
  try {
1009
- const pending = (await cs.listPending(scope)).map((r) => projectPendingForWire(r, governance));
1121
+ const pending = [...(await cs.listPending(scope)).map((r) => projectPendingForWire(r, governance)), ...(peerInbound?.() ?? [])];
1010
1122
  snapshot = { pending, livePending: live ? live.listLivePending(scope) : [] };
1011
1123
  }
1012
1124
  catch (err) {
@@ -16,6 +16,8 @@ import { buildEmptyMcpProbeFace, buildMcpInjectionRefusal, buildMcpProbeCacheKey
16
16
  import { mcpForScenario } from "../../web-admin-client/facade.js";
17
17
  import { sendScenarioNotFound } from "../scenario-read-face.js";
18
18
  import { sendJson, sendError } from "../send.js";
19
+ import { codeFieldOf } from "../../terminal.js";
20
+ import { engineRefusalAnswerOf } from "../engine-refusal.js";
19
21
  import { assertNever } from "../route-ctx.js";
20
22
  import { fileHistoryCaptureOf } from "../../file-history-capture.js";
21
23
  import { projectMemoryEngineCapability } from "../../memory-posture.js";
@@ -146,7 +148,7 @@ export async function handleCapabilities(req, res, match, ctx) {
146
148
  subagentSteer: facts.subagentSteer(deps).available,
147
149
  subagentResume: facts.subagentSteer(deps).available,
148
150
  taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: DEPLOYMENT_SHAPE_PREDICATES.callerCwd.holds(deps.config), hooks: taskHooksHonored(deps.config), pluginHooks: taskHooksHonored(deps.config), managedHookGroups: taskHooksHonored(deps.config), httpHookAllowlists: taskHooksHonored(deps.config), tightenOnlySeats: true, approvalFrameFamilies: true },
149
- settings: { permissions: { allowGrant: true, allowLayer: true, layers: true, callerTrust: callerTrust(req, deps.config).trust } },
151
+ settings: { permissions: { allowGrant: true, allowLayer: true, layers: true, layeredAllow: true, callerTrust: callerTrust(req, deps.config).trust } },
150
152
  permissionModeAuto: {
151
153
  accepted: true,
152
154
  classifierSeat: deps.autoModeFace?.seatMounted === true,
@@ -193,6 +195,7 @@ export async function handleCapabilities(req, res, match, ctx) {
193
195
  approvalFrameFamilies: [...facts.approvalFrameFamilies(streamApprovalFact)],
194
196
  approvalDecisionNote: true,
195
197
  approvalDecidedBy: true,
198
+ approvalDecidedVia: true,
196
199
  planReviewCheckpointId: true,
197
200
  permissionModes: [...PERMISSION_MODES],
198
201
  sessionReadRedaction: true,
@@ -307,7 +310,7 @@ export async function handleCapabilities(req, res, match, ctx) {
307
310
  modelPlane: buildModelPlaneFace(deps.config, {
308
311
  envTableIsPlaceholder: isRosterGateArmed(deps.config.webAdmin),
309
312
  candidateRejection,
310
- modelRefusals: deps.modelRefusalsState?.(),
313
+ latestPlaneRefusal: deps.latestPlaneRefusalState?.(),
311
314
  }),
312
315
  aliases: selectableAliasNames(deps.config.models, atCatalog, deps.config.modelRefusals).map((name) => ({ name, ...mentionBit(name) })),
313
316
  });
@@ -374,6 +377,12 @@ async function answerMcpProbe(res, ctx, answer, outcome, declaredBy) {
374
377
  sendMcpProbeRefusal(res, ctx, buildMcpProbeDeclarationRefusal(), "failed");
375
378
  return;
376
379
  }
380
+ const engineRefusal = engineRefusalAnswerOf(err);
381
+ if (engineRefusal !== undefined) {
382
+ deps.logger?.warn("mcp_status_face_declaration_refused", { ...codeFieldOf(err), error: engineRefusal.refusal.message });
383
+ sendError(res, engineRefusal.status, engineRefusal.refusal.errorCode, engineRefusal.refusal.message);
384
+ return;
385
+ }
377
386
  throw err;
378
387
  }
379
388
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * [ref] A9 域模块 —— memory 导出/同步 + 只读策略面。
3
3
  *
4
- * 路由:`GET /v1/memory/export`、`POST /v1/memory/sync/:scope`、`GET /v1/policy`。
4
+ * 路由:`GET /v1/memory/export`、`POST /v2/memory/sync/:scope`、`GET /v1/policy`。
5
5
  * owner 门 = 「本人 user scope / 本人的项目层 / 显式 operator / `org:` 键的目录成员」(见 {@link gateMemoryScope}),
6
6
  * 越界一律 404(零存在性 oracle)。本域零可变状态。
7
7
  */
@@ -35,7 +35,7 @@ export declare const MEMORY_POLICY_ROUTES: readonly [{
35
35
  }, {
36
36
  readonly id: "memory-sync-scope";
37
37
  readonly pattern: RegExp;
38
- readonly label: "/v1/memory/sync/:scope";
38
+ readonly label: "/v2/memory/sync/:scope";
39
39
  readonly methods: readonly ["POST"];
40
40
  }];
41
41
  /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
@@ -214,6 +214,6 @@ export async function handleMemoryPolicy(req, res, match, ctx) {
214
214
  export const MEMORY_POLICY_ROUTES = [
215
215
  { id: "memory-export", path: "/v1/memory/export", methods: ["GET"] },
216
216
  { id: "policy", path: "/v1/policy", methods: ["GET"] },
217
- { id: "memory-sync-scope", pattern: /^\/v1\/memory\/sync\/(?<scope>.*)$/, label: "/v1/memory/sync/:scope", methods: ["POST"] },
217
+ { id: "memory-sync-scope", pattern: /^\/v2\/memory\/sync\/(?<scope>.*)$/, label: "/v2/memory/sync/:scope", methods: ["POST"] },
218
218
  ];
219
219
  //# sourceMappingURL=memory-policy.js.map