@sema-agent/server 1.323.0 → 2.0.1

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 (565) hide show
  1. package/dist/approval-hmac.d.ts +40 -0
  2. package/dist/approval-hmac.js +67 -0
  3. package/dist/approval.d.ts +31 -0
  4. package/dist/approval.js +72 -4
  5. package/dist/audit.d.ts +26 -0
  6. package/dist/audit.js +40 -1
  7. package/dist/auth-bridge.d.ts +10 -0
  8. package/dist/auth-bridge.js +47 -4
  9. package/dist/auth-keys.d.ts +19 -0
  10. package/dist/auth-keys.js +13 -0
  11. package/dist/bake-runner/main.d.ts +5 -0
  12. package/dist/bake-runner/main.js +43 -4
  13. package/dist/bake-runner/protocol.d.ts +57 -0
  14. package/dist/bake-runner/protocol.js +58 -3
  15. package/dist/bake-runner/runner.d.ts +54 -0
  16. package/dist/bake-runner/runner.js +87 -5
  17. package/dist/bench/l8/artifact.d.ts +63 -0
  18. package/dist/bench/l8/artifact.js +61 -2
  19. package/dist/bench/l8/escape.d.ts +36 -0
  20. package/dist/bench/l8/escape.js +25 -0
  21. package/dist/bench/l8/index.d.ts +14 -0
  22. package/dist/bench/l8/index.js +14 -0
  23. package/dist/bench/l8/probes.d.ts +74 -0
  24. package/dist/bench/l8/probes.js +60 -0
  25. package/dist/bench/l8/run-probes.d.ts +43 -0
  26. package/dist/bench/l8/run-probes.js +45 -2
  27. package/dist/bench/s1/arms.d.ts +126 -0
  28. package/dist/bench/s1/arms.js +188 -13
  29. package/dist/bench/s1/live-deps.d.ts +66 -0
  30. package/dist/bench/s1/live-deps.js +249 -11
  31. package/dist/bench/s1/oracle.d.ts +49 -0
  32. package/dist/bench/s1/oracle.js +36 -1
  33. package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
  34. package/dist/bench/s1/repair-oracle-adapter.js +30 -0
  35. package/dist/bench/s1/reviewer.d.ts +38 -0
  36. package/dist/bench/s1/reviewer.js +34 -0
  37. package/dist/bench/s1/row.d.ts +77 -0
  38. package/dist/bench/s1/row.js +62 -0
  39. package/dist/bench/s1/run-firm.d.ts +47 -0
  40. package/dist/bench/s1/run-firm.js +70 -4
  41. package/dist/bench/s1/runner-ctx.d.ts +71 -0
  42. package/dist/bench/s1/runner-ctx.js +36 -0
  43. package/dist/bench/s1/tasks.d.ts +108 -0
  44. package/dist/bench/s1/tasks.js +115 -0
  45. package/dist/boot/budget-tracing.d.ts +48 -0
  46. package/dist/boot/budget-tracing.js +86 -0
  47. package/dist/boot/config-center.d.ts +62 -0
  48. package/dist/boot/config-center.js +995 -0
  49. package/dist/boot/coordinators.d.ts +33 -0
  50. package/dist/boot/coordinators.js +97 -0
  51. package/dist/boot/execution-env.d.ts +26 -0
  52. package/dist/boot/execution-env.js +370 -0
  53. package/dist/boot/leader.d.ts +27 -0
  54. package/dist/boot/leader.js +81 -0
  55. package/dist/boot/reapers.d.ts +53 -0
  56. package/dist/boot/reapers.js +252 -0
  57. package/dist/boot/resolve-spec.d.ts +70 -0
  58. package/dist/boot/resolve-spec.js +1072 -0
  59. package/dist/boot/runner-deps.d.ts +101 -0
  60. package/dist/boot/runner-deps.js +343 -0
  61. package/dist/boot/runtime-caps.d.ts +21 -0
  62. package/dist/boot/runtime-caps.js +62 -0
  63. package/dist/boot/session-faces.d.ts +57 -0
  64. package/dist/boot/session-faces.js +157 -0
  65. package/dist/boot/shutdown.d.ts +50 -0
  66. package/dist/boot/shutdown.js +129 -0
  67. package/dist/boot/stores.d.ts +32 -0
  68. package/dist/boot/stores.js +361 -0
  69. package/dist/boot/workflow-orchestration.d.ts +46 -0
  70. package/dist/boot/workflow-orchestration.js +150 -0
  71. package/dist/boot-reclaim.d.ts +19 -0
  72. package/dist/boot-reclaim.js +20 -0
  73. package/dist/brain.d.ts +25 -0
  74. package/dist/brain.js +61 -1
  75. package/dist/budget.d.ts +66 -0
  76. package/dist/budget.js +128 -4
  77. package/dist/capabilities/builtin-tools.d.ts +3 -0
  78. package/dist/capabilities/builtin-tools.js +3 -0
  79. package/dist/capabilities/center-plugins.d.ts +17 -1
  80. package/dist/capabilities/center-plugins.js +45 -4
  81. package/dist/capabilities/center-prompts.d.ts +46 -0
  82. package/dist/capabilities/center-prompts.js +57 -3
  83. package/dist/capabilities/code-review-council.d.ts +28 -0
  84. package/dist/capabilities/code-review-council.js +40 -7
  85. package/dist/capabilities/collab-wire.d.ts +2 -0
  86. package/dist/capabilities/collab-workflows.d.ts +7 -0
  87. package/dist/capabilities/collab-workflows.js +44 -1
  88. package/dist/capabilities/oa-tools.d.ts +6 -0
  89. package/dist/capabilities/oa-tools.js +10 -0
  90. package/dist/capabilities/prompt.d.ts +14 -0
  91. package/dist/capabilities/prompt.js +14 -0
  92. package/dist/capabilities/prompts/code-review.d.ts +14 -0
  93. package/dist/capabilities/prompts/code-review.js +14 -0
  94. package/dist/capabilities/prompts/identity.d.ts +6 -0
  95. package/dist/capabilities/prompts/identity.js +6 -0
  96. package/dist/capabilities/prompts/team.d.ts +4 -0
  97. package/dist/capabilities/prompts/team.js +4 -0
  98. package/dist/capabilities/repo-tools.d.ts +11 -0
  99. package/dist/capabilities/repo-tools.js +8 -0
  100. package/dist/capabilities/sandbox-file-send.d.ts +95 -0
  101. package/dist/capabilities/sandbox-file-send.js +91 -3
  102. package/dist/capabilities/scenarios.d.ts +130 -0
  103. package/dist/capabilities/scenarios.js +180 -7
  104. package/dist/capabilities/select-environment-tool.d.ts +7 -0
  105. package/dist/capabilities/select-environment-tool.js +30 -0
  106. package/dist/capabilities/send-user-file-tool.d.ts +33 -0
  107. package/dist/capabilities/send-user-file-tool.js +65 -1
  108. package/dist/capabilities/skills.d.ts +29 -0
  109. package/dist/capabilities/skills.js +13 -2
  110. package/dist/capabilities/team.d.ts +22 -0
  111. package/dist/capabilities/team.js +22 -2
  112. package/dist/capabilities/tool-defer.d.ts +26 -0
  113. package/dist/capabilities/tool-defer.js +3 -0
  114. package/dist/config-center/apply-effective.d.ts +65 -0
  115. package/dist/config-center/apply-effective.js +204 -12
  116. package/dist/config-center/http-client.d.ts +34 -0
  117. package/dist/config-center/http-client.js +68 -0
  118. package/dist/config-center/restart-signal.d.ts +22 -0
  119. package/dist/config-center/restart-signal.js +39 -0
  120. package/dist/config-center/skills-mcp.d.ts +20 -1
  121. package/dist/config-center/skills-mcp.js +42 -7
  122. package/dist/config-center/types.d.ts +92 -1
  123. package/dist/config-lkg.d.ts +12 -0
  124. package/dist/config-lkg.js +41 -0
  125. package/dist/config-provider.d.ts +78 -0
  126. package/dist/config-provider.js +165 -2
  127. package/dist/config-types.d.ts +585 -1
  128. package/dist/config.d.ts +78 -2
  129. package/dist/config.js +874 -301
  130. package/dist/degenerate-instrument.d.ts +38 -0
  131. package/dist/degenerate-instrument.js +55 -3
  132. package/dist/elicitation.d.ts +39 -0
  133. package/dist/elicitation.js +87 -3
  134. package/dist/env-facts.d.ts +61 -0
  135. package/dist/env-facts.js +73 -7
  136. package/dist/fleet/fleet-bus.d.ts +185 -0
  137. package/dist/fleet/fleet-bus.js +333 -27
  138. package/dist/fleet/subagent-tail-bus.d.ts +11 -0
  139. package/dist/fleet/subagent-tail-bus.js +33 -2
  140. package/dist/fleet-client.d.ts +38 -0
  141. package/dist/fleet-client.js +44 -9
  142. package/dist/fleet-lease.d.ts +46 -0
  143. package/dist/fleet-lease.js +73 -5
  144. package/dist/hooks/branch-transcript.d.ts +8 -0
  145. package/dist/hooks/branch-transcript.js +33 -0
  146. package/dist/hooks/cc-stop-prompt.d.ts +42 -0
  147. package/dist/hooks/cc-stop-prompt.js +43 -1
  148. package/dist/hooks/hook-llm.d.ts +15 -0
  149. package/dist/hooks/hook-llm.js +47 -0
  150. package/dist/hooks/hook-runner.d.ts +89 -0
  151. package/dist/hooks/hook-runner.js +327 -18
  152. package/dist/http/idempotency.d.ts +31 -0
  153. package/dist/http/idempotency.js +34 -0
  154. package/dist/http/principal-gate.d.ts +17 -0
  155. package/dist/http/principal-gate.js +35 -3
  156. package/dist/http/route-ctx.d.ts +36 -0
  157. package/dist/http/routes/approvals-assistant.d.ts +25 -0
  158. package/dist/http/routes/approvals-assistant.js +253 -23
  159. package/dist/http/routes/attachments.js +22 -4
  160. package/dist/http/routes/capabilities.d.ts +7 -0
  161. package/dist/http/routes/capabilities.js +202 -1
  162. package/dist/http/routes/fleet.d.ts +6 -0
  163. package/dist/http/routes/fleet.js +94 -5
  164. package/dist/http/routes/images.d.ts +17 -0
  165. package/dist/http/routes/images.js +222 -14
  166. package/dist/http/routes/leader.d.ts +7 -0
  167. package/dist/http/routes/leader.js +10 -0
  168. package/dist/http/routes/memory-policy.d.ts +6 -0
  169. package/dist/http/routes/memory-policy.js +38 -6
  170. package/dist/http/routes/notify-wake.d.ts +10 -0
  171. package/dist/http/routes/notify-wake.js +19 -3
  172. package/dist/http/routes/observability.d.ts +6 -0
  173. package/dist/http/routes/observability.js +19 -2
  174. package/dist/http/routes/runs.d.ts +15 -0
  175. package/dist/http/routes/runs.js +464 -41
  176. package/dist/http/routes/session-sync.d.ts +15 -0
  177. package/dist/http/routes/session-sync.js +229 -19
  178. package/dist/http/routes/sessions-list.d.ts +6 -0
  179. package/dist/http/routes/sessions-list.js +35 -4
  180. package/dist/http/routes/sessions.d.ts +14 -0
  181. package/dist/http/routes/sessions.js +176 -31
  182. package/dist/http/routes/side-query.d.ts +8 -0
  183. package/dist/http/routes/side-query.js +26 -1
  184. package/dist/http/routes/tasks.d.ts +18 -0
  185. package/dist/http/routes/tasks.js +501 -38
  186. package/dist/http/routes/trace-usage.d.ts +10 -0
  187. package/dist/http/routes/trace-usage.js +66 -14
  188. package/dist/http/routes/workflows.d.ts +12 -0
  189. package/dist/http/routes/workflows.js +180 -19
  190. package/dist/http/run-meta.d.ts +11 -0
  191. package/dist/http/run-meta.js +6 -0
  192. package/dist/http/send.d.ts +19 -0
  193. package/dist/http/send.js +11 -0
  194. package/dist/http/server.d.ts +255 -0
  195. package/dist/http/server.js +921 -75
  196. package/dist/http/sse-log.d.ts +15 -0
  197. package/dist/http/sse-log.js +16 -0
  198. package/dist/http/tar.d.ts +9 -0
  199. package/dist/http/tar.js +21 -5
  200. package/dist/http/wire-gate.d.ts +9 -0
  201. package/dist/http/wire-gate.js +9 -0
  202. package/dist/http/wire-types.d.ts +187 -0
  203. package/dist/http/workspace-content.d.ts +8 -0
  204. package/dist/http/workspace-content.js +10 -0
  205. package/dist/images/bake-validate.d.ts +63 -0
  206. package/dist/images/bake-validate.js +71 -1
  207. package/dist/images/manifest.d.ts +19 -0
  208. package/dist/images/manifest.js +6 -1
  209. package/dist/index.d.ts +1 -5
  210. package/dist/index.js +10 -5
  211. package/dist/key-resolver.d.ts +23 -0
  212. package/dist/key-resolver.js +27 -2
  213. package/dist/leader/diffout.d.ts +23 -0
  214. package/dist/leader/diffout.js +8 -1
  215. package/dist/leader/diffup.d.ts +32 -0
  216. package/dist/leader/diffup.js +43 -0
  217. package/dist/leader/endpoint.d.ts +20 -0
  218. package/dist/leader/endpoint.js +25 -2
  219. package/dist/leader/fanout.d.ts +60 -0
  220. package/dist/leader/fanout.js +29 -3
  221. package/dist/leader/grader-env-factory.d.ts +74 -0
  222. package/dist/leader/grader-env-factory.js +62 -3
  223. package/dist/leader/leader.d.ts +97 -0
  224. package/dist/leader/leader.js +151 -4
  225. package/dist/leader/merge.d.ts +94 -0
  226. package/dist/leader/merge.js +112 -9
  227. package/dist/leader/planner.d.ts +52 -0
  228. package/dist/leader/planner.js +47 -3
  229. package/dist/leader/repair-oracle.d.ts +61 -0
  230. package/dist/leader/repair-oracle.js +59 -1
  231. package/dist/leader/repair-wire.d.ts +78 -0
  232. package/dist/leader/repair-wire.js +79 -2
  233. package/dist/leader/wire.d.ts +79 -0
  234. package/dist/leader/wire.js +255 -8
  235. package/dist/lsp/e2b-bridge.d.ts +16 -0
  236. package/dist/lsp/e2b-bridge.js +32 -4
  237. package/dist/lsp/e2b-manager.d.ts +10 -0
  238. package/dist/lsp/e2b-manager.js +18 -2
  239. package/dist/lsp/lsp-frames.d.ts +12 -0
  240. package/dist/lsp/lsp-frames.js +12 -0
  241. package/dist/lsp/manager.d.ts +12 -0
  242. package/dist/lsp/manager.js +35 -2
  243. package/dist/lsp/types.d.ts +10 -0
  244. package/dist/lsp/ws-transport.d.ts +12 -0
  245. package/dist/lsp/ws-transport.js +53 -5
  246. package/dist/lsp-evict.d.ts +14 -0
  247. package/dist/lsp-evict.js +15 -1
  248. package/dist/main.js +530 -2129
  249. package/dist/memory-export.d.ts +1 -0
  250. package/dist/memory-export.js +4 -0
  251. package/dist/memory-scope.d.ts +49 -0
  252. package/dist/memory-scope.js +87 -4
  253. package/dist/memory-sync-client.d.ts +24 -0
  254. package/dist/memory-sync-client.js +41 -2
  255. package/dist/memory-sync.d.ts +23 -0
  256. package/dist/memory-sync.js +70 -1
  257. package/dist/model-select.d.ts +39 -0
  258. package/dist/model-select.js +42 -1
  259. package/dist/observability/cost-quota.d.ts +20 -0
  260. package/dist/observability/cost-quota.js +4 -0
  261. package/dist/observability/cost-taxonomy.d.ts +45 -0
  262. package/dist/observability/cost-taxonomy.js +34 -0
  263. package/dist/observability/logger.d.ts +1 -0
  264. package/dist/observability/logger.js +6 -0
  265. package/dist/observability/metrics.d.ts +13 -0
  266. package/dist/observability/metrics.js +63 -0
  267. package/dist/observability/otel-exporter.d.ts +9 -0
  268. package/dist/observability/otel-exporter.js +11 -3
  269. package/dist/observability/principal-context.d.ts +9 -0
  270. package/dist/observability/principal-context.js +9 -0
  271. package/dist/observability/prompt-manifest.d.ts +37 -0
  272. package/dist/observability/prompt-manifest.js +35 -1
  273. package/dist/observability/rate-limit.d.ts +10 -0
  274. package/dist/observability/rate-limit.js +1 -0
  275. package/dist/observability/tool-trace.d.ts +24 -0
  276. package/dist/observability/tool-trace.js +41 -1
  277. package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
  278. package/dist/orchestration/hardened-vm-runner.js +119 -4
  279. package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
  280. package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
  281. package/dist/orchestration/hardened-vm-worker.js +27 -0
  282. package/dist/orchestration/subagent-steer.d.ts +44 -0
  283. package/dist/orchestration/subagent-steer.js +45 -1
  284. package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
  285. package/dist/orchestration/workflow-agent-steer.js +80 -1
  286. package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
  287. package/dist/orchestration/workflow-completion-inbox.js +283 -32
  288. package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
  289. package/dist/orchestration/workflow-notify-journal.js +187 -13
  290. package/dist/parked-decide.d.ts +61 -0
  291. package/dist/parked-decide.js +58 -1
  292. package/dist/per-task-image.d.ts +54 -0
  293. package/dist/per-task-image.js +41 -0
  294. package/dist/plan-cache-probe.d.ts +35 -0
  295. package/dist/plan-cache-probe.js +18 -3
  296. package/dist/plugins/approval-exemption-store.d.ts +7 -0
  297. package/dist/plugins/approval-exemption-store.js +28 -2
  298. package/dist/plugins/approval-store-sql.d.ts +64 -0
  299. package/dist/plugins/approval-store-sql.js +32 -0
  300. package/dist/plugins/background-agent-store-sql.d.ts +55 -0
  301. package/dist/plugins/background-agent-store-sql.js +91 -4
  302. package/dist/plugins/background-shell-support.d.ts +98 -0
  303. package/dist/plugins/background-shell-support.js +117 -13
  304. package/dist/plugins/blob-backend.d.ts +104 -0
  305. package/dist/plugins/blob-backend.js +163 -6
  306. package/dist/plugins/breaker-state-sql.d.ts +65 -1
  307. package/dist/plugins/breaker-state-sql.js +46 -8
  308. package/dist/plugins/caching-session-store.d.ts +42 -0
  309. package/dist/plugins/caching-session-store.js +63 -3
  310. package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
  311. package/dist/plugins/checkpoint-store-sql.js +239 -11
  312. package/dist/plugins/file-outcome-sink.d.ts +4 -0
  313. package/dist/plugins/file-outcome-sink.js +9 -0
  314. package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
  315. package/dist/plugins/file-resume-anchor-store.js +43 -4
  316. package/dist/plugins/file-run-store.d.ts +90 -0
  317. package/dist/plugins/file-run-store.js +145 -13
  318. package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
  319. package/dist/plugins/file-snapshot-store-sql.js +178 -10
  320. package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
  321. package/dist/plugins/file-workflow-journal-store.js +10 -0
  322. package/dist/plugins/fork-routing-session-store.d.ts +47 -0
  323. package/dist/plugins/fork-routing-session-store.js +51 -3
  324. package/dist/plugins/host-platform.d.ts +69 -0
  325. package/dist/plugins/host-platform.js +81 -2
  326. package/dist/plugins/image-bake-store-sql.d.ts +136 -1
  327. package/dist/plugins/image-bake-store-sql.js +241 -7
  328. package/dist/plugins/image-index-sql.d.ts +46 -0
  329. package/dist/plugins/image-index-sql.js +113 -4
  330. package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
  331. package/dist/plugins/k8s-bg-scripts.js +88 -4
  332. package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
  333. package/dist/plugins/k8s-exec-protocol.js +41 -0
  334. package/dist/plugins/local-checkpoint-store.d.ts +40 -0
  335. package/dist/plugins/local-checkpoint-store.js +86 -7
  336. package/dist/plugins/local-session-store.d.ts +157 -0
  337. package/dist/plugins/local-session-store.js +289 -34
  338. package/dist/plugins/local-task-attachment-store.js +14 -2
  339. package/dist/plugins/mailbox-store-sql.d.ts +38 -0
  340. package/dist/plugins/mailbox-store-sql.js +39 -6
  341. package/dist/plugins/memory-engine-pg.d.ts +30 -0
  342. package/dist/plugins/memory-engine-pg.js +122 -4
  343. package/dist/plugins/memory-engine-tidb.d.ts +26 -0
  344. package/dist/plugins/memory-engine-tidb.js +115 -5
  345. package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
  346. package/dist/plugins/memory-engine-vector-util.js +5 -0
  347. package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
  348. package/dist/plugins/memory-resume-anchor-store.js +17 -0
  349. package/dist/plugins/memory-run-store.d.ts +63 -0
  350. package/dist/plugins/memory-run-store.js +73 -8
  351. package/dist/plugins/memory-session-policy-store.d.ts +17 -0
  352. package/dist/plugins/memory-session-policy-store.js +17 -0
  353. package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
  354. package/dist/plugins/memory-sync-store-pg.js +45 -2
  355. package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
  356. package/dist/plugins/memory-sync-store-tidb.js +31 -1
  357. package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
  358. package/dist/plugins/outcome-ledger-sql.js +97 -3
  359. package/dist/plugins/pg-approval-store.d.ts +7 -0
  360. package/dist/plugins/pg-approval-store.js +7 -0
  361. package/dist/plugins/pg-breaker-state.d.ts +6 -0
  362. package/dist/plugins/pg-breaker-state.js +6 -0
  363. package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
  364. package/dist/plugins/pg-checkpoint-store.js +8 -0
  365. package/dist/plugins/pg-cost-quota.d.ts +17 -0
  366. package/dist/plugins/pg-cost-quota.js +6 -0
  367. package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
  368. package/dist/plugins/pg-file-snapshot-store.js +6 -0
  369. package/dist/plugins/pg-image-bake.d.ts +9 -0
  370. package/dist/plugins/pg-image-bake.js +9 -0
  371. package/dist/plugins/pg-image-index.d.ts +9 -0
  372. package/dist/plugins/pg-image-index.js +9 -0
  373. package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
  374. package/dist/plugins/pg-outcome-ledger.js +9 -0
  375. package/dist/plugins/pg-pool.d.ts +50 -0
  376. package/dist/plugins/pg-pool.js +78 -0
  377. package/dist/plugins/pg-query.d.ts +11 -0
  378. package/dist/plugins/pg-rate-limiter.d.ts +23 -0
  379. package/dist/plugins/pg-rate-limiter.js +12 -2
  380. package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
  381. package/dist/plugins/pg-resume-anchor-store.js +5 -0
  382. package/dist/plugins/pg-run-store.d.ts +7 -0
  383. package/dist/plugins/pg-run-store.js +7 -0
  384. package/dist/plugins/pg-safe-json.d.ts +13 -0
  385. package/dist/plugins/pg-safe-json.js +40 -4
  386. package/dist/plugins/pg-session-policy-store.d.ts +5 -0
  387. package/dist/plugins/pg-session-policy-store.js +5 -0
  388. package/dist/plugins/pg-session-storage.d.ts +72 -0
  389. package/dist/plugins/pg-session-storage.js +152 -20
  390. package/dist/plugins/pg-session-store.d.ts +10 -0
  391. package/dist/plugins/pg-session-store.js +10 -0
  392. package/dist/plugins/pg-tool-result-store.d.ts +7 -0
  393. package/dist/plugins/pg-tool-result-store.js +7 -0
  394. package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
  395. package/dist/plugins/pg-workflow-journal-store.js +7 -0
  396. package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
  397. package/dist/plugins/pg-workflow-run-store.js +7 -0
  398. package/dist/plugins/posix-shell-fs.d.ts +3 -0
  399. package/dist/plugins/posix-shell-fs.js +31 -1
  400. package/dist/plugins/remote-env-adb.d.ts +25 -0
  401. package/dist/plugins/remote-env-adb.js +95 -12
  402. package/dist/plugins/remote-env-e2b.d.ts +146 -0
  403. package/dist/plugins/remote-env-e2b.js +371 -40
  404. package/dist/plugins/remote-env-file-error.d.ts +34 -0
  405. package/dist/plugins/remote-env-file-error.js +37 -0
  406. package/dist/plugins/remote-env-host.d.ts +151 -0
  407. package/dist/plugins/remote-env-host.js +468 -70
  408. package/dist/plugins/remote-env-k8s.d.ts +135 -0
  409. package/dist/plugins/remote-env-k8s.js +318 -32
  410. package/dist/plugins/remote-env-local-docker.d.ts +65 -0
  411. package/dist/plugins/remote-env-local-docker.js +149 -24
  412. package/dist/plugins/remote-env-ssh.d.ts +35 -0
  413. package/dist/plugins/remote-env-ssh.js +117 -20
  414. package/dist/plugins/remote-scratchpad.d.ts +38 -0
  415. package/dist/plugins/remote-scratchpad.js +32 -2
  416. package/dist/plugins/remote-shell.d.ts +22 -0
  417. package/dist/plugins/remote-shell.js +27 -1
  418. package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
  419. package/dist/plugins/resume-anchor-store-sql.js +11 -0
  420. package/dist/plugins/roster-store-sql.d.ts +27 -0
  421. package/dist/plugins/roster-store-sql.js +54 -0
  422. package/dist/plugins/run-store-sql.d.ts +194 -0
  423. package/dist/plugins/run-store-sql.js +210 -9
  424. package/dist/plugins/s3-presign.d.ts +20 -0
  425. package/dist/plugins/s3-presign.js +49 -2
  426. package/dist/plugins/scheduler-support.d.ts +12 -0
  427. package/dist/plugins/scheduler-support.js +75 -3
  428. package/dist/plugins/send-file-ledger.d.ts +15 -0
  429. package/dist/plugins/send-file-ledger.js +53 -4
  430. package/dist/plugins/send-user-file.d.ts +77 -0
  431. package/dist/plugins/send-user-file.js +92 -4
  432. package/dist/plugins/session-policy-store-sql.d.ts +15 -0
  433. package/dist/plugins/session-policy-store-sql.js +75 -3
  434. package/dist/plugins/session-store.d.ts +45 -0
  435. package/dist/plugins/session-store.js +51 -0
  436. package/dist/plugins/sql-driver.d.ts +77 -0
  437. package/dist/plugins/sql-driver.js +4 -0
  438. package/dist/plugins/sql-escape.d.ts +8 -0
  439. package/dist/plugins/sql-escape.js +8 -0
  440. package/dist/plugins/sql-row-helpers.d.ts +25 -0
  441. package/dist/plugins/sql-row-helpers.js +25 -0
  442. package/dist/plugins/store-backend.d.ts +103 -0
  443. package/dist/plugins/store-backend.js +120 -27
  444. package/dist/plugins/store-contracts.d.ts +62 -0
  445. package/dist/plugins/store-contracts.js +3 -0
  446. package/dist/plugins/task-attachment-store.d.ts +60 -0
  447. package/dist/plugins/task-attachment-store.js +49 -3
  448. package/dist/plugins/task-list-store-sql.d.ts +25 -0
  449. package/dist/plugins/task-list-store-sql.js +21 -0
  450. package/dist/plugins/tidb-approval-store.d.ts +6 -0
  451. package/dist/plugins/tidb-approval-store.js +6 -0
  452. package/dist/plugins/tidb-breaker-state.d.ts +5 -0
  453. package/dist/plugins/tidb-breaker-state.js +5 -0
  454. package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
  455. package/dist/plugins/tidb-checkpoint-store.js +7 -0
  456. package/dist/plugins/tidb-cost-quota.d.ts +6 -0
  457. package/dist/plugins/tidb-cost-quota.js +1 -0
  458. package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
  459. package/dist/plugins/tidb-file-snapshot-store.js +6 -0
  460. package/dist/plugins/tidb-image-bake.d.ts +8 -0
  461. package/dist/plugins/tidb-image-bake.js +8 -0
  462. package/dist/plugins/tidb-image-index.d.ts +8 -0
  463. package/dist/plugins/tidb-image-index.js +8 -0
  464. package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
  465. package/dist/plugins/tidb-outcome-ledger.js +10 -0
  466. package/dist/plugins/tidb-pool.d.ts +56 -0
  467. package/dist/plugins/tidb-pool.js +181 -1
  468. package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
  469. package/dist/plugins/tidb-rate-limiter.js +5 -2
  470. package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
  471. package/dist/plugins/tidb-resume-anchor-store.js +5 -0
  472. package/dist/plugins/tidb-run-store.d.ts +7 -0
  473. package/dist/plugins/tidb-run-store.js +7 -0
  474. package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
  475. package/dist/plugins/tidb-session-policy-store.js +5 -0
  476. package/dist/plugins/tidb-session-storage.d.ts +43 -0
  477. package/dist/plugins/tidb-session-storage.js +62 -3
  478. package/dist/plugins/tidb-session-store.d.ts +142 -0
  479. package/dist/plugins/tidb-session-store.js +264 -16
  480. package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
  481. package/dist/plugins/tidb-tool-result-store.js +7 -0
  482. package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
  483. package/dist/plugins/tidb-workflow-journal-store.js +7 -0
  484. package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
  485. package/dist/plugins/tidb-workflow-run-store.js +8 -0
  486. package/dist/plugins/tool-result-store-sql.d.ts +58 -0
  487. package/dist/plugins/tool-result-store-sql.js +38 -2
  488. package/dist/plugins/web-search.d.ts +35 -0
  489. package/dist/plugins/web-search.js +45 -9
  490. package/dist/plugins/workflow-journal-limits.d.ts +10 -0
  491. package/dist/plugins/workflow-journal-limits.js +10 -0
  492. package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
  493. package/dist/plugins/workflow-journal-store-sql.js +58 -2
  494. package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
  495. package/dist/plugins/workflow-run-store-sql.js +84 -6
  496. package/dist/plugins/worktree-isolation.d.ts +45 -0
  497. package/dist/plugins/worktree-isolation.js +105 -6
  498. package/dist/plugins/write-behind-counter.d.ts +56 -1
  499. package/dist/plugins/write-behind-counter.js +53 -14
  500. package/dist/principal-jwt.d.ts +24 -0
  501. package/dist/principal-jwt.js +60 -5
  502. package/dist/project-memory.d.ts +22 -0
  503. package/dist/project-memory.js +144 -15
  504. package/dist/prompts-domain-validate.d.ts +28 -0
  505. package/dist/prompts-domain-validate.js +42 -1
  506. package/dist/question.d.ts +33 -0
  507. package/dist/question.js +69 -2
  508. package/dist/resource-suspend.d.ts +21 -0
  509. package/dist/resource-suspend.js +16 -0
  510. package/dist/router/route-orchestration.d.ts +78 -0
  511. package/dist/router/route-orchestration.js +77 -0
  512. package/dist/run-local.d.ts +32 -0
  513. package/dist/run-local.js +172 -8
  514. package/dist/runs.d.ts +189 -4
  515. package/dist/runs.js +465 -24
  516. package/dist/runtime-caps-resolver.d.ts +106 -0
  517. package/dist/runtime-caps-resolver.js +89 -4
  518. package/dist/runtime-governance.d.ts +84 -0
  519. package/dist/runtime-governance.js +127 -1
  520. package/dist/sandbox-pkg-source.d.ts +23 -0
  521. package/dist/sandbox-pkg-source.js +37 -0
  522. package/dist/sealed-key.d.ts +54 -0
  523. package/dist/sealed-key.js +68 -3
  524. package/dist/security.d.ts +108 -0
  525. package/dist/security.js +145 -6
  526. package/dist/sema-registry.d.ts +21 -0
  527. package/dist/sema-registry.js +21 -0
  528. package/dist/session-leaf-bus.d.ts +31 -0
  529. package/dist/session-leaf-bus.js +32 -0
  530. package/dist/session-sync-content.d.ts +46 -0
  531. package/dist/session-sync-content.js +47 -1
  532. package/dist/session-sync-kernel.d.ts +89 -0
  533. package/dist/session-sync-kernel.js +48 -3
  534. package/dist/session-sync.d.ts +125 -0
  535. package/dist/session-sync.js +181 -8
  536. package/dist/session-titler.d.ts +26 -0
  537. package/dist/session-titler.js +41 -7
  538. package/dist/session-watch.d.ts +53 -1
  539. package/dist/session-watch.js +73 -9
  540. package/dist/sighup-idle.d.ts +30 -0
  541. package/dist/sighup-idle.js +12 -1
  542. package/dist/spec-fields.d.ts +91 -3
  543. package/dist/spec-fields.js +116 -4
  544. package/dist/task-cwd.d.ts +50 -0
  545. package/dist/task-cwd.js +68 -1
  546. package/dist/task-mcp.d.ts +47 -0
  547. package/dist/task-mcp.js +29 -0
  548. package/dist/task-settings.d.ts +175 -0
  549. package/dist/task-settings.js +226 -11
  550. package/dist/task-workflow.d.ts +67 -0
  551. package/dist/task-workflow.js +75 -8
  552. package/dist/tool-approval.d.ts +91 -0
  553. package/dist/tool-approval.js +215 -0
  554. package/dist/trace/artifacts.d.ts +9 -0
  555. package/dist/trace/artifacts.js +30 -5
  556. package/dist/trace/core-keyset-guard.d.ts +18 -0
  557. package/dist/trace/ledger-sink.d.ts +43 -0
  558. package/dist/trace/ledger-sink.js +64 -5
  559. package/dist/trace/project.d.ts +133 -0
  560. package/dist/trace/project.js +189 -2
  561. package/dist/trace/redact.d.ts +19 -0
  562. package/dist/trace/redact.js +66 -9
  563. package/dist/usage-analytics.d.ts +26 -0
  564. package/dist/usage-analytics.js +26 -3
  565. package/package.json +1 -1
@@ -1,3 +1,31 @@
1
+ /**
2
+ * File-backed async run registry + replayable event log — the DURABLE local twin of {@link MemoryRunStore} (and the
3
+ * file-backed sibling of {@link TiDBRunStore}/{@link PgRunStore}). P0.5 variant-1 (clay 2026-06-26): the file-backed
4
+ * follow-on the Memory twin's header promised — a locally-run HTTP service now keeps its runs ledger + trace event log
5
+ * across a process restart, not just in memory. Behaviour is byte-identical to MemoryRunStore (the shared parity oracle
6
+ * — same single-active claim, the suspend/needs_review parks that KEEP the claim, the markResuming CAS that resets
7
+ * cancel/preempt, the owner null-safe guard, the keyset list order, the (taskId,seq) event dedup, and the structuredClone
8
+ * write/read isolation); the only difference is durability.
9
+ *
10
+ * THREE on-disk data planes (all under `<root>/runs/`), each using core's crash-safe fs primitives — never hand-rolled
11
+ * fs (zero drift, no zero-length/torn corruption):
12
+ * 1. run registry — `<root>/runs/<taskId>/run.json` = the RunRow, replaced WHOLE on every status change via
13
+ * `atomicWriteFile` (tmp→fsync→rename→fsync-dir, the 5-step atomic replace).
14
+ * 2. single-active claim — `<root>/runs/active/<sanitizePathComponent(sessionId)>` carrying the active taskId, created
15
+ * with `writeThenLink` (atomic create; EEXIST ⇒ the session already has an active run → the
16
+ * createRun loses the claim, returns `{ok:false, activeTaskId}`). The file version of task_active's
17
+ * unique key.
18
+ * 3. per-run event log — `<root>/runs/<taskId>/events.jsonl` = an `AppendLog` (one line per event, fsync:false — events
19
+ * are high-frequency, the torn-tail-drop on replay covers a crash tail; only the run.json status
20
+ * commit + the active-claim create use fsync). (taskId,seq) is deduped on replay.
21
+ *
22
+ * An in-memory INDEX (the same Maps MemoryRunStore holds) is hydrated on construction by scanning each runs/<taskId>/
23
+ * run.json + the active claim files — so listRuns/listSessions/getActiveTaskId are O(in-mem) with byte-identical keyset sort to the SQL/Memory
24
+ * twins. Every write touches BOTH the disk and the index on one path. Single-process (the FileStorageBackend boot lock
25
+ * guarantees one writer per data dir), so there is no per-op lock. The checkpoint-coupled suspended-run reapers run
26
+ * off an injected {@link RunStoreCheckpointProbe} ([868]; wired by the local backend where checkpoint + run store
27
+ * share one root) and degrade to honest no-ops when the probe is absent — exactly like MemoryRunStore.
28
+ */
1
29
  import { type UsageRow } from "../usage-analytics.js";
2
30
  import type { TaskResult, TaskStatus } from "@sema-agent/core";
3
31
  import type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
@@ -6,10 +34,16 @@ export declare class FileRunStore {
6
34
  private readonly runsDir;
7
35
  private readonly activeDir;
8
36
  private readonly tmpDir;
37
+ /** taskId → RunRow (the run registry index). */
9
38
  private readonly runs;
39
+ /** sessionId → taskId — the single-active-run claim (the task_active table; index of active/*). */
10
40
  private readonly active;
41
+ /** taskId → events (kept seq-sorted on read), the event-log index. */
11
42
  private readonly events;
43
+ /** taskId → the open append log (one O_APPEND fd per run, lazily opened, held for lifetime). */
12
44
  private readonly eventLogs;
45
+ /** [868] the checkpoint-table stand-in (see memory-run-store.ts {@link RunStoreCheckpointProbe} — the full
46
+ * rationale lives on the interface); absent ⇒ the suspended-run reapers stay honest NO-OPs. */
13
47
  private checkpointProbe?;
14
48
  constructor(root: string);
15
49
  private runDir;
@@ -19,9 +53,16 @@ export declare class FileRunStore {
19
53
  private toRow;
20
54
  private toDisk;
21
55
  private toRecord;
56
+ /** Replay the on-disk registry/claims/event-logs into the in-memory index on boot. Each plane is torn-tail-/
57
+ * partial-write-safe (run.json via atomicWriteFile can never be half-written; the active file via writeThenLink is
58
+ * all-or-nothing; events.jsonl via readJsonlRecords drops a crash tail). A corrupt run.json (failed parse) is
59
+ * skipped — that run is lost, not a poison that fails the boot. */
22
60
  private hydrate;
61
+ /** Commit a RunRow to disk (whole-file atomic replace) AND the index — the single write path for every status change. */
23
62
  private persist;
24
63
  private eventLogFor;
64
+ /** Create a run iff the session has no active run (the task_active unique-key claim). The claim file is created with
65
+ * writeThenLink (atomic): if it already exists (EEXIST) the session has an active run → lose, return the holder. */
25
66
  createRun(taskId: string, sessionId: string, owner: string | null, instanceId: string, meta?: {
26
67
  jobId?: string | null;
27
68
  source?: string | null;
@@ -41,12 +82,18 @@ export declare class FileRunStore {
41
82
  maxSeq(taskId: string): Promise<number>;
42
83
  retainedFrom(taskId: string): Promise<number>;
43
84
  getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
85
+ /** [888] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
86
+ * [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
44
87
  setTerminal(taskId: string, status: TaskStatus, result: TaskResult | null, error: string | null): Promise<void>;
88
+ /** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard). */
45
89
  setSuspended(taskId: string): Promise<void>;
90
+ /** design/80 D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review. */
46
91
  setNeedsReview(taskId: string): Promise<void>;
92
+ /** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags. */
47
93
  markResuming(taskId: string): Promise<boolean>;
48
94
  getActiveTaskId(sessionId: string): Promise<string | undefined>;
49
95
  getRun(taskId: string): Promise<RunRecord | undefined>;
96
+ /** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
50
97
  listRuns(opts: {
51
98
  status?: string;
52
99
  jobId?: string;
@@ -58,6 +105,7 @@ export declare class FileRunStore {
58
105
  };
59
106
  limit: number;
60
107
  }): Promise<RunRecord[]>;
108
+ /** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
61
109
  listSessions(opts: {
62
110
  owner?: string;
63
111
  cursor?: {
@@ -67,11 +115,13 @@ export declare class FileRunStore {
67
115
  limit: number;
68
116
  q?: string;
69
117
  }): Promise<SessionSummary[]>;
118
+ /** E21 (§0.5 delete) — purge the runs ledger for one session, owner-guarded; aborts (returns {active}) if a run is live. */
70
119
  deleteBySession(sessionId: string, owner: string | null): Promise<{
71
120
  removed: number;
72
121
  } | {
73
122
  active: string;
74
123
  }>;
124
+ /** 用量扫窗——in-memory 腿:遍历窗内行,投影同 usage-analytics 纯函数(SQL 孪生 parity)。 */
75
125
  usageScan(fromMs: number, toMs: number, opts?: {
76
126
  owner?: string;
77
127
  limit?: number;
@@ -84,16 +134,56 @@ export declare class FileRunStore {
84
134
  status: string;
85
135
  count: number;
86
136
  }>>;
137
+ /** Fail stale `running` rows (crashed-run backstop) + release task_active for any now-terminal row. */
138
+ /** Release any claim whose row is now terminal; KEEP suspended/needs_review (resume re-finds them). One place for
139
+ * the claim-release invariant, shared by reapRunning + reclaimOrphanedAtBoot. */
87
140
  private sweepClaims;
141
+ /** Flip every `running` row matching `pred` → failed (with `reason`), then sweep terminal claims. */
88
142
  private reapRunning;
89
143
  reapStale(olderThanMs: number): Promise<number>;
144
+ /** TOC integration ask③ (INTEGRATION-DESIGN §11): a host-lane engine restart leaves the PREVIOUS process's
145
+ * `running` rows orphaned — their in-process driver (runInBackground, in the dead process's memory) is gone, but the
146
+ * `active/<sessionId>` claim persists on disk and EEXIST-blocks a re-submit (the shell reads the session as stuck).
147
+ * The boot flock (store-backend `LOCK`) already proves no concurrent live owner reached here: a still-alive old
148
+ * engine holds it so the new one FAILS FAST; a dead one had it kernel-released. So a `running` row here is orphaned
149
+ * → flip failed + release its claim UNCONDITIONALLY (no TTL wait, unlike reapStale's steady-state poll).
150
+ * 🔴 EXCEPT a resume-in-progress row: `markResuming` flips a suspended run (suspended→running) while its pending
151
+ * checkpoint still exists, and the resume picker will re-drive it — reclaiming it would STRAND a resumable session
152
+ * as failed. `isResumable(sessionId)` (wired by main.ts boot, which holds the checkpointStore) excludes those; with
153
+ * no callback every running row is treated as orphaned (conservative default when no checkpoint plane is wired).
154
+ * HOST/FILE LANE ONLY — the cross-replica tidb/pg store uses heartbeat→reapStale (a running row there may be alive
155
+ * on another replica; an unconditional reclaim would kill a live fleet run). */
90
156
  reclaimOrphanedAtBoot(isResumable?: (sessionId: string) => Promise<boolean>): Promise<number>;
157
+ /** [868] wire the checkpoint probe (LocalBackend.checkpoint(), where checkpoint + run store share one root). */
91
158
  setCheckpointProbe(probe: RunStoreCheckpointProbe): void;
159
+ /** [868] shared tail of both suspended-run reapers: flip one parked row → failed('approval.expired') and
160
+ * PERSIST it — the file twin of the SQL UPDATE (`error`/`error_code` set directly, no result blob). */
92
161
  private failExpiredPark;
162
+ /**
163
+ * [868] Durable F4 expiry — the file twin of TiDBRunStore.reapSuspended, driven by the injected checkpoint
164
+ * probe instead of a SQL JOIN (formerly a NO-OP — one of the five closed doors in the "suspended run locks the
165
+ * session forever" incident). Semantics verbatim from the SQL twin: suspended ∧ updatedAt < cutoff ∧ NOT
166
+ * probe.hasPending(session) → failed 'approval expired before decision' / errorCode 'approval.expired' +
167
+ * release the task_active claim. The NOT-pending clause is the design/80 D-D adversarial-blocker guard
168
+ * (tidb-run-store.ts:577-583): a row whose checkpoint is STILL pending belongs to the checkpoint-state sweeps —
169
+ * time-reaping it would orphan a model leg on a now-unlocked session. Probe absent ⇒ honest NO-OP.
170
+ * MemoryRunStore is the parity oracle (byte-identical predicate; only the persist differs).
171
+ */
93
172
  reapSuspended(olderThanMs: number): Promise<number>;
173
+ /**
174
+ * [868] design/80 §3 inv#3 crash-safe backstop — the file twin of TiDBRunStore.failSuspendedWithExpiredCheckpoint
175
+ * (checkpoint-STATE-driven, not time-driven; scheduled UNCONDITIONALLY by the main.ts reaper, no
176
+ * APPROVAL_TIMEOUT_SEC gate). Semantics verbatim from the SQL twin: (suspended ∨ needs_review) ∧
177
+ * probe.hasExpired(session) ∧ NOT probe.hasPending(session) → same fail + claim release (wq64gmm5e: an
178
+ * abandoned needs_review park leaks exactly like a suspended one; a re-suspended session that minted a NEW
179
+ * pending gate is excluded by the NOT-pending clause).
180
+ */
94
181
  failSuspendedWithExpiredCheckpoint(): Promise<number>;
182
+ /** Release a session's single-active claim: unlink the claim file + drop the index entry (idempotent). */
95
183
  private releaseClaim;
184
+ /** Drop a run entirely (registry + event log + open fd + on-disk dir) — used by deleteBySession. */
96
185
  private dropRun;
186
+ /** Release every open event-log fd (called by LocalBackend.close so a graceful restart can re-open the data dir). */
97
187
  dispose(): void;
98
188
  }
99
189
  //# sourceMappingURL=file-run-store.d.ts.map
@@ -1,26 +1,64 @@
1
+ /**
2
+ * File-backed async run registry + replayable event log — the DURABLE local twin of {@link MemoryRunStore} (and the
3
+ * file-backed sibling of {@link TiDBRunStore}/{@link PgRunStore}). P0.5 variant-1 (clay 2026-06-26): the file-backed
4
+ * follow-on the Memory twin's header promised — a locally-run HTTP service now keeps its runs ledger + trace event log
5
+ * across a process restart, not just in memory. Behaviour is byte-identical to MemoryRunStore (the shared parity oracle
6
+ * — same single-active claim, the suspend/needs_review parks that KEEP the claim, the markResuming CAS that resets
7
+ * cancel/preempt, the owner null-safe guard, the keyset list order, the (taskId,seq) event dedup, and the structuredClone
8
+ * write/read isolation); the only difference is durability.
9
+ *
10
+ * THREE on-disk data planes (all under `<root>/runs/`), each using core's crash-safe fs primitives — never hand-rolled
11
+ * fs (zero drift, no zero-length/torn corruption):
12
+ * 1. run registry — `<root>/runs/<taskId>/run.json` = the RunRow, replaced WHOLE on every status change via
13
+ * `atomicWriteFile` (tmp→fsync→rename→fsync-dir, the 5-step atomic replace).
14
+ * 2. single-active claim — `<root>/runs/active/<sanitizePathComponent(sessionId)>` carrying the active taskId, created
15
+ * with `writeThenLink` (atomic create; EEXIST ⇒ the session already has an active run → the
16
+ * createRun loses the claim, returns `{ok:false, activeTaskId}`). The file version of task_active's
17
+ * unique key.
18
+ * 3. per-run event log — `<root>/runs/<taskId>/events.jsonl` = an `AppendLog` (one line per event, fsync:false — events
19
+ * are high-frequency, the torn-tail-drop on replay covers a crash tail; only the run.json status
20
+ * commit + the active-claim create use fsync). (taskId,seq) is deduped on replay.
21
+ *
22
+ * An in-memory INDEX (the same Maps MemoryRunStore holds) is hydrated on construction by scanning each runs/<taskId>/
23
+ * run.json + the active claim files — so listRuns/listSessions/getActiveTaskId are O(in-mem) with byte-identical keyset sort to the SQL/Memory
24
+ * twins. Every write touches BOTH the disk and the index on one path. Single-process (the FileStorageBackend boot lock
25
+ * guarantees one writer per data dir), so there is no per-op lock. The checkpoint-coupled suspended-run reapers run
26
+ * off an injected {@link RunStoreCheckpointProbe} ([868]; wired by the local backend where checkpoint + run store
27
+ * share one root) and degrade to honest no-ops when the probe is absent — exactly like MemoryRunStore.
28
+ */
1
29
  import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
2
30
  import { join } from "node:path";
3
31
  import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
4
32
  import { AppendLog, atomicWriteFile, ensureDir, readJsonlRecords, sanitizePathComponent, writeThenLink } from "@sema-agent/core";
33
+ /** Null-safe owner equality — the JS twin of the SQL `owner <=> ?` guard (a legacy null-owner run matches owner=null). */
5
34
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
6
35
  export class FileRunStore {
7
36
  runsDir;
8
37
  activeDir;
9
38
  tmpDir;
39
+ /** taskId → RunRow (the run registry index). */
10
40
  runs = new Map();
41
+ /** sessionId → taskId — the single-active-run claim (the task_active table; index of active/*). */
11
42
  active = new Map();
43
+ /** taskId → events (kept seq-sorted on read), the event-log index. */
12
44
  events = new Map();
45
+ /** taskId → the open append log (one O_APPEND fd per run, lazily opened, held for lifetime). */
13
46
  eventLogs = new Map();
47
+ /** [868] the checkpoint-table stand-in (see memory-run-store.ts {@link RunStoreCheckpointProbe} — the full
48
+ * rationale lives on the interface); absent ⇒ the suspended-run reapers stay honest NO-OPs. */
14
49
  checkpointProbe;
15
50
  constructor(root) {
16
51
  this.runsDir = join(root, "runs");
17
52
  this.activeDir = join(root, "runs", "active");
18
- this.tmpDir = join(root, "tmp");
53
+ this.tmpDir = join(root, "tmp"); // atomicWriteFile temp dir (same FS as runs → rename is atomic)
19
54
  ensureDir(this.runsDir);
20
55
  ensureDir(this.activeDir);
21
56
  ensureDir(this.tmpDir);
22
57
  this.hydrate();
58
+ // NB: orphan reclaim is NOT auto-run here — main.ts boot drives it AFTER the checkpointStore is wired, so a
59
+ // resume-in-progress (markResuming) row with a pending checkpoint is excluded (see reclaimOrphanedAtBoot).
23
60
  }
61
+ // ── disk paths ──
24
62
  runDir(taskId) {
25
63
  return join(this.runsDir, sanitizePathComponent(taskId));
26
64
  }
@@ -56,6 +94,10 @@ export class FileRunStore {
56
94
  updatedAt: r.updatedAt.toISOString(),
57
95
  };
58
96
  }
97
+ /** Replay the on-disk registry/claims/event-logs into the in-memory index on boot. Each plane is torn-tail-/
98
+ * partial-write-safe (run.json via atomicWriteFile can never be half-written; the active file via writeThenLink is
99
+ * all-or-nothing; events.jsonl via readJsonlRecords drops a crash tail). A corrupt run.json (failed parse) is
100
+ * skipped — that run is lost, not a poison that fails the boot. */
59
101
  hydrate() {
60
102
  let entries;
61
103
  try {
@@ -66,19 +108,24 @@ export class FileRunStore {
66
108
  }
67
109
  for (const name of entries) {
68
110
  if (name === "active")
69
- continue;
111
+ continue; // the claims dir, scanned separately
70
112
  const dir = join(this.runsDir, name);
71
113
  const jsonPath = join(dir, "run.json");
72
114
  if (!existsSync(jsonPath))
73
115
  continue;
74
- const rows = readJsonlRecords(jsonPath);
75
- const disk = rows[rows.length - 1];
116
+ const rows = readJsonlRecords(jsonPath); // run.json is a single atomic record; tolerate a torn/empty file
117
+ const disk = rows[rows.length - 1]; // last complete record (atomicWriteFile writes the whole file each time)
76
118
  if (!disk || typeof disk.taskId !== "string")
77
119
  continue;
120
+ // R8 修:atomicWriteFile 防的是半写,防不了 schema 半态(旧版残留/手工改动的 run.json 缺时间戳字段)。
121
+ // 不挡在这里的话,坏行会在**读路径**上毒发:`toRecord` 对 Invalid Date 调 toISOString ⇒ RangeError ⇒
122
+ // 一条坏文件打死整个 listRuns(GET /v1/runs 全 500)。按顶注自述的隔离承诺同款处置:跳过该行,
123
+ // 一条坏行最多只影响它自己。
78
124
  if (typeof disk.createdAt !== "string" || Number.isNaN(Date.parse(disk.createdAt)) ||
79
125
  typeof disk.updatedAt !== "string" || Number.isNaN(Date.parse(disk.updatedAt)))
80
126
  continue;
81
127
  this.runs.set(disk.taskId, this.toRow(disk));
128
+ // event log
82
129
  const eventsPath = join(dir, "events.jsonl");
83
130
  const recs = readJsonlRecords(eventsPath);
84
131
  if (recs.length) {
@@ -86,13 +133,14 @@ export class FileRunStore {
86
133
  const seen = new Set();
87
134
  for (const e of recs) {
88
135
  if (!e || typeof e.seq !== "number" || seen.has(e.seq))
89
- continue;
136
+ continue; // (taskId,seq) dedup-on-replay (last write wins by skip-first? no: first wins, matching append-ignore-dup)
90
137
  seen.add(e.seq);
91
138
  list.push({ seq: e.seq, type: String(e.type), data: e.data ?? null, ts: String(e.ts) });
92
139
  }
93
140
  this.events.set(disk.taskId, list);
94
141
  }
95
142
  }
143
+ // active claims
96
144
  let claims;
97
145
  try {
98
146
  claims = readdirSync(this.activeDir);
@@ -108,6 +156,7 @@ export class FileRunStore {
108
156
  }
109
157
  }
110
158
  }
159
+ /** Commit a RunRow to disk (whole-file atomic replace) AND the index — the single write path for every status change. */
111
160
  persist(r) {
112
161
  mkdirSync(this.runDir(r.taskId), { recursive: true });
113
162
  atomicWriteFile(this.tmpDir, this.runJsonPath(r.taskId), `${JSON.stringify(this.toDisk(r))}\n`);
@@ -122,12 +171,18 @@ export class FileRunStore {
122
171
  }
123
172
  return log;
124
173
  }
174
+ /** Create a run iff the session has no active run (the task_active unique-key claim). The claim file is created with
175
+ * writeThenLink (atomic): if it already exists (EEXIST) the session has an active run → lose, return the holder. */
125
176
  async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
177
+ // Atomic single-active claim: writeThenLink throws EEXIST if the claim already exists (the file twin of the
178
+ // task_active unique key). The index is the fast-path read; the file is the authority on a concurrent create.
126
179
  try {
127
180
  writeThenLink(this.activePath(sessionId), `${JSON.stringify({ sessionId, taskId })}\n`);
128
181
  }
129
182
  catch (e) {
130
183
  if (e.code === "EEXIST") {
184
+ // Recover the current holder from the claim file (the index may lag a concurrent winner, but in this
185
+ // single-process model the index is authoritative; read disk for robustness anyway).
131
186
  const recs = readJsonlRecords(this.activePath(sessionId));
132
187
  const holder = recs[recs.length - 1]?.taskId ?? this.active.get(sessionId) ?? "";
133
188
  return { ok: false, activeTaskId: holder };
@@ -156,11 +211,15 @@ export class FileRunStore {
156
211
  });
157
212
  }
158
213
  catch (e) {
214
+ // R9 修:claim 与 run 行是一体的 —— 行没落下(如超长 taskId 的 ENAMETOOLONG)就把刚落的 claim 撤掉再
215
+ // rethrow。否则 session 被一个 getRun=undefined 的幽灵 taskId 占用:起不了新 run、也取消不了"活跃 run",
216
+ // 直到进程重启 sweepClaims 才解(SQL 孪生天然免疫:createRun 整体在一个事务里)。
159
217
  this.active.delete(sessionId);
160
218
  try {
161
219
  rmSync(this.activePath(sessionId), { force: true });
162
220
  }
163
221
  catch {
222
+ /* best-effort:claim 文件撤不掉时至少 index 已撤;重启 sweep 兜底 */
164
223
  }
165
224
  throw e;
166
225
  }
@@ -199,8 +258,14 @@ export class FileRunStore {
199
258
  }
200
259
  async appendEvent(taskId, seq, type, data) {
201
260
  const list = this.events.get(taskId) ?? [];
261
+ // Mirror the (task_id, seq) PK: a duplicate seq is a caller bug (callers use ++seq); ignore a re-append of the
262
+ // same seq rather than corrupt the log (the SQL twin throws ER_DUP_ENTRY — callers never hit it).
202
263
  if (list.some((e) => e.seq === seq))
203
264
  return;
265
+ // Deep-clone `data` on WRITE so the stored event is isolated from later caller mutation — the SQL twin
266
+ // JSON.stringify's it into the column, giving the same write-isolation (parity oracle). Persist BEFORE indexing
267
+ // so a crash after the append still replays the event (fsync:false — events are high-frequency, the torn-tail
268
+ // drop on replay covers a crash tail; only run.json status + the active claim need fsync).
204
269
  const clone = data == null ? null : structuredClone(data);
205
270
  const ts = new Date().toISOString();
206
271
  this.eventLogFor(taskId).append({ seq, type, data: clone, ts }, false);
@@ -217,11 +282,15 @@ export class FileRunStore {
217
282
  }
218
283
  async getEvents(taskId, afterSeq) {
219
284
  const list = this.events.get(taskId) ?? [];
285
+ // Deep-clone `data` on READ so each call yields a fresh, mutation-isolated object — the SQL twin re-parses the
286
+ // JSON column per read (parity oracle); a shallow `{...e}` would alias `data` into the stored list (review footgun).
220
287
  return list
221
288
  .filter((e) => e.seq > afterSeq)
222
289
  .sort((a, b) => a.seq - b.seq)
223
290
  .map((e) => ({ ...e, data: e.data == null ? null : structuredClone(e.data) }));
224
291
  }
292
+ /** [888] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
293
+ * [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
225
294
  async setTerminal(taskId, status, result, error) {
226
295
  const r = this.runs.get(taskId);
227
296
  if (!r)
@@ -234,9 +303,11 @@ export class FileRunStore {
234
303
  r.updatedAt = new Date();
235
304
  this.persist(r);
236
305
  }
306
+ // Release the single-active-run claim (DELETE FROM task_active WHERE task_id = ?) — unlink the claim file + index.
237
307
  if (this.active.get(r.sessionId) === taskId)
238
308
  this.releaseClaim(r.sessionId);
239
309
  }
310
+ /** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard). */
240
311
  async setSuspended(taskId) {
241
312
  const r = this.runs.get(taskId);
242
313
  if (r && (r.status === "running" || r.status === "suspended")) {
@@ -245,6 +316,7 @@ export class FileRunStore {
245
316
  this.persist(r);
246
317
  }
247
318
  }
319
+ /** design/80 D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review. */
248
320
  async setNeedsReview(taskId) {
249
321
  const r = this.runs.get(taskId);
250
322
  if (r && (r.status === "running" || r.status === "needs_review")) {
@@ -253,6 +325,7 @@ export class FileRunStore {
253
325
  this.persist(r);
254
326
  }
255
327
  }
328
+ /** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags. */
256
329
  async markResuming(taskId) {
257
330
  const r = this.runs.get(taskId);
258
331
  if (!r || (r.status !== "suspended" && r.status !== "needs_review"))
@@ -271,7 +344,11 @@ export class FileRunStore {
271
344
  const r = this.runs.get(taskId);
272
345
  return r ? this.toRecord(r) : undefined;
273
346
  }
347
+ /** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
274
348
  async listRuns(opts) {
349
+ // 夜测 N1(2026-07-09,LOW):a non-date cursor.createdAt made the four backends diverge (PG threw a raw
350
+ // DB error; the other three returned an empty page). HTTP's decodeCursor pre-validates, so this is
351
+ // defence-in-depth for direct store callers — normalize ALL backends to the empty page.
275
352
  if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
276
353
  return [];
277
354
  const cursorAt = opts.cursor ? Date.parse(opts.cursor.createdAt) : undefined;
@@ -279,31 +356,35 @@ export class FileRunStore {
279
356
  .filter((r) => (opts.status ? r.status === opts.status : true))
280
357
  .filter((r) => (opts.jobId ? r.jobId === opts.jobId : true))
281
358
  .filter((r) => (opts.source ? r.source === opts.source : true))
282
- .filter((r) => (opts.owner ? r.owner === opts.owner : true))
359
+ .filter((r) => (opts.owner ? r.owner === opts.owner : true)) // exact (SQL `owner = ?`): a set owner excludes null-owner rows
283
360
  .filter((r) => {
284
361
  if (cursorAt === undefined)
285
362
  return true;
286
363
  const t = r.createdAt.getTime();
287
- return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId);
364
+ return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId); // keyset: strictly older, ties by taskId
288
365
  })
289
366
  .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
290
367
  return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
291
368
  }
369
+ /** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
292
370
  async listSessions(opts) {
293
371
  const bySession = new Map();
294
372
  for (const r of this.runs.values()) {
295
373
  if (opts.owner && r.owner !== opts.owner)
296
- continue;
374
+ continue; // exact owner filter (SQL `owner = ?`)
297
375
  const arr = bySession.get(r.sessionId) ?? [];
298
376
  arr.push(r);
299
377
  bySession.set(r.sessionId, arr);
300
378
  }
301
379
  const cursorAt = opts.cursor ? Date.parse(opts.cursor.lastActivityAt) : undefined;
302
- const qlc = opts.q?.toLowerCase();
380
+ const qlc = opts.q?.toLowerCase(); // `?q=`: any-run preview match — signature parity with the memory twin
381
+ // (this lister is UNREACHABLE via GET /v1/sessions today [local backend's sessionStorage lister wins], but a
382
+ // carried lister must not silently ignore a filter key — the workflow-audit hygiene item, 2026-07-13).
303
383
  const summaries = [];
304
384
  for (const [sessionId, arr] of bySession) {
305
385
  if (qlc && !arr.some((r) => r.objectivePreview?.toLowerCase().includes(qlc)))
306
386
  continue;
387
+ // rn=1 = latest run (createdAt DESC, taskId DESC) → preview/status/owner.
307
388
  const sorted = [...arr].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
308
389
  const latest = sorted[0];
309
390
  const times = arr.map((r) => r.createdAt.getTime());
@@ -317,8 +398,8 @@ export class FileRunStore {
317
398
  runCount: arr.length,
318
399
  objectivePreview: latest.objectivePreview,
319
400
  lastStatus: latest.status,
320
- lastRunId: latest.taskId,
321
- title: null,
401
+ lastRunId: latest.taskId, // K-5c: latest run id = the live-tail re-attach anchor
402
+ title: null, // honest degrade (no session_meta on the file runs-ledger twin)
322
403
  });
323
404
  }
324
405
  return summaries
@@ -331,6 +412,7 @@ export class FileRunStore {
331
412
  .sort((a, b) => Date.parse(b.lastActivityAt) - Date.parse(a.lastActivityAt) || (a.sessionId < b.sessionId ? 1 : a.sessionId > b.sessionId ? -1 : 0))
332
413
  .slice(0, opts.limit);
333
414
  }
415
+ /** E21 (§0.5 delete) — purge the runs ledger for one session, owner-guarded; aborts (returns {active}) if a run is live. */
334
416
  async deleteBySession(sessionId, owner) {
335
417
  const claimed = this.active.get(sessionId);
336
418
  if (claimed !== undefined)
@@ -344,6 +426,7 @@ export class FileRunStore {
344
426
  }
345
427
  return { removed };
346
428
  }
429
+ /** 用量扫窗——in-memory 腿:遍历窗内行,投影同 usage-analytics 纯函数(SQL 孪生 parity)。 */
347
430
  async usageScan(fromMs, toMs, opts) {
348
431
  const limit = Math.min(opts?.limit ?? USAGE_SCAN_LIMIT, USAGE_SCAN_LIMIT);
349
432
  const all = [];
@@ -364,13 +447,16 @@ export class FileRunStore {
364
447
  for (const r of this.runs.values()) {
365
448
  if (r.createdAt.getTime() < sinceMs)
366
449
  continue;
367
- const key = JSON.stringify([r.source, r.status]);
450
+ const key = JSON.stringify([r.source, r.status]); // collision-proof + keeps NULL distinct from "" (SQL GROUP BY parity)
368
451
  const cur = counts.get(key) ?? { source: r.source, status: r.status, count: 0 };
369
452
  cur.count += 1;
370
453
  counts.set(key, cur);
371
454
  }
372
455
  return [...counts.values()];
373
456
  }
457
+ /** Fail stale `running` rows (crashed-run backstop) + release task_active for any now-terminal row. */
458
+ /** Release any claim whose row is now terminal; KEEP suspended/needs_review (resume re-finds them). One place for
459
+ * the claim-release invariant, shared by reapRunning + reclaimOrphanedAtBoot. */
374
460
  sweepClaims() {
375
461
  for (const [sessionId, taskId] of [...this.active]) {
376
462
  const r = this.runs.get(taskId);
@@ -378,6 +464,7 @@ export class FileRunStore {
378
464
  this.releaseClaim(sessionId);
379
465
  }
380
466
  }
467
+ /** Flip every `running` row matching `pred` → failed (with `reason`), then sweep terminal claims. */
381
468
  reapRunning(pred, reason) {
382
469
  let reaped = 0;
383
470
  for (const r of this.runs.values()) {
@@ -396,6 +483,18 @@ export class FileRunStore {
396
483
  const cutoff = Date.now() - olderThanMs;
397
484
  return this.reapRunning((r) => r.updatedAt.getTime() < cutoff, "run stalled (instance lost?)");
398
485
  }
486
+ /** TOC integration ask③ (INTEGRATION-DESIGN §11): a host-lane engine restart leaves the PREVIOUS process's
487
+ * `running` rows orphaned — their in-process driver (runInBackground, in the dead process's memory) is gone, but the
488
+ * `active/<sessionId>` claim persists on disk and EEXIST-blocks a re-submit (the shell reads the session as stuck).
489
+ * The boot flock (store-backend `LOCK`) already proves no concurrent live owner reached here: a still-alive old
490
+ * engine holds it so the new one FAILS FAST; a dead one had it kernel-released. So a `running` row here is orphaned
491
+ * → flip failed + release its claim UNCONDITIONALLY (no TTL wait, unlike reapStale's steady-state poll).
492
+ * 🔴 EXCEPT a resume-in-progress row: `markResuming` flips a suspended run (suspended→running) while its pending
493
+ * checkpoint still exists, and the resume picker will re-drive it — reclaiming it would STRAND a resumable session
494
+ * as failed. `isResumable(sessionId)` (wired by main.ts boot, which holds the checkpointStore) excludes those; with
495
+ * no callback every running row is treated as orphaned (conservative default when no checkpoint plane is wired).
496
+ * HOST/FILE LANE ONLY — the cross-replica tidb/pg store uses heartbeat→reapStale (a running row there may be alive
497
+ * on another replica; an unconditional reclaim would kill a live fleet run). */
399
498
  async reclaimOrphanedAtBoot(isResumable) {
400
499
  const orphans = [];
401
500
  for (const r of this.runs.values()) {
@@ -411,9 +510,12 @@ export class FileRunStore {
411
510
  this.sweepClaims();
412
511
  return orphans.length;
413
512
  }
513
+ /** [868] wire the checkpoint probe (LocalBackend.checkpoint(), where checkpoint + run store share one root). */
414
514
  setCheckpointProbe(probe) {
415
515
  this.checkpointProbe = probe;
416
516
  }
517
+ /** [868] shared tail of both suspended-run reapers: flip one parked row → failed('approval.expired') and
518
+ * PERSIST it — the file twin of the SQL UPDATE (`error`/`error_code` set directly, no result blob). */
417
519
  failExpiredPark(r) {
418
520
  r.status = "failed";
419
521
  r.error = "approval expired before decision";
@@ -421,6 +523,16 @@ export class FileRunStore {
421
523
  r.updatedAt = new Date();
422
524
  this.persist(r);
423
525
  }
526
+ /**
527
+ * [868] Durable F4 expiry — the file twin of TiDBRunStore.reapSuspended, driven by the injected checkpoint
528
+ * probe instead of a SQL JOIN (formerly a NO-OP — one of the five closed doors in the "suspended run locks the
529
+ * session forever" incident). Semantics verbatim from the SQL twin: suspended ∧ updatedAt < cutoff ∧ NOT
530
+ * probe.hasPending(session) → failed 'approval expired before decision' / errorCode 'approval.expired' +
531
+ * release the task_active claim. The NOT-pending clause is the design/80 D-D adversarial-blocker guard
532
+ * (tidb-run-store.ts:577-583): a row whose checkpoint is STILL pending belongs to the checkpoint-state sweeps —
533
+ * time-reaping it would orphan a model leg on a now-unlocked session. Probe absent ⇒ honest NO-OP.
534
+ * MemoryRunStore is the parity oracle (byte-identical predicate; only the persist differs).
535
+ */
424
536
  async reapSuspended(olderThanMs) {
425
537
  const probe = this.checkpointProbe;
426
538
  if (!probe)
@@ -432,7 +544,10 @@ export class FileRunStore {
432
544
  continue;
433
545
  const snap = r.updatedAt.getTime();
434
546
  if (await probe.hasPending(r.sessionId))
435
- continue;
547
+ continue; // D-D guard: a still-pending checkpoint owns this row
548
+ // codex H2:probe 的 await 让出事件循环——markResuming(approve 恢复)/cancel/新一轮 park 可能已改行。
549
+ // 复核与改写之间无 await(Node 单线程 ⇒ 原子);状态或 updatedAt 变了就放手,绝不覆盖一条已被别腿
550
+ // 认领的行(SQL 孪生靠单条条件 UPDATE 天然原子,这里的 snap 复核就是它的本地等价)。
436
551
  if (r.status !== "suspended" || r.updatedAt.getTime() !== snap)
437
552
  continue;
438
553
  this.failExpiredPark(r);
@@ -441,6 +556,14 @@ export class FileRunStore {
441
556
  this.sweepClaims();
442
557
  return reaped;
443
558
  }
559
+ /**
560
+ * [868] design/80 §3 inv#3 crash-safe backstop — the file twin of TiDBRunStore.failSuspendedWithExpiredCheckpoint
561
+ * (checkpoint-STATE-driven, not time-driven; scheduled UNCONDITIONALLY by the main.ts reaper, no
562
+ * APPROVAL_TIMEOUT_SEC gate). Semantics verbatim from the SQL twin: (suspended ∨ needs_review) ∧
563
+ * probe.hasExpired(session) ∧ NOT probe.hasPending(session) → same fail + claim release (wq64gmm5e: an
564
+ * abandoned needs_review park leaks exactly like a suspended one; a re-suspended session that minted a NEW
565
+ * pending gate is excluded by the NOT-pending clause).
566
+ */
444
567
  async failSuspendedWithExpiredCheckpoint() {
445
568
  const probe = this.checkpointProbe;
446
569
  if (!probe)
@@ -455,6 +578,7 @@ export class FileRunStore {
455
578
  continue;
456
579
  if (await probe.hasPending(r.sessionId))
457
580
  continue;
581
+ // codex H2 同款 snap 复核(两个 await 窗都盖住):行被别腿改过就放手。
458
582
  if (r.status !== parkedAs || r.updatedAt.getTime() !== snap)
459
583
  continue;
460
584
  this.failExpiredPark(r);
@@ -463,6 +587,8 @@ export class FileRunStore {
463
587
  this.sweepClaims();
464
588
  return reaped;
465
589
  }
590
+ // ── claim / run file lifecycle helpers ──
591
+ /** Release a session's single-active claim: unlink the claim file + drop the index entry (idempotent). */
466
592
  releaseClaim(sessionId) {
467
593
  this.active.delete(sessionId);
468
594
  const path = this.activePath(sessionId);
@@ -471,9 +597,11 @@ export class FileRunStore {
471
597
  rmSync(path, { force: true });
472
598
  }
473
599
  catch {
600
+ /* best-effort — a stale claim file is harmless (the index entry is already gone) */
474
601
  }
475
602
  }
476
603
  }
604
+ /** Drop a run entirely (registry + event log + open fd + on-disk dir) — used by deleteBySession. */
477
605
  dropRun(taskId) {
478
606
  const log = this.eventLogs.get(taskId);
479
607
  if (log) {
@@ -481,6 +609,7 @@ export class FileRunStore {
481
609
  log.close();
482
610
  }
483
611
  catch {
612
+ /* best-effort */
484
613
  }
485
614
  this.eventLogs.delete(taskId);
486
615
  }
@@ -492,15 +621,18 @@ export class FileRunStore {
492
621
  rmSync(dir, { recursive: true, force: true });
493
622
  }
494
623
  catch {
624
+ /* best-effort */
495
625
  }
496
626
  }
497
627
  }
628
+ /** Release every open event-log fd (called by LocalBackend.close so a graceful restart can re-open the data dir). */
498
629
  dispose() {
499
630
  for (const log of this.eventLogs.values()) {
500
631
  try {
501
632
  log.close();
502
633
  }
503
634
  catch {
635
+ /* best-effort */
504
636
  }
505
637
  }
506
638
  this.eventLogs.clear();