@sema-agent/server 1.322.0 → 2.0.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 (539) 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-reclaim.d.ts +19 -0
  46. package/dist/boot-reclaim.js +20 -0
  47. package/dist/brain.d.ts +25 -0
  48. package/dist/brain.js +61 -1
  49. package/dist/budget.d.ts +66 -0
  50. package/dist/budget.js +128 -4
  51. package/dist/capabilities/builtin-tools.d.ts +3 -0
  52. package/dist/capabilities/builtin-tools.js +3 -0
  53. package/dist/capabilities/center-plugins.d.ts +17 -1
  54. package/dist/capabilities/center-plugins.js +45 -4
  55. package/dist/capabilities/center-prompts.d.ts +46 -0
  56. package/dist/capabilities/center-prompts.js +57 -3
  57. package/dist/capabilities/code-review-council.d.ts +28 -0
  58. package/dist/capabilities/code-review-council.js +40 -7
  59. package/dist/capabilities/collab-wire.d.ts +2 -0
  60. package/dist/capabilities/collab-workflows.d.ts +7 -0
  61. package/dist/capabilities/collab-workflows.js +44 -1
  62. package/dist/capabilities/oa-tools.d.ts +6 -0
  63. package/dist/capabilities/oa-tools.js +10 -0
  64. package/dist/capabilities/prompt.d.ts +14 -0
  65. package/dist/capabilities/prompt.js +14 -0
  66. package/dist/capabilities/prompts/code-review.d.ts +14 -0
  67. package/dist/capabilities/prompts/code-review.js +14 -0
  68. package/dist/capabilities/prompts/identity.d.ts +6 -0
  69. package/dist/capabilities/prompts/identity.js +6 -0
  70. package/dist/capabilities/prompts/team.d.ts +4 -0
  71. package/dist/capabilities/prompts/team.js +4 -0
  72. package/dist/capabilities/repo-tools.d.ts +11 -0
  73. package/dist/capabilities/repo-tools.js +8 -0
  74. package/dist/capabilities/sandbox-file-send.d.ts +95 -0
  75. package/dist/capabilities/sandbox-file-send.js +91 -3
  76. package/dist/capabilities/scenarios.d.ts +130 -0
  77. package/dist/capabilities/scenarios.js +180 -7
  78. package/dist/capabilities/select-environment-tool.d.ts +7 -0
  79. package/dist/capabilities/select-environment-tool.js +30 -0
  80. package/dist/capabilities/send-user-file-tool.d.ts +33 -0
  81. package/dist/capabilities/send-user-file-tool.js +65 -1
  82. package/dist/capabilities/skills.d.ts +29 -0
  83. package/dist/capabilities/skills.js +13 -2
  84. package/dist/capabilities/team.d.ts +22 -0
  85. package/dist/capabilities/team.js +22 -2
  86. package/dist/capabilities/tool-defer.d.ts +26 -0
  87. package/dist/capabilities/tool-defer.js +3 -0
  88. package/dist/config-center/apply-effective.d.ts +65 -0
  89. package/dist/config-center/apply-effective.js +204 -12
  90. package/dist/config-center/http-client.d.ts +34 -0
  91. package/dist/config-center/http-client.js +68 -0
  92. package/dist/config-center/restart-signal.d.ts +22 -0
  93. package/dist/config-center/restart-signal.js +39 -0
  94. package/dist/config-center/skills-mcp.d.ts +20 -1
  95. package/dist/config-center/skills-mcp.js +42 -7
  96. package/dist/config-center/types.d.ts +92 -1
  97. package/dist/config-lkg.d.ts +12 -0
  98. package/dist/config-lkg.js +41 -0
  99. package/dist/config-provider.d.ts +78 -0
  100. package/dist/config-provider.js +165 -2
  101. package/dist/config-types.d.ts +546 -0
  102. package/dist/config.d.ts +64 -0
  103. package/dist/config.js +455 -60
  104. package/dist/degenerate-instrument.d.ts +38 -0
  105. package/dist/degenerate-instrument.js +55 -3
  106. package/dist/elicitation.d.ts +39 -0
  107. package/dist/elicitation.js +87 -3
  108. package/dist/env-facts.d.ts +61 -0
  109. package/dist/env-facts.js +73 -7
  110. package/dist/fleet/fleet-bus.d.ts +185 -0
  111. package/dist/fleet/fleet-bus.js +333 -27
  112. package/dist/fleet/subagent-tail-bus.d.ts +11 -0
  113. package/dist/fleet/subagent-tail-bus.js +33 -2
  114. package/dist/fleet-client.d.ts +38 -0
  115. package/dist/fleet-client.js +44 -9
  116. package/dist/fleet-lease.d.ts +46 -0
  117. package/dist/fleet-lease.js +73 -5
  118. package/dist/hooks/branch-transcript.d.ts +8 -0
  119. package/dist/hooks/branch-transcript.js +33 -0
  120. package/dist/hooks/cc-stop-prompt.d.ts +42 -0
  121. package/dist/hooks/cc-stop-prompt.js +43 -1
  122. package/dist/hooks/hook-llm.d.ts +15 -0
  123. package/dist/hooks/hook-llm.js +47 -0
  124. package/dist/hooks/hook-runner.d.ts +89 -0
  125. package/dist/hooks/hook-runner.js +327 -18
  126. package/dist/http/idempotency.d.ts +31 -0
  127. package/dist/http/idempotency.js +34 -0
  128. package/dist/http/principal-gate.d.ts +17 -0
  129. package/dist/http/principal-gate.js +35 -3
  130. package/dist/http/route-ctx.d.ts +36 -0
  131. package/dist/http/routes/approvals-assistant.d.ts +25 -0
  132. package/dist/http/routes/approvals-assistant.js +253 -23
  133. package/dist/http/routes/attachments.js +22 -4
  134. package/dist/http/routes/capabilities.d.ts +7 -0
  135. package/dist/http/routes/capabilities.js +202 -1
  136. package/dist/http/routes/fleet.d.ts +6 -0
  137. package/dist/http/routes/fleet.js +94 -5
  138. package/dist/http/routes/images.d.ts +17 -0
  139. package/dist/http/routes/images.js +222 -14
  140. package/dist/http/routes/leader.d.ts +7 -0
  141. package/dist/http/routes/leader.js +10 -0
  142. package/dist/http/routes/memory-policy.d.ts +6 -0
  143. package/dist/http/routes/memory-policy.js +38 -6
  144. package/dist/http/routes/notify-wake.d.ts +10 -0
  145. package/dist/http/routes/notify-wake.js +19 -3
  146. package/dist/http/routes/observability.d.ts +6 -0
  147. package/dist/http/routes/observability.js +19 -2
  148. package/dist/http/routes/runs.d.ts +15 -0
  149. package/dist/http/routes/runs.js +464 -41
  150. package/dist/http/routes/session-sync.d.ts +15 -0
  151. package/dist/http/routes/session-sync.js +229 -19
  152. package/dist/http/routes/sessions-list.d.ts +6 -0
  153. package/dist/http/routes/sessions-list.js +35 -4
  154. package/dist/http/routes/sessions.d.ts +14 -0
  155. package/dist/http/routes/sessions.js +176 -31
  156. package/dist/http/routes/side-query.d.ts +8 -0
  157. package/dist/http/routes/side-query.js +26 -1
  158. package/dist/http/routes/tasks.d.ts +18 -0
  159. package/dist/http/routes/tasks.js +501 -38
  160. package/dist/http/routes/trace-usage.d.ts +10 -0
  161. package/dist/http/routes/trace-usage.js +66 -14
  162. package/dist/http/routes/workflows.d.ts +12 -0
  163. package/dist/http/routes/workflows.js +180 -19
  164. package/dist/http/run-meta.d.ts +11 -0
  165. package/dist/http/run-meta.js +6 -0
  166. package/dist/http/send.d.ts +19 -0
  167. package/dist/http/send.js +11 -0
  168. package/dist/http/server.d.ts +255 -0
  169. package/dist/http/server.js +921 -75
  170. package/dist/http/sse-log.d.ts +15 -0
  171. package/dist/http/sse-log.js +16 -0
  172. package/dist/http/tar.d.ts +9 -0
  173. package/dist/http/tar.js +21 -5
  174. package/dist/http/wire-gate.d.ts +9 -0
  175. package/dist/http/wire-gate.js +9 -0
  176. package/dist/http/wire-types.d.ts +187 -0
  177. package/dist/http/workspace-content.d.ts +8 -0
  178. package/dist/http/workspace-content.js +10 -0
  179. package/dist/images/bake-validate.d.ts +63 -0
  180. package/dist/images/bake-validate.js +71 -1
  181. package/dist/images/manifest.d.ts +19 -0
  182. package/dist/images/manifest.js +6 -1
  183. package/dist/index.d.ts +1 -5
  184. package/dist/index.js +10 -5
  185. package/dist/key-resolver.d.ts +23 -0
  186. package/dist/key-resolver.js +27 -2
  187. package/dist/leader/diffout.d.ts +23 -0
  188. package/dist/leader/diffout.js +8 -1
  189. package/dist/leader/diffup.d.ts +32 -0
  190. package/dist/leader/diffup.js +43 -0
  191. package/dist/leader/endpoint.d.ts +20 -0
  192. package/dist/leader/endpoint.js +25 -2
  193. package/dist/leader/fanout.d.ts +60 -0
  194. package/dist/leader/fanout.js +29 -3
  195. package/dist/leader/grader-env-factory.d.ts +74 -0
  196. package/dist/leader/grader-env-factory.js +62 -3
  197. package/dist/leader/leader.d.ts +97 -0
  198. package/dist/leader/leader.js +151 -4
  199. package/dist/leader/merge.d.ts +94 -0
  200. package/dist/leader/merge.js +112 -9
  201. package/dist/leader/planner.d.ts +52 -0
  202. package/dist/leader/planner.js +47 -3
  203. package/dist/leader/repair-oracle.d.ts +61 -0
  204. package/dist/leader/repair-oracle.js +59 -1
  205. package/dist/leader/repair-wire.d.ts +78 -0
  206. package/dist/leader/repair-wire.js +79 -2
  207. package/dist/leader/wire.d.ts +79 -0
  208. package/dist/leader/wire.js +255 -8
  209. package/dist/lsp/e2b-bridge.d.ts +16 -0
  210. package/dist/lsp/e2b-bridge.js +32 -4
  211. package/dist/lsp/e2b-manager.d.ts +10 -0
  212. package/dist/lsp/e2b-manager.js +18 -2
  213. package/dist/lsp/lsp-frames.d.ts +12 -0
  214. package/dist/lsp/lsp-frames.js +12 -0
  215. package/dist/lsp/manager.d.ts +12 -0
  216. package/dist/lsp/manager.js +35 -2
  217. package/dist/lsp/types.d.ts +10 -0
  218. package/dist/lsp/ws-transport.d.ts +12 -0
  219. package/dist/lsp/ws-transport.js +53 -5
  220. package/dist/lsp-evict.d.ts +14 -0
  221. package/dist/lsp-evict.js +15 -1
  222. package/dist/main.js +2132 -96
  223. package/dist/memory-export.d.ts +1 -0
  224. package/dist/memory-export.js +4 -0
  225. package/dist/memory-scope.d.ts +49 -0
  226. package/dist/memory-scope.js +87 -4
  227. package/dist/memory-sync-client.d.ts +24 -0
  228. package/dist/memory-sync-client.js +41 -2
  229. package/dist/memory-sync.d.ts +23 -0
  230. package/dist/memory-sync.js +70 -1
  231. package/dist/model-select.d.ts +39 -0
  232. package/dist/model-select.js +42 -1
  233. package/dist/observability/cost-quota.d.ts +20 -0
  234. package/dist/observability/cost-quota.js +4 -0
  235. package/dist/observability/cost-taxonomy.d.ts +45 -0
  236. package/dist/observability/cost-taxonomy.js +34 -0
  237. package/dist/observability/logger.d.ts +1 -0
  238. package/dist/observability/logger.js +6 -0
  239. package/dist/observability/metrics.d.ts +13 -0
  240. package/dist/observability/metrics.js +63 -0
  241. package/dist/observability/otel-exporter.d.ts +9 -0
  242. package/dist/observability/otel-exporter.js +11 -3
  243. package/dist/observability/principal-context.d.ts +9 -0
  244. package/dist/observability/principal-context.js +9 -0
  245. package/dist/observability/prompt-manifest.d.ts +37 -0
  246. package/dist/observability/prompt-manifest.js +35 -1
  247. package/dist/observability/rate-limit.d.ts +10 -0
  248. package/dist/observability/rate-limit.js +1 -0
  249. package/dist/observability/tool-trace.d.ts +24 -0
  250. package/dist/observability/tool-trace.js +41 -1
  251. package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
  252. package/dist/orchestration/hardened-vm-runner.js +119 -4
  253. package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
  254. package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
  255. package/dist/orchestration/hardened-vm-worker.js +27 -0
  256. package/dist/orchestration/subagent-steer.d.ts +44 -0
  257. package/dist/orchestration/subagent-steer.js +45 -1
  258. package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
  259. package/dist/orchestration/workflow-agent-steer.js +80 -1
  260. package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
  261. package/dist/orchestration/workflow-completion-inbox.js +283 -32
  262. package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
  263. package/dist/orchestration/workflow-notify-journal.js +187 -13
  264. package/dist/parked-decide.d.ts +61 -0
  265. package/dist/parked-decide.js +58 -1
  266. package/dist/per-task-image.d.ts +54 -0
  267. package/dist/per-task-image.js +41 -0
  268. package/dist/plan-cache-probe.d.ts +35 -0
  269. package/dist/plan-cache-probe.js +18 -3
  270. package/dist/plugins/approval-exemption-store.d.ts +7 -0
  271. package/dist/plugins/approval-exemption-store.js +28 -2
  272. package/dist/plugins/approval-store-sql.d.ts +64 -0
  273. package/dist/plugins/approval-store-sql.js +32 -0
  274. package/dist/plugins/background-agent-store-sql.d.ts +55 -0
  275. package/dist/plugins/background-agent-store-sql.js +91 -4
  276. package/dist/plugins/background-shell-support.d.ts +98 -0
  277. package/dist/plugins/background-shell-support.js +117 -13
  278. package/dist/plugins/blob-backend.d.ts +104 -0
  279. package/dist/plugins/blob-backend.js +163 -6
  280. package/dist/plugins/breaker-state-sql.d.ts +65 -1
  281. package/dist/plugins/breaker-state-sql.js +46 -8
  282. package/dist/plugins/caching-session-store.d.ts +42 -0
  283. package/dist/plugins/caching-session-store.js +63 -3
  284. package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
  285. package/dist/plugins/checkpoint-store-sql.js +239 -11
  286. package/dist/plugins/file-outcome-sink.d.ts +4 -0
  287. package/dist/plugins/file-outcome-sink.js +9 -0
  288. package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
  289. package/dist/plugins/file-resume-anchor-store.js +43 -4
  290. package/dist/plugins/file-run-store.d.ts +90 -0
  291. package/dist/plugins/file-run-store.js +145 -13
  292. package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
  293. package/dist/plugins/file-snapshot-store-sql.js +178 -10
  294. package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
  295. package/dist/plugins/file-workflow-journal-store.js +10 -0
  296. package/dist/plugins/fork-routing-session-store.d.ts +47 -0
  297. package/dist/plugins/fork-routing-session-store.js +51 -3
  298. package/dist/plugins/host-platform.d.ts +69 -0
  299. package/dist/plugins/host-platform.js +81 -2
  300. package/dist/plugins/image-bake-store-sql.d.ts +136 -1
  301. package/dist/plugins/image-bake-store-sql.js +241 -7
  302. package/dist/plugins/image-index-sql.d.ts +46 -0
  303. package/dist/plugins/image-index-sql.js +113 -4
  304. package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
  305. package/dist/plugins/k8s-bg-scripts.js +88 -4
  306. package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
  307. package/dist/plugins/k8s-exec-protocol.js +41 -0
  308. package/dist/plugins/local-checkpoint-store.d.ts +40 -0
  309. package/dist/plugins/local-checkpoint-store.js +86 -7
  310. package/dist/plugins/local-session-store.d.ts +157 -0
  311. package/dist/plugins/local-session-store.js +289 -34
  312. package/dist/plugins/local-task-attachment-store.js +14 -2
  313. package/dist/plugins/mailbox-store-sql.d.ts +38 -0
  314. package/dist/plugins/mailbox-store-sql.js +39 -6
  315. package/dist/plugins/memory-engine-pg.d.ts +30 -0
  316. package/dist/plugins/memory-engine-pg.js +122 -4
  317. package/dist/plugins/memory-engine-tidb.d.ts +26 -0
  318. package/dist/plugins/memory-engine-tidb.js +115 -5
  319. package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
  320. package/dist/plugins/memory-engine-vector-util.js +5 -0
  321. package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
  322. package/dist/plugins/memory-resume-anchor-store.js +17 -0
  323. package/dist/plugins/memory-run-store.d.ts +63 -0
  324. package/dist/plugins/memory-run-store.js +73 -8
  325. package/dist/plugins/memory-session-policy-store.d.ts +17 -0
  326. package/dist/plugins/memory-session-policy-store.js +17 -0
  327. package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
  328. package/dist/plugins/memory-sync-store-pg.js +45 -2
  329. package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
  330. package/dist/plugins/memory-sync-store-tidb.js +31 -1
  331. package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
  332. package/dist/plugins/outcome-ledger-sql.js +97 -3
  333. package/dist/plugins/pg-approval-store.d.ts +7 -0
  334. package/dist/plugins/pg-approval-store.js +7 -0
  335. package/dist/plugins/pg-breaker-state.d.ts +6 -0
  336. package/dist/plugins/pg-breaker-state.js +6 -0
  337. package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
  338. package/dist/plugins/pg-checkpoint-store.js +8 -0
  339. package/dist/plugins/pg-cost-quota.d.ts +17 -0
  340. package/dist/plugins/pg-cost-quota.js +6 -0
  341. package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
  342. package/dist/plugins/pg-file-snapshot-store.js +6 -0
  343. package/dist/plugins/pg-image-bake.d.ts +9 -0
  344. package/dist/plugins/pg-image-bake.js +9 -0
  345. package/dist/plugins/pg-image-index.d.ts +9 -0
  346. package/dist/plugins/pg-image-index.js +9 -0
  347. package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
  348. package/dist/plugins/pg-outcome-ledger.js +9 -0
  349. package/dist/plugins/pg-pool.d.ts +50 -0
  350. package/dist/plugins/pg-pool.js +78 -0
  351. package/dist/plugins/pg-query.d.ts +11 -0
  352. package/dist/plugins/pg-rate-limiter.d.ts +23 -0
  353. package/dist/plugins/pg-rate-limiter.js +12 -2
  354. package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
  355. package/dist/plugins/pg-resume-anchor-store.js +5 -0
  356. package/dist/plugins/pg-run-store.d.ts +7 -0
  357. package/dist/plugins/pg-run-store.js +7 -0
  358. package/dist/plugins/pg-safe-json.d.ts +13 -0
  359. package/dist/plugins/pg-safe-json.js +40 -4
  360. package/dist/plugins/pg-session-policy-store.d.ts +5 -0
  361. package/dist/plugins/pg-session-policy-store.js +5 -0
  362. package/dist/plugins/pg-session-storage.d.ts +72 -0
  363. package/dist/plugins/pg-session-storage.js +152 -20
  364. package/dist/plugins/pg-session-store.d.ts +10 -0
  365. package/dist/plugins/pg-session-store.js +10 -0
  366. package/dist/plugins/pg-tool-result-store.d.ts +7 -0
  367. package/dist/plugins/pg-tool-result-store.js +7 -0
  368. package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
  369. package/dist/plugins/pg-workflow-journal-store.js +7 -0
  370. package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
  371. package/dist/plugins/pg-workflow-run-store.js +7 -0
  372. package/dist/plugins/posix-shell-fs.d.ts +3 -0
  373. package/dist/plugins/posix-shell-fs.js +31 -1
  374. package/dist/plugins/remote-env-adb.d.ts +25 -0
  375. package/dist/plugins/remote-env-adb.js +95 -12
  376. package/dist/plugins/remote-env-e2b.d.ts +146 -0
  377. package/dist/plugins/remote-env-e2b.js +371 -40
  378. package/dist/plugins/remote-env-file-error.d.ts +34 -0
  379. package/dist/plugins/remote-env-file-error.js +37 -0
  380. package/dist/plugins/remote-env-host.d.ts +151 -0
  381. package/dist/plugins/remote-env-host.js +468 -70
  382. package/dist/plugins/remote-env-k8s.d.ts +135 -0
  383. package/dist/plugins/remote-env-k8s.js +318 -32
  384. package/dist/plugins/remote-env-local-docker.d.ts +65 -0
  385. package/dist/plugins/remote-env-local-docker.js +149 -24
  386. package/dist/plugins/remote-env-ssh.d.ts +35 -0
  387. package/dist/plugins/remote-env-ssh.js +117 -20
  388. package/dist/plugins/remote-scratchpad.d.ts +38 -0
  389. package/dist/plugins/remote-scratchpad.js +32 -2
  390. package/dist/plugins/remote-shell.d.ts +22 -0
  391. package/dist/plugins/remote-shell.js +27 -1
  392. package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
  393. package/dist/plugins/resume-anchor-store-sql.js +11 -0
  394. package/dist/plugins/roster-store-sql.d.ts +27 -0
  395. package/dist/plugins/roster-store-sql.js +54 -0
  396. package/dist/plugins/run-store-sql.d.ts +194 -0
  397. package/dist/plugins/run-store-sql.js +210 -9
  398. package/dist/plugins/s3-presign.d.ts +20 -0
  399. package/dist/plugins/s3-presign.js +49 -2
  400. package/dist/plugins/scheduler-support.d.ts +12 -0
  401. package/dist/plugins/scheduler-support.js +75 -3
  402. package/dist/plugins/send-file-ledger.d.ts +15 -0
  403. package/dist/plugins/send-file-ledger.js +53 -4
  404. package/dist/plugins/send-user-file.d.ts +77 -0
  405. package/dist/plugins/send-user-file.js +92 -4
  406. package/dist/plugins/session-policy-store-sql.d.ts +15 -0
  407. package/dist/plugins/session-policy-store-sql.js +75 -3
  408. package/dist/plugins/session-store.d.ts +45 -0
  409. package/dist/plugins/session-store.js +51 -0
  410. package/dist/plugins/sql-driver.d.ts +77 -0
  411. package/dist/plugins/sql-driver.js +4 -0
  412. package/dist/plugins/sql-escape.d.ts +8 -0
  413. package/dist/plugins/sql-escape.js +8 -0
  414. package/dist/plugins/sql-row-helpers.d.ts +25 -0
  415. package/dist/plugins/sql-row-helpers.js +25 -0
  416. package/dist/plugins/store-backend.d.ts +103 -0
  417. package/dist/plugins/store-backend.js +120 -27
  418. package/dist/plugins/store-contracts.d.ts +62 -0
  419. package/dist/plugins/store-contracts.js +3 -0
  420. package/dist/plugins/task-attachment-store.d.ts +60 -0
  421. package/dist/plugins/task-attachment-store.js +49 -3
  422. package/dist/plugins/task-list-store-sql.d.ts +25 -0
  423. package/dist/plugins/task-list-store-sql.js +21 -0
  424. package/dist/plugins/tidb-approval-store.d.ts +6 -0
  425. package/dist/plugins/tidb-approval-store.js +6 -0
  426. package/dist/plugins/tidb-breaker-state.d.ts +5 -0
  427. package/dist/plugins/tidb-breaker-state.js +5 -0
  428. package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
  429. package/dist/plugins/tidb-checkpoint-store.js +7 -0
  430. package/dist/plugins/tidb-cost-quota.d.ts +6 -0
  431. package/dist/plugins/tidb-cost-quota.js +1 -0
  432. package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
  433. package/dist/plugins/tidb-file-snapshot-store.js +6 -0
  434. package/dist/plugins/tidb-image-bake.d.ts +8 -0
  435. package/dist/plugins/tidb-image-bake.js +8 -0
  436. package/dist/plugins/tidb-image-index.d.ts +8 -0
  437. package/dist/plugins/tidb-image-index.js +8 -0
  438. package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
  439. package/dist/plugins/tidb-outcome-ledger.js +10 -0
  440. package/dist/plugins/tidb-pool.d.ts +56 -0
  441. package/dist/plugins/tidb-pool.js +181 -1
  442. package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
  443. package/dist/plugins/tidb-rate-limiter.js +5 -2
  444. package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
  445. package/dist/plugins/tidb-resume-anchor-store.js +5 -0
  446. package/dist/plugins/tidb-run-store.d.ts +7 -0
  447. package/dist/plugins/tidb-run-store.js +7 -0
  448. package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
  449. package/dist/plugins/tidb-session-policy-store.js +5 -0
  450. package/dist/plugins/tidb-session-storage.d.ts +43 -0
  451. package/dist/plugins/tidb-session-storage.js +62 -3
  452. package/dist/plugins/tidb-session-store.d.ts +142 -0
  453. package/dist/plugins/tidb-session-store.js +264 -16
  454. package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
  455. package/dist/plugins/tidb-tool-result-store.js +7 -0
  456. package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
  457. package/dist/plugins/tidb-workflow-journal-store.js +7 -0
  458. package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
  459. package/dist/plugins/tidb-workflow-run-store.js +8 -0
  460. package/dist/plugins/tool-result-store-sql.d.ts +58 -0
  461. package/dist/plugins/tool-result-store-sql.js +38 -2
  462. package/dist/plugins/web-search.d.ts +35 -0
  463. package/dist/plugins/web-search.js +45 -9
  464. package/dist/plugins/workflow-journal-limits.d.ts +10 -0
  465. package/dist/plugins/workflow-journal-limits.js +10 -0
  466. package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
  467. package/dist/plugins/workflow-journal-store-sql.js +59 -3
  468. package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
  469. package/dist/plugins/workflow-run-store-sql.js +84 -6
  470. package/dist/plugins/worktree-isolation.d.ts +45 -0
  471. package/dist/plugins/worktree-isolation.js +105 -6
  472. package/dist/plugins/write-behind-counter.d.ts +56 -1
  473. package/dist/plugins/write-behind-counter.js +53 -14
  474. package/dist/principal-jwt.d.ts +24 -0
  475. package/dist/principal-jwt.js +60 -5
  476. package/dist/project-memory.d.ts +22 -0
  477. package/dist/project-memory.js +144 -15
  478. package/dist/prompts-domain-validate.d.ts +28 -0
  479. package/dist/prompts-domain-validate.js +42 -1
  480. package/dist/question.d.ts +33 -0
  481. package/dist/question.js +69 -2
  482. package/dist/resource-suspend.d.ts +21 -0
  483. package/dist/resource-suspend.js +16 -0
  484. package/dist/router/route-orchestration.d.ts +78 -0
  485. package/dist/router/route-orchestration.js +77 -0
  486. package/dist/run-local.d.ts +32 -0
  487. package/dist/run-local.js +172 -8
  488. package/dist/runs.d.ts +189 -4
  489. package/dist/runs.js +465 -24
  490. package/dist/runtime-caps-resolver.d.ts +106 -0
  491. package/dist/runtime-caps-resolver.js +89 -4
  492. package/dist/runtime-governance.d.ts +84 -0
  493. package/dist/runtime-governance.js +127 -1
  494. package/dist/sandbox-pkg-source.d.ts +23 -0
  495. package/dist/sandbox-pkg-source.js +37 -0
  496. package/dist/sealed-key.d.ts +54 -0
  497. package/dist/sealed-key.js +68 -3
  498. package/dist/security.d.ts +108 -0
  499. package/dist/security.js +145 -6
  500. package/dist/sema-registry.d.ts +21 -0
  501. package/dist/sema-registry.js +21 -0
  502. package/dist/session-leaf-bus.d.ts +31 -0
  503. package/dist/session-leaf-bus.js +32 -0
  504. package/dist/session-sync-content.d.ts +46 -0
  505. package/dist/session-sync-content.js +47 -1
  506. package/dist/session-sync-kernel.d.ts +89 -0
  507. package/dist/session-sync-kernel.js +48 -3
  508. package/dist/session-sync.d.ts +125 -0
  509. package/dist/session-sync.js +181 -8
  510. package/dist/session-titler.d.ts +26 -0
  511. package/dist/session-titler.js +41 -7
  512. package/dist/session-watch.d.ts +53 -1
  513. package/dist/session-watch.js +73 -9
  514. package/dist/sighup-idle.d.ts +30 -0
  515. package/dist/sighup-idle.js +12 -1
  516. package/dist/spec-fields.d.ts +91 -3
  517. package/dist/spec-fields.js +116 -4
  518. package/dist/task-cwd.d.ts +50 -0
  519. package/dist/task-cwd.js +68 -1
  520. package/dist/task-mcp.d.ts +47 -0
  521. package/dist/task-mcp.js +29 -0
  522. package/dist/task-settings.d.ts +175 -0
  523. package/dist/task-settings.js +226 -11
  524. package/dist/task-workflow.d.ts +67 -0
  525. package/dist/task-workflow.js +75 -8
  526. package/dist/tool-approval.d.ts +91 -0
  527. package/dist/tool-approval.js +215 -0
  528. package/dist/trace/artifacts.d.ts +9 -0
  529. package/dist/trace/artifacts.js +30 -5
  530. package/dist/trace/core-keyset-guard.d.ts +18 -0
  531. package/dist/trace/ledger-sink.d.ts +43 -0
  532. package/dist/trace/ledger-sink.js +64 -5
  533. package/dist/trace/project.d.ts +133 -0
  534. package/dist/trace/project.js +189 -2
  535. package/dist/trace/redact.d.ts +19 -0
  536. package/dist/trace/redact.js +66 -9
  537. package/dist/usage-analytics.d.ts +26 -0
  538. package/dist/usage-analytics.js +26 -3
  539. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { callKeyOrdinal } from "@sema-agent/core";
2
2
  import { oversizeJournalResult } from "./workflow-journal-limits.js";
3
3
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
4
- const DEFAULT_RESUME_CLAIM_TTL_MS = 15 * 60 * 1000;
4
+ const DEFAULT_RESUME_CLAIM_TTL_MS = 60 * 60 * 1000;
5
5
  export class SqlWorkflowJournalStore {
6
6
  db;
7
7
  resumeClaimTtlMs;
@@ -9,38 +9,83 @@ export class SqlWorkflowJournalStore {
9
9
  this.db = db;
10
10
  this.resumeClaimTtlMs = opts?.resumeClaimTtlMs ?? DEFAULT_RESUME_CLAIM_TTL_MS;
11
11
  }
12
+ /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
12
13
  q(tidb, pg) {
13
14
  return this.db.dialect === "tidb" ? tidb : pg;
14
15
  }
16
+ /** Record one agent's result. Idempotent per (run_id, ordinal): the PK upsert OVERWRITES (last-write-wins), so a
17
+ * resumed run re-appending its replayed prefix verbatim leaves no duplicate. callKeyOrdinal is the SOLE ordinal
18
+ * source (core's parser — a malformed key collapses to 0, matching InMemory). Throws on a REAL failure
19
+ * (LOAD-BEARING), EXCEPT an oversize result is skip-journaled (see below) rather than throwing the workflow dead. */
15
20
  async append(runId, scope, entry) {
21
+ // codex R10 定谳:result 列=TEXT(PG)/MEDIUMTEXT(TiDB),存的是 stringify 输出——NUL/lone surrogate
22
+ // 在输出里恒为 \uXXXX 六字符**转义形**(纯 ASCII),TEXT 列可精确存取;22P05 只咬 ::jsonb cast。
23
+ // 故 journal 无需任何 NUL 守卫:直存=逐位回放(R8 skip/R9 invalidate 均为过度修,已撤——那会把
24
+ // 可精确回放的完成结果作废成不必要的活跑重执行,外部副作用可能重复)。
16
25
  const serialized = JSON.stringify(entry.result);
26
+ // TiDB rejects a row over the ~6 MiB txn-entry-size-limit ("entry too large"); a large agent TaskResult would
27
+ // make this LOAD-BEARING append THROW + kill the workflow — and the PG twin (no such limit) would NOT, a
28
+ // dialect divergence. DEGRADE identically on both: skip-journal an oversize result (no-op, never insert/throw)
29
+ // → its ordinal isn't cached, so a resume re-runs that one agent live (resume is an optimization).
17
30
  if (oversizeJournalResult(serialized))
18
31
  return;
19
32
  await this.db.query(this.q("INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at) VALUES (?,?,?,?,?,?) " +
33
+ // scope NOT in the UPDATE: a re-append keeps the FIRST writer's scope (fail-closed — a re-append must never
34
+ // reassign tenancy). The owner is the sole writer + runId is unique per scope, so this never fires
35
+ // cross-scope in practice; the secure choice over matching InMemory's last-write-wins on scope.
20
36
  "ON DUPLICATE KEY UPDATE call_key = VALUES(call_key), result = VALUES(result)", "INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at) VALUES ($1,$2,$3,$4,$5,$6) " +
21
37
  "ON CONFLICT (run_id, ordinal) DO UPDATE SET call_key = EXCLUDED.call_key, result = EXCLUDED.result"), [runId, callKeyOrdinal(entry.callKey), scope, entry.callKey, serialized, Date.now()]);
22
38
  }
39
+ /** Entries for `runId` IF its recorded scope === `scope`, ASCENDING by ordinal; otherwise EMPTY. CORE-9 audit
40
+ * BLOCKER: scope in the WHERE (twin of WorkflowRunStore) — the LLM controls `resumeFromRunId`, so a cross-scope
41
+ * runId resolves to an empty journal (resume safely diverges to live, never discloses another tenant's results).
42
+ * Each row's `result` is the persisted JSON.stringify(TaskResult) → JSON.parse back. Throws on failure (LOAD-BEARING). */
23
43
  async load(runId, scope) {
24
44
  const { rows } = await this.db.query(this.q("SELECT call_key, result FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC", "SELECT call_key, result FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC"), [runId, scope]);
25
45
  return rows.map((r) => ({ callKey: String(r.call_key), result: JSON.parse(String(r.result)) }));
26
46
  }
47
+ /** [1412] locator 云形(core 1.353 WorkflowJournalStore.locator additive):SQL 店的 journal 坐标=
48
+ * [1402] HTTP 读面路由(file 店返 jsonl 路径=CC 本地形;此处返云形,core 铸 diagnostics 教句时单源引用)。 */
27
49
  locator(runId, _scope) {
28
50
  return `/v1/workflows/${runId}/journal`;
29
51
  }
52
+ /** codex R1-H2:分页投影读——LIMIT/OFFSET 界行数,字节门界单行字节(超限行不取 result 全文,只回字节数;
53
+ * 读面标 truncated)。ORDER BY ordinal 与 load 同轴。
54
+ * 🔴 param ORDER differs by dialect (see file header): TiDB's `?`s bind in text order (maxResultBytes first,
55
+ * inside the CASE); PG's `$n` are positional, so its array keeps runId/scope first. Never unify — a swap
56
+ * silently mis-binds the OTHER dialect's placeholders. */
30
57
  async loadPage(runId, scope, opts) {
31
58
  const params = this.db.dialect === "tidb"
32
59
  ? [opts.maxResultBytes, runId, scope, opts.limit, opts.offset]
33
60
  : [runId, scope, opts.maxResultBytes, opts.limit, opts.offset];
34
- const { rows } = await this.db.query(this.q("SELECT call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE result END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC LIMIT ? OFFSET ?", "SELECT call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE result END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC LIMIT $4 OFFSET $5"), params);
61
+ const { rows } = await this.db.query(this.q("SELECT call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE result END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC LIMIT ? OFFSET ?",
62
+ // codex R3: PG's LENGTH(text) is CHARACTER count (TiDB's LENGTH is byte count) — a multi-byte UTF-8 row
63
+ // would slip past the byte-budget door on plain LENGTH; OCTET_LENGTH keeps this the byte count PG-side
64
+ // too (real dual-DB-suite-confirmed anti-drift).
65
+ "SELECT call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE result END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC LIMIT $4 OFFSET $5"), params);
35
66
  return rows.map((r) => ({ callKey: String(r.call_key), resultJson: r.result_json === null ? null : String(r.result_json), resultBytes: Number(r.result_bytes) }));
36
67
  }
68
+ /** GC — purge ALL journal entries for a run (the run reaper calls this once the run is terminal + retained). Scoped
69
+ * by run_id alone; idempotent (affected-row count). Mirrors the resume-anchor store's deleteBySession. */
37
70
  async deleteByRun(runId) {
38
71
  const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE run_id = ?", "DELETE FROM workflow_journal WHERE run_id = $1"), [runId]);
39
72
  return res.affected;
40
73
  }
74
+ /** RB-242 / WF2([1981] core 拍板 a 形,签名逐字)—— cross-replica resume admission lease。
75
+ *
76
+ * 语义(bake-store `idem_key UNIQUE` 先例):PK (source_run_id, scope) 上的原子赢或观察。四步,每步
77
+ * 单语句原子,并发交叉在任一步都收敛到「恰一个持有者」:
78
+ * ① 抢空位:INSERT..DO NOTHING / ON DUP KEY 无操作 —— affected=1 即赢;
79
+ * ② 同持有者幂等重入(engine 重试同一 resume):按 (键, new_run_id) 守卫的 claimed_at 刷新;
80
+ * ③ TTL 崩溃兜底接管:claimed_at < now-ttl 守卫下的原子改持有者(engine 终态会显式释放,
81
+ * 走到这步=上一持有 engine 崩了没释放;两层分工见 SqlWorkflowJournalStoreOptions 注);
82
+ * ④ 都没赢 ⇒ 读在位者返 {granted:false, holder}(holder 进 engine 的拒绝文案供归因)。 */
41
83
  async resumeClaim(input) {
42
84
  const now = Date.now();
43
- const ins = await this.db.query(this.q("INSERT IGNORE INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES (?, ?, ?, ?)", "INSERT INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES ($1, $2, $3, $4) ON CONFLICT (source_run_id, scope) DO NOTHING"), [input.sourceRunId, input.scope, input.newRunId, now]);
85
+ const ins = await this.db.query(this.q(
86
+ // INSERT IGNORE(非 ON DUP KEY 无操作形):TiDB 对「无变化的 DUP KEY UPDATE」affected 报 1
87
+ // (MySQL 报 0)——真双库跑出来的方言差;IGNORE 形两家都在冲突时报 0。
88
+ "INSERT IGNORE INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES (?, ?, ?, ?)", "INSERT INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES ($1, $2, $3, $4) ON CONFLICT (source_run_id, scope) DO NOTHING"), [input.sourceRunId, input.scope, input.newRunId, now]);
44
89
  if (ins.affected === 1)
45
90
  return { granted: true };
46
91
  const refresh = await this.db.query(this.q("UPDATE workflow_resume_claim SET claimed_at = ? WHERE source_run_id = ? AND scope = ? AND new_run_id = ?", "UPDATE workflow_resume_claim SET claimed_at = $1 WHERE source_run_id = $2 AND scope = $3 AND new_run_id = $4"), [now, input.sourceRunId, input.scope, input.newRunId]);
@@ -50,25 +95,36 @@ export class SqlWorkflowJournalStore {
50
95
  if (takeover.affected >= 1)
51
96
  return { granted: true };
52
97
  const holder = await this.db.query(this.q("SELECT new_run_id FROM workflow_resume_claim WHERE source_run_id = ? AND scope = ?", "SELECT new_run_id FROM workflow_resume_claim WHERE source_run_id = $1 AND scope = $2"), [input.sourceRunId, input.scope]);
98
+ // 行在①-③间被释放的窄窗:holder 读空 ⇒ 如实返 denied 无 holder(engine 下一次重试会在①赢)。
53
99
  const row = holder.rows[0];
54
100
  const holderId = row?.new_run_id === undefined ? undefined : String(row.new_run_id);
101
+ // ②的 UPDATE 在 MySQL 缺省协议下只数**被改变**的行——同毫秒重入(claimed_at 未变)会 affected=0
102
+ // 掉到这里;持有者==自己仍是 granted(幂等重入语义不依赖 affected 的方言细节)。
55
103
  if (holderId === input.newRunId)
56
104
  return { granted: true };
57
105
  return { granted: false, holder: holderId };
58
106
  }
107
+ /** 只有 holder 能释放(new_run_id 守卫)—— engine 在 newRunId 终态/放弃时调;非 holder 调用=no-op。 */
59
108
  async releaseResumeClaim(input) {
60
109
  await this.db.query(this.q("DELETE FROM workflow_resume_claim WHERE source_run_id = ? AND scope = ? AND new_run_id = ?", "DELETE FROM workflow_resume_claim WHERE source_run_id = $1 AND scope = $2 AND new_run_id = $3"), [input.sourceRunId, input.scope, input.newRunId]);
61
110
  }
111
+ /** GC — time-based sweep: purge journal entries older than `maxAgeMs` (created_at < now - maxAgeMs). The
112
+ * per-run deleteByRun has no production caller (a run-store reap gives no per-run hook), so this bounded sweep
113
+ * — wired into the service reaper — is what keeps the heaviest table (TaskResult-bearing) from growing without
114
+ * bound. A resume of a journal older than the retention window simply re-runs live (resume is an optimization).
115
+ * Idempotent; returns the rows purged. */
62
116
  async reapExpired(now, maxAgeMs) {
63
117
  const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE created_at < ?", "DELETE FROM workflow_journal WHERE created_at < $1"), [now - maxAgeMs]);
64
118
  return res.affected;
65
119
  }
66
120
  }
121
+ /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
67
122
  export class TiDBWorkflowJournalStore extends SqlWorkflowJournalStore {
68
123
  constructor(pool, opts) {
69
124
  super(mysqlDriver(pool), opts);
70
125
  }
71
126
  }
127
+ /** PostgreSQL binding — historical class name + ctor shape preserved. */
72
128
  export class PgWorkflowJournalStore extends SqlWorkflowJournalStore {
73
129
  constructor(pool, opts) {
74
130
  super(pgDriver(pool), opts);
@@ -1,3 +1,59 @@
1
+ /**
2
+ * P1 (fleet failover correctness, clay 2026-07-05) — SINGLE-FILE DUAL-DIALECT (design/158 A12 定型半场).
3
+ * ONE implementation, TWO dialects; the historical `TiDBWorkflowRunStore`/`PgWorkflowRunStore`,
4
+ * `TiDBWorkflowCompletionInbox`/`PgWorkflowCompletionInbox`, `TiDBWorkflowNotifyJournalStore`/
5
+ * `PgWorkflowNotifyJournalStore` class names survive as thin ctor subclasses so every consumer
6
+ * (store-backend.ts, the DB-integration + slim-oversize-run suites) is untouched.
7
+ *
8
+ * These are the TiDB/PG twins of core's `WorkflowRunStore` and the service `WorkflowCompletionInbox`,
9
+ * closing the LAST replica-local gap in the fleet story: dispatch failover (taskId idempotency +
10
+ * announce/lease, 1.98–1.103) can land a session on ANOTHER instance, where a File-backed workflow
11
+ * record / completion inbox strands the run's history and its pending push.
12
+ *
13
+ * ## WorkflowRunStore twin
14
+ * One row per run: the full `WorkflowRun` as a JSON blob + EXTRACTED columns for everything SQL needs to
15
+ * index/filter (scope, status, created_at, ended_at) + an authoritative `rev` column (the OCC key — the
16
+ * blob's own `rev` is OVERLAID from the column on every read, so writes never have to know the bumped value
17
+ * up front). `update` is a single-statement CAS (`WHERE id AND scope [AND rev]`, `SET rev = rev + 1`);
18
+ * `listByScope` projects через core's SHARED `summarizeWorkflowRun` (anti-drift — identical to InMemory/File).
19
+ *
20
+ * ## Completion inbox twin
21
+ * The File inbox's fences (purge / poll-served) are in-memory BY DESIGN there (single box); here they are
22
+ * ROWS (`workflow_inbox_fence`), which is the actual point of the port — a poll served on replica A must
23
+ * fence the late notify landing on replica B, or the double-push returns the moment a fleet has two
24
+ * replicas. Fences are swept opportunistically on every write verb. Entry order rides an auto-incrementing
25
+ * `seq` (re-enqueue = atomic move-to-tail → fresh seq = tail, matching the File/InMemory Map semantics; 1.108
26
+ * review hardening: post-insert fence RE-CHECK closes the cross-replica check-then-insert TOCTOU).
27
+ *
28
+ * ## Notify-journal twin
29
+ * 1.108 review fix (lens③ HIGH): with a SQL run store + inbox, a replica-local File journal was the last
30
+ * replica-local piece of the at-least-once completion-notify chain (a replica that died holding an un-acked
31
+ * entry stranded the notify forever). Semantics mirror `FileWorkflowNotifyJournalStore` verb-for-verb:
32
+ * `record` is idempotent and NEVER resets an acked row; `ack` flips pending→acked once; `listPending` is the
33
+ * recovery backlog (any replica may sweep it — the receiver is idempotent on runId, so a double recovery
34
+ * across replicas is at-least-once, not N-times).
35
+ *
36
+ * ── Dialect deltas, kept EXPLICIT ────────────────────────────────────────────────────────────────────
37
+ * - `?` placeholders vs `$n` (and the OPTIONAL `AND rev = ?` / `AND rev = $6` OCC tail on `update`)
38
+ * - create-once dup-key: errno-string `ER_DUP_ENTRY` vs SQLSTATE `23505`
39
+ * - `listByScope`'s optional-session filter: `JSON_UNQUOTE(JSON_EXTRACT(run, '$.originatingSessionId'))`
40
+ * vs `run::json->>'originatingSessionId'` — AND the placeholder-numbering shape that follows (PG's `$n`
41
+ * is POSITIONAL and must track `params.length` as each optional clause is appended; TiDB's `?` is not),
42
+ * so this method keeps an explicit tidb/pg branch rather than a single `q()` pair.
43
+ * - completion-inbox move-to-tail: TiDB `REPLACE INTO …` (atomic delete-conflicting-row + insert, omitted
44
+ * `seq` mints a fresh AUTO_INCREMENT = tail) vs PG `INSERT … ON CONFLICT … DO UPDATE SET seq = nextval(…)`
45
+ * (re-mint `seq` off the BIGSERIAL's own sequence) — same outcome, structurally different statements.
46
+ * - the inbox overflow-scan needs `LIMIT 100000 OFFSET ?` on TiDB (MySQL-protocol requires a LIMIT
47
+ * alongside OFFSET) but plain `OFFSET $2` on PG.
48
+ * - bulk delete-by-id-set: `id IN (?,?,…)` (dynamic placeholder count) vs `id = ANY($n::text[])` /
49
+ * `seq = ANY($n::bigint[])` (array bind) — both twins do this for `reap` and the inbox overflow drop.
50
+ * - `run` is TEXT (not jsonb) on PG — matches TiDB's plain string column, so JSON binding is
51
+ * `JSON.stringify` verbatim on BOTH dialects (no `pgProtocolJsonStringify` envelope needed here; that
52
+ * concern belongs to stores whose column IS jsonb, e.g. checkpoint-store-sql.ts).
53
+ * - affectedRows vs rowCount (via SqlDriver's normalized `affected`).
54
+ * - schema ownership: TiDB DDL in tidb-pool.ts, PG DDL centrally in pg-pool.ts (neither store creates
55
+ * tables).
56
+ */
1
57
  import type { Pool as MySqlPool } from "mysql2/promise";
2
58
  import type { Pool as PgPool } from "pg";
3
59
  import { summarizeWorkflowRun, type WorkflowRunStore } from "@sema-agent/core";
@@ -5,16 +61,29 @@ import { type WorkflowCompletionInbox, type WorkflowCompletionInboxEntry } from
5
61
  import type { WorkflowNotifyJournalStore, WorkflowNotifyJournalEntry } from "../orchestration/workflow-notify-journal.js";
6
62
  import { type SqlDriver } from "./sql-driver.js";
7
63
  type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
64
+ /** Row cap guard: a run blob beyond this is NOT stored on update (the prior revision stays) — TiDB's
65
+ * ~6 MiB txn-entry limit would otherwise throw the (best-effort) progress write into the workflow loop.
66
+ * `put` (create, small) still throws on a real failure. Mirrors the journal's oversize skip-degrade. */
8
67
  export declare const MAX_RUN_BLOB_BYTES: number;
9
68
  declare const onWarnType: (msg: string, meta: Record<string, unknown>) => void;
10
69
  export type InboxWarn = typeof onWarnType;
70
+ /** Review fix (1.108, lens④ HIGH): an oversize blob must NOT fail the update outright — core's persist chain
71
+ * treats `false` as a CAS loss and does NOT advance its tracked rev, so once a run outgrows the cap EVERY later
72
+ * write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
73
+ * orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write:
74
+ * step 1 truncate the two unbounded strings (result/error); step 2 drop the heavy arrays (phases/agents/groups)
75
+ * with an explicit marker. Returns null only when even the skeleton is oversize (physics — caller keeps the
76
+ * prior revision, matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared
77
+ * verbatim by both twins. */
11
78
  export declare function slimOversizeRun(run: WorkflowRun & {
12
79
  id: string;
13
80
  scope: string;
14
81
  }, maxBytes: number): string | null;
82
+ /** Dual-dialect durable `WorkflowRunStore`. See the file header for the dialect-delta ledger. */
15
83
  export declare class SqlWorkflowRunStore implements WorkflowRunStore {
16
84
  protected readonly db: SqlDriver;
17
85
  constructor(db: SqlDriver);
86
+ /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
18
87
  private q;
19
88
  put(id: string, run: WorkflowRun): Promise<void>;
20
89
  get(id: string): Promise<WorkflowRun | null>;
@@ -26,6 +95,11 @@ export declare class SqlWorkflowRunStore implements WorkflowRunStore {
26
95
  limit?: number;
27
96
  session?: string;
28
97
  }): Promise<ReturnType<typeof summarizeWorkflowRun>[]>;
98
+ /** Retention (the store never auto-purges; reap is explicit): the contract's `reap` is scope-keyed and the
99
+ * contract deliberately has NO cross-scope enumeration — but the SQL twin CAN enumerate (DISTINCT on the
100
+ * extracted scope column), which is exactly what a periodic deployment sweep needs. Walks every scope through
101
+ * the contract-faithful `reap` (terminal-only; running rows are the orphan-grace sweep's business). Service
102
+ * extension, not a contract member. Byte-identical text on both dialects (no params) — no q() needed. */
29
103
  reapAllScopes(now: number, opts: {
30
104
  maxAgeMs?: number;
31
105
  keep?: number;
@@ -35,6 +109,7 @@ export declare class SqlWorkflowRunStore implements WorkflowRunStore {
35
109
  keep?: number;
36
110
  }): Promise<number>;
37
111
  }
112
+ /** Dual-dialect `WorkflowCompletionInbox`. See the file header for the dialect-delta ledger. */
38
113
  export declare class SqlWorkflowCompletionInbox implements WorkflowCompletionInbox {
39
114
  protected readonly db: SqlDriver;
40
115
  private readonly onWarn?;
@@ -47,6 +122,10 @@ export declare class SqlWorkflowCompletionInbox implements WorkflowCompletionInb
47
122
  purge(sessionId: string, purgedOwner?: string | null): Promise<void>;
48
123
  markTerminalServed(sessionId: string, runId: string): Promise<void>;
49
124
  }
125
+ /**
126
+ * Dual-dialect `WorkflowNotifyJournalStore` — the crash-safe recovery backlog for at-least-once completion
127
+ * notify. See the file header for the dialect-delta ledger.
128
+ */
50
129
  export declare class SqlWorkflowNotifyJournalStore implements WorkflowNotifyJournalStore {
51
130
  protected readonly db: SqlDriver;
52
131
  constructor(db: SqlDriver);
@@ -60,9 +139,13 @@ export declare class SqlWorkflowNotifyJournalStore implements WorkflowNotifyJour
60
139
  }): Promise<void>;
61
140
  ack(runId: string, ackedAt: number): Promise<void>;
62
141
  listPending(): Promise<WorkflowNotifyJournalEntry[]>;
142
+ /** Retention (same sweep as reapAllScopes): ACKED rows are pure history — without this the twin re-opens
143
+ * the unbounded-growth hole the same release closed for workflow_run. Pending rows are NEVER reaped (they
144
+ * are the recovery backlog; the orphan-grace sweep is what retires a stuck pending run). */
63
145
  reapAcked(before: number): Promise<number>;
64
146
  get(runId: string): Promise<WorkflowNotifyJournalEntry | null>;
65
147
  }
148
+ /** MySQL-protocol (TiDB) bindings — historical class names + ctor shapes preserved. */
66
149
  export declare class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
67
150
  constructor(pool: MySqlPool);
68
151
  }
@@ -72,6 +155,7 @@ export declare class TiDBWorkflowCompletionInbox extends SqlWorkflowCompletionIn
72
155
  export declare class TiDBWorkflowNotifyJournalStore extends SqlWorkflowNotifyJournalStore {
73
156
  constructor(pool: MySqlPool);
74
157
  }
158
+ /** PostgreSQL bindings — historical class names + ctor shapes preserved. */
75
159
  export declare class PgWorkflowRunStore extends SqlWorkflowRunStore {
76
160
  constructor(pool: PgPool);
77
161
  }
@@ -1,8 +1,19 @@
1
1
  import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
2
2
  import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
3
3
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
4
+ /** Row cap guard: a run blob beyond this is NOT stored on update (the prior revision stays) — TiDB's
5
+ * ~6 MiB txn-entry limit would otherwise throw the (best-effort) progress write into the workflow loop.
6
+ * `put` (create, small) still throws on a real failure. Mirrors the journal's oversize skip-degrade. */
4
7
  export const MAX_RUN_BLOB_BYTES = 4 * 1024 * 1024;
5
8
  const onWarnType = (msg, meta) => void [msg, meta];
9
+ /** Review fix (1.108, lens④ HIGH): an oversize blob must NOT fail the update outright — core's persist chain
10
+ * treats `false` as a CAS loss and does NOT advance its tracked rev, so once a run outgrows the cap EVERY later
11
+ * write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
12
+ * orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write:
13
+ * step 1 truncate the two unbounded strings (result/error); step 2 drop the heavy arrays (phases/agents/groups)
14
+ * with an explicit marker. Returns null only when even the skeleton is oversize (physics — caller keeps the
15
+ * prior revision, matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared
16
+ * verbatim by both twins. */
6
17
  export function slimOversizeRun(run, maxBytes) {
7
18
  const MARK = "…[truncated: run blob exceeded the store cap]";
8
19
  const cut = (s) => s.slice(0, 4000) + MARK;
@@ -18,20 +29,24 @@ export function slimOversizeRun(run, maxBytes) {
18
29
  blob = JSON.stringify(step2);
19
30
  return Buffer.byteLength(blob) <= maxBytes ? blob : null;
20
31
  }
32
+ /** Dual-dialect durable `WorkflowRunStore`. See the file header for the dialect-delta ledger. */
21
33
  export class SqlWorkflowRunStore {
22
34
  db;
23
35
  constructor(db) {
24
36
  this.db = db;
25
37
  }
38
+ /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
26
39
  q(tidb, pg) {
27
40
  return this.db.dialect === "tidb" ? tidb : pg;
28
41
  }
29
42
  async put(id, run) {
30
- const stored = { ...run, id, rev: run.rev ?? 0 };
43
+ const stored = { ...run, id, rev: run.rev ?? 0 }; // key authoritative + observed-rev base (InMemory parity)
31
44
  try {
32
45
  await this.db.query(this.q("INSERT INTO workflow_run (id, scope, status, run, rev, created_at, ended_at) VALUES (?,?,?,?,?,?,?)", "INSERT INTO workflow_run (id, scope, status, run, rev, created_at, ended_at) VALUES ($1,$2,$3,$4,$5,$6,$7)"), [id, run.scope, run.status, JSON.stringify(stored), stored.rev, run.createdAt, run.endedAt ?? null]);
33
46
  }
34
47
  catch (e) {
48
+ // create-once error every backend throws (contract) — dup-key classification is dialect-specific:
49
+ // TiDB's errno-string ER_DUP_ENTRY vs PG's SQLSTATE 23505.
35
50
  const isDupKey = this.db.dialect === "tidb" ? e.code === "ER_DUP_ENTRY" : e.code === "23505";
36
51
  if (isDupKey) {
37
52
  throw new WorkflowRunStoreError("workflow_run.already_exists", "workflow run id already exists");
@@ -45,23 +60,32 @@ export class SqlWorkflowRunStore {
45
60
  if (!r)
46
61
  return null;
47
62
  const run = JSON.parse(String(r.run));
48
- run.rev = Number(r.rev);
63
+ run.rev = Number(r.rev); // the COLUMN is the OCC authority — overlay it (the blob's rev may predate a CAS bump)
49
64
  return run;
50
65
  }
51
66
  async update(id, scope, run, expect) {
67
+ // id/scope immutable in the stored payload (InMemory parity: key + partition are authoritative). The blob's
68
+ // rev is NOT trusted (see get's overlay) — write it as-is; the column bump below is the real OCC state.
52
69
  let blob = JSON.stringify({ ...run, id, scope });
53
70
  if (Buffer.byteLength(blob) > MAX_RUN_BLOB_BYTES) {
71
+ // Oversize degrade (1.108 review fix): slim the payload so the write — critically the TERMINAL one —
72
+ // still lands (a `false` here would never be retried with a smaller payload; see slimOversizeRun).
54
73
  const slim = slimOversizeRun({ ...run, id, scope }, MAX_RUN_BLOB_BYTES);
55
74
  if (slim === null)
56
- return false;
75
+ return false; // even the skeleton is oversize — keep the prior revision
57
76
  blob = slim;
58
77
  }
59
78
  const res = await this.db.query(this.q(`UPDATE workflow_run SET run = ?, rev = rev + 1, status = ?, ended_at = ? WHERE id = ? AND scope = ?${expect !== undefined ? " AND rev = ?" : ""}`, `UPDATE workflow_run SET run = $1, rev = rev + 1, status = $2, ended_at = $3 WHERE id = $4 AND scope = $5${expect !== undefined ? " AND rev = $6" : ""}`), expect !== undefined
60
79
  ? [blob, run.status, run.endedAt ?? null, id, scope, expect.rev]
61
80
  : [blob, run.status, run.endedAt ?? null, id, scope]);
62
- return res.affected === 1;
81
+ return res.affected === 1; // rev always bumps → affected is a faithful CAS verdict
63
82
  }
64
83
  async listByScope(scope, opts) {
84
+ // 🔴 genuinely-divergent CONSTRUCTION (not just SQL text): PG's `$n` placeholders are POSITIONAL and must
85
+ // track `params.length` as each optional clause is appended, while TiDB's `?` is positionless — a single
86
+ // `q()` pair can't express that, so the where/limit assembly forks per dialect. The FILTER logic itself
87
+ // (status → session → limit, session dropping unmatched/absent-key rows via JSON extraction, limit applied
88
+ // last) is identical; only the placeholder bookkeeping differs.
65
89
  const params = [scope];
66
90
  let where;
67
91
  let limit = "";
@@ -72,6 +96,9 @@ export class SqlWorkflowRunStore {
72
96
  params.push(opts.status);
73
97
  }
74
98
  if (opts?.session !== undefined) {
99
+ // core 1.368([1516]②)session 下推臂:limit 前应用、严格等值、无字段行剔除(JSON path 缺席 →
100
+ // NULL → `= ?` 恒假 ✓)。JSON 提取而非投影列:对存量行零迁移直接生效;行量有 retention 界,
101
+ // 与本函数固有的全行 JSON.parse 同量级。
75
102
  where += " AND JSON_UNQUOTE(JSON_EXTRACT(run, '$.originatingSessionId')) = ?";
76
103
  params.push(opts.session);
77
104
  }
@@ -87,6 +114,7 @@ export class SqlWorkflowRunStore {
87
114
  where += ` AND status = $${params.length}`;
88
115
  }
89
116
  if (opts?.session !== undefined) {
117
+ // core 1.368([1516]②)session 下推臂(TiDB twin 同案注释):JSON 提取、limit 前、缺席键 → NULL → 拒 ✓
90
118
  params.push(opts.session);
91
119
  where += ` AND run::json->>'originatingSessionId' = $${params.length}`;
92
120
  }
@@ -95,6 +123,7 @@ export class SqlWorkflowRunStore {
95
123
  limit = ` LIMIT $${params.length}`;
96
124
  }
97
125
  }
126
+ // Sort in SQL (newest first), summarize via core's SHARED projection (anti-drift).
98
127
  const { rows } = await this.db.query(`SELECT run, rev FROM workflow_run WHERE ${where} ORDER BY created_at DESC, id DESC${limit}`, params);
99
128
  return rows.map((r) => {
100
129
  const run = JSON.parse(String(r.run));
@@ -102,6 +131,11 @@ export class SqlWorkflowRunStore {
102
131
  return summarizeWorkflowRun(run);
103
132
  });
104
133
  }
134
+ /** Retention (the store never auto-purges; reap is explicit): the contract's `reap` is scope-keyed and the
135
+ * contract deliberately has NO cross-scope enumeration — but the SQL twin CAN enumerate (DISTINCT on the
136
+ * extracted scope column), which is exactly what a periodic deployment sweep needs. Walks every scope through
137
+ * the contract-faithful `reap` (terminal-only; running rows are the orphan-grace sweep's business). Service
138
+ * extension, not a contract member. Byte-identical text on both dialects (no params) — no q() needed. */
105
139
  async reapAllScopes(now, opts) {
106
140
  const { rows } = await this.db.query("SELECT DISTINCT scope FROM workflow_run");
107
141
  let total = 0;
@@ -111,7 +145,9 @@ export class SqlWorkflowRunStore {
111
145
  }
112
146
  async reap(scope, now, opts) {
113
147
  if (opts?.maxAgeMs === undefined && opts?.keep === undefined)
114
- return 0;
148
+ return 0; // retention is always explicit
149
+ // Cheap columns only; terminal-set + keep-N semantics computed in JS to match InMemory EXACTLY
150
+ // (isTerminalWorkflowStatus is core's — no status list duplicated into SQL).
115
151
  const { rows } = await this.db.query(this.q("SELECT id, status, created_at, ended_at FROM workflow_run WHERE scope = ? ORDER BY created_at DESC, id DESC", "SELECT id, status, created_at, ended_at FROM workflow_run WHERE scope = $1 ORDER BY created_at DESC, id DESC"), [scope]);
116
152
  const terminal = rows.filter((r) => isTerminalWorkflowStatus(String(r.status)));
117
153
  const doomed = [];
@@ -125,12 +161,16 @@ export class SqlWorkflowRunStore {
125
161
  }
126
162
  if (doomed.length === 0)
127
163
  return 0;
164
+ // Bulk delete-by-id-set: TiDB expands a dynamic `IN (?,?,…)` (one placeholder per id, spread params);
165
+ // PG binds the whole set as one array via `= ANY($2::text[])`. Same result set, different statement shape.
128
166
  const res = await this.db.query(this.q(`DELETE FROM workflow_run WHERE scope = ? AND id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_run WHERE scope = $1 AND id = ANY($2::text[])"), this.db.dialect === "tidb" ? [scope, ...doomed] : [scope, doomed]);
129
167
  return res.affected;
130
168
  }
131
169
  }
170
+ /** The SQL fence row kinds. `run_id = ''` on a purge fence (session-wide). */
132
171
  const KIND_SERVED = "served";
133
172
  const KIND_PURGE = "purge";
173
+ /** Dual-dialect `WorkflowCompletionInbox`. See the file header for the dialect-delta ledger. */
134
174
  export class SqlWorkflowCompletionInbox {
135
175
  db;
136
176
  onWarn;
@@ -147,26 +187,43 @@ export class SqlWorkflowCompletionInbox {
147
187
  async enqueue(entry) {
148
188
  const now = Date.now();
149
189
  await this.sweepFences(now);
190
+ // Served fence FIRST (per-runId — cross-replica: a poll served on ANOTHER replica fences us here).
150
191
  const served = await this.db.query(this.q("SELECT 1 FROM workflow_inbox_fence WHERE session_id = ? AND kind = ? AND run_id IN (?, ?) AND until_ms >= ?", "SELECT 1 FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id IN ($3, $4) AND until_ms >= $5"), [entry.sessionId, KIND_SERVED, entry.runId, foldKeyFamily(entry.runId), now]);
151
192
  if (served.rows.length > 0) {
152
193
  this.onWarn?.("workflow_completion_enqueue_after_poll_served", { sessionId: entry.sessionId, runId: entry.runId });
153
194
  return;
154
195
  }
196
+ // Purge fence: owner-scoped exactly like the File impl (owner_known=0 → conservative fence-all;
197
+ // owner_known=1 → fences only the SAME owner; a different owner = a new legitimate claimant, passes).
155
198
  const purge = await this.db.query(this.q("SELECT owner, owner_known FROM workflow_inbox_fence WHERE session_id = ? AND kind = ? AND run_id = '' AND until_ms >= ?", "SELECT owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id = '' AND until_ms >= $3"), [entry.sessionId, KIND_PURGE, now]);
156
199
  const pf = purge.rows[0];
157
200
  if (pf && (Number(pf.owner_known) === 0 || (pf.owner === null ? entry.owner === null : String(pf.owner) === entry.owner))) {
158
201
  this.onWarn?.("workflow_completion_enqueue_after_purge", { sessionId: entry.sessionId, runId: entry.runId });
159
202
  return;
160
203
  }
204
+ // Atomic move-to-tail (1.108 review fix — the prior DELETE-then-INSERT had a crash window that dropped the
205
+ // old entry; self-healing via the notify journal, but atomic is free here). Genuinely divergent statement:
206
+ // TiDB `REPLACE` = delete-conflicting-row + insert atomically, and omitting `seq` mints a fresh
207
+ // AUTO_INCREMENT = tail. PG has no REPLACE, so the twin is an upsert that explicitly re-mints `seq` off the
208
+ // BIGSERIAL's own sequence — same outcome (idempotent-on-runId + move-to-tail in ONE statement).
161
209
  await this.db.query(this.q("REPLACE INTO workflow_completion_inbox (session_id, run_id, owner, status, summary, enqueued_at, kind, payload) VALUES (?,?,?,?,?,?,?,?)", "INSERT INTO workflow_completion_inbox (session_id, run_id, owner, status, summary, enqueued_at, kind, payload) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) " +
162
210
  "ON CONFLICT (session_id, run_id) DO UPDATE SET seq = nextval(pg_get_serial_sequence('workflow_completion_inbox','seq')), " +
163
211
  "owner = EXCLUDED.owner, status = EXCLUDED.status, summary = EXCLUDED.summary, enqueued_at = EXCLUDED.enqueued_at, kind = EXCLUDED.kind, payload = EXCLUDED.payload"), [entry.sessionId, entry.runId, entry.owner, entry.status, entry.summary, entry.enqueuedAt, entry.kind ?? null, entry.payload ?? null]);
212
+ // 1.108 review (lens① MAJOR ×2): the File impl's check-then-set is atomic on one event loop; here the
213
+ // fence check and the INSERT are two network statements, so a fence written IN BETWEEN (a poll served on
214
+ // another replica, or a racing purge) would leave fence + pending row coexisting → the next drain re-pushes
215
+ // an already-served/purged completion. Close the window by RE-CHECKING after the insert and self-revoking —
216
+ // ordering guarantees the loser is always visible to someone: markTerminalServed/purge write their fence
217
+ // BEFORE their delete, so either their delete kills our row, or our re-check sees their fence.
218
+ // (Residual, documented: a purge racing a NEW claimant's enqueue between fence-write and session-DELETE can
219
+ // still drop that one push — ms window + sessionId reclaim in it is contrived; the WorkflowStatus poll floor
220
+ // + notify-journal recovery cover it. File parity: purge drops the whole session queue there too.)
164
221
  const confirm = await this.db.query(this.q("SELECT kind, owner, owner_known FROM workflow_inbox_fence WHERE session_id = ? AND until_ms >= ? AND (run_id IN (?, ?) OR (kind = ? AND run_id = ''))", "SELECT kind, owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND until_ms >= $2 AND (run_id IN ($3, $4) OR (kind = $5 AND run_id = ''))"), [entry.sessionId, now, entry.runId, foldKeyFamily(entry.runId), KIND_PURGE]);
165
222
  for (const f of confirm.rows) {
166
223
  const isServed = String(f.kind) === KIND_SERVED;
167
224
  const purgeFencesUs = !isServed && (Number(f.owner_known) === 0 || (f.owner === null ? entry.owner === null : String(f.owner) === entry.owner));
168
225
  if (isServed || purgeFencesUs) {
169
- await this.ack(entry.sessionId, entry.runId);
226
+ await this.ack(entry.sessionId, entry.runId); // self-revoke: the fence won the race
170
227
  this.onWarn?.(isServed ? "workflow_completion_enqueue_after_poll_served" : "workflow_completion_enqueue_after_purge", {
171
228
  sessionId: entry.sessionId,
172
229
  runId: entry.runId,
@@ -174,14 +231,21 @@ export class SqlWorkflowCompletionInbox {
174
231
  return;
175
232
  }
176
233
  }
234
+ // Cap: drop-oldest beyond MAX_PENDING_PER_SESSION (warn per drop, File parity). TiDB requires a LIMIT
235
+ // alongside OFFSET (MySQL-protocol syntax); PG's OFFSET stands alone.
177
236
  const over = await this.db.query(this.q("SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = ? ORDER BY seq DESC LIMIT 100000 OFFSET ?", "SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = $1 ORDER BY seq DESC OFFSET $2"), [entry.sessionId, MAX_PENDING_PER_SESSION]);
178
237
  if (over.rows.length > 0) {
238
+ // Bulk delete-by-seq-set: dynamic `IN (?,?,…)` on TiDB vs array-bound `= ANY($2::bigint[])` on PG.
179
239
  await this.db.query(this.q(`DELETE FROM workflow_completion_inbox WHERE session_id = ? AND seq IN (${over.rows.map(() => "?").join(",")})`, "DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND seq = ANY($2::bigint[])"), this.db.dialect === "tidb" ? [entry.sessionId, ...over.rows.map((r) => r.seq)] : [entry.sessionId, over.rows.map((r) => r.seq)]);
180
240
  for (const r of over.rows)
181
241
  this.onWarn?.("workflow_completion_inbox_overflow", { sessionId: entry.sessionId, droppedRunId: String(r.run_id) });
182
242
  }
183
243
  }
184
244
  async pending(sessionId) {
245
+ // 2026-07-11(同一缺陷的 SQL 半场):filter SERVED-fenced rows like the in-memory
246
+ // inbox does — markTerminalServed's fence-INSERT and ack-DELETE are two statements, and a drain on
247
+ // another replica between them re-emitted an already-served completion (the in-memory pending()
248
+ // filters by servedAt; this brings the SQL twin to the same semantics).
185
249
  const { rows } = await this.db.query(this.q("SELECT i.run_id, i.owner, i.status, i.summary, i.enqueued_at, i.kind, i.payload FROM workflow_completion_inbox i " +
186
250
  "WHERE i.session_id = ? AND NOT EXISTS (SELECT 1 FROM workflow_inbox_fence f WHERE f.session_id = i.session_id AND f.kind = ? AND f.run_id IN (i.run_id, REGEXP_REPLACE(i.run_id, '#[0-9]+$', '')) AND f.until_ms >= ?) " +
187
251
  "ORDER BY i.seq ASC", "SELECT i.run_id, i.owner, i.status, i.summary, i.enqueued_at, i.kind, i.payload FROM workflow_completion_inbox i " +
@@ -204,6 +268,7 @@ export class SqlWorkflowCompletionInbox {
204
268
  async purge(sessionId, purgedOwner) {
205
269
  const now = Date.now();
206
270
  await this.sweepFences(now);
271
+ // Fence FIRST (an enqueue racing the purge must lose), then clear. Upsert = a re-purge refreshes the window.
207
272
  await this.db.query(this.q("INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES (?,?,?,?,?,?) " +
208
273
  "ON DUPLICATE KEY UPDATE owner = VALUES(owner), owner_known = VALUES(owner_known), until_ms = VALUES(until_ms)", "INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,'',$3,$4,$5) " +
209
274
  "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET owner = EXCLUDED.owner, owner_known = EXCLUDED.owner_known, until_ms = EXCLUDED.until_ms"), this.db.dialect === "tidb"
@@ -218,9 +283,15 @@ export class SqlWorkflowCompletionInbox {
218
283
  "ON DUPLICATE KEY UPDATE until_ms = VALUES(until_ms)", "INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,$3,NULL,0,$4) " +
219
284
  "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET until_ms = EXCLUDED.until_ms"), [sessionId, KIND_SERVED, runId, now + SERVED_FENCE_MS]);
220
285
  await this.ack(sessionId, runId);
286
+ // design/144 家族 ack(与 in-memory 语义对齐):fleet 帧无 seq 传家族前缀,park 行是 seq 键——
287
+ // 精确 DELETE 必 miss。剥行键尾部 #digits 与传入前缀比对(REGEXP_REPLACE/regexp_replace 语义=foldKeyFamily)。
221
288
  await this.db.query(this.q("DELETE FROM workflow_completion_inbox WHERE session_id = ? AND run_id <> ? AND REGEXP_REPLACE(run_id, '#[0-9]+$', '') = ?", "DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND run_id <> $2 AND regexp_replace(run_id, '#[0-9]+$', '') = $3"), [sessionId, runId, runId]);
222
289
  }
223
290
  }
291
+ /**
292
+ * Dual-dialect `WorkflowNotifyJournalStore` — the crash-safe recovery backlog for at-least-once completion
293
+ * notify. See the file header for the dialect-delta ledger.
294
+ */
224
295
  export class SqlWorkflowNotifyJournalStore {
225
296
  db;
226
297
  constructor(db) {
@@ -230,6 +301,8 @@ export class SqlWorkflowNotifyJournalStore {
230
301
  return this.db.dialect === "tidb" ? tidb : pg;
231
302
  }
232
303
  async record(input) {
304
+ // Idempotent-on-runId (a second record for the same run — incl. an acked one — is a no-op): TiDB
305
+ // `INSERT IGNORE` vs PG `ON CONFLICT (run_id) DO NOTHING`.
233
306
  await this.db.query(this.q("INSERT IGNORE INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at) VALUES (?,?,0,?,?,?)", "INSERT INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at) VALUES ($1,$2,0,$3,$4,$5) ON CONFLICT (run_id) DO NOTHING"), [input.runId, input.scope, input.sourceTaskId ?? null, input.principal ?? null, input.createdAt]);
234
307
  }
235
308
  async ack(runId, ackedAt) {
@@ -246,6 +319,9 @@ export class SqlWorkflowNotifyJournalStore {
246
319
  createdAt: Number(r.created_at),
247
320
  }));
248
321
  }
322
+ /** Retention (same sweep as reapAllScopes): ACKED rows are pure history — without this the twin re-opens
323
+ * the unbounded-growth hole the same release closed for workflow_run. Pending rows are NEVER reaped (they
324
+ * are the recovery backlog; the orphan-grace sweep is what retires a stuck pending run). */
249
325
  async reapAcked(before) {
250
326
  const res = await this.db.query(this.q("DELETE FROM workflow_notify_journal WHERE acked = 1 AND acked_at < ?", "DELETE FROM workflow_notify_journal WHERE acked = 1 AND acked_at < $1"), [before]);
251
327
  return res.affected;
@@ -266,6 +342,7 @@ export class SqlWorkflowNotifyJournalStore {
266
342
  };
267
343
  }
268
344
  }
345
+ /** MySQL-protocol (TiDB) bindings — historical class names + ctor shapes preserved. */
269
346
  export class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
270
347
  constructor(pool) {
271
348
  super(mysqlDriver(pool));
@@ -281,6 +358,7 @@ export class TiDBWorkflowNotifyJournalStore extends SqlWorkflowNotifyJournalStor
281
358
  super(mysqlDriver(pool));
282
359
  }
283
360
  }
361
+ /** PostgreSQL bindings — historical class names + ctor shapes preserved. */
284
362
  export class PgWorkflowRunStore extends SqlWorkflowRunStore {
285
363
  constructor(pool) {
286
364
  super(pgDriver(pool));
@@ -1,16 +1,61 @@
1
1
  import { type ExecutionEnv, type ExecutionEnvFactory } from "@sema-agent/core";
2
+ /** Structured logger surface this wrapper uses (matches the service `logger`; all calls optional-chained). */
2
3
  export interface WorktreeIsolationLogger {
3
4
  info?(event: string, fields?: Record<string, unknown>): void;
4
5
  warn?(event: string, fields?: Record<string, unknown>): void;
5
6
  }
7
+ /** Options for {@link withWorktreeIsolation}. ALL fields are operator-supplied (config/closure) — NONE come
8
+ * from a `TaskSpec` or the factory `ctx` (that is the trust boundary; see the file header). */
6
9
  export interface WorktreeIsolationOptions {
10
+ /**
11
+ * The operator-trusted base repository root — where `git worktree add/remove/prune` run (the base env's cwd
12
+ * for the git command). MUST be an absolute path to a git repo. This is a CLOSURE value (config), never a
13
+ * task input, so an untrusted caller can never redirect worktree creation to another path.
14
+ */
7
15
  repoRoot: string;
16
+ /**
17
+ * Operator-trusted allowlist of permitted repo roots. {@link repoRoot} MUST be contained within (or equal
18
+ * to) one of these — checked at wrap time, fails LOUD otherwise. Defence in depth against a mis-wired
19
+ * deployment (e.g. a `REPO_ROOT` env accidentally pointed outside the intended tree). Default: `[repoRoot]`
20
+ * (repoRoot trivially permits itself) — pass a broader set only when the operator intends multiple roots.
21
+ */
8
22
  allowedRoots?: string[];
23
+ /**
24
+ * The SHARED base {@link ExecutionEnv} used to run the git worktree commands (`add`/`remove`/`prune`). Its
25
+ * own cwd is irrelevant (core passes `cwd: repoRoot` explicitly on every git call) — it only needs a working
26
+ * `exec` with `git` on PATH. For the host lane this is a host env; we keep it as a single long-lived env per
27
+ * deployment (git ops are cheap and serial-safe). The reaper reuses it for {@link pruneWorktrees}.
28
+ */
9
29
  baseEnvForGit: ExecutionEnv;
30
+ /**
31
+ * Env-SPECIFIC constructor of a worktree-ROOTED {@link ExecutionEnv} (the only lane-specific bit). Host lane:
32
+ * `(dir) => new RemoteHostExecutionEnv({ workspaceDir: dir })` — a host env whose fs/shell operate relative
33
+ * to `dir` AND whose own `destroy()` does NOT delete `dir` (persistent-dir mode), so core's destroy wrapper
34
+ * owns removal via `git worktree remove`. A single-E2B/k8s deployment would pass a remote env rooted at the
35
+ * worktree dir (follow-on — see file header re: suspend incompatibility on the host lane).
36
+ */
10
37
  rootEnvAt: (worktreeDir: string) => ExecutionEnv | Promise<ExecutionEnv>;
38
+ /** Base commit/ref for every worktree (default core's `HEAD` at creation time). Operator-supplied. */
11
39
  commit?: string;
12
40
  logger?: WorktreeIsolationLogger;
13
41
  }
42
+ /**
43
+ * Wrap a deployment's `executionEnvFactory` so an agent with `ctx.isolation === "worktree"` runs in its own
44
+ * detached git worktree. See the file header for the full contract + trust gate + caveats.
45
+ *
46
+ * Throws at WRAP time (fail-loud, not faked) if `repoRoot` is not contained within `allowedRoots` — a
47
+ * mis-wired deployment must not silently git-worktree an arbitrary path.
48
+ */
14
49
  export declare function withWorktreeIsolation(baseFactory: ExecutionEnvFactory, opts: WorktreeIsolationOptions): ExecutionEnvFactory;
50
+ /**
51
+ * Best-effort reaper hook: deregister worktrees orphaned by a process crash (the Runner never reached
52
+ * `destroy`). Core's {@link pruneWorktrees} runs `git worktree prune` against the base repo — it cleans up
53
+ * registrations whose worktree dirs are already gone. Wired into the service reaper (userland; core ships no
54
+ * post-kill Runner hook). NEVER throws (a prune failure is logged, never fatal).
55
+ *
56
+ * NOTE: `git worktree prune` only deregisters worktrees whose DIRECTORY is missing. A crashed run that left
57
+ * the dir intact is NOT reaped by prune alone — that is acceptable for v1 (the worktree lives under the known
58
+ * `.sema-worktrees/` parent and is human-/script-reapable; a stricter age-based sweep is a follow-on).
59
+ */
15
60
  export declare function reapOrphanWorktrees(baseEnvForGit: ExecutionEnv, repoRoot: string, logger?: WorktreeIsolationLogger): Promise<void>;
16
61
  //# sourceMappingURL=worktree-isolation.d.ts.map