@sema-agent/server 7.132.0 → 7.133.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 (402) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +174 -55
  5. package/deploy/sema-up/README.md +19 -1
  6. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
  7. package/deploy/sema-up/chart/templates/server.yaml +5 -2
  8. package/deploy/sema-up/stack/compose.yaml +3 -0
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  10. package/dist/adoption/plan.d.ts +3 -2
  11. package/dist/adoption/plan.js +7 -5
  12. package/dist/adoption/runner.js +3 -10
  13. package/dist/adoption/sql.d.ts +3 -2
  14. package/dist/adoption/sql.js +3 -5
  15. package/dist/adoption/wire.d.ts +4 -1
  16. package/dist/adoption/wire.js +1 -0
  17. package/dist/approval-ask-audit-store.d.ts +31 -13
  18. package/dist/approval-ask-audit-store.js +272 -74
  19. package/dist/approval-ask-machine.d.ts +15 -0
  20. package/dist/approval-ask-machine.js +6 -0
  21. package/dist/approval-ask-retention.d.ts +136 -0
  22. package/dist/approval-ask-retention.js +102 -0
  23. package/dist/approval-card.d.ts +79 -19
  24. package/dist/approval-card.js +44 -13
  25. package/dist/approval-deny-reasons.d.ts +1 -1
  26. package/dist/approval.d.ts +9 -0
  27. package/dist/approval.js +24 -3
  28. package/dist/audit.d.ts +27 -10
  29. package/dist/audit.js +12 -16
  30. package/dist/bake-runner/main.js +3 -2
  31. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  32. package/dist/boot/execution-env.js +9 -7
  33. package/dist/boot/execution-lane.js +11 -0
  34. package/dist/boot/leader.js +2 -0
  35. package/dist/boot/memory-boundary.js +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +5 -4
  37. package/dist/boot/memory-consolidation.js +7 -6
  38. package/dist/boot/org-delivery.d.ts +28 -0
  39. package/dist/boot/org-delivery.js +20 -0
  40. package/dist/boot/reapers.d.ts +3 -0
  41. package/dist/boot/reapers.js +21 -1
  42. package/dist/boot/resolve-spec.d.ts +3 -0
  43. package/dist/boot/resolve-spec.js +60 -81
  44. package/dist/boot/runner-deps.js +1 -0
  45. package/dist/boot/session-faces.d.ts +1 -0
  46. package/dist/boot/session-faces.js +4 -2
  47. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  48. package/dist/boot/stage-06-runners.d.ts +2 -0
  49. package/dist/boot/stage-06-runners.js +6 -3
  50. package/dist/boot/stage-07-capability-layer.d.ts +2 -0
  51. package/dist/boot/stage-07-capability-layer.js +24 -3
  52. package/dist/boot/stage-08-reapers.d.ts +2 -0
  53. package/dist/boot/stage-08-reapers.js +2 -2
  54. package/dist/boot/stage-09-leader.d.ts +3 -0
  55. package/dist/boot/stage-10-http-server.d.ts +3 -0
  56. package/dist/boot/stage-10-http-server.js +5 -6
  57. package/dist/boot/stores.js +2 -2
  58. package/dist/boot/web-admin.d.ts +4 -0
  59. package/dist/boot/web-admin.js +151 -63
  60. package/dist/boot/workflow-orchestration.d.ts +2 -0
  61. package/dist/boot/workflow-orchestration.js +2 -1
  62. package/dist/brain.js +8 -3
  63. package/dist/breaker-key.d.ts +6 -0
  64. package/dist/breaker-key.js +29 -0
  65. package/dist/caller-text-echo.d.ts +9 -6
  66. package/dist/caller-text-echo.js +8 -11
  67. package/dist/capabilities/center-plugins.d.ts +7 -0
  68. package/dist/capabilities/center-plugins.js +1 -1
  69. package/dist/capabilities/composite-members.d.ts +1 -1
  70. package/dist/capabilities/composite-members.js +1 -1
  71. package/dist/capabilities/execution-lane.js +2 -2
  72. package/dist/capabilities/hands-lane.d.ts +21 -0
  73. package/dist/capabilities/hands-lane.js +10 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -4
  75. package/dist/capabilities/mcp-probe.js +27 -4
  76. package/dist/capabilities/scenarios.d.ts +63 -7
  77. package/dist/capabilities/scenarios.js +39 -7
  78. package/dist/capability-facts.d.ts +34 -4
  79. package/dist/capability-facts.js +28 -2
  80. package/dist/center-config-tier.d.ts +69 -0
  81. package/dist/center-config-tier.js +42 -0
  82. package/dist/checkpoint-identity.d.ts +16 -0
  83. package/dist/checkpoint-identity.js +19 -0
  84. package/dist/config-catalog.d.ts +17 -10
  85. package/dist/config-catalog.js +43 -28
  86. package/dist/config-d-migration.d.ts +3 -1
  87. package/dist/config-d-migration.js +2 -2
  88. package/dist/config-dir-trust.d.ts +114 -0
  89. package/dist/config-dir-trust.js +388 -0
  90. package/dist/config-invariants.d.ts +18 -5
  91. package/dist/config-invariants.js +35 -3
  92. package/dist/config-provider.d.ts +6 -1
  93. package/dist/config-provider.js +6 -2
  94. package/dist/config-types.d.ts +71 -14
  95. package/dist/config-types.js +8 -0
  96. package/dist/config.d.ts +13 -0
  97. package/dist/config.js +121 -59
  98. package/dist/delegated-session-owner.d.ts +185 -0
  99. package/dist/delegated-session-owner.js +119 -0
  100. package/dist/device-protocol.d.ts +5 -3
  101. package/dist/device-protocol.js +1 -1
  102. package/dist/device-ws-hub.js +21 -2
  103. package/dist/device-ws-protocol.d.ts +70 -6
  104. package/dist/device-ws-protocol.js +50 -23
  105. package/dist/elicitation.js +3 -2
  106. package/dist/entry-problems.d.ts +34 -0
  107. package/dist/entry-problems.js +14 -0
  108. package/dist/execution-lane-caps.d.ts +49 -10
  109. package/dist/execution-lane-caps.js +43 -4
  110. package/dist/field-conflict.d.ts +43 -0
  111. package/dist/field-conflict.js +20 -0
  112. package/dist/fleet/fleet-bus.d.ts +80 -6
  113. package/dist/fleet/fleet-bus.js +41 -9
  114. package/dist/hooks/classic-stations.d.ts +10 -5
  115. package/dist/hooks/classic-stations.js +4 -4
  116. package/dist/hooks/command-spawn.d.ts +57 -0
  117. package/dist/hooks/command-spawn.js +139 -0
  118. package/dist/hooks/hook-llm.js +2 -2
  119. package/dist/hooks/hook-runner.d.ts +127 -25
  120. package/dist/hooks/hook-runner.js +668 -287
  121. package/dist/hooks/hook-source.d.ts +44 -0
  122. package/dist/hooks/hook-source.js +40 -0
  123. package/dist/http/active-run-conflict.d.ts +5 -13
  124. package/dist/http/active-run-conflict.js +17 -27
  125. package/dist/http/admission.js +103 -60
  126. package/dist/http/approval-sweeps.js +55 -18
  127. package/dist/http/caller-trust.d.ts +77 -0
  128. package/dist/http/caller-trust.js +143 -0
  129. package/dist/http/checkpoint-ctx.d.ts +16 -16
  130. package/dist/http/checkpoint-ctx.js +13 -10
  131. package/dist/http/claimed-run.d.ts +4 -3
  132. package/dist/http/device-placement.d.ts +2 -4
  133. package/dist/http/device-placement.js +2 -1
  134. package/dist/http/dispatch.js +10 -4
  135. package/dist/http/durable-run-leg.d.ts +4 -32
  136. package/dist/http/durable-run-leg.js +34 -10
  137. package/dist/http/owner-access.d.ts +56 -0
  138. package/dist/http/owner-access.js +42 -0
  139. package/dist/http/principal-gate.d.ts +48 -23
  140. package/dist/http/principal-gate.js +39 -12
  141. package/dist/http/readiness.d.ts +11 -5
  142. package/dist/http/readiness.js +10 -3
  143. package/dist/http/request-guards.d.ts +16 -23
  144. package/dist/http/request-guards.js +30 -15
  145. package/dist/http/resume-ctx-read.d.ts +2 -5
  146. package/dist/http/resume-ctx-read.js +7 -4
  147. package/dist/http/resume-legs.d.ts +3 -4
  148. package/dist/http/resume-legs.js +126 -69
  149. package/dist/http/retired-service-deps.d.ts +43 -0
  150. package/dist/http/retired-service-deps.js +18 -0
  151. package/dist/http/route-ctx.d.ts +44 -28
  152. package/dist/http/route-ctx.js +4 -0
  153. package/dist/http/route-table.js +2 -0
  154. package/dist/http/routes/a2a-serve.js +3 -4
  155. package/dist/http/routes/admin-config-refresh.js +1 -1
  156. package/dist/http/routes/admin-drain.js +1 -1
  157. package/dist/http/routes/admin-memory-optout.js +5 -1
  158. package/dist/http/routes/adoption.js +2 -9
  159. package/dist/http/routes/approvals-assistant.js +49 -48
  160. package/dist/http/routes/background.js +3 -2
  161. package/dist/http/routes/capabilities.js +20 -4
  162. package/dist/http/routes/config-catalog.d.ts +3 -3
  163. package/dist/http/routes/config-catalog.js +1 -1
  164. package/dist/http/routes/devices.js +3 -17
  165. package/dist/http/routes/diagnostics.js +5 -1
  166. package/dist/http/routes/images.js +6 -7
  167. package/dist/http/routes/leader.js +1 -8
  168. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  169. package/dist/http/routes/mcp-reconnect.js +3 -2
  170. package/dist/http/routes/memory-bundle.js +1 -1
  171. package/dist/http/routes/memory-compliance.js +1 -1
  172. package/dist/http/routes/memory-consolidation.js +1 -1
  173. package/dist/http/routes/memory-origin.js +1 -1
  174. package/dist/http/routes/memory-policy.d.ts +1 -1
  175. package/dist/http/routes/memory-policy.js +3 -2
  176. package/dist/http/routes/notify-wake.js +8 -21
  177. package/dist/http/routes/observability.js +2 -2
  178. package/dist/http/routes/principal-inbox.d.ts +39 -0
  179. package/dist/http/routes/principal-inbox.js +76 -0
  180. package/dist/http/routes/retention-ops.js +2 -9
  181. package/dist/http/routes/rules.js +5 -18
  182. package/dist/http/routes/runs.d.ts +5 -0
  183. package/dist/http/routes/runs.js +33 -106
  184. package/dist/http/routes/session-sync.js +15 -28
  185. package/dist/http/routes/sessions.js +30 -40
  186. package/dist/http/routes/side-query.js +6 -4
  187. package/dist/http/routes/tasks.js +50 -14
  188. package/dist/http/routes/workflows.d.ts +351 -0
  189. package/dist/http/routes/workflows.js +153 -119
  190. package/dist/http/send.d.ts +24 -3
  191. package/dist/http/send.js +14 -2
  192. package/dist/http/server.d.ts +17 -18
  193. package/dist/http/server.js +15 -13
  194. package/dist/http/stored-body-replay.js +17 -4
  195. package/dist/http/submit-ctx.js +4 -3
  196. package/dist/http/submit-replay.d.ts +4 -3
  197. package/dist/http/submit-replay.js +5 -4
  198. package/dist/http/wire-types.d.ts +10 -7
  199. package/dist/index.d.ts +1 -1
  200. package/dist/index.js +0 -1
  201. package/dist/lane-home-probe.d.ts +78 -9
  202. package/dist/lane-home-probe.js +78 -12
  203. package/dist/leader/endpoint.d.ts +4 -1
  204. package/dist/leader/endpoint.js +11 -10
  205. package/dist/limit-refusal.d.ts +17 -5
  206. package/dist/limit-refusal.js +4 -1
  207. package/dist/mcp-assertion-header.d.ts +31 -10
  208. package/dist/mcp-assertion-header.js +10 -3
  209. package/dist/mcp-tool-faces.js +1 -3
  210. package/dist/memory-project-layer.d.ts +37 -0
  211. package/dist/memory-project-layer.js +46 -0
  212. package/dist/memory-scope.d.ts +23 -16
  213. package/dist/memory-scope.js +24 -8
  214. package/dist/memory-sync-client.d.ts +27 -13
  215. package/dist/memory-sync-client.js +107 -18
  216. package/dist/model-entry-refusal.d.ts +9 -1
  217. package/dist/model-entry-refusal.js +6 -1
  218. package/dist/model-plane-face.d.ts +51 -0
  219. package/dist/model-plane-face.js +24 -0
  220. package/dist/model-provider.d.ts +5 -0
  221. package/dist/model-provider.js +4 -2
  222. package/dist/model-route-endpoint.d.ts +8 -11
  223. package/dist/model-route-endpoint.js +4 -7
  224. package/dist/model-route-env.d.ts +22 -0
  225. package/dist/model-route-env.js +2 -0
  226. package/dist/model-route-gap.d.ts +60 -30
  227. package/dist/model-route-gap.js +21 -15
  228. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  229. package/dist/observability/corrupt-read-seat.js +9 -0
  230. package/dist/observability/err-text.d.ts +6 -0
  231. package/dist/observability/err-text.js +18 -4
  232. package/dist/observability/fail-open.d.ts +35 -9
  233. package/dist/observability/fail-open.js +35 -9
  234. package/dist/observability/metrics.js +1 -0
  235. package/dist/observability/prompt-manifest.d.ts +3 -17
  236. package/dist/observability/prompt-manifest.js +45 -42
  237. package/dist/observability/tool-trace.js +9 -9
  238. package/dist/operator-ask.d.ts +10 -0
  239. package/dist/operator-ask.js +3 -0
  240. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  241. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  242. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  243. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  244. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  245. package/dist/orchestration/workflow-notify-journal.js +27 -34
  246. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  247. package/dist/orchestration/workflow-park-decision.js +118 -0
  248. package/dist/org-delivery-gate.d.ts +81 -0
  249. package/dist/org-delivery-gate.js +59 -0
  250. package/dist/panel-tool-names.d.ts +23 -0
  251. package/dist/panel-tool-names.js +8 -0
  252. package/dist/parked-decide.d.ts +39 -6
  253. package/dist/parked-decide.js +98 -51
  254. package/dist/parked-revive-surface.d.ts +9 -2
  255. package/dist/parked-revive-surface.js +11 -8
  256. package/dist/permission-rule-list.d.ts +1 -1
  257. package/dist/permission-rule-list.js +2 -2
  258. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  259. package/dist/plugins/approval-ask-store-file.js +21 -0
  260. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  261. package/dist/plugins/approval-ask-store-memory.js +34 -2
  262. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  263. package/dist/plugins/approval-ask-store-sql.js +37 -1
  264. package/dist/plugins/blob-backend.d.ts +38 -25
  265. package/dist/plugins/blob-backend.js +29 -19
  266. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  267. package/dist/plugins/breaker-state-sql.js +4 -4
  268. package/dist/plugins/caching-session-store.d.ts +3 -0
  269. package/dist/plugins/caching-session-store.js +3 -0
  270. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  271. package/dist/plugins/checkpoint-store-sql.js +20 -2
  272. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  273. package/dist/plugins/file-history-store-sql.js +3 -1
  274. package/dist/plugins/file-run-store.d.ts +5 -13
  275. package/dist/plugins/file-run-store.js +9 -0
  276. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  277. package/dist/plugins/fork-routing-session-store.js +12 -1
  278. package/dist/plugins/host-platform.d.ts +2 -0
  279. package/dist/plugins/host-platform.js +2 -1
  280. package/dist/plugins/local-checkpoint-store.d.ts +2 -0
  281. package/dist/plugins/local-checkpoint-store.js +6 -1
  282. package/dist/plugins/local-session-store.d.ts +1 -1
  283. package/dist/plugins/local-session-window.d.ts +39 -0
  284. package/dist/plugins/local-session-window.js +54 -0
  285. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  286. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  287. package/dist/plugins/mailbox-store-sql.js +77 -52
  288. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  289. package/dist/plugins/memory-run-store.d.ts +5 -13
  290. package/dist/plugins/memory-run-store.js +8 -0
  291. package/dist/plugins/one-time-migrations.d.ts +110 -5
  292. package/dist/plugins/one-time-migrations.js +320 -8
  293. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  294. package/dist/plugins/org-delivery-quota-store.js +228 -0
  295. package/dist/plugins/pg-pool.js +39 -7
  296. package/dist/plugins/pg-session-storage.d.ts +17 -4
  297. package/dist/plugins/pg-session-storage.js +29 -6
  298. package/dist/plugins/remote-env-adb.d.ts +6 -12
  299. package/dist/plugins/remote-env-adb.js +12 -2
  300. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  301. package/dist/plugins/remote-env-k8s.js +23 -5
  302. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  303. package/dist/plugins/remote-env-local-docker.js +12 -9
  304. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  305. package/dist/plugins/remote-env-ssh.js +17 -11
  306. package/dist/plugins/run-store-sql.d.ts +16 -14
  307. package/dist/plugins/run-store-sql.js +22 -2
  308. package/dist/plugins/schema-indexes.js +2 -0
  309. package/dist/plugins/send-user-file.js +2 -2
  310. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  311. package/dist/plugins/session-epoch-anchor.js +2 -3
  312. package/dist/plugins/session-store.d.ts +18 -8
  313. package/dist/plugins/session-store.js +15 -4
  314. package/dist/plugins/sql-driver.d.ts +74 -11
  315. package/dist/plugins/sql-driver.js +210 -12
  316. package/dist/plugins/sql-errors.d.ts +23 -0
  317. package/dist/plugins/sql-errors.js +21 -0
  318. package/dist/plugins/store-backend.d.ts +36 -1
  319. package/dist/plugins/store-backend.js +40 -14
  320. package/dist/plugins/store-contracts.d.ts +42 -0
  321. package/dist/plugins/store-contracts.js +1 -0
  322. package/dist/plugins/tidb-pool.d.ts +6 -1
  323. package/dist/plugins/tidb-pool.js +44 -10
  324. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  325. package/dist/plugins/tidb-session-storage.js +2 -2
  326. package/dist/plugins/tidb-session-store.d.ts +24 -4
  327. package/dist/plugins/tidb-session-store.js +28 -4
  328. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  329. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  330. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  331. package/dist/plugins/workflow-run-store-sql.js +50 -9
  332. package/dist/posture-source.d.ts +18 -10
  333. package/dist/posture-source.js +4 -1
  334. package/dist/project-identity.d.ts +45 -6
  335. package/dist/project-identity.js +162 -8
  336. package/dist/project-layer-key.d.ts +28 -0
  337. package/dist/project-layer-key.js +29 -0
  338. package/dist/question.js +3 -2
  339. package/dist/read-deny-entry.d.ts +25 -0
  340. package/dist/read-deny-entry.js +12 -0
  341. package/dist/read-face-posture.d.ts +37 -11
  342. package/dist/read-face-posture.js +32 -3
  343. package/dist/request-key-closure.d.ts +7 -0
  344. package/dist/request-key-closure.js +9 -1
  345. package/dist/request-value-closure.d.ts +5 -0
  346. package/dist/request-value-closure.js +11 -3
  347. package/dist/rule-entry-list.d.ts +26 -2
  348. package/dist/rule-entry-list.js +72 -12
  349. package/dist/rules-consent.js +3 -4
  350. package/dist/run-acceptance.d.ts +19 -3
  351. package/dist/run-acceptance.js +13 -1
  352. package/dist/run-local.js +36 -6
  353. package/dist/runs.d.ts +10 -9
  354. package/dist/runs.js +21 -3
  355. package/dist/runtime-caps-resolver.d.ts +16 -6
  356. package/dist/runtime-caps-resolver.js +18 -5
  357. package/dist/sandbox-pkg-source.d.ts +19 -1
  358. package/dist/sandbox-pkg-source.js +29 -7
  359. package/dist/security.d.ts +87 -9
  360. package/dist/security.js +52 -9
  361. package/dist/session-error-wire.d.ts +19 -2
  362. package/dist/session-error-wire.js +5 -1
  363. package/dist/session-titled-frame.d.ts +33 -0
  364. package/dist/session-titled-frame.js +4 -0
  365. package/dist/session-titler.d.ts +9 -1
  366. package/dist/session-titler.js +15 -4
  367. package/dist/spec-fields.js +21 -2
  368. package/dist/sql-engine-posture.d.ts +3 -1
  369. package/dist/task-cwd.d.ts +1 -1
  370. package/dist/task-mcp.d.ts +3 -3
  371. package/dist/task-mcp.js +7 -66
  372. package/dist/task-settings.d.ts +89 -88
  373. package/dist/task-settings.js +71 -63
  374. package/dist/text-bidi.d.ts +0 -4
  375. package/dist/text-bidi.js +1 -3
  376. package/dist/tighten-only-seats.d.ts +150 -0
  377. package/dist/tighten-only-seats.js +196 -0
  378. package/dist/tool-approval.d.ts +303 -87
  379. package/dist/tool-approval.js +623 -239
  380. package/dist/trace/core-keyset-guard.d.ts +104 -22
  381. package/dist/trace/exit-seat.d.ts +28 -0
  382. package/dist/trace/exit-seat.js +76 -0
  383. package/dist/trace/ledger-events.d.ts +3 -2
  384. package/dist/trace/project.d.ts +80 -12
  385. package/dist/trace/project.js +41 -28
  386. package/dist/trace/projection-table.d.ts +100 -0
  387. package/dist/trace/projection-table.js +130 -0
  388. package/dist/trace/redact.d.ts +54 -0
  389. package/dist/trace/redact.js +778 -152
  390. package/dist/trace/self-minted-wire.d.ts +345 -1
  391. package/dist/trace/self-minted-wire.js +117 -1
  392. package/dist/trace/wire-projection-faces.d.ts +298 -2
  393. package/dist/trace/wire-projection-faces.js +154 -13
  394. package/dist/url-refusal.js +2 -0
  395. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  396. package/dist/web-admin-client/apply-effective.js +56 -5
  397. package/dist/web-admin-client/facade.d.ts +1 -1
  398. package/dist/web-admin-client/facade.js +1 -1
  399. package/dist/web-admin-client/read-face.js +5 -9
  400. package/dist/web-admin-client/skills-mcp.js +4 -5
  401. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  402. package/package.json +7 -3
@@ -1,9 +1,11 @@
1
1
  import { readClosedWordSeat } from "./approval-card.js";
2
2
  import { routeWorkflowPark } from "./orchestration/workflow-agent-session-index.js";
3
+ import { recordFailOpen } from "./observability/fail-open.js";
3
4
  import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
4
- import { backgroundScopesForCheckpointScope, decodeCheckpointScope } from "./security.js";
5
+ import { backgroundScopesForCheckpointScope, decodeCheckpointScope, ownerOfCheckpointScope } from "./security.js";
5
6
  import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
6
7
  import { sameSessionOccupant } from "./session-incarnation.js";
8
+ import { refusalBody } from "./http/refusal-body.js";
7
9
  export async function findParkOrigin(sources, cp) {
8
10
  if (sources.agentStore !== undefined) {
9
11
  let match;
@@ -51,6 +53,33 @@ export async function findParkOrigin(sources, cp) {
51
53
  return undefined;
52
54
  }
53
55
  }
56
+ export async function heldWorkflowParkDecisions(sources, hostSessionId, carried) {
57
+ const entries = [...(carried ?? [])];
58
+ const { workflowParkDecisions: decisions, checkpointStore: cs, workflowAgentSessionIndex: workflowIndex, workflowRunStore } = sources;
59
+ if (decisions !== undefined && cs !== undefined && workflowIndex !== undefined && workflowRunStore !== undefined) {
60
+ const own = new Set(entries.map((e) => String(e.token)));
61
+ const held = await decisions.listByHostSession(hostSessionId).then((rows) => ({ rows }), (err) => ({ err }));
62
+ if ("err" in held) {
63
+ recordFailOpen("server.workflow-park-decision.store-unreadable", `hostSessionId=${hostSessionId} err=${held.err instanceof Error ? held.err.message : String(held.err)}`);
64
+ return entries.length > 0 ? { workflowParkedResume: entries } : {};
65
+ }
66
+ for (const row of held.rows) {
67
+ if (own.has(String(row.token)))
68
+ continue;
69
+ const card = await cs.get(row.token).then((cp) => ({ cp }), (err) => ({ err }));
70
+ if ("err" in card) {
71
+ recordFailOpen("server.workflow-park-decision.card-unreadable", `runId=${row.runId} err=${card.err instanceof Error ? card.err.message : String(card.err)}`);
72
+ continue;
73
+ }
74
+ if (card.cp === null || card.cp.status !== "pending")
75
+ continue;
76
+ const origin = await findParkOrigin({ workflowIndex, workflowRunStore }, { token: row.token, scope: card.cp.scope, sessionId: card.cp.sessionId });
77
+ if (origin?.kind === "workflow")
78
+ entries.push({ runId: origin.runId, token: row.token, outcome: row.outcome });
79
+ }
80
+ }
81
+ return entries.length > 0 ? { workflowParkedResume: entries } : {};
82
+ }
54
83
  function quiet(warn, event, fields) {
55
84
  try {
56
85
  warn?.(event, fields);
@@ -106,21 +135,25 @@ export function denyWorkflowParksUnreadable(origin) {
106
135
  case "older_engine":
107
136
  return {
108
137
  status: 409,
109
- body: {
110
- error: "this decision cannot be matched to the pending step it belongs to: the run's record was written by an earlier engine version, which did not record which run is waiting on each pending step — and that record is the only proof that the approval you are deciding belongs to this run. The decision is therefore NOT delivered, rather than answered with a receipt nothing can redeem: the approval card stays pending and nothing is forged. Resume the run itself instead — `Workflow({resumeFromRunId})` with the runId below — and once the resumed run is waiting on this step again it records itself as the one waiting, after which deciding here works normally. Only if the engine also refuses that resume is the run truly unrecoverable",
138
+ body: refusalBody({
111
139
  errorCode: "decide.workflow_parks_unreadable",
112
- runId: origin.runId,
113
- },
140
+ message: "this decision cannot be matched to the pending step it belongs to: the run's record was written by an earlier engine version, which did not record which run is waiting on each pending step — and that record is the only proof that the approval you are deciding belongs to this run. The decision is therefore NOT delivered, rather than answered with a receipt nothing can redeem: the approval card stays pending and nothing is forged. Resume the run itself instead — `Workflow({resumeFromRunId})` with the runId below — and once the resumed run is waiting on this step again it records itself as the one waiting, after which deciding here works normally. Only if the engine also refuses that resume is the run truly unrecoverable",
141
+ facts: {
142
+ runId: origin.runId,
143
+ },
144
+ }),
114
145
  };
115
146
  case "unreadable":
116
147
  return {
117
148
  status: 409,
118
- body: {
119
- error: "this decision cannot be matched to the pending step it belongs to: this run's record cannot be read (the list of pending steps the run owns is damaged: one of its entries is malformed, or it is not a list at all) — and that list is the only proof that the approval you are deciding belongs to this run. The decision is therefore NOT delivered, rather than answered with a receipt nothing can redeem: the approval card stays pending and nothing is forged. Resuming does not clear this: the damaged record keeps naming this step whatever runs after it, so the decision stays undeliverable until whoever operates this deployment repairs that record (the runId below); after that, deciding here works normally. When the response also carries readableHolderRunId, that run's record is readable and is the latest one waiting on this step — it is where the work can be carried on, but the damaged record above still keeps this decision from being delivered until that record is fixed",
149
+ body: refusalBody({
120
150
  errorCode: "decide.workflow_parks_unreadable",
121
- runId: origin.runId,
122
- ...(origin.readableHolderRunId !== undefined ? { readableHolderRunId: origin.readableHolderRunId } : {}),
123
- },
151
+ message: "this decision cannot be matched to the pending step it belongs to: this run's record cannot be read (the list of pending steps the run owns is damaged: one of its entries is malformed, or it is not a list at all) — and that list is the only proof that the approval you are deciding belongs to this run. The decision is therefore NOT delivered, rather than answered with a receipt nothing can redeem: the approval card stays pending and nothing is forged. Resuming does not clear this: the damaged record keeps naming this step whatever runs after it, so the decision stays undeliverable until whoever operates this deployment repairs that record (the runId below); after that, deciding here works normally. When the response also carries readableHolderRunId, that run's record is readable and is the latest one waiting on this step — it is where the work can be carried on, but the damaged record above still keeps this decision from being delivered until that record is fixed",
152
+ facts: {
153
+ runId: origin.runId,
154
+ ...(origin.readableHolderRunId !== undefined ? { readableHolderRunId: origin.readableHolderRunId } : {}),
155
+ },
156
+ }),
124
157
  };
125
158
  default: {
126
159
  const unhandled = origin.cause;
@@ -131,11 +164,13 @@ export function denyWorkflowParksUnreadable(origin) {
131
164
  export function denyWorkflowParkForked(origin) {
132
165
  return {
133
166
  status: 409,
134
- body: {
135
- error: "this decision cannot be delivered: the same run was resumed more than once and every resumed copy listed in runIds is waiting on this same step, so there is no single run to hand the decision to. Nothing was delivered and the approval card stays pending. Resume one of the runs listed in runIds; once that run is waiting on this step again, deciding here works normally",
167
+ body: refusalBody({
136
168
  errorCode: "decide.workflow_park_forked",
137
- runIds: [...origin.runIds],
138
- },
169
+ message: "this decision cannot be delivered: the same run was resumed more than once and every resumed copy listed in runIds is waiting on this same step, so there is no single run to hand the decision to. Nothing was delivered and the approval card stays pending. Resume one of the runs listed in runIds; once that run is waiting on this step again, deciding here works normally",
170
+ facts: {
171
+ runIds: [...origin.runIds],
172
+ },
173
+ }),
139
174
  };
140
175
  }
141
176
  export function planWorkflowParkedDecide(origin, req) {
@@ -144,11 +179,13 @@ export function planWorkflowParkedDecide(origin, req) {
144
179
  ok: false,
145
180
  deny: {
146
181
  status: 400,
147
- body: {
148
- error: "remember:\"session\" is not supported for this approval: accepting it here only means the decision was handed on for delivery, not that the engine has applied it — an exemption granted now could outlive a decision the engine later rejects. Re-issue the decide without `remember`; the decision itself is accepted normally that way.",
182
+ body: refusalBody({
149
183
  errorCode: "decide.workflow_remember_unsupported",
150
- runId: origin.runId,
151
- },
184
+ message: "remember:\"session\" is not supported for this approval: accepting it here only means the decision was handed on for delivery, not that the engine has applied it — an exemption granted now could outlive a decision the engine later rejects. Re-issue the decide without `remember`; the decision itself is accepted normally that way.",
185
+ facts: {
186
+ runId: origin.runId,
187
+ },
188
+ }),
152
189
  },
153
190
  };
154
191
  }
@@ -158,11 +195,13 @@ export function planWorkflowParkedDecide(origin, req) {
158
195
  ok: false,
159
196
  deny: {
160
197
  status: 409,
161
- body: {
162
- error: "this run was started on its own rather than from a session, so this deployment has nowhere to deliver the decision. Nothing was decided and the approval is still pending — resume the run yourself with the runId below and decide it there",
198
+ body: refusalBody({
163
199
  errorCode: "decide.workflow_host_unknown",
164
- runId: origin.runId,
165
- },
200
+ message: "this run was started on its own rather than from a session, so this deployment has nowhere to deliver the decision. Nothing was decided and the approval is still pending — resume the run yourself with the runId below and decide it there",
201
+ facts: {
202
+ runId: origin.runId,
203
+ },
204
+ }),
166
205
  },
167
206
  };
168
207
  }
@@ -181,29 +220,31 @@ export function planWorkflowParkedDecide(origin, req) {
181
220
  function sessionGone(taskId) {
182
221
  return {
183
222
  status: 409,
184
- body: {
185
- error: "the session this agent belongs to was deleted (or deleted and started again under the same id) while its approval waited, so the approval no longer applies — nothing was resumed",
223
+ body: refusalBody({
186
224
  errorCode: "decide.not_parked",
187
- taskId,
188
- },
225
+ message: "the session this agent belongs to was deleted (or deleted and started again under the same id) while its approval waited, so the approval no longer applies — nothing was resumed",
226
+ facts: {
227
+ taskId,
228
+ },
229
+ }),
189
230
  };
190
231
  }
191
232
  export async function decideParkedAgent(deps, req, match) {
192
233
  const row = match.row;
193
234
  const rememberRoot = req.grantRemember !== undefined ? grantRootOf(row) : undefined;
194
235
  if (req.grantRemember !== undefined && rememberRoot === undefined) {
195
- return { status: 400, body: { error: "remember cannot be applied: this parked agent row carries no root/parent session anchor for the exemption", errorCode: "decide.parked_remember_unsupported", taskId: match.handle } };
236
+ return { status: 400, body: refusalBody({ errorCode: "decide.parked_remember_unsupported", message: "remember cannot be applied: this parked agent row carries no root/parent session anchor for the exemption", facts: { taskId: match.handle } }) };
196
237
  }
197
238
  const pendingTool = req.pendingAction?.toolName;
198
239
  if (pendingTool === ASK_USER_QUESTION_TOOL_NAME && req.decision === "approve" && req.answer === undefined) {
199
- return { status: 400, body: { error: "pending action is AskUserQuestion — approve requires body.answer (the operator's answers[]); deny needs none", errorCode: "decide.parked_answer_required", taskId: match.handle } };
240
+ return { status: 400, body: refusalBody({ errorCode: "decide.parked_answer_required", message: "pending action is AskUserQuestion — approve requires body.answer (the operator's answers[]); deny needs none", facts: { taskId: match.handle } }) };
200
241
  }
201
242
  if (row.name === undefined) {
202
- return { status: 409, body: { error: "parked row carries no agent name — the revive path requires a named agent (row damaged?)", errorCode: "decide.row_unrevivable", taskId: match.handle } };
243
+ return { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "parked row carries no agent name — the revive path requires a named agent (row damaged?)", facts: { taskId: match.handle } }) };
203
244
  }
204
245
  const root = row.rootSessionId;
205
246
  if (root === undefined) {
206
- return { status: 409, body: { error: "this agent's row carries no root session anchor — the revive path cannot tell which session this approval belongs to (row damaged?)", errorCode: "decide.row_unrevivable", taskId: match.handle } };
247
+ return { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "this agent's row carries no root session anchor — the revive path cannot tell which session this approval belongs to (row damaged?)", facts: { taskId: match.handle } }) };
207
248
  }
208
249
  const faceBefore = await deps.sessionFaceOf(root);
209
250
  if (faceBefore === undefined)
@@ -214,14 +255,14 @@ export async function decideParkedAgent(deps, req, match) {
214
255
  if (!claim.ok) {
215
256
  switch (claim.reason) {
216
257
  case "store_unreachable":
217
- return { status: 503, body: { error: "the durable agent store is unreachable — retry", errorCode: "decide.store_unreachable", taskId: match.handle } };
258
+ return { status: 503, body: refusalBody({ errorCode: "decide.store_unreachable", message: "the durable agent store is unreachable — retry", facts: { taskId: match.handle } }) };
218
259
  case "claim_lost":
219
- return { status: 409, body: { error: "the parked agent is already claimed — either a concurrent decide won (re-check the pending list), or a stale claim is pending automatic cleanup (retry after the stale-claim window)", errorCode: "decide.claim_lost", taskId: match.handle } };
260
+ return { status: 409, body: refusalBody({ errorCode: "decide.claim_lost", message: "the parked agent is already claimed — either a concurrent decide won (re-check the pending list), or a stale claim is pending automatic cleanup (retry after the stale-claim window)", facts: { taskId: match.handle } }) };
220
261
  case "binding_broken":
221
262
  case "checkpoint_not_pending":
222
- return { status: 409, body: { error: "the approval gate moved under this decide (re-parked or already settled) — re-check the pending list", errorCode: "decide.gate_moved", taskId: match.handle } };
263
+ return { status: 409, body: refusalBody({ errorCode: "decide.gate_moved", message: "the approval gate moved under this decide (re-parked or already settled) — re-check the pending list", facts: { taskId: match.handle } }) };
223
264
  default:
224
- return { status: 409, body: { error: "the parked agent moved under this decide (stopped or already decided)", errorCode: "decide.not_parked", taskId: match.handle } };
265
+ return { status: 409, body: refusalBody({ errorCode: "decide.not_parked", message: "the parked agent moved under this decide (stopped or already decided)", facts: { taskId: match.handle } }) };
225
266
  }
226
267
  }
227
268
  const ticket = claim.ticket;
@@ -281,7 +322,7 @@ export async function decideParkedAgent(deps, req, match) {
281
322
  toolCallId: `drv-${ticket.claimId}`,
282
323
  ...(surface.mountsCallerTool !== undefined ? { mountsCallerTool: surface.mountsCallerTool } : {}),
283
324
  ...(deps.approverSeatFor !== undefined && seatRoot !== undefined
284
- ? { onAsk: deps.approverSeatFor({ owner: decodeCheckpointScope(req.scope) ?? null, taskId: match.handle, sessionId: seatRoot }) }
325
+ ? { onAsk: deps.approverSeatFor({ owner: ownerOfCheckpointScope(req.scope), taskId: match.handle, sessionId: seatRoot }) }
285
326
  : {}),
286
327
  reviveClaim: {
287
328
  row: { ...ticket.row, parkClaimId: ticket.claimId },
@@ -302,12 +343,14 @@ export async function decideParkedAgent(deps, req, match) {
302
343
  const disp = await rollback("execute_threw");
303
344
  return {
304
345
  status: 500,
305
- body: {
306
- error: `revive failed before the resume was driven: ${e instanceof Error ? e.message : String(e)}`,
346
+ body: refusalBody({
307
347
  errorCode: "decide.revive_failed",
308
- taskId: match.handle,
309
- rollback: disp,
310
- },
348
+ message: `revive failed before the resume was driven: ${e instanceof Error ? e.message : String(e)}`,
349
+ facts: {
350
+ taskId: match.handle,
351
+ rollback: disp,
352
+ },
353
+ }),
311
354
  };
312
355
  }
313
356
  const isErr = typeof receipt === "object" && receipt !== null && receipt.isError === true;
@@ -319,23 +362,27 @@ export async function decideParkedAgent(deps, req, match) {
319
362
  const cause = readClosedWordSeat(receiptDetails, "cause");
320
363
  return {
321
364
  status: 422,
322
- body: {
323
- error: typeof content === "string" ? content : "the parked resume failed during startup",
365
+ body: refusalBody({
324
366
  errorCode: "parked_resume.startup_failed",
325
- taskId: match.handle,
326
- rollback: disp,
327
- ...(cause !== undefined ? { cause } : {}),
328
- },
367
+ message: typeof content === "string" ? content : "the parked resume failed during startup",
368
+ facts: {
369
+ taskId: match.handle,
370
+ rollback: disp,
371
+ ...(cause !== undefined ? { cause } : {}),
372
+ },
373
+ }),
329
374
  };
330
375
  }
331
376
  return {
332
377
  status: 409,
333
- body: {
334
- error: typeof content === "string" ? content : "revive was rejected by the delegation tool",
378
+ body: refusalBody({
335
379
  errorCode: "decide.revive_rejected",
336
- taskId: match.handle,
337
- rollback: disp,
338
- },
380
+ message: typeof content === "string" ? content : "revive was rejected by the delegation tool",
381
+ facts: {
382
+ taskId: match.handle,
383
+ rollback: disp,
384
+ },
385
+ }),
339
386
  };
340
387
  }
341
388
  return { status: 200, body: { taskId: match.handle, status: "resuming", decision: req.decision } };
@@ -19,15 +19,22 @@
19
19
  * 这里的第 ② 条是同一条事实在交座这一处的判据(规则只写在这里,腿只负责读两次)。根删除时把子孙的 parked 卡一并作废 = S-790 件 B
20
20
  * (`boot/session-faces.ts` 的删除协调器)。
21
21
  * · 只对**一层**行填座(`rootSessionId` 在场且 = `parentSessionId`:派它的 run 就是根会话的 run,列表正是那个 run 的挂载)。更深的行
22
- * (孙代:派它的是一个子代,它的挂载是继承裁剪后的子集,行上读不出来)不填 —— 用 boot 期那只裸 Agent、无座 = core 的 fail-closed 臂;
22
+ * (孙代:派它的是一个子代,它的挂载是继承裁剪后的子集,行上读不出来)不填 —— 无座 = core 的 fail-closed 臂;
23
23
  * 行上带继承组成是 core #1112(7.32.x)的半场,到货再改读行。
24
24
  * 近似如实记:重解的列表是**部署缺省**那一份,读不回那一 run 的逐请求差异(`settings.webSearch` 后端、center overlay 场景)——
25
25
  * 座只判 bundle 实例的成员性,继承面是 `[...web, ...scaffolds]`,那两项差异不改变谁在面内。
26
+ *
27
+ * **属主来源(S-1770 / S-1808)与交座是两个独立判据**:赎回工具的停驻挂点(复活的子代再要审批时停驻,`plugins/session-store.ts`)按委派继承律定宿主行属主,
28
+ * 律要的是**委派树根的见证**:根 id(行上本来就带 `rootSessionId`)+ 判断那一刻读到的根行住客(赎回腿 claim 前后两读的那一位 —— 两读不是同一位时赎回腿整条拒,
29
+ * 走不到这里;读数缺席 ⇒ 见证不带住客,挂点那一刻根行在 ⇒ 停驻否决)。所以**每一行**都交出它({@link ScenarioContext} 的 `delegationRoot`):交座的一层行以
30
+ * `{ sessionId: root, delegationRoot }` 重解,不交座的行(更深的行、根住客已变的一层行、读数缺席)以 `{ delegationRoot }` 重解 —— 只喂停驻挂点,不挂任何会话级设施、
31
+ * 不带 principal,交座判据(上面三条)一个字不动。行上没有根 ⇒ boot 期那只裸 Agent(没有根可交 ⇒ 律给哨兵)。挂点那一刻根行已不在 ⇒ 律给哨兵;在而不是见证的那一位
32
+ * (批准之后删了、同 id 被重新登记)⇒ 停驻否决 —— 两件都在挂点那一侧判,与这里无关。
26
33
  */
27
34
  import { type BackgroundAgentRecord, type ToolSpec } from "@sema-agent/core";
28
35
  import type { Scenario } from "./capabilities/scenarios.js";
29
36
  import { type SessionFace } from "./session-incarnation.js";
30
- /** 一次赎回用的面:驱动的 Agent 工具 + (一层行时)与它同出一张列表的继承座。 */
37
+ /** 一次赎回用的面:驱动的 Agent 工具(它的停驻挂点绑着行上的根)+ (交座时)与它同出一张列表的继承座。 */
31
38
  export interface ParkedReviveSurface {
32
39
  readonly reviveTool: ToolSpec;
33
40
  /** 缺席 = 不交座(core fail-closed:复活的子代不继承任何场景层工具)。 */
@@ -7,18 +7,21 @@ export function createParkedReviveSurface(defaultScenario) {
7
7
  return undefined;
8
8
  return (row, identity) => {
9
9
  const root = row.rootSessionId;
10
- if (root === undefined || row.parentSessionId !== root)
10
+ if (root === undefined)
11
11
  return { reviveTool: bootTool };
12
- const face = identity.root;
13
- if (face === undefined || !sameSessionOccupant(face.before, face.after))
14
- return { reviveTool: bootTool };
15
- if ((face.before.owner ?? undefined) !== identity.principal || (face.after.owner ?? undefined) !== identity.principal)
16
- return { reviveTool: bootTool };
17
- const tools = defaultScenario({}, identity.principal, { sessionId: root }).tools;
12
+ const seated = holdsSeat(row.parentSessionId === root, identity);
13
+ const delegationRoot = { sessionId: root, incarnation: identity.root?.after.incarnation ?? null };
14
+ const tools = (seated ? defaultScenario({}, identity.principal, { sessionId: root, delegationRoot }) : defaultScenario({}, undefined, { delegationRoot })).tools;
18
15
  const reviveTool = agentOf(tools);
19
16
  if (reviveTool === undefined)
20
17
  return { reviveTool: bootTool };
21
- return { reviveTool, mountsCallerTool: (t) => tools.includes(t) };
18
+ return seated ? { reviveTool, mountsCallerTool: (t) => tools.includes(t) } : { reviveTool };
22
19
  };
23
20
  }
21
+ function holdsSeat(oneLevel, identity) {
22
+ const face = identity.root;
23
+ if (!oneLevel || face === undefined || !sameSessionOccupant(face.before, face.after))
24
+ return false;
25
+ return (face.before.owner ?? undefined) === identity.principal && (face.after.owner ?? undefined) === identity.principal;
26
+ }
24
27
  //# sourceMappingURL=parked-revive-surface.js.map
@@ -6,7 +6,7 @@
6
6
  * `TaskSpec.allowedTools`(`task-settings.ts` 的 `applyTaskSettings`),运维的 `APPROVAL_GRANT` 投 `RunnerDeps.allowedTools`
7
7
  * (`boot/runner-deps.ts` 的部署姿态座席族)。本口的 `behavior` 型直接是 core 的 `PermissionRule["behavior"]`(不手抄:core 收窄,这里跟着收窄)。
8
8
  *
9
- * 为什么住在一只叶模块里(不留在 `task-settings.ts`):本模块只 import core 与 `text-bidi.ts`(纯叶),读者引它不成环。
9
+ * 为什么住在一只叶模块里(不留在 `task-settings.ts`):本模块只 import core 与契约包 settings-schema(纯叶),读者引它不成环。
10
10
  */
11
11
  import { type PathRuleBases, type PermissionRule, type ToolPolicy } from "@sema-agent/core";
12
12
  /** 一张表里规则条数的上限 —— 请求腿 wire 门(`task-settings.ts` 的 deny / ask 两桶判形)读的也是这一个名字:core 的缺省帽是 256,
@@ -1,5 +1,5 @@
1
1
  import { createPermissionRulePolicy } from "@sema-agent/core";
2
- import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
2
+ import { stripControlAndBidi } from "@sema-agent/settings-schema";
3
3
  export const MAX_PERMISSION_RULES_PER_LIST = 1_000;
4
4
  export function createRuleListPolicy(names, behavior, source, pathBases) {
5
5
  try {
@@ -10,7 +10,7 @@ export function createRuleListPolicy(names, behavior, source, pathBases) {
10
10
  });
11
11
  }
12
12
  catch (e) {
13
- const coreMsg = (e instanceof Error ? e.message : String(e)).replace(CONTROL_AND_BIDI_STRIP_RE, "");
13
+ const coreMsg = stripControlAndBidi(e instanceof Error ? e.message : String(e));
14
14
  throw new Error(`${source.replace("settings.", "")} has rule(s) core cannot compile — ${coreMsg}`, { cause: e });
15
15
  }
16
16
  }
@@ -22,7 +22,7 @@ export declare const ASK_JOURNAL_COMPACT_GROWTH = 2;
22
22
  * 账本的**动词闭集**([ref] 形)。运行期的词表判定与编译期的穷尽 `switch` 读同一份:
23
23
  * 往这里加一个词,{@link FileApprovalAskStore.replayOp} 的 switch 立刻编译红(漏一口不可能发布)。
24
24
  */
25
- declare const JOURNAL_OPS: readonly ["ensureAsk", "transitionAsk", "decideAsk", "claimTerminal", "claimTerminalRefuse", "expireAsk", "bindBatch", "abortBatch", "deferReconcile", "resolveProvisional", "deleteByTask"];
25
+ declare const JOURNAL_OPS: readonly ["ensureAsk", "transitionAsk", "decideAsk", "claimTerminal", "claimTerminalRefuse", "expireAsk", "bindBatch", "abortBatch", "deferReconcile", "resolveProvisional", "deleteByTask", "reapSettledAsks", "reapEmptyBatches"];
26
26
  type JournalOpWord = (typeof JOURNAL_OPS)[number];
27
27
  /** 运行期词表(上面的数组)与类型面动词维({@link JournalCall})的**双向**对表 —— 任一边加词而另一边
28
28
  * 没跟上,这个别名解析成 `never`,{@link JOURNAL_OP_TABLE_GATE} 的赋值当场编译红。 */
@@ -96,6 +96,17 @@ type JournalCall = {
96
96
  } | {
97
97
  op: "deleteByTask";
98
98
  taskId: string;
99
+ }
100
+ /** 留存律的问账两半(挂靠集落成数组;Set 不是 JSON)。更老的读者不认这两个动词 ⇒ 拒启并点名出路(与 `claimTerminalRefuse` 同一条降级纪律)。 */
101
+ | {
102
+ op: "reapSettledAsks";
103
+ cutoffMs: number;
104
+ limit: number;
105
+ keepAnchors: string[];
106
+ } | {
107
+ op: "reapEmptyBatches";
108
+ cutoffMs: number;
109
+ limit: number;
99
110
  };
100
111
  export declare class FileApprovalAskStore implements ApprovalAskStore {
101
112
  /** 语义内核(转移判定的唯一真源)。本类对它只做两件事:重放、代理。 */
@@ -171,6 +182,10 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
171
182
  deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;
172
183
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
173
184
  deleteByTask(taskId: string): Promise<void>;
185
+ /** 留存律的问行半场(谓词在内核,见接口注)。空的一次(内核的纯读选择为空)不落账本、不碰内存;否则照常「先落账、再翻内存」。 */
186
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
187
+ /** 留存律的批行半场(同上)。 */
188
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
174
189
  listByState(state: AskState, limit: number): Promise<AskRow[]>;
175
190
  listPendingByTask(taskId: string, signal?: AbortSignal): Promise<AskRow[]>;
176
191
  listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
@@ -21,6 +21,8 @@ const JOURNAL_OPS = [
21
21
  "deferReconcile",
22
22
  "resolveProvisional",
23
23
  "deleteByTask",
24
+ "reapSettledAsks",
25
+ "reapEmptyBatches",
24
26
  ];
25
27
  const JOURNAL_OP_WORDS = new Set(JOURNAL_OPS);
26
28
  export const JOURNAL_OP_TABLE_GATE = true;
@@ -159,6 +161,11 @@ export class FileApprovalAskStore {
159
161
  refuse("call record without a clock reading");
160
162
  if (probe.op === "claimTerminal" && !JOURNAL_V1_CLAIM_INTENT_WORDS.has(String(raw.intent)))
161
163
  refuse("claimTerminal intent outside this build's journal set (a newer server wrote it)");
164
+ if (probe.op === "reapSettledAsks") {
165
+ const anchors = raw.keepAnchors;
166
+ if (!Array.isArray(anchors) || !anchors.every((a) => typeof a === "string"))
167
+ refuse("reapSettledAsks record without a string-array keepAnchors");
168
+ }
162
169
  const rec = raw;
163
170
  this.pinnedNow = rec.t;
164
171
  try {
@@ -197,6 +204,10 @@ export class FileApprovalAskStore {
197
204
  return this.core.resolveProvisional(rec.askId, rec.from, rec.to, rec.patch);
198
205
  case "deleteByTask":
199
206
  return this.core.deleteByTask(rec.taskId);
207
+ case "reapSettledAsks":
208
+ return this.core.reapSettledAsks(rec.cutoffMs, rec.limit, new Set(rec.keepAnchors));
209
+ case "reapEmptyBatches":
210
+ return this.core.reapEmptyBatches(rec.cutoffMs, rec.limit);
200
211
  default: {
201
212
  const unreachable = rec;
202
213
  throw new Error(`FileApprovalAskStore: unreachable journal op ${JSON.stringify(unreachable)}`);
@@ -244,6 +255,16 @@ export class FileApprovalAskStore {
244
255
  async deleteByTask(taskId) {
245
256
  return this.write({ op: "deleteByTask", taskId }, (c) => this.core.deleteByTask(c.taskId));
246
257
  }
258
+ async reapSettledAsks(cutoffMs, limit, keepAnchors) {
259
+ if (this.core.planReapSettledAsks(cutoffMs, limit, keepAnchors).length === 0)
260
+ return 0;
261
+ return this.write({ op: "reapSettledAsks", cutoffMs, limit, keepAnchors: [...keepAnchors].sort() }, (c) => this.core.reapSettledAsks(c.cutoffMs, c.limit, new Set(c.keepAnchors)));
262
+ }
263
+ async reapEmptyBatches(cutoffMs, limit) {
264
+ if (this.core.planReapEmptyBatches(cutoffMs, limit).length === 0)
265
+ return 0;
266
+ return this.write({ op: "reapEmptyBatches", cutoffMs, limit }, (c) => this.core.reapEmptyBatches(c.cutoffMs, c.limit));
267
+ }
247
268
  listByState(state, limit) {
248
269
  return this.core.listByState(state, limit);
249
270
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * 🔴 **S-384:本类同时是 `FileApprovalAskStore` 的内核**(local 车道的持久形不再是「第二份手抄」——
8
8
  * 它把本类**原样**包起来,每次写前先把**调用本身**落 JSONL、重放时用同一只内核重放同一串调用)。
9
- * 为此本类多了三件、且**只**多这三件(全是数据面,零转移判定):
9
+ * 为此本类多了这几件、且**只**多这几件(全是数据面,零转移判定):
10
10
  * · {@link InMemoryApprovalAskStore.constructor} 的可注入钟 `now` —— 五个动词(decideAsk / claimTerminal /
11
11
  * expireAsk / bindBatch / abortBatch)原先直读 `Date.now()`,那是重放路上唯一的不确定源:同一串调用
12
12
  * 重放出来的 `updatedAtMs`/`decidedAtMs` 必须与当初**逐字节**相同,否则「重放等价」是假的。
@@ -16,6 +16,8 @@
16
16
  * · {@link InMemoryApprovalAskStore.exportRows} / {@link InMemoryApprovalAskStore.importRows} —— 压实用的
17
17
  * 整本快照口(纯搬行,不经任何谓词)。压实要把「一串调用」折成「一串终局行」,没有这一对就只能在
18
18
  * File 形里另写一套「哪些行还算数」的判据 —— 那才是第二份手抄。
19
+ * · {@link InMemoryApprovalAskStore.planReapSettledAsks} / {@link InMemoryApprovalAskStore.planReapEmptyBatches} —— 两只留存动词的
20
+ * **纯读选择**(删哪些行)。File 形据它判「这一次有没有东西可删」,空的一次不落账本;删除本身仍是接口动词,与选择共用这一只。
19
21
  *
20
22
  * 行为口径与 `SqlApprovalAskStore` 逐条对齐:
21
23
  * - ensureAsk 幂等 upsert(ask_id 已在 ⇒ 返回既有行,不改写;batch 行若不存在则以 OPEN 补齐)。
@@ -106,6 +108,21 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
106
108
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
107
109
  deleteByTask(taskId: string): Promise<void>;
108
110
  getBatch(batchId: string): Promise<BatchRow | null>;
111
+ /**
112
+ * 留存律的问行半场(谓词见接口注,与 SQL twin 的 `DELETE … LIMIT` 同答)。方法体全同步(零 `await`)⇒ File 形把它当一次落账的调用
113
+ * 重放时逐字复现(cutoff / limit / 挂靠集都是调用实参,不读钟)。
114
+ */
115
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
116
+ /** 留存律的批行半场(谓词见接口注,与 SQL twin 同答;同上,全同步)。 */
117
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
118
+ /**
119
+ * 两只留存动词会删哪些行(纯读,零写)。File 形据此判「这一次有没有东西可删」—— 空的一次不落账本(否则每次清扫都往账本里写一条什么都
120
+ * 没做的调用)。与删除共用这一只选择,没有第二份谓词。选哪几只:按 `(updatedAtMs, id)` 升序取前 `limit` 只 —— 确定性只为重放,律本身
121
+ * 不依赖次序(SQL twin 不排序)。
122
+ */
123
+ planReapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): readonly string[];
124
+ /** {@link reapEmptyBatches} 的选择(同上)。 */
125
+ planReapEmptyBatches(cutoffMs: number, limit: number): readonly string[];
109
126
  }
110
127
  export type { AskDecision, AskState, BatchState };
111
128
  //# sourceMappingURL=approval-ask-store-memory.d.ts.map
@@ -1,6 +1,6 @@
1
- import { canAskTransition } from "../approval-ask-machine.js";
1
+ import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
2
2
  import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
3
- import { askRowOnCall, assertDecideSettledBy, assertIdempotencyKeyShape, assertIntegralEpochMs, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./approval-ask-store-sql.js";
3
+ import { askRowOnCall, assertDecideSettledBy, assertIdempotencyKeyShape, assertIntegralEpochMs, assertReapBounds, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./approval-ask-store-sql.js";
4
4
  function applyPatch(row, patch) {
5
5
  if (patch.decision !== undefined)
6
6
  row.decision = patch.decision;
@@ -359,5 +359,37 @@ export class InMemoryApprovalAskStore {
359
359
  const b = this.batches.get(batchId);
360
360
  return b ? { ...b } : null;
361
361
  }
362
+ async reapSettledAsks(cutoffMs, limit, keepAnchors) {
363
+ const ids = this.planReapSettledAsks(cutoffMs, limit, keepAnchors);
364
+ for (const id of ids)
365
+ this.asks.delete(id);
366
+ return ids.length;
367
+ }
368
+ async reapEmptyBatches(cutoffMs, limit) {
369
+ const ids = this.planReapEmptyBatches(cutoffMs, limit);
370
+ for (const id of ids)
371
+ this.batches.delete(id);
372
+ return ids.length;
373
+ }
374
+ planReapSettledAsks(cutoffMs, limit, keepAnchors) {
375
+ assertReapBounds(cutoffMs, limit);
376
+ const anchored = (r) => keepAnchors.has(r.taskId) || keepAnchors.has(r.sessionId) || keepAnchors.has(r.sourceTaskId);
377
+ return [...this.asks.values()]
378
+ .filter((r) => ASK_TERMINAL.has(r.state) && !r.provisional && r.updatedAtMs < cutoffMs && !anchored(r))
379
+ .sort((a, b) => a.updatedAtMs - b.updatedAtMs || (a.askId < b.askId ? -1 : a.askId > b.askId ? 1 : 0))
380
+ .slice(0, limit)
381
+ .map((r) => r.askId);
382
+ }
383
+ planReapEmptyBatches(cutoffMs, limit) {
384
+ assertReapBounds(cutoffMs, limit);
385
+ const occupied = new Set();
386
+ for (const r of this.asks.values())
387
+ occupied.add(r.batchId);
388
+ return [...this.batches.values()]
389
+ .filter((b) => b.updatedAtMs < cutoffMs && !occupied.has(b.batchId))
390
+ .sort((a, b) => a.updatedAtMs - b.updatedAtMs || (a.batchId < b.batchId ? -1 : a.batchId > b.batchId ? 1 : 0))
391
+ .slice(0, limit)
392
+ .map((b) => b.batchId);
393
+ }
362
394
  }
363
395
  //# sourceMappingURL=approval-ask-store-memory.js.map
@@ -268,6 +268,8 @@ export type DecideResult = {
268
268
  * 隐式截断 —— 两方言不同答;这里改成两方言同一句 TypeError(响亮,点名字段与值),铸点侧(`tool-approval.ts`
269
269
  * 的 deadline 铸点)负责给整数,本守卫只拦漏网。 */
270
270
  export declare function assertIntegralEpochMs(field: string, value: number): void;
271
+ /** 留存动词两只的入参门(三 twin 同判):cutoff 是整数毫秒、limit 是正整数(limit 在 SQL twin 里内联进语句)。 */
272
+ export declare function assertReapBounds(cutoffMs: number, limit: number): void;
271
273
  export declare function assertIdempotencyKeyShape(key: string): void;
272
274
  /** `settled_by` 列宽 = 这一格的唯一尺(两方言 DDL 与下面的形门读同一个常量,不手抄第二份)。 */
273
275
  export declare const MAX_SETTLED_BY_CHARS = 16;
@@ -510,6 +512,21 @@ export interface ApprovalAskStore {
510
512
  getByIdempotencyKey(taskId: string, key: string): Promise<AskRow | null>;
511
513
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
512
514
  deleteByTask(taskId: string): Promise<void>;
515
+ /**
516
+ * **审批账本留存律的问账半场之一:问行**(律、节律与「还能动的 run」一处成文在 `approval-ask-retention.ts`;维护 tick 是唯一调用方)。
517
+ * 删至多 `limit` 只**结了**的问行 —— `state ∈ ASK_TERMINAL` ∧ `provisional = false`(provisional 行还能被 {@link resolveProvisional} 改判,没结)
518
+ * ∧ `updatedAtMs < cutoffMs` ∧ 行挂靠的三格(`taskId` / `sessionId` / `sourceTaskId`)都不在 `keepAnchors`(还能动的东西的挂靠集:
519
+ * 它还会回读这些行 —— 会话授权的争议判据 {@link listByCall} 把「这次调用在腿内没有行」读成可以代答)。非终态行多老都不删(它们归对账器
520
+ * 与起服收敛)。只删不改。
521
+ * 一条语句:要么删了并交回删掉的行数,要么一行没删就抛。三形同答(InMemory = 内核;File 形把这次调用照常落成一条账本动词,
522
+ * 空的一次不落;SQL 两方言 = 一条 `DELETE … LIMIT`,行谓词,多副本同扫只是一方删到零行)。
523
+ */
524
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
525
+ /**
526
+ * **问账半场之二:批行**。删至多 `limit` 只批里已经**没有任何问行**、且自己的 `updatedAtMs < cutoffMs` 的批 —— 批是问的容器,问走光了它才走。
527
+ * 一条语句,同上;三形同答。
528
+ */
529
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
513
530
  /** 🔴 车1 追加(不在设计定稿 §4 的接口字面清单里):设计的 `ApprovalAskStore` 接口本身没有批读方法,
514
531
  * 但 §6 测试钉(3/6/7 条)要求断言批行的 state/boundAskId 变化——没有读口就无法验证 store 真做对了
515
532
  * 它自己承诺的事。加一个纯观测方法(不改变任何写路径行为),SQL 双方言 + InMemory 三形都实现,
@@ -667,6 +684,14 @@ export declare class SqlApprovalAskStore implements ApprovalAskStore {
667
684
  deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;
668
685
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
669
686
  deleteByTask(taskId: string): Promise<void>;
687
+ /**
688
+ * 留存律的问行半场(谓词见接口注)。一条带 `LIMIT` 的 `DELETE`(PG 无 `DELETE … LIMIT` ⇒ `ctid IN (SELECT … LIMIT)`,与
689
+ * `permission-rule-store-sql.ts` 的清扫同形);终态闭集从 {@link ASK_TERMINAL} 展开成占位符,不在 SQL 里手抄态名;挂靠集对三格各一句
690
+ * (MySQL `NOT IN (?, …)` 三次展开、PG 同一只 `text[]` 参数用三次 `<> ALL`),空集不加。`limit` 先过整数门再内联(本仓常量,门只防将来的误用)。
691
+ */
692
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
693
+ /** 留存律的批行半场(谓词见接口注):一条带 `LIMIT` 的 `DELETE`,批里有没有问 = 对问表的 `NOT EXISTS`。 */
694
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
670
695
  getBatch(batchId: string): Promise<BatchRow | null>;
671
696
  }
672
697
  /** MySQL-protocol(TiDB)绑定。 */