@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,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { parkedApprovalCard } from "../approval-content-kind.js";
3
3
  import { parkRowFacts } from "../approval-card.js";
4
+ import { durableRowIdentity } from "../checkpoint-identity.js";
4
5
  import { createHash } from "node:crypto";
5
6
  import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from "node:fs";
6
7
  import { join } from "node:path";
@@ -160,9 +161,12 @@ export class LocalCheckpointStore {
160
161
  }
161
162
  return out;
162
163
  }
164
+ async listPendingSessionIds() {
165
+ return [...new Set((await this.pendings()).map(({ cp }) => cp.sessionId))];
166
+ }
163
167
  async listPending(scope) {
164
168
  const rows = [];
165
- for (const { cp } of await this.pendings(scope)) {
169
+ for (const { token, cp } of await this.pendings(scope)) {
166
170
  const pa = cp.pendingAction;
167
171
  const gate = cp.gate;
168
172
  const gateKind = queueRowGateKind(gate?.kind);
@@ -171,6 +175,7 @@ export class LocalCheckpointStore {
171
175
  const toolCallId = pa?.toolCallId ?? null;
172
176
  rows.push({
173
177
  sessionId: cp.sessionId,
178
+ ...durableRowIdentity("approvals-queue", cp.checkpointId, token),
174
179
  scope: cp.scope,
175
180
  toolName: pa?.toolName ?? null,
176
181
  toolCallId,
@@ -7,7 +7,7 @@ import { type InstructionSourceBaseline } from "../instruction-source-notice.js"
7
7
  import { type StagingHandle, type SyncRelation } from "../session-sync-kernel.js";
8
8
  /**
9
9
  * local 车道会话存档的**唯一 repo 出口**:本仓里每一只读 `<root>/sessions/*.jsonl` 的 `FileSessionRepo` 都从这里取(`LocalSessionStore` 的底座与会话视图口
10
- * `audit.ts#buildSessionAuditLocal` 的读旁路),所以坏记录的披露座只有这一份接线 —— 修前视图口自建一只不带座的 repo,同一份坏档在这里读到有计数 / 探针,
10
+ * 窗口 `local-session-window.ts#LocalSessionWindow.wake` 的读旁路),所以坏记录的披露座只有这一份接线 —— 修前视图口自建一只不带座的 repo,同一份坏档在这里读到有计数 / 探针,
11
11
  * 在视图口读到一个字都没有。座 = F 类 `server.session-repo.corrupt-record-skipped`(会话存档里读不出的记录丢的是可用性:历史少一截 / 列表少一行,不开任何门;
12
12
  * 计数单位 = 一条被跳过的记录,逐命中探针行,warn 每进程一次),外加会话打开作用域的计数腿(`noteReplayInSessionOpenScope`:只在 `openSessionForItself`
13
13
  * 的作用域里才记,作用域外 —— 列表 / 导出 / 视图口 —— 是空操作,那些读只进运维面)。
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The local (File) leg's **bounded window** of one session — the reader that loads `[floor .. leaf]` instead of the whole tree
3
+ * (today: the session view, `audit.ts#buildSessionAuditLocal`). The twin of `TiDBSessionStorage.wake` / `PgSessionStorage.wake`.
4
+ *
5
+ * ## One rule: the view reads what the run reads
6
+ * A run on this backend wakes the **whole tree** (`LocalSessionStore.acquire` → core `FileSessionRepo.open`): core resolves the
7
+ * log's leaf and the engine walks the branch from that leaf to the root. A window shows the same thing only when it is cut **from
8
+ * that branch**, keeps **that leaf**, and carries the reading below its floor — the halves of core's bounded-storage contract:
9
+ * · the window = core's `boundedTail` over the **branch path** (root → the run's leaf), not over the log in file order. The log
10
+ * also holds abandoned branches (a resumeAt moves the leaf in place): cut in file order, the floor can come from a compaction
11
+ * the branch no longer contains — the view then reads a context the run does not (a rewind inside the kept tail), or cannot be
12
+ * built at all (a rewind past the last compaction: the branch's parent lies below the cut);
13
+ * · the leaf = the one core resolved over the whole log, handed to the window as is (a window that re-resolved its leaf from its
14
+ * own entries would read a leaf entry on the branch a second time and land on an entry it does not hold);
15
+ * · below the floor = `getEpochAnchor`, the one reading every windowed storage shares ({@link scanEpochAnchor}), paged over the
16
+ * branch below the floor — so the record the engine reads is the one the run's walk reaches.
17
+ * Read-only: the log is read once (`exportEntries`, pure), no append log is opened, and the window refuses writes.
18
+ *
19
+ * ## Cost: one linear walk
20
+ * The branch is the `parentId` chain from the leaf — the chain core's `getPathToRoot` follows, with the same stop rule (an entry
21
+ * without a parent ends it; a parent the log does not hold is `invalid_session`). It is walked here once, in linear time: core
22
+ * 7.52.0 builds its path with `unshift` (quadratic in the branch length — seconds on a long session, on the event loop), and the
23
+ * view must cost a window read, not a whole-branch walk. Every later read of the window goes through core's own walk, bounded by
24
+ * the floor.
25
+ */
26
+ import { BaseSessionStorage } from "@sema-agent/core";
27
+ export declare class LocalSessionWindow extends BaseSessionStorage {
28
+ /** The branch below the floor, root first (empty when the window is the whole branch). */
29
+ private readonly below;
30
+ private constructor();
31
+ appendEntry(): Promise<void>;
32
+ setLeafId(): Promise<void>;
33
+ /** `SessionStorage.getEpochAnchor?()` — the nearest carrier on the branch below the floor, as stored ({@link scanEpochAnchor}).
34
+ * A row's `seq` is its position on the branch; the whole branch is in memory, so one page holds every row. */
35
+ getEpochAnchor(): Promise<unknown>;
36
+ /** Wake the window of `sessionId` under the local data root `root`; `null` if there is no such session. */
37
+ static wake(root: string, sessionId: string): Promise<LocalSessionWindow | null>;
38
+ }
39
+ //# sourceMappingURL=local-session-window.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { BaseSessionStorage, InMemorySessionStorage, SessionError, boundedTail } from "@sema-agent/core";
2
+ import { createLocalSessionRepo } from "./local-session-store.js";
3
+ import { scanEpochAnchor } from "./session-epoch-anchor.js";
4
+ export class LocalSessionWindow extends BaseSessionStorage {
5
+ below;
6
+ constructor(metadata, window, leafId, floorEntryId, below) {
7
+ super(metadata, window, leafId, { floorEntryId });
8
+ this.below = below;
9
+ }
10
+ async appendEntry() {
11
+ throw new SessionError("storage", "LocalSessionWindow is a read-only view of a session — write through the session store");
12
+ }
13
+ async setLeafId() {
14
+ throw new SessionError("storage", "LocalSessionWindow is a read-only view of a session — write through the session store");
15
+ }
16
+ async getEpochAnchor() {
17
+ return scanEpochAnchor(this.below.length, async (beforeSeq) => this.below.slice(0, beforeSeq).map((entry, seq) => ({ seq, entry })).reverse(), (row) => row.entry);
18
+ }
19
+ static async wake(root, sessionId) {
20
+ const repo = createLocalSessionRepo(root);
21
+ let log;
22
+ try {
23
+ log = await repo.exportEntries(sessionId);
24
+ }
25
+ catch (e) {
26
+ if (e.code === "not_found")
27
+ return null;
28
+ throw e;
29
+ }
30
+ const metadata = (await repo.list()).find((m) => m.id === sessionId) ?? { id: sessionId, createdAt: new Date().toISOString() };
31
+ const whole = new InMemorySessionStorage({ entries: log });
32
+ const leafId = await whole.getLeafId();
33
+ const branch = await branchOf(whole, leafId, log.length);
34
+ const { tail, floorEntryId } = boundedTail(branch);
35
+ return new LocalSessionWindow(metadata, tail, leafId, floorEntryId, branch.slice(0, branch.length - tail.length));
36
+ }
37
+ }
38
+ async function branchOf(whole, leafId, logLength) {
39
+ const fromLeaf = [];
40
+ let current = leafId === null ? undefined : await whole.getEntry(leafId);
41
+ while (current) {
42
+ fromLeaf.push(current);
43
+ if (!current.parentId)
44
+ break;
45
+ if (fromLeaf.length > logLength)
46
+ throw new SessionError("invalid_session", `Entry ${current.id} is on a parent cycle`);
47
+ const parent = await whole.getEntry(current.parentId);
48
+ if (!parent)
49
+ throw new SessionError("invalid_session", `Entry ${current.parentId} not found`);
50
+ current = parent;
51
+ }
52
+ return fromLeaf.reverse();
53
+ }
54
+ //# sourceMappingURL=local-session-window.js.map
@@ -66,8 +66,9 @@ export interface MailboxRecipientLifecycle {
66
66
  * **只打标**——不删消息、不动租约、不碰盒行的高水位(理由 = core T2 试剂盒的两格副作用判据,见模块头)。
67
67
  * 幂等(重复 retire 只更新时刻/名字)。
68
68
  *
69
- * @returns 是否真的落了一张耐久墓碑。`false` = 本后端没有预删除态,箱已被直接 `drop`
70
- * (拒的方向不变,退化的只有拒绝措辞与「正在删除中」那一行的可见性)。
69
+ * @returns 是否真的落了一张耐久墓碑。`false` 有两种,都是「没落」:① 本后端没有预删除态,箱已被直接 `drop`
70
+ * (拒的方向不变,退化的只有拒绝措辞与「正在删除中」那一行的可见性);② 这只键收件箱的键判官不收(SQL 两孪生:没有箱可打标,
71
+ * 这只键的 `append` 本来就以判官那一句拒,不带墓碑码)。
71
72
  */
72
73
  retireRecipient(scope: string, handle: string, at: number, label?: string): Promise<boolean>;
73
74
  /** 本 scope 下句柄以 `handlePrefix` 开头的墓碑。无预删除态的后端恒返回空。 */
@@ -1,49 +1,3 @@
1
- /**
2
- * MailboxStore SQL twins([ref] S3c server pg 半场,[ref] 提货单①)——core teammate 信箱
3
- * (SendMessage 链)的 TiDB/PG 双方言实现。挂载缝 = S3c 行为车的 `RunnerDeps.mailboxStore`(core
4
- * 命名预告,[ref]§三);本文件先就绪 store 半场,行为车到货接线。
5
- *
6
- * 语义真源 = core `InMemoryMailboxStore`(dist/core/mailbox-store.js)+ [ref] 定谳(core 1.372
7
- * 终版文本,conformance 无两可):
8
- * 1. **同主重 claim = 续期 + 纳新**(crash-retry 正形):活 lease 只挡**别的** owner;同 owner 任意
9
- * 时刻重 claim 重设 TTL 并拿到当前全部未 ack 消息(maxSeq 随最新行走)。
10
- * 2. **seq = 盒生命周期内不复用**:box 行携 `next_seq`,分配与 append 同事务;`drop` 删整盒(含 box 行)
11
- * = 生命周期终结,重建从 1(core 定谳:行随盒亡,dedup 键携 handle)。⚠️ **`reap` 自 [ref](core
12
- * 2026-07-25)起不再与 drop 同句**:reap 是收件人活着时的龄扫,**清箱保高水位**(删消息、清租约、盒行与
13
- * next_seq 留着)——本文件 2026-09-03(车BF,codex r1 [high])才跟上;此前 reap 删盒行 ⇒ 下一条 append 从 1
14
- * 重铸,陈旧 ack 可删掉一条没人见过的新消息。跨进程试剂盒明钉此条。
15
- * 3. **ack 带 owner 围栏**(MB1,core 1.452 起;原「无守卫、信任 claim 赢者链」已被推翻):当前持租者才删
16
- * `seq <= upToSeq` 的行;lease 的 `maxSeq <= upToSeq` 时顺带清 lease(全量 ack = 释放)。**围栏的判决报出来**
17
- * (core 7.30.0 #1036,S-616):放行 ⇒ `{ acked: true }`(删 0 行也是 true —— 契约是「围栏放行」不是「删了几行」),
18
- * 盒不在 / 无人持租 / 非持租者 ⇒ `{ acked: false }`;逐格与 core `InMemoryMailboxStore.ack` 同答。
19
- * 4. **lease 过期判 = 严格 `expiresAt > now` 才挡**;`releaseLease` 只在 owner 匹配时清。
20
- * 5. `peekCount` 含已 lease 消息(可见性 ≠ lease 态);空盒 claim = null(box 行在而消息 0 也 null)。
21
- * 6. **reap = SQL 全量真扫**(core File 实现只扫已加载盒 = 进程内 backstop,[ref] 确认 SQL 是全量
22
- * 真扫的正确层):`maxAgeMs` 缺省 → 0;按盒 newest `sentAt < now - maxAgeMs` **清箱**(删消息 + 清租约,
23
- * 盒行/高水位保留,见定谳 2 的 [ref] 半句);空盒(有 box 行无消息)不清(InMemory 同形——`newest ===
24
- * undefined` 不动)。
25
- *
26
- * 键列 = 字节等价(TiDB VARBINARY / PG COLLATE "C",roster F2 案):scope/handle/lease_owner 的
27
- * `=` 必须纯字节——PAD SPACE 近撞不得穿隔离/抢别人 lease。PG unstorable bytes([ref] 三层定谳):
28
- * `content` 是消息**内容面**(收件人将读到、注入对话)→ 与 PG 转录 payload 同族 lossy(pgSanitizeText,
29
- * U+FFFD 标记,内容不失行);**身份键 scope/handle/owner/from 拒绝式**(pgHasUnstorable →
30
- * PgUnstorableError fail-loud——attribution/路由键清洗形变 = 行与查询键错位,协议数据同向)。TiDB
31
- * 侧 LONGTEXT/VARBINARY verbatim(replay 面定谳)。
32
- *
33
- * 并发正确性(InMemory 单进程无此象限、SQL 多副本有;发车复审 F2/F3/F4 批):
34
- * - **锁序统一「盒行 → 消息行」**(append/claim/ack/drop/reap 五面同序)——ack/drop/reap 原为
35
- * 消息先行,与 claim 的盒先行构成 ABBA 死锁形;统一后同盒并发只在盒行锁上排队。
36
- * - drop/reap 先删/锁**盒行**再动消息:反序留「消息已删、盒行未删」窗,并发 append 完整穿过后
37
- * 盒重建会撞存活的孤儿消息行(PK 冲突→此后 append 永久失败)。
38
- * - reap 候删 = 事务外粗筛 + 事务内**逐盒锁定重验**(当前读 newest):窗内新到的消息救活盒,
39
- * 绝不连带删信;盒行已被并发 drop = 跳过不计。
40
- * - append 的 ensure 与 FOR UPDATE 之间被并发 drop 提交 = 同事务内重建一次(语义 = drop 后重建,
41
- * seq 从 1)。建盒动词 = 形 A(S-127)`ON DUPLICATE KEY UPDATE <pk> = <pk>`:重复键取 X 锁,与随后的
42
- * `FOR UPDATE` **同锁模式**(`INSERT IGNORE` 取 S 再升 X = InnoDB 上的并发死锁形,B-013)。
43
- * - 键长 > 190 字节写入即拒(fail-loud):截断键与全长查询参数字节比对恒 miss(分配读空行崩/隔离键
44
- * 错位)。守卫在**语句之前**,与建行动词无关 —— 换成 ON DUPLICATE 之后超长键由引擎直接报错(严格
45
- * 模式下 IGNORE 会把它降级成静默截断),方向=更响亮,守卫依旧是第一道。
46
- */
47
1
  import type { Pool as MySqlPool } from "mysql2/promise";
48
2
  import type { Pool as PgPool } from "pg";
49
3
  import type { MailboxAckResult, MailboxAppendMessage, MailboxLease, MailboxStore } from "@sema-agent/core";
@@ -57,6 +11,36 @@ export declare const MAILBOX_MSG_TABLE = "mailbox_message";
57
11
  * —— `mailbox` 行是**盒**事实(next_seq / lease),墓碑是**收件人**事实,两者的生命周期本来就不同
58
12
  * (`drop` 同时终结两者 —— 墓碑随箱亡,理由全文见 `mailbox-recipient-lifecycle.ts` 模块头)。 */
59
13
  export declare const MAILBOX_TOMBSTONE_TABLE = "mailbox_tombstone";
14
+ /**
15
+ * 身份键(`scope` / `handle` / lease `owner`)的 UTF-8 字节上界 = 键列宽(TiDB `VARBINARY(190)`;PG 列按字符,本判官在两方言上都按字节拒)。
16
+ */
17
+ export declare const MAILBOX_KEY_MAX_UTF8_BYTES = 190;
18
+ /**
19
+ * 🔴 **「这只串能当收件箱的身份键吗」的唯一判官**(三只键列:`scope` / `handle` / lease `owner`;两方言同一只、同一句拒因)。一只串当得了键 ⇔
20
+ * ⓪ `scope` 非空(引擎 InMemory 同一句「without a scope」:没有箱址;handle / owner 空照引擎参照形放行);
21
+ * ① **良构 Unicode**(没有孤立代理):两只驱动按 UTF-8 送库时都把孤立代理换成 U+FFFD 的三字节 ⇒ 库里它与另一只键同键(TiDB 写进别人的箱、续不了自己的租约;
22
+ * PG 另把它当不可存字节);
23
+ * ② **不含控制字符**(C0 含 NUL、DEL):键要能逐字节寻址([ref] 试剂盒:NUL 键两后端一致拒、不得串箱);
24
+ * ③ **UTF-8 字节不超过键列宽**:截断的键与全长查询参数逐字节比对恒 miss(seq 分配读空行崩 / 隔离键错位)。
25
+ * 答第一道不过的拒因,都过 ⇒ `undefined`。**判官拒的键没有箱**,两方言每一只入口在语句之前问它,一条律两种答法:建状态的两面(`append` 的 scope 与 handle、
26
+ * `claimLease` 写进租约的 owner)拒就抛这一句({@link assertMailboxKey});其余每一面(读 / 清 / 打标 / 放租 / 确认)照「箱不在」答、不把键送进驱动({@link keyless}):
27
+ * `peekCount` 0、`claimLease` null、`ack` `{ acked: false }`、`releaseLease` / `drop` 无事、`reap` 0(候选扫描交回的句柄同判:判官拒的跳过、零语句)、
28
+ * `retireRecipient` false(没落墓碑 —— 没有箱可打标;这只键的 append 本来就以判官那一句拒)、`listRetiredRecipients` 空。跨主体投递门在扣投递额度之前问它
29
+ * (收件人 scope 就是这只键,`org-delivery-gate.ts`)。库里已有的这种键(升级前写下的)由一次性迁移 M27 删掉,判据是同一只判官({@link storedMailboxKeyless})。没有第二道键判。
30
+ */
31
+ export declare function mailboxKeyProblem(what: MailboxKeyColumn, v: string): string | undefined;
32
+ /** 收件箱的三只键列(判官的 `what`;闭集)。 */
33
+ export type MailboxKeyColumn = "scope" | "handle" | "owner";
34
+ /** 库里读回的一对键列判官拒不拒(与入口同一只判官;字节先无损读成串,读不成 = 不良构)。`reap` 的候选句柄与一次性迁移 M27 都按它判。 */
35
+ export declare function storedMailboxKeyless(scopeKey: unknown, handle: unknown): boolean;
36
+ /**
37
+ * 收件箱表族:三张表的键都是 `(scope_key, handle)`。次序 = 锁序(盒行 → 消息行 → 墓碑行;盒行先删的理由见 `drop`)。
38
+ * 「丢掉一只箱」= 按这个次序逐表按键删 —— `drop` 与一次性迁移 M27 是同一组语句({@link mailboxKeyDeleteSql})。
39
+ */
40
+ export declare const MAILBOX_KEYED_TABLES: readonly ["mailbox", "mailbox_message", "mailbox_tombstone"];
41
+ export type MailboxKeyedTable = (typeof MAILBOX_KEYED_TABLES)[number];
42
+ /** 按键删收件箱表族的一张表(两方言;参数 = [scope_key, handle])。 */
43
+ export declare function mailboxKeyDeleteSql(dialect: "tidb" | "pg", table: MailboxKeyedTable): string;
60
44
  /** 店侧日志座(可选;boot 注入 `Logger`)。7.58.0 合并重扫 [medium]:坏行的 fail-closed 抛必须**响亮**——core 唯一的
61
45
  * claimLease 调用方(peer-session-drain)吞掉该错,店不记这一笔就成了「静默永久卡箱」。抛之前记
62
46
  * `mailbox_message_column_corrupt`(scope/handle/seq/column/code),运维按 seq 修行。
@@ -106,8 +90,6 @@ export declare class PgMailboxStore implements MailboxStore, MailboxRecipientLif
106
90
  readonly crossProcessSafe = true;
107
91
  constructor(pool: PgPool, logger?: MailboxStoreLogger | undefined);
108
92
  private tx;
109
- /** 身份键拒绝式([ref] 协议纪律):scope/handle/owner/from 清洗形变 = 路由/attribution 错位。 */
110
- private assertIdentity;
111
93
  append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
112
94
  claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
113
95
  ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
@@ -1,5 +1,7 @@
1
+ import { isUtf8 } from "node:buffer";
1
2
  import { MAILBOX_INVALID_PEER_META_CODE, MAILBOX_TOMBSTONED_RECIPIENT_CODE, MailboxStoreError, readMailboxPeerMeta } from "@sema-agent/core";
2
3
  import { pgHasUnstorable, pgSanitizeText, PgUnstorableError } from "./pg-safe-json.js";
4
+ import { isWellFormedUnicode } from "../principal-jwt.js";
3
5
  import { ensureIndexes } from "./ensure-index.js";
4
6
  import { escapeLike } from "./sql-escape.js";
5
7
  import { decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readStringArrayColumn } from "./sql-json-column.js";
@@ -19,20 +21,38 @@ function rowToTombstone(r) {
19
21
  const label = r.label === null || r.label === undefined ? undefined : bufToStr(r.label);
20
22
  return { handle: bufToStr(r.handle), retiredAt: Number(r.retired_at_ms), ...(label !== undefined ? { label } : {}) };
21
23
  }
22
- function assertScope(scope) {
23
- if (scope === undefined || scope === "") {
24
- throw new Error("MailboxStore.append: refusing a message without a scope");
25
- }
24
+ export const MAILBOX_KEY_MAX_UTF8_BYTES = 190;
25
+ export function mailboxKeyProblem(what, v) {
26
+ if (what === "scope" && (v === undefined || v === ""))
27
+ return "MailboxStore: refusing a message without a scope (an empty scope addresses no box)";
28
+ if (!isWellFormedUnicode(v))
29
+ return `MailboxStore: ${what} is not well-formed Unicode (lone surrogate) (refusing — the driver would store it as U+FFFD and alias a different key)`;
30
+ if ([...v].some((ch) => ch.charCodeAt(0) < 0x20 || ch.charCodeAt(0) === 0x7f))
31
+ return `MailboxStore: ${what} contains control characters (refusing — identity keys must be byte-exact addressable)`;
32
+ if (Buffer.byteLength(v, "utf8") > MAILBOX_KEY_MAX_UTF8_BYTES) {
33
+ return `MailboxStore: ${what} exceeds the ${MAILBOX_KEY_MAX_UTF8_BYTES}-byte key column (refusing — a truncated key would desync from its byte-exact lookups)`;
34
+ }
35
+ return undefined;
26
36
  }
27
- function assertIdentityChars(what, v) {
28
- if ([...v].some((ch) => ch.charCodeAt(0) < 0x20 || ch.charCodeAt(0) === 0x7f)) {
29
- throw new Error(`MailboxStore: ${what} contains control characters (refusing — identity keys must be byte-exact addressable)`);
30
- }
37
+ function assertMailboxKey(what, v) {
38
+ const problem = mailboxKeyProblem(what, v);
39
+ if (problem !== undefined)
40
+ throw new Error(problem);
31
41
  }
32
- function assertKeyBytes(what, v) {
33
- if (Buffer.byteLength(v, "utf8") > 190) {
34
- throw new Error(`MailboxStore: ${what} exceeds the 190-byte key column (refusing — a truncated key would desync from its byte-exact lookups)`);
35
- }
42
+ const keyless = (scope, handle, owner) => mailboxKeyProblem("scope", scope) !== undefined || mailboxKeyProblem("handle", handle) !== undefined || (owner !== undefined && mailboxKeyProblem("owner", owner) !== undefined);
43
+ function storedKeyText(v) {
44
+ if (!Buffer.isBuffer(v))
45
+ return String(v);
46
+ return isUtf8(v) ? v.toString("utf8") : undefined;
47
+ }
48
+ export function storedMailboxKeyless(scopeKey, handle) {
49
+ const scope = storedKeyText(scopeKey);
50
+ const h = storedKeyText(handle);
51
+ return scope === undefined || h === undefined || keyless(scope, h);
52
+ }
53
+ export const MAILBOX_KEYED_TABLES = [MAILBOX_TABLE, MAILBOX_MSG_TABLE, MAILBOX_TOMBSTONE_TABLE];
54
+ export function mailboxKeyDeleteSql(dialect, table) {
55
+ return dialect === "tidb" ? `DELETE FROM ${table} WHERE scope_key = ? AND handle = ?` : `DELETE FROM ${table} WHERE scope_key = $1 AND handle = $2`;
36
56
  }
37
57
  function bufToStr(v) {
38
58
  return Buffer.isBuffer(v) ? v.toString("utf8") : String(v);
@@ -220,11 +240,8 @@ export class TiDBMailboxStore {
220
240
  }
221
241
  }
222
242
  async append(scope, handle, msg) {
223
- assertScope(scope);
224
- assertIdentityChars("scope", scope);
225
- assertIdentityChars("handle", handle);
226
- assertKeyBytes("scope", scope);
227
- assertKeyBytes("handle", handle);
243
+ assertMailboxKey("scope", scope);
244
+ assertMailboxKey("handle", handle);
228
245
  const hopChain = encodeHopChain(msg.hopChain);
229
246
  const peerMeta = encodePeerMeta(msg.peerMeta);
230
247
  return this.tx(async (c) => {
@@ -244,6 +261,9 @@ export class TiDBMailboxStore {
244
261
  });
245
262
  }
246
263
  async claimLease(scope, handle, owner, ttlMs, now = Date.now()) {
264
+ assertMailboxKey("owner", owner);
265
+ if (keyless(scope, handle))
266
+ return null;
247
267
  return this.tx(async (c) => {
248
268
  const [boxes] = (await c.query(`SELECT lease_owner, lease_expires_at_ms FROM ${MAILBOX_TABLE} WHERE scope_key = ? AND handle = ? FOR UPDATE`, [scope, handle]));
249
269
  if (boxes.length === 0)
@@ -263,6 +283,8 @@ export class TiDBMailboxStore {
263
283
  });
264
284
  }
265
285
  async ack(scope, handle, owner, upToSeq) {
286
+ if (keyless(scope, handle, owner))
287
+ return { acked: false };
266
288
  return this.tx(async (c) => {
267
289
  const [rows] = (await c.query(`SELECT lease_owner FROM ${MAILBOX_TABLE} WHERE scope_key = ? AND handle = ? FOR UPDATE`, [scope, handle]));
268
290
  if (rows.length === 0 || rows[0].lease_owner == null || bufToStr(rows[0].lease_owner) !== owner)
@@ -274,28 +296,33 @@ export class TiDBMailboxStore {
274
296
  });
275
297
  }
276
298
  async releaseLease(scope, handle, owner) {
299
+ if (keyless(scope, handle, owner))
300
+ return;
277
301
  await this.pool.query(`UPDATE ${MAILBOX_TABLE} SET lease_owner = NULL, lease_expires_at_ms = NULL, lease_max_seq = NULL
278
302
  WHERE scope_key = ? AND handle = ? AND lease_owner = ?`, [scope, handle, owner]);
279
303
  }
280
304
  async peekCount(scope, handle) {
305
+ if (keyless(scope, handle))
306
+ return 0;
281
307
  const [rows] = (await this.pool.query(`SELECT COUNT(*) AS n FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ?`, [scope, handle]));
282
308
  return Number(rows[0].n);
283
309
  }
284
310
  async drop(scope, handle) {
311
+ if (keyless(scope, handle))
312
+ return;
285
313
  await this.tx(async (c) => {
286
- await c.query(`DELETE FROM ${MAILBOX_TABLE} WHERE scope_key = ? AND handle = ?`, [scope, handle]);
287
- await c.query(`DELETE FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ?`, [scope, handle]);
288
- await c.query(`DELETE FROM ${MAILBOX_TOMBSTONE_TABLE} WHERE scope_key = ? AND handle = ?`, [scope, handle]);
314
+ for (const table of MAILBOX_KEYED_TABLES)
315
+ await c.query(mailboxKeyDeleteSql("tidb", table), [scope, handle]);
289
316
  });
290
317
  }
291
318
  async reap(scope, now, opts) {
292
- if (opts?.maxAgeMs === undefined)
319
+ if (opts?.maxAgeMs === undefined || mailboxKeyProblem("scope", scope) !== undefined)
293
320
  return 0;
294
321
  const cutoff = now - opts.maxAgeMs;
295
322
  const [cands] = (await this.pool.query(`SELECT handle FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? GROUP BY handle HAVING MAX(sent_at_ms) < ?`, [scope, cutoff]));
296
- if (cands.length === 0)
323
+ const handles = cands.filter((r) => !storedMailboxKeyless(scope, r.handle)).map((r) => bufToStr(r.handle));
324
+ if (handles.length === 0)
297
325
  return 0;
298
- const handles = cands.map((r) => bufToStr(r.handle));
299
326
  return this.tx(async (c) => {
300
327
  let dropped = 0;
301
328
  for (const h of handles) {
@@ -313,11 +340,8 @@ export class TiDBMailboxStore {
313
340
  });
314
341
  }
315
342
  async retireRecipient(scope, handle, at, label) {
316
- assertScope(scope);
317
- assertIdentityChars("scope", scope);
318
- assertIdentityChars("handle", handle);
319
- assertKeyBytes("scope", scope);
320
- assertKeyBytes("handle", handle);
343
+ if (keyless(scope, handle))
344
+ return false;
321
345
  await this.tx(async (c) => {
322
346
  await c.query(`INSERT INTO ${MAILBOX_TABLE} (scope_key, handle, scope, next_seq) VALUES (?, ?, ?, 1) ON DUPLICATE KEY UPDATE scope_key = scope_key`, [scope, handle, scope]);
323
347
  await c.query(`SELECT next_seq FROM ${MAILBOX_TABLE} WHERE scope_key = ? AND handle = ? FOR UPDATE`, [scope, handle]);
@@ -327,6 +351,8 @@ export class TiDBMailboxStore {
327
351
  return true;
328
352
  }
329
353
  async listRetiredRecipients(scope, handlePrefix) {
354
+ if (mailboxKeyProblem("scope", scope) !== undefined)
355
+ return [];
330
356
  const [rows] = await this.pool.query(`SELECT handle, retired_at_ms, label FROM ${MAILBOX_TOMBSTONE_TABLE} WHERE scope_key = ? AND handle LIKE ? ESCAPE '\\\\'`, [scope, `${escapeLike(handlePrefix)}%`]);
331
357
  return rows.map(rowToTombstone);
332
358
  }
@@ -355,18 +381,12 @@ export class PgMailboxStore {
355
381
  c.release();
356
382
  }
357
383
  }
358
- assertIdentity(what, v) {
359
- if (v !== undefined && pgHasUnstorable(v))
360
- throw new PgUnstorableError(`mailbox ${what}`);
361
- }
362
384
  async append(scope, handle, msg) {
363
- assertScope(scope);
364
- this.assertIdentity("scope", scope);
365
- this.assertIdentity("handle", handle);
366
- this.assertIdentity("from", msg.from);
385
+ assertMailboxKey("scope", scope);
386
+ assertMailboxKey("handle", handle);
387
+ if (msg.from !== undefined && pgHasUnstorable(msg.from))
388
+ throw new PgUnstorableError("mailbox from");
367
389
  const content = pgSanitizeText(msg.content);
368
- assertKeyBytes("scope", scope);
369
- assertKeyBytes("handle", handle);
370
390
  const hopChain = encodeHopChain(msg.hopChain);
371
391
  const peerMeta = encodePeerMeta(msg.peerMeta);
372
392
  return this.tx(async (c) => {
@@ -394,7 +414,9 @@ export class PgMailboxStore {
394
414
  });
395
415
  }
396
416
  async claimLease(scope, handle, owner, ttlMs, now = Date.now()) {
397
- this.assertIdentity("owner", owner);
417
+ assertMailboxKey("owner", owner);
418
+ if (keyless(scope, handle))
419
+ return null;
398
420
  return this.tx(async (c) => {
399
421
  const { rows: boxes } = await c.query(`SELECT lease_owner, lease_expires_at_ms FROM ${MAILBOX_TABLE} WHERE scope_key = $1 AND handle = $2 FOR UPDATE`, [scope, handle]);
400
422
  if (boxes.length === 0)
@@ -411,6 +433,8 @@ export class PgMailboxStore {
411
433
  });
412
434
  }
413
435
  async ack(scope, handle, owner, upToSeq) {
436
+ if (keyless(scope, handle, owner))
437
+ return { acked: false };
414
438
  return this.tx(async (c) => {
415
439
  const r = (await c.query(`SELECT lease_owner FROM ${MAILBOX_TABLE} WHERE scope_key = $1 AND handle = $2 FOR UPDATE`, [scope, handle]));
416
440
  if (r.rows.length === 0 || r.rows[0].lease_owner == null || bufToStr(r.rows[0].lease_owner) !== owner)
@@ -422,31 +446,33 @@ export class PgMailboxStore {
422
446
  });
423
447
  }
424
448
  async releaseLease(scope, handle, owner) {
425
- this.assertIdentity("owner", owner);
449
+ if (keyless(scope, handle, owner))
450
+ return;
426
451
  await this.pool.query(`UPDATE ${MAILBOX_TABLE} SET lease_owner = NULL, lease_expires_at_ms = NULL, lease_max_seq = NULL
427
452
  WHERE scope_key = $1 AND handle = $2 AND lease_owner = $3`, [scope, handle, owner]);
428
453
  }
429
454
  async peekCount(scope, handle) {
430
- if (pgHasUnstorable(scope) || pgHasUnstorable(handle))
455
+ if (keyless(scope, handle))
431
456
  return 0;
432
457
  const { rows } = await this.pool.query(`SELECT COUNT(*) AS n FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2`, [scope, handle]);
433
458
  return Number(rows[0].n);
434
459
  }
435
460
  async drop(scope, handle) {
461
+ if (keyless(scope, handle))
462
+ return;
436
463
  await this.tx(async (c) => {
437
- await c.query(`DELETE FROM ${MAILBOX_TABLE} WHERE scope_key = $1 AND handle = $2`, [scope, handle]);
438
- await c.query(`DELETE FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2`, [scope, handle]);
439
- await c.query(`DELETE FROM ${MAILBOX_TOMBSTONE_TABLE} WHERE scope_key = $1 AND handle = $2`, [scope, handle]);
464
+ for (const table of MAILBOX_KEYED_TABLES)
465
+ await c.query(mailboxKeyDeleteSql("pg", table), [scope, handle]);
440
466
  });
441
467
  }
442
468
  async reap(scope, now, opts) {
443
- if (opts?.maxAgeMs === undefined)
469
+ if (opts?.maxAgeMs === undefined || mailboxKeyProblem("scope", scope) !== undefined)
444
470
  return 0;
445
471
  const cutoff = now - opts.maxAgeMs;
446
472
  const { rows: cands } = await this.pool.query(`SELECT handle FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 GROUP BY handle HAVING MAX(sent_at_ms) < $2`, [scope, cutoff]);
447
- if (cands.length === 0)
473
+ const handles = cands.filter((r) => !storedMailboxKeyless(scope, r.handle)).map((r) => String(r.handle));
474
+ if (handles.length === 0)
448
475
  return 0;
449
- const handles = cands.map((r) => String(r.handle));
450
476
  return this.tx(async (c) => {
451
477
  let dropped = 0;
452
478
  for (const h of handles) {
@@ -464,11 +490,8 @@ export class PgMailboxStore {
464
490
  });
465
491
  }
466
492
  async retireRecipient(scope, handle, at, label) {
467
- assertScope(scope);
468
- this.assertIdentity("scope", scope);
469
- this.assertIdentity("handle", handle);
470
- assertKeyBytes("scope", scope);
471
- assertKeyBytes("handle", handle);
493
+ if (keyless(scope, handle))
494
+ return false;
472
495
  const bounded = boundedLabel(label);
473
496
  const stored = bounded === null ? null : pgSanitizeText(bounded);
474
497
  await this.tx(async (c) => {
@@ -480,6 +503,8 @@ export class PgMailboxStore {
480
503
  return true;
481
504
  }
482
505
  async listRetiredRecipients(scope, handlePrefix) {
506
+ if (mailboxKeyProblem("scope", scope) !== undefined)
507
+ return [];
483
508
  const { rows } = await this.pool.query(`SELECT handle, retired_at_ms, label FROM ${MAILBOX_TOMBSTONE_TABLE} WHERE scope_key = $1 AND handle LIKE $2 ESCAPE '\\'`, [scope, `${escapeLike(handlePrefix)}%`]);
484
509
  return rows.map(rowToTombstone);
485
510
  }
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { PG_MEMORY_ENGINE_TABLES } from "./memory-engine-pg.js";
3
3
  import { createZodColumnReader, decodeJsonColumn, jsonColumnText, SqlJsonColumnError } from "./sql-json-column.js";
4
+ import { echoCallerText } from "../caller-text-echo.js";
4
5
  export const PG_MEMORY_ENGINE_META_TABLE = "agent_memory_engine_meta";
5
6
  export const EMBEDDER_IDENTITY_META_KEY = "embedder_identity";
6
7
  export const CAS_MAX_ROUNDS = 3;
@@ -22,7 +23,7 @@ function parseStoredText(text) {
22
23
  catch (err) {
23
24
  if (!(err instanceof SqlJsonColumnError))
24
25
  throw err;
25
- return { kind: "unreadable", raw: `${text.slice(0, 160)} [${err.problem}: ${err.message.slice(0, 120)}]` };
26
+ return { kind: "unreadable", raw: `${echoCallerText(text, { maxChars: 160 })} [${err.problem}: ${echoCallerText(err.message, { maxChars: 120 })}]` };
26
27
  }
27
28
  }
28
29
  async function readStored(query) {
@@ -1,6 +1,6 @@
1
1
  import { type UsageRow } from "../usage-analytics.js";
2
2
  import type { TaskStatus } from "@sema-agent/core";
3
- import { type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
3
+ import { 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 { LedgerEventType } from "../trace/ledger-events.js";
5
5
  import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
6
6
  /**
@@ -22,7 +22,7 @@ export interface RunStoreCheckpointProbe {
22
22
  hasPending(sessionId: string): Promise<boolean>;
23
23
  hasExpired(sessionId: string): Promise<boolean>;
24
24
  }
25
- export declare class MemoryRunStore {
25
+ export declare class MemoryRunStore implements InProcessClaimFence {
26
26
  private readonly runs;
27
27
  /** sessionId → taskId — the single-active-run claim (the `task_active` table). */
28
28
  private readonly active;
@@ -82,19 +82,11 @@ export declare class MemoryRunStore {
82
82
  * [ref](codex R1 [medium]):认领 = 接管 `instanceId`(= 当前持有副本;SQL 孪生同注)。CAS 输不改。 */
83
83
  markResuming(taskId: string, instanceId: string): Promise<boolean>;
84
84
  getActiveTaskId(sessionId: string): Promise<string | undefined>;
85
+ /** 认领当写栅栏用(契约 = `store-contracts.ts` 的 `InProcessClaimFence`):判、刷新活性与 `write` 是同一个同步段。 */
86
+ whileHoldingClaim(sessionId: string, taskId: string, write: () => FenceLanded): boolean;
85
87
  getRun(taskId: string): Promise<RunRecord | undefined>;
86
88
  /** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
87
- listRuns(opts: {
88
- status?: string;
89
- jobId?: string;
90
- source?: string;
91
- owner?: string;
92
- cursor?: {
93
- createdAt: string;
94
- taskId: string;
95
- };
96
- limit: number;
97
- }): Promise<RunRecord[]>;
89
+ listRuns(opts: RunListQuery): Promise<RunRecord[]>;
98
90
  /**
99
91
  * S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
100
92
  * `createdAt DESC, taskId DESC`,与本店 {@link MemoryRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
@@ -231,6 +231,14 @@ export class MemoryRunStore {
231
231
  async getActiveTaskId(sessionId) {
232
232
  return this.active.get(sessionId);
233
233
  }
234
+ whileHoldingClaim(sessionId, taskId, write) {
235
+ const r = this.runs.get(taskId);
236
+ if (this.active.get(sessionId) !== taskId || r === undefined || r.status !== "running")
237
+ return false;
238
+ r.updatedAt = new Date();
239
+ write();
240
+ return true;
241
+ }
234
242
  async getRun(taskId) {
235
243
  const r = this.runs.get(taskId);
236
244
  return r ? this.toRecord(r) : undefined;