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,182 @@
1
+ import type { PtyProcess } from "./pty-process.adapter"
2
+ import type { OutputRing } from "./output-ring"
3
+
4
+ export const TRUST_DIALOG_MARKER = "trust this folder"
5
+ export const TUI_READY_MARKER = "❯ "
6
+ export const TUI_READY_HARD_CAP_DEFAULT_MS = 3000
7
+ // Quiet-period gate: after first ❯ marker hit, the TUI may still be in the
8
+ // middle of rendering (splash → cwd line → MCP spinner → input frame). The
9
+ // marker can leak from a transient render before Ink mounts the keyboard
10
+ // handler. Waiting for the output ring to stay quiet for this long after the
11
+ // marker is the cheapest proxy for "input handler attached and ready". Drops
12
+ // the race where the first prompt lands during splash and gets discarded.
13
+ export const TUI_READY_QUIET_DEFAULT_MS = 300
14
+
15
+ // Strip VT100/ANSI escape sequences and normalize non-breaking spaces so
16
+ // plain-text markers can be matched against raw PTY output. The TUI renders:
17
+ // - spaces as \x1b[1C (cursor-right-1) — replaced with regular space
18
+ // - the ❯ input prompt followed by U+00A0 (NBSP) — normalized to regular space
19
+ function stripAnsi(s: string): string {
20
+ return s
21
+ .replace(/\x1b\[[0-9;]*[A-Za-z]/g, " ")
22
+ .replace(/\x1b./g, "")
23
+ .replace(/\u00a0/g, " ")
24
+ }
25
+
26
+ export interface WaitForTuiReadyOpts {
27
+ hardCapMs?: number
28
+ pollMs?: number
29
+ quietPeriodMs?: number
30
+ }
31
+
32
+ export async function waitForTuiReady(
33
+ ring: OutputRing,
34
+ opts: WaitForTuiReadyOpts = {},
35
+ ): Promise<"marker" | "timeout"> {
36
+ const hardCapMs = opts.hardCapMs ?? TUI_READY_HARD_CAP_DEFAULT_MS
37
+ const pollMs = opts.pollMs ?? 50
38
+ const quietPeriodMs = opts.quietPeriodMs ?? TUI_READY_QUIET_DEFAULT_MS
39
+ const start = Date.now()
40
+ while (true) {
41
+ if (stripAnsi(ring.tail()).includes(TUI_READY_MARKER)) {
42
+ await waitForRingQuiet(ring, { quietMs: quietPeriodMs, pollMs, deadline: start + hardCapMs })
43
+ return "marker"
44
+ }
45
+ if (Date.now() - start >= hardCapMs) return "timeout"
46
+ await new Promise((r) => setTimeout(r, pollMs))
47
+ }
48
+ }
49
+
50
+ /**
51
+ * After the ❯ marker first appears, wait until the ring stays the same size
52
+ * for `quietMs` straight — that is the cheapest proxy for "TUI render queue
53
+ * drained, Ink keyboard handler mounted". If the deadline is reached first,
54
+ * return early (best-effort: we did see the marker). Polls every `pollMs`.
55
+ */
56
+ async function waitForRingQuiet(
57
+ ring: OutputRing,
58
+ opts: { quietMs: number; pollMs: number; deadline: number },
59
+ ): Promise<void> {
60
+ if (opts.quietMs <= 0) return
61
+ let lastLength = ring.tail().length
62
+ let quietStart = Date.now()
63
+ while (Date.now() - quietStart < opts.quietMs) {
64
+ if (Date.now() >= opts.deadline) return
65
+ await new Promise((r) => setTimeout(r, opts.pollMs))
66
+ const currentLength = ring.tail().length
67
+ if (currentLength !== lastLength) {
68
+ lastLength = currentLength
69
+ quietStart = Date.now()
70
+ }
71
+ }
72
+ }
73
+
74
+ export async function dismissTrustDialogIfPresent(
75
+ pty: PtyProcess,
76
+ ring: OutputRing,
77
+ ): Promise<boolean> {
78
+ // Strip ANSI before matching: the TUI renders spaces as \x1b[1C so the
79
+ // literal phrase "trust this folder" never appears in the raw ring bytes.
80
+ if (!stripAnsi(ring.tail()).includes(TRUST_DIALOG_MARKER)) return false
81
+ await pty.sendInput("\r")
82
+ return true
83
+ }
84
+
85
+ export interface WaitForTuiReadyWithTrustDismissOpts {
86
+ hardCapMs?: number
87
+ pollMs?: number
88
+ quietPeriodMs?: number
89
+ }
90
+
91
+ /**
92
+ * Combined helper: polls for the TUI input-box marker ("❯ ") while
93
+ * concurrently watching for the trust dialog. Dismisses the dialog once
94
+ * (via \r) and keeps polling until the real input box appears.
95
+ *
96
+ * Use this instead of separate waitForTuiReady + dismissTrustDialogIfPresent
97
+ * calls — the two-step approach races: the trust dialog blocks the input box,
98
+ * so waitForTuiReady times out before the dialog is dismissed.
99
+ */
100
+ export async function waitForTuiReadyWithTrustDismiss(
101
+ pty: PtyProcess,
102
+ ring: OutputRing,
103
+ opts: WaitForTuiReadyWithTrustDismissOpts = {},
104
+ ): Promise<"ready" | "timeout"> {
105
+ const hardCapMs = opts.hardCapMs ?? 15_000
106
+ const pollMs = opts.pollMs ?? 50
107
+ const quietPeriodMs = opts.quietPeriodMs ?? TUI_READY_QUIET_DEFAULT_MS
108
+ const start = Date.now()
109
+ let trustDismissed = false
110
+ // After dismissing the trust dialog, only match the ready marker against
111
+ // content added after the dismiss point — the trust dialog rendering itself
112
+ // contains "❯\x1b[1C1. Yes,..." which strips to "❯ 1. Yes,..." and would
113
+ // false-trigger the TUI_READY_MARKER check if the full ring were searched.
114
+ let postDismissOffset = 0
115
+
116
+ while (Date.now() - start < hardCapMs) {
117
+ const raw = ring.tail()
118
+ if (!trustDismissed && stripAnsi(raw).includes(TRUST_DIALOG_MARKER)) {
119
+ postDismissOffset = raw.length
120
+ await pty.sendInput("\r")
121
+ trustDismissed = true
122
+ } else {
123
+ const checkWindow = trustDismissed ? raw.slice(postDismissOffset) : raw
124
+ if (stripAnsi(checkWindow).includes(TUI_READY_MARKER)) {
125
+ await waitForRingQuiet(ring, { quietMs: quietPeriodMs, pollMs, deadline: start + hardCapMs })
126
+ return "ready"
127
+ }
128
+ }
129
+ await new Promise((r) => setTimeout(r, pollMs))
130
+ }
131
+ return "timeout"
132
+ }
133
+
134
+ export interface SendUserPromptOpts {
135
+ /**
136
+ * Hard cap on how long to wait for the TUI to commit the bracketed
137
+ * paste to its input box before sending Enter. Defaults to 2 s.
138
+ */
139
+ commitTimeoutMs?: number
140
+ /** Poll interval while waiting for ring growth. Defaults to 10 ms. */
141
+ pollMs?: number
142
+ }
143
+
144
+ export async function sendUserPrompt(
145
+ pty: PtyProcess,
146
+ ring: OutputRing,
147
+ text: string,
148
+ opts: SendUserPromptOpts = {},
149
+ ): Promise<void> {
150
+ // Bracketed paste (\x1b[200~...\x1b[201~) tells the TUI "this is pasted
151
+ // text, do not interpret control chars" so newlines in `text` don't
152
+ // submit prematurely. The follow-up \r is the actual "submit" key.
153
+ //
154
+ // The catch: claude's TUI processes bracketed paste asynchronously —
155
+ // multi-line pastes get collapsed into a "[Pasted text #N +X lines]"
156
+ // reference, and the input box rendering happens AFTER the paste-end
157
+ // marker is consumed. If we send \r before that rendering completes,
158
+ // the keystroke is absorbed into the still-open paste buffer instead
159
+ // of being treated as submit. A fixed-time sleep here is a brittle
160
+ // timing hack — system load, model effort settings, and PTY scheduling
161
+ // all shift the window.
162
+ //
163
+ // Adaptive fix: snapshot the output ring length, write the paste,
164
+ // then wait until the ring GROWS (i.e. the TUI rendered something in
165
+ // response to the paste) before sending Enter. The grow signal is
166
+ // deterministic — if it never arrives within commitTimeoutMs we fall
167
+ // through and send Enter anyway, matching prior timeout behaviour.
168
+ const commitTimeoutMs = opts.commitTimeoutMs ?? 2_000
169
+ const pollMs = opts.pollMs ?? 10
170
+ const baseline = ring.tail().length
171
+ await pty.sendInput(`\x1b[200~${text}\x1b[201~`)
172
+ const deadline = Date.now() + commitTimeoutMs
173
+ while (Date.now() < deadline) {
174
+ if (ring.tail().length > baseline) break
175
+ await new Promise((r) => setTimeout(r, pollMs))
176
+ }
177
+ await pty.sendInput("\r")
178
+ }
179
+
180
+ export async function sendExitCommand(pty: PtyProcess): Promise<void> {
181
+ await pty.sendInput("/exit\r")
182
+ }
@@ -0,0 +1,360 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from "bun:test"
2
+ import { mkdtemp, rm, writeFile, mkdir, appendFile } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import {
6
+ findLatestTranscript,
7
+ startTranscriptStream,
8
+ waitForResultEntry,
9
+ } from "./tui-source.adapter"
10
+ import { encodeCwd } from "./jsonl-path.adapter"
11
+
12
+ let workHome: string
13
+ let projectDir: string
14
+
15
+ beforeEach(async () => {
16
+ workHome = await mkdtemp(path.join(tmpdir(), "kanna-tui-source-"))
17
+ projectDir = path.join(workHome, ".claude", "projects", "fake-cwd")
18
+ await mkdir(projectDir, { recursive: true })
19
+ })
20
+
21
+ afterEach(async () => {
22
+ await rm(workHome, { recursive: true, force: true })
23
+ })
24
+
25
+ describe("findLatestTranscript", () => {
26
+ test("returns null when project dir empty", async () => {
27
+ const result = await findLatestTranscript(projectDir)
28
+ expect(result).toBeNull()
29
+ })
30
+
31
+ test("returns path of newest .jsonl file", async () => {
32
+ const fileA = path.join(projectDir, "aaa.jsonl")
33
+ const fileB = path.join(projectDir, "bbb.jsonl")
34
+ await writeFile(fileA, "{}\n")
35
+ await new Promise((r) => setTimeout(r, 20))
36
+ await writeFile(fileB, "{}\n")
37
+ const result = await findLatestTranscript(projectDir)
38
+ expect(result).toBe(fileB)
39
+ })
40
+
41
+ test("ignores non-.jsonl files", async () => {
42
+ await writeFile(path.join(projectDir, "notes.txt"), "hello")
43
+ const result = await findLatestTranscript(projectDir)
44
+ expect(result).toBeNull()
45
+ })
46
+
47
+ test("returns null when project dir does not exist", async () => {
48
+ const result = await findLatestTranscript(path.join(workHome, "no-such-dir"))
49
+ expect(result).toBeNull()
50
+ })
51
+
52
+ test("minMtimeMs filter skips JSONLs older than the floor", async () => {
53
+ const stale = path.join(projectDir, "stale.jsonl")
54
+ const fresh = path.join(projectDir, "fresh.jsonl")
55
+ await writeFile(stale, "{}\n")
56
+ await new Promise((r) => setTimeout(r, 20))
57
+ const floor = Date.now()
58
+ await new Promise((r) => setTimeout(r, 20))
59
+ await writeFile(fresh, "{}\n")
60
+ const result = await findLatestTranscript(projectDir, { minMtimeMs: floor })
61
+ expect(result).toBe(fresh)
62
+ })
63
+
64
+ test("minMtimeMs returns null when every JSONL is older than the floor", async () => {
65
+ const a = path.join(projectDir, "a.jsonl")
66
+ await writeFile(a, "{}\n")
67
+ await new Promise((r) => setTimeout(r, 20))
68
+ const result = await findLatestTranscript(projectDir, { minMtimeMs: Date.now() })
69
+ expect(result).toBeNull()
70
+ })
71
+ })
72
+
73
+ describe("startTranscriptStream (dir-watch)", () => {
74
+ test("picks up file written after stream start", async () => {
75
+ const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
76
+ const filePath = path.join(projectDir, "new.jsonl")
77
+ setTimeout(() => writeFile(filePath, '{"type":"hello"}\n'), 100)
78
+ const resolved = await stream.filePath
79
+ expect(resolved).toBe(filePath)
80
+ stream.close()
81
+ }, 5000)
82
+
83
+ test("opens existing file when present at start", async () => {
84
+ const filePath = path.join(projectDir, "existing.jsonl")
85
+ await writeFile(filePath, '{"type":"hello"}\n')
86
+ const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
87
+ const resolved = await stream.filePath
88
+ expect(resolved).toBe(filePath)
89
+ stream.close()
90
+ }, 5000)
91
+
92
+ test("emits complete lines as they are appended", async () => {
93
+ const filePath = path.join(projectDir, "stream.jsonl")
94
+ await writeFile(filePath, '{"type":"one"}\n')
95
+ const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
96
+ const iter = stream.lines[Symbol.asyncIterator]()
97
+ const first = await iter.next()
98
+ expect(first.value).toBe('{"type":"one"}')
99
+ setTimeout(() => writeFile(filePath, '{"type":"one"}\n{"type":"two"}\n'), 100)
100
+ const second = await iter.next()
101
+ expect(second.value).toBe('{"type":"two"}')
102
+ stream.close()
103
+ }, 5000)
104
+
105
+ test("holds partial line across writes", async () => {
106
+ const filePath = path.join(projectDir, "partial.jsonl")
107
+ await writeFile(filePath, '{"type":')
108
+ const stream = await startTranscriptStream({
109
+ projectDir,
110
+ firstFileTimeoutMs: 2000,
111
+ pollMode: true,
112
+ pollIntervalMs: 30,
113
+ })
114
+ const iter = stream.lines[Symbol.asyncIterator]()
115
+ let resolved = false
116
+ // Capture the first pending promise — it should not resolve yet (partial line)
117
+ const firstPromise = iter.next().then((r: IteratorResult<string>) => { resolved = true; return r })
118
+ await new Promise((r) => setTimeout(r, 200))
119
+ expect(resolved).toBe(false)
120
+ // Overwrite the file with a complete line — poller should pick it up
121
+ await writeFile(filePath, '{"type":"one"}\n')
122
+ const first = await firstPromise
123
+ expect(first.value).toBe('{"type":"one"}')
124
+ stream.close()
125
+ }, 5000)
126
+
127
+ test("times out when no file appears within firstFileTimeoutMs", async () => {
128
+ const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 200 })
129
+ await expect(stream.filePath).rejects.toThrow(/transcript file did not appear/)
130
+ stream.close()
131
+ }, 5000)
132
+
133
+ test("knownFilePath skips dir-watch", async () => {
134
+ const filePath = path.join(projectDir, "known.jsonl")
135
+ await writeFile(filePath, '{"type":"hello"}\n')
136
+ const stream = await startTranscriptStream({
137
+ projectDir,
138
+ knownFilePath: filePath,
139
+ firstFileTimeoutMs: 500,
140
+ })
141
+ const resolved = await stream.filePath
142
+ expect(resolved).toBe(filePath)
143
+ stream.close()
144
+ }, 5000)
145
+ })
146
+
147
+ describe("startTranscriptStream (poll-mode)", () => {
148
+ test("emits lines via polling when pollMode=true", async () => {
149
+ const stream = await startTranscriptStream({
150
+ projectDir,
151
+ pollMode: true,
152
+ pollIntervalMs: 30,
153
+ firstFileTimeoutMs: 2000,
154
+ })
155
+ const filePath = path.join(projectDir, "poll.jsonl")
156
+ setTimeout(() => writeFile(filePath, '{"type":"polled"}\n'), 100)
157
+ const iter = stream.lines[Symbol.asyncIterator]()
158
+ const first = await iter.next()
159
+ expect(first.value).toBe('{"type":"polled"}')
160
+ stream.close()
161
+ }, 5000)
162
+ })
163
+
164
+ describe("startTranscriptStream (registry resolution)", () => {
165
+ // Regression: claude-code's per-pid session registry pins the JSONL path
166
+ // to the live child. When the registry-resolved JSONL never appears (e.g.
167
+ // claude was spawned but no prompt sent), older builds fell back to the
168
+ // newest mtime in the project dir — which is another concurrent chat's
169
+ // JSONL. That caused cross-session transcript bleed. The fix removed the
170
+ // fallback: registry path is authoritative, poll until close.
171
+ test("registry resolves but JSONL missing — never falls back to other JSONL in same dir", async () => {
172
+ const pid = 99001
173
+ // Real cwd dir so `encodeCwd` (which realpaths) doesn't ENOENT.
174
+ const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
175
+ const encoded = encodeCwd(realCwd)
176
+ const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
177
+ await mkdir(ownProjectDir, { recursive: true })
178
+ const sessionsDir = path.join(workHome, ".claude", "sessions")
179
+ await mkdir(sessionsDir, { recursive: true })
180
+ const ownSessionId = "our-session-aaaaaaaaaa"
181
+ await writeFile(
182
+ path.join(sessionsDir, `${pid}.json`),
183
+ JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
184
+ )
185
+ // Tempt the bug: drop a NEWER unrelated JSONL into the same project dir.
186
+ // Under the old mtime fallback this would have been picked up after
187
+ // `firstFileTimeoutMs` elapsed with no own-JSONL present.
188
+ const strangerFile = path.join(ownProjectDir, "stranger-session.jsonl")
189
+ await writeFile(strangerFile, '{"type":"assistant","message":{"content":[{"type":"text","text":"NOT OURS"}]}}\n')
190
+
191
+ const stream = await startTranscriptStream({
192
+ projectDir: ownProjectDir,
193
+ homeDir: workHome,
194
+ claudeChildPid: pid,
195
+ sessionRegistryTimeoutMs: 300,
196
+ // High timeout so the registry-poll timeout cannot fire during the
197
+ // 600 ms pending check below — this test guards bleed isolation, not
198
+ // timeout behaviour (covered separately).
199
+ firstFileTimeoutMs: 5_000,
200
+ pollIntervalMs: 20,
201
+ })
202
+
203
+ // filePath must NOT resolve to the stranger file even after timeouts.
204
+ const beforeWrite = await Promise.race([
205
+ stream.filePath
206
+ .then((fp) => ({ kind: "resolved" as const, fp }))
207
+ .catch((err: Error) => ({ kind: "rejected" as const, err })),
208
+ new Promise<{ kind: "pending" }>((r) => setTimeout(() => r({ kind: "pending" }), 600)),
209
+ ])
210
+ expect(beforeWrite.kind).toBe("pending")
211
+
212
+ // Now the registry-pointed JSONL appears — filePath should resolve to it.
213
+ const ownFile = path.join(ownProjectDir, `${ownSessionId}.jsonl`)
214
+ await writeFile(ownFile, '{"type":"assistant","message":{"content":[{"type":"text","text":"ours"}]}}\n')
215
+ const resolved = await stream.filePath
216
+ expect(resolved).toBe(ownFile)
217
+ expect(resolved).not.toBe(strangerFile)
218
+ stream.close()
219
+ }, 5000)
220
+
221
+ // Regression: when claude TUI rendered the input box but the first prompt
222
+ // never reached it (input-handler mount race, splash banner swallow, etc.),
223
+ // the registry-resolved JSONL was never created and the driver waited
224
+ // forever inside locateFirstFile. firstFileTimeoutMs now bounds that wait;
225
+ // the rejection surfaces as a failure event and the user can retry instead
226
+ // of seeing a wedged session.
227
+ test("registry resolved but JSONL never appears — filePath rejects after firstFileTimeoutMs", async () => {
228
+ const pid = 99003
229
+ const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
230
+ const encoded = encodeCwd(realCwd)
231
+ const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
232
+ await mkdir(ownProjectDir, { recursive: true })
233
+ const sessionsDir = path.join(workHome, ".claude", "sessions")
234
+ await mkdir(sessionsDir, { recursive: true })
235
+ const ownSessionId = "our-session-cccccccccc"
236
+ await writeFile(
237
+ path.join(sessionsDir, `${pid}.json`),
238
+ JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
239
+ )
240
+
241
+ const stream = await startTranscriptStream({
242
+ projectDir: ownProjectDir,
243
+ homeDir: workHome,
244
+ claudeChildPid: pid,
245
+ sessionRegistryTimeoutMs: 300,
246
+ firstFileTimeoutMs: 150,
247
+ pollIntervalMs: 20,
248
+ })
249
+
250
+ const start = Date.now()
251
+ await expect(stream.filePath).rejects.toThrow(/did not appear in 150ms/)
252
+ const elapsed = Date.now() - start
253
+ // Allow scheduler slack but ensure we did not wait orders of magnitude longer.
254
+ expect(elapsed).toBeLessThan(1_500)
255
+ stream.close()
256
+ }, 5000)
257
+
258
+ test("registry resolves with existing JSONL — returns registry path immediately", async () => {
259
+ const pid = 99002
260
+ const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
261
+ const encoded = encodeCwd(realCwd)
262
+ const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
263
+ await mkdir(ownProjectDir, { recursive: true })
264
+ const sessionsDir = path.join(workHome, ".claude", "sessions")
265
+ await mkdir(sessionsDir, { recursive: true })
266
+ const ownSessionId = "our-session-bbbbbbbbbb"
267
+ const ownFile = path.join(ownProjectDir, `${ownSessionId}.jsonl`)
268
+ await writeFile(ownFile, "{}\n")
269
+ await writeFile(
270
+ path.join(sessionsDir, `${pid}.json`),
271
+ JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
272
+ )
273
+ // Newer stranger JSONL must not win — registry path is authoritative.
274
+ await new Promise((r) => setTimeout(r, 20))
275
+ await writeFile(path.join(ownProjectDir, "stranger.jsonl"), "{}\n")
276
+
277
+ const stream = await startTranscriptStream({
278
+ projectDir: ownProjectDir,
279
+ homeDir: workHome,
280
+ claudeChildPid: pid,
281
+ })
282
+ const resolved = await stream.filePath
283
+ expect(resolved).toBe(ownFile)
284
+ stream.close()
285
+ }, 5000)
286
+ })
287
+
288
+ describe("startTranscriptStream (safety-net poll vs fs.watch drops)", () => {
289
+ // Regression: fs.watch on macOS/FSEvents was observed to coalesce or drop
290
+ // events when claude appended `assistant` + `system/turn_duration` rows in
291
+ // rapid succession at the end of a turn — Kanna's stream would silently
292
+ // stop reading at ~52k bytes while the JSONL grew to ~55k. The safety-net
293
+ // poll runs alongside fs.watch and guarantees eventual delivery.
294
+ test("appends made after stream setup are delivered even with no further watcher fires", async () => {
295
+ const filePath = path.join(projectDir, "watched.jsonl")
296
+ await writeFile(filePath, '{"type":"system","subtype":"init"}\n')
297
+ const stream = await startTranscriptStream({
298
+ projectDir,
299
+ knownFilePath: filePath,
300
+ firstFileTimeoutMs: 500,
301
+ })
302
+ const iter = stream.lines[Symbol.asyncIterator]()
303
+ const first = await iter.next()
304
+ expect(first.value).toContain('"system"')
305
+
306
+ // Append multiple rows AFTER the watcher is set up. On a buggy build
307
+ // where fs.watch drops the second/third append, the safety-net poll
308
+ // (fires every 500 ms) must still pick them up within the test
309
+ // timeout.
310
+ await appendFile(filePath, '{"type":"assistant","message":{"content":[{"type":"text","text":"a"}]}}\n')
311
+ await appendFile(filePath, '{"type":"assistant","message":{"content":[{"type":"text","text":"b"}]}}\n')
312
+ await appendFile(filePath, '{"type":"system","subtype":"turn_duration","durationMs":42}\n')
313
+
314
+ const collected: string[] = []
315
+ const deadline = Date.now() + 2_000
316
+ while (collected.length < 3 && Date.now() < deadline) {
317
+ const nxt = await Promise.race([
318
+ iter.next(),
319
+ new Promise<{ value: undefined; done: true }>((r) => setTimeout(() => r({ value: undefined, done: true }), 1_500)),
320
+ ])
321
+ if (nxt.done) break
322
+ collected.push(nxt.value)
323
+ }
324
+ expect(collected.length).toBe(3)
325
+ expect(collected[0]).toContain('"a"')
326
+ expect(collected[1]).toContain('"b"')
327
+ expect(collected[2]).toContain("turn_duration")
328
+ stream.close()
329
+ }, 8000)
330
+ })
331
+
332
+ describe("waitForResultEntry", () => {
333
+ test("resolves on first result line", async () => {
334
+ const filePath = path.join(projectDir, "result.jsonl")
335
+ await writeFile(filePath, '{"type":"system"}\n{"type":"assistant"}\n')
336
+ const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
337
+ setTimeout(() => writeFile(filePath, '{"type":"system"}\n{"type":"assistant"}\n{"type":"result","subtype":"success"}\n'), 100)
338
+ const entry = await waitForResultEntry(stream, { timeoutMs: 2000 })
339
+ expect(entry.parsed.type).toBe("result")
340
+ stream.close()
341
+ }, 5000)
342
+
343
+ test("rejects on abort signal", async () => {
344
+ const filePath = path.join(projectDir, "abort.jsonl")
345
+ await writeFile(filePath, '{"type":"system"}\n')
346
+ const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
347
+ const ctrl = new AbortController()
348
+ setTimeout(() => ctrl.abort(), 50)
349
+ await expect(waitForResultEntry(stream, { signal: ctrl.signal })).rejects.toThrow(/aborted/i)
350
+ stream.close()
351
+ }, 5000)
352
+
353
+ test("rejects on timeout", async () => {
354
+ const filePath = path.join(projectDir, "timeout.jsonl")
355
+ await writeFile(filePath, '{"type":"system"}\n')
356
+ const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
357
+ await expect(waitForResultEntry(stream, { timeoutMs: 100 })).rejects.toThrow(/timed out/i)
358
+ stream.close()
359
+ }, 5000)
360
+ })