toolroll 0.5.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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
package/dist/exec.js ADDED
@@ -0,0 +1,1841 @@
1
+ /**
2
+ * Running external commands.
3
+ *
4
+ * Two rules define this module. Arguments are passed as an array and never
5
+ * through a shell, so a directory named `; rm -rf ~` is a string and not a
6
+ * command. And nothing here rejects: a failed command is a value. Discovery
7
+ * walks repos it has never seen, and one broken repo must not end the scan.
8
+ */
9
+ import { linuxFenceAvailable, linuxFenced, macosFenceAvailable, macosFenced } from "./agent-fence.js";
10
+ import { execFile, spawn } from "node:child_process";
11
+ import { realpathSync } from "node:fs";
12
+ import { fileURLToPath } from "node:url";
13
+ import { observeProcessTree, stopProcessTree, readProcessObservationFailure } from "./process-tree.js";
14
+ import { jsonlDiscriminants } from "./jsonl-discriminants.js";
15
+ import { createContainer, currentContainment } from "./containment.js";
16
+ import { claudeLimitsOf, noteLimits } from "./provider-limits.js";
17
+ /** Shell convention for "could not be executed": the refusal before any target ran. */
18
+ export const CONTAINMENT_REFUSED_CODE = 126;
19
+ /** Live provider children, for the deterministic stop. Registered only when `processGroup` was set. */
20
+ const liveProviders = new Set();
21
+ /** Live children by owner tag (v52): the exact-attempt stop's handle. A
22
+ * child is registered here beside liveProviders when its options named an
23
+ * owner, and dropped the moment it closes. */
24
+ const ownedChildren = new Map();
25
+ function registerOwned(owner, child) {
26
+ if (owner === undefined)
27
+ return;
28
+ let set = ownedChildren.get(owner);
29
+ if (set === undefined) {
30
+ set = new Set();
31
+ ownedChildren.set(owner, set);
32
+ }
33
+ set.add(child);
34
+ child.once("close", () => {
35
+ const owned = ownedChildren.get(owner);
36
+ if (owned === undefined)
37
+ return;
38
+ owned.delete(child);
39
+ if (owned.size === 0)
40
+ ownedChildren.delete(owner);
41
+ });
42
+ }
43
+ /** The native OS object behind a live child, when it has one. */
44
+ const containers = new WeakMap();
45
+ /** Thrown BEFORE any spawn when required containment cannot be had. */
46
+ export class ContainmentRefusal extends Error {
47
+ name = "ContainmentRefusal";
48
+ }
49
+ /** A spawn callback refused custody AFTER the child existed: the caller
50
+ * must reap exactly this child before answering. */
51
+ class SpawnCustodyFailure extends Error {
52
+ child;
53
+ reason;
54
+ name = "SpawnCustodyFailure";
55
+ constructor(child, reason) {
56
+ super(String(reason));
57
+ this.child = child;
58
+ this.reason = reason;
59
+ }
60
+ }
61
+ /** The child a failed spawn road left behind, if any. */
62
+ function rejectedChild(error, child) {
63
+ return error instanceof SpawnCustodyFailure ? error.child : child;
64
+ }
65
+ /**
66
+ * The ONE spawn road for containable children (buffered, streaming and
67
+ * held transports): consult the pinned policy, make this spawn's OS
68
+ * object, spawn the target INSIDE it, and register the object beside the
69
+ * handle. Under observed containment this is exactly the old spawn. A
70
+ * required policy that cannot be met throws ContainmentRefusal here —
71
+ * nothing has been spawned, so the caller's reap is a no-op and its
72
+ * result says `refused` in the policy's words.
73
+ */
74
+ function spawnContained(file, args, spawnOptions, label, bag, contain = true) {
75
+ // The agent fence wraps the target itself (sandbox-exec then execs it, same pid).
76
+ if (bag.fence !== undefined && bag.fence.length > 0 && macosFenceAvailable())
77
+ ({ file, args } = macosFenced(file, args, bag.fence));
78
+ else if (bag.fence !== undefined && bag.fence.length > 0 && linuxFenceAvailable())
79
+ ({ file, args } = linuxFenced(file, args, bag.fence));
80
+ // The policy refusal comes first: nothing is reserved, recorded or
81
+ // spawned for a spawn that cannot be contained as required.
82
+ const effective = contain ? currentContainment() : null;
83
+ if (effective !== null && effective.refusal !== null)
84
+ throw new ContainmentRefusal(effective.refusal);
85
+ if (bag.beforeSpawn?.() === false)
86
+ throw new Error("the attempt stopped before this process could spawn");
87
+ const made = effective === null ? { container: null } : createContainer(effective, label);
88
+ if ("refused" in made)
89
+ throw new ContainmentRefusal(made.refused);
90
+ const container = made.container;
91
+ try {
92
+ if (container !== null)
93
+ bag.onContainer?.({ backend: container.backend, id: container.id, ...(container.identity ? { identity: container.identity } : {}) });
94
+ }
95
+ catch (error) {
96
+ container?.release();
97
+ throw error;
98
+ }
99
+ let launch;
100
+ try {
101
+ launch = container === null ? null : container.launch(file, args);
102
+ }
103
+ catch (error) {
104
+ container?.release();
105
+ throw error;
106
+ }
107
+ let child;
108
+ try {
109
+ child = spawn(launch === null ? file : launch.file, launch === null ? [...args] : launch.args, {
110
+ shell: false,
111
+ windowsHide: true,
112
+ detached: spawnOptions.detached,
113
+ stdio: [...spawnOptions.stdio, ...(launch === null ? [] : launch.extraStdio)],
114
+ ...(spawnOptions.cwd === undefined ? {} : { cwd: spawnOptions.cwd }),
115
+ ...(spawnOptions.env === undefined ? {} : { env: spawnOptions.env }),
116
+ });
117
+ }
118
+ catch (error) {
119
+ // The OS spawn itself threw: this invocation made no target process.
120
+ try {
121
+ if (container !== null)
122
+ bag.onContainerEmpty?.();
123
+ }
124
+ catch { }
125
+ container?.release();
126
+ throw error;
127
+ }
128
+ if (container !== null) {
129
+ containers.set(child, container);
130
+ // A descendant may keep stdout/stderr open after the root exits. Begin
131
+ // cleanup at root exit; waiting for `close` would wait for that descendant.
132
+ child.once("exit", () => {
133
+ void settleContainer(container, bag).then(empty => {
134
+ if (!empty) {
135
+ child.stdout?.destroy();
136
+ child.stderr?.destroy();
137
+ }
138
+ });
139
+ });
140
+ child.once("error", () => { void settleContainer(container, bag); });
141
+ }
142
+ try {
143
+ if (child.pid !== undefined) {
144
+ bag.onSpawn?.(child.pid);
145
+ }
146
+ }
147
+ catch (error) {
148
+ throw new SpawnCustodyFailure(child, error);
149
+ }
150
+ const attached = launch === null || child.pid === undefined ? Promise.resolve({ ok: true }) : launch.attach(child);
151
+ return { child, container, attached };
152
+ }
153
+ /**
154
+ * The settlement every containable transport runs at the root's exit:
155
+ * members that outlived the root (a setsid'd helper, a double fork) are
156
+ * killed and the OS asked for its empty state, bounded. The answer is
157
+ * the proof the custody record keeps — `false` leaves the record
158
+ * unproven and every stop/resume fence closed.
159
+ */
160
+ const containerSettlements = new WeakMap();
161
+ function settleContainer(container, bag, timeoutMs = 5_000) {
162
+ const existing = containerSettlements.get(container);
163
+ if (existing !== undefined)
164
+ return existing;
165
+ const settling = finishContainer(container, bag, timeoutMs);
166
+ containerSettlements.set(container, settling);
167
+ return settling;
168
+ }
169
+ async function finishContainer(container, bag, timeoutMs) {
170
+ let empty = container.populated() === false;
171
+ try {
172
+ if (!empty)
173
+ empty = await container.kill(timeoutMs);
174
+ }
175
+ catch {
176
+ empty = false;
177
+ }
178
+ try {
179
+ if (empty)
180
+ bag.onContainerEmpty?.();
181
+ else
182
+ bag.onUnknown?.();
183
+ }
184
+ catch {
185
+ // Custody that cannot be written stays unproven; the fences hold.
186
+ empty = false;
187
+ }
188
+ try {
189
+ container.release();
190
+ }
191
+ catch { /* proof already determines the result */ }
192
+ return empty;
193
+ }
194
+ /** The result's containment field for a spawn that ran inside an object. */
195
+ function containmentOf(container, empty, attachFailure) {
196
+ if (attachFailure !== null)
197
+ return { containment: { refused: attachFailure } };
198
+ if (container === null || empty === null)
199
+ return {};
200
+ return { containment: { backend: container.backend, id: container.id, empty } };
201
+ }
202
+ /** The streaming transports' spawn: contained when the spawn is a provider (process group), plain otherwise. */
203
+ function spawnStream(file, args, options, stdio, childEnv) {
204
+ return spawnContained(file, args, { cwd: options.cwd, env: childEnv, stdio, detached: options.processGroup === true && process.platform !== "win32" }, options.owner ?? "stream", { beforeSpawn: options.beforeSpawn, onSpawn: options.onSpawn, onContainer: options.onContainer, onContainerEmpty: options.onContainerEmpty, onUnknown: options.onUnknown, fence: options.fence }, options.processGroup === true);
205
+ }
206
+ /** The refusal-before-spawn shape shared by every containable transport. */
207
+ function refusedResult(error) {
208
+ if (error instanceof ContainmentRefusal) {
209
+ return { code: CONTAINMENT_REFUSED_CODE, stdout: "", stderr: error.message, timedOut: false, notFound: false, containment: { refused: error.message } };
210
+ }
211
+ return { code: 1, stdout: "", stderr: String(error instanceof SpawnCustodyFailure ? error.reason : error), timedOut: false, notFound: false };
212
+ }
213
+ const databaseOwners = new WeakMap();
214
+ let memoryDatabaseOwner = 0;
215
+ /** Run IDs are local to a database. Connections to the same file share
216
+ * custody; separate files and separate in-memory databases never do. */
217
+ export function runOwnerTag(store, runId) {
218
+ let owner = databaseOwners.get(store.handle);
219
+ if (owner === undefined) {
220
+ const main = store.handle.prepare("PRAGMA database_list").all().find(row => row["name"] === "main");
221
+ const file = main?.["file"];
222
+ owner = typeof file === "string" && file !== "" ? `file:${realpathSync(file)}` : `memory:${++memoryDatabaseOwner}`;
223
+ databaseOwners.set(store.handle, owner);
224
+ }
225
+ return JSON.stringify([owner, runId]);
226
+ }
227
+ /** How many live children this process still tracks under an owner tag. */
228
+ // Zero is local to this process registry. Recovery must also inspect durable
229
+ // witnesses and its authenticated OS census; this count is not an absence proof.
230
+ export function ownedProcessCount(owner) {
231
+ return ownedChildren.get(owner)?.size ?? 0;
232
+ }
233
+ /**
234
+ * The exact-attempt stop (v52): SIGKILL the process group of every live
235
+ * child registered under the owner tag — and only those. Returns how many
236
+ * were signalled. Idempotent: a child already gone was dropped at close,
237
+ * and a tag nobody registered kills nothing. This is the ONLY kill road a
238
+ * task action may take; the global sweep below is the watch's shutdown.
239
+ */
240
+ export function terminateOwnedProcesses(owner) {
241
+ const owned = ownedChildren.get(owner);
242
+ if (owned === undefined)
243
+ return 0;
244
+ let terminated = 0;
245
+ for (const child of owned) {
246
+ if (child.exitCode !== null || child.signalCode !== null)
247
+ continue;
248
+ killGroup(child);
249
+ terminated += 1;
250
+ }
251
+ return terminated;
252
+ }
253
+ /**
254
+ * Live held-session SUPERVISORS (v6 W7). Not in liveProviders: SIGKILLing a
255
+ * supervisor's group would orphan the agent living in its own fresh group.
256
+ * The sweep's contract here is SIGTERM first — the supervisor's handler
257
+ * fences its child (EOF → grace → group SIGKILL) — and SIGKILL only on a
258
+ * repeat sweep, which is the one road that can orphan an agent and is why
259
+ * it is the escalation, never the opener.
260
+ */
261
+ const heldSupervisors = new Map();
262
+ /** SIGKILL the child's whole process group; fall back to the child alone. */
263
+ function killGroup(child) {
264
+ const container = containers.get(child);
265
+ if (container !== undefined) {
266
+ // The OS object reaches every member at once — detached helpers
267
+ // included — and its empty state is awaited by the transport's
268
+ // settlement, never assumed here.
269
+ void container.kill().catch(() => { });
270
+ // The Windows helper is the job's owner and reporter: it terminates the
271
+ // job on the order above and answers "empty"; killing it instead would
272
+ // close the handle (kill-on-close still ends the job) but lose the proof.
273
+ if (container.backend !== "job-object") {
274
+ try {
275
+ child.kill("SIGKILL");
276
+ }
277
+ catch { /* already gone */ }
278
+ }
279
+ return;
280
+ }
281
+ if (stopProcessTree(child))
282
+ return;
283
+ const pid = child.pid;
284
+ if (pid !== undefined && process.platform === "win32") {
285
+ // Windows has no process groups to signal (Codex M5-M8 audit, IV-6):
286
+ // taskkill /T walks the tree, so a harness's shell grandchildren die
287
+ // with it instead of writing to the worktree after "stopped". Untested
288
+ // on physical Windows, like the daemon — stated, not hidden.
289
+ try {
290
+ spawn("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }).on("error", () => { });
291
+ }
292
+ catch {
293
+ // taskkill missing or refused — the direct kill below still runs.
294
+ }
295
+ }
296
+ if (pid !== undefined && process.platform !== "win32") {
297
+ try {
298
+ process.kill(-pid, "SIGKILL");
299
+ return;
300
+ }
301
+ catch {
302
+ // No group of ours (not detached, or already gone) — fall through.
303
+ }
304
+ }
305
+ try {
306
+ child.kill("SIGKILL");
307
+ }
308
+ catch {
309
+ // Already gone.
310
+ }
311
+ }
312
+ /**
313
+ * The hard stop (M6.12): SIGKILL every live provider's process group.
314
+ * Called by the watch when its stop grace expires — never on the first
315
+ * signal, which stays graceful. Late output cannot commit or publish:
316
+ * the killed process exits nonzero, the build finalizes as the failure
317
+ * it is, and every commit sits behind fences the corpse cannot pass.
318
+ */
319
+ export function terminateLiveProviders() {
320
+ let terminated = 0;
321
+ for (const child of liveProviders) {
322
+ killGroup(child);
323
+ terminated += 1;
324
+ }
325
+ // Held supervisors take the graceful road first (v6 W7): SIGTERM makes
326
+ // the supervisor fence its own agent race-free through the handle only
327
+ // it holds. A second sweep escalates to SIGKILL — the documented
328
+ // residual hole, reachable only by double-escalation, with the startup
329
+ // orphan fence paging for anything that lingers.
330
+ for (const [supervisor, stage] of heldSupervisors) {
331
+ try {
332
+ if (stage === "fresh") {
333
+ supervisor.kill("SIGTERM");
334
+ heldSupervisors.set(supervisor, "termed");
335
+ }
336
+ else {
337
+ // With a native OS object the escalation reaches the agent too:
338
+ // the object's kill is the one road that cannot orphan it.
339
+ killGroup(supervisor);
340
+ }
341
+ }
342
+ catch {
343
+ // Already gone.
344
+ }
345
+ terminated += 1;
346
+ }
347
+ return terminated;
348
+ }
349
+ /** The one place a child's environment is decided (audit IV-5). */
350
+ /**
351
+ * Chat credentials never reach a child process (Codex chat v3 review,
352
+ * change 10): ANTHROPIC_API_KEY is the chat key AND would silently flip
353
+ * the claude harness from subscription auth to API billing if inherited.
354
+ * A caller that genuinely needs it re-supplies it via `env` explicitly.
355
+ * (OPENROUTER_API_KEY is not listed: the openrouter BUILD adapter reads
356
+ * it from the child env by design, and the agent-facing shell excludes
357
+ * it separately via the provider's own config.)
358
+ */
359
+ const CHAT_KEYS_NEVER_INHERITED = ["ANTHROPIC_API_KEY"];
360
+ function resolveChildEnv(options) {
361
+ const { env, omitEnv, envAllowlist } = options;
362
+ if (envAllowlist !== undefined) {
363
+ const picked = {};
364
+ for (const name of envAllowlist) {
365
+ const value = process.env[name];
366
+ if (value !== undefined)
367
+ picked[name] = value;
368
+ }
369
+ Object.assign(picked, env ?? {});
370
+ for (const name of omitEnv ?? [])
371
+ delete picked[name];
372
+ for (const name of CHAT_KEYS_NEVER_INHERITED) {
373
+ if (env?.[name] === undefined)
374
+ delete picked[name];
375
+ }
376
+ return picked;
377
+ }
378
+ const merged = { ...process.env, ...(env ?? {}) };
379
+ for (const name of omitEnv ?? [])
380
+ delete merged[name];
381
+ for (const name of CHAT_KEYS_NEVER_INHERITED) {
382
+ if (env?.[name] === undefined)
383
+ delete merged[name];
384
+ }
385
+ return merged;
386
+ }
387
+ /** Conventions from the shell and from coreutils `timeout(1)`. */
388
+ export const NOT_FOUND_CODE = 127;
389
+ export const TIMEOUT_CODE = 124;
390
+ /** Output too large to hold. Not a timeout, and must not be reported as one. */
391
+ /** A spawn callback records custody after the child exists. If that write
392
+ * fails, stop and reap the owned child before reporting failure; otherwise
393
+ * an unrecorded provider could keep writing after its caller retries. */
394
+ function reapRejectedSpawn(child, group) {
395
+ if (child === undefined)
396
+ return Promise.resolve();
397
+ return new Promise(resolve => {
398
+ if (group)
399
+ liveProviders.add(child);
400
+ child.once("close", () => {
401
+ liveProviders.delete(child);
402
+ const container = containers.get(child);
403
+ if (container === undefined)
404
+ resolve();
405
+ else
406
+ void settleContainer(container, {}).then(() => resolve());
407
+ });
408
+ child.on("error", () => { });
409
+ child.stdout?.resume();
410
+ child.stderr?.resume();
411
+ if (group)
412
+ killGroup(child);
413
+ else {
414
+ try {
415
+ child.kill("SIGKILL");
416
+ }
417
+ catch { /* The child must still exit before failure is returned. */ }
418
+ }
419
+ });
420
+ }
421
+ export const OVERFLOW_CODE = 125;
422
+ export const DEFAULT_TIMEOUT_MS = 15_000;
423
+ export const DEFAULT_MAX_BUFFER = 8 * 1024 * 1024;
424
+ /**
425
+ * A streaming process may run for hours while it is making observable
426
+ * progress. This controller keeps the ordinary hard timeout available for
427
+ * bounded work, while allowing provider callers to choose an activity-based
428
+ * watchdog with no wall-clock deadline. Both roads share the same kill and
429
+ * settlement semantics.
430
+ */
431
+ function streamWatchdog(options, onTimeout) {
432
+ const hardMs = options.timeoutMs ?? (options.idleTimeoutMs === undefined ? DEFAULT_TIMEOUT_MS : undefined);
433
+ const idleMs = options.idleTimeoutMs;
434
+ let fired = false;
435
+ let hardTimer;
436
+ let idleTimer;
437
+ const fire = () => {
438
+ if (fired)
439
+ return;
440
+ fired = true;
441
+ onTimeout();
442
+ };
443
+ const armIdle = () => {
444
+ if (idleMs === undefined)
445
+ return;
446
+ if (idleTimer !== undefined)
447
+ clearTimeout(idleTimer);
448
+ idleTimer = setTimeout(fire, idleMs);
449
+ idleTimer.unref?.();
450
+ };
451
+ if (hardMs !== undefined) {
452
+ hardTimer = setTimeout(fire, hardMs);
453
+ hardTimer.unref?.();
454
+ }
455
+ armIdle();
456
+ return {
457
+ touch: armIdle,
458
+ stop: () => {
459
+ if (hardTimer !== undefined)
460
+ clearTimeout(hardTimer);
461
+ if (idleTimer !== undefined)
462
+ clearTimeout(idleTimer);
463
+ },
464
+ };
465
+ }
466
+ const MAX_BUFFER_ERROR = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
467
+ /**
468
+ * A spawn the OS refused for the moment — no free process slot or file
469
+ * descriptor (a loaded machine, a fork storm from a parallel test suite) —
470
+ * is not the command failing: the command never ran. Left as an exit 1, a
471
+ * `git symbolic-ref` that never started reads as "no branch checked out"
472
+ * and an unattended pass refuses on a lie. So the two buffered transports
473
+ * retry EXACTLY this class, bounded, before answering.
474
+ */
475
+ const TRANSIENT_SPAWN_CODES = new Set(["EAGAIN", "EMFILE", "ENFILE"]);
476
+ export const SPAWN_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
477
+ export function isTransientSpawnFailure(error) {
478
+ return error !== null && error !== undefined && typeof error.code === "string" && TRANSIENT_SPAWN_CODES.has(error.code);
479
+ }
480
+ /**
481
+ * Retry `attempt` while it reports a transient spawn failure, sleeping the
482
+ * given delays between tries; the last answer stands when they run out.
483
+ * A non-transient answer — success, a real exit code, not found, timeout —
484
+ * is returned at once: only the never-started case is retried.
485
+ */
486
+ export async function retryTransientSpawn(attempt, delays = SPAWN_RETRY_DELAYS_MS, sleep = ms => new Promise(resolve => setTimeout(resolve, ms))) {
487
+ let last = await attempt();
488
+ for (const delay of delays) {
489
+ if (!last.transient)
490
+ return last.result;
491
+ await sleep(delay);
492
+ last = await attempt();
493
+ }
494
+ return last.result;
495
+ }
496
+ export function run(file, args, options = {}) {
497
+ const { cwd, timeoutMs = DEFAULT_TIMEOUT_MS, maxBuffer = DEFAULT_MAX_BUFFER } = options;
498
+ const childEnv = resolveChildEnv(options);
499
+ // A provider run needs its own process group; execFile cannot give one,
500
+ // so the buffered path detours through spawn with identical semantics.
501
+ if (options.processGroup === true) {
502
+ return retryTransientSpawn(() => runBufferedGroup(file, args, {
503
+ timeoutMs,
504
+ maxBuffer,
505
+ ...(options.stdin === undefined ? {} : { stdin: options.stdin }),
506
+ ...(cwd === undefined ? {} : { cwd }),
507
+ ...(childEnv === undefined ? {} : { childEnv }),
508
+ ...(options.onSpawn === undefined ? {} : { onSpawn: options.onSpawn }),
509
+ ...(options.owner === undefined ? {} : { owner: options.owner }),
510
+ ...(options.beforeSpawn === undefined ? {} : { beforeSpawn: options.beforeSpawn }),
511
+ ...(options.onStdout === undefined ? {} : { onStdout: options.onStdout }),
512
+ ...(options.onStderr === undefined ? {} : { onStderr: options.onStderr }),
513
+ ...(options.fence === undefined ? {} : { fence: options.fence }),
514
+ ...(options.onDescendant === undefined ? {} : { onDescendant: options.onDescendant }),
515
+ ...(options.onDescendantWriteFailure === undefined ? {} : { onDescendantWriteFailure: options.onDescendantWriteFailure }),
516
+ ...(options.onDescendantExit === undefined ? {} : { onDescendantExit: options.onDescendantExit }),
517
+ ...(options.onUnknown === undefined ? {} : { onUnknown: options.onUnknown }),
518
+ ...(options.onObservationFailure === undefined ? {} : { onObservationFailure: options.onObservationFailure }),
519
+ ...(options.onContainer === undefined ? {} : { onContainer: options.onContainer }),
520
+ ...(options.onContainerEmpty === undefined ? {} : { onContainerEmpty: options.onContainerEmpty }),
521
+ }));
522
+ }
523
+ return retryTransientSpawn(() => new Promise(resolve => {
524
+ execFile(file, [...args], {
525
+ cwd,
526
+ timeout: timeoutMs,
527
+ maxBuffer,
528
+ encoding: "utf8",
529
+ shell: false,
530
+ windowsHide: true,
531
+ ...(childEnv === undefined ? {} : { env: childEnv }),
532
+ }, (error, stdout, stderr) => {
533
+ if (error === null) {
534
+ resolve({ result: { code: 0, stdout, stderr, timedOut: false, notFound: false }, transient: false });
535
+ return;
536
+ }
537
+ resolve({
538
+ result: describeFailure(error, stdout, stderr),
539
+ transient: isTransientSpawnFailure(error),
540
+ });
541
+ });
542
+ }));
543
+ }
544
+ /**
545
+ * The buffered transport for process-group providers (claude): spawn
546
+ * detached so the harness and everything it launches share one killable
547
+ * group, collect output up to the same maxBuffer contract as execFile,
548
+ * and register as a live provider for the watch's hard stop.
549
+ */
550
+ function runBufferedGroup(file, args, bag) {
551
+ return new Promise(resolve => {
552
+ let child;
553
+ let container = null;
554
+ let attached;
555
+ try {
556
+ const spawned = spawnContained(file, args, { cwd: bag.cwd, env: bag.childEnv, stdio: [bag.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"], detached: process.platform !== "win32" }, bag.owner ?? "buffered", { beforeSpawn: bag.beforeSpawn, onSpawn: bag.onSpawn, onContainer: bag.onContainer, onContainerEmpty: bag.onContainerEmpty, onUnknown: bag.onUnknown, fence: bag.fence });
557
+ child = spawned.child;
558
+ container = spawned.container;
559
+ attached = spawned.attached;
560
+ }
561
+ catch (error) {
562
+ const left = rejectedChild(error, child);
563
+ void reapRejectedSpawn(left, true).then(() => {
564
+ resolve({
565
+ result: refusedResult(error),
566
+ transient: left === undefined && isTransientSpawnFailure(error),
567
+ });
568
+ });
569
+ return;
570
+ }
571
+ liveProviders.add(child);
572
+ registerOwned(bag.owner, child);
573
+ if (container === null)
574
+ observeProcessTree(child, bag);
575
+ let stdout = "";
576
+ let stderr = "";
577
+ let timedOut = false;
578
+ let overflowed = false;
579
+ let inputFailed = false;
580
+ let notFound = false;
581
+ let attachFailure = null;
582
+ void attached.then(outcome => { if (!outcome.ok)
583
+ attachFailure = outcome.detail; });
584
+ const timer = setTimeout(() => {
585
+ timedOut = true;
586
+ killGroup(child);
587
+ }, bag.timeoutMs);
588
+ child.stdout?.setEncoding("utf8");
589
+ child.stdout?.on("data", (chunk) => {
590
+ stdout += chunk;
591
+ if (stdout.length > bag.maxBuffer) {
592
+ overflowed = true;
593
+ killGroup(child);
594
+ }
595
+ try {
596
+ bag.onStdout?.(chunk);
597
+ }
598
+ catch { /* a listener never breaks the run */ }
599
+ });
600
+ child.stderr?.setEncoding("utf8");
601
+ child.stderr?.on("data", (chunk) => {
602
+ if (stderr.length < bag.maxBuffer)
603
+ stderr += chunk.slice(0, bag.maxBuffer - stderr.length);
604
+ try {
605
+ bag.onStderr?.(chunk);
606
+ }
607
+ catch { /* a listener never breaks the run */ }
608
+ });
609
+ let settled = false;
610
+ let transient = false;
611
+ const finish = (code) => {
612
+ if (settled)
613
+ return;
614
+ settled = true;
615
+ clearTimeout(timer);
616
+ liveProviders.delete(child);
617
+ // The OS object is settled BEFORE the answer: a root that exited is
618
+ // not an empty container until the OS says so.
619
+ const settlement = container === null ? Promise.resolve(null) : settleContainer(container, bag);
620
+ void settlement.then(empty => {
621
+ resolve({
622
+ result: {
623
+ code: notFound ? NOT_FOUND_CODE : overflowed ? OVERFLOW_CODE : timedOut ? TIMEOUT_CODE : inputFailed ? 1 : attachFailure !== null || empty === false ? CONTAINMENT_REFUSED_CODE : (code ?? 1),
624
+ stdout,
625
+ stderr: empty === false ? `${stderr}\nNative process containment could not prove all descendants exited.`.trim() : attachFailure !== null && stderr === "" ? attachFailure : stderr,
626
+ // Overflow also killed the child; it is not a timeout and must not read as one.
627
+ timedOut: timedOut && !overflowed,
628
+ notFound,
629
+ ...containmentOf(container, empty, attachFailure),
630
+ },
631
+ transient,
632
+ });
633
+ });
634
+ };
635
+ child.on("error", error => {
636
+ notFound = error.code === "ENOENT";
637
+ transient = isTransientSpawnFailure(error);
638
+ if (stderr === "")
639
+ stderr = String(error);
640
+ finish(null);
641
+ });
642
+ child.on("close", code => finish(code));
643
+ if (bag.stdin !== undefined && child.stdin !== null) {
644
+ child.stdin.on("error", () => {
645
+ inputFailed = true;
646
+ stderr = "prompt input failed: the provider closed its input before delivery completed";
647
+ killGroup(child);
648
+ });
649
+ child.stdin.end(bag.stdin, "utf8");
650
+ }
651
+ });
652
+ }
653
+ /**
654
+ * Turn an execFile error into a result. The order matters: a maxBuffer overflow
655
+ * also arrives with `killed: true`, so it has to be recognised before the
656
+ * timeout check or a truncated `git log` reads as a hung one.
657
+ */
658
+ function describeFailure(error, stdout, stderr) {
659
+ const notFound = error.code === "ENOENT";
660
+ const overflowed = error.code === MAX_BUFFER_ERROR;
661
+ const timedOut = !overflowed && error.killed === true;
662
+ return {
663
+ code: resolveCode(error, { notFound, overflowed, timedOut }),
664
+ stdout,
665
+ stderr: stderr === "" ? error.message : stderr,
666
+ timedOut,
667
+ notFound,
668
+ };
669
+ }
670
+ function resolveCode(error, flags) {
671
+ if (typeof error.code === "number")
672
+ return error.code;
673
+ if (flags.notFound)
674
+ return NOT_FOUND_CODE;
675
+ if (flags.overflowed)
676
+ return OVERFLOW_CODE;
677
+ if (flags.timedOut)
678
+ return TIMEOUT_CODE;
679
+ return 1;
680
+ }
681
+ /**
682
+ * The streaming transport for JSONL-emitting providers.
683
+ *
684
+ * A long agent session can write far more event stream than any fixed
685
+ * buffer should hold, and the lines that matter — the session identity,
686
+ * the terminal usage — arrive LAST. Buffering-and-overflowing would kill
687
+ * the process at 8 MiB and lose exactly the facts a paid run must not
688
+ * lose (Codex provider review, high finding 2). So stdout is consumed
689
+ * incrementally and only the load-bearing lines are retained:
690
+ *
691
+ * - a bounded identity witness for `thread.started` (first usable id,
692
+ * first conflicting id, and last event), plus the LAST `turn.completed`
693
+ * and `turn.failed` line
694
+ * - the LAST `item.completed` line carrying an agent_message
695
+ *
696
+ * each capped per line; everything else is counted and dropped. The
697
+ * result's stdout is the retained lines joined — a synthetic, bounded
698
+ * envelope the parser reads exactly like test fixtures.
699
+ */
700
+ const JSONL_LINE_CAP = 64 * 1024;
701
+ const JSONL_STDERR_CAP = 64 * 1024;
702
+ /** Canonical identity at the transport boundary. Never persist or expose
703
+ * provider framing whitespace as part of a resumable session id. */
704
+ function transportSessionId(value) {
705
+ if (typeof value !== "string")
706
+ return null;
707
+ const id = value.trim();
708
+ return id === "" ? null : id;
709
+ }
710
+ /**
711
+ * Provider replies are nested inside their JSONL envelope. A 64 KiB reply
712
+ * therefore needs appreciably more than 64 KiB on the wire once quotes,
713
+ * backslashes, and control characters are escaped. 512 KiB carries that
714
+ * worst useful case while remaining a hard, per-event memory boundary.
715
+ */
716
+ export const JSONL_EVENT_HARD_CAP = 512 * 1024;
717
+ const JSONL_OVERFLOW_PREFIX_CAP = 4 * 1024;
718
+ /**
719
+ * Split a byte stream into JSONL records without ever accumulating an
720
+ * unbounded partial line. The callback is synchronous so the same fixed
721
+ * storage can be reused for the next record. Counting the original bytes
722
+ * (rather than JavaScript characters) makes the overflow receipt exact.
723
+ */
724
+ function boundedJsonlFramer(cap, onLine, inspectDiscriminants = false) {
725
+ const retained = Buffer.allocUnsafe(cap);
726
+ let retainedBytes = 0;
727
+ let originalBytes = 0;
728
+ let nonWhitespace = false;
729
+ let discriminants = inspectDiscriminants ? jsonlDiscriminants() : null;
730
+ const append = (part) => {
731
+ discriminants?.push(part);
732
+ originalBytes += part.length;
733
+ if (!nonWhitespace) {
734
+ for (const byte of part) {
735
+ if (byte !== 0x09 && byte !== 0x0a && byte !== 0x0d && byte !== 0x20) {
736
+ nonWhitespace = true;
737
+ break;
738
+ }
739
+ }
740
+ }
741
+ if (retainedBytes < cap) {
742
+ retainedBytes += part.copy(retained, retainedBytes, 0, cap - retainedBytes);
743
+ }
744
+ };
745
+ const emit = () => {
746
+ onLine({
747
+ prefix: retained.subarray(0, retainedBytes),
748
+ bytesOriginal: originalBytes,
749
+ overflowed: originalBytes > cap,
750
+ ...(discriminants === null ? {} : { discriminants: discriminants.finish() }),
751
+ });
752
+ retainedBytes = 0;
753
+ originalBytes = 0;
754
+ nonWhitespace = false;
755
+ discriminants = inspectDiscriminants ? jsonlDiscriminants() : null;
756
+ };
757
+ return {
758
+ push(chunk) {
759
+ let start = 0;
760
+ for (;;) {
761
+ const newline = chunk.indexOf(0x0a, start);
762
+ if (newline === -1) {
763
+ append(chunk.subarray(start));
764
+ return;
765
+ }
766
+ append(chunk.subarray(start, newline));
767
+ emit();
768
+ start = newline + 1;
769
+ }
770
+ },
771
+ finish() {
772
+ // Match the old `partial.trim() !== ""` rule: a final unterminated
773
+ // whitespace-only record is not an event and needs no receipt.
774
+ if (originalBytes > 0 && nonWhitespace)
775
+ emit();
776
+ },
777
+ };
778
+ }
779
+ function overflowReceiptText(transport, line) {
780
+ const rawPrefix = line.prefix.subarray(0, Math.min(line.prefix.length, JSONL_OVERFLOW_PREFIX_CAP));
781
+ return JSON.stringify({
782
+ type: "standing-orders.stream-event-overflow",
783
+ transport,
784
+ message: `The provider emitted a JSONL event larger than the ${JSONL_EVENT_HARD_CAP}-byte safety limit.`,
785
+ eventBytesOriginal: line.bytesOriginal,
786
+ hardCapBytes: JSONL_EVENT_HARD_CAP,
787
+ prefixBytes: rawPrefix.length,
788
+ prefix: rawPrefix.toString("utf8"),
789
+ // The readable prefix can end halfway through a UTF-8 character. This
790
+ // companion is the exact retained byte prefix for forensic evidence.
791
+ prefixBase64: rawPrefix.toString("base64"),
792
+ });
793
+ }
794
+ function malformedReceiptText(transport, line) {
795
+ const rawPrefix = line.prefix.subarray(0, Math.min(line.prefix.length, JSONL_OVERFLOW_PREFIX_CAP));
796
+ return JSON.stringify({
797
+ type: "standing-orders.stream-event-malformed",
798
+ transport,
799
+ message: "The provider emitted malformed JSON for a load-bearing stream event after initialization.",
800
+ eventBytesOriginal: line.bytesOriginal,
801
+ prefixBytes: rawPrefix.length,
802
+ prefix: rawPrefix.toString("utf8"),
803
+ prefixBase64: rawPrefix.toString("base64"),
804
+ });
805
+ }
806
+ function codexOverflowLine(line) {
807
+ return JSON.stringify({
808
+ type: "turn.failed",
809
+ error: {
810
+ type: "standing-orders.stream-event-overflow",
811
+ message: overflowReceiptText("codex", line),
812
+ },
813
+ });
814
+ }
815
+ function codexMalformedLine(line) {
816
+ return JSON.stringify({
817
+ type: "turn.failed",
818
+ error: {
819
+ type: "standing-orders.stream-event-malformed",
820
+ message: malformedReceiptText("codex", line),
821
+ },
822
+ });
823
+ }
824
+ function claudeOverflowLine(line) {
825
+ return JSON.stringify({
826
+ type: "result",
827
+ subtype: "standing-orders-stream-event-overflow",
828
+ is_error: true,
829
+ result: overflowReceiptText("claude", line),
830
+ });
831
+ }
832
+ function geminiOverflowLine(line) {
833
+ return JSON.stringify({
834
+ type: "result",
835
+ status: "standing-orders-stream-event-overflow",
836
+ error: { message: overflowReceiptText("gemini", line) },
837
+ });
838
+ }
839
+ function geminiMalformedLine(line) {
840
+ return JSON.stringify({
841
+ type: "result",
842
+ status: "standing-orders-stream-event-malformed",
843
+ error: { message: malformedReceiptText("gemini", line) },
844
+ });
845
+ }
846
+ /**
847
+ * Read only the leading, top-level `type` discriminant from an incomplete
848
+ * JSON object. Provider JSONL dialects put `type` first. Anchoring the match
849
+ * means content later in an oversized event can never spoof classification.
850
+ */
851
+ function leadingJsonObjectType(prefix) {
852
+ const match = /^\s*\{\s*"type"\s*:\s*"([A-Za-z0-9._-]+)"/.exec(prefix.toString("utf8", 0, Math.min(prefix.length, 256)));
853
+ return match?.[1] ?? null;
854
+ }
855
+ /** Codex emits `item` immediately after the outer type. Only an agent
856
+ * message is load-bearing; completed command/tool telemetry is droppable. */
857
+ function leadingCodexCompletedItemType(prefix) {
858
+ const match = /^\s*\{\s*"type"\s*:\s*"item\.completed"\s*,\s*"item"\s*:\s*\{\s*"type"\s*:\s*"([A-Za-z0-9._-]+)"/.exec(prefix.toString("utf8", 0, Math.min(prefix.length, 512)));
859
+ return match?.[1] ?? null;
860
+ }
861
+ function leadingClaudeSystemSubtype(prefix) {
862
+ const match = /^\s*\{\s*"type"\s*:\s*"system"\s*,\s*"subtype"\s*:\s*"([A-Za-z0-9._-]+)"/.exec(prefix.toString("utf8", 0, Math.min(prefix.length, 512)));
863
+ return match?.[1] ?? null;
864
+ }
865
+ function malformedCodexEventIsLoadBearing(prefix) {
866
+ const type = leadingJsonObjectType(prefix);
867
+ if (type === "thread.started" || type === "turn.completed" || type === "turn.failed")
868
+ return true;
869
+ if (type !== "item.completed")
870
+ return false;
871
+ const itemType = leadingCodexCompletedItemType(prefix);
872
+ return itemType === null || itemType === "agent_message";
873
+ }
874
+ function malformedGeminiEventIsLoadBearing(prefix) {
875
+ const type = leadingJsonObjectType(prefix);
876
+ // These two records authorize the session and the successful terminal.
877
+ // Malformed messages remain diagnostics only; startup prose, tool traffic,
878
+ // and warnings retain the transport's established noise tolerance.
879
+ return type === "init" || type === "result";
880
+ }
881
+ export function runStreamJsonl(file, args, options = {}) {
882
+ const { cwd } = options;
883
+ const childEnv = resolveChildEnv(options);
884
+ return new Promise(resolve => {
885
+ let child;
886
+ let container = null;
887
+ let attachFailure = null;
888
+ try {
889
+ const spawned = spawnStream(file, args, options, [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"], childEnv);
890
+ child = spawned.child;
891
+ container = spawned.container;
892
+ void spawned.attached.then(outcome => { if (!outcome.ok)
893
+ attachFailure = outcome.detail; });
894
+ }
895
+ catch (error) {
896
+ void reapRejectedSpawn(rejectedChild(error, child), options.processGroup === true).then(() => resolve(refusedResult(error)));
897
+ return;
898
+ }
899
+ if (options.processGroup === true)
900
+ liveProviders.add(child);
901
+ registerOwned(options.owner, child);
902
+ if (options.processGroup === true && container === null)
903
+ observeProcessTree(child, options);
904
+ let startedLine = null;
905
+ let startedIdentityLine = null;
906
+ let conflictingStartedLine = null;
907
+ let startedSessionId = null;
908
+ let completedLine = null;
909
+ let failedLine = null;
910
+ let overflowLine = null;
911
+ let malformedLine = null;
912
+ let lastMessage = null;
913
+ let stderr = "";
914
+ let timedOut = false;
915
+ let notFound = false;
916
+ let inputFailed = false;
917
+ const keep = (framed) => {
918
+ if (framed.overflowed) {
919
+ // Inspect structural keys across the complete byte stream, including
920
+ // keys AFTER discarded command output. Quoted or nested types cannot
921
+ // spoof these discriminants, and malformed/ambiguous input stays red.
922
+ const type = framed.discriminants?.type ?? null;
923
+ if (type !== null && !["thread.started", "turn.completed", "turn.failed", "item.completed"].includes(type)) {
924
+ return;
925
+ }
926
+ if (type === "item.completed") {
927
+ const itemType = framed.discriminants?.itemType;
928
+ if (itemType !== undefined && itemType !== null && ["command_execution", "mcp_tool_call", "web_search", "file_change", "todo_list"].includes(itemType))
929
+ return;
930
+ }
931
+ // A load-bearing or unclassifiable hard-cap breach is independently
932
+ // retained as failure. Once those bytes were discarded, a later
933
+ // ordinary message must never turn the run green.
934
+ if (overflowLine === null)
935
+ overflowLine = codexOverflowLine(framed);
936
+ return;
937
+ }
938
+ const line = framed.prefix.toString("utf8");
939
+ try {
940
+ const event = JSON.parse(line);
941
+ const type = String(event["type"] ?? "");
942
+ if (type === "thread.started") {
943
+ // Keep the last init for the existing transport contract and a
944
+ // bounded pair of earlier witnesses so a conflict cannot hide.
945
+ startedLine = line;
946
+ const id = transportSessionId(event["thread_id"]);
947
+ if (id !== null) {
948
+ // Retain only enough events to prove the identity contract:
949
+ // one usable id when the first init was blank, and one conflict.
950
+ if (startedSessionId === null) {
951
+ startedSessionId = id;
952
+ startedIdentityLine = line;
953
+ }
954
+ else if (id !== startedSessionId && conflictingStartedLine === null) {
955
+ conflictingStartedLine = line;
956
+ }
957
+ if (options.onSessionId !== undefined) {
958
+ try {
959
+ options.onSessionId(id);
960
+ }
961
+ catch {
962
+ // A registry that cannot be written must not kill the turn.
963
+ }
964
+ }
965
+ }
966
+ }
967
+ else if (type === "turn.completed") {
968
+ completedLine = line;
969
+ }
970
+ else if (type === "turn.failed") {
971
+ // Retain failure independently from completion: seeing either at
972
+ // any point is load-bearing, while one slot per kind stays bounded.
973
+ failedLine = line;
974
+ }
975
+ else if (type === "item.completed") {
976
+ const item = event["item"];
977
+ if (item !== undefined && String(item["type"] ?? "") === "agent_message") {
978
+ lastMessage = line;
979
+ }
980
+ }
981
+ if (options.onStreamEvent !== undefined) {
982
+ try {
983
+ options.onStreamEvent(event);
984
+ }
985
+ catch {
986
+ // Observational only: a broken listener never touches the run.
987
+ }
988
+ }
989
+ }
990
+ catch {
991
+ // Startup prose and ordinary malformed telemetry remain ignorable.
992
+ // Once initialization is proven, however, a recognizable control or
993
+ // answer record cannot disappear merely because its JSON was cut.
994
+ if (startedLine !== null &&
995
+ malformedLine === null &&
996
+ malformedCodexEventIsLoadBearing(framed.prefix)) {
997
+ malformedLine = codexMalformedLine(framed);
998
+ }
999
+ }
1000
+ };
1001
+ const framing = boundedJsonlFramer(JSONL_EVENT_HARD_CAP, keep, true);
1002
+ const watchdog = streamWatchdog(options, () => {
1003
+ timedOut = true;
1004
+ if (options.processGroup === true)
1005
+ killGroup(child);
1006
+ else
1007
+ child.kill("SIGKILL");
1008
+ });
1009
+ child.stdout?.on("data", (chunk) => {
1010
+ watchdog.touch();
1011
+ framing.push(chunk);
1012
+ });
1013
+ child.stderr?.setEncoding("utf8");
1014
+ child.stderr?.on("data", (chunk) => {
1015
+ watchdog.touch();
1016
+ if (stderr.length < JSONL_STDERR_CAP)
1017
+ stderr += chunk.slice(0, JSONL_STDERR_CAP - stderr.length);
1018
+ });
1019
+ let settled = false;
1020
+ const finish = (code) => {
1021
+ if (settled)
1022
+ return;
1023
+ settled = true;
1024
+ watchdog.stop();
1025
+ liveProviders.delete(child);
1026
+ framing.finish();
1027
+ const lines = [
1028
+ startedIdentityLine,
1029
+ conflictingStartedLine,
1030
+ startedLine,
1031
+ completedLine,
1032
+ failedLine,
1033
+ overflowLine,
1034
+ malformedLine,
1035
+ lastMessage,
1036
+ ]
1037
+ .filter((line) => line !== null)
1038
+ .filter((line, index, all) => all.indexOf(line) === index);
1039
+ const settlement = container === null ? Promise.resolve(null) : settleContainer(container, options);
1040
+ void settlement.then(empty => resolve({
1041
+ code: notFound ? NOT_FOUND_CODE : timedOut ? TIMEOUT_CODE : inputFailed ? 1 : attachFailure !== null || empty === false ? CONTAINMENT_REFUSED_CODE : (code ?? 1),
1042
+ stdout: lines.join("\n"),
1043
+ stderr: empty === false ? `${stderr}\nNative process containment could not prove all descendants exited.`.trim() : attachFailure !== null && stderr === "" ? attachFailure : stderr,
1044
+ timedOut,
1045
+ notFound,
1046
+ ...containmentOf(container, empty, attachFailure),
1047
+ }));
1048
+ };
1049
+ // A failed spawn fires 'error' and may never fire 'close' — both routes
1050
+ // settle, exactly once.
1051
+ child.on("error", error => {
1052
+ notFound = error.code === "ENOENT";
1053
+ if (stderr === "")
1054
+ stderr = String(error);
1055
+ finish(null);
1056
+ });
1057
+ child.on("close", code => finish(code));
1058
+ if (options.stdin !== undefined && child.stdin !== null) {
1059
+ // Even an exit-0 child cannot prove a review if prompt delivery failed.
1060
+ // Handle EPIPE without crashing the supervisor, then wait for close.
1061
+ child.stdin.on("error", error => {
1062
+ inputFailed = true;
1063
+ stderr = `review prompt input failed: ${String(error)}`.slice(0, JSONL_STDERR_CAP);
1064
+ if (options.processGroup === true)
1065
+ killGroup(child);
1066
+ else
1067
+ child.kill("SIGKILL");
1068
+ });
1069
+ child.stdin.end(options.stdin, "utf8");
1070
+ }
1071
+ });
1072
+ }
1073
+ /**
1074
+ * The streaming transport for gemini's stream-json dialect (Phase 3 D1/A7).
1075
+ *
1076
+ * Retention: a bounded `init` identity witness (the first init signal, first
1077
+ * usable session id, and first conflicting id), the FIRST
1078
+ * `severity:"error"` error event (diagnostics),
1079
+ * the LAST `result` event (tokens + structural status), and the assistant's
1080
+ * text — which gemini emits only as many `message` delta lines — ASSEMBLED
1081
+ * into one line of a dedicated internal schema the real CLI cannot emit:
1082
+ *
1083
+ * {"type":"synthetic_message","content":"...","truncated":true?}
1084
+ *
1085
+ * Assembly rules (round-3 f9): only `role:"assistant"` events with
1086
+ * `delta === true` concatenate; a non-delta assistant message REPLACES the
1087
+ * buffer (full-message semantics); non-string content is dropped; the cap
1088
+ * is enforced on the SERIALIZED UTF-8 BYTES of the complete synthetic line
1089
+ * (marker included), so it can never violate the runner's own line
1090
+ * discipline. Everything else — user echoes, tool_use, tool_result,
1091
+ * warnings — is dropped. Timeout, process-group, kill, and spawn
1092
+ * semantics match `runStreamJsonl`.
1093
+ */
1094
+ export function runGeminiStreamJsonl(file, args, options = {}) {
1095
+ const { cwd } = options;
1096
+ const childEnv = resolveChildEnv(options);
1097
+ return new Promise(resolve => {
1098
+ let child;
1099
+ let container = null;
1100
+ let attachFailure = null;
1101
+ try {
1102
+ const spawned = spawnStream(file, args, options, ["ignore", "pipe", "pipe"], childEnv);
1103
+ child = spawned.child;
1104
+ container = spawned.container;
1105
+ void spawned.attached.then(outcome => { if (!outcome.ok)
1106
+ attachFailure = outcome.detail; });
1107
+ }
1108
+ catch (error) {
1109
+ void reapRejectedSpawn(rejectedChild(error, child), options.processGroup === true).then(() => resolve(refusedResult(error)));
1110
+ return;
1111
+ }
1112
+ if (options.processGroup === true)
1113
+ liveProviders.add(child);
1114
+ registerOwned(options.owner, child);
1115
+ if (options.processGroup === true && container === null)
1116
+ observeProcessTree(child, options);
1117
+ let initLine = null;
1118
+ let initIdentityLine = null;
1119
+ let conflictingInitLine = null;
1120
+ let initSessionId = null;
1121
+ let errorLine = null;
1122
+ let resultLine = null;
1123
+ let message = "";
1124
+ let messageTruncated = false;
1125
+ let stderr = "";
1126
+ let timedOut = false;
1127
+ let notFound = false;
1128
+ let overflowLine = null;
1129
+ let malformedLine = null;
1130
+ const keep = (framed) => {
1131
+ if (framed.overflowed) {
1132
+ const type = leadingJsonObjectType(framed.prefix);
1133
+ // Tool plumbing is never a terminal or answer. All load-bearing and
1134
+ // unknown oversized events latch a terminal protocol failure so a
1135
+ // later small success cannot hide discarded provider bytes.
1136
+ if (type !== "tool_use" && type !== "tool_result") {
1137
+ if (overflowLine === null)
1138
+ overflowLine = geminiOverflowLine(framed);
1139
+ }
1140
+ return;
1141
+ }
1142
+ const line = framed.prefix.toString("utf8");
1143
+ let event;
1144
+ try {
1145
+ event = JSON.parse(line);
1146
+ }
1147
+ catch {
1148
+ if (initLine !== null &&
1149
+ malformedLine === null &&
1150
+ malformedGeminiEventIsLoadBearing(framed.prefix)) {
1151
+ malformedLine = geminiMalformedLine(framed);
1152
+ }
1153
+ return; // startup prose and non-load-bearing malformed noise are dropped
1154
+ }
1155
+ if (event === null || typeof event !== "object")
1156
+ return;
1157
+ const type = String(event["type"] ?? "");
1158
+ if (type === "init") {
1159
+ if (initLine === null)
1160
+ initLine = line;
1161
+ const id = transportSessionId(event["session_id"]);
1162
+ let identityWitness = false;
1163
+ if (id !== null) {
1164
+ if (initSessionId === null) {
1165
+ initSessionId = id;
1166
+ initIdentityLine = line;
1167
+ identityWitness = true;
1168
+ }
1169
+ else if (id !== initSessionId && conflictingInitLine === null) {
1170
+ conflictingInitLine = line;
1171
+ identityWitness = true;
1172
+ }
1173
+ if (identityWitness && options.onSessionId !== undefined) {
1174
+ try {
1175
+ options.onSessionId(id);
1176
+ }
1177
+ catch {
1178
+ // A registry that cannot be written must not kill the turn.
1179
+ }
1180
+ }
1181
+ }
1182
+ }
1183
+ else if (type === "result") {
1184
+ resultLine = line; // last one wins
1185
+ }
1186
+ else if (type === "error") {
1187
+ if (errorLine === null && String(event["severity"] ?? "") === "error")
1188
+ errorLine = line;
1189
+ }
1190
+ else if (type === "message" && String(event["role"] ?? "") === "assistant") {
1191
+ const content = event["content"];
1192
+ if (typeof content !== "string")
1193
+ return; // non-string content: dropped
1194
+ if (event["delta"] === true) {
1195
+ if (!messageTruncated) {
1196
+ message += content;
1197
+ if (Buffer.byteLength(message, "utf8") > JSONL_LINE_CAP)
1198
+ messageTruncated = true;
1199
+ }
1200
+ }
1201
+ else {
1202
+ // A full (non-delta) assistant message replaces the buffer.
1203
+ message = content;
1204
+ messageTruncated = Buffer.byteLength(message, "utf8") > JSONL_LINE_CAP;
1205
+ }
1206
+ }
1207
+ if (options.onStreamEvent !== undefined) {
1208
+ try {
1209
+ options.onStreamEvent(event);
1210
+ }
1211
+ catch {
1212
+ // Observational only: a broken listener never touches the run.
1213
+ }
1214
+ }
1215
+ };
1216
+ const framing = boundedJsonlFramer(JSONL_EVENT_HARD_CAP, keep);
1217
+ const watchdog = streamWatchdog(options, () => {
1218
+ timedOut = true;
1219
+ if (options.processGroup === true)
1220
+ killGroup(child);
1221
+ else
1222
+ child.kill("SIGKILL");
1223
+ });
1224
+ child.stdout?.on("data", (chunk) => {
1225
+ watchdog.touch();
1226
+ framing.push(chunk);
1227
+ });
1228
+ child.stderr?.setEncoding("utf8");
1229
+ child.stderr?.on("data", (chunk) => {
1230
+ watchdog.touch();
1231
+ if (stderr.length < JSONL_STDERR_CAP)
1232
+ stderr += chunk.slice(0, JSONL_STDERR_CAP - stderr.length);
1233
+ });
1234
+ let settled = false;
1235
+ const finish = (code) => {
1236
+ if (settled)
1237
+ return;
1238
+ settled = true;
1239
+ watchdog.stop();
1240
+ liveProviders.delete(child);
1241
+ framing.finish();
1242
+ const lines = [initLine, initIdentityLine, conflictingInitLine]
1243
+ .filter((line) => line !== null)
1244
+ .filter((line, index, all) => all.indexOf(line) === index);
1245
+ if (errorLine !== null)
1246
+ lines.push(errorLine);
1247
+ if (message !== "")
1248
+ lines.push(syntheticMessageLine(message, messageTruncated));
1249
+ if (resultLine !== null)
1250
+ lines.push(resultLine);
1251
+ // Keep the overflow last: gemini's contract is last-result-wins, and
1252
+ // no later small event may erase evidence that a record was discarded.
1253
+ if (overflowLine !== null)
1254
+ lines.push(overflowLine);
1255
+ // A recognizable, malformed terminal is just as conclusive as an
1256
+ // oversized one. Keep it last so last-result-wins cannot erase it.
1257
+ if (malformedLine !== null)
1258
+ lines.push(malformedLine);
1259
+ const settlement = container === null ? Promise.resolve(null) : settleContainer(container, options);
1260
+ void settlement.then(empty => resolve({
1261
+ code: notFound ? NOT_FOUND_CODE : timedOut ? TIMEOUT_CODE : attachFailure !== null || empty === false ? CONTAINMENT_REFUSED_CODE : (code ?? 1),
1262
+ stdout: lines.join("\n"),
1263
+ stderr: empty === false ? `${stderr}\nNative process containment could not prove all descendants exited.`.trim() : attachFailure !== null && stderr === "" ? attachFailure : stderr,
1264
+ timedOut,
1265
+ notFound,
1266
+ ...containmentOf(container, empty, attachFailure),
1267
+ }));
1268
+ };
1269
+ child.on("error", error => {
1270
+ notFound = error.code === "ENOENT";
1271
+ if (stderr === "")
1272
+ stderr = String(error);
1273
+ finish(null);
1274
+ });
1275
+ child.on("close", code => finish(code));
1276
+ });
1277
+ }
1278
+ const TRUNCATION_MARKER = "…[truncated]";
1279
+ /**
1280
+ * One serialized synthetic line, guaranteed ≤ JSONL_LINE_CAP in UTF-8
1281
+ * bytes — the trim loop measures the COMPLETE serialized line (escaping
1282
+ * and marker included), so pathological escaping cannot smuggle it past
1283
+ * the cap (round-3 f9).
1284
+ */
1285
+ function syntheticMessageLine(content, truncated) {
1286
+ let body = content;
1287
+ let marked = truncated;
1288
+ for (;;) {
1289
+ const line = JSON.stringify(marked
1290
+ ? { type: "synthetic_message", content: body + TRUNCATION_MARKER, truncated: true }
1291
+ : { type: "synthetic_message", content: body });
1292
+ const over = Buffer.byteLength(line, "utf8") - JSONL_LINE_CAP;
1293
+ if (over <= 0)
1294
+ return line;
1295
+ marked = true;
1296
+ // Cut at least the overage in UTF-16 units; the loop re-measures.
1297
+ const cut = Math.max(1, over);
1298
+ body = body.slice(0, Math.max(0, body.length - cut));
1299
+ }
1300
+ }
1301
+ /**
1302
+ * The streaming transport for claude's stream-json dialect (arc 1).
1303
+ *
1304
+ * Same rationale as `runStreamJsonl`: a long agent session writes far more
1305
+ * event stream than any fixed buffer should hold, and the line that matters
1306
+ * — the terminal result carrying session, usage, and dollars — arrives
1307
+ * LAST. The 8 MiB buffered kill would destroy exactly that accounting
1308
+ * envelope, so stdout is consumed incrementally and only the load-bearing
1309
+ * lines are retained:
1310
+ *
1311
+ * - a bounded identity witness for `system`/`init`: the first init signal,
1312
+ * the first usable session id, and one conflicting id
1313
+ * - the FIRST primary `result` event, selected STRUCTURALLY (finding 10):
1314
+ * origin absent or `origin.kind === "human"` — an allowlist, so a
1315
+ * background task's result (or any future origin kind) can never be
1316
+ * mistaken for the main query's accounting. Later results are dropped.
1317
+ *
1318
+ * The result's stdout is the retained lines joined — a synthetic, bounded
1319
+ * envelope `claudeParse` reads exactly like test fixtures. Timeout,
1320
+ * process-group, kill, and spawn semantics match `runStreamJsonl`.
1321
+ */
1322
+ function primaryResultOrigin(event) {
1323
+ const origin = event["origin"];
1324
+ if (origin === undefined || origin === null)
1325
+ return true;
1326
+ if (typeof origin === "object" && String(origin["kind"] ?? "") === "human")
1327
+ return true;
1328
+ return false; // task-notification, channel, peer, coordinator, unknown: never the envelope
1329
+ }
1330
+ export function runClaudeStreamJsonl(file, args, options = {}) {
1331
+ const { cwd } = options;
1332
+ const childEnv = resolveChildEnv(options);
1333
+ return new Promise(resolve => {
1334
+ let child;
1335
+ let container = null;
1336
+ let attachFailure = null;
1337
+ try {
1338
+ const spawned = spawnStream(file, args, options, ["ignore", "pipe", "pipe"], childEnv);
1339
+ child = spawned.child;
1340
+ container = spawned.container;
1341
+ void spawned.attached.then(outcome => { if (!outcome.ok)
1342
+ attachFailure = outcome.detail; });
1343
+ }
1344
+ catch (error) {
1345
+ void reapRejectedSpawn(rejectedChild(error, child), options.processGroup === true).then(() => resolve(refusedResult(error)));
1346
+ return;
1347
+ }
1348
+ if (options.processGroup === true)
1349
+ liveProviders.add(child);
1350
+ registerOwned(options.owner, child);
1351
+ if (options.processGroup === true && container === null)
1352
+ observeProcessTree(child, options);
1353
+ let initLine = null;
1354
+ let initIdentityLine = null;
1355
+ let conflictingInitLine = null;
1356
+ let initSessionId = null;
1357
+ let resultLine = null;
1358
+ let overflowLine = null;
1359
+ let receiptFired = false;
1360
+ let stderr = "";
1361
+ let timedOut = false;
1362
+ let notFound = false;
1363
+ // Latch BEFORE invoking (finding 13): a throwing listener has already
1364
+ // consumed its one firing, and the stream goes on unharmed.
1365
+ const fireReceipt = () => {
1366
+ if (receiptFired || options.onReceipt === undefined) {
1367
+ receiptFired = true;
1368
+ return;
1369
+ }
1370
+ receiptFired = true;
1371
+ try {
1372
+ options.onReceipt();
1373
+ }
1374
+ catch {
1375
+ // A receipt that cannot be recorded must not alter the run; the
1376
+ // note stays honestly unreceipted and re-attaches later.
1377
+ }
1378
+ };
1379
+ const keep = (framed) => {
1380
+ if (framed.overflowed) {
1381
+ // An oversized result is the first primary candidate and therefore
1382
+ // closes the slot fail-closed; an oversized non-result is merely a
1383
+ // fallback that a later parseable primary result can supersede.
1384
+ // Claude's parser trusts the first primary result. When a line is
1385
+ // too large to classify, reserve that first slot fail-closed rather
1386
+ // than let a later small result hide emitted bytes we discarded.
1387
+ const type = leadingJsonObjectType(framed.prefix);
1388
+ if (type !== null && type !== "result") {
1389
+ if (type !== "system")
1390
+ return;
1391
+ const subtype = leadingClaudeSystemSubtype(framed.prefix);
1392
+ if (subtype !== null && subtype !== "init")
1393
+ return;
1394
+ // An init too large to inspect may conceal a conflicting session
1395
+ // identity. Latch it independently from the primary result so the
1396
+ // same event set fails regardless of whether it arrived before or
1397
+ // after the terminal, while a normal terminal can still account
1398
+ // for the turn's usage.
1399
+ if (overflowLine === null)
1400
+ overflowLine = claudeOverflowLine(framed);
1401
+ return;
1402
+ }
1403
+ if (resultLine === null)
1404
+ resultLine = claudeOverflowLine(framed);
1405
+ else if (overflowLine === null)
1406
+ overflowLine = claudeOverflowLine(framed);
1407
+ return;
1408
+ }
1409
+ const line = framed.prefix.toString("utf8");
1410
+ let event;
1411
+ try {
1412
+ event = JSON.parse(line);
1413
+ }
1414
+ catch {
1415
+ return; // not JSON: not an event; dropped
1416
+ }
1417
+ if (event === null || typeof event !== "object")
1418
+ return;
1419
+ const type = String(event["type"] ?? "");
1420
+ if (type === "system" && String(event["subtype"] ?? "") === "init") {
1421
+ if (initLine === null)
1422
+ initLine = line;
1423
+ const id = transportSessionId(event["session_id"]);
1424
+ let identityWitness = false;
1425
+ if (id !== null) {
1426
+ if (initSessionId === null) {
1427
+ initSessionId = id;
1428
+ initIdentityLine = line;
1429
+ identityWitness = true;
1430
+ }
1431
+ else if (id !== initSessionId && conflictingInitLine === null) {
1432
+ conflictingInitLine = line;
1433
+ identityWitness = true;
1434
+ }
1435
+ if (identityWitness && options.onSessionId !== undefined) {
1436
+ try {
1437
+ options.onSessionId(id);
1438
+ }
1439
+ catch {
1440
+ // A registry that cannot be written must not kill the turn.
1441
+ }
1442
+ }
1443
+ }
1444
+ }
1445
+ else if (type === "assistant") {
1446
+ // Top-level only (finding 11): assistant events carry no origin;
1447
+ // parent_tool_use_id is the correlation field, and a subagent's
1448
+ // words prove nothing about the main prompt.
1449
+ const parent = event["parent_tool_use_id"];
1450
+ if (parent === undefined || parent === null)
1451
+ fireReceipt();
1452
+ }
1453
+ else if (type === "rate_limit_event") {
1454
+ // v105: the plan's usage windows, as Claude says them each turn (the Tasks page's limits).
1455
+ noteLimits(claudeLimitsOf(event));
1456
+ }
1457
+ else if (type === "result" && resultLine === null && primaryResultOrigin(event)) {
1458
+ resultLine = line;
1459
+ // Fallback receipt (finding 11): a SUCCESSFUL main-query completion
1460
+ // entails the prompt ran even if the stream elided assistant
1461
+ // events. Error results never fire — startup death is not delivery.
1462
+ if (event["is_error"] !== true && String(event["subtype"] ?? "") === "success")
1463
+ fireReceipt();
1464
+ }
1465
+ if (options.onStreamEvent !== undefined) {
1466
+ try {
1467
+ options.onStreamEvent(event);
1468
+ }
1469
+ catch {
1470
+ // Observational only: a broken listener never touches the run.
1471
+ }
1472
+ }
1473
+ };
1474
+ const framing = boundedJsonlFramer(JSONL_EVENT_HARD_CAP, keep);
1475
+ const watchdog = streamWatchdog(options, () => {
1476
+ timedOut = true;
1477
+ if (options.processGroup === true)
1478
+ killGroup(child);
1479
+ else
1480
+ child.kill("SIGKILL");
1481
+ });
1482
+ child.stdout?.on("data", (chunk) => {
1483
+ watchdog.touch();
1484
+ framing.push(chunk);
1485
+ });
1486
+ child.stderr?.setEncoding("utf8");
1487
+ child.stderr?.on("data", (chunk) => {
1488
+ watchdog.touch();
1489
+ if (stderr.length < JSONL_STDERR_CAP)
1490
+ stderr += chunk.slice(0, JSONL_STDERR_CAP - stderr.length);
1491
+ });
1492
+ let settled = false;
1493
+ const finish = (code) => {
1494
+ if (settled)
1495
+ return;
1496
+ settled = true;
1497
+ watchdog.stop();
1498
+ liveProviders.delete(child);
1499
+ framing.finish();
1500
+ const lines = [initLine, initIdentityLine, conflictingInitLine, resultLine, overflowLine]
1501
+ .filter((one) => one !== null)
1502
+ .filter((line, index, all) => all.indexOf(line) === index);
1503
+ const settlement = container === null ? Promise.resolve(null) : settleContainer(container, options);
1504
+ void settlement.then(empty => resolve({
1505
+ code: notFound ? NOT_FOUND_CODE : timedOut ? TIMEOUT_CODE : attachFailure !== null || empty === false ? CONTAINMENT_REFUSED_CODE : (code ?? 1),
1506
+ stdout: lines.join("\n"),
1507
+ stderr: empty === false ? `${stderr}\nNative process containment could not prove all descendants exited.`.trim() : attachFailure !== null && stderr === "" ? attachFailure : stderr,
1508
+ timedOut,
1509
+ notFound,
1510
+ ...containmentOf(container, empty, attachFailure),
1511
+ }));
1512
+ };
1513
+ child.on("error", error => {
1514
+ notFound = error.code === "ENOENT";
1515
+ if (stderr === "")
1516
+ stderr = String(error);
1517
+ finish(null);
1518
+ });
1519
+ child.on("close", code => finish(code));
1520
+ });
1521
+ }
1522
+ // ---- the held session transport (Parity II Phase 2, spec v2 S0a + v6 W9) ----
1523
+ /**
1524
+ * Where the supervisor script lives: a plain-.mjs sibling of this module in
1525
+ * BOTH layouts (src/ under tsx, dist/ after build — postbuild copies it).
1526
+ */
1527
+ export function supervisorPath() {
1528
+ return fileURLToPath(new URL("./supervisor.mjs", import.meta.url));
1529
+ }
1530
+ /**
1531
+ * Darwin's sun_path is 104 bytes including the terminator; Linux allows a
1532
+ * hair more. Refusing at spawn beats failing inside bind() with a raw
1533
+ * ENAMETOOLONG after custody was already recorded.
1534
+ */
1535
+ export const HELD_SOCKET_PATH_LIMIT = 103;
1536
+ export function heldSocketPathProblem(path) {
1537
+ const bytes = Buffer.byteLength(path, "utf8");
1538
+ if (bytes > HELD_SOCKET_PATH_LIMIT) {
1539
+ return `the control socket path is ${bytes} bytes — the platform limit is ${HELD_SOCKET_PATH_LIMIT}: ${path}`;
1540
+ }
1541
+ return null;
1542
+ }
1543
+ /**
1544
+ * Hold one claude process open across stream-json turns, under a
1545
+ * supervisor whose parenthood makes the eventual kill provable. Returns
1546
+ * once the supervisor's control frame settles the spawn either way.
1547
+ *
1548
+ * No timers live here: the per-turn wall deadline, liveness lapse, and
1549
+ * absolute expiry are the coordinator's exact timers (v2 S1d) — this
1550
+ * transport only moves bytes and counts the protocol's own marks.
1551
+ */
1552
+ export function startClaudeHeldSession(file, args, options) {
1553
+ const pathProblem = heldSocketPathProblem(options.socketPath);
1554
+ if (pathProblem !== null) {
1555
+ return Promise.resolve({ ok: false, reason: "socket-path", message: pathProblem });
1556
+ }
1557
+ const childEnv = resolveChildEnv(options) ?? { ...process.env };
1558
+ const events = options.events ?? {};
1559
+ return new Promise(resolveStart => {
1560
+ let supervisor;
1561
+ let container = null;
1562
+ let attachFailure = null;
1563
+ try {
1564
+ // The supervisor spawns INSIDE the OS object, so the agent it holds
1565
+ // and every tool the agent launches are members too: one kill reaches
1566
+ // the whole hold, and the object's empty state proves the hold over.
1567
+ const spawned = spawnContained(process.execPath, [supervisorPath(), file, ...args], {
1568
+ cwd: options.cwd,
1569
+ env: {
1570
+ ...childEnv,
1571
+ SO_HELD_SOCKET: options.socketPath,
1572
+ SO_HELD_COOKIE: options.cookie,
1573
+ SO_HELD_GRACE_MS: String(options.graceMs ?? 10_000),
1574
+ SO_HELD_DIAGNOSTIC_FD: currentContainment().mode === "observed" ? "3" : "",
1575
+ },
1576
+ stdio: currentContainment().mode === "observed" ? ["pipe", "pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],
1577
+ detached: false,
1578
+ }, options.owner ?? "held", {
1579
+ beforeSpawn: options.beforeSpawn,
1580
+ onSpawn: options.onSpawn,
1581
+ onContainer: options.onContainer,
1582
+ onContainerEmpty: options.onContainerEmpty,
1583
+ onUnknown: options.onUnknown,
1584
+ fence: options.fence,
1585
+ });
1586
+ supervisor = spawned.child;
1587
+ container = spawned.container;
1588
+ void spawned.attached.then(outcome => { if (!outcome.ok)
1589
+ attachFailure = outcome.detail; });
1590
+ if (container === null)
1591
+ observeProcessTree(supervisor, options);
1592
+ }
1593
+ catch (error) {
1594
+ const left = rejectedChild(error, supervisor);
1595
+ const refused = () => resolveStart({ ok: false, reason: "spawn-failed", message: String(error instanceof SpawnCustodyFailure ? error.reason : error) });
1596
+ if (left === undefined)
1597
+ refused();
1598
+ else {
1599
+ left.once("close", refused);
1600
+ left.on("error", () => { });
1601
+ killGroup(left);
1602
+ }
1603
+ return;
1604
+ }
1605
+ // This pipe belongs to the supervisor, not the agent's relayed stream.
1606
+ // Native containment uses its existing attachment descriptors instead.
1607
+ if (container === null) {
1608
+ const diagnostics = supervisor.stdio[3];
1609
+ let buffer = "", reportedFault = false;
1610
+ const diagnosticFault = () => {
1611
+ if (reportedFault)
1612
+ return;
1613
+ reportedFault = true;
1614
+ // This channel carries diagnostics only. Its failure cannot invent
1615
+ // lost child identity; genuine supervisor uncertainty still exits126.
1616
+ try {
1617
+ options.onObservationFailure?.({ phase: "supervisor", operation: "diagnostic-read", code: "SUPERVISOR_DIAGNOSTIC", rootPid: supervisor.pid ?? null, at: new Date().toISOString(), identityUnknown: false });
1618
+ }
1619
+ catch { }
1620
+ };
1621
+ if (diagnostics !== null && diagnostics !== undefined && "on" in diagnostics) {
1622
+ diagnostics.on("data", (chunk) => {
1623
+ buffer += chunk.toString("utf8");
1624
+ if (buffer.length > 16_384) {
1625
+ buffer = "";
1626
+ diagnosticFault();
1627
+ return;
1628
+ }
1629
+ let cut;
1630
+ while ((cut = buffer.indexOf("\n")) !== -1) {
1631
+ const line = buffer.slice(0, cut);
1632
+ buffer = buffer.slice(cut + 1);
1633
+ let failure = null;
1634
+ try {
1635
+ failure = readProcessObservationFailure(JSON.parse(line));
1636
+ }
1637
+ catch { }
1638
+ if (failure === null) {
1639
+ diagnosticFault();
1640
+ continue;
1641
+ }
1642
+ try {
1643
+ options.onObservationFailure?.(failure);
1644
+ }
1645
+ catch { /* No diagnostic can certify exit or clear an existing witness. */ }
1646
+ }
1647
+ });
1648
+ diagnostics.on("error", diagnosticFault);
1649
+ diagnostics.on("end", () => { if (buffer !== "")
1650
+ diagnosticFault(); });
1651
+ }
1652
+ }
1653
+ heldSupervisors.set(supervisor, "fresh");
1654
+ let settledStart = false;
1655
+ let agentPgid = null;
1656
+ let initSeq = 0;
1657
+ let resultSeq = 0;
1658
+ let sessionSeen = false;
1659
+ let partial = "";
1660
+ let exitResolve = () => { };
1661
+ const exited = new Promise(pass => {
1662
+ exitResolve = pass;
1663
+ });
1664
+ const readyTimer = setTimeout(() => {
1665
+ if (settledStart)
1666
+ return;
1667
+ settledStart = true;
1668
+ try {
1669
+ killGroup(supervisor);
1670
+ }
1671
+ catch {
1672
+ // Already gone.
1673
+ }
1674
+ resolveStart({ ok: false, reason: "ready-timeout", message: "the supervisor never sent its control frame" });
1675
+ }, options.readyTimeoutMs ?? 10_000);
1676
+ const handle = {
1677
+ supervisorPid: null,
1678
+ agentPgid: null,
1679
+ writeTurn(json) {
1680
+ const stdin = supervisor.stdin;
1681
+ if (stdin === null || stdin.destroyed || !stdin.writable)
1682
+ return false;
1683
+ try {
1684
+ stdin.write(json.endsWith("\n") ? json : `${json}\n`);
1685
+ return true;
1686
+ }
1687
+ catch {
1688
+ return false;
1689
+ }
1690
+ },
1691
+ endInput() {
1692
+ try {
1693
+ supervisor.stdin?.end();
1694
+ }
1695
+ catch {
1696
+ // Already closed.
1697
+ }
1698
+ },
1699
+ terminate() {
1700
+ try {
1701
+ supervisor.kill("SIGTERM");
1702
+ heldSupervisors.set(supervisor, "termed");
1703
+ }
1704
+ catch {
1705
+ // Already gone.
1706
+ }
1707
+ },
1708
+ killHard() {
1709
+ try {
1710
+ killGroup(supervisor);
1711
+ }
1712
+ catch {
1713
+ // Already gone.
1714
+ }
1715
+ },
1716
+ exited,
1717
+ };
1718
+ const keep = (line) => {
1719
+ if (line.length > JSONL_LINE_CAP)
1720
+ return;
1721
+ let event;
1722
+ try {
1723
+ event = JSON.parse(line);
1724
+ }
1725
+ catch {
1726
+ return;
1727
+ }
1728
+ if (event === null || typeof event !== "object")
1729
+ return;
1730
+ // The control frame settles the start, exactly once, before any
1731
+ // agent byte — the two-hop handshake (v6 W9).
1732
+ if (!settledStart && typeof event["so_supervisor"] === "string") {
1733
+ settledStart = true;
1734
+ clearTimeout(readyTimer);
1735
+ if (event["so_supervisor"] === "ready") {
1736
+ agentPgid = typeof event["agentPgid"] === "number" ? event["agentPgid"] : null;
1737
+ handle.agentPgid = agentPgid;
1738
+ handle.supervisorPid =
1739
+ typeof event["supervisorPid"] === "number" ? event["supervisorPid"] : (supervisor.pid ?? null);
1740
+ resolveStart({ ok: true, handle });
1741
+ }
1742
+ else {
1743
+ const message = typeof event["message"] === "string" ? event["message"] : "spawn failed";
1744
+ resolveStart({ ok: false, reason: "spawn-failed", message });
1745
+ }
1746
+ return;
1747
+ }
1748
+ if (typeof event["so_supervisor"] === "string")
1749
+ return; // never two frames; drop strays
1750
+ const type = String(event["type"] ?? "");
1751
+ if (type === "system" && String(event["subtype"] ?? "") === "init") {
1752
+ initSeq += 1;
1753
+ if (!sessionSeen && events.onSessionId !== undefined) {
1754
+ const id = transportSessionId(event["session_id"]);
1755
+ if (id !== null) {
1756
+ sessionSeen = true;
1757
+ try {
1758
+ events.onSessionId(id);
1759
+ }
1760
+ catch {
1761
+ // A registry that cannot be written must not kill the turn.
1762
+ }
1763
+ }
1764
+ }
1765
+ try {
1766
+ events.onTurnInit?.(initSeq);
1767
+ }
1768
+ catch {
1769
+ // Observational.
1770
+ }
1771
+ }
1772
+ else if (type === "rate_limit_event") {
1773
+ noteLimits(claudeLimitsOf(event));
1774
+ }
1775
+ else if (type === "result" && primaryResultOrigin(event)) {
1776
+ resultSeq += 1;
1777
+ try {
1778
+ events.onTurnResult?.(resultSeq, event);
1779
+ }
1780
+ catch {
1781
+ // Observational.
1782
+ }
1783
+ }
1784
+ try {
1785
+ events.onStreamEvent?.(event);
1786
+ }
1787
+ catch {
1788
+ // Observational only, arc-1 rule unchanged.
1789
+ }
1790
+ };
1791
+ supervisor.stdout?.setEncoding("utf8");
1792
+ supervisor.stdout?.on("data", (chunk) => {
1793
+ partial += chunk;
1794
+ let cut = partial.indexOf("\n");
1795
+ while (cut !== -1) {
1796
+ keep(partial.slice(0, cut));
1797
+ partial = partial.slice(cut + 1);
1798
+ cut = partial.indexOf("\n");
1799
+ }
1800
+ if (partial.length > JSONL_LINE_CAP * 2)
1801
+ partial = partial.slice(-JSONL_LINE_CAP);
1802
+ });
1803
+ supervisor.on("error", error => {
1804
+ heldSupervisors.delete(supervisor);
1805
+ if (!settledStart) {
1806
+ settledStart = true;
1807
+ clearTimeout(readyTimer);
1808
+ resolveStart({ ok: false, reason: "spawn-failed", message: String(error) });
1809
+ }
1810
+ exitResolve({ code: null });
1811
+ });
1812
+ supervisor.on("close", code => {
1813
+ heldSupervisors.delete(supervisor);
1814
+ if (partial.trim() !== "")
1815
+ keep(partial);
1816
+ // The POSIX supervisor could not prove its observed tree drained.
1817
+ // Persist uncertainty before any onExit handler can release custody.
1818
+ if (container === null && code === CONTAINMENT_REFUSED_CODE) {
1819
+ try {
1820
+ options.onUnknown?.();
1821
+ }
1822
+ catch { /* the observer must not prevent settlement */ }
1823
+ }
1824
+ const settlement = container === null ? Promise.resolve(true) : settleContainer(container, options);
1825
+ void settlement.then(empty => {
1826
+ if (!settledStart) {
1827
+ settledStart = true;
1828
+ clearTimeout(readyTimer);
1829
+ resolveStart({ ok: false, reason: "spawn-failed", message: attachFailure ?? "the supervisor exited before its control frame" });
1830
+ }
1831
+ try {
1832
+ events.onExit?.({ code: empty ? code : CONTAINMENT_REFUSED_CODE });
1833
+ }
1834
+ catch {
1835
+ // Observational.
1836
+ }
1837
+ exitResolve({ code: empty ? code : CONTAINMENT_REFUSED_CODE });
1838
+ });
1839
+ });
1840
+ });
1841
+ }