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,807 @@
1
+ import { homedir } from "node:os"
2
+ import path from "node:path"
3
+ import { randomUUID } from "node:crypto"
4
+ import { createRuntimeDir, writeRuntimeFile, removeRuntimeDir } from "./runtime-dir.adapter"
5
+ import { verifyPtyAuth } from "./auth"
6
+ import { startKannaMcpHttpServer, buildMcpConfigJson, type KannaMcpHttpHandle } from "../claude-pty-mcp/mcp-http"
7
+ import type { KannaMcpDelegationContext } from "../claude-pty-mcp/mcp"
8
+ import type { SubagentOrchestrator } from "./subagent-orchestrator"
9
+ import { parseConfiguredContextWindowFromModelId, timestamped } from "./agent-normalizers"
10
+ import { KANNA_SYSTEM_PROMPT_APPEND } from "../../shared/tinkaria-system-prompt"
11
+ import { resolveClaudeBinary } from "./resolve-binary.adapter"
12
+ import { createJsonlEventParser } from "./jsonl-to-event"
13
+ import { OutputRing, OUTPUT_RING_DEFAULT_BYTES } from "./output-ring"
14
+ import { createSmokeTestGate, createFileSmokeTestCache, buildLiveSmokeProbe, type SmokeTestGate } from "./smoke-test"
15
+ import { computeBinarySha256 } from "./preflight/binary-fingerprint.adapter"
16
+ import { spawnPtyProcess as defaultSpawnPtyProcess, type PtyProcess, type SpawnPtyProcessArgs } from "./pty-process.adapter"
17
+ import type { ClaudePtyRegistry } from "./pid-registry.adapter"
18
+ import type { PtyInstanceRegistry } from "./pty-instance-registry"
19
+ import { sampleProcessTreeUsage as defaultSampleProcessTreeUsage, type ProcessTreeSample } from "./pty-memory-sampler.adapter"
20
+ import { waitForTuiReady, waitForTuiReadyWithTrustDismiss, sendUserPrompt, sendExitCommand } from "./tui-control"
21
+ import { startTranscriptStream } from "./tui-source.adapter"
22
+ import { computeJsonlPath, computeProjectDir } from "./jsonl-path.adapter"
23
+ import type { ClaudeSessionHandle } from "./agent-normalizers"
24
+ import type { HarnessEvent, HarnessToolRequest } from "../harness-types"
25
+ import type { AccountInfo, McpServerConfig, SlashCommand } from "../../shared/types"
26
+ import type { ToolCallbackService } from "./tool-callback"
27
+ import type { TunnelGateway } from "./tunnel-gateway"
28
+ import type { ChatPermissionPolicy } from "../../shared/permission-policy"
29
+
30
+ // Fallback list returned by getSupportedCommands() if claude's system_init
31
+ // JSONL message hasn't been observed yet (cold start before first spawn).
32
+ // Names follow claude's own format — no leading "/" — so the chat input
33
+ // renders `/clear` (not `//clear`) after `applyCommandToInput` prefixes the
34
+ // slash. The driver overwrites this with the full live list as soon as
35
+ // the spawned claude subprocess emits its system_init entry.
36
+ const STATIC_SUPPORTED_COMMANDS: SlashCommand[] = [
37
+ { name: "model", description: "Switch model", argumentHint: "model name" },
38
+ { name: "exit", description: "Exit the session", argumentHint: "" },
39
+ { name: "clear", description: "Clear context", argumentHint: "" },
40
+ { name: "help", description: "List commands", argumentHint: "" },
41
+ ]
42
+
43
+ export interface StartClaudeSessionPtyArgs {
44
+ chatId: string
45
+ projectId: string
46
+ localPath: string
47
+ model: string
48
+ effort?: string
49
+ planMode: boolean
50
+ forkSession: boolean
51
+ oauthToken: string | null
52
+ sessionToken: string | null
53
+ additionalDirectories?: string[]
54
+ onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
55
+ /**
56
+ * Append text for `--append-system-prompt`. Defaults to the static
57
+ * {@link KANNA_SYSTEM_PROMPT_APPEND} blurb for back-compat with older
58
+ * callers; production callers in `agent.ts` pass the dynamic value
59
+ * from `buildKannaSystemPromptAppend` so the subagent roster is
60
+ * embedded.
61
+ */
62
+ systemPromptAppend?: string
63
+ systemPromptOverride?: string
64
+ initialPrompt?: string
65
+ homeDir?: string
66
+ env?: NodeJS.ProcessEnv
67
+ /** Routes AskUserQuestion/ExitPlanMode + built-in shims through durable approval when KANNA_MCP_TOOL_CALLBACKS=1. */
68
+ toolCallback?: ToolCallbackService
69
+ /** Tunnel gateway for kanna-mcp expose_port. */
70
+ tunnelGateway?: TunnelGateway | null
71
+ /** Per-chat permission policy for kanna-mcp built-in shims. */
72
+ chatPolicy?: ChatPermissionPolicy
73
+ /** Orchestrator for delegate_subagent. Omit to hide the tool from the model. */
74
+ subagentOrchestrator?: SubagentOrchestrator
75
+ /** Per-spawn delegation context (depth / ancestor chain / parentUserMessageId resolver). */
76
+ delegationContext?: KannaMcpDelegationContext
77
+ /** Enabled user-defined MCP servers, written into mcp-config.json. */
78
+ customMcpServers?: readonly McpServerConfig[]
79
+ /** Optional override used by tests to inject a fake HTTP MCP starter. */
80
+ startKannaMcpHttpServer?: typeof startKannaMcpHttpServer
81
+ /** Optional smoke-test gate override (used by tests to inject a fake gate). */
82
+ smokeTestGate?: SmokeTestGate
83
+ /** Optional PTY spawn override (used by tests to inject a fake PTY). */
84
+ spawnPtyProcess?: (args: SpawnPtyProcessArgs) => Promise<PtyProcess>
85
+ /** Optional transcript stream factory override (used by tests). */
86
+ startTranscriptStreamFn?: typeof startTranscriptStream
87
+ /**
88
+ * One-shot semantics: after the first `result` entry, close stdin so
89
+ * the subprocess exits. Mirrors the SDK driver's prompt-queue close
90
+ * for single-turn subagent runs.
91
+ */
92
+ oneShot?: boolean
93
+ /** Label of the OAuth-pool token. Surfaces in AccountInfo since the CLI doesn't emit account info in stream-json. */
94
+ oauthLabel?: string
95
+ /** Masked OAuth-pool token (e.g. `sk-ant-oat01...XXXX`). Computed by AgentCoordinator; never the raw token. */
96
+ oauthKeyMasked?: string
97
+ /**
98
+ * Optional on-disk registry of claude PTY children so a non-graceful
99
+ * server crash can reap orphan processes on the next boot. When set,
100
+ * the driver registers the spawn's pid + runtimeDir before sending the
101
+ * first prompt and unregisters during cleanup.
102
+ */
103
+ ptyRegistry?: ClaudePtyRegistry
104
+ /**
105
+ * Optional in-memory live-status registry surfaced to the client UI.
106
+ * Driver upserts phase transitions; ws-router fans deltas out to
107
+ * subscribed sockets.
108
+ */
109
+ ptyInstanceRegistry?: PtyInstanceRegistry
110
+ /** Optional sampler override (tests inject deterministic values). */
111
+ sampleProcessTreeUsage?: (pid: number) => Promise<ProcessTreeSample | null>
112
+ /** Optional poll-interval override (ms). Defaults to 2000. */
113
+ memorySamplerIntervalMs?: number
114
+ /**
115
+ * Optional usage-sample sink. Invoked on every memory-sampler tick with the
116
+ * process-tree resident memory and CPU% plus their session peaks. Lets the
117
+ * runner path (which publishes its own deltas instead of wiring a
118
+ * PtyInstanceRegistry) surface cpu/mem to the UI.
119
+ */
120
+ onUsageSample?: (usage: PtyUsageSample) => void
121
+ }
122
+
123
+ export interface PtyUsageSample {
124
+ rssBytes: number
125
+ rssPeakBytes: number
126
+ cpuPercent: number
127
+ cpuPeakPercent: number
128
+ }
129
+
130
+ /**
131
+ * Derive an AccountInfo from the picked OAuth-pool token. The claude CLI
132
+ * never emits account info in stream-json, so the user-configured token
133
+ * label and the coordinator-computed masked key are the only account
134
+ * signals PTY has.
135
+ */
136
+ export function deriveAccountInfoFromOauth(args: { label?: string; oauthKeyMasked?: string }): AccountInfo | null {
137
+ const hasLabel = Boolean(args.label && args.label.length > 0)
138
+ const hasMasked = Boolean(args.oauthKeyMasked && args.oauthKeyMasked.length > 0)
139
+ if (!hasLabel && !hasMasked) return null
140
+ const info: AccountInfo = { tokenSource: "kanna-oauth-pool" }
141
+ if (hasLabel) info.organization = args.label
142
+ if (hasMasked) info.oauthKeyMasked = args.oauthKeyMasked
143
+ return info
144
+ }
145
+
146
+ /** VT100 Shift+Tab sequence sent to exit plan mode (one press cycles back to acceptEdits). */
147
+ export const SHIFT_TAB_KEY = "\x1b[Z"
148
+
149
+ export const PLAN_MODE_EXIT_UNSUPPORTED =
150
+ "[claude-pty] cannot exit plan mode: driver-tracked plan mode is inactive "
151
+ + "(plan mode may have been toggled externally via Shift+Tab). "
152
+ + "Restart the session to return to acceptEdits."
153
+
154
+ /** Backward-compat re-exports — callers that import from driver.ts continue to work. */
155
+ export const PTY_STDERR_RING_BYTES = OUTPUT_RING_DEFAULT_BYTES
156
+ export { OutputRing }
157
+
158
+ /**
159
+ * Native CLI built-ins removed from the model's context under PTY (issue
160
+ * #215). The SDK driver intercepts these via the `canUseTool` hook
161
+ * (`buildCanUseTool` in agent.ts); PTY has no such hook, so the CLI
162
+ * auto-rejects them with `is_error: "Answer questions?"` and the model
163
+ * mis-reads it as a user cancel. Disallowing the natives forces the model
164
+ * onto the `mcp__kanna__ask_user_question` / `mcp__kanna__exit_plan_mode`
165
+ * shims, which the PTY driver always registers (forceInteractiveToolCallbacks)
166
+ * and which route through the durable approval protocol to the UI.
167
+ * `EnterPlanMode` is intentionally excluded — it has no user round-trip and
168
+ * the SDK hook never intercepts it, so leaving it native preserves parity.
169
+ */
170
+ export const PTY_DISALLOWED_NATIVE_TOOLS = ["AskUserQuestion", "ExitPlanMode"] as const
171
+
172
+ export interface BuildPtyCliArgsInput {
173
+ sessionId: string
174
+ model: string
175
+ effort?: string
176
+ planMode: boolean
177
+ sessionToken: string | null
178
+ forkSession: boolean
179
+ additionalDirectories?: string[]
180
+ systemPromptOverride?: string
181
+ systemPromptAppend?: string
182
+ /** Absolute path to kanna's own mcp-config JSON. Merged with user's MCP configs (no --strict-mcp-config). */
183
+ mcpConfigPath?: string
184
+ }
185
+
186
+ /**
187
+ * Build claude CLI args for TUI driver mode.
188
+ *
189
+ * Kanna spawns the claude CLI under a real PTY and watches the on-disk
190
+ * transcript JSONL file as the event source. The CLI runs interactively
191
+ * with `--dangerously-skip-permissions` so tool calls are auto-approved.
192
+ *
193
+ * • No `--print` / `--output-format` / `--input-format` / `--verbose` —
194
+ * TUI mode does NOT use the stream-json headless transport.
195
+ * • No `--session-id` for new sessions — TUI claude generates its own UUID
196
+ * on first prompt; kanna identifies the session via the transcript file.
197
+ * • `--strict-mcp-config` — CLI ignores user MCP config; kanna provides
198
+ * its own via `--mcp-config` so the MCP surface is fully controlled.
199
+ * • `--setting-sources user,project,local` — user's installed skills,
200
+ * slash commands, plugins, agents, and project / local settings layers
201
+ * all load normally.
202
+ * • `--dangerously-skip-permissions` — auto-run tools because the CLI's
203
+ * own interactive permission prompt is not routed through kanna's UI.
204
+ */
205
+ export function buildPtyCliArgs(args: BuildPtyCliArgsInput): string[] {
206
+ const cliArgs: string[] = [
207
+ "--model", args.model,
208
+ "--setting-sources", "user,project,local",
209
+ "--permission-mode", args.planMode ? "plan" : "acceptEdits",
210
+ "--dangerously-skip-permissions",
211
+ ]
212
+ // TUI mode session handling:
213
+ // • New session (no sessionToken) → no --session-id (TUI ignores it; claude generates its own UUID)
214
+ // • Resume existing session (sessionToken set) → --resume <token>
215
+ // • Fork existing session (sessionToken + fork) → --session-id <newUuid> --resume <token> --fork-session
216
+ //
217
+ // Interactive TUI claude ignores `--session-id` for new sessions and
218
+ // always generates its own UUID. Watcher uses an mtime filter on the
219
+ // project dir instead — only JSONLs created at or after spawn start are
220
+ // candidates, so stale JSONLs from prior sessions cannot win the race.
221
+ if (args.sessionToken && !args.forkSession) {
222
+ cliArgs.push("--resume", args.sessionToken)
223
+ } else if (args.sessionToken && args.forkSession) {
224
+ cliArgs.push("--session-id", args.sessionId, "--resume", args.sessionToken, "--fork-session")
225
+ }
226
+ if (args.mcpConfigPath) {
227
+ cliArgs.push("--mcp-config", args.mcpConfigPath, "--strict-mcp-config")
228
+ }
229
+ if (args.effort && args.effort.length > 0) cliArgs.push("--effort", args.effort)
230
+ if (args.additionalDirectories) {
231
+ for (const dir of args.additionalDirectories) cliArgs.push("--add-dir", dir)
232
+ }
233
+ if (args.systemPromptOverride) {
234
+ cliArgs.push("--system-prompt", args.systemPromptOverride)
235
+ } else {
236
+ cliArgs.push("--append-system-prompt", args.systemPromptAppend ?? KANNA_SYSTEM_PROMPT_APPEND)
237
+ }
238
+ // `--disallowedTools` is variadic in the claude CLI (space-separated tool
239
+ // strings as separate argv — code.claude.com/docs/en/cli-reference). Push
240
+ // it LAST so it cannot greedily swallow a subsequent flag value.
241
+ cliArgs.push("--disallowedTools", ...PTY_DISALLOWED_NATIVE_TOOLS)
242
+ return cliArgs
243
+ }
244
+
245
+ export function buildPtyEnv(args: {
246
+ baseEnv: NodeJS.ProcessEnv
247
+ homeDir: string
248
+ oauthToken: string | null
249
+ }): NodeJS.ProcessEnv {
250
+ const spawnEnv: NodeJS.ProcessEnv = { ...args.baseEnv }
251
+ delete spawnEnv.ANTHROPIC_API_KEY
252
+ spawnEnv.HOME = args.homeDir
253
+ spawnEnv.DISABLE_AUTOUPDATER = "1"
254
+ if (args.oauthToken && args.oauthToken.length > 0) {
255
+ spawnEnv.CLAUDE_CODE_OAUTH_TOKEN = args.oauthToken
256
+ }
257
+ return spawnEnv
258
+ }
259
+
260
+ export async function startClaudeSessionPTY(args: StartClaudeSessionPtyArgs): Promise<ClaudeSessionHandle> {
261
+ const home = args.homeDir ?? homedir()
262
+ const env = args.env ?? process.env
263
+
264
+ console.log("[kanna/pty] startClaudeSessionPTY begin", {
265
+ chatId: args.chatId,
266
+ projectId: args.projectId,
267
+ localPath: args.localPath,
268
+ model: args.model,
269
+ planMode: args.planMode,
270
+ forkSession: args.forkSession,
271
+ hasOauthToken: Boolean(args.oauthToken),
272
+ oauthLabel: args.oauthLabel ?? null,
273
+ sandboxEnvOverride: env.KANNA_PTY_SANDBOX ?? null,
274
+ platform: process.platform,
275
+ anthropicApiKeySet: Boolean(env.ANTHROPIC_API_KEY),
276
+ claudeExecutable: env.CLAUDE_EXECUTABLE ?? null,
277
+ })
278
+
279
+ const spawnStartedAt = Date.now()
280
+ args.ptyInstanceRegistry?.upsert(args.chatId, {
281
+ cwd: args.localPath,
282
+ model: args.model,
283
+ accountLabel: args.oauthLabel ?? null,
284
+ oauthMasked: args.oauthKeyMasked ?? null,
285
+ phase: "spawning",
286
+ startedAt: spawnStartedAt,
287
+ lastEventAt: spawnStartedAt,
288
+ planMode: args.planMode,
289
+ })
290
+
291
+ const auth = await verifyPtyAuth({ env, oauthToken: args.oauthToken })
292
+ if (!auth.ok) {
293
+ console.error("[kanna/pty] verifyPtyAuth failed", {
294
+ chatId: args.chatId,
295
+ error: auth.error,
296
+ hasOauthToken: Boolean(args.oauthToken),
297
+ anthropicApiKeySet: Boolean(env.ANTHROPIC_API_KEY),
298
+ })
299
+ throw new Error(auth.error)
300
+ }
301
+
302
+ const resolved = await resolveClaudeBinary({ env, homeDir: home })
303
+ console.log("[kanna/pty] resolved claude binary", {
304
+ chatId: args.chatId,
305
+ path: resolved.path,
306
+ source: resolved.source,
307
+ })
308
+ const claudeBinAbs = resolved.path
309
+
310
+ const binarySha256 = await computeBinarySha256(claudeBinAbs)
311
+ const smokeGate = args.smokeTestGate ?? createSmokeTestGate({
312
+ probe: buildLiveSmokeProbe({
313
+ claudeBinPath: claudeBinAbs,
314
+ model: args.model,
315
+ oauthToken: args.oauthToken ?? "",
316
+ homeDir: home,
317
+ }),
318
+ cache: createFileSmokeTestCache({ cacheDir: path.join(home, ".tinkaria", "cache", "smoke-test") }),
319
+ ttlMs: 24 * 3600 * 1000,
320
+ now: () => Date.now(),
321
+ })
322
+ const smoke = await smokeGate.canSpawn({ binarySha256, model: args.model })
323
+ if (!smoke.ok) {
324
+ console.error("[kanna/pty] smoke-test refused spawn", { chatId: args.chatId, reason: smoke.reason })
325
+ throw new Error(`PTY smoke-test refused spawn: ${smoke.reason}`)
326
+ }
327
+
328
+ const spawnEnv = buildPtyEnv({
329
+ baseEnv: env,
330
+ homeDir: home,
331
+ oauthToken: args.oauthToken,
332
+ })
333
+
334
+ const sessionId = args.sessionToken ?? randomUUID()
335
+
336
+ const runtimeDir = await createRuntimeDir(`kanna-pty-${sessionId.slice(0, 8)}-`)
337
+
338
+ const mcpConfigPath = path.join(runtimeDir, "mcp-config.json")
339
+ let mcpHandle: KannaMcpHttpHandle
340
+ const startMcp = args.startKannaMcpHttpServer ?? startKannaMcpHttpServer
341
+ try {
342
+ mcpHandle = await startMcp({
343
+ args: {
344
+ projectId: args.projectId,
345
+ localPath: args.localPath,
346
+ chatId: args.chatId,
347
+ sessionId,
348
+ tunnelGateway: args.tunnelGateway ?? null,
349
+ toolCallback: args.toolCallback,
350
+ chatPolicy: args.chatPolicy,
351
+ subagentOrchestrator: args.subagentOrchestrator,
352
+ delegationContext: args.delegationContext,
353
+ // PTY has no canUseTool hook — the durable approval protocol is the
354
+ // only host path for AskUserQuestion/ExitPlanMode. Force the shims
355
+ // on regardless of KANNA_MCP_TOOL_CALLBACKS (issue #215). Paired
356
+ // with --disallowedTools AskUserQuestion ExitPlanMode above so the
357
+ // model uses the shim instead of the auto-rejected native built-in.
358
+ forceInteractiveToolCallbacks: true,
359
+ },
360
+ })
361
+ await writeRuntimeFile(
362
+ mcpConfigPath,
363
+ buildMcpConfigJson(mcpHandle, args.customMcpServers ?? []),
364
+ { encoding: "utf8", mode: 0o600 },
365
+ )
366
+ } catch (err) {
367
+ try { await (mcpHandle! as KannaMcpHttpHandle | undefined)?.close() } catch { /* swallow */ }
368
+ try { await removeRuntimeDir(runtimeDir) } catch { /* swallow */ }
369
+ throw err
370
+ }
371
+
372
+ const claudeBin = claudeBinAbs
373
+ const cliArgs = buildPtyCliArgs({
374
+ sessionId,
375
+ model: args.model,
376
+ effort: args.effort,
377
+ planMode: args.planMode,
378
+ sessionToken: args.sessionToken,
379
+ forkSession: args.forkSession,
380
+ additionalDirectories: args.additionalDirectories,
381
+ systemPromptOverride: args.systemPromptOverride,
382
+ systemPromptAppend: args.systemPromptAppend,
383
+ mcpConfigPath,
384
+ })
385
+
386
+ let closed = false
387
+ let cleanedUp = false
388
+ let cachedAccountInfo: AccountInfo | null = deriveAccountInfoFromOauth({ label: args.oauthLabel, oauthKeyMasked: args.oauthKeyMasked })
389
+ let sawResultEntry = false
390
+ let cachedSlashCommands: SlashCommand[] | null = null
391
+ let localPlanModeActive = args.planMode
392
+ const mergedQueue: HarnessEvent[] = []
393
+ const mergedWaiters: Array<(r: IteratorResult<HarnessEvent>) => void> = []
394
+
395
+ async function cleanupResources() {
396
+ if (cleanedUp) return
397
+ cleanedUp = true
398
+ stopMemorySampler()
399
+ args.ptyInstanceRegistry?.upsert(args.chatId, {
400
+ phase: "exited",
401
+ exitedAt: Date.now(),
402
+ lastEventAt: Date.now(),
403
+ })
404
+ if (args.toolCallback) {
405
+ try { await args.toolCallback.cancelAllForSession(sessionId, "session_closed") } catch (err) {
406
+ console.warn("[kanna/pty] toolCallback.cancelAllForSession failed", { chatId: args.chatId, sessionId, err })
407
+ }
408
+ }
409
+ try { await mcpHandle.close() } catch (err) {
410
+ // Logged because a swallowed mcpHandle close error means the loopback
411
+ // HTTP server may still be listening — a real resource leak.
412
+ console.warn("[kanna/pty] mcpHandle.close failed (HTTP server may leak)", { chatId: args.chatId, sessionId, err })
413
+ }
414
+ try { await removeRuntimeDir(runtimeDir) } catch (err) {
415
+ console.warn("[kanna/pty] runtimeDir cleanup failed", { chatId: args.chatId, runtimeDir, err })
416
+ }
417
+ if (args.ptyRegistry) {
418
+ try { await args.ptyRegistry.unregister(sessionId) } catch (err) {
419
+ // A stale entry on disk only matters across server restarts — log
420
+ // for observability but do not fail cleanup.
421
+ console.warn("[kanna/pty] ptyRegistry.unregister failed", { chatId: args.chatId, sessionId, err })
422
+ }
423
+ }
424
+ }
425
+
426
+ function pushMerged(ev: HarnessEvent) {
427
+ if (ev.type === "transcript" && ev.entry) {
428
+ const entry = ev.entry as { kind?: string; accountInfo?: unknown; slashCommands?: unknown }
429
+ if (entry.kind === "account_info" && entry.accountInfo !== undefined) {
430
+ cachedAccountInfo = entry.accountInfo as AccountInfo
431
+ }
432
+ if (entry.kind === "result") {
433
+ sawResultEntry = true
434
+ }
435
+ // system_init carries the full slash-command list the spawned claude
436
+ // CLI knows about — including every skill, plugin command, project
437
+ // command, and built-in. Cache it so getSupportedCommands() returns
438
+ // the live set instead of the cold-start fallback.
439
+ if (entry.kind === "system_init" && Array.isArray(entry.slashCommands)) {
440
+ cachedSlashCommands = (entry.slashCommands as string[]).map((name) => ({
441
+ name,
442
+ description: "",
443
+ argumentHint: "",
444
+ }))
445
+ }
446
+ }
447
+ const w = mergedWaiters.shift()
448
+ if (w) w({ value: ev, done: false })
449
+ else mergedQueue.push(ev)
450
+
451
+ if (
452
+ args.oneShot
453
+ && ev.type === "transcript"
454
+ && (ev.entry as { kind?: string } | undefined)?.kind === "result"
455
+ ) {
456
+ void oneShotClose()
457
+ }
458
+ }
459
+
460
+ let oneShotClosing = false
461
+ // pty is declared before use; assigned in the spawn try-block below.
462
+ let pty: PtyProcess
463
+
464
+ let memorySamplerHandle: ReturnType<typeof setInterval> | null = null
465
+ let rssPeakBytes = 0
466
+ let cpuPeakPercent = 0
467
+
468
+ function stopMemorySampler(): void {
469
+ if (memorySamplerHandle !== null) {
470
+ clearInterval(memorySamplerHandle)
471
+ memorySamplerHandle = null
472
+ }
473
+ }
474
+
475
+ function startMemorySampler(rootPid: number): void {
476
+ if (memorySamplerHandle !== null) return
477
+ const sampler = args.sampleProcessTreeUsage ?? defaultSampleProcessTreeUsage
478
+ const intervalMs = args.memorySamplerIntervalMs ?? 2000
479
+ const tick = async (): Promise<void> => {
480
+ let sample: ProcessTreeSample | null
481
+ try {
482
+ sample = await sampler(rootPid)
483
+ } catch {
484
+ sample = null
485
+ }
486
+ if (sample === null) return
487
+ if (sample.rssBytes > rssPeakBytes) rssPeakBytes = sample.rssBytes
488
+ if (sample.cpuPercent > cpuPeakPercent) cpuPeakPercent = sample.cpuPercent
489
+ args.ptyInstanceRegistry?.upsert(args.chatId, {
490
+ rssBytes: sample.rssBytes,
491
+ rssPeakBytes,
492
+ cpuPercent: sample.cpuPercent,
493
+ cpuPeakPercent,
494
+ })
495
+ args.onUsageSample?.({
496
+ rssBytes: sample.rssBytes,
497
+ rssPeakBytes,
498
+ cpuPercent: sample.cpuPercent,
499
+ cpuPeakPercent,
500
+ })
501
+ }
502
+ memorySamplerHandle = setInterval(() => { void tick() }, intervalMs)
503
+ void tick()
504
+ }
505
+
506
+ const ring = new OutputRing()
507
+ const spawnPty = args.spawnPtyProcess ?? defaultSpawnPtyProcess
508
+ try {
509
+ console.log("[kanna/pty] spawn begin", {
510
+ chatId: args.chatId,
511
+ command: claudeBin,
512
+ cwd: args.localPath,
513
+ })
514
+ pty = await spawnPty({
515
+ command: claudeBin,
516
+ args: cliArgs,
517
+ cwd: args.localPath,
518
+ env: spawnEnv,
519
+ onOutput: (chunk) => { ring.append(chunk) },
520
+ })
521
+ console.log("[kanna/pty] pty spawned", { chatId: args.chatId, sessionId, pid: pty.pid })
522
+ args.ptyInstanceRegistry?.upsert(args.chatId, {
523
+ sessionId,
524
+ pid: pty.pid,
525
+ phase: "trust-dialog",
526
+ lastEventAt: Date.now(),
527
+ })
528
+ startMemorySampler(pty.pid)
529
+ // Record the live PTY in the on-disk registry so a non-graceful
530
+ // server crash can reap this orphan on the next boot. Persistence is
531
+ // best-effort — failure to write must not block the spawn.
532
+ if (args.ptyRegistry) {
533
+ try {
534
+ await args.ptyRegistry.register({
535
+ chatId: args.chatId,
536
+ sessionId,
537
+ pid: pty.pid,
538
+ cwd: args.localPath,
539
+ runtimeDir,
540
+ })
541
+ } catch (err) {
542
+ console.warn("[kanna/pty] ptyRegistry.register failed (orphan reap on crash disabled for this session)", { chatId: args.chatId, sessionId, err })
543
+ }
544
+ }
545
+ } catch (err) {
546
+ console.error("[kanna/pty] spawn failed", {
547
+ chatId: args.chatId,
548
+ sessionId,
549
+ error: err instanceof Error ? err.message : String(err),
550
+ })
551
+ try { await mcpHandle.close() } catch { /* swallow */ }
552
+ try { await removeRuntimeDir(runtimeDir) } catch { /* swallow */ }
553
+ throw err
554
+ }
555
+
556
+ // Wait for TUI to render its input box, dismissing the trust dialog if
557
+ // present. The combined helper handles the ANSI-encoded trust dialog text
558
+ // and keeps polling until the real "❯ " input box appears after dismiss.
559
+ const tuiReadyMs = Number((args.env ?? process.env).KANNA_PTY_TUI_BOOT_MS ?? 3000)
560
+ const tuiReadyQuietRaw = (args.env ?? process.env).KANNA_PTY_TUI_READY_QUIET_MS
561
+ const tuiReadyQuietMs = tuiReadyQuietRaw !== undefined ? Number(tuiReadyQuietRaw) : undefined
562
+ const trustDismiss = (args.env ?? process.env).KANNA_PTY_TRUST_DISMISS ?? "enabled"
563
+ if (trustDismiss !== "disabled") {
564
+ // +5 s over the base cap to absorb trust-dialog dismiss + project reload.
565
+ const readyResult = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: tuiReadyMs + 5_000, quietPeriodMs: tuiReadyQuietMs })
566
+ if (readyResult === "timeout") {
567
+ console.warn("[kanna/pty] TUI ready marker not detected after trust dismiss", { chatId: args.chatId, hardCapMs: tuiReadyMs + 5_000 })
568
+ } else {
569
+ console.log("[kanna/pty] TUI ready", { chatId: args.chatId })
570
+ }
571
+ } else {
572
+ const readyResult = await waitForTuiReady(ring, { hardCapMs: tuiReadyMs, quietPeriodMs: tuiReadyQuietMs })
573
+ if (readyResult === "timeout") {
574
+ console.warn("[kanna/pty] TUI ready marker not detected within hard cap", { chatId: args.chatId, hardCapMs: tuiReadyMs })
575
+ }
576
+ }
577
+
578
+ args.ptyInstanceRegistry?.upsert(args.chatId, {
579
+ phase: "ready",
580
+ lastEventAt: Date.now(),
581
+ })
582
+
583
+ // Open transcript-file event stream.
584
+ const projectDir = computeProjectDir({ homeDir: home, cwd: args.localPath })
585
+ // knownFilePath: only known up-front when resuming (we know the
586
+ // sessionToken). For new sessions interactive TUI claude generates its
587
+ // own UUID and ignores `--session-id`, so the path is unknown — fall
588
+ // back to discovery via `findLatestTranscript` with an mtime floor at
589
+ // spawn-start time to filter out stale JSONLs from prior sessions in
590
+ // the same project dir.
591
+ const knownFilePath = args.sessionToken && !args.forkSession
592
+ ? computeJsonlPath({ homeDir: home, cwd: args.localPath, sessionId: args.sessionToken })
593
+ : undefined
594
+ const spawnStartedAtMs = Date.now()
595
+ const startStream = args.startTranscriptStreamFn ?? startTranscriptStream
596
+ const transcriptStream = await startStream({
597
+ projectDir,
598
+ knownFilePath,
599
+ minMtimeMs: spawnStartedAtMs,
600
+ pollMode: (args.env ?? process.env).KANNA_PTY_TRANSCRIPT_WATCH === "poll",
601
+ // Race-free discovery via claude's per-PID session registry at
602
+ // `${home}/.claude/sessions/<pid>.json`. Falls back to the mtime
603
+ // heuristic if the registry file does not appear in time (older
604
+ // claude versions, broken HOME, etc).
605
+ claudeChildPid: pty.pid,
606
+ homeDir: home,
607
+ })
608
+
609
+ const parser = createJsonlEventParser({
610
+ configuredContextWindow: parseConfiguredContextWindowFromModelId(args.model),
611
+ })
612
+
613
+ // Pipe transcript JSONL lines through the parser into the merged event queue.
614
+ void (async () => {
615
+ try {
616
+ for await (const line of transcriptStream.lines) {
617
+ try {
618
+ const events = parser.parse(line)
619
+ for (const ev of events) pushMerged(ev)
620
+ } catch (err) {
621
+ console.warn("[kanna/pty] parser threw on line", { chatId: args.chatId, sessionId, err })
622
+ }
623
+ }
624
+ console.log("[kanna/pty] transcript stream ended", { chatId: args.chatId, sessionId })
625
+ } catch (err) {
626
+ console.warn("[kanna/pty] transcript stream errored", { chatId: args.chatId, sessionId, err })
627
+ }
628
+ })()
629
+
630
+ function drainTerminate(exitCode: number | null) {
631
+ console.log("[kanna/pty] drainTerminate", {
632
+ chatId: args.chatId,
633
+ sessionId,
634
+ exitCode,
635
+ closed,
636
+ oneShotClosing,
637
+ sawResultEntry,
638
+ oneShot: Boolean(args.oneShot),
639
+ waitersAwaitingEvent: mergedWaiters.length,
640
+ })
641
+ if (closed || oneShotClosing) {
642
+ while (mergedWaiters.length > 0) {
643
+ const w = mergedWaiters.shift()
644
+ if (w) w({ value: undefined as unknown as HarnessEvent, done: true })
645
+ }
646
+ return
647
+ }
648
+ if (!sawResultEntry) {
649
+ const tail = ring.tail().trim()
650
+ const codeNote = exitCode === null ? "signal" : `exit code ${exitCode}`
651
+ const resultText = tail.length > 0
652
+ ? tail
653
+ : `claude PTY process exited (${codeNote}) before producing a result.`
654
+ console.warn("[kanna/pty] synthesizing error-result for early PTY exit (no turn_duration / result row seen)", {
655
+ chatId: args.chatId,
656
+ sessionId,
657
+ exitCode,
658
+ ringTailBytes: tail.length,
659
+ })
660
+ pushMerged({
661
+ type: "transcript",
662
+ entry: timestamped({
663
+ kind: "result",
664
+ subtype: "error",
665
+ isError: true,
666
+ durationMs: 0,
667
+ result: resultText,
668
+ debugRaw: JSON.stringify({ source: "pty-exit", exitCode }),
669
+ }),
670
+ })
671
+ }
672
+ void cleanupResources()
673
+ while (mergedWaiters.length > 0) {
674
+ const w = mergedWaiters.shift()
675
+ if (w) w({ value: undefined as unknown as HarnessEvent, done: true })
676
+ }
677
+ }
678
+
679
+ void pty.exited
680
+ .then((code) => {
681
+ console.log("[kanna/pty] pty.exited resolved", { chatId: args.chatId, sessionId, pid: pty.pid, code })
682
+ drainTerminate(typeof code === "number" ? code : null)
683
+ })
684
+ .catch((err) => {
685
+ console.warn("[kanna/pty] pty.exited rejected", { chatId: args.chatId, sessionId, pid: pty.pid, err })
686
+ drainTerminate(null)
687
+ })
688
+
689
+ async function oneShotClose() {
690
+ if (oneShotClosing || closed) return
691
+ oneShotClosing = true
692
+ console.log("[kanna/pty] oneShotClose start", { chatId: args.chatId, sessionId, sawResultEntry })
693
+ try { await sendExitCommand(pty) } catch (err) {
694
+ console.warn("[kanna/pty] oneShotClose sendExitCommand failed", { chatId: args.chatId, sessionId, err })
695
+ }
696
+ try { await pty.exited } catch { /* swallow */ }
697
+ try { transcriptStream.close() } catch { /* swallow */ }
698
+ await cleanupResources()
699
+ console.log("[kanna/pty] oneShotClose finished", { chatId: args.chatId, sessionId })
700
+ }
701
+
702
+ if (args.initialPrompt) {
703
+ try {
704
+ await sendUserPrompt(pty, ring, args.initialPrompt)
705
+ } catch (err) {
706
+ console.warn("[kanna/pty] initialPrompt write failed", err)
707
+ }
708
+ }
709
+
710
+ const stream: AsyncIterable<HarnessEvent> = {
711
+ [Symbol.asyncIterator]() {
712
+ return {
713
+ next(): Promise<IteratorResult<HarnessEvent>> {
714
+ if (mergedQueue.length > 0) {
715
+ const ev = mergedQueue.shift()
716
+ if (ev) return Promise.resolve({ value: ev, done: false })
717
+ }
718
+ if (closed) {
719
+ return Promise.resolve({ value: undefined as unknown as HarnessEvent, done: true })
720
+ }
721
+ return new Promise((resolve) => {
722
+ mergedWaiters.push(resolve)
723
+ })
724
+ },
725
+ }
726
+ },
727
+ }
728
+
729
+ return {
730
+ provider: "claude",
731
+ stream,
732
+ interrupt: async () => {
733
+ try { await pty.sendInput("\x03") } catch { /* swallow */ }
734
+ },
735
+ sendPrompt: async (content) => {
736
+ const text = typeof content === "string"
737
+ ? content
738
+ : Array.isArray(content)
739
+ ? (content as Array<{ type?: string; text?: string }>)
740
+ .filter((c) => c.type === "text")
741
+ .map((c) => c.text ?? "")
742
+ .join("\n")
743
+ : String(content)
744
+ await sendUserPrompt(pty, ring, text)
745
+ },
746
+ setModel: async (model) => {
747
+ try {
748
+ await pty.sendInput(`/model ${model}\r`)
749
+ } catch (err) {
750
+ console.warn("[kanna/pty] setModel via /model slash command failed", err)
751
+ }
752
+ },
753
+ setPermissionMode: async (planMode) => {
754
+ if (planMode) {
755
+ try {
756
+ await pty.sendInput("/plan\r")
757
+ localPlanModeActive = true
758
+ } catch (err) {
759
+ console.warn("[kanna/pty] /plan slash command failed", err)
760
+ }
761
+ return
762
+ }
763
+ if (localPlanModeActive) {
764
+ try {
765
+ await pty.sendInput(SHIFT_TAB_KEY)
766
+ localPlanModeActive = false
767
+ } catch (err) {
768
+ console.warn("[kanna/pty] Shift+Tab exit-plan failed", err)
769
+ }
770
+ return
771
+ }
772
+ console.warn(PLAN_MODE_EXIT_UNSUPPORTED)
773
+ },
774
+ getSupportedCommands: async () => cachedSlashCommands ?? STATIC_SUPPORTED_COMMANDS,
775
+ getAccountInfo: async () => cachedAccountInfo,
776
+ close: () => {
777
+ if (closed) return
778
+ closed = true
779
+ void (async () => {
780
+ // 3-stage shutdown escalation:
781
+ // 1. /exit (graceful REPL exit) — 2 s grace
782
+ // 2. SIGTERM (terminal.close + proc.kill) — 3 s grace
783
+ // 3. SIGKILL (force kill, unblocks hung TUI)
784
+ // Each timer is cleared if pty.exited resolves before the deadline.
785
+ try { await sendExitCommand(pty) } catch { /* swallow */ }
786
+ const sigkillTimer = { ref: null as ReturnType<typeof setTimeout> | null }
787
+ const termTimer = setTimeout(() => {
788
+ try { pty.close() } catch { /* swallow */ }
789
+ sigkillTimer.ref = setTimeout(() => {
790
+ try { pty.kill("SIGKILL") } catch { /* swallow */ }
791
+ }, 3000)
792
+ }, 2000)
793
+ try {
794
+ await pty.exited
795
+ } catch { /* swallow */ }
796
+ clearTimeout(termTimer)
797
+ if (sigkillTimer.ref !== null) clearTimeout(sigkillTimer.ref)
798
+ try { transcriptStream.close() } catch { /* swallow */ }
799
+ await cleanupResources()
800
+ while (mergedWaiters.length > 0) {
801
+ const w = mergedWaiters.shift()
802
+ if (w) w({ value: undefined as unknown as HarnessEvent, done: true })
803
+ }
804
+ })()
805
+ },
806
+ }
807
+ }