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,201 @@
1
+ import { afterEach, describe, expect, test } from "bun:test"
2
+ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import { scanSkillDirs, SkillCache } from "./skill-discovery"
6
+
7
+ const tempDirs: string[] = []
8
+
9
+ afterEach(async () => {
10
+ await Promise.all(
11
+ tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))
12
+ )
13
+ })
14
+
15
+ async function makeTempDir(prefix = "tinkaria-skill-discovery-") {
16
+ const dir = await mkdtemp(join(tmpdir(), prefix))
17
+ tempDirs.push(dir)
18
+ return dir
19
+ }
20
+
21
+ async function createSkillDir(parent: string, name: string): Promise<void> {
22
+ const skillDir = join(parent, name)
23
+ await mkdir(skillDir, { recursive: true })
24
+ await writeFile(join(skillDir, `${name}.md`), `---\nname: ${name}\n---\nSkill content`)
25
+ }
26
+
27
+ describe("scanSkillDirs", () => {
28
+ test("returns skill names from user-level skills dir", async () => {
29
+ const homeDir = await makeTempDir()
30
+ const projectDir = await makeTempDir()
31
+ const skillsDir = join(homeDir, ".claude", "skills")
32
+ await mkdir(skillsDir, { recursive: true })
33
+ await createSkillDir(skillsDir, "commit")
34
+ await createSkillDir(skillsDir, "review-pr")
35
+
36
+ const skills = await scanSkillDirs(projectDir, homeDir)
37
+ expect(skills).toContain("commit")
38
+ expect(skills).toContain("review-pr")
39
+ expect(skills).toHaveLength(2)
40
+ })
41
+
42
+ test("returns skill names from project-level skills dir", async () => {
43
+ const homeDir = await makeTempDir()
44
+ const projectDir = await makeTempDir()
45
+ const skillsDir = join(projectDir, ".claude", "skills")
46
+ await mkdir(skillsDir, { recursive: true })
47
+ await createSkillDir(skillsDir, "c3")
48
+ await createSkillDir(skillsDir, "frontend-design")
49
+
50
+ const skills = await scanSkillDirs(projectDir, homeDir)
51
+ expect(skills).toContain("c3")
52
+ expect(skills).toContain("frontend-design")
53
+ expect(skills).toHaveLength(2)
54
+ })
55
+
56
+ test("merges user and project skills, deduplicates", async () => {
57
+ const homeDir = await makeTempDir()
58
+ const projectDir = await makeTempDir()
59
+
60
+ const userSkills = join(homeDir, ".claude", "skills")
61
+ await mkdir(userSkills, { recursive: true })
62
+ await createSkillDir(userSkills, "commit")
63
+ await createSkillDir(userSkills, "shared-skill")
64
+
65
+ const projSkills = join(projectDir, ".claude", "skills")
66
+ await mkdir(projSkills, { recursive: true })
67
+ await createSkillDir(projSkills, "c3")
68
+ await createSkillDir(projSkills, "shared-skill")
69
+
70
+ const skills = await scanSkillDirs(projectDir, homeDir)
71
+ expect(skills).toContain("commit")
72
+ expect(skills).toContain("c3")
73
+ expect(skills).toContain("shared-skill")
74
+ expect(skills).toHaveLength(3)
75
+ })
76
+
77
+ test("returns empty array when no skills dirs exist", async () => {
78
+ const homeDir = await makeTempDir()
79
+ const projectDir = await makeTempDir()
80
+
81
+ const skills = await scanSkillDirs(projectDir, homeDir)
82
+ expect(skills).toEqual([])
83
+ })
84
+
85
+ test("ignores plain files in skills directory", async () => {
86
+ const homeDir = await makeTempDir()
87
+ const projectDir = await makeTempDir()
88
+ const skillsDir = join(homeDir, ".claude", "skills")
89
+ await mkdir(skillsDir, { recursive: true })
90
+ await createSkillDir(skillsDir, "real-skill")
91
+ await writeFile(join(skillsDir, "not-a-skill.md"), "just a file")
92
+
93
+ const skills = await scanSkillDirs(projectDir, homeDir)
94
+ expect(skills).toEqual(["real-skill"])
95
+ })
96
+
97
+ test("returns sorted skill names for deterministic output", async () => {
98
+ const homeDir = await makeTempDir()
99
+ const projectDir = await makeTempDir()
100
+ const skillsDir = join(homeDir, ".claude", "skills")
101
+ await mkdir(skillsDir, { recursive: true })
102
+ await createSkillDir(skillsDir, "zebra")
103
+ await createSkillDir(skillsDir, "alpha")
104
+ await createSkillDir(skillsDir, "middle")
105
+
106
+ const skills = await scanSkillDirs(projectDir, homeDir)
107
+ expect(skills).toEqual(["alpha", "middle", "zebra"])
108
+ })
109
+ })
110
+
111
+ describe("SkillCache", () => {
112
+ test("returns discovered skills on first call", async () => {
113
+ const homeDir = await makeTempDir()
114
+ const projectDir = await makeTempDir()
115
+ const skillsDir = join(projectDir, ".claude", "skills")
116
+ await mkdir(skillsDir, { recursive: true })
117
+ await createSkillDir(skillsDir, "c3")
118
+
119
+ const cache = new SkillCache({ ttlMs: 5000, homeDir })
120
+ const skills = await cache.get(projectDir)
121
+ expect(skills).toEqual(["c3"])
122
+ })
123
+
124
+ test("returns cached result within TTL", async () => {
125
+ const homeDir = await makeTempDir()
126
+ const projectDir = await makeTempDir()
127
+ const skillsDir = join(projectDir, ".claude", "skills")
128
+ await mkdir(skillsDir, { recursive: true })
129
+ await createSkillDir(skillsDir, "c3")
130
+
131
+ const cache = new SkillCache({ ttlMs: 5000, homeDir })
132
+ const first = await cache.get(projectDir)
133
+
134
+ // Add another skill after first scan
135
+ await createSkillDir(skillsDir, "new-skill")
136
+ const second = await cache.get(projectDir)
137
+
138
+ // Should return cached (stale) result
139
+ expect(second).toEqual(first)
140
+ expect(second).not.toContain("new-skill")
141
+ })
142
+
143
+ test("re-scans after TTL expires", async () => {
144
+ const homeDir = await makeTempDir()
145
+ const projectDir = await makeTempDir()
146
+ const skillsDir = join(projectDir, ".claude", "skills")
147
+ await mkdir(skillsDir, { recursive: true })
148
+ await createSkillDir(skillsDir, "c3")
149
+
150
+ const cache = new SkillCache({ ttlMs: 50, homeDir })
151
+ const first = await cache.get(projectDir)
152
+ expect(first).toEqual(["c3"])
153
+
154
+ await createSkillDir(skillsDir, "new-skill")
155
+ await new Promise((r) => setTimeout(r, 60))
156
+
157
+ const second = await cache.get(projectDir)
158
+ expect(second).toContain("c3")
159
+ expect(second).toContain("new-skill")
160
+ })
161
+
162
+ test("invalidate clears specific project cache", async () => {
163
+ const homeDir = await makeTempDir()
164
+ const projectDir = await makeTempDir()
165
+ const skillsDir = join(projectDir, ".claude", "skills")
166
+ await mkdir(skillsDir, { recursive: true })
167
+ await createSkillDir(skillsDir, "c3")
168
+
169
+ const cache = new SkillCache({ ttlMs: 60_000, homeDir })
170
+ await cache.get(projectDir)
171
+
172
+ await createSkillDir(skillsDir, "new-skill")
173
+ cache.invalidate(projectDir)
174
+
175
+ const refreshed = await cache.get(projectDir)
176
+ expect(refreshed).toContain("new-skill")
177
+ })
178
+
179
+ test("invalidate without args clears all caches", async () => {
180
+ const homeDir = await makeTempDir()
181
+ const proj1 = await makeTempDir()
182
+ const proj2 = await makeTempDir()
183
+
184
+ for (const dir of [proj1, proj2]) {
185
+ const skillsDir = join(dir, ".claude", "skills")
186
+ await mkdir(skillsDir, { recursive: true })
187
+ await createSkillDir(skillsDir, "skill-a")
188
+ }
189
+
190
+ const cache = new SkillCache({ ttlMs: 60_000, homeDir })
191
+ await cache.get(proj1)
192
+ await cache.get(proj2)
193
+
194
+ cache.invalidate()
195
+
196
+ // Add new skills after invalidation
197
+ await createSkillDir(join(proj1, ".claude", "skills"), "added")
198
+ const refreshed = await cache.get(proj1)
199
+ expect(refreshed).toContain("added")
200
+ })
201
+ })
@@ -0,0 +1,77 @@
1
+ import { readdir } from "node:fs/promises"
2
+ import { join } from "node:path"
3
+ import { homedir } from "node:os"
4
+ import { LOG_PREFIX } from "../shared/branding"
5
+
6
+ async function listSkillNames(dir: string): Promise<string[]> {
7
+ try {
8
+ const entries = await readdir(dir, { withFileTypes: true })
9
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name)
10
+ } catch (error) {
11
+ if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return []
12
+ const message = error instanceof Error ? error.message : String(error)
13
+ console.warn(`${LOG_PREFIX} Failed to scan skills dir ${dir}: ${message}`)
14
+ return []
15
+ }
16
+ }
17
+
18
+ export async function scanSkillDirs(workspacePath: string, homeDir?: string): Promise<string[]> {
19
+ const home = homeDir ?? homedir()
20
+ const userSkillsDir = join(home, ".claude", "skills")
21
+ const projectSkillsDir = join(workspacePath, ".claude", "skills")
22
+
23
+ const [userSkills, projectSkills] = await Promise.all([
24
+ listSkillNames(userSkillsDir),
25
+ listSkillNames(projectSkillsDir),
26
+ ])
27
+
28
+ const seen = new Set<string>()
29
+ const merged: string[] = []
30
+ for (const name of [...projectSkills, ...userSkills]) {
31
+ if (!seen.has(name)) {
32
+ seen.add(name)
33
+ merged.push(name)
34
+ }
35
+ }
36
+ return merged.sort()
37
+ }
38
+
39
+ interface CacheEntry {
40
+ skills: string[]
41
+ expiresAt: number
42
+ }
43
+
44
+ interface SkillCacheOptions {
45
+ ttlMs?: number
46
+ homeDir?: string
47
+ }
48
+
49
+ export class SkillCache {
50
+ private readonly cache = new Map<string, CacheEntry>()
51
+ private readonly ttlMs: number
52
+ private readonly homeDir: string | undefined
53
+
54
+ constructor(options?: SkillCacheOptions) {
55
+ this.ttlMs = options?.ttlMs ?? 30_000
56
+ this.homeDir = options?.homeDir
57
+ }
58
+
59
+ async get(workspacePath: string): Promise<string[]> {
60
+ const entry = this.cache.get(workspacePath)
61
+ if (entry && Date.now() < entry.expiresAt) {
62
+ return entry.skills
63
+ }
64
+
65
+ const skills = await scanSkillDirs(workspacePath, this.homeDir)
66
+ this.cache.set(workspacePath, { skills, expiresAt: Date.now() + this.ttlMs })
67
+ return skills
68
+ }
69
+
70
+ invalidate(workspacePath?: string): void {
71
+ if (workspacePath) {
72
+ this.cache.delete(workspacePath)
73
+ } else {
74
+ this.cache.clear()
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,67 @@
1
+ // In-memory EventStore stub for unit tests.
2
+ // Implements only the subset of EventStore methods required by tool-callback
3
+ // and related MCP tool tests.
4
+
5
+ import type { ToolRequest, ToolRequestStatus, ToolRequestDecision } from "../../shared/permission-policy"
6
+ import { POLICY_TERMINAL_STATUSES } from "../../shared/permission-policy"
7
+ import type { EventStore } from "../event-store"
8
+
9
+ export function createTestStorage(): { dir: string; cleanup: () => Promise<void> } {
10
+ throw new Error("test-helpers stub — implement when activating PTY mcp-tool tests")
11
+ }
12
+
13
+ interface InMemoryEventStore {
14
+ initialize(): Promise<void>
15
+ putToolRequest(req: ToolRequest): Promise<void>
16
+ getToolRequest(id: string): ToolRequest | null
17
+ listPendingToolRequests(chatId: string): ToolRequest[]
18
+ resolveToolRequest(
19
+ id: string,
20
+ update: { status: ToolRequestStatus; decision: ToolRequestDecision; resolvedAt: number; mismatchReason?: string },
21
+ ): Promise<void>
22
+ scanAllToolRequests(): ToolRequest[]
23
+ }
24
+
25
+ export function createTestEventStore(_dataDir?: string): EventStore {
26
+ const requests = new Map<string, ToolRequest>()
27
+
28
+ const store: InMemoryEventStore = {
29
+ async initialize() {
30
+ // No-op for in-memory store
31
+ },
32
+
33
+ async putToolRequest(req: ToolRequest): Promise<void> {
34
+ requests.set(req.id, { ...req })
35
+ },
36
+
37
+ getToolRequest(id: string): ToolRequest | null {
38
+ return requests.get(id) ?? null
39
+ },
40
+
41
+ listPendingToolRequests(chatId: string): ToolRequest[] {
42
+ const result: ToolRequest[] = []
43
+ for (const req of requests.values()) {
44
+ if (req.chatId === chatId && !POLICY_TERMINAL_STATUSES.has(req.status)) {
45
+ result.push({ ...req })
46
+ }
47
+ }
48
+ return result
49
+ },
50
+
51
+ async resolveToolRequest(
52
+ id: string,
53
+ update: { status: ToolRequestStatus; decision: ToolRequestDecision; resolvedAt: number; mismatchReason?: string },
54
+ ): Promise<void> {
55
+ const existing = requests.get(id)
56
+ if (existing) {
57
+ requests.set(id, { ...existing, ...update })
58
+ }
59
+ },
60
+
61
+ scanAllToolRequests(): ToolRequest[] {
62
+ return Array.from(requests.values()).map((r) => ({ ...r }))
63
+ },
64
+ }
65
+
66
+ return store as unknown as EventStore
67
+ }
@@ -0,0 +1,309 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test"
2
+ import { mkdtemp, rm } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { TerminalManager } from "./terminal-manager"
6
+
7
+ const SHELL_START_TIMEOUT_MS = 5_000
8
+ const COMMAND_TIMEOUT_MS = 5_000
9
+ const FOCUS_IN_SEQUENCE = "\x1b[I"
10
+ const RAW_READ_HEX_COMMAND = `python3 -c "exec('import os,sys,tty,termios,select\\nfd=sys.stdin.fileno()\\nold=termios.tcgetattr(fd)\\ntty.setraw(fd)\\ntry:\\n sys.stdout.write(\"__RAW_READY__\\\\n\")\\n sys.stdout.flush()\\n r,_,_=select.select([fd],[],[],1)\\n data=os.read(fd,8) if r else b\"\"\\n print(data.hex() or \"__EMPTY__\")\\nfinally:\\n termios.tcsetattr(fd, termios.TCSADRAIN, old)')"\r`
11
+
12
+ const isSupportedPlatform = process.platform !== "win32" && typeof Bun.Terminal === "function"
13
+ const describeIfSupported = isSupportedPlatform ? describe : describe.skip
14
+
15
+ let tempProjectPath = ""
16
+ const originalTestShell = process.env.TINKARIA_SHELL
17
+
18
+ beforeAll(async () => {
19
+ if (!isSupportedPlatform) return
20
+ process.env.TINKARIA_SHELL = "/bin/sh"
21
+ tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "tinkaria-terminal-manager-"))
22
+ })
23
+
24
+ afterAll(() => {
25
+ if (originalTestShell === undefined) {
26
+ delete process.env.TINKARIA_SHELL
27
+ return
28
+ }
29
+
30
+ process.env.TINKARIA_SHELL = originalTestShell
31
+ })
32
+
33
+ afterEach(async () => {
34
+ if (!tempProjectPath) return
35
+ await rm(tempProjectPath, { recursive: true, force: true })
36
+ tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "tinkaria-terminal-manager-"))
37
+ })
38
+
39
+ async function waitFor(check: () => boolean, timeoutMs: number, intervalMs = 25) {
40
+ const startedAt = Date.now()
41
+ while (Date.now() - startedAt < timeoutMs) {
42
+ if (check()) return
43
+ await Bun.sleep(intervalMs)
44
+ }
45
+ throw new Error(`Timed out after ${timeoutMs}ms`)
46
+ }
47
+
48
+ async function createSession(terminalId: string) {
49
+ const manager = new TerminalManager()
50
+ let output = ""
51
+ manager.onEvent((event) => {
52
+ if (event.type === "terminal.output" && event.terminalId === terminalId) {
53
+ output += event.data
54
+ }
55
+ })
56
+
57
+ manager.createTerminal({
58
+ workspacePath: tempProjectPath,
59
+ terminalId,
60
+ cols: 80,
61
+ rows: 24,
62
+ scrollback: 1_000,
63
+ })
64
+
65
+ manager.write(terminalId, "printf '__KANNA_READY__\\n'\r")
66
+ await waitFor(() => output.includes("__KANNA_READY__"), SHELL_START_TIMEOUT_MS)
67
+ await waitForTerminalToSettle(() => output)
68
+
69
+ return {
70
+ manager,
71
+ getOutput: () => output,
72
+ }
73
+ }
74
+
75
+ async function waitForOutputToContain(getOutput: () => string, value: string, timeoutMs = COMMAND_TIMEOUT_MS) {
76
+ await waitFor(() => getOutput().includes(value), timeoutMs)
77
+ }
78
+
79
+ async function waitForTerminalToSettle(getOutput: () => string, idleMs = 75, timeoutMs = COMMAND_TIMEOUT_MS) {
80
+ let lastOutput = getOutput()
81
+ let unchangedSince = Date.now()
82
+
83
+ await waitFor(() => {
84
+ const currentOutput = getOutput()
85
+ if (currentOutput !== lastOutput) {
86
+ lastOutput = currentOutput
87
+ unchangedSince = Date.now()
88
+ return false
89
+ }
90
+
91
+ return Date.now() - unchangedSince >= idleMs
92
+ }, timeoutMs)
93
+ }
94
+
95
+ describeIfSupported("TerminalManager", () => {
96
+ test("ctrl+c interrupts the foreground job and keeps the shell alive", async () => {
97
+ const terminalId = "terminal-ctrl-c-foreground"
98
+ const { manager, getOutput } = await createSession(terminalId)
99
+
100
+ try {
101
+ manager.write(terminalId, 'python3 -c "import time; time.sleep(30)"\r')
102
+ await waitFor(() => getOutput().includes("time.sleep(30)"), COMMAND_TIMEOUT_MS)
103
+
104
+ manager.write(terminalId, "\x03")
105
+ manager.write(terminalId, "printf '__KANNA_AFTER_INT__\\n'\r")
106
+
107
+ await waitFor(() => getOutput().includes("__KANNA_AFTER_INT__"), COMMAND_TIMEOUT_MS)
108
+
109
+ const snapshot = manager.getSnapshot(terminalId)
110
+ expect(snapshot?.status).toBe("running")
111
+ expect(getOutput()).toContain("__KANNA_AFTER_INT__")
112
+ } finally {
113
+ manager.close(terminalId)
114
+ }
115
+ })
116
+
117
+ test("ctrl+c at an idle prompt does not exit the shell", async () => {
118
+ const terminalId = "terminal-ctrl-c-prompt"
119
+ const { manager, getOutput } = await createSession(terminalId)
120
+
121
+ try {
122
+ const before = getOutput()
123
+ manager.write(terminalId, "\x03")
124
+
125
+ await waitFor(() => getOutput().length > before.length, COMMAND_TIMEOUT_MS)
126
+
127
+ const snapshot = manager.getSnapshot(terminalId)
128
+ expect(snapshot?.status).toBe("running")
129
+ expect(getOutput().length).toBeGreaterThan(before.length)
130
+ } finally {
131
+ manager.close(terminalId)
132
+ }
133
+ })
134
+
135
+ test("ctrl+d preserves eof behavior", async () => {
136
+ const terminalId = "terminal-ctrl-d"
137
+ const { manager, getOutput } = await createSession(terminalId)
138
+
139
+ try {
140
+ manager.write(terminalId, "printf '__KANNA_CTRL_D_READY__\\n'\r")
141
+ await waitFor(() => getOutput().includes("__KANNA_CTRL_D_READY__"), COMMAND_TIMEOUT_MS)
142
+ await waitForTerminalToSettle(getOutput, 200)
143
+
144
+ manager.write(terminalId, "\x04")
145
+
146
+ await waitFor(() => manager.getSnapshot(terminalId)?.status === "exited", COMMAND_TIMEOUT_MS)
147
+
148
+ expect(manager.getSnapshot(terminalId)?.exitCode).toBe(0)
149
+ } finally {
150
+ manager.close(terminalId)
151
+ }
152
+ })
153
+
154
+ test("filters leaked focus reports while focus mode is disabled", async () => {
155
+ const terminalId = "terminal-focus-filtered"
156
+ const { manager, getOutput } = await createSession(terminalId)
157
+
158
+ try {
159
+ const beforeLength = getOutput().length
160
+ manager.write(terminalId, RAW_READ_HEX_COMMAND)
161
+ await waitForOutputToContain(getOutput, "__RAW_READY__")
162
+
163
+ manager.write(terminalId, FOCUS_IN_SEQUENCE)
164
+ await waitForOutputToContain(getOutput, "__EMPTY__")
165
+
166
+ const interactionOutput = getOutput().slice(beforeLength)
167
+ expect(interactionOutput).toContain("__EMPTY__")
168
+ expect(interactionOutput).not.toContain("1b5b49")
169
+ } finally {
170
+ manager.close(terminalId)
171
+ }
172
+ })
173
+
174
+ test("forwards focus reports when the session mode is enabled", () => {
175
+ const manager = new TerminalManager() as unknown as {
176
+ sessions: Map<
177
+ string,
178
+ {
179
+ status: "running" | "exited"
180
+ focusReportingEnabled: boolean
181
+ terminal: { write: (data: string) => void }
182
+ process: Bun.Subprocess | null
183
+ }
184
+ >
185
+ write: (terminalId: string, data: string) => void
186
+ }
187
+ const writes: string[] = []
188
+
189
+ manager.sessions.set("terminal-focus-forwarded", {
190
+ status: "running",
191
+ focusReportingEnabled: true,
192
+ terminal: {
193
+ write(data: string) {
194
+ writes.push(data)
195
+ },
196
+ },
197
+ process: null,
198
+ })
199
+
200
+ manager.write("terminal-focus-forwarded", FOCUS_IN_SEQUENCE)
201
+
202
+ expect(writes).toEqual([FOCUS_IN_SEQUENCE])
203
+ })
204
+
205
+ test("resize signals the shell process group with SIGWINCH", () => {
206
+ const manager = new TerminalManager() as unknown as {
207
+ sessions: Map<
208
+ string,
209
+ {
210
+ cols: number
211
+ rows: number
212
+ headless: { resize: (cols: number, rows: number) => void }
213
+ terminal: { resize: (cols: number, rows: number) => void }
214
+ process: { pid: number } | null
215
+ }
216
+ >
217
+ resize: (terminalId: string, cols: number, rows: number) => void
218
+ }
219
+ const resizeCalls: Array<{ cols: number; rows: number }> = []
220
+ const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []
221
+ const originalKill = process.kill
222
+
223
+ ;(process as typeof process & {
224
+ kill: (pid: number, signal?: NodeJS.Signals | number) => boolean
225
+ }).kill = ((pid: number, signal?: NodeJS.Signals | number) => {
226
+ if (typeof signal === "string") {
227
+ killCalls.push({ pid, signal })
228
+ }
229
+ return true
230
+ }) as typeof process.kill
231
+
232
+ manager.sessions.set("terminal-resize-sigwinch", {
233
+ cols: 80,
234
+ rows: 24,
235
+ headless: {
236
+ resize(cols, rows) {
237
+ resizeCalls.push({ cols, rows })
238
+ },
239
+ },
240
+ terminal: {
241
+ resize(cols, rows) {
242
+ resizeCalls.push({ cols, rows })
243
+ },
244
+ },
245
+ process: { pid: 4321 },
246
+ })
247
+
248
+ try {
249
+ manager.resize("terminal-resize-sigwinch", 120, 40)
250
+ } finally {
251
+ process.kill = originalKill
252
+ }
253
+
254
+ expect(resizeCalls).toEqual([
255
+ { cols: 120, rows: 40 },
256
+ { cols: 120, rows: 40 },
257
+ ])
258
+ expect(killCalls).toContainEqual({ pid: -4321, signal: "SIGWINCH" })
259
+ })
260
+
261
+ test("new sessions reset focus mode back to filtered", async () => {
262
+ const manager = new TerminalManager()
263
+ const firstTerminalId = "terminal-focus-first"
264
+ const secondTerminalId = "terminal-focus-second"
265
+ let outputByTerminalId = new Map<string, string>()
266
+
267
+ manager.onEvent((event) => {
268
+ if (event.type !== "terminal.output") return
269
+ outputByTerminalId.set(event.terminalId, `${outputByTerminalId.get(event.terminalId) ?? ""}${event.data}`)
270
+ })
271
+
272
+ const getOutput = (terminalId: string) => outputByTerminalId.get(terminalId) ?? ""
273
+
274
+ const createManagedSession = async (terminalId: string) => {
275
+ manager.createTerminal({
276
+ workspacePath: tempProjectPath,
277
+ terminalId,
278
+ cols: 80,
279
+ rows: 24,
280
+ scrollback: 1_000,
281
+ })
282
+ manager.write(terminalId, "printf '__KANNA_READY__\\n'\r")
283
+ await waitForOutputToContain(() => getOutput(terminalId), "__KANNA_READY__", SHELL_START_TIMEOUT_MS)
284
+ }
285
+
286
+ try {
287
+ await createManagedSession(firstTerminalId)
288
+ const firstBeforeLength = getOutput(firstTerminalId).length
289
+ manager.write(firstTerminalId, "printf '\\033[?1004h'\r")
290
+ await waitFor(() => getOutput(firstTerminalId).length > firstBeforeLength, COMMAND_TIMEOUT_MS)
291
+ manager.close(firstTerminalId)
292
+
293
+ await createManagedSession(secondTerminalId)
294
+ const before = getOutput(secondTerminalId).length
295
+ manager.write(secondTerminalId, "cat -v\r")
296
+ await waitFor(() => getOutput(secondTerminalId).length > before, COMMAND_TIMEOUT_MS)
297
+ manager.write(secondTerminalId, FOCUS_IN_SEQUENCE)
298
+ manager.write(secondTerminalId, "\x03")
299
+ manager.write(secondTerminalId, "printf '__KANNA_FRESH_SESSION__\\n'\r")
300
+ await waitForOutputToContain(() => getOutput(secondTerminalId), "__KANNA_FRESH_SESSION__")
301
+
302
+ const interactionOutput = getOutput(secondTerminalId).slice(before)
303
+ expect(interactionOutput).not.toContain("^[[I")
304
+ } finally {
305
+ manager.close(firstTerminalId)
306
+ manager.close(secondTerminalId)
307
+ }
308
+ })
309
+ })