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,453 @@
1
+ import { jetstream, type JetStream } from "@nats-io/jetstream"
2
+ import type { NatsConnection } from "@nats-io/transport-node"
3
+ import { existsSync } from "node:fs"
4
+ import { LOG_PREFIX } from "../shared/branding"
5
+ import type { HarnessToolRequest, HarnessTurn } from "../shared/harness-types"
6
+ import {
7
+ runnerEventsSubject,
8
+ type RunnerTurnEvent,
9
+ type StartTurnCommand,
10
+ } from "../shared/runner-protocol"
11
+ import type {
12
+ AgentProvider,
13
+ NormalizedToolCall,
14
+ PendingToolSnapshot,
15
+ SessionStatus,
16
+ TranscriptEntry,
17
+ } from "../shared/types"
18
+ import { timestamped, discardedToolResult } from "../shared/transcript-entries"
19
+ import type { CoordinationStore } from "../shared/coordination-store"
20
+
21
+ const encoder = new TextEncoder()
22
+ const CANCEL_INTERRUPT_TIMEOUT_MS = 2_000
23
+
24
+ // ── Types ───────────────────────────────────────────────────────────
25
+
26
+ export type TurnFactory = (args: {
27
+ provider: AgentProvider
28
+ content: string
29
+ localPath: string
30
+ model: string
31
+ effort?: string
32
+ serviceTier?: "fast"
33
+ planMode: boolean
34
+ sessionToken: string | null
35
+ onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
36
+ chatId: string
37
+ store?: CoordinationStore
38
+ extraEnv?: Record<string, string>
39
+ }) => Promise<HarnessTurn>
40
+
41
+ function buildHarnessInput(cmd: StartTurnCommand): string {
42
+ if (!cmd.delegatedContext?.trim()) {
43
+ return cmd.content
44
+ }
45
+ return `${cmd.delegatedContext}\n\nNew task:\n${cmd.content}`
46
+ }
47
+
48
+ interface PendingToolRequest {
49
+ toolUseId: string
50
+ tool: NormalizedToolCall & { toolKind: "ask_user_question" | "exit_plan_mode" }
51
+ resolve: (result: unknown) => void
52
+ }
53
+
54
+ interface ActiveTurn {
55
+ chatId: string
56
+ provider: AgentProvider
57
+ turn: HarnessTurn
58
+ model: string
59
+ effort?: string
60
+ serviceTier?: "fast"
61
+ planMode: boolean
62
+ status: SessionStatus
63
+ pendingTool: PendingToolRequest | null
64
+ postToolFollowUp: { content: string; planMode: boolean } | null
65
+ hasFinalResult: boolean
66
+ cancelRequested: boolean
67
+ cancelRecorded: boolean
68
+ /** Preserved from the original StartTurnCommand for follow-up turns */
69
+ originalCmd: StartTurnCommand
70
+ }
71
+
72
+ export interface RunnerAgentOptions {
73
+ nc: NatsConnection
74
+ createTurn: TurnFactory
75
+ generateTitle?: (content: string, cwd: string) => Promise<string | null>
76
+ coordinationStore?: CoordinationStore
77
+ /**
78
+ * Hook the runner calls when a chat is being torn down (cancel without
79
+ * active turn, chat.delete). Implementation closes any live claude-pty
80
+ * session for the chat. Wired from runner.ts to turn-factories.ts.
81
+ */
82
+ stopClaudePtySession?: (chatId: string) => void
83
+ }
84
+
85
+ // ── RunnerAgent ─────────────────────────────────────────────────────
86
+
87
+ export class RunnerAgent {
88
+ private readonly js: JetStream
89
+ private readonly nc: NatsConnection
90
+ private readonly createTurn: TurnFactory
91
+ private readonly generateTitle: ((content: string, cwd: string) => Promise<string | null>) | undefined
92
+ private readonly coordinationStore: CoordinationStore | undefined
93
+ private readonly stopClaudePtySessionFn: ((chatId: string) => void) | undefined
94
+ readonly activeTurns = new Map<string, ActiveTurn>()
95
+
96
+ constructor(options: RunnerAgentOptions) {
97
+ this.nc = options.nc
98
+ this.js = jetstream(options.nc)
99
+ this.createTurn = options.createTurn
100
+ this.generateTitle = options.generateTitle
101
+ this.coordinationStore = options.coordinationStore
102
+ this.stopClaudePtySessionFn = options.stopClaudePtySession
103
+ }
104
+
105
+ // ── Publishing ──────────────────────────────────────────────────
106
+
107
+ private publishEvent(chatId: string, event: RunnerTurnEvent): void {
108
+ const subject = runnerEventsSubject(chatId)
109
+ void this.js.publish(subject, encoder.encode(JSON.stringify(event))).catch((error) => {
110
+ const message = error instanceof Error ? error.message : String(error)
111
+ console.warn(LOG_PREFIX, `JetStream publish failed on ${subject}: ${message}`)
112
+ })
113
+ }
114
+
115
+ private publishTranscript(chatId: string, entry: TranscriptEntry): void {
116
+ this.publishEvent(chatId, { type: "transcript", chatId, entry })
117
+ }
118
+
119
+ // ── Public API ────────────────────────────────────────────────────
120
+
121
+ getActiveStatuses(): Map<string, SessionStatus> {
122
+ const statuses = new Map<string, SessionStatus>()
123
+ for (const [chatId, turn] of this.activeTurns.entries()) {
124
+ statuses.set(chatId, turn.status)
125
+ }
126
+ return statuses
127
+ }
128
+
129
+ getPendingTool(chatId: string): PendingToolSnapshot | null {
130
+ const pending = this.activeTurns.get(chatId)?.pendingTool
131
+ if (!pending) return null
132
+ return { toolUseId: pending.toolUseId, toolKind: pending.tool.toolKind }
133
+ }
134
+
135
+ async startTurn(cmd: StartTurnCommand): Promise<void> {
136
+ if (this.activeTurns.has(cmd.chatId)) {
137
+ throw new Error("Chat is already running")
138
+ }
139
+
140
+ // Fail fast (instead of a silent 10s timeout) when the chat's workspace does
141
+ // not exist on THIS runner — e.g. a remote runner handed a server-only path.
142
+ // Launching the agent with a nonexistent cwd hangs the spawn, so the start_turn
143
+ // request never gets a reply. Surface a clear, actionable error instead.
144
+ if (cmd.workspaceLocalPath && !existsSync(cmd.workspaceLocalPath)) {
145
+ throw new Error(
146
+ `Workspace "${cmd.workspaceLocalPath}" does not exist on this runner machine. ` +
147
+ `The workspace must exist on the runner to run a turn here — pick a runner that ` +
148
+ `has this path, or make the path available on the runner.`,
149
+ )
150
+ }
151
+
152
+ const shouldGenerateTitle =
153
+ cmd.appendUserPrompt &&
154
+ cmd.chatTitle === "New Chat" &&
155
+ cmd.existingMessageCount === 0
156
+
157
+ // Publish user prompt
158
+ if (cmd.appendUserPrompt) {
159
+ this.publishTranscript(
160
+ cmd.chatId,
161
+ timestamped({ kind: "user_prompt", content: cmd.content })
162
+ )
163
+ }
164
+
165
+ // Create tool request handler
166
+ const onToolRequest = async (request: HarnessToolRequest): Promise<unknown> => {
167
+ const active = this.activeTurns.get(cmd.chatId)
168
+ if (!active) throw new Error("Chat turn ended unexpectedly")
169
+
170
+ active.status = "waiting_for_user"
171
+ this.publishEvent(cmd.chatId, {
172
+ type: "status_change",
173
+ chatId: cmd.chatId,
174
+ status: "waiting_for_user",
175
+ })
176
+ this.publishEvent(cmd.chatId, {
177
+ type: "pending_tool",
178
+ chatId: cmd.chatId,
179
+ tool: { toolUseId: request.tool.toolId, toolKind: request.tool.toolKind },
180
+ })
181
+
182
+ return new Promise<unknown>((resolve) => {
183
+ active.pendingTool = {
184
+ toolUseId: request.tool.toolId,
185
+ tool: request.tool,
186
+ resolve,
187
+ }
188
+ })
189
+ }
190
+
191
+ // Start the harness turn — binary resolution is done inside each factory
192
+ const turn = await this.createTurn({
193
+ provider: cmd.provider,
194
+ content: buildHarnessInput(cmd),
195
+ localPath: cmd.workspaceLocalPath,
196
+ model: cmd.model,
197
+ planMode: cmd.planMode,
198
+ sessionToken: cmd.sessionToken,
199
+ onToolRequest,
200
+ chatId: cmd.chatId,
201
+ store: this.coordinationStore,
202
+ extraEnv: cmd.extraEnv,
203
+ })
204
+
205
+ const active: ActiveTurn = {
206
+ chatId: cmd.chatId,
207
+ provider: cmd.provider,
208
+ turn,
209
+ model: cmd.model,
210
+ planMode: cmd.planMode,
211
+ status: "starting",
212
+ pendingTool: null,
213
+ postToolFollowUp: null,
214
+ hasFinalResult: false,
215
+ cancelRequested: false,
216
+ cancelRecorded: false,
217
+ originalCmd: cmd,
218
+ }
219
+ this.activeTurns.set(cmd.chatId, active)
220
+
221
+ this.publishEvent(cmd.chatId, {
222
+ type: "status_change",
223
+ chatId: cmd.chatId,
224
+ status: "starting",
225
+ })
226
+
227
+ // Background: title generation
228
+ if (shouldGenerateTitle) {
229
+ void this.generateTitleInBackground(cmd.chatId, cmd.content, cmd.workspaceLocalPath)
230
+ }
231
+
232
+ // Run the turn asynchronously
233
+ void this.runTurn(active)
234
+ }
235
+
236
+ async cancel(chatId: string): Promise<void> {
237
+ const active = this.activeTurns.get(chatId)
238
+ if (!active) {
239
+ // No active turn but a long-lived claude-pty session may still own a
240
+ // claude CLI child, MCP HTTP server, file watcher, sampler interval,
241
+ // and an OAuth pool reservation. Closing the session here matches
242
+ // kanna's `closeChat` discipline — cancel of an idle chat must release
243
+ // the PTY, not silently no-op.
244
+ this.stopClaudePtySessionFn?.(chatId)
245
+ return
246
+ }
247
+
248
+ active.cancelRequested = true
249
+
250
+ const pendingTool = active.pendingTool
251
+ active.pendingTool = null
252
+
253
+ if (pendingTool) {
254
+ const result = discardedToolResult(pendingTool.tool)
255
+ this.publishTranscript(
256
+ chatId,
257
+ timestamped({ kind: "tool_result", toolId: pendingTool.toolUseId, content: result })
258
+ )
259
+ if (active.provider === "codex" && pendingTool.tool.toolKind === "exit_plan_mode") {
260
+ pendingTool.resolve(result)
261
+ }
262
+ }
263
+
264
+ this.publishTranscript(chatId, timestamped({ kind: "interrupted" }))
265
+ this.publishEvent(chatId, { type: "turn_cancelled", chatId })
266
+ active.cancelRecorded = true
267
+ active.hasFinalResult = true
268
+ this.activeTurns.delete(chatId)
269
+
270
+ void this.interruptTurnAfterCancel(active).catch(() => {})
271
+ }
272
+
273
+ /**
274
+ * Tear down any live claude-pty session for the chat unconditionally.
275
+ * Called from RunnerProxy.disposeChat after a separate `cancel_turn`
276
+ * has interrupted any in-flight turn. Always safe — no-op when the chat
277
+ * has no session.
278
+ */
279
+ stopChatPty(chatId: string): void {
280
+ this.stopClaudePtySessionFn?.(chatId)
281
+ }
282
+
283
+ async respondTool(chatId: string, toolUseId: string, result: unknown): Promise<void> {
284
+ const active = this.activeTurns.get(chatId)
285
+ if (!active?.pendingTool) throw new Error("No pending tool request")
286
+ if (active.pendingTool.toolUseId !== toolUseId) throw new Error("Tool response does not match active request")
287
+
288
+ const pending = active.pendingTool
289
+ this.publishTranscript(
290
+ chatId,
291
+ timestamped({ kind: "tool_result", toolId: toolUseId, content: result })
292
+ )
293
+
294
+ active.pendingTool = null
295
+ active.status = "running"
296
+
297
+ this.publishEvent(chatId, { type: "pending_tool", chatId, tool: null })
298
+ this.publishEvent(chatId, { type: "status_change", chatId, status: "running" })
299
+
300
+ // Handle exit_plan_mode follow-up for Codex
301
+ if (pending.tool.toolKind === "exit_plan_mode") {
302
+ const res = (result ?? {}) as { confirmed?: boolean; clearContext?: boolean; message?: string }
303
+ if (res.confirmed && res.clearContext) {
304
+ this.publishEvent(chatId, { type: "session_token", chatId, sessionToken: "" })
305
+ this.publishTranscript(chatId, timestamped({ kind: "context_cleared" }))
306
+ }
307
+ if (active.provider === "codex") {
308
+ active.postToolFollowUp = res.confirmed
309
+ ? {
310
+ content: res.message
311
+ ? `Proceed with the approved plan. Additional guidance: ${res.message}`
312
+ : "Proceed with the approved plan.",
313
+ planMode: false,
314
+ }
315
+ : {
316
+ content: res.message
317
+ ? `Revise the plan using this feedback: ${res.message}`
318
+ : "Revise the plan using this feedback.",
319
+ planMode: true,
320
+ }
321
+ }
322
+ }
323
+
324
+ pending.resolve(result)
325
+ }
326
+
327
+ // ── Private ───────────────────────────────────────────────────────
328
+
329
+ private async generateTitleInBackground(chatId: string, content: string, cwd: string): Promise<void> {
330
+ if (!this.generateTitle) return
331
+ try {
332
+ const title = await this.generateTitle(content, cwd)
333
+ if (!title) return
334
+ this.publishEvent(chatId, { type: "title_generated", chatId, title })
335
+ } catch (_error) {
336
+ // Ignore background title generation failures
337
+ }
338
+ }
339
+
340
+ private async interruptTurnAfterCancel(active: ActiveTurn): Promise<void> {
341
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
342
+ let timedOut = false
343
+ let failed = false
344
+
345
+ try {
346
+ await Promise.race([
347
+ active.turn.interrupt(),
348
+ new Promise<void>((resolve) => {
349
+ timeoutId = setTimeout(() => {
350
+ timedOut = true
351
+ resolve()
352
+ }, CANCEL_INTERRUPT_TIMEOUT_MS)
353
+ }),
354
+ ])
355
+ } catch (_error) {
356
+ failed = true
357
+ } finally {
358
+ if (timeoutId !== null) clearTimeout(timeoutId)
359
+ if (timedOut || failed) {
360
+ active.turn.close()
361
+ }
362
+ }
363
+ }
364
+
365
+ private async runTurn(active: ActiveTurn): Promise<void> {
366
+ try {
367
+ for await (const event of active.turn.stream) {
368
+ if (active.cancelRequested) {
369
+ continue
370
+ }
371
+
372
+ if (event.type === "session_token" && event.sessionToken) {
373
+ this.publishEvent(active.chatId, {
374
+ type: "session_token",
375
+ chatId: active.chatId,
376
+ sessionToken: event.sessionToken,
377
+ })
378
+ continue
379
+ }
380
+
381
+ if (!event.entry) continue
382
+
383
+ this.publishTranscript(active.chatId, event.entry)
384
+
385
+ if (event.entry.kind === "system_init") {
386
+ active.status = "running"
387
+ this.publishEvent(active.chatId, {
388
+ type: "status_change",
389
+ chatId: active.chatId,
390
+ status: "running",
391
+ })
392
+ }
393
+
394
+ if (event.entry.kind === "result") {
395
+ active.hasFinalResult = true
396
+ if (event.entry.isError) {
397
+ this.publishEvent(active.chatId, {
398
+ type: "turn_failed",
399
+ chatId: active.chatId,
400
+ error: event.entry.result || "Turn failed",
401
+ })
402
+ } else if (!active.cancelRequested) {
403
+ this.publishEvent(active.chatId, {
404
+ type: "turn_finished",
405
+ chatId: active.chatId,
406
+ })
407
+ }
408
+ }
409
+ }
410
+ } catch (error) {
411
+ if (!active.cancelRequested) {
412
+ const message = error instanceof Error ? error.message : String(error)
413
+ this.publishTranscript(
414
+ active.chatId,
415
+ timestamped({ kind: "result", subtype: "error", isError: true, durationMs: 0, result: message })
416
+ )
417
+ this.publishEvent(active.chatId, {
418
+ type: "turn_failed",
419
+ chatId: active.chatId,
420
+ error: message,
421
+ })
422
+ }
423
+ } finally {
424
+ if (active.cancelRequested && !active.cancelRecorded) {
425
+ this.publishEvent(active.chatId, { type: "turn_cancelled", chatId: active.chatId })
426
+ }
427
+ this.activeTurns.delete(active.chatId)
428
+
429
+ // Handle follow-up turn (Codex exit_plan_mode) — close() deferred so
430
+ // the follow-up can reuse the live session (avoids re-spawn cost).
431
+ if (active.postToolFollowUp && !active.cancelRequested) {
432
+ try {
433
+ await this.startTurn({
434
+ ...active.originalCmd,
435
+ content: active.postToolFollowUp.content,
436
+ planMode: active.postToolFollowUp.planMode,
437
+ appendUserPrompt: false,
438
+ })
439
+ } catch (error) {
440
+ active.turn.close()
441
+ const message = error instanceof Error ? error.message : String(error)
442
+ this.publishTranscript(
443
+ active.chatId,
444
+ timestamped({ kind: "result", subtype: "error", isError: true, durationMs: 0, result: message })
445
+ )
446
+ this.publishEvent(active.chatId, { type: "turn_failed", chatId: active.chatId, error: message })
447
+ }
448
+ } else {
449
+ active.turn.close()
450
+ }
451
+ }
452
+ }
453
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Tests for the runner credential store (PR2 Stage 2).
3
+ *
4
+ * Covers:
5
+ * - write→read round-trip; shape is preserved.
6
+ * - credential file is written with mode 0600.
7
+ * - TINKARIA_RUNNER_HOME overrides the default ~/.tinkaria directory.
8
+ * - missing file returns null (not throw).
9
+ * - second write overwrites cleanly.
10
+ */
11
+
12
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test"
13
+ import { mkdtempSync, rmSync, statSync } from "node:fs"
14
+ import { join } from "node:path"
15
+ import { tmpdir } from "node:os"
16
+ import { writeRunnerCredential, readRunnerCredential, type RunnerCredential } from "./runner-credential"
17
+
18
+ const SAMPLE: RunnerCredential = {
19
+ runnerId: "runner-1234567890-999",
20
+ token: "eytest.token.value",
21
+ natsUrl: "nats://127.0.0.1:4222",
22
+ natsWsUrl: "ws://127.0.0.1:8222",
23
+ pairedAt: 1700000000000,
24
+ }
25
+
26
+ let tmpDir: string
27
+ let savedRunnerHome: string | undefined
28
+
29
+ beforeEach(() => {
30
+ tmpDir = mkdtempSync(join(tmpdir(), "pr2-cred-test-"))
31
+ savedRunnerHome = process.env.TINKARIA_RUNNER_HOME
32
+ process.env.TINKARIA_RUNNER_HOME = tmpDir
33
+ })
34
+
35
+ afterEach(() => {
36
+ rmSync(tmpDir, { recursive: true, force: true })
37
+ if (savedRunnerHome === undefined) {
38
+ delete process.env.TINKARIA_RUNNER_HOME
39
+ } else {
40
+ process.env.TINKARIA_RUNNER_HOME = savedRunnerHome
41
+ }
42
+ })
43
+
44
+ describe("runner-credential", () => {
45
+ test("write then read round-trips all fields", async () => {
46
+ await writeRunnerCredential(SAMPLE)
47
+ const got = await readRunnerCredential()
48
+ expect(got).toEqual(SAMPLE)
49
+ })
50
+
51
+ test("credential file is written with mode 0600", async () => {
52
+ await writeRunnerCredential(SAMPLE)
53
+ // The file lives at <TINKARIA_RUNNER_HOME>/runner-secret.json
54
+ const filePath = join(tmpDir, "runner-secret.json")
55
+ const mode = statSync(filePath).mode & 0o777
56
+ expect(mode).toBe(0o600)
57
+ })
58
+
59
+ test("TINKARIA_RUNNER_HOME is honoured", async () => {
60
+ const altDir = mkdtempSync(join(tmpdir(), "pr2-cred-alt-"))
61
+ try {
62
+ process.env.TINKARIA_RUNNER_HOME = altDir
63
+ await writeRunnerCredential(SAMPLE)
64
+ // Original tmpDir should have nothing.
65
+ const inOriginal = await readRunnerCredential()
66
+ // readRunnerCredential also reads from TINKARIA_RUNNER_HOME (already set to altDir)
67
+ expect(inOriginal).toEqual(SAMPLE)
68
+ const filePath = join(altDir, "runner-secret.json")
69
+ const mode = statSync(filePath).mode & 0o777
70
+ expect(mode).toBe(0o600)
71
+ } finally {
72
+ process.env.TINKARIA_RUNNER_HOME = tmpDir
73
+ rmSync(altDir, { recursive: true, force: true })
74
+ }
75
+ })
76
+
77
+ test("missing file returns null", async () => {
78
+ const result = await readRunnerCredential()
79
+ expect(result).toBeNull()
80
+ })
81
+
82
+ test("second write overwrites the first", async () => {
83
+ await writeRunnerCredential(SAMPLE)
84
+ const updated: RunnerCredential = { ...SAMPLE, runnerId: "runner-updated-42", pairedAt: 9999999999999 }
85
+ await writeRunnerCredential(updated)
86
+ const got = await readRunnerCredential()
87
+ expect(got).toEqual(updated)
88
+ // Mode preserved after overwrite
89
+ const filePath = join(tmpDir, "runner-secret.json")
90
+ const mode = statSync(filePath).mode & 0o777
91
+ expect(mode).toBe(0o600)
92
+ })
93
+ })
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Runner credential store (PR2 Stage 2).
3
+ *
4
+ * Reads and writes the durable runner credential to
5
+ * `<TINKARIA_RUNNER_HOME>/runner-secret.json` (default: `~/.tinkaria/`).
6
+ * The file is written atomically (tmp + rename) with mode 0600.
7
+ *
8
+ * The credential is a PR1 callout token minted at pairing time:
9
+ * { runnerId, token, natsUrl, natsWsUrl, pairedAt }
10
+ *
11
+ * Env-var TINKARIA_RUNNER_HOME overrides the default directory so that
12
+ * tests and multiple local runners can use isolated locations.
13
+ */
14
+
15
+ import { join, resolve } from "node:path"
16
+ import { chmodSync, mkdirSync, renameSync } from "node:fs"
17
+ import { homedir } from "node:os"
18
+
19
+ export interface RunnerCredential {
20
+ runnerId: string
21
+ /** PR1 callout credential token (long-lived, self-verifying). Never log in full. */
22
+ token: string
23
+ natsUrl: string
24
+ natsWsUrl: string
25
+ /**
26
+ * WebSocket URL of the server's `/nats-ws` proxy (through the tunneled HTTP
27
+ * port), derived from the server URL the runner paired against. Preferred over
28
+ * the raw `natsUrl` TCP path: the raw NATS port is often not the tunneled one,
29
+ * and direct tailnet connections can fail to receive server-initiated pushes.
30
+ * Optional for backward compat — older credentials only have natsUrl (TCP).
31
+ */
32
+ natsWsProxyUrl?: string
33
+ /** Unix epoch ms when the runner was paired. */
34
+ pairedAt: number
35
+ }
36
+
37
+ const CREDENTIAL_FILE = "runner-secret.json"
38
+
39
+ /**
40
+ * Resolve the runner home directory (overridable via TINKARIA_RUNNER_HOME).
41
+ * The env var is operator-controlled config (like HOME) — it may legitimately
42
+ * point anywhere, so we don't restrict it; we normalize it to an absolute path
43
+ * for predictability. The directory is created 0700 (see writeRunnerCredential)
44
+ * so the secret file's parent isn't world-traversable.
45
+ */
46
+ function runnerHomeDir(): string {
47
+ const raw = process.env.TINKARIA_RUNNER_HOME ?? join(homedir(), ".tinkaria")
48
+ return resolve(raw)
49
+ }
50
+
51
+ function credentialPath(): string {
52
+ return join(runnerHomeDir(), CREDENTIAL_FILE)
53
+ }
54
+
55
+ /**
56
+ * Write the runner credential to disk atomically with mode 0600.
57
+ * Creates the directory if it does not exist.
58
+ */
59
+ export async function writeRunnerCredential(cred: RunnerCredential): Promise<void> {
60
+ const dir = runnerHomeDir()
61
+ mkdirSync(dir, { recursive: true, mode: 0o700 })
62
+
63
+ const dest = credentialPath()
64
+ const tmp = `${dest}.tmp.${process.pid}`
65
+
66
+ await Bun.write(tmp, JSON.stringify(cred, null, 2) + "\n")
67
+ chmodSync(tmp, 0o600)
68
+ renameSync(tmp, dest)
69
+ // chmod again after rename in case umask widened it on some platforms
70
+ chmodSync(dest, 0o600)
71
+ }
72
+
73
+ /**
74
+ * Read the runner credential from disk.
75
+ * Returns null if the file does not exist (runner not yet paired).
76
+ */
77
+ export async function readRunnerCredential(): Promise<RunnerCredential | null> {
78
+ const file = Bun.file(credentialPath())
79
+ if (!(await file.exists())) return null
80
+ const text = await file.text()
81
+ return JSON.parse(text) as RunnerCredential
82
+ }