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,494 @@
1
+ import type { NatsConnection } from "@nats-io/transport-node"
2
+ import type { ClientCommand } from "../shared/protocol"
3
+ import type { ProviderProfileRecord } from "../shared/profile-types"
4
+ import { resolveProfile } from "../shared/profile-types"
5
+ import type { AgentProvider, SessionStatus, PendingToolSnapshot } from "../shared/types"
6
+ import { resolveClaudeApiModelId } from "../shared/types"
7
+ import { runnerCmdSubject, SUPPORTED_RANGE, type RunnerCapabilities, type StartTurnCommand } from "../shared/runner-protocol"
8
+ import type { EventStore } from "./event-store"
9
+ import type { RuntimeRegistry } from "./runtime-registry"
10
+ import type { RunnerRouter } from "./runner-router"
11
+ import {
12
+ deriveServerProviderCatalog,
13
+ getServerProviderCatalog,
14
+ normalizeClaudeModelOptions,
15
+ normalizeServerModel,
16
+ } from "./provider-catalog"
17
+
18
+ // ── RunnerPickRequired ────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Thrown by `resolveRunnerForChat` when selection returns `needs_pick` with
22
+ * non-empty candidates. The WS layer converts this to a `chat.runnerPickRequired`
23
+ * event so the client can render a picker.
24
+ */
25
+ export class RunnerPickRequired extends Error {
26
+ readonly chatId: string
27
+ readonly candidates: import("./runner-router").RunnerDescriptor[]
28
+ readonly reason: "ambiguous" | "sticky_offline"
29
+
30
+ constructor(args: {
31
+ chatId: string
32
+ candidates: import("./runner-router").RunnerDescriptor[]
33
+ reason: "ambiguous" | "sticky_offline"
34
+ }) {
35
+ super(`Runner pick required for chat ${args.chatId}: ${args.reason}`)
36
+ this.name = "RunnerPickRequired"
37
+ this.chatId = args.chatId
38
+ this.candidates = args.candidates
39
+ this.reason = args.reason
40
+ }
41
+ }
42
+
43
+ const encoder = new TextEncoder()
44
+ const decoder = new TextDecoder()
45
+
46
+ type ChatSendCommand = Extract<ClientCommand, { type: "chat.send" }>
47
+ type ChatQueueCommand = Extract<ClientCommand, { type: "chat.queue" }>
48
+
49
+ export interface RunnerProxyOptions {
50
+ nc: NatsConnection
51
+ store: EventStore
52
+ runnerId: string
53
+ getActiveStatuses: () => Map<string, SessionStatus>
54
+ getPendingTool?: (chatId: string) => PendingToolSnapshot | null
55
+ runtimeRegistry?: RuntimeRegistry | null
56
+ /** Optional: called before start_turn dispatch to enforce the protocol-version + capability gate.
57
+ * When `router` is provided the signature becomes `(runnerId: string) => {...}` so each runner
58
+ * can be checked individually. A zero-arg `() => ({...})` (as existing tests pass) is still
59
+ * assignable in TS — the parameter is simply ignored in the legacy path. */
60
+ getRunnerReadiness?: (runnerId: string) => { incompatible: boolean; protocolVersion: number | null; capabilities?: RunnerCapabilities | null }
61
+ /** PR5: optional router for per-session runner selection. When absent the proxy
62
+ * behaves exactly as before — always dispatching to `this.runnerId`. */
63
+ router?: RunnerRouter
64
+ /** PR5: returns the current shared/fallback runner id. Defaults to `() => this.runnerId`. */
65
+ sharedRunnerId?: () => string
66
+ }
67
+
68
+ export class RunnerProxy {
69
+ private readonly nc: NatsConnection
70
+ private readonly store: EventStore
71
+ private readonly runnerId: string
72
+ private readonly _getActiveStatuses: () => Map<string, SessionStatus>
73
+ private readonly runtimeRegistry: RuntimeRegistry | null
74
+ private readonly _getRunnerReadiness: ((runnerId: string) => { incompatible: boolean; protocolVersion: number | null; capabilities?: RunnerCapabilities | null }) | null
75
+ private readonly recentlyStartedChats = new Set<string>()
76
+ private readonly router: RunnerRouter | null
77
+ private readonly _sharedRunnerId: () => string
78
+
79
+ /** Orchestration compatibility: check if a chat has an active turn */
80
+ readonly activeTurns: { has(chatId: string): boolean }
81
+
82
+ constructor(options: RunnerProxyOptions) {
83
+ this.nc = options.nc
84
+ this.store = options.store
85
+ this.runnerId = options.runnerId
86
+ this._getActiveStatuses = options.getActiveStatuses
87
+ this.runtimeRegistry = options.runtimeRegistry ?? null
88
+ this._getRunnerReadiness = options.getRunnerReadiness ?? null
89
+ this.router = options.router ?? null
90
+ this._sharedRunnerId = options.sharedRunnerId ?? (() => this.runnerId)
91
+ this.activeTurns = {
92
+ has: (chatId: string) => this.hasActiveOrJustStartedTurn(chatId),
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Resolve profile overrides for a workspace+provider into non-secret extraEnv.
98
+ * Binary resolution is done runner-side; the server no longer sets binaryPath.
99
+ */
100
+ private resolveProfileOverrides(workspaceId: string, provider: AgentProvider): { extraEnv?: Record<string, string> } {
101
+ // Find all profiles for this provider
102
+ const profiles = [...this.store.state.providerProfiles.values()]
103
+ .filter((r: ProviderProfileRecord) => r.profile.provider === provider)
104
+
105
+ if (profiles.length === 0) return {}
106
+
107
+ // Use the first matching profile (TODO: workspace-level default selection)
108
+ const record = profiles[0]
109
+ const wsOverrides = this.store.state.workspaceProfileOverrides.get(workspaceId)
110
+ const override = wsOverrides?.get(record.id)
111
+ const resolved = resolveProfile(record.profile, override?.overrides)
112
+
113
+ // Runtime secret-boundary guard: extraEnv transits the server → the runner,
114
+ // so it must carry NO secrets. Drop (and warn on) any secret-shaped key/value
115
+ // before it leaves the server — secrets are resolved runner-side only.
116
+ const SECRET_PATTERN = /API_KEY|TOKEN|SECRET|Bearer|sk-/i
117
+ const safe: Record<string, string> = {}
118
+ for (const [k, v] of Object.entries(resolved.env ?? {})) {
119
+ if (SECRET_PATTERN.test(k) || SECRET_PATTERN.test(v)) {
120
+ console.warn(`[RunnerProxy] dropping secret-shaped env "${k}" from extraEnv — secrets must stay runner-side`)
121
+ continue
122
+ }
123
+ safe[k] = v
124
+ }
125
+ return { extraEnv: Object.keys(safe).length > 0 ? safe : undefined }
126
+ }
127
+
128
+ getActiveStatuses(): Map<string, SessionStatus> {
129
+ return this._getActiveStatuses()
130
+ }
131
+
132
+ private hasObservedActiveTurn(chatId: string): boolean {
133
+ return this._getActiveStatuses().has(chatId)
134
+ }
135
+
136
+ private hasActiveOrJustStartedTurn(chatId: string): boolean {
137
+ return this.hasObservedActiveTurn(chatId) || this.recentlyStartedChats.has(chatId)
138
+ }
139
+
140
+ private async sendCommand(cmd: string, payload: unknown, runnerId: string): Promise<unknown> {
141
+ // Gate: incompatible runners must not receive start_turn — fail fast with a clear message.
142
+ // Fail CLOSED: if no readiness source is wired we cannot prove compatibility, so refuse
143
+ // rather than silently dispatching to a possibly-incompatible runner.
144
+ if (cmd === "start_turn") {
145
+ if (!this._getRunnerReadiness) {
146
+ throw new Error(
147
+ `RunnerProxy ${runnerId}: getRunnerReadiness not provided — refusing start_turn (cannot enforce the compatibility gate)`,
148
+ )
149
+ }
150
+ const { incompatible, protocolVersion, capabilities } = this._getRunnerReadiness(runnerId)
151
+ if (incompatible) {
152
+ throw new Error(
153
+ `Runner ${runnerId} is incompatible (protocol v${protocolVersion ?? "unknown"}, server supports v${SUPPORTED_RANGE.min}–${SUPPORTED_RANGE.max}) — run tinkaria-runner upgrade`,
154
+ )
155
+ }
156
+ // Capability gate: if the runner advertised capabilities, verify the
157
+ // requested provider is installed. If capabilities is null/undefined (not
158
+ // yet probed — e.g. pre-PR4 runner), skip and allow (fail open for
159
+ // backward compat with runners that haven't registered capabilities yet).
160
+ if (capabilities) {
161
+ const turn = payload as { provider?: AgentProvider }
162
+ const requestedProvider = turn.provider
163
+ if (requestedProvider && !capabilities.providers.includes(requestedProvider)) {
164
+ const installed = capabilities.providers.join(", ") || "none"
165
+ console.warn(
166
+ `[RunnerProxy] capability gate: runner ${runnerId} cannot run provider="${requestedProvider}" (installed: ${installed})`,
167
+ )
168
+ throw new Error(
169
+ `Runner ${runnerId} cannot run ${requestedProvider} (installed: ${installed}) — install it on the runner or pick another`,
170
+ )
171
+ }
172
+ }
173
+ }
174
+
175
+ // A claude-pty first turn runs a one-time PTY spawn + security smoke-probe
176
+ // that can legitimately take up to ~65s on a cold cache. The runner only
177
+ // replies once startTurn (and thus the spawn) resolves, so the default 10s
178
+ // window expires while the runner is still spawning — the request then
179
+ // fails with a generic NATS "timeout" even though the turn proceeds and
180
+ // streams via events. Give claude-pty start_turn a window that covers the
181
+ // worst-case probe so the real outcome (success or the actual refusal
182
+ // reason) propagates. Other providers/commands keep fast failure detection.
183
+ const isPtyStartTurn = cmd === "start_turn"
184
+ && (payload as { provider?: string } | null)?.provider === "claude-pty"
185
+ const timeoutMs = isPtyStartTurn ? 90_000 : 10_000
186
+ const subject = runnerCmdSubject(runnerId, cmd)
187
+ // Observability (decision 0012): the dispatch path was previously silent, so a
188
+ // start_turn that timed out left no server-side trace of which runner it went
189
+ // to. Log the dispatch and its outcome (timeout vs not-ok reply).
190
+ const provider = (payload as { provider?: string } | null)?.provider
191
+ console.warn(`[RunnerProxy] dispatch cmd=${cmd} runnerId=${runnerId} provider=${provider ?? "-"} subject=${subject} timeoutMs=${timeoutMs}`)
192
+ let reply
193
+ try {
194
+ reply = await this.nc.request(
195
+ subject,
196
+ encoder.encode(JSON.stringify(payload)),
197
+ { timeout: timeoutMs },
198
+ )
199
+ } catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error)
201
+ console.warn(`[RunnerProxy] cmd=${cmd} runnerId=${runnerId} request FAILED (no reply within ${timeoutMs}ms): ${message}`)
202
+ throw error
203
+ }
204
+ const response = JSON.parse(decoder.decode(reply.data))
205
+ if (!response.ok) {
206
+ console.warn(`[RunnerProxy] cmd=${cmd} runnerId=${runnerId} runner replied not-ok: ${response.error ?? "(no error)"}`)
207
+ throw new Error(response.error ?? "Runner command failed")
208
+ }
209
+ return response.result
210
+ }
211
+
212
+ /**
213
+ * PR5: Resolve which runner should handle the next turn for this chat.
214
+ *
215
+ * - No router → legacy single-runner path: return `{ runnerId: this.runnerId, shouldPersist: false }`.
216
+ * - Router present → consult it with the chat's sticky pin (if any):
217
+ * - `selected` → return `{ runnerId, shouldPersist: runnerId !== chat.runnerId }`.
218
+ * - `needs_pick` with no candidates → fail-fast (no eligible runners at all).
219
+ * - `needs_pick` with candidates → throw RunnerPickRequired for the picker.
220
+ * - `unavailable` → fail-fast Error.
221
+ *
222
+ * NOTE: does NOT persist the pin — callers must call `store.setChatRunner` after
223
+ * a successful dispatch when `shouldPersist` is true.
224
+ */
225
+ private async resolveRunnerForChat(chatId: string, provider: AgentProvider): Promise<{ runnerId: string; shouldPersist: boolean }> {
226
+ if (!this.router) {
227
+ return { runnerId: this.runnerId, shouldPersist: false }
228
+ }
229
+
230
+ const chat = this.store.requireChat(chatId)
231
+ const preferred = chat.runnerId ?? null
232
+ const sel = await this.router.select({ provider, preferredRunnerId: preferred })
233
+
234
+ if (sel.kind === "selected") {
235
+ const shouldPersist = sel.runnerId !== (chat.runnerId ?? null)
236
+ return { runnerId: sel.runnerId, shouldPersist }
237
+ }
238
+
239
+ if (sel.kind === "needs_pick") {
240
+ if (sel.candidates.length === 0) {
241
+ throw new Error(
242
+ `No eligible runners for provider "${provider}" — start or pair a runner`,
243
+ )
244
+ }
245
+ throw new RunnerPickRequired({ chatId, candidates: sel.candidates, reason: sel.reason })
246
+ }
247
+
248
+ // unavailable
249
+ throw new Error(sel.reason)
250
+ }
251
+
252
+ /**
253
+ * For non-start commands (cancel, respond_tool, stop_chat_pty) the runner
254
+ * MUST be the already-pinned one — never re-route mid-session. If the chat
255
+ * has no pin yet (e.g. being disposed before any turn) fall back to the
256
+ * shared/default runner.
257
+ */
258
+ private pinnedRunnerForChat(chatId: string): string {
259
+ const chat = this.store.requireChat(chatId)
260
+ return chat.runnerId ?? this._sharedRunnerId()
261
+ }
262
+
263
+ /** Send a chat message — creates chat if needed, delegates turn to runner */
264
+ async send(command: ChatSendCommand): Promise<{ chatId: string }> {
265
+ let chatId = command.chatId
266
+ if (!chatId) {
267
+ if (!command.workspaceId) throw new Error("Missing workspaceId for new chat")
268
+ const created = await this.store.createChat(command.workspaceId)
269
+ chatId = created.id
270
+ }
271
+
272
+ const chat = this.store.requireChat(chatId)
273
+ const provider = chat.provider ?? command.provider ?? "claude"
274
+
275
+ const dynamicCatalog = this.runtimeRegistry
276
+ ? deriveServerProviderCatalog(this.runtimeRegistry.getProviderCapabilities("claude"))
277
+ : undefined
278
+ const catalog = getServerProviderCatalog(provider)
279
+ let model: string
280
+ let planMode: boolean
281
+
282
+ if (provider === "claude") {
283
+ model = normalizeServerModel(provider, command.model, dynamicCatalog)
284
+ const modelOptions = normalizeClaudeModelOptions(model, command.modelOptions, command.effort)
285
+ model = resolveClaudeApiModelId(model, modelOptions.contextWindow)
286
+ planMode = catalog.supportsPlanMode ? Boolean(command.planMode) : false
287
+ } else {
288
+ model = normalizeServerModel(provider, command.model, dynamicCatalog)
289
+ planMode = catalog.supportsPlanMode ? Boolean(command.planMode) : false
290
+ }
291
+
292
+ const project = this.store.getProject(chat.workspaceId)
293
+ if (!project) throw new Error("Project not found")
294
+
295
+ const existingMessages = await this.store.getMessages(chatId)
296
+ const profileOverrides = this.resolveProfileOverrides(chat.workspaceId, provider)
297
+
298
+ const startCmd: StartTurnCommand = {
299
+ chatId,
300
+ provider,
301
+ content: command.content,
302
+ model,
303
+ planMode,
304
+ appendUserPrompt: true,
305
+ workspaceLocalPath: project.localPath,
306
+ sessionToken: chat.sessionToken,
307
+ chatTitle: chat.title,
308
+ existingMessageCount: existingMessages.length,
309
+ workspaceId: chat.workspaceId,
310
+ ...profileOverrides,
311
+ }
312
+
313
+ if (chat.provider !== provider) {
314
+ if (chat.sessionToken) {
315
+ await this.store.setSessionToken(chatId, null)
316
+ }
317
+ await this.store.setChatProvider(chatId, provider)
318
+ }
319
+ await this.store.setChatModel(chatId, model)
320
+ await this.store.setPlanMode(chatId, planMode)
321
+
322
+ const { runnerId, shouldPersist } = await this.resolveRunnerForChat(chatId, provider)
323
+ await this.sendCommand("start_turn", startCmd, runnerId)
324
+ if (shouldPersist) await this.store.setChatRunner(chatId, runnerId)
325
+ this.recentlyStartedChats.add(chatId)
326
+ return { chatId }
327
+ }
328
+
329
+ async queue(command: ChatQueueCommand): Promise<{ chatId: string; queued: boolean }> {
330
+ if (!this.hasActiveOrJustStartedTurn(command.chatId)) {
331
+ await this.send({
332
+ ...command,
333
+ type: "chat.send",
334
+ })
335
+ return { chatId: command.chatId, queued: false }
336
+ }
337
+
338
+ await this.store.enqueueQueuedTurn({
339
+ chatId: command.chatId,
340
+ provider: command.provider,
341
+ content: command.content,
342
+ model: command.model,
343
+ modelOptions: command.modelOptions,
344
+ effort: command.effort,
345
+ planMode: command.planMode,
346
+ })
347
+ return { chatId: command.chatId, queued: true }
348
+ }
349
+
350
+ async drainQueuedTurn(chatId: string): Promise<boolean> {
351
+ this.recentlyStartedChats.delete(chatId)
352
+ if (this.hasObservedActiveTurn(chatId)) return false
353
+
354
+ const queued = this.store.getQueuedTurn(chatId)
355
+ if (!queued) return false
356
+
357
+ await this.store.clearQueuedTurn(chatId)
358
+ try {
359
+ await this.send({
360
+ type: "chat.send",
361
+ chatId,
362
+ provider: queued.provider,
363
+ content: queued.content,
364
+ model: queued.model,
365
+ modelOptions: queued.modelOptions,
366
+ effort: queued.effort,
367
+ planMode: queued.planMode,
368
+ })
369
+ return true
370
+ } catch (error) {
371
+ await this.store.enqueueQueuedTurn(queued)
372
+ throw error
373
+ }
374
+ }
375
+
376
+ /** Resume parent chat after a delegated child agent completes */
377
+ async drainDelegationResult(chatId: string, delegationId: string): Promise<boolean> {
378
+ if (this.hasObservedActiveTurn(chatId)) return false
379
+
380
+ const queued = this.store.getQueuedTurn(chatId)
381
+ if (queued) return false
382
+
383
+ const chat = this.store.requireChat(chatId)
384
+ const project = this.store.getProject(chat.workspaceId)
385
+ if (!project) throw new Error("Project not found")
386
+
387
+ const provider = chat.provider ?? "claude"
388
+ const profileOverrides = this.resolveProfileOverrides(chat.workspaceId, provider)
389
+ const existingMessages = await this.store.getMessages(chatId)
390
+
391
+ const startCmd: StartTurnCommand = {
392
+ chatId,
393
+ provider,
394
+ content: `[Delegation result ready] The delegated agent has completed. Review the agent_result entry above and continue.`,
395
+ model: chat.model ?? "sonnet",
396
+ planMode: chat.planMode ?? false,
397
+ appendUserPrompt: false,
398
+ workspaceLocalPath: project.localPath,
399
+ sessionToken: chat.sessionToken,
400
+ chatTitle: chat.title,
401
+ existingMessageCount: existingMessages.length,
402
+ workspaceId: chat.workspaceId,
403
+ ...profileOverrides,
404
+ }
405
+
406
+ const { runnerId, shouldPersist } = await this.resolveRunnerForChat(chatId, provider)
407
+ await this.sendCommand("start_turn", startCmd, runnerId)
408
+ if (shouldPersist) await this.store.setChatRunner(chatId, runnerId)
409
+ this.recentlyStartedChats.add(chatId)
410
+ return true
411
+ }
412
+
413
+ /** Start a turn for a specific chat — used by orchestration */
414
+ async startTurnForChat(args: {
415
+ chatId: string
416
+ provider: AgentProvider
417
+ content: string
418
+ delegatedContext?: string
419
+ isSpawned?: boolean
420
+ model: string
421
+ effort?: string
422
+ serviceTier?: "fast"
423
+ planMode: boolean
424
+ appendUserPrompt: boolean
425
+ }): Promise<void> {
426
+ const chat = this.store.requireChat(args.chatId)
427
+ const project = this.store.getProject(chat.workspaceId)
428
+ if (!project) throw new Error("Project not found")
429
+
430
+ if (chat.provider !== args.provider) {
431
+ if (chat.sessionToken) {
432
+ await this.store.setSessionToken(args.chatId, null)
433
+ }
434
+ await this.store.setChatProvider(args.chatId, args.provider)
435
+ }
436
+ await this.store.setChatModel(args.chatId, args.model)
437
+ await this.store.setPlanMode(args.chatId, args.planMode)
438
+
439
+ const profileOverrides = this.resolveProfileOverrides(chat.workspaceId, args.provider)
440
+ const startCmd: StartTurnCommand = {
441
+ chatId: args.chatId,
442
+ provider: args.provider,
443
+ content: args.content,
444
+ delegatedContext: args.delegatedContext,
445
+ isSpawned: args.isSpawned,
446
+ model: args.model,
447
+ planMode: args.planMode,
448
+ appendUserPrompt: args.appendUserPrompt,
449
+ workspaceLocalPath: project.localPath,
450
+ sessionToken: chat.sessionToken,
451
+ chatTitle: chat.title,
452
+ existingMessageCount: (await this.store.getMessages(args.chatId)).length,
453
+ workspaceId: chat.workspaceId,
454
+ ...profileOverrides,
455
+ }
456
+ const { runnerId, shouldPersist } = await this.resolveRunnerForChat(args.chatId, args.provider)
457
+ await this.sendCommand("start_turn", startCmd, runnerId)
458
+ if (shouldPersist) await this.store.setChatRunner(args.chatId, runnerId)
459
+ this.recentlyStartedChats.add(args.chatId)
460
+ }
461
+
462
+ async cancel(chatId: string): Promise<void> {
463
+ const runnerId = this.pinnedRunnerForChat(chatId)
464
+ await this.sendCommand("cancel_turn", { chatId }, runnerId)
465
+ }
466
+
467
+ async respondTool(command: Extract<ClientCommand, { type: "chat.respondTool" }>): Promise<void> {
468
+ const runnerId = this.pinnedRunnerForChat(command.chatId)
469
+ await this.sendCommand("respond_tool", {
470
+ chatId: command.chatId,
471
+ toolUseId: command.toolUseId,
472
+ result: command.result,
473
+ }, runnerId)
474
+ }
475
+
476
+ async disposeChat(chatId: string): Promise<void> {
477
+ const runnerId = this.pinnedRunnerForChat(chatId)
478
+ try {
479
+ await this.sendCommand("cancel_turn", { chatId }, runnerId)
480
+ } catch (_error) {
481
+ // Chat might not be running — swallow
482
+ }
483
+ // Tear down any long-lived claude-pty session for this chat. Cancel
484
+ // above only sends ^C to the active turn (preserving the session for
485
+ // a follow-up prompt). A chat being deleted must release the claude
486
+ // CLI child + MCP HTTP server + file watcher + memory sampler + OAuth
487
+ // pool reservation. Mirrors kanna's `closeChat` discipline.
488
+ try {
489
+ await this.sendCommand("stop_chat_pty", { chatId }, runnerId)
490
+ } catch (_error) {
491
+ // Runner may have already cleared the session — swallow
492
+ }
493
+ }
494
+ }