airaknit 1.1.2-rc.9

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 (440) hide show
  1. package/LICENSE +84 -0
  2. package/README.md +202 -0
  3. package/bin/airaknit +9 -0
  4. package/bin/airaknit-project +14 -0
  5. package/bin/kanna +9 -0
  6. package/dist/client/assets/CompactSummaryMessage-Yw0BDWEJ.js +1 -0
  7. package/dist/client/assets/ExitPlanModeMessage-DIdkQ4uF.js +1 -0
  8. package/dist/client/assets/LocalFilePreviewDialog-DQx2eiCc.js +3 -0
  9. package/dist/client/assets/LocalProjectsSection-C4xlWkgS.js +1 -0
  10. package/dist/client/assets/TextMessage-B5G39DEJ.js +1 -0
  11. package/dist/client/assets/UserMessage-CIkWk-0L.js +1 -0
  12. package/dist/client/assets/_basePickBy-CVrAFfnZ.js +1 -0
  13. package/dist/client/assets/_baseUniq-JL-aaF4P.js +1 -0
  14. package/dist/client/assets/arc-B07zg7ol.js +1 -0
  15. package/dist/client/assets/architecture-YZFGNWBL-PSLVJL3p.js +1 -0
  16. package/dist/client/assets/architectureDiagram-Q4EWVU46-DfWIF1G_.js +36 -0
  17. package/dist/client/assets/array-BGFCBI0e.js +1 -0
  18. package/dist/client/assets/blockDiagram-DXYQGD6D-CzwIeo_B.js +132 -0
  19. package/dist/client/assets/bundle-mjs-BDE2gWbQ.js +1 -0
  20. package/dist/client/assets/button-DO50qOGv.js +1 -0
  21. package/dist/client/assets/c4Diagram-AHTNJAMY-CR8DCQRE.js +10 -0
  22. package/dist/client/assets/channel-Dj-UUfaF.js +1 -0
  23. package/dist/client/assets/chunk-2KRD3SAO-dznP-cn8.js +1 -0
  24. package/dist/client/assets/chunk-336JU56O-Dqss5Vu6.js +2 -0
  25. package/dist/client/assets/chunk-426QAEUC-CEKis_0_.js +1 -0
  26. package/dist/client/assets/chunk-4BX2VUAB-BYOv0Gm1.js +1 -0
  27. package/dist/client/assets/chunk-4TB4RGXK-BxzubH5S.js +206 -0
  28. package/dist/client/assets/chunk-55IACEB6-BSlTj03a.js +1 -0
  29. package/dist/client/assets/chunk-5FUZZQ4R-9Au93Bi1.js +62 -0
  30. package/dist/client/assets/chunk-5PVQY5BW-BhksHFEZ.js +2 -0
  31. package/dist/client/assets/chunk-67CJDMHE-BFwhz-8t.js +1 -0
  32. package/dist/client/assets/chunk-7N4EOEYR-BDUPds87.js +1 -0
  33. package/dist/client/assets/chunk-AA7GKIK3-CEWTdyXO.js +1 -0
  34. package/dist/client/assets/chunk-BO2N2NFS-D0LvxnhU.js +103 -0
  35. package/dist/client/assets/chunk-BSJP7CBP-BNJnK6sq.js +1 -0
  36. package/dist/client/assets/chunk-Bj-mKKzh.js +1 -0
  37. package/dist/client/assets/chunk-CIAEETIT-CYhfoCeN.js +1 -0
  38. package/dist/client/assets/chunk-EDXVE4YY-C5ovJLc0.js +1 -0
  39. package/dist/client/assets/chunk-ENJZ2VHE-HOhYaeGr.js +10 -0
  40. package/dist/client/assets/chunk-FMBD7UC4-BLCiKcAQ.js +15 -0
  41. package/dist/client/assets/chunk-FOC6F5B3-B6GtY2ek.js +1 -0
  42. package/dist/client/assets/chunk-ICPOFSXX-DPoIZoC5.js +122 -0
  43. package/dist/client/assets/chunk-K5T4RW27-BsKN63rv.js +94 -0
  44. package/dist/client/assets/chunk-KGLVRYIC-BUGn9uuY.js +1 -0
  45. package/dist/client/assets/chunk-LIHQZDEY-DhaZyo03.js +1 -0
  46. package/dist/client/assets/chunk-ORNJ4GCN-DlpeeJyi.js +1 -0
  47. package/dist/client/assets/chunk-OYMX7WX6-Dc9q7aYA.js +231 -0
  48. package/dist/client/assets/chunk-QZHKN3VN-BEdrPoSb.js +1 -0
  49. package/dist/client/assets/chunk-U2HBQHQK-CIB3Bjjd.js +70 -0
  50. package/dist/client/assets/chunk-X2U36JSP-CtB-o8Yp.js +1 -0
  51. package/dist/client/assets/chunk-XPW4576I-C6iHhX_8.js +32 -0
  52. package/dist/client/assets/chunk-YZCP3GAM-CTmKr6ZH.js +1 -0
  53. package/dist/client/assets/chunk-ZZ45TVLE-BgU8A2RF.js +1 -0
  54. package/dist/client/assets/classDiagram-6PBFFD2Q-Bqk5e679.js +1 -0
  55. package/dist/client/assets/classDiagram-v2-HSJHXN6E-6pSaZOkC.js +1 -0
  56. package/dist/client/assets/client-BrKWI4CM.js +1 -0
  57. package/dist/client/assets/client-CGgNRU9w.js +1 -0
  58. package/dist/client/assets/client-DMSLRzg9.js +6 -0
  59. package/dist/client/assets/clone-DWcL7whJ.js +1 -0
  60. package/dist/client/assets/cose-bilkent-S5V4N54A-CrV5wsV_.js +1 -0
  61. package/dist/client/assets/cytoscape.esm--aLzKuep.js +321 -0
  62. package/dist/client/assets/dagre-CuRxWcrj.js +1 -0
  63. package/dist/client/assets/dagre-KV5264BT-BIDiVnkA.js +4 -0
  64. package/dist/client/assets/defaultLocale-CRZydyG6.js +1 -0
  65. package/dist/client/assets/diagram-5BDNPKRD-i1kjKRCB.js +10 -0
  66. package/dist/client/assets/diagram-G4DWMVQ6-9ZSLuhbl.js +24 -0
  67. package/dist/client/assets/diagram-MMDJMWI5-B4_CUjgv.js +43 -0
  68. package/dist/client/assets/diagram-TYMM5635-Ct5eTGS8.js +24 -0
  69. package/dist/client/assets/dist-CuB4kiSK.js +1 -0
  70. package/dist/client/assets/erDiagram-SMLLAGMA-Cy38ercc.js +85 -0
  71. package/dist/client/assets/flowDiagram-DWJPFMVM-CZKuYl0V.js +162 -0
  72. package/dist/client/assets/ganttDiagram-T4ZO3ILL-DLPjCh7a.js +292 -0
  73. package/dist/client/assets/gitGraph-7Q5UKJZL-DqbrtEp9.js +1 -0
  74. package/dist/client/assets/gitGraphDiagram-UUTBAWPF-BoRBkDhQ.js +106 -0
  75. package/dist/client/assets/graphlib-BcQ6qlQh.js +1 -0
  76. package/dist/client/assets/highlighted-body-OFNGDK62-BEpBVDTX.js +1 -0
  77. package/dist/client/assets/index-CetCiuqP.js +105 -0
  78. package/dist/client/assets/index-N29Mip7A.css +1 -0
  79. package/dist/client/assets/info-OMHHGYJF-D98DRBJX.js +1 -0
  80. package/dist/client/assets/infoDiagram-42DDH7IO-BAcdTWbt.js +2 -0
  81. package/dist/client/assets/init-B8gtcn7T.js +1 -0
  82. package/dist/client/assets/isArrayLikeObject-D8SJFmkN.js +1 -0
  83. package/dist/client/assets/isEmpty-BF3YX5Jk.js +1 -0
  84. package/dist/client/assets/ishikawaDiagram-UXIWVN3A-Ynu2VKdC.js +70 -0
  85. package/dist/client/assets/journeyDiagram-VCZTEJTY-BjfhQaN3.js +139 -0
  86. package/dist/client/assets/jsx-runtime-CyI9ICYU.js +1 -0
  87. package/dist/client/assets/kanban-definition-6JOO6SKY-JLXH9zUJ.js +89 -0
  88. package/dist/client/assets/katex-B94qP8b6.js +265 -0
  89. package/dist/client/assets/lib--QVjyxmL.js +29 -0
  90. package/dist/client/assets/lib-B6rgJiZ9.js +1 -0
  91. package/dist/client/assets/line-DCrYfLBn.js +1 -0
  92. package/dist/client/assets/linear-_4upLmeo.js +1 -0
  93. package/dist/client/assets/mermaid-GHXKKRXX-rwJHYUmW.js +1 -0
  94. package/dist/client/assets/mermaid-parser.core-KZinfW8o.js +4 -0
  95. package/dist/client/assets/mermaid.core-QqY9gSNe.js +11 -0
  96. package/dist/client/assets/mindmap-definition-QFDTVHPH-TWgHDAzp.js +96 -0
  97. package/dist/client/assets/ordinal-CCj7PWgZ.js +1 -0
  98. package/dist/client/assets/packet-4T2RLAQJ-DEvfkn3F.js +1 -0
  99. package/dist/client/assets/path-DZF-JdEe.js +1 -0
  100. package/dist/client/assets/pie-ZZUOXDRM-72e6WVjb.js +1 -0
  101. package/dist/client/assets/pieDiagram-DEJITSTG-Cl8PCsoj.js +30 -0
  102. package/dist/client/assets/preload-helper-rov5CBGT.js +1 -0
  103. package/dist/client/assets/pty-client-DZ27IS00.js +1 -0
  104. package/dist/client/assets/ptyInstancesStore-D9ag7SYd.js +1 -0
  105. package/dist/client/assets/quadrantDiagram-34T5L4WZ-CHyVGp9E.js +7 -0
  106. package/dist/client/assets/radar-PYXPWWZC-Cp7xd_EY.js +1 -0
  107. package/dist/client/assets/react-CClhXMB2.js +1 -0
  108. package/dist/client/assets/react-Dd6D81m0.js +1 -0
  109. package/dist/client/assets/react-dom--G6_6fQ_.js +1 -0
  110. package/dist/client/assets/requirementDiagram-MS252O5E-DaanG2iM.js +84 -0
  111. package/dist/client/assets/rough.esm-BsmKo2S5.js +1 -0
  112. package/dist/client/assets/sankeyDiagram-XADWPNL6-B_fhLY36.js +10 -0
  113. package/dist/client/assets/sequenceDiagram-FGHM5R23-C5FNrveI.js +157 -0
  114. package/dist/client/assets/src-DeTlMJAU.js +1 -0
  115. package/dist/client/assets/stateDiagram-FHFEXIEX-nTTcdjjQ.js +1 -0
  116. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Dw0632j_.js +1 -0
  117. package/dist/client/assets/timeline-definition-GMOUNBTQ-DkQV1yP8.js +120 -0
  118. package/dist/client/assets/treeView-SZITEDCU-ZLIgC7_K.js +1 -0
  119. package/dist/client/assets/treemap-W4RFUUIX-BqaMbB6N.js +1 -0
  120. package/dist/client/assets/uiIdentityOverlay-Ba7GNj7m.js +1 -0
  121. package/dist/client/assets/vennDiagram-DHZGUBPP-DbZ2xgs6.js +34 -0
  122. package/dist/client/assets/wardley-RL74JXVD-DXQS8zf4.js +1 -0
  123. package/dist/client/assets/wardleyDiagram-NUSXRM2D-BzCJ6MAu.js +20 -0
  124. package/dist/client/assets/xychartDiagram-5P7HB3ND-BSlFecop.js +7 -0
  125. package/dist/client/favicon.png +0 -0
  126. package/dist/client/favicon.svg +15 -0
  127. package/dist/client/fonts/body-medium.woff2 +0 -0
  128. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  129. package/dist/client/fonts/body-regular.woff2 +0 -0
  130. package/dist/client/fonts/body-semibold.woff2 +0 -0
  131. package/dist/client/index.html +31 -0
  132. package/dist/client/manifest.webmanifest +12 -0
  133. package/dist/client/sw.js +32 -0
  134. package/package.json +122 -0
  135. package/src/nats/auth-callout/callout-config.test.ts +93 -0
  136. package/src/nats/auth-callout/callout-config.ts +109 -0
  137. package/src/nats/auth-callout/callout.integration.test.ts +332 -0
  138. package/src/nats/auth-callout/keys.ts +103 -0
  139. package/src/nats/auth-callout/responder.ts +241 -0
  140. package/src/nats/auth-callout/scope-policy.test.ts +159 -0
  141. package/src/nats/auth-callout/scope-policy.ts +210 -0
  142. package/src/nats/auth-callout/token.test.ts +163 -0
  143. package/src/nats/auth-callout/token.ts +157 -0
  144. package/src/nats/nats-daemon-callout.ts +194 -0
  145. package/src/nats/nats-daemon.test.ts +77 -0
  146. package/src/nats/nats-daemon.ts +50 -0
  147. package/src/nats/nats-token.test.ts +61 -0
  148. package/src/nats/nats-token.ts +59 -0
  149. package/src/runner/coordination-mcp-integration.test.ts +134 -0
  150. package/src/runner/nats-coordination-client.test.ts +49 -0
  151. package/src/runner/nats-coordination-client.ts +94 -0
  152. package/src/runner/runner-agent.test.ts +469 -0
  153. package/src/runner/runner-agent.ts +453 -0
  154. package/src/runner/runner-credential.test.ts +93 -0
  155. package/src/runner/runner-credential.ts +82 -0
  156. package/src/runner/runner-nats.test.ts +495 -0
  157. package/src/runner/runner-nats.ts +323 -0
  158. package/src/runner/runner-pair.test.ts +107 -0
  159. package/src/runner/runner-pair.ts +81 -0
  160. package/src/runner/runner.test.ts +135 -0
  161. package/src/runner/runner.ts +212 -0
  162. package/src/runner/turn-factories.test.ts +97 -0
  163. package/src/runner/turn-factories.ts +475 -0
  164. package/src/server/agent-config-journey.test.ts +106 -0
  165. package/src/server/agent.ts +8 -0
  166. package/src/server/auto-continue/auth-error-detector.ts +66 -0
  167. package/src/server/auto-continue/limit-detector.ts +194 -0
  168. package/src/server/bm25.test.ts +92 -0
  169. package/src/server/bm25.ts +101 -0
  170. package/src/server/chat-events-jetstream.test.ts +135 -0
  171. package/src/server/claude-harness.ts +360 -0
  172. package/src/server/claude-pty/agent-normalizers.ts +309 -0
  173. package/src/server/claude-pty/auth.test.ts +38 -0
  174. package/src/server/claude-pty/auth.ts +32 -0
  175. package/src/server/claude-pty/claude-session-registry.adapter.ts +81 -0
  176. package/src/server/claude-pty/claude-session-registry.test.ts +149 -0
  177. package/src/server/claude-pty/driver.test.ts +902 -0
  178. package/src/server/claude-pty/driver.ts +807 -0
  179. package/src/server/claude-pty/jsonl-path.adapter.ts +57 -0
  180. package/src/server/claude-pty/jsonl-path.test.ts +114 -0
  181. package/src/server/claude-pty/jsonl-to-event.test.ts +241 -0
  182. package/src/server/claude-pty/jsonl-to-event.ts +174 -0
  183. package/src/server/claude-pty/output-ring.test.ts +35 -0
  184. package/src/server/claude-pty/output-ring.ts +25 -0
  185. package/src/server/claude-pty/parity-matrix.test.ts +227 -0
  186. package/src/server/claude-pty/pid-registry.adapter.ts +135 -0
  187. package/src/server/claude-pty/pid-registry.test.ts +122 -0
  188. package/src/server/claude-pty/preflight/binary-fingerprint.adapter.ts +20 -0
  189. package/src/server/claude-pty/preflight/binary-fingerprint.test.ts +32 -0
  190. package/src/server/claude-pty/pty-instance-registry.test.ts +177 -0
  191. package/src/server/claude-pty/pty-instance-registry.ts +166 -0
  192. package/src/server/claude-pty/pty-memory-sampler.adapter.test.ts +103 -0
  193. package/src/server/claude-pty/pty-memory-sampler.adapter.ts +85 -0
  194. package/src/server/claude-pty/pty-process.adapter.ts +66 -0
  195. package/src/server/claude-pty/pty-process.test.ts +49 -0
  196. package/src/server/claude-pty/resolve-binary.adapter.ts +106 -0
  197. package/src/server/claude-pty/resolve-binary.test.ts +118 -0
  198. package/src/server/claude-pty/runtime-dir.adapter.ts +19 -0
  199. package/src/server/claude-pty/settings-writer.adapter.ts +27 -0
  200. package/src/server/claude-pty/settings-writer.test.ts +22 -0
  201. package/src/server/claude-pty/smoke-test-io.adapter.ts +28 -0
  202. package/src/server/claude-pty/smoke-test.test.ts +191 -0
  203. package/src/server/claude-pty/smoke-test.ts +185 -0
  204. package/src/server/claude-pty/subagent-orchestrator.ts +887 -0
  205. package/src/server/claude-pty/tool-callback.ts +274 -0
  206. package/src/server/claude-pty/tui-control.test.ts +272 -0
  207. package/src/server/claude-pty/tui-control.ts +182 -0
  208. package/src/server/claude-pty/tui-source.adapter.test.ts +360 -0
  209. package/src/server/claude-pty/tui-source.adapter.ts +343 -0
  210. package/src/server/claude-pty/tunnel-gateway.ts +12 -0
  211. package/src/server/claude-pty-mcp/canonical-args.ts +15 -0
  212. package/src/server/claude-pty-mcp/fs-stat.adapter.ts +8 -0
  213. package/src/server/claude-pty-mcp/history-primer.ts +90 -0
  214. package/src/server/claude-pty-mcp/http-server.adapter.ts +33 -0
  215. package/src/server/claude-pty-mcp/mcp-http.ts +177 -0
  216. package/src/server/claude-pty-mcp/mcp.ts +412 -0
  217. package/src/server/claude-pty-mcp/mention-parser.ts +25 -0
  218. package/src/server/claude-pty-mcp/paths.ts +24 -0
  219. package/src/server/claude-pty-mcp/permission-gate.ts +243 -0
  220. package/src/server/claude-pty-mcp/terminal-pid-registry.adapter.ts +107 -0
  221. package/src/server/claude-pty-mcp/tools/ask-user-question.test.ts +119 -0
  222. package/src/server/claude-pty-mcp/tools/ask-user-question.ts +61 -0
  223. package/src/server/claude-pty-mcp/tools/bash.adapter.ts +76 -0
  224. package/src/server/claude-pty-mcp/tools/bash.test.ts +56 -0
  225. package/src/server/claude-pty-mcp/tools/delegate-subagent.test.ts +155 -0
  226. package/src/server/claude-pty-mcp/tools/delegate-subagent.ts +111 -0
  227. package/src/server/claude-pty-mcp/tools/edit.adapter.ts +95 -0
  228. package/src/server/claude-pty-mcp/tools/edit.test.ts +93 -0
  229. package/src/server/claude-pty-mcp/tools/exit-plan-mode.test.ts +61 -0
  230. package/src/server/claude-pty-mcp/tools/exit-plan-mode.ts +50 -0
  231. package/src/server/claude-pty-mcp/tools/glob.adapter.ts +86 -0
  232. package/src/server/claude-pty-mcp/tools/glob.test.ts +61 -0
  233. package/src/server/claude-pty-mcp/tools/grep.adapter.ts +126 -0
  234. package/src/server/claude-pty-mcp/tools/grep.test.ts +62 -0
  235. package/src/server/claude-pty-mcp/tools/read.adapter.ts +58 -0
  236. package/src/server/claude-pty-mcp/tools/read.test.ts +62 -0
  237. package/src/server/claude-pty-mcp/tools/tool-callback-shim.ts +42 -0
  238. package/src/server/claude-pty-mcp/tools/webfetch.test.ts +81 -0
  239. package/src/server/claude-pty-mcp/tools/webfetch.ts +82 -0
  240. package/src/server/claude-pty-mcp/tools/websearch.test.ts +40 -0
  241. package/src/server/claude-pty-mcp/tools/websearch.ts +42 -0
  242. package/src/server/claude-pty-mcp/tools/write.adapter.ts +60 -0
  243. package/src/server/claude-pty-mcp/tools/write.test.ts +52 -0
  244. package/src/server/claude-pty-mcp/uploads.adapter.ts +98 -0
  245. package/src/server/claude-pty-mcp/uploads.ts +38 -0
  246. package/src/server/claude-turn.test.ts +176 -0
  247. package/src/server/cli-runtime.test.ts +456 -0
  248. package/src/server/cli-runtime.ts +374 -0
  249. package/src/server/cli-supervisor.ts +81 -0
  250. package/src/server/cli.ts +78 -0
  251. package/src/server/client-log-forwarder.test.ts +74 -0
  252. package/src/server/client-log-forwarder.ts +75 -0
  253. package/src/server/codex-app-server-protocol.ts +449 -0
  254. package/src/server/codex-app-server.test.ts +2990 -0
  255. package/src/server/codex-app-server.ts +1713 -0
  256. package/src/server/coordination-integration.test.ts +63 -0
  257. package/src/server/coordination-mcp.test.ts +149 -0
  258. package/src/server/coordination-mcp.ts +197 -0
  259. package/src/server/delegation-coordinator.test.ts +675 -0
  260. package/src/server/delegation-coordinator.ts +454 -0
  261. package/src/server/discovery.test.ts +211 -0
  262. package/src/server/discovery.ts +301 -0
  263. package/src/server/event-store-agent-config.test.ts +124 -0
  264. package/src/server/event-store-coordination.test.ts +149 -0
  265. package/src/server/event-store-profile.test.ts +132 -0
  266. package/src/server/event-store-repo.test.ts +154 -0
  267. package/src/server/event-store-runner-team.test.ts +104 -0
  268. package/src/server/event-store.test.ts +342 -0
  269. package/src/server/event-store.ts +2208 -0
  270. package/src/server/events.ts +379 -0
  271. package/src/server/extension-router.test.ts +183 -0
  272. package/src/server/extension-router.ts +114 -0
  273. package/src/server/extensions/agents/server.test.ts +191 -0
  274. package/src/server/extensions/agents/server.ts +108 -0
  275. package/src/server/extensions/c3/server.test.ts +284 -0
  276. package/src/server/extensions/c3/server.ts +212 -0
  277. package/src/server/extensions/code/server.test.ts +200 -0
  278. package/src/server/extensions/code/server.ts +150 -0
  279. package/src/server/extensions.config.ts +10 -0
  280. package/src/server/external-open.ts +69 -0
  281. package/src/server/generate-fork-context.ts +58 -0
  282. package/src/server/generate-merge-context.test.ts +290 -0
  283. package/src/server/generate-merge-context.ts +141 -0
  284. package/src/server/generate-title.ts +36 -0
  285. package/src/server/git-clone-policy.test.ts +138 -0
  286. package/src/server/git-clone-policy.ts +27 -0
  287. package/src/server/harness-types.ts +1 -0
  288. package/src/server/journey-verification.test.ts +640 -0
  289. package/src/server/journey-verification.ts +195 -0
  290. package/src/server/machine-name.ts +22 -0
  291. package/src/server/nats-auth.test.ts +92 -0
  292. package/src/server/nats-auth.ts +6 -0
  293. package/src/server/nats-bind-guard.test.ts +71 -0
  294. package/src/server/nats-bind-guard.ts +42 -0
  295. package/src/server/nats-bridge.test.ts +141 -0
  296. package/src/server/nats-bridge.ts +111 -0
  297. package/src/server/nats-connector.test.ts +56 -0
  298. package/src/server/nats-connector.ts +71 -0
  299. package/src/server/nats-daemon-manager.test.ts +76 -0
  300. package/src/server/nats-daemon-manager.ts +174 -0
  301. package/src/server/nats-publisher.test.ts +356 -0
  302. package/src/server/nats-publisher.ts +271 -0
  303. package/src/server/nats-responders.test.ts +1018 -0
  304. package/src/server/nats-responders.ts +925 -0
  305. package/src/server/nats-streams.test.ts +112 -0
  306. package/src/server/nats-streams.ts +129 -0
  307. package/src/server/oauth-pool/oauth-responders.ts +185 -0
  308. package/src/server/oauth-pool/oauth-settings-store.ts +173 -0
  309. package/src/server/oauth-pool/oauth-token-pool.ts +303 -0
  310. package/src/server/orchestration.test.ts +1063 -0
  311. package/src/server/orchestration.ts +716 -0
  312. package/src/server/pairing-endpoints.test.ts +171 -0
  313. package/src/server/pairing-store.test.ts +154 -0
  314. package/src/server/pairing-store.ts +124 -0
  315. package/src/server/paths.ts +27 -0
  316. package/src/server/pr3-liveness.test.ts +252 -0
  317. package/src/server/process-utils.ts +10 -0
  318. package/src/server/project-cli.ts +180 -0
  319. package/src/server/provider-catalog.test.ts +177 -0
  320. package/src/server/provider-catalog.ts +146 -0
  321. package/src/server/pty-responders.ts +345 -0
  322. package/src/server/push-notifications.test.ts +234 -0
  323. package/src/server/push-notifications.ts +188 -0
  324. package/src/server/quick-response.test.ts +196 -0
  325. package/src/server/quick-response.ts +154 -0
  326. package/src/server/read-models-agent-config.test.ts +59 -0
  327. package/src/server/read-models-coordination.test.ts +69 -0
  328. package/src/server/read-models.test.ts +332 -0
  329. package/src/server/read-models.ts +258 -0
  330. package/src/server/repo-journey.test.ts +96 -0
  331. package/src/server/repo-manager.test.ts +118 -0
  332. package/src/server/repo-manager.ts +97 -0
  333. package/src/server/repo-status.test.ts +54 -0
  334. package/src/server/repo-status.ts +82 -0
  335. package/src/server/restart.test.ts +27 -0
  336. package/src/server/restart.ts +30 -0
  337. package/src/server/runner-incompatible-gate.test.ts +383 -0
  338. package/src/server/runner-manager.test.ts +72 -0
  339. package/src/server/runner-manager.ts +312 -0
  340. package/src/server/runner-pairing-urls.test.ts +59 -0
  341. package/src/server/runner-pairing-urls.ts +67 -0
  342. package/src/server/runner-proxy.test.ts +456 -0
  343. package/src/server/runner-proxy.ts +494 -0
  344. package/src/server/runner-router.test.ts +429 -0
  345. package/src/server/runner-router.ts +212 -0
  346. package/src/server/runner-routing.test.ts +584 -0
  347. package/src/server/runtime-registry.test.ts +436 -0
  348. package/src/server/runtime-registry.ts +307 -0
  349. package/src/server/sandbox-health.test.ts +127 -0
  350. package/src/server/sandbox-health.ts +94 -0
  351. package/src/server/sandbox-journey.test.ts +232 -0
  352. package/src/server/sandbox-manager.test.ts +146 -0
  353. package/src/server/sandbox-manager.ts +159 -0
  354. package/src/server/server.test.ts +287 -0
  355. package/src/server/server.ts +1108 -0
  356. package/src/server/session-discovery.test.ts +129 -0
  357. package/src/server/session-discovery.ts +475 -0
  358. package/src/server/session-index.test.ts +362 -0
  359. package/src/server/session-index.ts +119 -0
  360. package/src/server/session-seed.ts +288 -0
  361. package/src/server/share.test.ts +108 -0
  362. package/src/server/share.ts +113 -0
  363. package/src/server/skill-discovery.test.ts +201 -0
  364. package/src/server/skill-discovery.ts +77 -0
  365. package/src/server/storage/test-helpers.ts +67 -0
  366. package/src/server/terminal-manager.test.ts +309 -0
  367. package/src/server/terminal-manager.ts +354 -0
  368. package/src/server/transcript-consumer.test.ts +339 -0
  369. package/src/server/transcript-consumer.ts +162 -0
  370. package/src/server/transcript-search.test.ts +193 -0
  371. package/src/server/transcript-search.ts +83 -0
  372. package/src/server/transcript-utils.ts +52 -0
  373. package/src/server/update-manager.test.ts +107 -0
  374. package/src/server/update-manager.ts +230 -0
  375. package/src/server/workflow-engine.test.ts +251 -0
  376. package/src/server/workflow-engine.ts +169 -0
  377. package/src/server/workflow-mcp.ts +49 -0
  378. package/src/server/workflow-store.test.ts +155 -0
  379. package/src/server/workflow-store.ts +139 -0
  380. package/src/server/workspace-agent-integration.test.ts +167 -0
  381. package/src/server/workspace-agent-routes.test.ts +127 -0
  382. package/src/server/workspace-agent-routes.ts +89 -0
  383. package/src/server/workspace-agent.test.ts +103 -0
  384. package/src/server/workspace-agent.ts +102 -0
  385. package/src/server/workspace-cli.test.ts +79 -0
  386. package/src/server/workspace-config-manager.test.ts +109 -0
  387. package/src/server/workspace-config-manager.ts +83 -0
  388. package/src/server/workspace-directory-policy.test.ts +109 -0
  389. package/src/server/workspace-directory-policy.ts +56 -0
  390. package/src/shared/agent-config-types.ts +25 -0
  391. package/src/shared/branding.test.ts +42 -0
  392. package/src/shared/branding.ts +54 -0
  393. package/src/shared/compression.test.ts +85 -0
  394. package/src/shared/compression.ts +42 -0
  395. package/src/shared/coordination-store.test.ts +24 -0
  396. package/src/shared/coordination-store.ts +26 -0
  397. package/src/shared/dev-ports.test.ts +84 -0
  398. package/src/shared/dev-ports.ts +100 -0
  399. package/src/shared/extension-types.ts +45 -0
  400. package/src/shared/fork-presets.ts +54 -0
  401. package/src/shared/harness-types.test.ts +15 -0
  402. package/src/shared/harness-types.ts +21 -0
  403. package/src/shared/log-sink.test.ts +112 -0
  404. package/src/shared/log-sink.ts +185 -0
  405. package/src/shared/mention-pattern.ts +7 -0
  406. package/src/shared/merge-presets.ts +41 -0
  407. package/src/shared/nats-subjects.test.ts +61 -0
  408. package/src/shared/nats-subjects.ts +131 -0
  409. package/src/shared/permission-policy.ts +136 -0
  410. package/src/shared/ports.ts +3 -0
  411. package/src/shared/preset-types.ts +15 -0
  412. package/src/shared/profile-types.ts +49 -0
  413. package/src/shared/projectFileUrl.ts +36 -0
  414. package/src/shared/protocol.ts +153 -0
  415. package/src/shared/pty-instance.ts +43 -0
  416. package/src/shared/puggy/diagnostics/result.ts +18 -0
  417. package/src/shared/puggy/expressions/evaluate.ts +292 -0
  418. package/src/shared/puggy/index.test.ts +101 -0
  419. package/src/shared/puggy/index.ts +69 -0
  420. package/src/shared/puggy/parser/ast.ts +110 -0
  421. package/src/shared/puggy/parser/parser.ts +624 -0
  422. package/src/shared/puggy/renderer/html.ts +447 -0
  423. package/src/shared/runner-protocol.test.ts +277 -0
  424. package/src/shared/runner-protocol.ts +210 -0
  425. package/src/shared/runner-team-types.ts +73 -0
  426. package/src/shared/runtime-types.ts +48 -0
  427. package/src/shared/sandbox-types.ts +53 -0
  428. package/src/shared/tailwind-build.test.ts +12 -0
  429. package/src/shared/tinkaria-system-prompt.ts +101 -0
  430. package/src/shared/tools.test.ts +335 -0
  431. package/src/shared/tools.ts +397 -0
  432. package/src/shared/transcript-entries.ts +27 -0
  433. package/src/shared/transcript-render.test.ts +225 -0
  434. package/src/shared/transcript-render.ts +467 -0
  435. package/src/shared/types.ts +1031 -0
  436. package/src/shared/vite-config.test.ts +47 -0
  437. package/src/shared/web-context.test.ts +110 -0
  438. package/src/shared/web-context.ts +76 -0
  439. package/src/shared/workflow-types.ts +51 -0
  440. package/src/shared/workspace-types.ts +100 -0
@@ -0,0 +1,177 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import {
3
+ createPtyInstanceRegistry,
4
+ type PtyInstanceDelta,
5
+ type PtyInstanceState,
6
+ } from "./pty-instance-registry"
7
+
8
+ function baseline(overrides: Partial<PtyInstanceState> = {}): Omit<PtyInstanceState, "chatId"> {
9
+ return {
10
+ sessionId: null,
11
+ pid: null,
12
+ cwd: "/tmp",
13
+ model: "claude-opus-4-7",
14
+ accountLabel: null,
15
+ oauthMasked: null,
16
+ phase: "spawning",
17
+ startedAt: 1_000,
18
+ lastEventAt: 1_000,
19
+ turnCount: 0,
20
+ tokensIn: 0,
21
+ tokensOut: 0,
22
+ planMode: null,
23
+ smokeTest: null,
24
+ outputRingTail: null,
25
+ exitedAt: null,
26
+ exitCode: null,
27
+ rssBytes: null,
28
+ rssPeakBytes: null,
29
+ cpuPercent: null,
30
+ cpuPeakPercent: null,
31
+ ...overrides,
32
+ }
33
+ }
34
+
35
+ describe("PtyInstanceRegistry", () => {
36
+ test("upsert with new chatId fires added", () => {
37
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0 })
38
+ const events: PtyInstanceDelta[] = []
39
+ registry.subscribe((d) => events.push(d))
40
+ registry.upsert("c1", baseline())
41
+ expect(events).toHaveLength(1)
42
+ expect(events[0]).toMatchObject({ type: "added", instance: { chatId: "c1" } })
43
+ })
44
+
45
+ test("upsert with existing chatId fires updated and merges patch", () => {
46
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0 })
47
+ registry.upsert("c1", baseline())
48
+ const events: PtyInstanceDelta[] = []
49
+ registry.subscribe((d) => events.push(d))
50
+ registry.upsert("c1", { phase: "ready", pid: 42 })
51
+ expect(events).toHaveLength(1)
52
+ expect(events[0]).toMatchObject({
53
+ type: "updated",
54
+ instance: { chatId: "c1", phase: "ready", pid: 42, cwd: "/tmp" },
55
+ })
56
+ })
57
+
58
+ test("remove fires removed event and drops state", () => {
59
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0 })
60
+ registry.upsert("c1", baseline())
61
+ const events: PtyInstanceDelta[] = []
62
+ registry.subscribe((d) => events.push(d))
63
+ registry.remove("c1")
64
+ expect(events).toEqual([{ type: "removed", chatId: "c1" }])
65
+ expect(registry.snapshot()).toEqual([])
66
+ })
67
+
68
+ test("remove for unknown chatId is a no-op", () => {
69
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0 })
70
+ const events: PtyInstanceDelta[] = []
71
+ registry.subscribe((d) => events.push(d))
72
+ registry.remove("nope")
73
+ expect(events).toEqual([])
74
+ })
75
+
76
+ test("snapshot returns clones, not references", () => {
77
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0 })
78
+ registry.upsert("c1", baseline())
79
+ const a = registry.snapshot()
80
+ const b = registry.snapshot()
81
+ expect(a[0]).not.toBe(b[0])
82
+ a[0]!.phase = "exited"
83
+ expect(registry.snapshot()[0]!.phase).toBe("spawning")
84
+ })
85
+
86
+ test("unsubscribe stops further events", () => {
87
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0 })
88
+ const events: PtyInstanceDelta[] = []
89
+ const off = registry.subscribe((d) => events.push(d))
90
+ off()
91
+ registry.upsert("c1", baseline())
92
+ expect(events).toEqual([])
93
+ })
94
+
95
+ test("coalesce: rapid updates within window emit one trailing delta", async () => {
96
+ const registry = createPtyInstanceRegistry({ coalesceMs: 30 })
97
+ registry.upsert("c1", baseline())
98
+ const events: PtyInstanceDelta[] = []
99
+ registry.subscribe((d) => events.push(d))
100
+ registry.upsert("c1", { tokensIn: 1 })
101
+ registry.upsert("c1", { tokensIn: 2 })
102
+ registry.upsert("c1", { tokensIn: 3 })
103
+ expect(events).toEqual([])
104
+ await new Promise((r) => setTimeout(r, 50))
105
+ expect(events).toHaveLength(1)
106
+ expect(events[0]).toMatchObject({ type: "updated", instance: { tokensIn: 3 } })
107
+ })
108
+
109
+ test("coalesce: added events are not delayed", () => {
110
+ const registry = createPtyInstanceRegistry({ coalesceMs: 30 })
111
+ const events: PtyInstanceDelta[] = []
112
+ registry.subscribe((d) => events.push(d))
113
+ registry.upsert("c1", baseline())
114
+ expect(events).toHaveLength(1)
115
+ expect(events[0]!.type).toBe("added")
116
+ })
117
+
118
+ test("coalesce: removed flushes pending update for same chatId", async () => {
119
+ const registry = createPtyInstanceRegistry({ coalesceMs: 30 })
120
+ registry.upsert("c1", baseline())
121
+ const events: PtyInstanceDelta[] = []
122
+ registry.subscribe((d) => events.push(d))
123
+ registry.upsert("c1", { phase: "streaming" })
124
+ registry.remove("c1")
125
+ await new Promise((r) => setTimeout(r, 50))
126
+ expect(events.map((e) => e.type)).toEqual(["removed"])
127
+ })
128
+
129
+ test("exitedTtlMs: entry auto-removed after TTL when phase becomes exited", async () => {
130
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0, exitedTtlMs: 30 })
131
+ registry.upsert("c1", baseline({ phase: "ready" }))
132
+ const events: PtyInstanceDelta[] = []
133
+ registry.subscribe((d) => events.push(d))
134
+ registry.upsert("c1", { phase: "exited", exitedAt: 2_000 })
135
+ expect(events.map((e) => e.type)).toEqual(["updated"])
136
+ expect(registry.snapshot()).toHaveLength(1)
137
+ await new Promise((r) => setTimeout(r, 60))
138
+ expect(events.map((e) => e.type)).toEqual(["updated", "removed"])
139
+ expect(registry.snapshot()).toEqual([])
140
+ })
141
+
142
+ test("exitedTtlMs: prune cancelled if phase moves away from exited before TTL", async () => {
143
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0, exitedTtlMs: 30 })
144
+ registry.upsert("c1", baseline({ phase: "exited", exitedAt: 1_000 }))
145
+ registry.upsert("c1", { phase: "ready", exitedAt: null })
146
+ await new Promise((r) => setTimeout(r, 60))
147
+ expect(registry.snapshot()).toHaveLength(1)
148
+ expect(registry.snapshot()[0]!.phase).toBe("ready")
149
+ })
150
+
151
+ test("exitedTtlMs: 0 disables auto-prune", async () => {
152
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0, exitedTtlMs: 0 })
153
+ registry.upsert("c1", baseline({ phase: "exited", exitedAt: 1_000 }))
154
+ await new Promise((r) => setTimeout(r, 30))
155
+ expect(registry.snapshot()).toHaveLength(1)
156
+ })
157
+
158
+ test("exitedTtlMs: manual remove cancels pending prune", async () => {
159
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0, exitedTtlMs: 30 })
160
+ registry.upsert("c1", baseline({ phase: "exited", exitedAt: 1_000 }))
161
+ const events: PtyInstanceDelta[] = []
162
+ registry.subscribe((d) => events.push(d))
163
+ registry.remove("c1")
164
+ await new Promise((r) => setTimeout(r, 60))
165
+ expect(events.map((e) => e.type)).toEqual(["removed"])
166
+ })
167
+
168
+ test("subscribe replay seeds listener with current state", () => {
169
+ const registry = createPtyInstanceRegistry({ coalesceMs: 0 })
170
+ registry.upsert("c1", baseline())
171
+ registry.upsert("c2", baseline({ phase: "ready" }))
172
+ const events: PtyInstanceDelta[] = []
173
+ registry.subscribe((d) => events.push(d), { replay: true })
174
+ expect(events).toHaveLength(2)
175
+ expect(events.every((e) => e.type === "added")).toBe(true)
176
+ })
177
+ })
@@ -0,0 +1,166 @@
1
+ import type { PtyInstanceDelta, PtyInstanceState } from "../../shared/pty-instance"
2
+
3
+ export type { PtyInstanceDelta, PtyInstanceState }
4
+
5
+ export type PtyInstanceListener = (delta: PtyInstanceDelta) => void
6
+
7
+ export interface PtyInstanceSubscribeOptions {
8
+ replay?: boolean
9
+ }
10
+
11
+ export interface PtyInstanceRegistry {
12
+ snapshot(): PtyInstanceState[]
13
+ subscribe(listener: PtyInstanceListener, options?: PtyInstanceSubscribeOptions): () => void
14
+ upsert(chatId: string, patch: Partial<Omit<PtyInstanceState, "chatId">>): void
15
+ remove(chatId: string): void
16
+ }
17
+
18
+ export interface CreatePtyInstanceRegistryOptions {
19
+ /** Trailing-edge coalesce window for "updated" deltas, in ms. 0 disables. */
20
+ coalesceMs?: number
21
+ /**
22
+ * TTL after which entries that enter `phase: "exited"` are auto-removed
23
+ * to bound in-memory growth. 0 disables auto-prune.
24
+ */
25
+ exitedTtlMs?: number
26
+ }
27
+
28
+ const DEFAULT_COALESCE_MS = 100
29
+ const DEFAULT_EXITED_TTL_MS = 60_000
30
+
31
+ export function createPtyInstanceRegistry(
32
+ options: CreatePtyInstanceRegistryOptions = {},
33
+ ): PtyInstanceRegistry {
34
+ const coalesceMs = options.coalesceMs ?? DEFAULT_COALESCE_MS
35
+ const exitedTtlMs = options.exitedTtlMs ?? DEFAULT_EXITED_TTL_MS
36
+ const states = new Map<string, PtyInstanceState>()
37
+ const listeners = new Set<PtyInstanceListener>()
38
+ const pendingFlushes = new Map<string, ReturnType<typeof setTimeout>>()
39
+ const exitedPrunes = new Map<string, ReturnType<typeof setTimeout>>()
40
+
41
+ function emit(delta: PtyInstanceDelta): void {
42
+ for (const listener of listeners) listener(delta)
43
+ }
44
+
45
+ function clone(state: PtyInstanceState): PtyInstanceState {
46
+ return { ...state }
47
+ }
48
+
49
+ function cancelPendingFlush(chatId: string): void {
50
+ const handle = pendingFlushes.get(chatId)
51
+ if (handle) {
52
+ clearTimeout(handle)
53
+ pendingFlushes.delete(chatId)
54
+ }
55
+ }
56
+
57
+ function cancelExitedPrune(chatId: string): void {
58
+ const handle = exitedPrunes.get(chatId)
59
+ if (handle) {
60
+ clearTimeout(handle)
61
+ exitedPrunes.delete(chatId)
62
+ }
63
+ }
64
+
65
+ function scheduleExitedPrune(chatId: string): void {
66
+ if (exitedTtlMs <= 0) return
67
+ cancelExitedPrune(chatId)
68
+ exitedPrunes.set(
69
+ chatId,
70
+ setTimeout(() => {
71
+ exitedPrunes.delete(chatId)
72
+ removeInternal(chatId)
73
+ }, exitedTtlMs),
74
+ )
75
+ }
76
+
77
+ function reconcileExitedTimer(chatId: string, phase: PtyInstanceState["phase"]): void {
78
+ if (phase === "exited") scheduleExitedPrune(chatId)
79
+ else cancelExitedPrune(chatId)
80
+ }
81
+
82
+ function flushUpdate(chatId: string): void {
83
+ pendingFlushes.delete(chatId)
84
+ const state = states.get(chatId)
85
+ if (!state) return
86
+ emit({ type: "updated", instance: clone(state) })
87
+ }
88
+
89
+ function removeInternal(chatId: string): void {
90
+ if (!states.has(chatId)) return
91
+ cancelPendingFlush(chatId)
92
+ cancelExitedPrune(chatId)
93
+ states.delete(chatId)
94
+ emit({ type: "removed", chatId })
95
+ }
96
+
97
+ return {
98
+ snapshot(): PtyInstanceState[] {
99
+ return Array.from(states.values(), clone)
100
+ },
101
+
102
+ subscribe(listener, opts): () => void {
103
+ listeners.add(listener)
104
+ if (opts?.replay) {
105
+ for (const state of states.values()) {
106
+ listener({ type: "added", instance: clone(state) })
107
+ }
108
+ }
109
+ return () => {
110
+ listeners.delete(listener)
111
+ }
112
+ },
113
+
114
+ upsert(chatId, patch): void {
115
+ const existing = states.get(chatId)
116
+ if (existing) {
117
+ const next: PtyInstanceState = { ...existing, ...patch, chatId }
118
+ states.set(chatId, next)
119
+ reconcileExitedTimer(chatId, next.phase)
120
+ if (coalesceMs <= 0) {
121
+ emit({ type: "updated", instance: clone(next) })
122
+ return
123
+ }
124
+ if (!pendingFlushes.has(chatId)) {
125
+ pendingFlushes.set(
126
+ chatId,
127
+ setTimeout(() => flushUpdate(chatId), coalesceMs),
128
+ )
129
+ }
130
+ return
131
+ }
132
+ const baseline: PtyInstanceState = {
133
+ chatId,
134
+ sessionId: null,
135
+ pid: null,
136
+ cwd: "",
137
+ model: "",
138
+ accountLabel: null,
139
+ oauthMasked: null,
140
+ phase: "spawning",
141
+ startedAt: 0,
142
+ lastEventAt: 0,
143
+ turnCount: 0,
144
+ tokensIn: 0,
145
+ tokensOut: 0,
146
+ planMode: null,
147
+ smokeTest: null,
148
+ outputRingTail: null,
149
+ exitedAt: null,
150
+ exitCode: null,
151
+ rssBytes: null,
152
+ rssPeakBytes: null,
153
+ cpuPercent: null,
154
+ cpuPeakPercent: null,
155
+ ...patch,
156
+ }
157
+ states.set(chatId, baseline)
158
+ reconcileExitedTimer(chatId, baseline.phase)
159
+ emit({ type: "added", instance: clone(baseline) })
160
+ },
161
+
162
+ remove(chatId): void {
163
+ removeInternal(chatId)
164
+ },
165
+ }
166
+ }
@@ -0,0 +1,103 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import process from "node:process"
3
+ import {
4
+ collectTreePids,
5
+ parsePsOutput,
6
+ sampleProcessTreeUsage,
7
+ sumTreeUsage,
8
+ type PsProcessRow,
9
+ } from "./pty-memory-sampler.adapter"
10
+
11
+ describe("parsePsOutput", () => {
12
+ test("parses normal ps output with pid/ppid/rss/pcpu", () => {
13
+ const out = [
14
+ " 1 0 1024 0.0",
15
+ " 100 1 2048 5.2",
16
+ " 101 100 4096 12.5",
17
+ "",
18
+ " 102 100 8192 99.9",
19
+ ].join("\n")
20
+ expect(parsePsOutput(out)).toEqual([
21
+ { pid: 1, ppid: 0, rssKb: 1024, cpuPercent: 0.0 },
22
+ { pid: 100, ppid: 1, rssKb: 2048, cpuPercent: 5.2 },
23
+ { pid: 101, ppid: 100, rssKb: 4096, cpuPercent: 12.5 },
24
+ { pid: 102, ppid: 100, rssKb: 8192, cpuPercent: 99.9 },
25
+ ])
26
+ })
27
+
28
+ test("skips malformed rows without throwing", () => {
29
+ const out = [
30
+ "100 1 1024 3.0",
31
+ "garbage line here",
32
+ "abc def ghi jkl",
33
+ "200 1 2048 7.5",
34
+ " ",
35
+ "300 1 100",
36
+ ].join("\n")
37
+ expect(parsePsOutput(out)).toEqual([
38
+ { pid: 100, ppid: 1, rssKb: 1024, cpuPercent: 3.0 },
39
+ { pid: 200, ppid: 1, rssKb: 2048, cpuPercent: 7.5 },
40
+ ])
41
+ })
42
+
43
+ test("returns empty array for empty input", () => {
44
+ expect(parsePsOutput("")).toEqual([])
45
+ expect(parsePsOutput("\n\n\n")).toEqual([])
46
+ })
47
+ })
48
+
49
+ describe("collectTreePids", () => {
50
+ const rows: PsProcessRow[] = [
51
+ { pid: 1, ppid: 0, rssKb: 0, cpuPercent: 0 },
52
+ { pid: 100, ppid: 1, rssKb: 0, cpuPercent: 0 },
53
+ { pid: 101, ppid: 100, rssKb: 0, cpuPercent: 0 },
54
+ { pid: 102, ppid: 100, rssKb: 0, cpuPercent: 0 },
55
+ { pid: 103, ppid: 101, rssKb: 0, cpuPercent: 0 },
56
+ { pid: 200, ppid: 1, rssKb: 0, cpuPercent: 0 },
57
+ ]
58
+
59
+ test("collects root + descendants transitively", () => {
60
+ expect(collectTreePids(rows, 100)).toEqual(new Set([100, 101, 102, 103]))
61
+ })
62
+
63
+ test("returns only root when no children", () => {
64
+ expect(collectTreePids(rows, 200)).toEqual(new Set([200]))
65
+ })
66
+
67
+ test("returns only root when root absent from rows", () => {
68
+ expect(collectTreePids(rows, 999)).toEqual(new Set([999]))
69
+ })
70
+ })
71
+
72
+ describe("sumTreeUsage", () => {
73
+ const rows: PsProcessRow[] = [
74
+ { pid: 1, ppid: 0, rssKb: 100, cpuPercent: 1.0 },
75
+ { pid: 100, ppid: 1, rssKb: 200, cpuPercent: 25.0 },
76
+ { pid: 101, ppid: 100, rssKb: 300, cpuPercent: 50.5 },
77
+ ]
78
+
79
+ test("sums rss (kb->bytes) + cpu for pids in tree", () => {
80
+ const tree = new Set<number>([100, 101])
81
+ expect(sumTreeUsage(rows, tree)).toEqual({
82
+ rssBytes: (200 + 300) * 1024,
83
+ cpuPercent: 25.0 + 50.5,
84
+ })
85
+ })
86
+
87
+ test("returns zeros for empty tree", () => {
88
+ expect(sumTreeUsage(rows, new Set())).toEqual({ rssBytes: 0, cpuPercent: 0 })
89
+ })
90
+ })
91
+
92
+ describe("sampleProcessTreeUsage", () => {
93
+ test("returns positive rss + finite cpu for current process", async () => {
94
+ const sample = await sampleProcessTreeUsage(process.pid)
95
+ expect(sample).not.toBeNull()
96
+ expect(sample?.rssBytes).toBeGreaterThan(0)
97
+ expect(Number.isFinite(sample?.cpuPercent ?? Number.NaN)).toBe(true)
98
+ }, 5_000)
99
+
100
+ test("returns null for pid that does not exist", async () => {
101
+ expect(await sampleProcessTreeUsage(2_147_483_646)).toBeNull()
102
+ }, 5_000)
103
+ })
@@ -0,0 +1,85 @@
1
+ import { execFile } from "node:child_process"
2
+ import { promisify } from "node:util"
3
+
4
+ const execFileAsync = promisify(execFile)
5
+
6
+ const PS_TIMEOUT_MS = 2000
7
+
8
+ export interface PsProcessRow {
9
+ pid: number
10
+ ppid: number
11
+ rssKb: number
12
+ cpuPercent: number
13
+ }
14
+
15
+ export interface ProcessTreeSample {
16
+ rssBytes: number
17
+ cpuPercent: number
18
+ }
19
+
20
+ export function parsePsOutput(stdout: string): PsProcessRow[] {
21
+ const rows: PsProcessRow[] = []
22
+ for (const rawLine of stdout.split("\n")) {
23
+ const line = rawLine.trim()
24
+ if (!line) continue
25
+ const parts = line.split(/\s+/)
26
+ if (parts.length < 4) continue
27
+ const pid = Number(parts[0])
28
+ const ppid = Number(parts[1])
29
+ const rssKb = Number(parts[2])
30
+ const cpuPercent = Number(parts[3])
31
+ if (!Number.isFinite(pid) || !Number.isFinite(ppid) || !Number.isFinite(rssKb) || !Number.isFinite(cpuPercent)) continue
32
+ rows.push({ pid, ppid, rssKb, cpuPercent })
33
+ }
34
+ return rows
35
+ }
36
+
37
+ export function collectTreePids(rows: readonly PsProcessRow[], rootPid: number): Set<number> {
38
+ const childrenByParent = new Map<number, number[]>()
39
+ for (const row of rows) {
40
+ const list = childrenByParent.get(row.ppid)
41
+ if (list) list.push(row.pid)
42
+ else childrenByParent.set(row.ppid, [row.pid])
43
+ }
44
+ const tree = new Set<number>([rootPid])
45
+ const queue: number[] = [rootPid]
46
+ while (queue.length > 0) {
47
+ const next = queue.shift() as number
48
+ const kids = childrenByParent.get(next)
49
+ if (!kids) continue
50
+ for (const kid of kids) {
51
+ if (tree.has(kid)) continue
52
+ tree.add(kid)
53
+ queue.push(kid)
54
+ }
55
+ }
56
+ return tree
57
+ }
58
+
59
+ export function sumTreeUsage(rows: readonly PsProcessRow[], tree: ReadonlySet<number>): ProcessTreeSample {
60
+ let totalKb = 0
61
+ let totalCpu = 0
62
+ for (const row of rows) {
63
+ if (!tree.has(row.pid)) continue
64
+ totalKb += row.rssKb
65
+ totalCpu += row.cpuPercent
66
+ }
67
+ return { rssBytes: totalKb * 1024, cpuPercent: totalCpu }
68
+ }
69
+
70
+ export async function sampleProcessTreeUsage(rootPid: number): Promise<ProcessTreeSample | null> {
71
+ let stdout: string
72
+ try {
73
+ const result = await execFileAsync("ps", ["-A", "-o", "pid=,ppid=,rss=,pcpu="], {
74
+ timeout: PS_TIMEOUT_MS,
75
+ maxBuffer: 4 * 1024 * 1024,
76
+ })
77
+ stdout = result.stdout
78
+ } catch {
79
+ return null
80
+ }
81
+ const rows = parsePsOutput(stdout)
82
+ if (!rows.some((r) => r.pid === rootPid)) return null
83
+ const tree = collectTreePids(rows, rootPid)
84
+ return sumTreeUsage(rows, tree)
85
+ }
@@ -0,0 +1,66 @@
1
+ export interface PtyProcess {
2
+ /** OS pid of the spawned child (== pgid because Bun.Terminal setsid). */
3
+ pid: number
4
+ sendInput(data: string): Promise<void>
5
+ resize(cols: number, rows: number): void
6
+ exited: Promise<number>
7
+ /** Default terminate: SIGTERM (gives the child a chance to flush). */
8
+ close(): void
9
+ /**
10
+ * Force kill (SIGKILL) — use after SIGTERM has had a grace window and
11
+ * the process still hasn't exited. Bypasses any child cleanup.
12
+ */
13
+ kill(signal?: NodeJS.Signals | number): void
14
+ }
15
+
16
+ export interface SpawnPtyProcessArgs {
17
+ command: string
18
+ args: string[]
19
+ cwd: string
20
+ env: NodeJS.ProcessEnv
21
+ cols?: number
22
+ rows?: number
23
+ onOutput?: (chunk: string) => void
24
+ }
25
+
26
+ export async function spawnPtyProcess(opts: SpawnPtyProcessArgs): Promise<PtyProcess> {
27
+ if (typeof Bun.Terminal !== "function") {
28
+ throw new Error("Bun.Terminal not available — requires Bun 1.3.5+")
29
+ }
30
+
31
+ const cols = opts.cols ?? 120
32
+ const rows = opts.rows ?? 40
33
+
34
+ const terminal = new Bun.Terminal({
35
+ cols,
36
+ rows,
37
+ name: "xterm-256color",
38
+ data: (_t, data) => {
39
+ if (opts.onOutput) {
40
+ const chunk = Buffer.from(data).toString("utf8")
41
+ opts.onOutput(chunk)
42
+ }
43
+ },
44
+ })
45
+
46
+ const proc = Bun.spawn([opts.command, ...opts.args], {
47
+ cwd: opts.cwd,
48
+ env: opts.env,
49
+ terminal,
50
+ })
51
+
52
+ return {
53
+ pid: proc.pid,
54
+ async sendInput(data) { terminal.write(data) },
55
+ resize(newCols, newRows) { terminal.resize(newCols, newRows) },
56
+ exited: proc.exited,
57
+ close() {
58
+ try { terminal.close() } catch { /* swallow */ }
59
+ try { proc.kill("SIGTERM") } catch { /* swallow */ }
60
+ },
61
+ kill(signal) {
62
+ try { terminal.close() } catch { /* swallow */ }
63
+ try { proc.kill(signal ?? "SIGKILL") } catch { /* swallow */ }
64
+ },
65
+ }
66
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { spawnPtyProcess } from "./pty-process.adapter"
3
+
4
+ describe("spawnPtyProcess", () => {
5
+ test(
6
+ "spawns a child process and exits cleanly",
7
+ async () => {
8
+ if (process.platform === "win32") {
9
+ console.log("skip: PTY not supported on Windows")
10
+ return
11
+ }
12
+ if (typeof Bun.Terminal !== "function") {
13
+ console.log("skip: Bun.Terminal not available")
14
+ return
15
+ }
16
+ const handle = await spawnPtyProcess({
17
+ command: "/bin/sh",
18
+ args: ["-c", "echo hello"],
19
+ cwd: "/tmp",
20
+ env: process.env,
21
+ cols: 80,
22
+ rows: 24,
23
+ })
24
+ const exitCode = await handle.exited
25
+ expect(exitCode).toBe(0)
26
+ handle.close()
27
+ },
28
+ 30_000,
29
+ )
30
+
31
+ test(
32
+ "captures output via onOutput callback",
33
+ async () => {
34
+ if (process.platform === "win32" || typeof Bun.Terminal !== "function") return
35
+ const chunks: string[] = []
36
+ const handle = await spawnPtyProcess({
37
+ command: "/bin/sh",
38
+ args: ["-c", "echo hi"],
39
+ cwd: "/tmp",
40
+ env: process.env,
41
+ onOutput: (chunk) => chunks.push(chunk),
42
+ })
43
+ await handle.exited
44
+ handle.close()
45
+ expect(chunks.join("")).toContain("hi")
46
+ },
47
+ 30_000,
48
+ )
49
+ })