@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
@@ -3,21 +3,27 @@ import { escapeLike } from "./sql-escape.js";
3
3
  import { pgSafeJsonStringify, pgSanitizeText } from "./pg-safe-json.js";
4
4
  import { parseJsonLenient as parseJson, toIso as iso } from "./sql-row-helpers.js";
5
5
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
6
+ /** Dual-dialect async run registry + event log. See the file header for the dialect-delta ledger. */
6
7
  export class SqlRunStore {
7
8
  db;
8
9
  constructor(db) {
9
10
  this.db = db;
10
11
  }
12
+ /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
11
13
  q(tidb, pg) {
12
14
  return this.db.dialect === "tidb" ? tidb : pg;
13
15
  }
16
+ /** mysql2 names the dup-key `ER_DUP_ENTRY`; PG raises SQLSTATE `23505`. */
14
17
  isDupKey(err) {
15
18
  const code = err?.code;
16
19
  return Boolean(err) && (this.db.dialect === "tidb" ? code === "ER_DUP_ENTRY" : code === "23505");
17
20
  }
21
+ /** JSON column binding: TiDB stores the string verbatim; PG goes through the protocol-safe envelope
22
+ * (a raw NUL byte in an event payload would otherwise throw 22P05 mid-write). */
18
23
  json(value) {
19
24
  return this.db.dialect === "tidb" ? JSON.stringify(value) : pgSafeJsonStringify(value);
20
25
  }
26
+ /** Run statements atomically; rolls back on failure. */
21
27
  async tx(fn) {
22
28
  const conn = await this.db.connect();
23
29
  try {
@@ -30,6 +36,7 @@ export class SqlRunStore {
30
36
  await conn.rollback();
31
37
  }
32
38
  catch {
39
+ /* ignore */
33
40
  }
34
41
  throw err;
35
42
  }
@@ -37,6 +44,14 @@ export class SqlRunStore {
37
44
  conn.release();
38
45
  }
39
46
  }
47
+ /**
48
+ * Create a run, but only if the session has no other active run (double-text guard).
49
+ *
50
+ * Mutual exclusion is a unique-key claim on `task_active(session_id)` — NOT a SELECT…FOR UPDATE,
51
+ * which on TiDB (no gap locks) would fail to serialize two concurrent createRuns when the session
52
+ * has no prior row. A plain INSERT either claims the session or throws a duplicate-key error (TiDB
53
+ * does NOT report a reliable affectedRows for `ON DUPLICATE KEY UPDATE …=…`, so we catch dup-key).
54
+ */
40
55
  async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
41
56
  const conn = await this.db.connect();
42
57
  try {
@@ -62,6 +77,8 @@ export class SqlRunStore {
62
77
  instanceId,
63
78
  meta.jobId ?? null,
64
79
  meta.source ?? null,
80
+ // PG-only (R12 同族): objective 预览=模型可控标量,走 pgSanitizeText(NUL/protocol-byte 防护)。
81
+ // TiDB 存 verbatim(无该字节类)。
65
82
  this.db.dialect === "tidb"
66
83
  ? meta.objectivePreview ?? null
67
84
  : meta.objectivePreview == null
@@ -78,6 +95,7 @@ export class SqlRunStore {
78
95
  await conn.rollback();
79
96
  }
80
97
  catch {
98
+ /* ignore */
81
99
  }
82
100
  throw err;
83
101
  }
@@ -85,39 +103,79 @@ export class SqlRunStore {
85
103
  conn.release();
86
104
  }
87
105
  }
106
+ /** Request cancel of a RUNNING run (durable, cross-replica). Sets the flag the owning instance's heartbeat
107
+ * tick polls; a same-replica caller additionally aborts the in-flight controller for instant effect. Only
108
+ * flags a `running` row (terminal/suspended → 0 affected = no-op). Returns whether a running row was flagged.
109
+ *
110
+ * `owner` is a single-DB-fleet defense-in-depth guard: the WHERE pins the row to the authenticated owner
111
+ * (null-safe compare so a legacy null-owner run matches owner=null), so even if the HTTP owner-gate has a bug
112
+ * a caller can never flip another tenant's run. Pass the run's owner (from the looked-up RunRecord). */
88
113
  async requestCancel(taskId, owner) {
89
114
  const { affected } = await this.db.query(this.q("UPDATE task_run SET cancel_requested = 1 WHERE task_id = ? AND owner <=> ? AND status = 'running'", "UPDATE task_run SET cancel_requested = 1 WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2 AND status = 'running'"), [taskId, owner]);
90
115
  return affected > 0;
91
116
  }
117
+ /** Whether a cancel has been requested for this run (polled by the owning instance's heartbeat tick).
118
+ * `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}). */
92
119
  async isCancelRequested(taskId, owner) {
93
120
  const { rows } = await this.db.query(this.q("SELECT cancel_requested FROM task_run WHERE task_id = ? AND owner <=> ?", "SELECT cancel_requested FROM task_run WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2"), [taskId, owner]);
94
121
  return Boolean(rows[0]?.cancel_requested);
95
122
  }
123
+ /** design/80 seam #2: request PREEMPT (graceful durable yield, ≠ cancel's kill) of a RUNNING run — durable +
124
+ * cross-replica. Sets the flag the owning instance's heartbeat tick polls (it then aborts its preemptSignal →
125
+ * the task suspends at the next clean turn boundary, gate resource_limit/reason preempt); a same-replica caller
126
+ * additionally aborts the in-flight preempt controller for instant effect. Only flags a `running` row
127
+ * (terminal/suspended → 0 affected = no-op). Returns whether a running row was flagged.
128
+ * `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}). */
96
129
  async requestPreempt(taskId, owner) {
97
130
  const { affected } = await this.db.query(this.q("UPDATE task_run SET preempt_requested = 1 WHERE task_id = ? AND owner <=> ? AND status = 'running'", "UPDATE task_run SET preempt_requested = 1 WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2 AND status = 'running'"), [taskId, owner]);
98
131
  return affected > 0;
99
132
  }
133
+ /** Whether a preempt has been requested for this run (polled by the owning instance's heartbeat tick).
134
+ * `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}). */
100
135
  async isPreemptRequested(taskId, owner) {
101
136
  const { rows } = await this.db.query(this.q("SELECT preempt_requested FROM task_run WHERE task_id = ? AND owner <=> ?", "SELECT preempt_requested FROM task_run WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2"), [taskId, owner]);
102
137
  return Boolean(rows[0]?.preempt_requested);
103
138
  }
139
+ /** Liveness heartbeat: refresh updated_at for a still-running run, independent of events. The
140
+ * running instance calls this on a timer so a long-but-alive turn (e.g. a subagent that emits no
141
+ * parent events for minutes) is not mistaken for a dead instance.
142
+ * `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}); the heartbeat is
143
+ * always issued by the owning instance with the run's own owner, so this only narrows, never blocks. */
104
144
  async heartbeat(taskId, owner) {
105
145
  await this.db.query(this.q("UPDATE task_run SET updated_at = ? WHERE task_id = ? AND owner <=> ? AND status = 'running'", "UPDATE task_run SET updated_at = $1 WHERE task_id = $2 AND owner IS NOT DISTINCT FROM $3 AND status = 'running'"), [new Date(), taskId, owner]);
106
146
  }
147
+ /** Append one event. A single INSERT — `updated_at` (liveness) is owned by `heartbeat`, so there
148
+ * is no second statement to keep atomic here. */
107
149
  async appendEvent(taskId, seq, type, data) {
108
150
  await this.db.query(this.q("INSERT INTO task_event (task_id, seq, type, data, ts) VALUES (?,?,?,?,?)", "INSERT INTO task_event (task_id, seq, type, data, ts) VALUES ($1,$2,$3,$4::jsonb,$5)"), [taskId, seq, type, data == null ? null : this.json(data), new Date()]);
109
151
  }
152
+ /** Highest event seq for a task (0 if none). Lets a resumed leg CONTINUE the durable event log past the
153
+ * suspend (core 1.70 `resumeStream`) — the suspend wrote events 1..K, the resume appends from K+1 without
154
+ * colliding on the (task_id, seq) PK. */
110
155
  async maxSeq(taskId) {
111
156
  const { rows } = await this.db.query(this.q("SELECT MAX(seq) AS m FROM task_event WHERE task_id = ?", "SELECT MAX(seq) AS m FROM task_event WHERE task_id = $1"), [taskId]);
112
157
  const m = rows[0]?.m;
113
158
  return m == null ? 0 : Number(m);
114
159
  }
160
+ /** Mark terminal AND release the single-active-run claim atomically — if the DELETE were a separate
161
+ * statement that failed, the session would stay permanently locked. [888] FIRST terminal writer wins:
162
+ * the UPDATE is CAS'd POSITIVELY on the live statuses (running/suspended/needs_review) — every call site
163
+ * is a settle-once flow whose backstops may double-fire (cancel × leg-throw × reaper), and an
164
+ * unconditional write let the LAST racer overwrite errorCode (a `cancelled` row flipping to a generic
165
+ * abort text, or vice versa). Positive form ([1.207 codex M1]): the negative NOT IN missed the
166
+ * blocked/timeout terminals — a first write of those could still be overwritten. The claim DELETE stays
167
+ * unconditional (idempotent; the first writer already released it). */
115
168
  async setTerminal(taskId, status, result, error) {
169
+ // Denormalize the structured code into its own column so operators can `WHERE error_code LIKE
170
+ // 'budget.%'` without parsing every result JSON. Source of truth stays the result blob.
116
171
  const errorCode = result?.errorCode ?? null;
117
172
  await this.tx(async (conn) => {
118
173
  await conn.query(this.q("UPDATE task_run SET status = ?, result = ?, error = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND status IN ('running','suspended','needs_review')", "UPDATE task_run SET status = $1, result = $2::jsonb, error = $3, error_code = $4, updated_at = $5 WHERE task_id = $6 AND status IN ('running','suspended','needs_review')"), [
119
174
  status,
120
175
  result == null ? null : this.json(result),
176
+ // codex R12 (PG only): error/errorCode = 裸 TEXT 参数 — 真 NUL 直炸 22P05,UPDATE 抛 → 事务回滚 →
177
+ // task_active 保持 → run 卡非终态到 reaper(错误文案带脏字节的形:tool 报错内嵌 raw 输出)。观测标量
178
+ // 同族有损 sanitize(与 result jsonb 同臂同纪律)。TiDB 存 verbatim。
121
179
  this.db.dialect === "tidb" ? error : error == null ? null : pgSanitizeText(error),
122
180
  this.db.dialect === "tidb" ? errorCode : errorCode == null ? null : pgSanitizeText(errorCode),
123
181
  new Date(),
@@ -126,20 +184,64 @@ export class SqlRunStore {
126
184
  await conn.query(this.q("DELETE FROM task_active WHERE task_id = ?", "DELETE FROM task_active WHERE task_id = $1"), [taskId]);
127
185
  });
128
186
  }
187
+ /**
188
+ * Durable F4 (design/45): a task suspended on a policy `ask`. Sets the run NON-terminal `suspended` and
189
+ * KEEPS the `task_active` claim — the session stays locked (no new run) until an operator resumes it to a
190
+ * terminal state or the reaper expires it. The token-bearing result is NOT stored (the submitter polling
191
+ * this row must never receive the capability token); only the status flips.
192
+ */
129
193
  async setSuspended(taskId) {
194
+ // CAS on `running` (idempotent on an already-suspended row): without it, a heartbeat-stall race where
195
+ // `reapStale` already flipped the row to `failed` and DELETED its `task_active` claim would be reverted
196
+ // here to `suspended` WITHOUT re-claiming `task_active` — leaving a suspended run with no session lock, so
197
+ // a new run could start on the same session while the checkpoint is still pending. The CAS makes the
198
+ // reaped→suspended revert a no-op, so the reaper's decision stands and the invariant holds.
130
199
  await this.db.query(this.q("UPDATE task_run SET status = 'suspended', updated_at = ? WHERE task_id = ? AND status IN ('running','suspended')", "UPDATE task_run SET status = 'suspended', updated_at = $1 WHERE task_id = $2 AND status IN ('running','suspended')"), [new Date(), taskId]);
131
200
  }
201
+ /**
202
+ * design/80 D-B: PARK a run that paused on a REVIEW gate — a `needs_review` result (a dry-run diff review OR a
203
+ * pre-action `plan_review`; core assemble-result slot 8.6). Like {@link setSuspended} this is a NON-terminal park
204
+ * that KEEPS the `task_active` session claim (it does NOT delete it — only {@link setTerminal} does), so the
205
+ * resume path (`getActiveTaskId` → `markResuming`) can find + claim the row. core treats a review pause "like a
206
+ * suspend, an INTENTIONAL durable pause" (design/76 §2.5) — the service MUST park it the same way, else the run is
207
+ * terminalized + its lock released and the plan_review/dry_run_review resume can never run (it gets
208
+ * taskId=undefined and drives the model unprotected on an unlocked session). CAS on `running`/`needs_review`
209
+ * (reaper-revert guard, mirrors setSuspended). 🔴 The store SQL here is exercised by mocked unit tests only — a
210
+ * real-TiDB integration test of the park→resume→claim invariant is a required-before-GA TODO (the lesson from the
211
+ * P2 lease-SQL + this needs_review-park bug: mocked tests hide store-SQL defects).
212
+ */
132
213
  async setNeedsReview(taskId) {
133
214
  await this.db.query(this.q("UPDATE task_run SET status = 'needs_review', updated_at = ? WHERE task_id = ? AND status IN ('running','needs_review')", "UPDATE task_run SET status = 'needs_review', updated_at = $1 WHERE task_id = $2 AND status IN ('running','needs_review')"), [new Date(), taskId]);
134
215
  }
216
+ /**
217
+ * Durable F4 resume: a suspended run is being actively resumed. Flip it back to `running` (CAS — only if
218
+ * still suspended) and refresh liveness, so the in-flight resume is protected by the running-run heartbeat
219
+ * and is NOT killed by `reapSuspended` (which targets `status='suspended'` past the approval TTL). Without
220
+ * this the row sits at `suspended`/updated_at=suspend-time for the WHOLE resume execution, so a resume that
221
+ * crosses the TTL is marked `failed` mid-run and its session lock (`task_active`) is released. Returns true
222
+ * if it claimed the flip (false ⇒ the reaper already expired it, or it isn't suspended).
223
+ */
135
224
  async markResuming(taskId) {
136
- const { affected } = await this.db.query(this.q("UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, updated_at = ? WHERE task_id = ? AND status IN ('suspended','needs_review')", "UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, updated_at = $1 WHERE task_id = $2 AND status IN ('suspended','needs_review')"), [new Date(), taskId]);
225
+ const { affected } = await this.db.query(
226
+ // cancel_requested is RESET on resume: a cancel that raced the suspend (flag set but never honored —
227
+ // the run parked first) must not silently kill an operator-APPROVED resume later. Cancelling a suspended
228
+ // run is the deny-approval path; a cancel of the resumed leg sets the flag afresh. preempt_requested is
229
+ // RESET for the same reason (design/80 seam #2): a preempt flag that survived into the resumed leg would
230
+ // make the heartbeat re-preempt it immediately (you resume to make PROGRESS) — the durable twin of core's
231
+ // pre-aborted-signal strip; a genuine re-preempt of the resumed leg sets the flag afresh.
232
+ // design/80 D-B: a `needs_review` (plan_review / dry_run_review) park is resumable too — the CAS accepts it
233
+ // alongside `suspended` so the plan_review resume can claim the parked row. The flag-reset rationale is
234
+ // identical for both park kinds.
235
+ this.q("UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, updated_at = ? WHERE task_id = ? AND status IN ('suspended','needs_review')", "UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, updated_at = $1 WHERE task_id = $2 AND status IN ('suspended','needs_review')"), [new Date(), taskId]);
137
236
  return affected === 1;
138
237
  }
238
+ /** The active run's taskId for a session (the `task_active` claim) — lets resume find the suspended run row. */
139
239
  async getActiveTaskId(sessionId) {
140
240
  const { rows } = await this.db.query(this.q("SELECT task_id FROM task_active WHERE session_id = ?", "SELECT task_id FROM task_active WHERE session_id = $1"), [sessionId]);
141
241
  return rows[0] ? String(rows[0].task_id) : undefined;
142
242
  }
243
+ /** Shared row→record mapper — byte-identical between dialects (both drivers hand back the same column
244
+ * names + comparable JS types after SqlDriver's result normalization). */
143
245
  rowToRun(r) {
144
246
  return {
145
247
  taskId: String(r.task_id),
@@ -161,7 +263,18 @@ export class SqlRunStore {
161
263
  const { rows } = await this.db.query(this.q("SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, instance_id, objective_preview, created_at, updated_at FROM task_run WHERE task_id = ?", "SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, instance_id, objective_preview, created_at, updated_at FROM task_run WHERE task_id = $1"), [taskId]);
162
264
  return rows[0] ? this.rowToRun(rows[0]) : undefined;
163
265
  }
266
+ /** Task Trace list (S1): runs newest-first, keyset-paginated on (created_at, task_id). Optional status +
267
+ * jobId filters (jobId = the work-view group, served by idx_job). Caller passes `limit+1` to detect a next
268
+ * page. Cursor fields are bound as params (no SQL injection).
269
+ *
270
+ * 🔴 Genuinely-divergent BUILDER (not just SQL text): TiDB's `?` is position-agnostic, so the WHERE array is
271
+ * built with plain string pushes in call order. PG's `$n` needs a NUMBERED placeholder per param, so that arm
272
+ * keeps its own positional-counter closure (`p()`) — collapsing the two into one shared counter would hide
273
+ * the very placeholder-numbering divergence this file exists to keep visible. */
164
274
  async listRuns(opts) {
275
+ // 夜测 N1(2026-07-09,LOW):a non-date cursor.createdAt made the four backends diverge (PG threw a raw
276
+ // DB error; the other three returned an empty page). HTTP's decodeCursor pre-validates, so this is
277
+ // defence-in-depth for direct store callers — normalize ALL backends to the empty page.
165
278
  if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
166
279
  return [];
167
280
  if (this.db.dialect === "tidb") {
@@ -180,11 +293,11 @@ export class SqlRunStore {
180
293
  params.push(opts.source);
181
294
  }
182
295
  if (opts.owner) {
183
- where.push("owner = ?");
296
+ where.push("owner = ?"); // idx_owner — the per-user list view (portal scopes a normal user to their own)
184
297
  params.push(opts.owner);
185
298
  }
186
299
  if (opts.cursor) {
187
- where.push("(created_at < ? OR (created_at = ? AND task_id < ?))");
300
+ where.push("(created_at < ? OR (created_at = ? AND task_id < ?))"); // keyset: strictly older, ties by task_id
188
301
  params.push(new Date(opts.cursor.createdAt), new Date(opts.cursor.createdAt), opts.cursor.taskId);
189
302
  }
190
303
  const sql = "SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, objective_preview, created_at, updated_at FROM task_run" +
@@ -220,6 +333,12 @@ export class SqlRunStore {
220
333
  const { rows } = await this.db.query(sql, params);
221
334
  return rows.map((r) => this.rowToRun(r));
222
335
  }
336
+ /** GET /v1/sessions (CC /resume picker): DISTINCT sessions newest-first by last activity, keyset-paginated on
337
+ * (last_activity, session_id). Aggregates task_run by session_id — one window pass picks the latest run per
338
+ * session (rn=1) for the preview/status and rolls up first/last/count. Owner-scoped when set (the per-user view).
339
+ * Bounded by per-tenant data today; a denormalized session table is the optimization if it ever goes hot.
340
+ *
341
+ * 🔴 Same builder divergence as {@link listRuns} — see its header note. */
223
342
  async listSessions(opts) {
224
343
  if (this.db.dialect === "tidb") {
225
344
  const params = [];
@@ -230,15 +349,18 @@ export class SqlRunStore {
230
349
  }
231
350
  const outer = ["rn = 1"];
232
351
  if (opts.q) {
352
+ // `?q=` — ANY-run preview match, session-granular (an outer LIKE on the rn=1 row alone would
353
+ // miss sessions whose match lives in an older run). LOWER-LIKE = collation-proof ci; escapeLike pins wildcards.
354
+ // Owner/sql_mode rationale: see tidb-session-store.ts listSessions.
233
355
  outer.push("EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = t.session_id AND LOWER(tr.objective_preview) LIKE LOWER(?) ESCAPE '\\\\')");
234
356
  params.push(`%${escapeLike(opts.q)}%`);
235
357
  }
236
358
  if (opts.cursor) {
237
- outer.push("(last_activity < ? OR (last_activity = ? AND session_id < ?))");
359
+ outer.push("(last_activity < ? OR (last_activity = ? AND session_id < ?))"); // keyset: strictly older, ties by session_id
238
360
  params.push(new Date(opts.cursor.lastActivityAt), new Date(opts.cursor.lastActivityAt), opts.cursor.sessionId);
239
361
  }
240
362
  const sql = "SELECT session_id, owner, task_id, last_activity, first_activity, run_count, objective_preview, status FROM (" +
241
- "SELECT session_id, owner, task_id, objective_preview, status," +
363
+ "SELECT session_id, owner, task_id, objective_preview, status," + // K-5c: carry the rn=1 (latest) run's task_id
242
364
  " ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at DESC, task_id DESC) AS rn," +
243
365
  " MAX(created_at) OVER (PARTITION BY session_id) AS last_activity," +
244
366
  " MIN(created_at) OVER (PARTITION BY session_id) AS first_activity," +
@@ -256,6 +378,7 @@ export class SqlRunStore {
256
378
  };
257
379
  const innerWhere = opts.owner ? ` WHERE owner = ${p(opts.owner)}` : "";
258
380
  const outer = ["rn = 1"];
381
+ // `?q=` — the PG twin of the TiDB EXISTS predicate above.
259
382
  if (opts.q)
260
383
  outer.push(`EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = t.session_id AND LOWER(tr.objective_preview) LIKE LOWER(${p(`%${escapeLike(opts.q)}%`)}) ESCAPE '\\')`);
261
384
  if (opts.cursor) {
@@ -265,7 +388,7 @@ export class SqlRunStore {
265
388
  outer.push(`(last_activity < ${a} OR (last_activity = ${b} AND session_id < ${sid}))`);
266
389
  }
267
390
  const sql = "SELECT session_id, owner, task_id, last_activity, first_activity, run_count, objective_preview, status FROM (" +
268
- "SELECT session_id, owner, task_id, objective_preview, status," +
391
+ "SELECT session_id, owner, task_id, objective_preview, status," + // K-5c: carry the rn=1 (latest) run's task_id
269
392
  " ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at DESC, task_id DESC) AS rn," +
270
393
  " MAX(created_at) OVER (PARTITION BY session_id) AS last_activity," +
271
394
  " MIN(created_at) OVER (PARTITION BY session_id) AS first_activity," +
@@ -275,6 +398,7 @@ export class SqlRunStore {
275
398
  const { rows } = await this.db.query(sql, params);
276
399
  return rows.map((r) => this.rowToSession(r));
277
400
  }
401
+ /** Shared row→summary mapper for listSessions (byte-identical between dialects). */
278
402
  rowToSession(r) {
279
403
  return {
280
404
  sessionId: String(r.session_id),
@@ -284,18 +408,41 @@ export class SqlRunStore {
284
408
  runCount: Number(r.run_count),
285
409
  objectivePreview: r.objective_preview ?? null,
286
410
  lastStatus: String(r.status),
287
- lastRunId: r.task_id ?? null,
288
- title: null,
411
+ lastRunId: r.task_id ?? null, // K-5c: the latest run's id (the live-tail re-attach anchor)
412
+ title: null, // the legacy task_run aggregate has no session_meta — honest degrade
289
413
  };
290
414
  }
415
+ /**
416
+ * E21 (§0.5 session delete) — purge the service-owned RUNS LEDGER for one session: task_event (the replayable
417
+ * event log), task_active (the single-active-run claim), and task_run (the run rows). Owner-guarded (null-safe
418
+ * compare, single-DB defense-in-depth, mirrors {@link requestCancel}) so even a buggy HTTP gate can't purge
419
+ * another tenant's runs. Idempotent — returns `{ removed }` (the count of task_run rows removed; 0 = nothing to
420
+ * purge). The conversation HISTORY (session_meta/session_event) is purged separately by the session store; the
421
+ * checkpoint/tool-result rows by their stores — this method owns only the runs-ledger tables. Done in one TX so a
422
+ * partial purge can't leak a task_active claim with no run row (a permanent session lock).
423
+ *
424
+ * 🔴 Active-run re-assert INSIDE the TX (adversarial-review LOW — TOCTOU): the route's pre-purge `getActiveTaskId`
425
+ * 409 check is a non-transactional SELECT — a concurrent createRun can claim `task_active(session_id)` in the
426
+ * window between that check and this purge, then have its LIVE rows deleted here. So we re-take the no-active-run
427
+ * invariant transactionally: `SELECT task_id FROM task_active WHERE session_id=? FOR UPDATE` at the TOP of the TX.
428
+ * `FOR UPDATE` serializes against createRun's unique-key INSERT on the SAME session_id (createRun either already
429
+ * holds the row — we see it and abort — or blocks on our lock until commit, then fails its own unique-key claim
430
+ * against the now-absent row). If a claim exists we return `{ active }` (the route maps it to 409) and delete
431
+ * NOTHING. Returns `{ removed }` on a clean purge.
432
+ *
433
+ * 🔴 The multi-table DELETEs are dialect-divergent SQL FORMS (not just placeholders): TiDB's
434
+ * `DELETE te FROM te JOIN tr ON …` vs PG's `DELETE FROM te USING tr WHERE …`.
435
+ */
291
436
  async deleteBySession(sessionId, owner) {
292
437
  let result = { removed: 0 };
293
438
  await this.tx(async (conn) => {
439
+ // Transactional no-active-run gate (FOR UPDATE serializes against createRun's task_active claim).
294
440
  const { rows: active } = await conn.query(this.q("SELECT task_id FROM task_active WHERE session_id = ? FOR UPDATE", "SELECT task_id FROM task_active WHERE session_id = $1 FOR UPDATE"), [sessionId]);
295
441
  if (active[0]) {
296
442
  result = { active: String(active[0].task_id) };
297
- return;
443
+ return; // abort — delete nothing; the route 409s (don't purge a live run's rows)
298
444
  }
445
+ // task_event has no owner column → scope it through the owner-guarded task_run set for this session.
299
446
  await conn.query(this.q("DELETE te FROM task_event te JOIN task_run tr ON te.task_id = tr.task_id WHERE tr.session_id = ? AND tr.owner <=> ?", "DELETE FROM task_event te USING task_run tr WHERE te.task_id = tr.task_id AND tr.session_id = $1 AND tr.owner IS NOT DISTINCT FROM $2"), [sessionId, owner]);
300
447
  await conn.query(this.q("DELETE ta FROM task_active ta JOIN task_run tr ON ta.task_id = tr.task_id WHERE tr.session_id = ? AND tr.owner <=> ?", "DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.session_id = $1 AND tr.owner IS NOT DISTINCT FROM $2"), [sessionId, owner]);
301
448
  const { affected } = await conn.query(this.q("DELETE FROM task_run WHERE session_id = ? AND owner <=> ?", "DELETE FROM task_run WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2"), [sessionId, owner]);
@@ -303,10 +450,15 @@ export class SqlRunStore {
303
450
  });
304
451
  return result;
305
452
  }
453
+ /** Per-door usage rollup (center systems-UI input): COUNT by (source,status) since a cutoff. Reads the
454
+ * denormalized columns only — no JSON parsing; served by full scan bounded by created_at (small windows). */
306
455
  async sourceSummary(sinceMs) {
307
456
  const { rows } = await this.db.query(this.q("SELECT source, status, COUNT(*) AS c FROM task_run WHERE created_at >= ? GROUP BY source, status", "SELECT source, status, COUNT(*) AS c FROM task_run WHERE created_at >= $1 GROUP BY source, status"), [new Date(sinceMs)]);
308
457
  return rows.map((r) => ({ source: r.source ?? null, status: String(r.status), count: Number(r.c) }));
309
458
  }
459
+ /** 用量扫窗(usage-analytics):按时间窗取 {owner,created_at,status,result} 四列,行内投影
460
+ * stats(JSON 提取在 JS,免 SQL JSON 方言分叉);LIMIT+1 探测截断(触顶=truncated:true 诚实上抛)。
461
+ * 聚合在 src/usage-analytics.ts 纯函数(三后端同代码)。可选 owner 过滤=JWT 主体只看自己。 */
310
462
  async usageScan(fromMs, toMs, opts) {
311
463
  const limit = Math.min(opts?.limit ?? USAGE_SCAN_LIMIT, USAGE_SCAN_LIMIT);
312
464
  const where = [this.q("created_at >= ?", "created_at >= $1"), this.q("created_at < ?", "created_at < $2")];
@@ -328,11 +480,14 @@ export class SqlRunStore {
328
480
  })),
329
481
  };
330
482
  }
483
+ /** Earliest retained event seq for a task (0 if none) → `retainedFrom` for /turns and the 416 boundary for
484
+ * /stream (a resume point below this has been evicted past retention). */
331
485
  async retainedFrom(taskId) {
332
486
  const { rows } = await this.db.query(this.q("SELECT MIN(seq) AS m FROM task_event WHERE task_id = ?", "SELECT MIN(seq) AS m FROM task_event WHERE task_id = $1"), [taskId]);
333
487
  const m = rows[0]?.m;
334
488
  return m == null ? 0 : Number(m);
335
489
  }
490
+ /** Events with seq strictly greater than `afterSeq` (use 0 for the whole stream). */
336
491
  async getEvents(taskId, afterSeq) {
337
492
  const { rows } = await this.db.query(this.q("SELECT seq, type, data, ts FROM task_event WHERE task_id = ? AND seq > ? ORDER BY seq ASC", "SELECT seq, type, data, ts FROM task_event WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC"), [taskId, afterSeq]);
338
493
  return rows.map((r) => ({
@@ -342,6 +497,12 @@ export class SqlRunStore {
342
497
  ts: iso(r.ts),
343
498
  }));
344
499
  }
500
+ /**
501
+ * Mark stale `running` rows as failed (best-effort recovery for runs whose instance died), then
502
+ * free any active-run claim whose run is no longer running (reaped here, or a terminal whose
503
+ * DELETE was lost). `olderThanMs` must exceed the longest expected gap between a run's events
504
+ * (each event refreshes `updated_at`), else a healthy slow run is wrongly reaped.
505
+ */
345
506
  async reapStale(olderThanMs) {
346
507
  const cutoff = new Date(Date.now() - olderThanMs);
347
508
  let reaped = 0;
@@ -350,10 +511,32 @@ export class SqlRunStore {
350
511
  "WHERE status = 'running' AND updated_at < ?", "UPDATE task_run SET status = 'failed', error = 'run stalled (instance lost?)', updated_at = $1 " +
351
512
  "WHERE status = 'running' AND updated_at < $2"), [new Date(), cutoff]);
352
513
  reaped = affected;
514
+ // Release task_active for TERMINAL rows only. A 'suspended' row (durable F4) intentionally KEEPS its
515
+ // claim — the session stays locked until an operator resumes it or reapSuspended expires it. (Was
516
+ // `<> 'running'`, which would have prematurely unlocked a parked suspended session.)
353
517
  await conn.query(this.q("DELETE ta FROM task_active ta JOIN task_run tr ON ta.task_id = tr.task_id WHERE tr.status NOT IN ('running','suspended','needs_review')", "DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.status NOT IN ('running','suspended','needs_review')"));
354
518
  });
355
519
  return reaped;
356
520
  }
521
+ /**
522
+ * Durable F4 expiry: an approval parked beyond `olderThanMs` (the approval TTL) was never answered → fail
523
+ * the run + RELEASE its task_active (unlock the session). Mirrors the checkpoint reaper's CAS-expire (≈ deny);
524
+ * idempotent across replicas. Returns count.
525
+ *
526
+ * 🔴 design/80 D-D (adversarial blocker fix): SKIPS any row whose checkpoint is STILL pending — that row is
527
+ * owned by a checkpoint-state-driven sweep (the SLA deny-sweep for human/irreversible_ask, or
528
+ * failSuspendedWithExpiredCheckpoint once the checkpoint is expired). Without this guard, this time-based
529
+ * UPDATE fired at the SAME instant as the deny-sweep (deadline == createdAt + ttl, both keyed on the TTL),
530
+ * almost always committed FIRST (it is a single UPDATE), and deleted task_active + failed the run BEFORE the
531
+ * deny-sweep's resumeCheckpoint ran — so resumeCheckpoint saw no task row (markResuming skipped) yet STILL ran
532
+ * the model on a now-unlocked session (the pending checkpoint's resolve-CAS wins), orphaning a model leg and
533
+ * opening a two-runs-one-session window. The NOT-EXISTS-pending guard (same shape as
534
+ * failSuspendedWithExpiredCheckpoint) means this only ever reaps a row whose checkpoint is already gone/expired.
535
+ *
536
+ * 🔴 Alias-qualification divergence (kept EXPLICIT, never "fixed" to match): TiDB's SET clause QUALIFIES with
537
+ * `tr.` (`SET tr.status = …`); PG's SET clause must NOT be alias-qualified (`SET status = …` — PG rejects
538
+ * `SET a.col`). Both WHERE clauses use the `tr.` alias.
539
+ */
357
540
  async reapSuspended(olderThanMs) {
358
541
  const cutoff = new Date(Date.now() - olderThanMs);
359
542
  let reaped = 0;
@@ -368,10 +551,26 @@ export class SqlRunStore {
368
551
  });
369
552
  return reaped;
370
553
  }
554
+ /**
555
+ * design/80 §3 inv#3 (crash-safe backstop — the run-row half of the checkpoint `terminal_at` sweep): fail a
556
+ * suspended run whose durable checkpoint was ALREADY EXPIRED by `reapExpired` (past its `deadline` or its
557
+ * absolute `terminal_at`), and release its task_active (unlock the session). Runs UNCONDITIONALLY (no
558
+ * approval-TTL gate), CHECKPOINT-STATE-driven not time-driven, so it aligns EXACTLY with the per-row
559
+ * `terminal_at` (which never falls before an operator's >30d deadline) — unlike a uniform timer, which would
560
+ * either be inert (gated off at APPROVAL_TIMEOUT_SEC=0) or prematurely kill a long gate. Safe against the two
561
+ * windows a naive predicate mis-fires on: (a) the transient suspend-write window — a run suspended before its
562
+ * checkpoint row exists has NO expired checkpoint, so it is not matched; (b) a re-suspended session that minted
563
+ * a NEW pending checkpoint — the `NOT EXISTS pending` clause excludes it. Idempotent across replicas. Returns count.
564
+ *
565
+ * Same alias-qualification divergence as {@link reapSuspended} — see its header note.
566
+ */
371
567
  async failSuspendedWithExpiredCheckpoint() {
372
568
  let reaped = 0;
373
569
  await this.tx(async (conn) => {
374
570
  const { affected } = await conn.query(this.q("UPDATE task_run tr SET tr.status = 'failed', tr.error = 'approval expired before decision', tr.error_code = 'approval.expired', tr.updated_at = ? " +
571
+ // wq64gmm5e HIGH: a needs_review park is ALSO a non-terminal claim-keeping state; an abandoned review
572
+ // park whose checkpoint reapExpired already expired must be failed + its task_active released, exactly
573
+ // like a suspended park — else the run row + session lock leak forever (no other reaper targets it).
375
574
  "WHERE tr.status IN ('suspended','needs_review') " +
376
575
  "AND EXISTS (SELECT 1 FROM checkpoint ce WHERE ce.session_id = tr.session_id AND ce.status = 'expired') " +
377
576
  "AND NOT EXISTS (SELECT 1 FROM checkpoint cp WHERE cp.session_id = tr.session_id AND cp.status = 'pending')", "UPDATE task_run AS tr SET status = 'failed', error = 'approval expired before decision', error_code = 'approval.expired', updated_at = $1 " +
@@ -384,11 +583,13 @@ export class SqlRunStore {
384
583
  return reaped;
385
584
  }
386
585
  }
586
+ /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
387
587
  export class TiDBRunStore extends SqlRunStore {
388
588
  constructor(pool) {
389
589
  super(mysqlDriver(pool));
390
590
  }
391
591
  }
592
+ /** PostgreSQL binding — historical class name + ctor shape preserved. */
392
593
  export class PgRunStore extends SqlRunStore {
393
594
  constructor(pool) {
394
595
  super(pgDriver(pool));
@@ -1,26 +1,37 @@
1
1
  export interface S3PresignParams {
2
+ /** Base endpoint, e.g. `http://minio.minio.svc.cluster.local:9000` (the host the SANDBOX will reach). */
2
3
  endpoint: string;
3
4
  bucket: string;
5
+ /** Object key (path within the bucket); each "/" segment is encoded, "/" preserved. */
4
6
  key: string;
5
7
  method: "GET" | "PUT" | "HEAD" | "DELETE";
6
8
  accessKey: string;
7
9
  secretKey: string;
8
10
  region?: string;
9
11
  expiresSec?: number;
12
+ /** Override the signing clock (tests). Defaults to now. */
10
13
  now?: Date;
11
14
  }
15
+ /** Build a SigV4 presigned **single-object** URL. Pure string math — no network. */
12
16
  export declare function presignS3Url(p: S3PresignParams): string;
13
17
  export interface S3BucketPresignParams {
14
18
  endpoint: string;
15
19
  bucket: string;
20
+ /** Bucket-level verbs only: PUT = CreateBucket, DELETE = DeleteBucket (MinIO path-style, live-verified). */
16
21
  method: "PUT" | "DELETE";
17
22
  accessKey: string;
18
23
  secretKey: string;
19
24
  region?: string;
20
25
  expiresSec?: number;
26
+ /** Override the signing clock (tests). Defaults to now. */
21
27
  now?: Date;
22
28
  }
29
+ /** Build a SigV4 presigned **bucket-level** URL (`/<bucket>/`, no key) — the explicit face for
30
+ * CreateBucket/DeleteBucket (codex 1.189 F8: previously test fixtures smuggled this through
31
+ * presignS3Url with key "", which the single-object contract now rejects). Same signing core. */
23
32
  export declare function presignS3BucketUrl(p: S3BucketPresignParams): string;
33
+ /** ListObjectsV2 presign(D-1 孤儿对象 GC,clay 拍 2026-07-28):GET `/<bucket>/?list-type=2&prefix=…`。
34
+ * bucket 级 GET 是**列举能力**——只给 server 端 sweep 用,URL 绝不外发(与 copy 面同纪律)。 */
24
35
  export declare function presignS3ListUrl(p: {
25
36
  endpoint: string;
26
37
  bucket: string;
@@ -34,9 +45,12 @@ export declare function presignS3ListUrl(p: {
34
45
  now?: Date;
35
46
  }): string;
36
47
  export interface S3CopyPresignParams {
48
+ /** INTERNAL endpoint — CopyObject is a SERVER-side request with adapter creds; never hand this URL out. */
37
49
  endpoint: string;
50
+ /** Destination coordinates (the PUT target). */
38
51
  bucket: string;
39
52
  key: string;
53
+ /** Source coordinates (ride the signed `x-amz-copy-source` header). */
40
54
  sourceBucket: string;
41
55
  sourceKey: string;
42
56
  accessKey: string;
@@ -45,6 +59,12 @@ export interface S3CopyPresignParams {
45
59
  expiresSec?: number;
46
60
  now?: Date;
47
61
  }
62
+ /** Build a SigV4 presigned **CopyObject** request (修3 staging-key two-phase): S3 CopyObject = `PUT
63
+ * /<dstBucket>/<dstKey>` with `x-amz-copy-source: /<srcBucket>/<srcKey>`. Query auth requires every
64
+ * `x-amz-*` request header to be SIGNED, so the copy-source header goes through the signing core's
65
+ * signedHeaders seam; the caller MUST send `headers` verbatim on the fetch (the source key is
66
+ * RFC-3986-encoded — MinIO/S3 both accept the encoded copy-source form, and header values must be
67
+ * ASCII anyway). Same single-object guards as presignS3Url on BOTH sides. */
48
68
  export declare function presignS3CopyUrl(p: S3CopyPresignParams): {
49
69
  url: string;
50
70
  headers: Record<string, string>;
@@ -1,4 +1,16 @@
1
+ /**
2
+ * Minimal AWS SigV4 **presigned URL** generator for S3-compatible object stores (MinIO) — zero dependencies
3
+ * (crypto only), matching the repo's no-SDK posture (OTLP exporter, the k8s adapter). We only need presign:
4
+ * the k8s sandbox adapter uploads/downloads a workspace snapshot by handing the SANDBOX a short-lived,
5
+ * single-object URL so the adapter's MinIO credentials never enter the sandbox (design/53 lethal-trifecta
6
+ * red line — "adapter-held, sandbox sees only a presigned URL").
7
+ *
8
+ * Path-style addressing (`<endpoint>/<bucket>/<key>`) — MinIO's default and what an IP/cluster-DNS endpoint
9
+ * needs (no virtual-host bucket subdomain). Payload is UNSIGNED-PAYLOAD so a streamed `curl -T -` body
10
+ * (chunked, unknown length) verifies — the signature binds method+path+headers+expiry, not the bytes.
11
+ */
1
12
  import { createHash, createHmac } from "node:crypto";
13
+ /** URI-encode per RFC 3986 (S3 canonicalization): unreserved chars stay literal, everything else %XX. */
2
14
  function uriEncode(s, encodeSlash) {
3
15
  let out = "";
4
16
  for (const ch of Buffer.from(s, "utf8")) {
@@ -18,22 +30,40 @@ function uriEncode(s, encodeSlash) {
18
30
  function hmac(key, data) {
19
31
  return createHmac("sha256", key).update(data, "utf8").digest();
20
32
  }
33
+ /** The shared SigV4 signing core: everything below the canonical-URI choice is identical for an
34
+ * object URL and a bucket URL — one implementation so the two public faces can never drift.
35
+ *
36
+ * Endpoint PATH PREFIX (三路复审修1): an endpoint like `https://files.example.com/minio` (reverse-proxy
37
+ * sub-path in front of MinIO) contributes its path to BOTH the canonical URI (what SigV4 signs) and the
38
+ * returned URL — previously only `url.origin` was used, so the signed canonical URI (`/bucket/key`) never
39
+ * matched the request path the store actually saw (`/minio/bucket/key`), and the presigned track silently
40
+ * diverged from the permanent track's plain string concat (which DID keep the prefix). One rstrip'd
41
+ * pathname, prepended to both, keeps signature and request path congruent for any endpoint shape.
42
+ *
43
+ * `signedHeaders` (修3 CopyObject support): extra headers to SIGN beyond `host` — presigned-URL (query)
44
+ * auth requires every `x-amz-*` header the request carries to be in X-Amz-SignedHeaders, so a server-side
45
+ * CopyObject (`x-amz-copy-source`) must sign it. Names are lowercased; canonical headers are sorted;
46
+ * the CALLER must send exactly these header values on the request. */
21
47
  function presignCanonical(p) {
22
48
  const region = p.region ?? "us-east-1";
23
49
  const expires = p.expiresSec ?? 600;
24
50
  const now = p.now ?? new Date();
25
- const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
51
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, ""); // YYYYMMDDTHHMMSSZ
26
52
  const dateStamp = amzDate.slice(0, 8);
27
53
  const url = new URL(p.endpoint);
28
- const host = url.host;
54
+ const host = url.host; // includes port
55
+ // Endpoint path prefix (修1): "" for a root endpoint; "/minio" for a sub-path endpoint (trailing "/"
56
+ // stripped; `new URL().pathname` is already percent-encoded, which is what the canonical URI wants).
29
57
  const basePath = url.pathname.replace(/\/+$/, "");
30
58
  const canonicalUri = `${basePath}${p.canonicalUri}`;
31
59
  const scope = `${dateStamp}/${region}/s3/aws4_request`;
60
+ // Signed headers: `host` always; extras (lowercased, value-trimmed) merged in; canonical order = sorted.
32
61
  const headerMap = new Map([["host", host]]);
33
62
  for (const [k, v] of Object.entries(p.signedHeaders ?? {}))
34
63
  headerMap.set(k.toLowerCase(), v.trim());
35
64
  const headerNames = [...headerMap.keys()].sort();
36
65
  const signedHeaderList = headerNames.join(";");
66
+ // Query params must be sorted; values URI-encoded (the credential's "/" too).
37
67
  const params = {
38
68
  ...(p.extraQuery ?? {}),
39
69
  "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
@@ -56,18 +86,29 @@ function presignCanonical(p) {
56
86
  const signature = createHmac("sha256", kSigning).update(stringToSign, "utf8").digest("hex");
57
87
  return `${url.origin}${canonicalUri}?${canonicalQuery}&X-Amz-Signature=${signature}`;
58
88
  }
89
+ /** Build a SigV4 presigned **single-object** URL. Pure string math — no network. */
59
90
  export function presignS3Url(p) {
91
+ // codex 1.189 F8: the contract here is SINGLE-OBJECT — an empty key would silently sign the BUCKET URI
92
+ // (`/<bucket>/` = CreateBucket/DeleteBucket/ListObjects depending on method), a far broader capability
93
+ // than any caller of this face intends. Refuse; bucket-level operations go through presignS3BucketUrl.
60
94
  if (p.key === "") {
61
95
  throw new Error("presignS3Url: refusing to sign an empty key (single-object contract) — use presignS3BucketUrl for bucket-level operations");
62
96
  }
97
+ // Backstop (defense in depth): callers MUST pass a pre-sanitized key, but a path-traversal segment here would
98
+ // sign a URL that escapes its intended prefix once the store normalizes the path — refuse rather than sign it.
63
99
  if (p.key.startsWith("/") || p.key.split("/").some((seg) => seg === "." || seg === "..")) {
64
100
  throw new Error(`presignS3Url: refusing to sign a key with a traversal segment: ${p.key}`);
65
101
  }
66
102
  return presignCanonical({ ...p, canonicalUri: `/${uriEncode(p.bucket, true)}/${uriEncode(p.key, false)}` });
67
103
  }
104
+ /** Build a SigV4 presigned **bucket-level** URL (`/<bucket>/`, no key) — the explicit face for
105
+ * CreateBucket/DeleteBucket (codex 1.189 F8: previously test fixtures smuggled this through
106
+ * presignS3Url with key "", which the single-object contract now rejects). Same signing core. */
68
107
  export function presignS3BucketUrl(p) {
69
108
  return presignCanonical({ ...p, canonicalUri: `/${uriEncode(p.bucket, true)}/` });
70
109
  }
110
+ /** ListObjectsV2 presign(D-1 孤儿对象 GC,clay 拍 2026-07-28):GET `/<bucket>/?list-type=2&prefix=…`。
111
+ * bucket 级 GET 是**列举能力**——只给 server 端 sweep 用,URL 绝不外发(与 copy 面同纪律)。 */
71
112
  export function presignS3ListUrl(p) {
72
113
  return presignCanonical({
73
114
  endpoint: p.endpoint,
@@ -86,6 +127,12 @@ export function presignS3ListUrl(p) {
86
127
  },
87
128
  });
88
129
  }
130
+ /** Build a SigV4 presigned **CopyObject** request (修3 staging-key two-phase): S3 CopyObject = `PUT
131
+ * /<dstBucket>/<dstKey>` with `x-amz-copy-source: /<srcBucket>/<srcKey>`. Query auth requires every
132
+ * `x-amz-*` request header to be SIGNED, so the copy-source header goes through the signing core's
133
+ * signedHeaders seam; the caller MUST send `headers` verbatim on the fetch (the source key is
134
+ * RFC-3986-encoded — MinIO/S3 both accept the encoded copy-source form, and header values must be
135
+ * ASCII anyway). Same single-object guards as presignS3Url on BOTH sides. */
89
136
  export function presignS3CopyUrl(p) {
90
137
  for (const [label, key] of [
91
138
  ["destination", p.key],