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,227 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtemp, rm, writeFile, appendFile, mkdir } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import type { Query } from "@anthropic-ai/claude-agent-sdk"
6
+ import { createClaudeHarnessStream } from "./agent-normalizers"
7
+ import { createJsonlEventParser } from "./jsonl-to-event"
8
+ import { startTranscriptStream } from "./tui-source.adapter"
9
+ import type { HarnessEvent } from "../harness-types"
10
+
11
+ /**
12
+ * Phase 6 — SDK ↔ PTY HarnessEvent equivalence matrix.
13
+ *
14
+ * Drives both paths with the same Claude-SDK message fixtures and
15
+ * asserts they emit the same `HarnessEvent` sequence (after stripping
16
+ * volatile fields like `_id` and `createdAt`). The claude CLI mirrors
17
+ * SDKMessage shapes into the JSONL transcript verbatim, so a single
18
+ * fixture stands in for both:
19
+ * - SDK path: yielded from a fake `Query` iterable into
20
+ * `createClaudeHarnessStream`.
21
+ * - PTY path: serialized to JSON and fed to `createJsonlEventParser`
22
+ * one line per message.
23
+ */
24
+
25
+ function fakeQuery(messages: unknown[]): Query {
26
+ const q = (async function* () {
27
+ for (const m of messages) yield m as never
28
+ })()
29
+ return q as unknown as Query
30
+ }
31
+
32
+ function normalize(events: HarnessEvent[]): unknown[] {
33
+ return events.map((ev) => {
34
+ if (ev.type === "transcript") {
35
+ const { _id: _i, createdAt: _c, ...rest } = ev.entry as unknown as Record<string, unknown> & {
36
+ _id?: string
37
+ createdAt?: number
38
+ }
39
+ return { type: ev.type, entry: rest }
40
+ }
41
+ return ev
42
+ })
43
+ }
44
+
45
+ async function collectSdk(messages: unknown[], configuredContextWindow?: number): Promise<HarnessEvent[]> {
46
+ const events: HarnessEvent[] = []
47
+ for await (const ev of createClaudeHarnessStream(fakeQuery(messages), configuredContextWindow)) {
48
+ events.push(ev)
49
+ }
50
+ return events
51
+ }
52
+
53
+ async function ptyEventsViaTranscriptStream(messages: unknown[], configuredContextWindow?: number): Promise<HarnessEvent[]> {
54
+ const tmpDir = await mkdtemp(path.join(tmpdir(), "kanna-parity-"))
55
+ const projectDir = path.join(tmpDir, "projects", "fake")
56
+ await mkdir(projectDir, { recursive: true })
57
+ const filePath = path.join(projectDir, "fixture.jsonl")
58
+ await writeFile(filePath, "")
59
+ const stream = await startTranscriptStream({ projectDir, knownFilePath: filePath, firstFileTimeoutMs: 2000 })
60
+ const parser = createJsonlEventParser({ configuredContextWindow })
61
+ const events: HarnessEvent[] = []
62
+ const writeAll = (async () => {
63
+ for (const m of messages) {
64
+ await appendFile(filePath, JSON.stringify(m) + "\n")
65
+ await new Promise<void>((r) => setTimeout(r, 30))
66
+ }
67
+ await appendFile(filePath, '{"type":"__parity_sentinel__"}\n')
68
+ })()
69
+ const collectDone = (async () => {
70
+ for await (const line of stream.lines) {
71
+ let parsed: { type?: string }
72
+ try { parsed = JSON.parse(line) as { type?: string } } catch { continue }
73
+ if (parsed.type === "__parity_sentinel__") break
74
+ for (const ev of parser.parse(line)) events.push(ev)
75
+ }
76
+ })()
77
+ await writeAll
78
+ await collectDone
79
+ stream.close()
80
+ await rm(tmpDir, { recursive: true, force: true })
81
+ return events
82
+ }
83
+
84
+ async function assertSameEvents(messages: unknown[], configuredContextWindow?: number): Promise<void> {
85
+ const sdk = await collectSdk(messages, configuredContextWindow)
86
+ const pty = await ptyEventsViaTranscriptStream(messages, configuredContextWindow)
87
+ expect(normalize(pty)).toEqual(normalize(sdk))
88
+ }
89
+
90
+ describe("SDK ↔ PTY HarnessEvent equivalence matrix", () => {
91
+ test("simple turn: system/init → assistant → result", async () => {
92
+ await assertSameEvents([
93
+ {
94
+ type: "system",
95
+ subtype: "init",
96
+ session_id: "sess-1",
97
+ model: "claude-sonnet-4-6",
98
+ tools: [],
99
+ mcp_servers: [],
100
+ slash_commands: [],
101
+ cwd: "/tmp",
102
+ permissionMode: "acceptEdits",
103
+ apiKeySource: "none",
104
+ claude_code_version: "0.0.0",
105
+ output_style: "default",
106
+ skills: [],
107
+ plugins: [],
108
+ uuid: "u1",
109
+ },
110
+ {
111
+ type: "assistant",
112
+ session_id: "sess-1",
113
+ message: {
114
+ id: "msg-1",
115
+ role: "assistant",
116
+ content: [{ type: "text", text: "hi" }],
117
+ },
118
+ usage: { input_tokens: 100, output_tokens: 25 },
119
+ uuid: "u2",
120
+ },
121
+ {
122
+ type: "result",
123
+ subtype: "success",
124
+ session_id: "sess-1",
125
+ result: "done",
126
+ is_error: false,
127
+ duration_ms: 500,
128
+ usage: { input_tokens: 100, output_tokens: 25 },
129
+ modelUsage: { "claude-sonnet-4-6": { contextWindow: 200000 } },
130
+ uuid: "u3",
131
+ },
132
+ ])
133
+ })
134
+
135
+ test("rate_limit_event (SDK-native shape)", async () => {
136
+ await assertSameEvents([
137
+ {
138
+ type: "rate_limit_event",
139
+ session_id: "sess-rl",
140
+ rate_limit_info: { status: "rejected", resetsAt: 1_748_800_000 },
141
+ },
142
+ ])
143
+ })
144
+
145
+ test("prompt-too-long error result", async () => {
146
+ await assertSameEvents([
147
+ {
148
+ type: "result",
149
+ subtype: "error",
150
+ session_id: "sess-err",
151
+ is_error: true,
152
+ result: "prompt is too long",
153
+ duration_ms: 0,
154
+ uuid: "u-err",
155
+ },
156
+ ])
157
+ })
158
+
159
+ test("multiple assistant messages dedupe on usage id (same id seen twice)", async () => {
160
+ await assertSameEvents([
161
+ {
162
+ type: "assistant",
163
+ session_id: "sess-d",
164
+ message: { id: "dup", role: "assistant", content: [{ type: "text", text: "a" }] },
165
+ usage: { input_tokens: 10, output_tokens: 3 },
166
+ },
167
+ {
168
+ type: "assistant",
169
+ session_id: "sess-d",
170
+ message: { id: "dup", role: "assistant", content: [{ type: "text", text: "a" }] },
171
+ usage: { input_tokens: 10, output_tokens: 3 },
172
+ },
173
+ ])
174
+ })
175
+
176
+ test("1M context window floor preserved against modelUsage.contextWindow=200k", async () => {
177
+ await assertSameEvents(
178
+ [
179
+ {
180
+ type: "assistant",
181
+ session_id: "sess-1m",
182
+ message: { id: "msg-1m", role: "assistant", content: [{ type: "text", text: "x" }] },
183
+ usage: { input_tokens: 100, output_tokens: 50 },
184
+ },
185
+ {
186
+ type: "result",
187
+ subtype: "success",
188
+ session_id: "sess-1m",
189
+ is_error: false,
190
+ duration_ms: 500,
191
+ usage: { input_tokens: 100, output_tokens: 50 },
192
+ modelUsage: { "claude-sonnet-4-6": { contextWindow: 200000 } },
193
+ },
194
+ ],
195
+ 1_000_000,
196
+ )
197
+ })
198
+
199
+ test("session_token emitted from every message carrying session_id", async () => {
200
+ await assertSameEvents([
201
+ { type: "system", subtype: "init", session_id: "a", model: "m", tools: [], mcp_servers: [], slash_commands: [], cwd: "/", permissionMode: "acceptEdits", apiKeySource: "none", claude_code_version: "0", output_style: "d", skills: [], plugins: [], uuid: "1" },
202
+ { type: "assistant", session_id: "a", message: { id: "m1", role: "assistant", content: [] } },
203
+ { type: "result", subtype: "success", session_id: "a", is_error: false, duration_ms: 0, uuid: "r" },
204
+ ])
205
+ })
206
+
207
+ test("compact_boundary turn does not produce phantom context_window_updated", async () => {
208
+ await assertSameEvents([
209
+ {
210
+ type: "system",
211
+ subtype: "compact_boundary",
212
+ session_id: "sess-c",
213
+ compact_metadata: { trigger: "auto", pre_tokens: 50000 },
214
+ uuid: "cb",
215
+ },
216
+ {
217
+ type: "result",
218
+ subtype: "success",
219
+ session_id: "sess-c",
220
+ is_error: false,
221
+ duration_ms: 100,
222
+ usage: { input_tokens: 1000, cache_read_input_tokens: 49000, output_tokens: 0 },
223
+ uuid: "r-c",
224
+ },
225
+ ])
226
+ })
227
+ })
@@ -0,0 +1,135 @@
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises"
2
+ import path from "node:path"
3
+ import process from "node:process"
4
+
5
+ /**
6
+ * On-disk registry of claude PTY children so a non-graceful server crash
7
+ * does not leak orphan claude processes (Bun.Terminal allocates a PTY via
8
+ * `setsid`, so the child lives in its own session and survives parent
9
+ * death). On the next server boot `reapStale()` SIGKILLs each recorded
10
+ * process group and removes its runtimeDir (mcp-config.json + settings).
11
+ *
12
+ * Mirrors {@link import("../terminal-pid-registry").TerminalPidRegistry}
13
+ * but adds `runtimeDir` so we can clean up the tmp dir kanna allocated
14
+ * for the spawn (otherwise it leaks every restart).
15
+ */
16
+ export interface ClaudePtyEntry {
17
+ chatId: string
18
+ sessionId: string
19
+ pid: number
20
+ cwd: string
21
+ runtimeDir: string
22
+ createdAt: number
23
+ }
24
+
25
+ interface RegistryFile {
26
+ entries: ClaudePtyEntry[]
27
+ }
28
+
29
+ export class ClaudePtyRegistry {
30
+ private readonly filePath: string
31
+ private entries: ClaudePtyEntry[] = []
32
+ private loaded = false
33
+ private writeQueue: Promise<void> = Promise.resolve()
34
+
35
+ constructor(filePath: string) {
36
+ this.filePath = filePath
37
+ }
38
+
39
+ async register(entry: Omit<ClaudePtyEntry, "createdAt">): Promise<void> {
40
+ await this.loadIfNeeded()
41
+ const next = this.entries.filter((existing) => existing.sessionId !== entry.sessionId)
42
+ next.push({ ...entry, createdAt: Date.now() })
43
+ this.entries = next
44
+ await this.persist()
45
+ }
46
+
47
+ async unregister(sessionId: string): Promise<void> {
48
+ await this.loadIfNeeded()
49
+ this.entries = this.entries.filter((entry) => entry.sessionId !== sessionId)
50
+ await this.persist()
51
+ }
52
+
53
+ async reapStale(): Promise<ClaudePtyEntry[]> {
54
+ const stored = await this.readFromDisk()
55
+ if (stored.length === 0) {
56
+ this.entries = []
57
+ this.loaded = true
58
+ return []
59
+ }
60
+ for (const entry of stored) {
61
+ killPgroup(entry.pid)
62
+ // Best-effort: remove the spawn's runtimeDir (mcp-config.json +
63
+ // settings.local.json + any other kanna-side scratch). Children
64
+ // wrote nothing user-facing here, but the dir leaks per restart
65
+ // without cleanup.
66
+ if (entry.runtimeDir && entry.runtimeDir.length > 0) {
67
+ try { await rm(entry.runtimeDir, { recursive: true, force: true }) } catch {
68
+ /* swallow — best-effort */
69
+ }
70
+ }
71
+ }
72
+ this.entries = []
73
+ this.loaded = true
74
+ await this.persist()
75
+ return stored
76
+ }
77
+
78
+ private async loadIfNeeded() {
79
+ if (this.loaded) return
80
+ this.entries = await this.readFromDisk()
81
+ this.loaded = true
82
+ }
83
+
84
+ private async readFromDisk(): Promise<ClaudePtyEntry[]> {
85
+ let raw: string
86
+ try {
87
+ raw = await readFile(this.filePath, "utf8")
88
+ } catch {
89
+ return []
90
+ }
91
+ try {
92
+ const parsed = JSON.parse(raw) as Partial<RegistryFile>
93
+ if (!parsed || !Array.isArray(parsed.entries)) return []
94
+ return parsed.entries.filter(isValidEntry)
95
+ } catch {
96
+ return []
97
+ }
98
+ }
99
+
100
+ private async persist() {
101
+ const snapshot: RegistryFile = { entries: [...this.entries] }
102
+ const serialized = JSON.stringify(snapshot)
103
+ this.writeQueue = this.writeQueue
104
+ .catch(() => undefined)
105
+ .then(async () => {
106
+ await mkdir(path.dirname(this.filePath), { recursive: true })
107
+ await writeFile(this.filePath, serialized, "utf8")
108
+ })
109
+ await this.writeQueue
110
+ }
111
+ }
112
+
113
+ function isValidEntry(value: unknown): value is ClaudePtyEntry {
114
+ if (!value || typeof value !== "object") return false
115
+ const candidate = value as Partial<ClaudePtyEntry>
116
+ return (
117
+ typeof candidate.chatId === "string"
118
+ && typeof candidate.sessionId === "string"
119
+ && typeof candidate.pid === "number"
120
+ && Number.isFinite(candidate.pid)
121
+ && typeof candidate.cwd === "string"
122
+ && typeof candidate.runtimeDir === "string"
123
+ && typeof candidate.createdAt === "number"
124
+ )
125
+ }
126
+
127
+ export function killPgroup(pid: number) {
128
+ if (process.platform === "win32") return
129
+ if (!Number.isFinite(pid) || pid <= 0) return
130
+ try {
131
+ process.kill(-pid, "SIGKILL")
132
+ } catch {
133
+ // ESRCH (already gone) and EPERM (race with kernel reap) are fine.
134
+ }
135
+ }
@@ -0,0 +1,122 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test"
2
+ import { mkdtemp, readFile, rm, writeFile, mkdir, stat } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { ClaudePtyRegistry } from "./pid-registry.adapter"
6
+
7
+ let tempDir = ""
8
+ let registryPath = ""
9
+
10
+ beforeEach(async () => {
11
+ tempDir = await mkdtemp(path.join(os.tmpdir(), "kanna-claude-pty-registry-"))
12
+ registryPath = path.join(tempDir, "claude-pty.json")
13
+ })
14
+
15
+ afterEach(async () => {
16
+ if (tempDir) {
17
+ await rm(tempDir, { recursive: true, force: true })
18
+ }
19
+ })
20
+
21
+ describe("ClaudePtyRegistry", () => {
22
+ test("register persists entries with sessionId, pid, cwd, runtimeDir", async () => {
23
+ const registry = new ClaudePtyRegistry(registryPath)
24
+ await registry.register({ chatId: "c1", sessionId: "s1", pid: 12345, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
25
+ await registry.register({ chatId: "c2", sessionId: "s2", pid: 23456, cwd: "/tmp/b", runtimeDir: "/tmp/r2" })
26
+
27
+ const raw = JSON.parse(await readFile(registryPath, "utf8")) as {
28
+ entries: Array<{ chatId: string; sessionId: string; pid: number; runtimeDir: string }>
29
+ }
30
+ expect(raw.entries).toHaveLength(2)
31
+ expect(raw.entries[0]).toMatchObject({ chatId: "c1", sessionId: "s1", pid: 12345, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
32
+ expect(raw.entries[1]).toMatchObject({ chatId: "c2", sessionId: "s2", pid: 23456, cwd: "/tmp/b", runtimeDir: "/tmp/r2" })
33
+ })
34
+
35
+ test("re-registering the same sessionId replaces the prior entry", async () => {
36
+ const registry = new ClaudePtyRegistry(registryPath)
37
+ await registry.register({ chatId: "c1", sessionId: "s1", pid: 100, cwd: "/tmp/old", runtimeDir: "/tmp/r-old" })
38
+ await registry.register({ chatId: "c1", sessionId: "s1", pid: 200, cwd: "/tmp/new", runtimeDir: "/tmp/r-new" })
39
+
40
+ const raw = JSON.parse(await readFile(registryPath, "utf8")) as { entries: Array<{ pid: number }> }
41
+ expect(raw.entries).toHaveLength(1)
42
+ expect(raw.entries[0]?.pid).toBe(200)
43
+ })
44
+
45
+ test("unregister removes only the matching sessionId", async () => {
46
+ const registry = new ClaudePtyRegistry(registryPath)
47
+ await registry.register({ chatId: "c1", sessionId: "s1", pid: 1, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
48
+ await registry.register({ chatId: "c2", sessionId: "s2", pid: 2, cwd: "/tmp/b", runtimeDir: "/tmp/r2" })
49
+ await registry.unregister("s1")
50
+
51
+ const raw = JSON.parse(await readFile(registryPath, "utf8")) as { entries: Array<{ sessionId: string }> }
52
+ expect(raw.entries).toHaveLength(1)
53
+ expect(raw.entries[0]?.sessionId).toBe("s2")
54
+ })
55
+
56
+ test("reapStale kills live process groups, removes runtimeDirs, and clears the file", async () => {
57
+ const child = Bun.spawn(
58
+ ["python3", "-c", "import os, sys, time; os.setsid(); sys.stdout.write('ready\\n'); sys.stdout.flush(); time.sleep(60)"],
59
+ { stdout: "pipe", stderr: "ignore", stdin: "ignore" },
60
+ )
61
+ const reader = child.stdout.getReader()
62
+ const decoded = new TextDecoder().decode((await reader.read()).value ?? new Uint8Array())
63
+ expect(decoded).toContain("ready")
64
+ reader.releaseLock()
65
+ const childPid = child.pid
66
+
67
+ const runtimeDir = path.join(tempDir, "spawn-runtime")
68
+ await mkdir(runtimeDir, { recursive: true })
69
+ await writeFile(path.join(runtimeDir, "mcp-config.json"), "{}", "utf8")
70
+
71
+ await writeFile(
72
+ registryPath,
73
+ JSON.stringify({
74
+ entries: [
75
+ { chatId: "c1", sessionId: "s1", pid: childPid, cwd: "/tmp/a", runtimeDir, createdAt: Date.now() },
76
+ { chatId: "c2", sessionId: "s2", pid: 999_999_999, cwd: "/tmp/b", runtimeDir: "/tmp/nonexistent", createdAt: Date.now() },
77
+ ],
78
+ }),
79
+ "utf8",
80
+ )
81
+
82
+ const registry = new ClaudePtyRegistry(registryPath)
83
+ const reaped = await registry.reapStale()
84
+
85
+ expect(reaped.map((entry) => entry.sessionId).sort()).toEqual(["s1", "s2"])
86
+
87
+ const exited = await Promise.race([
88
+ child.exited,
89
+ new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 3_000)),
90
+ ])
91
+ expect(exited).not.toBe("timeout")
92
+ expect(child.signalCode).toBe("SIGKILL")
93
+ void childPid
94
+
95
+ // runtimeDir cleaned up
96
+ await expect(stat(runtimeDir)).rejects.toThrow()
97
+
98
+ const raw = JSON.parse(await readFile(registryPath, "utf8")) as { entries: unknown[] }
99
+ expect(raw.entries).toEqual([])
100
+ }, 30_000)
101
+
102
+ test("reapStale tolerates a missing registry file", async () => {
103
+ const registry = new ClaudePtyRegistry(registryPath)
104
+ const reaped = await registry.reapStale()
105
+ expect(reaped).toEqual([])
106
+ })
107
+
108
+ test("reapStale tolerates a malformed registry file", async () => {
109
+ await writeFile(registryPath, "not json", "utf8")
110
+ const registry = new ClaudePtyRegistry(registryPath)
111
+ const reaped = await registry.reapStale()
112
+ expect(reaped).toEqual([])
113
+ })
114
+
115
+ test("register creates the parent directory if missing", async () => {
116
+ const nestedPath = path.join(tempDir, "nested", "deep", "claude-pty.json")
117
+ const registry = new ClaudePtyRegistry(nestedPath)
118
+ await registry.register({ chatId: "c1", sessionId: "s1", pid: 1, cwd: "/tmp/a", runtimeDir: "/tmp/r1" })
119
+ const raw = JSON.parse(await readFile(nestedPath, "utf8")) as { entries: unknown[] }
120
+ expect(raw.entries).toHaveLength(1)
121
+ })
122
+ })
@@ -0,0 +1,20 @@
1
+ import { createHash } from "node:crypto"
2
+ import { open } from "node:fs/promises"
3
+
4
+ export async function computeBinarySha256(filePath: string): Promise<string> {
5
+ const fd = await open(filePath, "r")
6
+ try {
7
+ const hash = createHash("sha256")
8
+ const buf = Buffer.alloc(64 * 1024)
9
+ let pos = 0
10
+ while (true) {
11
+ const { bytesRead } = await fd.read(buf, 0, buf.length, pos)
12
+ if (bytesRead === 0) break
13
+ hash.update(buf.subarray(0, bytesRead))
14
+ pos += bytesRead
15
+ }
16
+ return hash.digest("hex")
17
+ } finally {
18
+ await fd.close()
19
+ }
20
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { computeBinarySha256 } from "./binary-fingerprint.adapter"
3
+ import { mkdtemp, rm, writeFile } from "node:fs/promises"
4
+ import { tmpdir } from "node:os"
5
+ import path from "node:path"
6
+
7
+ describe("computeBinarySha256", () => {
8
+ test("returns 64-char hex sha256 of file contents", async () => {
9
+ const dir = await mkdtemp(path.join(tmpdir(), "kanna-binsha-"))
10
+ try {
11
+ const f = path.join(dir, "fake-claude")
12
+ await writeFile(f, "hello", "utf8")
13
+ const sha = await computeBinarySha256(f)
14
+ expect(sha).toMatch(/^[0-9a-f]{64}$/)
15
+ } finally { await rm(dir, { recursive: true, force: true }) }
16
+ })
17
+
18
+ test("identical content → identical sha", async () => {
19
+ const dir = await mkdtemp(path.join(tmpdir(), "kanna-binsha-"))
20
+ try {
21
+ const a = path.join(dir, "a")
22
+ const b = path.join(dir, "b")
23
+ await writeFile(a, "x", "utf8")
24
+ await writeFile(b, "x", "utf8")
25
+ expect(await computeBinarySha256(a)).toBe(await computeBinarySha256(b))
26
+ } finally { await rm(dir, { recursive: true, force: true }) }
27
+ })
28
+
29
+ test("throws when file does not exist", async () => {
30
+ await expect(computeBinarySha256("/nonexistent/path")).rejects.toThrow()
31
+ })
32
+ })