@sema-agent/server 1.323.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 +58 -2
  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,24 +1,123 @@
1
+ /**
2
+ * P1 ①② — the async-workflow COMPLETION INBOX (the deployment half of "route a finished
3
+ * background workflow back to the session that started it").
4
+ *
5
+ * ## Why this exists
6
+ * A self-orchestration / background workflow (started via core's `RunWorkflow`) runs to terminal OUTSIDE the
7
+ * originating session's request loop — often minutes later, possibly after that turn's SSE stream already
8
+ * closed. core's notifier seam + the SVC-1 {@link WorkflowNotifyGate} deliver its terminal payload to the
9
+ * deployment's `deliverWorkflowCompletion` AT-LEAST-ONCE, but that was a log-only shell: the model that
10
+ * launched the workflow never learned it finished (it had only the deterministic `WorkflowStatus` poll floor).
11
+ *
12
+ * This inbox is the PUSH half. `deliverWorkflowCompletion` resolves the originating **session + owner** (from
13
+ * the payload's `sourceTaskId`) and `enqueue`s the completion here; when that session next OPENS a stream (a
14
+ * new `POST /v1/runs` turn), the HTTP layer `drain`s the inbox and — for entries whose `owner` matches the
15
+ * draining caller's VERIFIED principal — emits a `workflow_complete` out-of-band SSE frame (mirroring the
16
+ * question/elicit frame pattern) that the shell turns into a `<task-notification>` (the shell's
17
+ * `enqueuePendingNotification`, deduped by runId). Delivery is thus decoupled from whether a stream was open
18
+ * at completion time — a disconnected-then-reconnected shell still gets it — WITHOUT a cross-loop append into
19
+ * the originating run's own event log (its `seq` counter is owned by that run's loop; appending from outside
20
+ * would race it — so completions live in this SEPARATE store).
21
+ *
22
+ * ## Tenancy
23
+ * Each entry carries the originating run's `owner`. The drain-side owner check (in the HTTP layer) is a
24
+ * defence-in-depth SECOND gate on top of the authorizer's sessionId→owner binding: a session that is deleted
25
+ * then re-claimed by a different tenant rotates that binding, so the stamped `owner` is what actually gates
26
+ * whether a completion is EMITTED. `purge(sessionId)` is called on session delete so orphaned completions
27
+ * don't linger. (`owner: null` = the single-user turnkey posture, no tenancy boundary.)
28
+ *
29
+ * ## Durability / delivery model
30
+ * - **At-least-once, shell-deduped**: the shell dedups by `runId` (`markTaskNotified`), so a completion frame
31
+ * re-delivered across a crash is harmless. `drain` therefore removes on read; the small window where a crash
32
+ * after `drain` but before the frame is flushed loses a push is covered by the deterministic `WorkflowStatus`
33
+ * poll floor (core 1.197) — never a silent hang.
34
+ * - **Crash-safe File ledger** (default): an append-only `(op) → record` journal, replayed into an in-memory
35
+ * `sessionId → (runId → entry)` map. A torn trailing line (crash mid-append) is dropped on replay
36
+ * (uncommitted). **Compacted** — rewritten from the live in-memory state on boot AND once the ledger grows
37
+ * past {@link COMPACT_EVERY} appends — so tombstones/evicted/drained lines don't accumulate without bound.
38
+ * Owner-only perms (identity data).
39
+ * - **Bounded**: at most {@link MAX_PENDING_PER_SESSION} newest completions are retained per session; the
40
+ * oldest is dropped (with a warn on LIVE enqueue — suppressed during replay to avoid log noise).
41
+ */
1
42
  import { mkdirSync, readFileSync, existsSync, openSync, writeSync, fsyncSync, closeSync, renameSync } from "node:fs";
2
43
  import { join } from "node:path";
3
44
  import { redactSecrets } from "../trace/redact.js";
45
+ /** Cap on retained pending completions per session (drop-oldest beyond it). A session with more than this many
46
+ * undelivered workflow completions is pathological; the poll floor still covers the dropped ones. */
4
47
  export const MAX_PENDING_PER_SESSION = 100;
48
+ /** Compact the File ledger (rewrite from live state) once this many records have been appended since the last
49
+ * compaction — bounds on-disk size + boot replay cost for a long-lived process with high workflow churn. */
5
50
  export const COMPACT_EVERY = 512;
51
+ /** How long after a `purge` an `enqueue` for the same session is refused (the purge-vs-enqueue fence): a
52
+ * workflow completion landing AFTER its session was deleted must not resurrect as a live entry the next
53
+ * claimant of that sessionId would see. In-memory only (a restart forgets the fence — the cross-restart race
54
+ * window is negligible and the drain-side owner gate still stands as the real tenancy boundary). */
6
55
  export const PURGE_FENCE_MS = 10 * 60 * 1000;
56
+ /** How long after {@link WorkflowCompletionInbox.markTerminalServed} an `enqueue` for the SAME (session,
57
+ * runId) is refused — covers the notify path lagging the model's poll (the notify gate is async +
58
+ * at-least-once, so the completion can land in the inbox AFTER the poll already told the model). Long enough
59
+ * to swallow any realistic notify lag incl. a boot-recovery re-fire racing a fresh poll; per-runId scoped, so
60
+ * it can never starve a DIFFERENT workflow's push. In-memory only (see {@link PURGE_FENCE_MS} rationale). */
7
61
  export const SERVED_FENCE_MS = 30 * 60 * 1000;
62
+ /** design/144 §4 server 折叠键:`JSON([域, taskId, RAW status])[#seq]`。域=external 帧独立 lane(task_id 是
63
+ * 调用方自由串,不分域可预占内部帧键把真实子代 settle 帧吞掉)/其余内部帧统一 "task" 域。
64
+ *
65
+ * 三路复审修1(MED):status 保留 RAW,不再归一 completed/failed —— 归一让同 id 无 seq 的两条 external
66
+ * 通知(先 `event` 后 `cancelled`)折叠成同一把 `…:failed` 键,inbox map 里第二条 REPLACE 第一条,drain
67
+ * 只吐一条(entry 的存储 status 列仍归一,那是账本的双终态列,不动;RAW status 本就随 payload extras
68
+ * 透传)。这与 {@link taskNotificationStreamKey} 的 RAW 语义对齐(stream 侧早已保 RAW)。
69
+ *
70
+ * 编码=防碰撞元组:域/taskId/status 走 JSON 数组(调用方自由串伪造不了分隔符);`#seq` 尾折在 JSON 之外
71
+ * ——JSON 段恒以 `"]` 结尾,`#digits` 尾无歧义,于是 {@link foldKeyFamily} 的剥尾语义 AND SQL 双生子
72
+ * (tidb/pg-workflow-run-store 的 `regexp_replace(run_id, '#[0-9]+$', '')` 家族比对)原样成立——把 seq 折进
73
+ * JSON 元组会让 SQL 侧家族 ack/fence 永 miss(那是另一批的真库改动面,本修有意不碰)。
74
+ * seq 折入=逐停机周期各自成键(1.283 起 background_agent 四腿 notify 铸 cycleSeq);无 seq=单周期旧键
75
+ * (bash/monitor terminal/retain 降级,诚实语义)。serve-fence 侧必须用同一函数(或其家族前缀)构造,
76
+ * 否则键形漂移=fence 永 miss(server.ts fleet 腿的 `taskNotificationFoldKey({task_id, status})` 无 seq 形
77
+ * 即家族前缀,两侧同为 RAW status,依旧咬合)。 */
8
78
  export function taskNotificationFoldKey(n) {
9
79
  const domain = n.task_type === "external" ? "external" : "task";
10
80
  return `${JSON.stringify([domain, n.task_id, n.status])}${n.seq !== undefined ? `#${n.seq}` : ""}`;
11
81
  }
82
+ /** design/144 stream 侧去重键(core [784]④a + codex 1.189 前双审 F2)= `域:taskId:status[#seq]`。
83
+ * 与 {@link taskNotificationFoldKey} 同族,域二分也一致(codex F2 HIGH:external 通知的 task_id 是
84
+ * POST /v1/sessions/:id/notify 调用方自由串——同一条腿上,drain 出的 external 帧可与内部任务帧同形
85
+ * `task_id:status:seq` 撞键互吞,per-leg 键集并不豁免这个面,旧注释「没有跨调用方预占面」判错);
86
+ * 仍与 fold key 有意不同的一点:status 保留 RAW(killed/cancelled/event 不归一 failed)——归一会让同
87
+ * task 的 Monitor event 批与 failed 终态在一条腿上互吞;wire 去重要的是逐帧身份,不是 inbox 的双终态列。
88
+ * `seq` 折入 = 停→唤→停的第二周期同 status 帧各自成键(core 1.283 起 background_agent 四腿 notify 铸
89
+ * cycleSeq;缺 seq 时旧键曾把二周期 completed 帧在 observer 臂反向吞掉——core [784]④a);无 seq 的帧
90
+ * (bash/monitor terminal/retain 降级)保持单周期旧键=同键第二帧吞,诚实语义。task_type 缺席(旧 core
91
+ * 内部帧)归 "task" 域——external 帧的 task_type 三来源齐备(notify 端点 core 铸 / inbox entry extras 存
92
+ * / drain 帧 spread 复原),不会漏域。observer 臂 / stream 臂 / turn-open drain 登记三处必须用本函数
93
+ * 构键且都传 task_type,键形漂移=去重永 miss。 */
12
94
  export function taskNotificationStreamKey(n) {
13
95
  const domain = n.task_type === "external" ? "external" : "task";
14
96
  return `${domain}:${n.task_id}:${n.status}${n.seq !== undefined ? `#${n.seq}` : ""}`;
15
97
  }
98
+ /** Per-leg notified-key ledger cap (codex 1.189 前双审 F3):一条超长腿(海量互异 bg 通知)不许把键集
99
+ * 撑到无界——超出删最老插入(Map 迭代序=插入序)。远高于任何真实 per-leg 通知量;被逐键的老通知
100
+ * 最坏情形=同键晚到帧多写一次(at-least-once,shell 按键 dedup 兜底),绝不吞新帧。 */
16
101
  export const NOTIFIED_KEYS_MAX = 4096;
102
+ /**
103
+ * Per-leg delivered-notification ledger(codex 1.189 前双审 F1 HIGH 的修位)。
104
+ * 键 = {@link taskNotificationStreamKey};值 = 「认领这把键的那次写是否真落盘」的 promise(true=成功)。
105
+ * 为什么是 Map<string, Promise<boolean>> 而不是 Set:bg/resume 腿的 observer 臂是 fire-and-forget append
106
+ * (`void append(...)`)——旧 Set 下 append 失败(SQL blip)时键已被占而盘上无帧,core pend-drain 把同
107
+ * payload 重放到 stream 臂时被键吞 = 通知永丢。现在 stream 臂对已见键 `await` 认领方的真实结果,false 则
108
+ * 照写并把键值升为 true。sync 腿的写是同步 res.write(无异步失败窗口)、drain 臂的写被
109
+ * emitPendingWorkflowCompletions await(失败→不 ack→下腿重投)——两者登记 resolved(true) 即可;三腿统一
110
+ * 用本 Map 形状,让三对臂保持逐行孪生。
111
+ */
17
112
  export class NotifiedKeys {
18
113
  byKey = new Map();
114
+ /** The pending/settled outcome of the write that claimed `key`, or undefined when unclaimed. */
19
115
  get(key) {
20
116
  return this.byKey.get(key);
21
117
  }
118
+ /** Claim (or update) `key` with the outcome of its write. Bounded: past {@link NOTIFIED_KEYS_MAX} the
119
+ * oldest-inserted key is dropped (F3; an update keeps the original insertion slot — good enough for a
120
+ * pathology cap). */
22
121
  set(key, written) {
23
122
  this.byKey.set(key, written);
24
123
  while (this.byKey.size > NOTIFIED_KEYS_MAX) {
@@ -27,15 +126,27 @@ export class NotifiedKeys {
27
126
  }
28
127
  }
29
128
  }
129
+ /** 家族前缀 = 折叠键只剥 `#seq` 分量(仅当尾是纯数字才剥)。新元组键的 JSON 段恒以 `"]` 结尾、taskId/
130
+ * status 里天然的 # 被 JSON 引号包住,`#digits` 尾无歧义=必是 seq;旧格式遗留行(`域:task:status#seq`)
131
+ * 与 workflow_complete 的裸 runId 键同一语义原样兼容。 */
30
132
  export function foldKeyFamily(runId) {
31
133
  const i = runId.lastIndexOf("#");
32
134
  return i > 0 && /^\d+$/.test(runId.slice(i + 1)) ? runId.slice(0, i) : runId;
33
135
  }
136
+ /** Collision-safe (session, runId) fence key — ids are caller-influenced strings, so a plain separator could
137
+ * be forged into a cross-pair collision; JSON tuple encoding cannot. */
34
138
  const servedKey = (sessionId, runId) => JSON.stringify([sessionId, runId]);
139
+ /** In-memory inbox — the parity oracle + the ephemeral opt-out (WORKFLOW_RUN_STORE=memory). No persistence:
140
+ * a restart forgets undelivered completions (the poll floor covers them). */
35
141
  export class InMemoryWorkflowCompletionInbox {
36
142
  onWarn;
37
143
  bySession = new Map();
144
+ /** sessionId → {at, owner}: purge stamps; enqueues within {@link PURGE_FENCE_MS} for the SAME owner are
145
+ * refused. Swept on BOTH enqueue and purge (a purge-heavy, enqueue-quiet process must not accumulate
146
+ * stamps unboundedly — review). */
38
147
  purgedAt = new Map();
148
+ /** servedKey(sessionId, runId) → stamp: terminal-poll-served fences; an enqueue for the pair within
149
+ * {@link SERVED_FENCE_MS} is refused (the poll already told the model). Swept alongside the purge fence. */
39
150
  servedAt = new Map();
40
151
  constructor(onWarn) {
41
152
  this.onWarn = onWarn;
@@ -48,9 +159,16 @@ export class InMemoryWorkflowCompletionInbox {
48
159
  if (now - at > SERVED_FENCE_MS)
49
160
  this.servedAt.delete(key);
50
161
  }
162
+ /** True (and warns) when `entry`'s session was purged within the fence window AND the fence applies to its
163
+ * owner: same owner as the purged session (a late completion of the DELETED session) — or an owner-less
164
+ * fence (caller couldn't say), conservative. A DIFFERENT owner = a new legitimate claimant of a reused
165
+ * sessionId → not fenced (their push must not be starved; the drain-side owner gate stays the real
166
+ * tenancy boundary either way). */
51
167
  purgeFenced(entry) {
52
168
  const now = Date.now();
53
169
  this.sweepFence(now);
170
+ // Served fence FIRST (per-runId, so it can't shadow the purge fence's owner semantics): this exact
171
+ // completion already reached the model through its own poll — enqueueing it would be the double delivery.
54
172
  if (this.servedAt.has(servedKey(entry.sessionId, entry.runId)) || this.servedAt.has(servedKey(entry.sessionId, foldKeyFamily(entry.runId)))) {
55
173
  this.onWarn?.("workflow_completion_enqueue_after_poll_served", { sessionId: entry.sessionId, runId: entry.runId });
56
174
  return true;
@@ -59,14 +177,20 @@ export class InMemoryWorkflowCompletionInbox {
59
177
  if (st === undefined)
60
178
  return false;
61
179
  if (st.owner !== undefined && entry.owner !== st.owner)
62
- return false;
180
+ return false; // new claimant, different owner → pass
63
181
  this.onWarn?.("workflow_completion_enqueue_after_purge", { sessionId: entry.sessionId, runId: entry.runId });
64
182
  return true;
65
183
  }
184
+ /** Fence FIRST (an enqueue racing the poll is refused even mid-call), then drop any already-pending
185
+ * entry via `ack` — the durable subclass inherits this and gets a durable ack for free. */
66
186
  async markTerminalServed(sessionId, runId) {
67
187
  this.sweepFence(Date.now());
68
188
  this.servedAt.set(servedKey(sessionId, runId), Date.now());
69
189
  await this.ack(sessionId, runId);
190
+ // 家族 ack(design/144 消费批):fleet 常开流帧不带 seq,而 1.283 起终态 park 是 seq 键——精确 ack
191
+ // 必 miss ⇒ 下次开流 drain 二次注入模型。传入 runId 视作家族前缀,清掉同 (域,taskId,status) 的全部
192
+ // seq 键成员。取舍如实记:同家族的更早 Monitor 批可能被连带清(终态已送达后丢进度行,低害),
193
+ // 换取每个 bg 终态必然双投的消除;workflow_complete 键无 # 尾,家族=自身,行为不变。
70
194
  const q = this.bySession.get(sessionId);
71
195
  if (q) {
72
196
  for (const key of [...q.keys()]) {
@@ -76,14 +200,16 @@ export class InMemoryWorkflowCompletionInbox {
76
200
  }
77
201
  }
78
202
  stampPurge(sessionId, owner) {
79
- this.sweepFence(Date.now());
203
+ this.sweepFence(Date.now()); // bound the map on the purge side too (enqueue-quiet processes)
80
204
  this.purgedAt.set(sessionId, { at: Date.now(), owner });
81
205
  }
206
+ /** Fold one entry into the map. `warn` is false during ledger replay (a historical overflow must not re-log
207
+ * on every boot) and true on a live enqueue. */
82
208
  applyEnqueue(entry, warn) {
83
209
  let q = this.bySession.get(entry.sessionId);
84
210
  if (!q)
85
211
  this.bySession.set(entry.sessionId, (q = new Map()));
86
- q.delete(entry.runId);
212
+ q.delete(entry.runId); // idempotent + re-insert at the tail (Map preserves insertion order → oldest-first)
87
213
  q.set(entry.runId, entry);
88
214
  while (q.size > MAX_PENDING_PER_SESSION) {
89
215
  const oldest = q.keys().next().value;
@@ -94,13 +220,16 @@ export class InMemoryWorkflowCompletionInbox {
94
220
  }
95
221
  async enqueue(entry) {
96
222
  if (this.purgeFenced(entry))
97
- return;
223
+ return; // late completion of a DELETED session → drop (poll floor covers)
98
224
  this.applyEnqueue(entry, true);
99
225
  }
100
226
  async pending(sessionId) {
101
227
  const q = this.bySession.get(sessionId);
102
228
  if (!q || q.size === 0)
103
229
  return [];
230
+ // The served fence is armed SYNCHRONOUSLY in markTerminalServed (before its async ack
231
+ // lands), so filtering here closes the same-process race where a stream-open drain reads a still-pending
232
+ // entry while the ack write is in flight — the drain must never re-deliver what the fence says was served.
104
233
  return [...q.values()].filter((e) => !this.servedAt.has(servedKey(e.sessionId, e.runId)) && !this.servedAt.has(servedKey(e.sessionId, foldKeyFamily(e.runId))));
105
234
  }
106
235
  async ack(sessionId, runId) {
@@ -116,6 +245,8 @@ export class InMemoryWorkflowCompletionInbox {
116
245
  this.stampPurge(sessionId, purgedOwner);
117
246
  }
118
247
  }
248
+ /** File-backed crash-safe inbox (the durable default — a background workflow's completion survives a replica
249
+ * restart, the prerequisite for pushing it to a session that reconnects after the crash). */
119
250
  export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox {
120
251
  fsyncEnabled;
121
252
  dir;
@@ -126,10 +257,12 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
126
257
  super(onWarn);
127
258
  this.fsyncEnabled = fsyncEnabled;
128
259
  this.dir = join(root, "workflow-completion-inbox");
260
+ // 0o700 dir + 0o600 ledger: entries carry the completion summary (may reference tenant work) + sessionId +
261
+ // owner — owner-only, parity with the notify-journal's identity-data posture.
129
262
  mkdirSync(this.dir, { recursive: true, mode: 0o700 });
130
263
  this.ledgerPath = join(this.dir, "inbox.jsonl");
131
264
  this.replay();
132
- this.compact();
265
+ this.compact(); // rewrite the ledger to just the live pending set → tombstones/evicted lines don't survive restarts
133
266
  }
134
267
  replay() {
135
268
  if (!existsSync(this.ledgerPath))
@@ -143,7 +276,7 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
143
276
  rec = JSON.parse(line);
144
277
  }
145
278
  catch {
146
- continue;
279
+ continue; // torn/partial trailing line (crash mid-append) — uncommitted, skip
147
280
  }
148
281
  if (rec.op === "enqueue" && rec.entry?.sessionId && rec.entry.runId)
149
282
  this.applyEnqueue(rec.entry, false);
@@ -151,29 +284,40 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
151
284
  const q = this.bySession.get(rec.sessionId);
152
285
  q?.delete(rec.runId);
153
286
  if (q && q.size === 0)
154
- this.bySession.delete(rec.sessionId);
287
+ this.bySession.delete(rec.sessionId); // mirror the live ack — replay is a strict fold
155
288
  }
156
289
  else if (rec.op === "drain" && rec.sessionId)
157
- this.bySession.delete(rec.sessionId);
290
+ this.bySession.delete(rec.sessionId); // purge today; pre-1.80 drain lines too
158
291
  }
159
292
  }
160
293
  commit(rec) {
161
294
  if (this.fd === undefined)
162
- this.fd = openSync(this.ledgerPath, "a", 0o600);
295
+ this.fd = openSync(this.ledgerPath, "a", 0o600); // owner-only (identity/work data)
163
296
  writeSync(this.fd, `${JSON.stringify(rec)}\n`);
164
297
  if (this.fsyncEnabled)
165
- fsyncSync(this.fd);
298
+ fsyncSync(this.fd); // durable BEFORE the in-memory state the caller observes
166
299
  this.appendsSinceCompact++;
167
300
  }
301
+ /** Compact iff we've appended enough since the last one. MUST be called AFTER the in-memory mutation the
302
+ * triggering commit records — compact() rewrites from `bySession`, so a compact fired BEFORE the caller
303
+ * applied its enqueue/delete would rewrite a stale snapshot and lose that record. */
168
304
  maybeCompact() {
169
305
  if (this.appendsSinceCompact >= COMPACT_EVERY)
170
306
  this.compact();
171
307
  }
308
+ /** Rewrite the ledger from the authoritative in-memory state — one `enqueue` record per LIVE pending entry —
309
+ * dropping every accumulated drain-tombstone + evicted/drained line. Atomic (temp + rename) so a crash
310
+ * mid-compact leaves the old ledger intact. */
172
311
  compact() {
173
312
  if (this.fd !== undefined) {
174
313
  closeSync(this.fd);
175
314
  this.fd = undefined;
176
315
  }
316
+ // tmp 名带 pid(2026-07-28 核查修):两个进程共用同一数据根时(例:两个 memory 形引擎共用一个
317
+ // HOME——multi-instance 共享目录本不受支持,但 memory 形用户合理预期「无盘无锁」,却默默共享
318
+ // `~/.ai-agent`),固定 `.tmp` 名会互相 rename 走对方的 staging ⇒ 对方 renameSync ENOENT,而 compact
319
+ // 在 constructor 里同步跑 = **boot fatal**(真机复现:两只引擎并发 boot 恢复期)。带 pid 后各进程
320
+ // staging 独立,rename 原子替换回落到 last-writer-wins(账本内容级共享仍不支持,但不再拒启)。
177
321
  const tmp = `${this.ledgerPath}.tmp.${process.pid}`;
178
322
  const lines = [];
179
323
  for (const q of this.bySession.values())
@@ -189,7 +333,9 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
189
333
  finally {
190
334
  closeSync(tfd);
191
335
  }
192
- renameSync(tmp, this.ledgerPath);
336
+ renameSync(tmp, this.ledgerPath); // atomic replace of the ledger
337
+ // fsync the DIRECTORY so the rename (a namespace change) is itself crash-durable on POSIX/macOS, not just
338
+ // the file contents. Best-effort: a dir that can't be opened for fsync (rare) is skipped.
193
339
  if (this.fsyncEnabled) {
194
340
  try {
195
341
  const dfd = openSync(this.dir, "r");
@@ -201,35 +347,40 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
201
347
  }
202
348
  }
203
349
  catch {
350
+ /* dir fsync unsupported/failed — the rename is still namespace-atomic; skip */
204
351
  }
205
352
  }
206
353
  this.appendsSinceCompact = 0;
207
354
  }
208
355
  async enqueue(entry) {
209
356
  if (this.purgeFenced(entry))
210
- return;
357
+ return; // late completion of a DELETED session → never re-committed to the ledger
211
358
  this.commit({ op: "enqueue", entry });
212
359
  this.applyEnqueue(entry, true);
213
- this.maybeCompact();
360
+ this.maybeCompact(); // AFTER applyEnqueue → compact's rewrite includes this entry
214
361
  }
362
+ // `pending` is the inherited non-destructive read — deliberately NO ledger write (phase 1 of two-phase
363
+ // delivery leaves the durable state untouched; only the per-entry `ack` mutates it, so a crash/failed
364
+ // frame-write between the two re-delivers on the next leg instead of silently eating the completion).
215
365
  async ack(sessionId, runId) {
216
366
  const q = this.bySession.get(sessionId);
217
367
  if (!q?.has(runId))
218
- return;
219
- this.commit({ op: "ack", sessionId, runId });
368
+ return; // idempotent — a raced double-ack (two legs) writes ONE tombstone
369
+ this.commit({ op: "ack", sessionId, runId }); // durable BEFORE the in-memory delete (deliver-then-ack)
220
370
  q.delete(runId);
221
371
  if (q.size === 0)
222
372
  this.bySession.delete(sessionId);
223
- this.maybeCompact();
373
+ this.maybeCompact(); // AFTER delete → compact's rewrite reflects the ack (no resurrection)
224
374
  }
225
375
  async purge(sessionId, purgedOwner) {
226
- this.stampPurge(sessionId, purgedOwner);
376
+ this.stampPurge(sessionId, purgedOwner); // fence FIRST — an enqueue racing the purge is refused even mid-purge
227
377
  if (!this.bySession.has(sessionId))
228
378
  return;
229
- this.commit({ op: "drain", sessionId });
379
+ this.commit({ op: "drain", sessionId }); // a purge clears the whole session (session-level tombstone)
230
380
  this.bySession.delete(sessionId);
231
- this.maybeCompact();
381
+ this.maybeCompact(); // AFTER delete → compact's rewrite reflects the purge
232
382
  }
383
+ /** Close the ledger fd (shutdown). */
233
384
  close() {
234
385
  if (this.fd !== undefined) {
235
386
  closeSync(this.fd);
@@ -237,14 +388,41 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
237
388
  }
238
389
  }
239
390
  }
240
- export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPrincipal, emit, diag) {
391
+ /**
392
+ * P1 ①② delivery half, TWO-PHASE since 1.80: read the session's pending completions, emit
393
+ * one `workflow_complete` out-of-band frame per entry, and `ack` (remove) each entry only AFTER its emit
394
+ * SUCCEEDED — a failed frame write (dead socket / durable-append error) leaves the entry pending, so the next
395
+ * leg re-delivers instead of silently eating it (the 1.77-review "drained-but-append-failed" window, closed).
396
+ * Called at STREAM-OPEN of EVERY run leg (the sync `POST /v1/runs` SSE leg emits a live frame; the bg + resume
397
+ * legs append a durable `workflow_complete` event their tailing client replays). Best-effort throughout (the
398
+ * deterministic `WorkflowStatus` poll is the floor); at-least-once — the shell dedups by runId (two legs racing
399
+ * the same session may double-emit; same contract).
400
+ *
401
+ * 🔒 Tenancy (defence-in-depth over the authorizer's sessionId→owner gate): only entries whose stamped
402
+ * `owner` matches `callerPrincipal` are EMITTED — a session deleted-then-reclaimed by a different tenant
403
+ * (which rotates the sessionId→owner binding) can't surface the prior owner's completions. `owner: null`
404
+ * (single-user turnkey) has no tenancy boundary → always emitted. A non-matching entry is a DELIBERATE drop —
405
+ * acked (the session is gone/reclaimed for its original owner; the poll floor is their fallback), so it never
406
+ * wedges the queue.
407
+ */
408
+ export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPrincipal, emit,
409
+ /** Emit-TARGET diagnostics — WHICH leg drained (route) onto WHAT kind of connection
410
+ * (live SSE write vs durable event-log append), plus the enqueue→emit lag, so a "workflow completed but the
411
+ * shell only saw it on the next manual turn" report can be pinned to the leg that finally carried the frame
412
+ * (the push is stream-open-driven by design — a session with NO open leg has no push path until its next
413
+ * leg; the lag makes that visible instead of a mystery). Info-level, one line per delivered frame. */
414
+ diag) {
241
415
  if (!inbox || !sessionId)
242
416
  return;
417
+ // Diagnostics must never influence delivery (1.108 review): a throwing log sink inside the try would
418
+ // misroute an already-delivered-and-acked frame into the emit_failed line — a lie in the very diagnostics
419
+ // this sink exists to provide. Swallow here; the drain semantics stay byte-identical with diag absent.
243
420
  const dlog = (msg, meta) => {
244
421
  try {
245
422
  diag?.log(msg, meta);
246
423
  }
247
424
  catch {
425
+ /* never let diagnostics fault the drain */
248
426
  }
249
427
  };
250
428
  let entries;
@@ -252,28 +430,34 @@ export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPri
252
430
  entries = await inbox.pending(sessionId);
253
431
  }
254
432
  catch {
255
- return;
433
+ return; // best-effort push — the model can always fall back to the WorkflowStatus poll
256
434
  }
257
435
  for (const e of entries) {
258
436
  try {
259
437
  if (e.owner !== null && e.owner !== callerPrincipal) {
260
- await inbox.ack(sessionId, e.runId);
438
+ await inbox.ack(sessionId, e.runId); // 🔒 not this caller's completion → deliberate drop (ack, never emit)
261
439
  dlog("workflow_complete_dropped_owner_mismatch", { route: diag?.route, sessionId, runId: e.runId });
262
440
  continue;
263
441
  }
264
442
  if (e.kind === "task_notification") {
443
+ // A background subagent/bash completion parked while the session was idle — drain it as the
444
+ // SAME frame family the live legs emit (`task_notification`, task_id keyed), so the shell's existing
445
+ // renderer + dedup consume it unchanged. Extras (task_type/result/…) were bounded+redacted at enqueue.
265
446
  let extras = {};
266
447
  try {
267
448
  extras = e.payload ? JSON.parse(e.payload) : {};
268
449
  }
269
450
  catch {
451
+ /* a torn payload degrades to the summary-only frame */
270
452
  }
453
+ // `injected: false` AFTER the extras spread:恒量判别位([1818]§四 (b)案,与 project.ts live 投影
454
+ // 同义)——parked 通知更如实:park 发生恰因 leg 已死、core 的注入无处可去。
271
455
  await emit({ type: "task_notification", task_id: e.runId, status: e.status, summary: e.summary, ...extras, injected: false });
272
456
  }
273
457
  else {
274
458
  await emit({ type: "workflow_complete", runId: e.runId, status: e.status, summary: e.summary });
275
459
  }
276
- await inbox.ack(sessionId, e.runId);
460
+ await inbox.ack(sessionId, e.runId); // phase 2: the frame durably landed → remove
277
461
  dlog("workflow_complete_emitted", {
278
462
  route: diag?.route,
279
463
  connection: diag?.connection,
@@ -284,22 +468,51 @@ export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPri
284
468
  });
285
469
  }
286
470
  catch (err) {
471
+ // emit failed → NOT acked → re-delivered on the next leg (at-least-once; a dead socket / failed append
472
+ // must not fault the turn). An ack failure after a successful emit re-delivers too — same contract.
287
473
  dlog("workflow_complete_emit_failed", { route: diag?.route, connection: diag?.connection, sessionId, runId: e.runId, err: err instanceof Error ? err.message : String(err) });
288
474
  }
289
475
  }
290
476
  }
291
- export function taskNotificationInboxEntry(sessionId, owner, n, enqueuedAt, parentTaskId) {
477
+ /**
478
+ * The durable half is the deployment's job: map a core `TaskNotificationPayload`
479
+ * (background subagent / background bash completion) onto an inbox entry, for the IDLE gap — core's in-process
480
+ * injection covers a LIVE harness; a completion that lands after the leg ended had NO durable path (idle no-show,
481
+ * lost across restart). Bounded + redacted HERE (the store carries only egress-safe bytes; summary/result are
482
+ * UNTRUSTED free text). `status` collapses onto the inbox's completed|failed column; the ORIGINAL status rides
483
+ * the payload extras and overrides on drain (killed/cancelled reach the shell verbatim). Workflow-type
484
+ * notifications are the CALLER's job to filter (they have their own durable chain — double-enqueueing here would
485
+ * re-open the poll-served double-push).
486
+ */
487
+ export function taskNotificationInboxEntry(sessionId, owner, n, enqueuedAt,
488
+ /** lead-b (dual-ID audit): the PARENT run's durable taskId (the uuid the shell's footer rows
489
+ * key on) — rides the drained frame so a consumer can bind the a*-keyed child notification to its uuid-keyed
490
+ * parent row without a side lookup. */
491
+ parentTaskId) {
292
492
  const cap = (s, max) => (s.length > max ? `${s.slice(0, max)}…[+${s.length - max} chars]` : s);
293
493
  const extras = {
294
494
  task_type: n.task_type,
295
495
  ...(parentTaskId ? { parentTaskId } : {}),
496
+ // core 1.238: the payload's own sessionId — the stable a*↔local-uuid cross-system anchor
497
+ // (same handle TaskResult.sessionId/fleet rows carry). Optional (bash lane has none).
296
498
  ...(n.sessionId ? { sessionId: n.sessionId } : {}),
297
499
  ...(n.toolUseId ? { toolUseId: n.toolUseId } : {}),
298
- ...(n.status !== "completed" && n.status !== "failed" ? { status: n.status } : {}),
500
+ ...(n.status !== "completed" && n.status !== "failed" ? { status: n.status } : {}), // original rides through
501
+ // core drain 案: the Monitor/stoppedBy fields must survive the PARK too, or a drained event batch
502
+ // arrives with no dedup discriminator (`seq` — core's taskNotificationDedupKey folds it in), no line text,
503
+ // and no stop attribution. Same trust posture as the live projection (project.ts): seq/stoppedBy verbatim
504
+ // (core counter / open enum), lines = watched-process stdout → redact + cap each line.
299
505
  ...(n.seq !== undefined ? { seq: n.seq } : {}),
506
+ // 对抗评审 2026-07-11:redact BEFORE cap — capping first could bisect a secret so its residue no longer
507
+ // matches any redact pattern (a half-token is still a usable prefix). Order: redact, then bound.
300
508
  ...(n.lines !== undefined ? { lines: n.lines.map((l) => cap(redactSecrets(l), 1024)) } : {}),
301
509
  ...(n.stoppedBy !== undefined ? { stoppedBy: n.stoppedBy } : {}),
302
- ...(n.diagnostics !== undefined ? { diagnostics: cap(n.diagnostics, 4_000) } : {}),
510
+ ...(n.diagnostics !== undefined ? { diagnostics: cap(n.diagnostics, 4_000) } : {}), // [1412] 诊断槽(bounded;引擎值单源,park 原样过)
511
+ // (core 1.262.0 残局字段的 PARK 半场——live 腿 1.154.0 已放行,但「用户杀 bg 子代」的
512
+ // 主动线恰是 between-turns,通知落这里):与 project.ts 同一 trust posture——recentSteps.tool verbatim,
513
+ // target/outcome UNTRUSTED(命令行/工具输出首行可为 secret 形)→ redact+cap;editedFiles.path → redact+cap
514
+ // (attachedFiles 姿势),edits 数字 verbatim;resumable boolean verbatim。字段挑取绝不 spread;数组防御
515
+ // 截断(core 上游本就 bound ≤10 步——slice 是入库前的双保险)。
303
516
  ...(n.recentSteps !== undefined
304
517
  ? { recentSteps: n.recentSteps.slice(0, 10).map((s) => ({ tool: s.tool, target: cap(redactSecrets(s.target), 512), outcome: cap(redactSecrets(s.outcome), 512) })) }
305
518
  : {}),
@@ -313,14 +526,43 @@ export function taskNotificationInboxEntry(sessionId, owner, n, enqueuedAt, pare
313
526
  return {
314
527
  sessionId,
315
528
  owner,
529
+ // 对抗评审 2026-07-11(MED):the inbox key is REPLACE-on-runId (the workflow_complete contract) — keying a
530
+ // task_notification by the bare task_id COLLAPSES multiple between-turns notifications of the same bg task
531
+ // (a Monitor event batch, then the killed terminal: the terminal REPLACED the batch, drain emitted 1 of 2).
532
+ // Fold `seq` into the key (core's own taskNotificationDedupKey folds seq for exactly this reason); the frame's
533
+ // task_id comes from the extras override below, so the wire shape is unchanged. Same-key retries still dedup.
534
+ // design/144 §4 键升级:(域,taskId,status,seq?)——status 进键且保 RAW(修1:归一曾让同 id 的
535
+ // event/cancelled 两条 external 通知互吞),external 分域([769]①),seq 折入(逐周期)。
536
+ // wire 形状不变(task_id 从 extras override 恢复);存储 status 列(下一行)仍归一双终态,不受键影响。
316
537
  runId: taskNotificationFoldKey(n),
317
538
  status: n.status === "completed" ? "completed" : "failed",
318
539
  summary: cap(redactSecrets(n.summary ?? ""), 4000),
319
540
  enqueuedAt,
320
541
  kind: "task_notification",
321
- payload: JSON.stringify({ ...extras, task_id: n.task_id }),
542
+ payload: JSON.stringify({ ...extras, task_id: n.task_id }), // task_id rides extras → drain's spread restores it over the keyed runId
322
543
  };
323
544
  }
545
+ /**
546
+ * Resolve WHERE a workflow completion routes (core 1.208): the SESSION comes straight off
547
+ * `originatingSessionId` (the Runner closes over it at RunWorkflow mount — lookup-free). The OWNER comes from,
548
+ * in order:
549
+ * 1. the originating RUN ROW's owner (`getRun(sourceTaskId)`, 1.75) — immutable, stamped at submit. Only the
550
+ * BG leg has a run-row `sourceTaskId` though: the sync + resume legs deliberately leave `spec.taskId` unset
551
+ * (subagent nesting), so core derives `sourceTaskId = sessionId` and this lookup MISSES for them (review
552
+ * review, 1.78) — which under the old rule silently killed the multi-tenant push for every sync-leg workflow;
553
+ * 2. the payload's `principal` — core threads `spec.principal`, which in THIS service is ALWAYS the VERIFIED
554
+ * identity (the 1.55 F-fix invariant, main.ts: gated door = BFF-verified header, direct door = JWT `sub`,
555
+ * never the spoofable header; core 1.187 hardened the `TaskSpec.principal` contract to exactly this).
556
+ * Captured at SPAWN time → immutable, no delete-then-reclaim window. This is also the same value core's own
557
+ * workflow scope guard (`canAccessWorkflowRun`) keys off;
558
+ * 3. neither → single-user stamps `owner: null` (no tenancy boundary); multi-tenant (`requirePrincipal`)
559
+ * returns undefined = DROP fail-closed (an unverifiable owner must not ride the reclaim gate; the
560
+ * `WorkflowStatus` poll floor covers). Returns undefined when there is no route at all.
561
+ * The `getRun` lookup also carries the session FALLBACK for a crash-RECOVERY notify payload that lacks
562
+ * `originatingSessionId` — since core 1.210 the durable `WorkflowRun` records it and the recovery
563
+ * re-derivation threads it through (workflow-notify-journal), so this fallback covers only pre-1.210 rows
564
+ * (or a writer that omitted it), not the recovery path wholesale.
565
+ */
324
566
  export async function resolveCompletionRoute(p, getRun, requirePrincipal) {
325
567
  let sessionId = p.originatingSessionId;
326
568
  let owner;
@@ -329,23 +571,31 @@ export async function resolveCompletionRoute(p, getRun, requirePrincipal) {
329
571
  const originating = await getRun(p.sourceTaskId);
330
572
  if (originating) {
331
573
  sessionId ??= originating.sessionId ?? undefined;
332
- owner = originating.owner ?? null;
574
+ owner = originating.owner ?? null; // the run row's owner — stamped at submit, immutable
333
575
  }
334
576
  }
335
577
  catch {
578
+ // lookup outage: fall through — the session may still route via the payload; owner resolves below
336
579
  }
337
580
  }
338
581
  if (!sessionId)
339
- return undefined;
582
+ return undefined; // no route → poll floor
340
583
  if (owner === undefined && p.principal !== undefined)
341
- owner = p.principal;
584
+ owner = p.principal; // VERIFIED spec.principal (see doc #2)
342
585
  if (owner === undefined) {
343
586
  if (requirePrincipal)
344
- return undefined;
345
- owner = null;
587
+ return undefined; // multi-tenant + no verifiable owner → fail-closed (poll floor)
588
+ owner = null; // single-user turnkey: no tenancy boundary
346
589
  }
347
590
  return { sessionId, owner };
348
591
  }
592
+ /**
593
+ * core 1.232 — resolve WHICH session's inbox entry a poll-served ack targets: the payload's
594
+ * `originatingSessionId` directly (core threads it on `ackServed`), else the originating run row via
595
+ * `sourceTaskId` (mirrors {@link resolveCompletionRoute}'s session half — same fallback, same reason:
596
+ * older runs may predate the lookup-free field). Returns undefined when there is no attribution — the
597
+ * entry stays pending (at-least-once; the shell's own dedup covers a rare duplicate).
598
+ */
349
599
  export async function resolveServedSession(p, getRun) {
350
600
  if (p.originatingSessionId)
351
601
  return p.originatingSessionId;
@@ -354,6 +604,7 @@ export async function resolveServedSession(p, getRun) {
354
604
  return (await getRun(p.sourceTaskId))?.sessionId ?? undefined;
355
605
  }
356
606
  catch {
607
+ /* lookup outage → no attribution; the poll floor + shell dedup cover */
357
608
  }
358
609
  }
359
610
  return undefined;