@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,5 +1,5 @@
1
1
  import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
2
- import { canAskTransition } from "../approval-ask-machine.js";
2
+ import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
3
3
  import { APPROVAL_ENDING_CAUSES, causeOfAskRow } from "../approval-deny-reasons.js";
4
4
  import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
5
5
  import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, readTriStateJsonValueColumn } from "./sql-json-column.js";
@@ -9,6 +9,11 @@ export function assertIntegralEpochMs(field, value) {
9
9
  throw new TypeError(`approval-ask store: ${field} must be an integer epoch-ms (BIGINT column), got ${String(value)}`);
10
10
  }
11
11
  }
12
+ export function assertReapBounds(cutoffMs, limit) {
13
+ assertIntegralEpochMs("cutoffMs", cutoffMs);
14
+ if (!Number.isSafeInteger(limit) || limit <= 0)
15
+ throw new Error(`approval-ask-store: reap limit must be a positive integer, got ${String(limit)}`);
16
+ }
12
17
  export function assertIdempotencyKeyShape(key) {
13
18
  if (key.length === 0 || key.length > 255 || key.trim() !== key) {
14
19
  throw new Error(`invalid idempotency key: must be 1-255 chars with no leading/trailing whitespace (got ${JSON.stringify(key.slice(0, 32))}${key.length > 32 ? "…" : ""}, length ${key.length})`);
@@ -722,6 +727,37 @@ export class SqlApprovalAskStore {
722
727
  await this.db.query(this.q(`DELETE FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ?`, `DELETE FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1`), [taskId]);
723
728
  await this.db.query(this.q(`DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE task_id = ?`, `DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE task_id = $1`), [taskId]);
724
729
  }
730
+ async reapSettledAsks(cutoffMs, limit, keepAnchors) {
731
+ assertReapBounds(cutoffMs, limit);
732
+ const terminal = [...ASK_TERMINAL];
733
+ const keep = [...keepAnchors];
734
+ const dialect = this.db.dialect;
735
+ let n = 0;
736
+ const statePh = terminal.map(() => ph(dialect, ++n)).join(", ");
737
+ const cutPh = ph(dialect, ++n);
738
+ const ANCHOR_COLUMNS = ["task_id", "session_id", "source_task_id"];
739
+ let keepSql = "";
740
+ let keepParams = [];
741
+ if (keep.length > 0 && dialect === "tidb") {
742
+ const list = keep.map(() => "?").join(", ");
743
+ keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} NOT IN (${list})`).join("");
744
+ keepParams = ANCHOR_COLUMNS.flatMap(() => keep);
745
+ }
746
+ else if (keep.length > 0) {
747
+ const arr = ph(dialect, ++n);
748
+ keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} <> ALL(${arr}::text[])`).join("");
749
+ keepParams = [keep];
750
+ }
751
+ const where = `state IN (${statePh}) AND provisional = 0 AND updated_at_ms < ${cutPh}${keepSql}`;
752
+ const params = [...terminal, cutoffMs, ...keepParams];
753
+ const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_ASK_TABLE} WHERE ${where} LIMIT ${limit}`, `DELETE FROM ${APPROVAL_ASK_TABLE} WHERE ctid IN (SELECT ctid FROM ${APPROVAL_ASK_TABLE} WHERE ${where} LIMIT ${limit})`), params);
754
+ return res.affected;
755
+ }
756
+ async reapEmptyBatches(cutoffMs, limit) {
757
+ assertReapBounds(cutoffMs, limit);
758
+ const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE updated_at_ms < ? AND NOT EXISTS (SELECT 1 FROM ${APPROVAL_ASK_TABLE} a WHERE a.batch_id = ${APPROVAL_BATCH_TABLE}.batch_id) LIMIT ${limit}`, `DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE ctid IN (SELECT b.ctid FROM ${APPROVAL_BATCH_TABLE} b WHERE b.updated_at_ms < $1 AND NOT EXISTS (SELECT 1 FROM ${APPROVAL_ASK_TABLE} a WHERE a.batch_id = b.batch_id) LIMIT ${limit})`), [cutoffMs]);
759
+ return res.affected;
760
+ }
725
761
  async getBatch(batchId) {
726
762
  const { rows } = await this.db.query(this.q(`SELECT ${BATCH_COLS} FROM ${APPROVAL_BATCH_TABLE} WHERE batch_id = ?`, `SELECT ${BATCH_COLS} FROM ${APPROVAL_BATCH_TABLE} WHERE batch_id = $1`), [batchId]);
727
763
  return rows[0] ? mapBatchRow(rows[0]) : null;
@@ -1,29 +1,25 @@
1
1
  import type { Pool as MysqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
- import { type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
3
+ import { type SqlEngineFacts, type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
4
+ import { type Logger } from "../observability/logger.js";
4
5
  /**
5
6
  * 文本协议的 blob 膨胀律 —— mysql2 `query()` 把 Buffer 参数写成 `X'<hex>'`,一字节两个字符,所以承载一个 blob 的语句至少是它的 2 倍。
6
7
  * 包墙折成「能装下多大的 blob」只用这一只常量,推导里不手写 `/ 2`。
7
8
  */
8
9
  export declare const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
9
10
  /**
10
- * 包墙下能装多大的 blob:语句里的载荷量({@link packetWallStatementBytes},与提交体受理口读同一个折算)再按 {@link SQL_TEXT_PROTOCOL_BLOB_INFLATION} 折半。
11
- * 提交体按原样进语句、没有膨胀,blob 以十六进制字面进语句、有 2× —— 膨胀律是 blob 腿自己的,余量不是。
11
+ * 包墙下能装多大的 blob:语句里的载荷量({@link packetWallStatementBytes})再按 {@link SQL_TEXT_PROTOCOL_BLOB_INFLATION} 折半。
12
+ * blob 以十六进制字面进语句、有 2× —— 膨胀律是 blob 腿自己的,余量不是。
12
13
  */
13
14
  export declare function packetWallBlobCap(maxPacketBytes: number): number;
14
- /**
15
- * **事实本身还没建立**时 MySQL 协议方言的缺省帽(池上还没借出过连接,或最近一次连接初始化被拒:`SqlDriver.facts === null`)= 事实缺席时的墙
16
- * (`sql-driver.ts` `SQL_FACTS_ABSENT_WALLS`,判官 `sqlWallsOf`;提交受理口读同一只)走下面同一个推导 —— 不是第二份落点,只是那个推导在这组墙上的值
17
- * (今天 = TiDB 缺省行墙走 blob 行的折算 6 MiB − 1 KiB:最矮的包墙缺省 16 MiB 扣语句余量再折半仍近 8 MiB,行腿更矮)。
18
- */
19
- export declare const SQL_BLOB_FACTS_ABSENT_CAP_BYTES: number;
20
15
  /**
21
16
  * SQL 快照 blob 的**缺省**帽(运维没设 `SNAPSHOT_BLOB_SQL_MAX_BYTES` 时)的唯一推导 —— 入参是判官 `sqlWallsOf` 给的墙(不读事实、不判缺席、不看方言),**两堵墙取最小**:
22
17
  * 行墙腿(`SqlEngineFacts.maxRowBytes`,TiDB:{@link rowWallPayloadBytes} 的 `blob` = 行墙 − blob 行的余量 1 KiB —— 按这一行的真实形算,不借 JSON 提交体行的 64 KiB)
23
18
  * 与包墙腿(`SqlEngineFacts.maxPacketBytes`,InnoDB 与 TiDB 都有:{@link packetWallBlobCap} = ⌊(包墙 − 1 KiB 语句余量) ÷ 2⌋)。余量只在 `sql-driver.ts` 定义一次
24
19
  * (墙 × 行的形各一只),提交体受理口(`http/request-guards.ts` `refuseSubmitBody`)读同一张表的 `json` 那一只,不各抄。
25
- * 墙的 `null` 只表示「这堵墙不存在」;两堵都不存在(PG —— 扩展协议无这两堵墙,事实在不在都一样 —— 或没有 SQL 后端)⇒ `undefined` 不设帽。事实本身缺席时
26
- * 墙由判官给(MySQL 协议按最保守的墙),走同一个推导 = {@link SQL_BLOB_FACTS_ABSENT_CAP_BYTES}。显式旋钮是运维的策略帽,在本推导之上(见 {@link sqlBlobCapBytes})。
20
+ * 墙的 `null` 只表示「这堵墙不存在」;两堵都不存在(PG —— 扩展协议无这两堵墙 —— 或没有 SQL 后端)⇒ `undefined` 不设帽。
21
+ * 「事实本身缺席」不是这里的入参:店的写前判读的是借出连接自己的事实(恒已建立,见 {@link SqlConnectionReading}),缺席的读法只在提交受理口读池级读口时出现(判官 `sqlWallsOf` 给墙)。
22
+ * 显式旋钮是运维的策略帽,在本推导之上(见 {@link sqlBlobCapBytes})。
27
23
  *
28
24
  * 两堵墙的真库读数(clay 拍 a+c,2026-07-27;k3s8 tidb-test v7.5.1 默认配置探针):7 MiB / 31 MiB = `entry too large`(行墙,默认 6291456)、
29
25
  * 32 MiB = `max_allowed_packet`(包墙,64 MiB ÷ ~2× 文本协议膨胀)。帽=诚实拒绝:超限 blob 在 query 之前 typed 拒(消费面 HTTP 413 / capture warn
@@ -43,15 +39,29 @@ export interface SqlBlobCapReading {
43
39
  */
44
40
  export declare function sqlBlobDefaultCapOf(walls: SqlWallsReading): SqlBlobCapReading | undefined;
45
41
  /**
46
- * 一个 SQL 快照 blob 此刻的帽 —— **显式旋钮 ?? 判官给的墙走缺省推导**的唯一一只函数。两个读者同调它、各传本池的**引擎读口**(方言与活事实同出一只驱动,
47
- * 与提交受理口读的是同一种读口):店的写前帽({@link SqlBlobBackend},本池的驱动)与 `PUT /sync/blobs` 的前置 413(`boot/stage-10-http-server.ts` 装配的读口,
48
- * 按请求调,读 `ServiceDeps.sqlEngine`)。同一池上两个读口读的是同一份池级事实。
42
+ * 一条**借出连接**的引擎读口:方言(装配事实)+ 它自己那次初始化回读的事实。与 {@link SqlEngineReading} 的差别只有一处 —— `facts` 恒在:连接是借出时才有的,
43
+ * 借出即已初始化({@link connectJudged} 交给判决的就是借出连接的 `sqlConnectionFacts`),所以「事实缺席」在这条读法上**不可表示**(编译期拒,不是运行期一个没人走的分支)。
44
+ * 事实缺席(池上还没建立过、或最近一次初始化被拒)只出现在读**池级**读口的地方 —— 提交受理口 —— 那一格的墙由判官 `sqlWallsOf` 给,不经过帽的推导。
49
45
  */
50
- export declare function sqlBlobCapOf(explicitCapBytes: number | undefined, engine: SqlEngineReading): SqlBlobCapReading | undefined;
46
+ export interface SqlConnectionReading {
47
+ readonly dialect: SqlEngineReading["dialect"];
48
+ readonly facts: SqlEngineFacts;
49
+ }
50
+ /**
51
+ * 一个 SQL 快照 blob 此刻的帽 —— **显式旋钮 ?? 判官给的墙走缺省推导**的唯一一只函数。唯一读者 = 店的写前判({@link SqlBlobBackend.putBlob}),传的读口({@link SqlConnectionReading})是
52
+ * **将要执行写入的那条连接**的(方言 + 它自己那次初始化回读的事实,墙经与提交受理口同一只判官 `sqlWallsOf`):包墙是连接的会话值,所以判决只在写入的那条连接上做,
53
+ * HTTP 面不替店另算一份帽。
54
+ */
55
+ export declare function sqlBlobCapOf(explicitCapBytes: number | undefined, connection: SqlConnectionReading): SqlBlobCapReading | undefined;
51
56
  /** {@link sqlBlobCapOf} 的字节投影(同一个推导,不另读)。 */
52
- export declare function sqlBlobCapBytes(explicitCapBytes: number | undefined, engine: SqlEngineReading): number | undefined;
57
+ export declare function sqlBlobCapBytes(explicitCapBytes: number | undefined, connection: SqlConnectionReading): number | undefined;
58
+ /**
59
+ * 这一拒是不是**那条连接的包墙**定的:帽是缺省推导出的(不是运维旋钮)、那条连接有包墙、且包墙腿的折算恰是这个帽(包墙腿是最矮的一腿,或与行墙腿并列)。
60
+ * 只有这种拒才值得问「这条连接过时了吗」({@link connectJudged}) —— 旋钮的拒、行墙的拒与连接新旧无关。`walls` = 判出这个帽的那一份墙({@link sqlWallsOf})。
61
+ */
62
+ export declare function capBoundByPacketWall(cap: SqlBlobCapReading, walls: SqlWallsReading): boolean;
53
63
  /**
54
- * 一次 blob 超帽的拒句 —— **唯一的组句器**(`PUT /sync/blobs` 的前置 413 与店的 typed {@link BlobTooLargeError} 同调它):句子按帽的出处({@link SqlBlobCapReading.source})
64
+ * 一次 blob 超帽的拒句 —— **唯一的组句器**(店的 typed {@link BlobTooLargeError} 调它;`PUT /sync/blobs` 的 413 原样转出它的句子):句子按帽的出处({@link SqlBlobCapReading.source})
55
65
  * 分三形,不把假定说成库的上限、也不把运维的旋钮说成库的上限。
56
66
  * · `operator` —— 帽是运维设的 `SNAPSHOT_BLOB_SQL_MAX_BYTES`;
57
67
  * · `reported` —— 帽由库报的两堵墙推出(单行上限与包墙÷文本协议膨胀的较小者);
@@ -97,21 +107,24 @@ export interface BlobBackend {
97
107
  export declare class SqlBlobBackend implements BlobBackend {
98
108
  private readonly dialect;
99
109
  private readonly pool;
100
- /** Explicit per-blob cap (the operator's `SNAPSHOT_BLOB_SQL_MAX_BYTES`); absent ⇒ the derivation in {@link sqlBlobCapBytes} over this pool's live engine facts. */
110
+ /** Explicit per-blob cap (the operator's `SNAPSHOT_BLOB_SQL_MAX_BYTES`); absent ⇒ the derivation in {@link sqlBlobCapOf} over the walls of the connection each write borrows. */
101
111
  private readonly explicitCapBytes;
102
- /** This pool's engine reading (dialect + live facts, from the pool's own driver) — the one source the cap's walls are read from. */
103
- private readonly engine;
104
112
  /** Blob-domain table ([ref] blob 域隔离:file-history 域用自己的表,reap/GC 不跨域)。代码内字面量,非用户输入。 */
105
113
  private readonly table;
106
- constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string);
107
- /** The cap in force for this write: the explicit one, else the default derived from the pool's engine facts as they
108
- * stand now (the driver refreshes them on each connection initialization). */
109
- private cap;
114
+ /** The write path's connection source: every `putBlob` borrows ONE connection from it (through {@link connectJudged}), judges on that connection's walls, writes on it. */
115
+ private readonly db;
116
+ /** Where a stale connection's replacement is logged ({@link connectJudged}); the boot assembly passes the service logger. */
117
+ private readonly log;
118
+ constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string, log?: Pick<Logger, "warn">);
110
119
  /** The created_at-refreshing content-addressed upsert — VERBATIM the tidb/pg stores' `putBlobRow`. Refreshes
111
120
  * created_at even on a dedup hit so a re-used/just-written blob is "touched=now" and GC-grace-protected.
112
121
  * (a): an over-cap blob is REJECTED (typed {@link BlobTooLargeError}) BEFORE the driver ever sees the bytes —
113
122
  * the alternative is an inscrutable packet-limit error deep in the driver, and on the HTTP face a 502 that
114
- * invites the peer to retry a blob that can never land. */
123
+ * invites the peer to retry a blob that can never land.
124
+ * 🔴 The cap is judged ON THE CONNECTION THAT WRITES (S-1746): `max_allowed_packet` is a session value, fixed per
125
+ * connection, so the write borrows one connection through {@link connectJudged}, which reads ITS walls, judges, and — when
126
+ * the refusal is that connection's packet wall and the server's global wall is higher — replaces the stale connection and
127
+ * judges once more; the INSERT runs on the connection that passed. Never a pool-level reading, never a bare `pool.query`. */
115
128
  putBlob(hash: string, bytes: Uint8Array): Promise<void>;
116
129
  /** The bytes for `hash` (undefined if absent) — VERBATIM the tidb/pg stores' getBlob SELECT. A pool/query error
117
130
  * PROPAGATES (a transient DB fault is loud, not a silent "missing blob" — parity with the MinIO backend's throw). */
@@ -1,12 +1,12 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
3
- import { SQL_FACTS_ABSENT_WALLS, mysqlDriver, packetWallStatementBytes, pgDriver, rowWallPayloadBytes, sqlWallsOf } from "./sql-driver.js";
3
+ import { connectJudged, mysqlDriver, packetWallStatementBytes, pgDriver, rowWallPayloadBytes, sqlWallsOf } from "./sql-driver.js";
4
+ import { createLogger } from "../observability/logger.js";
4
5
  const BLOB_GC_GRACE_MS = 3_600_000;
5
6
  export const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
6
7
  export function packetWallBlobCap(maxPacketBytes) {
7
8
  return Math.floor(packetWallStatementBytes(maxPacketBytes) / SQL_TEXT_PROTOCOL_BLOB_INFLATION);
8
9
  }
9
- export const SQL_BLOB_FACTS_ABSENT_CAP_BYTES = sqlBlobDefaultCapBytes(SQL_FACTS_ABSENT_WALLS);
10
10
  export function sqlBlobDefaultCapBytes(walls) {
11
11
  return sqlBlobDefaultCapOf(walls)?.capBytes;
12
12
  }
@@ -18,11 +18,14 @@ export function sqlBlobDefaultCapOf(walls) {
18
18
  legs.push({ capBytes: packetWallBlobCap(walls.maxPacketBytes), source: walls.sources.maxPacketBytes });
19
19
  return legs.reduce((best, leg) => (best === undefined || leg.capBytes < best.capBytes ? leg : best), undefined);
20
20
  }
21
- export function sqlBlobCapOf(explicitCapBytes, engine) {
22
- return explicitCapBytes !== undefined ? { capBytes: explicitCapBytes, source: "operator" } : sqlBlobDefaultCapOf(sqlWallsOf(engine));
21
+ export function sqlBlobCapOf(explicitCapBytes, connection) {
22
+ return explicitCapBytes !== undefined ? { capBytes: explicitCapBytes, source: "operator" } : sqlBlobDefaultCapOf(sqlWallsOf(connection));
23
23
  }
24
- export function sqlBlobCapBytes(explicitCapBytes, engine) {
25
- return sqlBlobCapOf(explicitCapBytes, engine)?.capBytes;
24
+ export function sqlBlobCapBytes(explicitCapBytes, connection) {
25
+ return sqlBlobCapOf(explicitCapBytes, connection)?.capBytes;
26
+ }
27
+ export function capBoundByPacketWall(cap, walls) {
28
+ return cap.source !== "operator" && walls.maxPacketBytes !== null && packetWallBlobCap(walls.maxPacketBytes) === cap.capBytes;
26
29
  }
27
30
  export function sqlBlobCapRefusalText(byteLen, cap) {
28
31
  const objectStorage = "configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs";
@@ -64,27 +67,34 @@ export class SqlBlobBackend {
64
67
  dialect;
65
68
  pool;
66
69
  explicitCapBytes;
67
- engine;
68
70
  table;
69
- constructor(dialect, pool, maxBytes, table = "snapshot_blob") {
71
+ db;
72
+ log;
73
+ constructor(dialect, pool, maxBytes, table = "snapshot_blob", log) {
70
74
  this.dialect = dialect;
71
75
  this.pool = pool;
72
76
  this.explicitCapBytes = maxBytes;
73
77
  this.table = table;
74
- this.engine = dialect === "tidb" ? mysqlDriver(pool) : pgDriver(pool);
75
- }
76
- cap() {
77
- return sqlBlobCapOf(this.explicitCapBytes, this.engine);
78
+ this.db = dialect === "tidb" ? mysqlDriver(pool) : pgDriver(pool);
79
+ this.log = log ?? createLogger();
78
80
  }
79
81
  async putBlob(hash, bytes) {
80
- const cap = this.cap();
81
- if (cap !== undefined && bytes.byteLength > cap.capBytes)
82
- throw new BlobTooLargeError(bytes.byteLength, cap);
83
- if (this.dialect === "tidb") {
84
- await this.pool.query(`INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
82
+ const judged = await connectJudged(this.db, (facts) => {
83
+ const connection = { dialect: this.dialect, facts };
84
+ const cap = sqlBlobCapOf(this.explicitCapBytes, connection);
85
+ if (cap === undefined || bytes.byteLength <= cap.capBytes)
86
+ return undefined;
87
+ return { cap, packetBound: capBoundByPacketWall(cap, sqlWallsOf(connection)) };
88
+ }, (refusal) => refusal.packetBound, this.log);
89
+ if (judged.refusal !== undefined)
90
+ throw new BlobTooLargeError(bytes.byteLength, judged.refusal.cap);
91
+ try {
92
+ await judged.conn.query(this.dialect === "tidb"
93
+ ? `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`
94
+ : `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
85
95
  }
86
- else {
87
- await this.pool.query(`INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
96
+ finally {
97
+ judged.conn.release();
88
98
  }
89
99
  }
90
100
  async getBlob(hash) {
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * core's `createCircuitBreakerBrain` defaults to a per-PROCESS Map → on a multi-replica deployment
8
8
  * every replica re-discovers a provider outage independently (slow fleet-wide reaction). This backs
9
- * the breaker with the shared `circuit_breaker` table so a trip on one replica is learned by the others.
9
+ * the breaker with the shared `breaker_state` table so a trip on one replica is learned by the others.
10
10
  *
11
11
  * IMPORTANT — `BreakerState.get/set` are **synchronous** (core calls them on the hot path of every
12
12
  * brain invocation; a DB round-trip per call is untenable). So this is a **write-through cache**:
@@ -40,7 +40,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
40
40
  import type { Pool as PgPool, PoolClient } from "pg";
41
41
  import type { BreakerSnapshot, BreakerState } from "@sema-agent/core";
42
42
  import { type SqlDriver } from "./sql-driver.js";
43
- /** PG translation of the tidb-pool.ts `circuit_breaker` DDL (DATETIME(3)→TIMESTAMPTZ(3)). Disjoint from other
43
+ /** PG translation of the tidb-pool.ts `breaker_state` DDL (DATETIME(3)→TIMESTAMPTZ(3)). Disjoint from other
44
44
  * stores' tables, so a self-contained ensureSchema is safe (no central-schema shadowing). TiDB's DDL lives
45
45
  * centrally in tidb-pool.ts (no PG-side twin needed there — production wires ONE ensureTidbSchema). */
46
46
  export declare const PG_BREAKER_STATE_SCHEMA: string[];
@@ -1,6 +1,6 @@
1
1
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
2
2
  export const PG_BREAKER_STATE_SCHEMA = [
3
- `CREATE TABLE IF NOT EXISTS circuit_breaker (
3
+ `CREATE TABLE IF NOT EXISTS breaker_state (
4
4
  breaker_key VARCHAR(190) COLLATE "C" NOT NULL,
5
5
  phase VARCHAR(16) COLLATE "C" NOT NULL,
6
6
  failures INTEGER NOT NULL DEFAULT 0,
@@ -37,8 +37,8 @@ export class SqlBreakerState {
37
37
  }
38
38
  async writeThrough(key, snap) {
39
39
  try {
40
- await this.db.query(this.q("INSERT INTO circuit_breaker (breaker_key, phase, failures, opened_at, updated_at) VALUES (?,?,?,?,?) " +
41
- "ON DUPLICATE KEY UPDATE phase=VALUES(phase), failures=VALUES(failures), opened_at=VALUES(opened_at), updated_at=VALUES(updated_at)", "INSERT INTO circuit_breaker (breaker_key, phase, failures, opened_at, updated_at) VALUES ($1,$2,$3,$4,$5) " +
40
+ await this.db.query(this.q("INSERT INTO breaker_state (breaker_key, phase, failures, opened_at, updated_at) VALUES (?,?,?,?,?) " +
41
+ "ON DUPLICATE KEY UPDATE phase=VALUES(phase), failures=VALUES(failures), opened_at=VALUES(opened_at), updated_at=VALUES(updated_at)", "INSERT INTO breaker_state (breaker_key, phase, failures, opened_at, updated_at) VALUES ($1,$2,$3,$4,$5) " +
42
42
  "ON CONFLICT (breaker_key) DO UPDATE SET phase=EXCLUDED.phase, failures=EXCLUDED.failures, opened_at=EXCLUDED.opened_at, updated_at=EXCLUDED.updated_at"), [key, snap.phase, snap.failures, snap.openedAt != null ? new Date(snap.openedAt) : null, new Date()]);
43
43
  this.pending.delete(key);
44
44
  if (this.writeFailStreak > 0) {
@@ -60,7 +60,7 @@ export class SqlBreakerState {
60
60
  try {
61
61
  const pendingAtStart = new Set(this.pending);
62
62
  const guarded = (key) => this.pending.has(key) || pendingAtStart.has(key);
63
- const { rows } = await this.db.query("SELECT breaker_key, phase, failures, opened_at FROM circuit_breaker");
63
+ const { rows } = await this.db.query("SELECT breaker_key, phase, failures, opened_at FROM breaker_state");
64
64
  const seen = new Set();
65
65
  for (const r of rows) {
66
66
  const key = String(r.breaker_key);
@@ -4,6 +4,7 @@ import type { StagingHandle } from "../session-sync-kernel.js";
4
4
  import { type PlacementAcquireOpts } from "./session-placement.js";
5
5
  import type { InstructionSourceStore } from "../instruction-source-notice.js";
6
6
  import type { SessionFaceReader } from "../session-incarnation.js";
7
+ import type { OwnerlessClaim } from "../delegated-session-owner.js";
7
8
  export interface CachingOptions {
8
9
  /** Idle seconds a woken session is kept warm. 0 disables caching (pure passthrough). */
9
10
  ttlSec: number;
@@ -59,6 +60,8 @@ export declare class CachingSessionStore implements SessionStore {
59
60
  * (缓存的会话活得比「另一只店删掉又重建了这一行」长)。🔴 必须转发:k8s 工作区空间的每 run 读与起服门读的就是本类。 */
60
61
  readonly incarnationOf?: SessionStore["incarnationOf"];
61
62
  readonly register?: (sessionId: string, owner: string | null) => Promise<void>;
63
+ /** S-1770 委派继承律的认领口,纯转发(写的是行上的属主那一格 —— 只 CAS 子行自己,不碰暖缓存里的会话;停驻挂点在默认部署上读的就是本类 —— 与 `placementOf` 同转发同缺:内店记录委派树 ⇒ 两只都在)。 */
64
+ readonly claimOwnerless?: OwnerlessClaim;
62
65
  /** Delegated SessionRepo seam (§0.5) — list/fork/delete operate on the DURABLE tables directly (they bypass the
63
66
  * warm cache by design; a fork/delete must read/write durable state, not a possibly-stale cached view). */
64
67
  readonly listSessions?: (opts: {
@@ -10,6 +10,7 @@ export class CachingSessionStore {
10
10
  sessionFaceOf;
11
11
  incarnationOf;
12
12
  register;
13
+ claimOwnerless;
13
14
  listSessions;
14
15
  fork;
15
16
  deleteSession;
@@ -57,6 +58,8 @@ export class CachingSessionStore {
57
58
  this.incarnationOf = inner.incarnationOf.bind(inner);
58
59
  if (ownerAware.register)
59
60
  this.register = ownerAware.register.bind(inner);
61
+ if (ownerAware.claimOwnerless)
62
+ this.claimOwnerless = ownerAware.claimOwnerless.bind(inner);
60
63
  if (ownerAware.listSessions)
61
64
  this.listSessions = ownerAware.listSessions.bind(inner);
62
65
  if (ownerAware.sessionSummaryOf)
@@ -37,6 +37,14 @@ export declare function tokenFingerprint(token: string): string;
37
37
  * 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。 */
38
38
  export interface PendingCheckpoint extends ParkRowFacts {
39
39
  sessionId: string;
40
+ /**
41
+ * 这道停驻门的**非秘密稳定身份**(core `Checkpoint.checkpointId`,引擎每次停驻铸)—— 三处 durable 读面(本行 = `GET /v1/approvals` 的 `pending[]`
42
+ * 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的 `inbox[]`)共用的 **join 键**,也是 409 载体 A
43
+ * `pendingGate.checkpointId` / `terminal.paused.checkpointId` 的同一个值。**在场就是可信的那一个值,缺席就是缺席**:读法唯一 = `checkpoint-identity.ts` 的
44
+ * `durableRowIdentity`(过信任边界 —— 不含 token、有界),没有它的行(core 5.42.0 之前停驻的老行 / 没过边界的值)**照列**、键缺席(这一行没有可 join 的身份)、
45
+ * 每行计一次 `server.approvals.durable-row-checkpoint-id-missing`。两只店各读自己手里的那一份(SQL 孪生读 `checkpoint` 列的成员,LOCAL 孪生读 blob)。
46
+ */
47
+ checkpointId?: string;
40
48
  scope: string;
41
49
  toolName: string | null;
42
50
  toolCallId: string | null;
@@ -705,6 +713,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
705
713
  * {@link SqlCheckpointStore.pendingRowQuery};语义与三态全文见 {@link PendingRowBinding} 顶注。
706
714
  * `scope` 传 `""`(空串)历史上与缺席同义(旧形 `scope ?` 真值判),这里逐字保留。
707
715
  */
716
+ /**
717
+ * 每一只**待决** checkpoint 的会话(去重)—— 审批账本留存律的挂靠源(`approval-ask-retention.ts`:待决 checkpoint = 一个还能动的停驻,
718
+ * 它的会话就是停驻那条腿上问行的 `source_task_id`)。**不按门型过滤**:运维队列 {@link listPending} 不列 `gate_kind` 读不出的行,
719
+ * 而决断口的令牌回落仍认它 —— 留存若跟着队列漏掉它,就会删掉一只还能被决的停驻的问。只读 `session_id`,不读 blob。
720
+ */
721
+ listPendingSessionIds(): Promise<string[]>;
708
722
  findPendingTokenBySession(sessionId: string, scope?: string, binding?: PendingRowBinding): Promise<CheckpointToken | null>;
709
723
  /**
710
724
  * 🔴 [ref] 车5 §9 C4 —— 流内审批对账收敛器判据 1 的**窄读口**(店面承载;收敛器本体不在本车)。
@@ -6,6 +6,7 @@ import { redactDeep, redactSecrets } from "../trace/redact.js";
6
6
  import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, RuleOfferRawSchema, backfillLegacyBatchMemberKind, parkRowFacts, screenUncoveredDetailSeat } from "../approval-card.js";
7
7
  import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
8
8
  import { recordFailOpen } from "../observability/fail-open.js";
9
+ import { durableRowIdentity } from "../checkpoint-identity.js";
9
10
  import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
10
11
  import { isDupKeyError, isMissingColumnError, rowTooLargeOrSelf } from "./sql-errors.js";
11
12
  import { MANAGED_RETENTION } from "./retention-store-sql.js";
@@ -183,6 +184,18 @@ function readParkRowFactsCell(cell) {
183
184
  return parkRowFacts(pendingAction);
184
185
  }
185
186
  const PARK_ROW_PENDING_ACTION = { key: "pendingAction", as: "pending_action" };
187
+ const PARK_ROW_CHECKPOINT_ID = { key: "checkpointId", as: "checkpoint_id" };
188
+ const readCheckpointIdMemberBody = (body) => (typeof body === "string" ? { value: body } : { problem: "checkpointId is not a JSON string" });
189
+ function readCheckpointIdCell(cell) {
190
+ try {
191
+ return decodeNullableJsonColumn(cell, readCheckpointIdMemberBody, "checkpoint.checkpoint $.checkpointId (operator queue row)");
192
+ }
193
+ catch (e) {
194
+ if (!isJsonColumnBodyProblem(e))
195
+ throw e;
196
+ return null;
197
+ }
198
+ }
186
199
  const PARK_ROW_GATE = { key: "gate", as: "park_gate" };
187
200
  function readParkGateCell(cell) {
188
201
  try {
@@ -581,9 +594,9 @@ export class SqlCheckpointStore {
581
594
  }
582
595
  async listPending(scope) {
583
596
  const d = this.db.dialect;
584
- const base = `SELECT c.session_id, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
597
+ const base = `SELECT c.session_id, c.token, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
585
598
  `${jsonColumnText(d, "risk_descriptor")}, ${jsonColumnText(d, "rule_suggestions")}, c.has_bidi_controls, c.created_at_ms, c.deadline, c.gate_kind, ta.task_id, ` +
586
- `${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_GATE)} ` +
599
+ `${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_GATE)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)} ` +
587
600
  "FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
588
601
  const { rows } = scope
589
602
  ? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
@@ -599,6 +612,7 @@ export class SqlCheckpointStore {
599
612
  const approvalCard = parkedApprovalCard(parkedGateOfRow(r));
600
613
  return [{
601
614
  sessionId: String(r.session_id),
615
+ ...durableRowIdentity("approvals-queue", readCheckpointIdCell(r[PARK_ROW_CHECKPOINT_ID.as]), r.token),
602
616
  scope: String(r.scope),
603
617
  toolName,
604
618
  toolCallId,
@@ -628,6 +642,10 @@ export class SqlCheckpointStore {
628
642
  }
629
643
  return out;
630
644
  }
645
+ async listPendingSessionIds() {
646
+ const { rows } = await this.db.query("SELECT DISTINCT session_id FROM checkpoint WHERE status='pending'", []);
647
+ return rows.map((r) => String(r.session_id));
648
+ }
631
649
  async findPendingTokenBySession(sessionId, scope, binding) {
632
650
  const { sql, params } = this.pendingRowQuery("token", sessionId, scope || undefined, binding);
633
651
  const { rows } = await this.db.query(sql, params);
@@ -2,6 +2,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
3
  import { type FileHistoryRetentionPolicy, type ExecutionEnv, type FileHistoryStore, type FileHistoryResult, type FileHistoryTrackResult, type FileHistoryRestoreResult, type FileHistoryExport } from "@sema-agent/core";
4
4
  import { type BlobBackend } from "./blob-backend.js";
5
+ import type { SyncBlobPutResult } from "./store-backend.js";
5
6
  import { type SqlDriver } from "./sql-driver.js";
6
7
  /** The file-history blob domain's SQL table (BlobBackend `table`/MinIO index binding + the A7 assert). */
7
8
  export declare const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
@@ -147,8 +148,10 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
147
148
  * Domain-isolated by construction (this store's blob table only — [ref] D1). Reaper-driven. */
148
149
  sweepOrphanBlobs(): Promise<number>;
149
150
  /** Upload ONE content-addressed blob ahead of an importHistory (§5 streamed negotiation). Store-level
150
- * integrity re-check (the route pre-verifies too — the internal caller might not). Never-throw. */
151
- putBlob(hash: string, bytes: Uint8Array): Promise<FileHistoryResult>;
151
+ * integrity re-check (the route pre-verifies too — the internal caller might not). Never-throw.
152
+ * The SQL blob judge's refusal ({@link BlobTooLargeError}, judged on the connection that would have written the blob)
153
+ * keeps its own code — the route answers it 413 (the sentence names that connection's walls), every other write failure 502 ("retry"). */
154
+ putBlob(hash: string, bytes: Uint8Array): Promise<SyncBlobPutResult>;
152
155
  getBlob(hash: string): Promise<Uint8Array | undefined>;
153
156
  /** The subset of `hashes` already present — one SQL INDEX probe (the import presence pre-check). */
154
157
  hasBlobs(hashes: string[]): Promise<Set<string>>;
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
3
- import { SqlBlobBackend } from "./blob-backend.js";
3
+ import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
4
4
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
5
5
  import { isMissingColumnError } from "./sql-errors.js";
6
6
  import { recordFailOpen } from "../observability/fail-open.js";
@@ -594,6 +594,8 @@ export class SqlFileHistoryStore {
594
594
  return { ok: true };
595
595
  }
596
596
  catch (err) {
597
+ if (err instanceof BlobTooLargeError)
598
+ return { ok: false, error: { code: err.code, message: err.message, wallSource: err.cap.source } };
597
599
  return { ok: false, error: { code: "snapshot_failed", message: `putBlob ${hash} write/upload failed: ${discloseCaughtFailure(err)}` } };
598
600
  }
599
601
  }
@@ -1,6 +1,6 @@
1
1
  import { type UsageRow } from "../usage-analytics.js";
2
2
  import type { TaskStatus } from "@sema-agent/core";
3
- import { StoreBootRefusalError, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
3
+ import { StoreBootRefusalError, type FenceLanded, type InProcessClaimFence, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
4
4
  import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
5
5
  import type { LedgerEventType } from "../trace/ledger-events.js";
6
6
  import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
@@ -22,7 +22,7 @@ import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState }
22
22
  export declare class RunStoreStrictHydrateError extends StoreBootRefusalError {
23
23
  readonly name = "RunStoreStrictHydrateError";
24
24
  }
25
- export declare class FileRunStore {
25
+ export declare class FileRunStore implements InProcessClaimFence {
26
26
  private readonly runsDir;
27
27
  private readonly activeDir;
28
28
  private readonly tmpDir;
@@ -148,6 +148,8 @@ export declare class FileRunStore {
148
148
  * [ref](codex R1 [medium]):认领 = 接管 `instanceId`(= 当前持有副本;SQL 孪生同注)。CAS 输不改。 */
149
149
  markResuming(taskId: string, instanceId: string): Promise<boolean>;
150
150
  getActiveTaskId(sessionId: string): Promise<string | undefined>;
151
+ /** 认领当写栅栏用(契约 = `store-contracts.ts` 的 `InProcessClaimFence`):判、刷新活性(同步落盘)与 `write` 是同一个同步段。 */
152
+ whileHoldingClaim(sessionId: string, taskId: string, write: () => FenceLanded): boolean;
151
153
  getRun(taskId: string): Promise<RunRecord | undefined>;
152
154
  /** Undo THIS submission's binding (key + body fingerprint → null) — called only where the submit surface refuses the submission
153
155
  * before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, a device-placement reject):
@@ -161,17 +163,7 @@ export declare class FileRunStore {
161
163
  /** The run a submission's scoped `Idempotency-Key` names (the file twin of `uq_task_run_idem`), if any. */
162
164
  getRunByIdem(idemKey: string): Promise<RunRecord | undefined>;
163
165
  /** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
164
- listRuns(opts: {
165
- status?: string;
166
- jobId?: string;
167
- source?: string;
168
- owner?: string;
169
- cursor?: {
170
- createdAt: string;
171
- taskId: string;
172
- };
173
- limit: number;
174
- }): Promise<RunRecord[]>;
166
+ listRuns(opts: RunListQuery): Promise<RunRecord[]>;
175
167
  /**
176
168
  * S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
177
169
  * `createdAt DESC, taskId DESC`,与本店 {@link FileRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
@@ -525,6 +525,15 @@ export class FileRunStore {
525
525
  async getActiveTaskId(sessionId) {
526
526
  return this.active.get(sessionId);
527
527
  }
528
+ whileHoldingClaim(sessionId, taskId, write) {
529
+ const r = this.runs.get(taskId);
530
+ if (this.active.get(sessionId) !== taskId || r === undefined || r.status !== "running")
531
+ return false;
532
+ r.updatedAt = new Date();
533
+ this.persist(r);
534
+ write();
535
+ return true;
536
+ }
528
537
  async getRun(taskId) {
529
538
  const r = this.runs.get(taskId);
530
539
  return r ? this.toRecord(r) : undefined;
@@ -24,6 +24,7 @@
24
24
  * (requireExisting 的 host-first 重试仍落对店),中途 touch/noteTaskRun 缺路由=落 transient(no-op 级)。
25
25
  */
26
26
  import { type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionPlacement, type PlacedSessionRow } from "@sema-agent/core";
27
+ import { type DelegationTree } from "../delegated-session-owner.js";
27
28
  /** acquire 选项形(core 5.48 SessionStore 契约):[ref] placement=受信 spawner 的分区建会话声明。 */
28
29
  type AcquireOpts = {
29
30
  requireExisting?: boolean;
@@ -85,8 +86,15 @@ export declare class ForkRoutingSessionStore implements SessionStore {
85
86
  * unpin 不落错店)。
86
87
  *
87
88
  * 已在 host(路由或真行)⇒ 幂等直返。两店都无此会话 ⇒ 抛(调用方=park veto,诚实 fail-closed)。
89
+ *
90
+ * S-1770:宿主行**在建行那一笔里**带属主 `owner` —— 调用方按委派继承律算好(`delegated-session-owner.ts`:委派树根的属主,
91
+ * 推不出 ⇒ 孤行哨兵;`tree` = 判据读到的委派树,判断用:宿主行记着别的树 ⇒ 拒),这里经宿主店的首写登记写下、再按律定夺(core `register` 契约:登记不判授权,判在复读)。
92
+ * 宿主店里同 id 已有一行:记着这一棵委派树且无主 ⇒ 按同一条律认领(只有记录委派树的店);别的树的 / 属主不是它(别人先认领的空壳,或 local 店的无主空壳)⇒ 抛(停驻否决),
93
+ * 转录不拷进别人的行;宿主店记不了 / 读不回属主 ⇒ 同样抛 —— 晋升出一只无主的转录行就是把它交给任何拿到 id 的人。
88
94
  */
89
- promoteToHost(sessionId: string): Promise<void>;
95
+ promoteToHost(sessionId: string, owner: string | null, tree: DelegationTree): Promise<void>;
96
+ /** 晋升的建行那一笔(S-1770,规则见 {@link promoteToHost} 头注):宿主店首写登记 `owner`,然后按律定下宿主行的属主(与停驻快路同一只 `settleDelegatedOwner`)。 */
97
+ private registerPromotedOwner;
90
98
  fork(sourceId: string, owner?: string | null): Promise<string | null>;
91
99
  get size(): number;
92
100
  dispose(): void | Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import { SessionError } from "@sema-agent/core";
2
2
  import { recordFailOpen } from "../observability/fail-open.js";
3
+ import { delegatedOwnerHostOf, settleDelegatedOwner } from "../delegated-session-owner.js";
3
4
  const ROUTE_MAX = 8192;
4
5
  export class ForkRoutingSessionStore {
5
6
  host;
@@ -170,7 +171,7 @@ export class ForkRoutingSessionStore {
170
171
  list() {
171
172
  return this.transient.list?.() ?? Promise.resolve([]);
172
173
  }
173
- async promoteToHost(sessionId) {
174
+ async promoteToHost(sessionId, owner, tree) {
174
175
  if (this.promoted.has(sessionId))
175
176
  return;
176
177
  if (this.route.get(sessionId) === "host") {
@@ -190,6 +191,7 @@ export class ForkRoutingSessionStore {
190
191
  }
191
192
  const entries = await src.session.getEntries();
192
193
  const leaf = await src.session.getLeafId();
194
+ await this.registerPromotedOwner(sessionId, owner, tree);
193
195
  const dst = await this.host.acquire(sessionId);
194
196
  try {
195
197
  const storage = dst.session.getStorage();
@@ -217,6 +219,15 @@ export class ForkRoutingSessionStore {
217
219
  this.promoted.add(sessionId);
218
220
  this.remember(sessionId, "host");
219
221
  }
222
+ async registerPromotedOwner(sessionId, owner, tree) {
223
+ const register = this.host.register;
224
+ const host = delegatedOwnerHostOf(this.host);
225
+ if (register === undefined || host === undefined) {
226
+ throw new Error(`session "${sessionId}" cannot be promoted: the host session store cannot record or read back a session owner (register / ownerOf / sessionFaceOf; placementOf and claimOwnerless together or neither), so the promoted transcript would land ownerless`);
227
+ }
228
+ await register.call(this.host, sessionId, owner);
229
+ await settleDelegatedOwner(host, sessionId, owner, tree);
230
+ }
220
231
  async fork(sourceId, owner) {
221
232
  const routed = this.route.get(sourceId);
222
233
  const tryFork = async (store, where) => {
@@ -34,6 +34,8 @@
34
34
  import type { ChildProcess } from "node:child_process";
35
35
  import { killProcessTree, type BackgroundHardKillOutcome } from "@sema-agent/core";
36
36
  import type { Logger } from "../observability/logger.js";
37
+ /** 平台读点全仓一处(`test/host-shell-settle-order.test.ts` 源码钉);别处要平台串从这里取,不直读 Node 的平台字段。 */
38
+ export declare const HOST_PLATFORM: NodeJS.Platform;
37
39
  export declare const IS_WIN32: boolean;
38
40
  export interface HostShell {
39
41
  shell: string;
@@ -1,7 +1,8 @@
1
1
  import os from "node:os";
2
2
  import { getShellConfig, killProcessTree, signalProcessTree } from "@sema-agent/core";
3
3
  import { redactSecrets } from "../trace/redact.js";
4
- export const IS_WIN32 = process.platform === "win32";
4
+ export const HOST_PLATFORM = process.platform;
5
+ export const IS_WIN32 = HOST_PLATFORM === "win32";
5
6
  const POSIX_SH_FALLBACK = { shell: "/bin/sh", args: ["-c"] };
6
7
  let cachedPosixShell;
7
8
  let winShellState = { kind: "unsettled" };
@@ -98,6 +98,8 @@ export declare class LocalCheckpointStore {
98
98
  /** All still-pending checkpoints across the registered scopes (summary + full row via get). */
99
99
  private pendings;
100
100
  /** Operator approval queue — the same NON-secret card as the SQL twins (never the token). */
101
+ /** 每一只待决 checkpoint 的会话(去重;不按门型过滤 —— 理由与 SQL 孪生 `listPendingSessionIds` 逐字同,见那里)。 */
102
+ listPendingSessionIds(): Promise<string[]>;
101
103
  listPending(scope?: string): Promise<PendingCheckpoint[]>;
102
104
  /**
103
105
  * [ref]:与 SQL 孪生**同判**的 session 键读口 —— `binding.gateKinds` 过滤门型、序恒「最新优先,token 兜平局」