@sema-agent/server 7.141.0 → 7.143.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 (341) hide show
  1. package/MIGRATION.md +657 -0
  2. package/USAGE.md +102 -30
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +61 -43
  5. package/dist/approval-ask-audit-store.js +69 -44
  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 +126 -27
  9. package/dist/approval-card.js +103 -23
  10. package/dist/approval-crash-convergence.d.ts +17 -7
  11. package/dist/approval-crash-convergence.js +94 -22
  12. package/dist/approval-deny-reasons.d.ts +34 -30
  13. package/dist/approval-deny-reasons.js +21 -6
  14. package/dist/approval-policy-settlement.d.ts +1 -1
  15. package/dist/approval-queue-kind.d.ts +27 -0
  16. package/dist/approval-queue-kind.js +6 -0
  17. package/dist/approval-reconciler.d.ts +41 -9
  18. package/dist/approval-reconciler.js +45 -42
  19. package/dist/audit.js +4 -2
  20. package/dist/auth-bridge.d.ts +9 -6
  21. package/dist/auth-bridge.js +9 -3
  22. package/dist/boot/adoption.js +2 -1
  23. package/dist/boot/budget-tracing.d.ts +17 -1
  24. package/dist/boot/budget-tracing.js +10 -6
  25. package/dist/boot/device-lane.d.ts +1 -1
  26. package/dist/boot/execution-env.d.ts +24 -4
  27. package/dist/boot/execution-env.js +26 -23
  28. package/dist/boot/execution-lane.js +2 -1
  29. package/dist/boot/exit-boundary.js +5 -4
  30. package/dist/boot/governance-seams.d.ts +26 -41
  31. package/dist/boot/governance-seams.js +61 -28
  32. package/dist/boot/late-binds.d.ts +7 -4
  33. package/dist/boot/late-binds.js +3 -1
  34. package/dist/boot/leader.js +2 -4
  35. package/dist/boot/memory-consolidation.d.ts +0 -8
  36. package/dist/boot/memory-consolidation.js +5 -4
  37. package/dist/boot/parked-revive-gate.js +2 -1
  38. package/dist/boot/permission-rules-audit.d.ts +0 -28
  39. package/dist/boot/permission-rules-audit.js +2 -1
  40. package/dist/boot/read-deny-anchor-audit.js +2 -1
  41. package/dist/boot/reapers.js +9 -7
  42. package/dist/boot/resolve-spec.d.ts +1 -1
  43. package/dist/boot/resolve-spec.js +19 -10
  44. package/dist/boot/retention-lane.js +5 -3
  45. package/dist/boot/runner-deps.d.ts +10 -7
  46. package/dist/boot/runner-deps.js +20 -29
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.js +4 -3
  49. package/dist/boot/shutdown.d.ts +9 -0
  50. package/dist/boot/shutdown.js +19 -4
  51. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  52. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  53. package/dist/boot/stage-03-store-backend.js +1 -2
  54. package/dist/boot/stage-04-stores.d.ts +5 -2
  55. package/dist/boot/stage-04-stores.js +6 -7
  56. package/dist/boot/stage-05-execution-env.d.ts +6 -3
  57. package/dist/boot/stage-05-execution-env.js +8 -6
  58. package/dist/boot/stage-06-runners.d.ts +6 -3
  59. package/dist/boot/stage-06-runners.js +3 -4
  60. package/dist/boot/stage-07-capability-layer.d.ts +6 -3
  61. package/dist/boot/stage-07-capability-layer.js +7 -2
  62. package/dist/boot/stage-08-reapers.d.ts +6 -3
  63. package/dist/boot/stage-08-reapers.js +3 -2
  64. package/dist/boot/stage-09-leader.d.ts +6 -3
  65. package/dist/boot/stage-10-http-server.d.ts +6 -3
  66. package/dist/boot/stage-10-http-server.js +14 -5
  67. package/dist/boot/stores.d.ts +5 -2
  68. package/dist/boot/stores.js +10 -9
  69. package/dist/boot/web-admin.d.ts +5 -1
  70. package/dist/boot/web-admin.js +48 -36
  71. package/dist/boot/webfetch-summarize-lane.js +2 -1
  72. package/dist/boot/workflow-orchestration.js +3 -2
  73. package/dist/capabilities/builtin-tools.d.ts +2 -1
  74. package/dist/capabilities/builtin-tools.js +2 -4
  75. package/dist/capabilities/composite-members.d.ts +2 -1
  76. package/dist/capabilities/composite-members.js +2 -1
  77. package/dist/capabilities/hands-lane.d.ts +9 -39
  78. package/dist/capabilities/hands-lane.js +8 -13
  79. package/dist/capabilities/mcp-probe.d.ts +3 -2
  80. package/dist/capabilities/scenarios.d.ts +11 -1
  81. package/dist/capabilities/scenarios.js +1 -0
  82. package/dist/capabilities/skills.d.ts +1 -17
  83. package/dist/capabilities/skills.js +0 -8
  84. package/dist/capability-facts.d.ts +31 -7
  85. package/dist/capability-facts.js +27 -4
  86. package/dist/card-void.d.ts +6 -9
  87. package/dist/config-catalog.js +2 -1
  88. package/dist/config-dir-trust.js +58 -29
  89. package/dist/config-invariants.d.ts +7 -1
  90. package/dist/config-invariants.js +7 -0
  91. package/dist/config-provider.d.ts +10 -0
  92. package/dist/config-provider.js +15 -4
  93. package/dist/config-root-migration.d.ts +13 -0
  94. package/dist/config-root-migration.js +99 -0
  95. package/dist/config-types.d.ts +19 -8
  96. package/dist/config.d.ts +10 -3
  97. package/dist/config.js +18 -4
  98. package/dist/delegated-session-owner.d.ts +5 -3
  99. package/dist/delegated-session-owner.js +1 -1
  100. package/dist/device-ws-protocol.d.ts +1 -0
  101. package/dist/device-ws-protocol.js +2 -1
  102. package/dist/env-model-table-gate.d.ts +53 -0
  103. package/dist/env-model-table-gate.js +53 -0
  104. package/dist/execution-lane-caps.d.ts +2 -1
  105. package/dist/fleet/fleet-bus.d.ts +34 -55
  106. package/dist/fleet/fleet-bus.js +35 -100
  107. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  108. package/dist/fleet/fleet-reconciler.js +1 -1
  109. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  110. package/dist/fleet/subagent-tail-bus.js +15 -28
  111. package/dist/hooks/hook-runner.d.ts +6 -0
  112. package/dist/hooks/hook-runner.js +58 -8
  113. package/dist/host-decision.d.ts +54 -20
  114. package/dist/host-decision.js +42 -14
  115. package/dist/http/admission.d.ts +1 -1
  116. package/dist/http/admission.js +7 -3
  117. package/dist/http/approval-sweeps.js +11 -8
  118. package/dist/http/caller-trust.d.ts +1 -1
  119. package/dist/http/dispatch.js +4 -2
  120. package/dist/http/durable-run-leg.js +2 -2
  121. package/dist/http/engine-refusal.d.ts +16 -0
  122. package/dist/http/engine-refusal.js +17 -0
  123. package/dist/http/execution-context.d.ts +2 -2
  124. package/dist/http/idempotency.d.ts +1 -1
  125. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  126. package/dist/http/memory-compliance-failure-reply.js +4 -6
  127. package/dist/http/owner-access.d.ts +11 -0
  128. package/dist/http/owner-access.js +13 -0
  129. package/dist/http/principal-gate.d.ts +3 -1
  130. package/dist/http/principal-gate.js +17 -8
  131. package/dist/http/resume-legs.d.ts +21 -7
  132. package/dist/http/resume-legs.js +76 -58
  133. package/dist/http/resume-restatement.d.ts +39 -0
  134. package/dist/http/resume-restatement.js +25 -0
  135. package/dist/http/route-ctx.d.ts +35 -10
  136. package/dist/http/route-ctx.js +1 -1
  137. package/dist/http/route-table.js +2 -0
  138. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  139. package/dist/http/routes/approvals-assistant.js +142 -31
  140. package/dist/http/routes/attachments.js +18 -0
  141. package/dist/http/routes/capabilities.js +16 -4
  142. package/dist/http/routes/memory-policy.d.ts +2 -2
  143. package/dist/http/routes/memory-policy.js +27 -7
  144. package/dist/http/routes/notify-wake.js +23 -25
  145. package/dist/http/routes/rules.js +48 -13
  146. package/dist/http/routes/runs.js +12 -11
  147. package/dist/http/routes/session-memory-sync.d.ts +91 -0
  148. package/dist/http/routes/session-memory-sync.js +174 -0
  149. package/dist/http/routes/session-sync.js +13 -20
  150. package/dist/http/routes/sessions-list.d.ts +2 -1
  151. package/dist/http/routes/sessions.d.ts +2 -2
  152. package/dist/http/routes/sessions.js +22 -23
  153. package/dist/http/routes/tasks.js +34 -54
  154. package/dist/http/run-read-face.d.ts +7 -0
  155. package/dist/http/run-read-face.js +9 -3
  156. package/dist/http/send.d.ts +5 -0
  157. package/dist/http/send.js +9 -2
  158. package/dist/http/server.d.ts +27 -15
  159. package/dist/http/server.js +4 -6
  160. package/dist/http/stored-body-replay.js +15 -4
  161. package/dist/http/submit-replay.d.ts +4 -3
  162. package/dist/http/submit-replay.js +2 -2
  163. package/dist/http/sync-submit-body.d.ts +21 -4
  164. package/dist/http/sync-submit-body.js +13 -0
  165. package/dist/http/wire-types.d.ts +17 -7
  166. package/dist/identity-source.d.ts +21 -2
  167. package/dist/identity-source.js +6 -0
  168. package/dist/leader/leader.js +5 -2
  169. package/dist/leader/wire.d.ts +7 -1
  170. package/dist/leader/wire.js +6 -5
  171. package/dist/leg-facts.d.ts +37 -0
  172. package/dist/leg-facts.js +15 -0
  173. package/dist/memory-bundle-engine.d.ts +1 -4
  174. package/dist/memory-bundle-engine.js +2 -2
  175. package/dist/memory-incident-sink.d.ts +19 -6
  176. package/dist/memory-incident-sink.js +8 -4
  177. package/dist/memory-operator-faces.d.ts +30 -66
  178. package/dist/memory-operator-faces.js +17 -88
  179. package/dist/memory-scope-stores.d.ts +38 -40
  180. package/dist/memory-scope-stores.js +51 -36
  181. package/dist/memory-scope.d.ts +0 -9
  182. package/dist/memory-scope.js +0 -9
  183. package/dist/memory-sync-client.d.ts +21 -21
  184. package/dist/memory-sync-client.js +62 -43
  185. package/dist/memory-sync-identity.d.ts +24 -0
  186. package/dist/memory-sync-identity.js +57 -0
  187. package/dist/memory-sync.d.ts +27 -5
  188. package/dist/memory-sync.js +29 -7
  189. package/dist/model-plane-face.d.ts +13 -7
  190. package/dist/model-plane-face.js +2 -1
  191. package/dist/observability/fail-open.d.ts +47 -14
  192. package/dist/observability/fail-open.js +37 -11
  193. package/dist/observability/run-terminal.d.ts +92 -34
  194. package/dist/observability/run-terminal.js +171 -162
  195. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  196. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  197. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  198. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  199. package/dist/orchestration/workflow-park-decision.js +5 -3
  200. package/dist/org-delivery-gate.d.ts +11 -0
  201. package/dist/org-delivery-gate.js +19 -1
  202. package/dist/parked-decide.d.ts +13 -9
  203. package/dist/parked-decide.js +7 -7
  204. package/dist/parked-revive-surface.js +1 -1
  205. package/dist/peer-inbound-face.d.ts +127 -0
  206. package/dist/peer-inbound-face.js +158 -0
  207. package/dist/permission-rule-list.d.ts +13 -9
  208. package/dist/permission-rule-list.js +7 -3
  209. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  210. package/dist/plugins/approval-ask-store-file.js +64 -11
  211. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  212. package/dist/plugins/approval-ask-store-memory.js +17 -14
  213. package/dist/plugins/approval-ask-store-sql.d.ts +70 -23
  214. package/dist/plugins/approval-ask-store-sql.js +79 -30
  215. package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
  216. package/dist/plugins/checkpoint-store-sql.js +9 -7
  217. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  218. package/dist/plugins/file-history-store-sql.js +24 -19
  219. package/dist/plugins/file-run-store.d.ts +35 -1
  220. package/dist/plugins/file-run-store.js +108 -9
  221. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  222. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  223. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  224. package/dist/plugins/local-checkpoint-store.js +2 -2
  225. package/dist/plugins/local-session-store.d.ts +42 -9
  226. package/dist/plugins/local-session-store.js +83 -33
  227. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  228. package/dist/plugins/local-task-attachment-store.js +67 -43
  229. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  230. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  231. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  232. package/dist/plugins/mailbox-store-sql.js +76 -7
  233. package/dist/plugins/memory-key-guards.d.ts +3 -3
  234. package/dist/plugins/memory-run-store.d.ts +6 -1
  235. package/dist/plugins/memory-run-store.js +11 -1
  236. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  237. package/dist/plugins/memory-sync-store-pg.js +21 -0
  238. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  239. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  240. package/dist/plugins/one-time-migrations.d.ts +58 -2
  241. package/dist/plugins/one-time-migrations.js +161 -4
  242. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  243. package/dist/plugins/org-delivery-quota-store.js +2 -12
  244. package/dist/plugins/park-commit-watch.d.ts +1 -1
  245. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  246. package/dist/plugins/permission-rule-store-file.js +15 -2
  247. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  248. package/dist/plugins/permission-rule-store-sql.js +35 -2
  249. package/dist/plugins/remote-env-device.d.ts +8 -11
  250. package/dist/plugins/remote-env-device.js +6 -3
  251. package/dist/plugins/remote-env-e2b.d.ts +23 -4
  252. package/dist/plugins/remote-env-e2b.js +111 -28
  253. package/dist/plugins/remote-env-k8s.d.ts +61 -22
  254. package/dist/plugins/remote-env-k8s.js +133 -33
  255. package/dist/plugins/remote-env-local-docker.d.ts +15 -3
  256. package/dist/plugins/remote-env-local-docker.js +40 -12
  257. package/dist/plugins/retention-store-sql.d.ts +2 -2
  258. package/dist/plugins/run-store-sql.d.ts +9 -1
  259. package/dist/plugins/run-store-sql.js +9 -1
  260. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  261. package/dist/plugins/sandbox-read-floor.js +2 -7
  262. package/dist/plugins/sql-errors.d.ts +10 -1
  263. package/dist/plugins/sql-errors.js +13 -0
  264. package/dist/plugins/store-backend.d.ts +33 -35
  265. package/dist/plugins/store-backend.js +10 -1
  266. package/dist/plugins/store-contracts.d.ts +50 -1
  267. package/dist/plugins/store-contracts.js +6 -0
  268. package/dist/plugins/task-attachment-store.d.ts +112 -14
  269. package/dist/plugins/task-attachment-store.js +207 -29
  270. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  271. package/dist/plugins/tool-result-store-sql.js +23 -2
  272. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  273. package/dist/plugins/wiring-section-codec.js +55 -0
  274. package/dist/plugins/workspace-fill.d.ts +60 -0
  275. package/dist/plugins/workspace-fill.js +106 -0
  276. package/dist/project-bundle.d.ts +38 -0
  277. package/dist/project-bundle.js +609 -0
  278. package/dist/rule-entry-list.d.ts +3 -2
  279. package/dist/rule-entry-list.js +5 -1
  280. package/dist/rules-consent.d.ts +28 -5
  281. package/dist/rules-consent.js +40 -28
  282. package/dist/run-local.js +11 -13
  283. package/dist/runs.d.ts +12 -8
  284. package/dist/runs.js +6 -8
  285. package/dist/runtime-governance.d.ts +1 -1
  286. package/dist/sandbox-egress-policy.d.ts +4 -1
  287. package/dist/sandbox-egress-policy.js +3 -1
  288. package/dist/sandbox-git-plane.d.ts +31 -12
  289. package/dist/sandbox-git-plane.js +24 -4
  290. package/dist/security.d.ts +9 -0
  291. package/dist/security.js +8 -0
  292. package/dist/server-secret-env.d.ts +4 -0
  293. package/dist/session-error-wire.d.ts +36 -0
  294. package/dist/session-error-wire.js +13 -0
  295. package/dist/session-incarnation.d.ts +1 -1
  296. package/dist/session-notify-door.d.ts +106 -0
  297. package/dist/session-notify-door.js +93 -0
  298. package/dist/session-sync.d.ts +9 -14
  299. package/dist/session-sync.js +5 -5
  300. package/dist/session-workspace.d.ts +3 -13
  301. package/dist/session-workspace.js +0 -9
  302. package/dist/standing-answer-grant.d.ts +5 -0
  303. package/dist/standing-answer-grant.js +10 -0
  304. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  305. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  306. package/dist/task-mcp.d.ts +1 -1
  307. package/dist/task-settings.d.ts +21 -10
  308. package/dist/task-settings.js +46 -13
  309. package/dist/terminal.d.ts +39 -19
  310. package/dist/terminal.js +65 -22
  311. package/dist/tool-approval.d.ts +56 -37
  312. package/dist/tool-approval.js +78 -71
  313. package/dist/trace/core-keyset-guard.d.ts +134 -14
  314. package/dist/trace/engine-notice-wire.d.ts +9 -3
  315. package/dist/trace/engine-notice-wire.js +7 -3
  316. package/dist/trace/exit-seat.d.ts +9 -2
  317. package/dist/trace/exit-seat.js +1 -0
  318. package/dist/trace/ledger-sink.d.ts +10 -6
  319. package/dist/trace/ledger-sink.js +2 -2
  320. package/dist/trace/model-segment.d.ts +55 -25
  321. package/dist/trace/model-segment.js +101 -33
  322. package/dist/trace/project.d.ts +25 -5
  323. package/dist/trace/project.js +22 -0
  324. package/dist/trace/redact.d.ts +61 -9
  325. package/dist/trace/redact.js +1490 -468
  326. package/dist/trace/self-minted-wire.d.ts +745 -24
  327. package/dist/trace/self-minted-wire.js +239 -9
  328. package/dist/trace/wire-projection-faces.d.ts +2 -2
  329. package/dist/trace/wire-projection-faces.js +2 -2
  330. package/dist/verifier-pause-card.d.ts +33 -5
  331. package/dist/verifier-pause-card.js +15 -10
  332. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  333. package/dist/web-admin-client/apply-effective.js +109 -9
  334. package/dist/web-admin-client/effective-keys.js +2 -0
  335. package/dist/web-admin-client/types.d.ts +4 -0
  336. package/dist/workspace-space.d.ts +19 -3
  337. package/dist/workspace-space.js +8 -4
  338. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  339. package/package.json +3 -2
  340. package/wire-changes/server@7.142.0.json +639 -0
  341. package/wire-changes/server@7.143.0.json +1167 -0
@@ -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
@@ -32,6 +32,7 @@ import { handleRunMemoryCapture, RUN_MEMORY_CAPTURE_ROUTES } from "./routes/run-
32
32
  import { handlePermissionMode, RUN_PERMISSION_MODE_ROUTES } from "./routes/run-permission-mode.js";
33
33
  import { handleMcpReconnect, MCP_RECONNECT_ROUTES } from "./routes/mcp-reconnect.js";
34
34
  import { handleBackground, BACKGROUND_ROUTES } from "./routes/background.js";
35
+ import { handleSessionMemorySync, SESSION_MEMORY_SYNC_ROUTES } from "./routes/session-memory-sync.js";
35
36
  import { handleSideQuery, SIDE_QUERY_ROUTES } from "./routes/side-query.js";
36
37
  import { handleTasks, TASKS_ROUTES } from "./routes/tasks.js";
37
38
  import { handleLeader, LEADER_ROUTES } from "./routes/leader.js";
@@ -55,6 +56,7 @@ export const ROUTES = [
55
56
  row({ handler: handlePermissionMode, decls: RUN_PERMISSION_MODE_ROUTES, position: "post-credential", label: "permission-mode" }),
56
57
  row({ handler: handleMcpReconnect, decls: MCP_RECONNECT_ROUTES, position: "post-credential", label: "mcp-reconnect" }),
57
58
  row({ handler: handleBackground, decls: BACKGROUND_ROUTES, position: "post-credential", label: "background" }),
59
+ row({ handler: handleSessionMemorySync, decls: SESSION_MEMORY_SYNC_ROUTES, position: "post-credential", label: "session-memory-sync" }),
58
60
  row({ handler: handleLeader, decls: LEADER_ROUTES, position: "post-credential", label: "leader" }),
59
61
  row({ handler: handleImages, decls: IMAGES_ROUTES, position: "post-credential", label: "images" }),
60
62
  row({ handler: handleApprovalsAssistant, decls: APPROVALS_ASSISTANT_ROUTES, position: "post-credential", label: "approvals-assistant" }),
@@ -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) } : {}),
@@ -528,6 +542,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
528
542
  sendPrincipalRefusal(res, req, deps.config);
529
543
  return;
530
544
  }
545
+ const restatement = readResumeRestatement(await readJson(req));
531
546
  const taskId = match.params.taskId;
532
547
  const run = await runStore.getRun(taskId);
533
548
  if (!run) {
@@ -539,7 +554,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
539
554
  sendError(res, 404, "not_found.run", "task not found");
540
555
  return;
541
556
  }
542
- const out = await resumePreempted(run.sessionId, req, true, { principal, explicitOperator });
557
+ const out = await resumePreempted(run.sessionId, req, true, { principal, explicitOperator }, restatement);
543
558
  sendResumeOutcome(res, out, deps.logger);
544
559
  return;
545
560
  }
@@ -574,13 +589,13 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
574
589
  sendError(res, 400, "request.field_conflict", "decision 'edit' requires a non-empty editedPlan (the operator's revised plan)", fieldConflictFacts("plan_edit", ["decision", "editedPlan"]));
575
590
  return;
576
591
  }
577
- const decidedByReading = readDecidedBy(body.decidedBy, deps.config.directDoorActive);
592
+ const decidedByReading = readDecider(body, deps.config.directDoorActive);
578
593
  switch (decidedByReading.kind) {
579
594
  case "not_in_proof":
580
595
  sendError(res, 400, "decided_by_not_in_proof", DECIDED_BY_NOT_IN_PROOF);
581
596
  return;
582
597
  case "outside_closed_set":
583
- sendError(res, 400, "request.field_invalid", DECIDED_BY_OUTSIDE_CLOSED_SET);
598
+ sendError(res, 400, "request.field_invalid", decidedByReading.message);
584
599
  return;
585
600
  case "accepted":
586
601
  break;
@@ -589,6 +604,9 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
589
604
  throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
590
605
  }
591
606
  }
607
+ const restated = readDecisionDoorRestatement(res, body, deps.config.directDoorActive);
608
+ if (restated === null)
609
+ return;
592
610
  if (decision === "edit" && !isWellFormedUnicode(body.editedPlan)) {
593
611
  sendError(res, 400, "request.field_invalid", "editedPlan must be well-formed Unicode text (a lone surrogate cannot be signed unambiguously)");
594
612
  return;
@@ -704,7 +722,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
704
722
  return;
705
723
  }
706
724
  }
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);
725
+ 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
726
  sendResumeOutcome(res, out, deps.logger);
709
727
  return;
710
728
  }
@@ -716,10 +734,22 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
716
734
  sendError(res, 400, "request.path_malformed", "malformed approval id (invalid percent-encoding)");
717
735
  return;
718
736
  }
737
+ const doorKind = approvalQueueKindOfDoorId(sessionId);
738
+ switch (doorKind) {
739
+ case "peer_inbound":
740
+ await decidePeerInbound(req, res, sessionId, { readJson, config: deps.config, logger: deps.logger, principal, face: defaultPeerInboundFace });
741
+ return;
742
+ case "checkpoint":
743
+ break;
744
+ default: {
745
+ const unreachable = doorKind;
746
+ throw new Error(`approval decide: door id of an unknown kind ${String(unreachable)}`);
747
+ }
748
+ }
719
749
  const body = (await readJson(req));
720
750
  const decision = body?.decision === "approve" ? "approve" : body?.decision === "deny" ? "deny" : undefined;
721
751
  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);
752
+ 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
753
  return;
724
754
  }
725
755
  {
@@ -756,10 +786,6 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
756
786
  sendError(res, 501, "capability.store_required", "approval exemptions need a store backend (DB_BACKEND=mysql|pg|local)");
757
787
  return;
758
788
  }
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
789
  remember = true;
764
790
  }
765
791
  let answer;
@@ -799,13 +825,13 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
799
825
  sendError(res, 400, "feedback_not_in_proof", "feedback is not supported on a direct-door worker (not covered by the decision proof)");
800
826
  return;
801
827
  }
802
- const decidedByReading = readDecidedBy(body.decidedBy, deps.config.directDoorActive);
828
+ const decidedByReading = readDecider(body, deps.config.directDoorActive);
803
829
  switch (decidedByReading.kind) {
804
830
  case "not_in_proof":
805
831
  sendError(res, 400, "decided_by_not_in_proof", DECIDED_BY_NOT_IN_PROOF);
806
832
  return;
807
833
  case "outside_closed_set":
808
- sendError(res, 400, "request.field_invalid", DECIDED_BY_OUTSIDE_CLOSED_SET);
834
+ sendError(res, 400, "request.field_invalid", decidedByReading.message);
809
835
  return;
810
836
  case "accepted":
811
837
  break;
@@ -814,12 +840,15 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
814
840
  throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
815
841
  }
816
842
  }
817
- const decidedBy = decidedByReading.decidedBy;
843
+ const decider = decidedByReading.decider;
844
+ const restated = readDecisionDoorRestatement(res, body, deps.config.directDoorActive);
845
+ if (restated === null)
846
+ return;
818
847
  if (body.feedback !== undefined && decision !== "approve") {
819
848
  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
849
  return;
821
850
  }
822
- if (body.feedback !== undefined && decidedBy !== "person") {
851
+ if (body.feedback !== undefined && decider.decidedBy !== "person") {
823
852
  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
853
  return;
825
854
  }
@@ -871,7 +900,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
871
900
  pendingGrant = { sessionId: grant.overrideSessionId ?? sessionId, toolName: canonical };
872
901
  }
873
902
  : undefined;
874
- const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, hostDecisionFacts(decidedBy, deciderPrincipal), req, answer, binding, grantOnCommit, true, { principal: deciderPrincipal, explicitOperator });
903
+ const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, hostDecisionFacts(decider, deciderPrincipal), req, answer, binding, grantOnCommit, true, { principal: deciderPrincipal, explicitOperator }, restated.restatement);
875
904
  if (pendingGrant !== undefined && decisionCommittedOf(out) && deps.approvalExemptionStore) {
876
905
  try {
877
906
  await deps.approvalExemptionStore.grant(pendingGrant.sessionId, pendingGrant.toolName, deciderPrincipal ?? null);
@@ -907,6 +936,60 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
907
936
  }
908
937
  return assertNever(match.decl.id);
909
938
  }
939
+ async function decidePeerInbound(req, res, id, env) {
940
+ const body = (await env.readJson(req));
941
+ const decision = body?.decision === "approve" ? "approve" : body?.decision === "deny" ? "deny" : undefined;
942
+ if (!decision) {
943
+ sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny' }", retiredDecisionKeyFacts(body)?.extra);
944
+ return;
945
+ }
946
+ {
947
+ const keyIssue = decisionBodyKeyIssue("approval-decide", body);
948
+ if (keyIssue) {
949
+ const retired = retiredDecisionKeyFacts(body);
950
+ sendError(res, 400, "request.body_shape", retired?.error ?? keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys, ...retired?.extra });
951
+ return;
952
+ }
953
+ }
954
+ const notForThisKind = Object.keys(body).filter((k) => k !== "decision").sort();
955
+ if (notForThisKind.length > 0) {
956
+ 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) });
957
+ return;
958
+ }
959
+ let decider = env.principal;
960
+ if (env.config.directDoorActive) {
961
+ const hdr = (n) => {
962
+ const h = req.headers[n];
963
+ return Array.isArray(h) ? h[0] : h;
964
+ };
965
+ 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);
966
+ if (!proof.ok) {
967
+ sendError(res, proof.status, proof.errorCode, proof.error);
968
+ return;
969
+ }
970
+ decider = proof.principal;
971
+ }
972
+ const coord = parsePeerInboundId(id);
973
+ const outcome = coord === undefined ? { kind: "not_found" } : await env.face.decide(peerBoxScope(decider), coord, decision);
974
+ switch (outcome.kind) {
975
+ case "not_found":
976
+ sendError(res, 404, "not_found.approval", "approval not found");
977
+ return;
978
+ case "unrecorded":
979
+ env.logger?.warn("peer_inbound_decision_unrecorded", { id, decision, err: seatText("operator-log-text", String(outcome.error)) });
980
+ 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");
981
+ return;
982
+ case "settled": {
983
+ const reply = { kind: "peer_inbound", id, outcome: outcome.outcome };
984
+ sendJson(res, 200, reply);
985
+ return;
986
+ }
987
+ default: {
988
+ const unreachable = outcome;
989
+ throw new Error(`peer inbound decide: unhandled result ${JSON.stringify(unreachable)}`);
990
+ }
991
+ }
992
+ }
910
993
  const APPROVALS_STREAM_POLL_MS = 3000;
911
994
  function approvalsReadFailureOf(err) {
912
995
  const fault = storeFaultClassOf(err);
@@ -946,6 +1029,12 @@ function redactPendingDisclosures(row) {
946
1029
  const next = redactRiskDescriptor(rd);
947
1030
  return next === rd ? row : { ...row, riskDescriptor: next };
948
1031
  }
1032
+ function peerInboundRowsFor(face, principal, operatorOwnerParam) {
1033
+ if (operatorOwnerParam !== undefined && operatorOwnerParam !== principal)
1034
+ return () => [];
1035
+ const scope = peerBoxScope(principal);
1036
+ return () => face.list(scope);
1037
+ }
949
1038
  function projectPendingForWire(row, deployment) {
950
1039
  const governance = deployment;
951
1040
  const redacted = redactPendingDisclosures(row);
@@ -959,7 +1048,7 @@ function projectPendingForWire(row, deployment) {
959
1048
  classes: row.classes,
960
1049
  deploymentListQuestion,
961
1050
  });
962
- return { ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}) };
1051
+ return { kind: "checkpoint", ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}) };
963
1052
  }
964
1053
  function createSseDeltaLane(res, spec) {
965
1054
  let prev = new Map();
@@ -978,8 +1067,19 @@ function createSseDeltaLane(res, spec) {
978
1067
  },
979
1068
  };
980
1069
  }
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]);
1070
+ export async function streamApprovals(req, res, cs, live, peerInbound, scope, governance, pollMs = APPROVALS_STREAM_POLL_MS) {
1071
+ const keyOf = (p) => {
1072
+ switch (p.kind) {
1073
+ case "checkpoint":
1074
+ return JSON.stringify([p.kind, p.sessionId, p.toolCallId ?? null]);
1075
+ case "peer_inbound":
1076
+ return JSON.stringify([p.kind, p.id]);
1077
+ default: {
1078
+ const unreachable = p;
1079
+ throw new Error(`approvals stream: pending row of an unknown kind ${JSON.stringify(unreachable)}`);
1080
+ }
1081
+ }
1082
+ };
983
1083
  sseHeaders(res);
984
1084
  res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, mode: "approvals-delta", pollMs: APPROVALS_STREAM_POLL_MS })}\n\n`);
985
1085
  let closed = false;
@@ -992,7 +1092,18 @@ export async function streamApprovals(req, res, cs, live, scope, governance, pol
992
1092
  keyOf,
993
1093
  bytesOf: (p) => JSON.stringify({ type: "pending", ...p }),
994
1094
  upsertData: (p, nowMs) => ({ type: "pending", ...p, serverNowMs: nowMs }),
995
- exitData: (p, nowMs) => ({ type: "resolved", sessionId: p.sessionId, toolCallId: p.toolCallId, serverNowMs: nowMs }),
1095
+ exitData: (p, nowMs) => {
1096
+ switch (p.kind) {
1097
+ case "checkpoint":
1098
+ return { type: "resolved", kind: p.kind, sessionId: p.sessionId, toolCallId: p.toolCallId, serverNowMs: nowMs };
1099
+ case "peer_inbound":
1100
+ return { type: "resolved", kind: p.kind, id: p.id, serverNowMs: nowMs };
1101
+ default: {
1102
+ const unreachable = p;
1103
+ throw new Error(`approvals stream: pending row of an unknown kind ${JSON.stringify(unreachable)}`);
1104
+ }
1105
+ }
1106
+ },
996
1107
  });
997
1108
  const liveLane = createSseDeltaLane(res, {
998
1109
  upsertEvent: "live_pending",
@@ -1006,7 +1117,7 @@ export async function streamApprovals(req, res, cs, live, scope, governance, pol
1006
1117
  while (!closed) {
1007
1118
  let snapshot;
1008
1119
  try {
1009
- const pending = (await cs.listPending(scope)).map((r) => projectPendingForWire(r, governance));
1120
+ const pending = [...(await cs.listPending(scope)).map((r) => projectPendingForWire(r, governance)), ...(peerInbound?.() ?? [])];
1010
1121
  snapshot = { pending, livePending: live ? live.listLivePending(scope) : [] };
1011
1122
  }
1012
1123
  catch (err) {
@@ -4,6 +4,7 @@ import { HttpError } from "../../security.js";
4
4
  import { sendJson, sendError } from "../send.js";
5
5
  import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  import { assertNever } from "../route-ctx.js";
7
+ import { attachmentKindOf, readProjectBundle } from "../../project-bundle.js";
7
8
  export async function handleAttachments(req, res, match, ctx) {
8
9
  const { deps } = ctx;
9
10
  const { rateLimited, quotaExceeded, safeDecode, readRawBody } = ctx.helpers;
@@ -55,6 +56,23 @@ export async function handleAttachments(req, res, match, ctx) {
55
56
  }
56
57
  throw err;
57
58
  }
59
+ const kind = attachmentKindOf(mime);
60
+ switch (kind) {
61
+ case "project_bundle": {
62
+ const reading = await readProjectBundle(body);
63
+ if (!reading.ok) {
64
+ sendError(res, 400, "attachment.bundle_invalid", `not a valid project bundle (${reading.reason}): ${reading.detail}`, { reason: reading.reason, ...(reading.path !== undefined ? { path: reading.path } : {}) });
65
+ return;
66
+ }
67
+ break;
68
+ }
69
+ case "attachment":
70
+ break;
71
+ default: {
72
+ const unreachable = kind;
73
+ throw new Error(`unhandled attachment kind: ${String(unreachable)}`);
74
+ }
75
+ }
58
76
  const rec = {
59
77
  id: uuidv7(),
60
78
  owner,
@@ -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";
@@ -124,6 +126,9 @@ export async function handleCapabilities(req, res, match, ctx) {
124
126
  memoryOrigin: facts.memoryOrigin(deps).available,
125
127
  memoryConsolidationDriver: facts.memoryConsolidation(deps).available,
126
128
  memoryOptOutGrant: facts.memoryOptOutGrant(deps).available,
129
+ sessionMemorySync: facts.sessionMemorySync(deps).available,
130
+ memorySyncServe: ((f) => (f.available ? { identity: f.identity } : false))(facts.memorySyncServe(deps)),
131
+ memoryExport: facts.memoryExport(deps).available,
127
132
  outcomeLedger: facts.outcomeLedger(deps).available,
128
133
  permissionRules: rulesLaneForCaller,
129
134
  permissionRulesWrite: rulesLaneForCaller,
@@ -142,8 +147,8 @@ export async function handleCapabilities(req, res, match, ctx) {
142
147
  scratchpadDir: true,
143
148
  subagentSteer: facts.subagentSteer(deps).available,
144
149
  subagentResume: facts.subagentSteer(deps).available,
145
- 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), tightenOnlySeats: true, approvalFrameFamilies: true },
146
- settings: { permissions: { allowGrant: true, allowLayer: true, layers: true, callerTrust: callerTrust(req, deps.config).trust } },
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 },
151
+ settings: { permissions: { allowGrant: true, allowLayer: true, layers: true, layeredAllow: true, callerTrust: callerTrust(req, deps.config).trust } },
147
152
  permissionModeAuto: {
148
153
  accepted: true,
149
154
  classifierSeat: deps.autoModeFace?.seatMounted === true,
@@ -190,6 +195,7 @@ export async function handleCapabilities(req, res, match, ctx) {
190
195
  approvalFrameFamilies: [...facts.approvalFrameFamilies(streamApprovalFact)],
191
196
  approvalDecisionNote: true,
192
197
  approvalDecidedBy: true,
198
+ approvalDecidedVia: true,
193
199
  planReviewCheckpointId: true,
194
200
  permissionModes: [...PERMISSION_MODES],
195
201
  sessionReadRedaction: true,
@@ -235,7 +241,7 @@ export async function handleCapabilities(req, res, match, ctx) {
235
241
  configCatalog: facts.configCatalog(deps).available,
236
242
  sealedKey: projectSealedKeyCapability(deps.sealedKeyPublic),
237
243
  workspaceSync: ((space, cache) => space.available || cache.available ? { ...(space.available ? space.policy : {}), ...(cache.available ? { cache: cache.cache } : {}) } : undefined)(facts.workspaceSpace(deps.config), facts.workspaceCache(deps.config)),
238
- credentialPlane: { sandboxGit: deps.sandboxGitPlane ?? judgeSandboxGitPlane(sandboxGitPlanePremisesOf(deps.config, {})) },
244
+ credentialPlane: { sandboxGit: deps.sandboxGitPlane?.() ?? judgeSandboxGitPlane(sandboxGitPlanePremisesOf(deps.config, {})) },
239
245
  }));
240
246
  return;
241
247
  }
@@ -304,7 +310,7 @@ export async function handleCapabilities(req, res, match, ctx) {
304
310
  modelPlane: buildModelPlaneFace(deps.config, {
305
311
  envTableIsPlaceholder: isRosterGateArmed(deps.config.webAdmin),
306
312
  candidateRejection,
307
- modelRefusals: deps.modelRefusalsState?.(),
313
+ latestPlaneRefusal: deps.latestPlaneRefusalState?.(),
308
314
  }),
309
315
  aliases: selectableAliasNames(deps.config.models, atCatalog, deps.config.modelRefusals).map((name) => ({ name, ...mentionBit(name) })),
310
316
  });
@@ -371,6 +377,12 @@ async function answerMcpProbe(res, ctx, answer, outcome, declaredBy) {
371
377
  sendMcpProbeRefusal(res, ctx, buildMcpProbeDeclarationRefusal(), "failed");
372
378
  return;
373
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
+ }
374
386
  throw err;
375
387
  }
376
388
  }