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,274 @@
1
+ import { createHmac } from "node:crypto"
2
+ import type {
3
+ ChatPermissionPolicy,
4
+ ToolRequest,
5
+ ToolRequestDecision,
6
+ ToolRequestStatus,
7
+ } from "../../shared/permission-policy"
8
+ import { POLICY_TERMINAL_STATUSES } from "../../shared/permission-policy"
9
+ import { policy } from "../claude-pty-mcp/permission-gate"
10
+ import { canonicalArgsHash } from "../claude-pty-mcp/canonical-args"
11
+ import type { EventStore } from "../event-store"
12
+
13
+ export interface ToolCallbackServiceArgs {
14
+ store: EventStore
15
+ serverSecret: string
16
+ now: () => number
17
+ timeoutMs: number
18
+ }
19
+
20
+ export interface ToolCallbackSubmitArgs {
21
+ chatId: string
22
+ sessionId: string
23
+ toolUseId: string
24
+ toolName: string
25
+ args: Record<string, unknown>
26
+ chatPolicy: ChatPermissionPolicy
27
+ cwd: string
28
+ }
29
+
30
+ export interface ToolCallbackResult {
31
+ status: ToolRequestStatus
32
+ decision: ToolRequestDecision
33
+ mismatchReason?: string
34
+ }
35
+
36
+ export interface ToolCallbackService {
37
+ submit(args: ToolCallbackSubmitArgs): Promise<ToolCallbackResult>
38
+ answer(id: string, decision: ToolRequestDecision): Promise<void>
39
+ cancel(id: string, reason: string): Promise<void>
40
+ cancelAllForChat(chatId: string, reason: string): Promise<void>
41
+ cancelAllForSession(sessionId: string, reason: string): Promise<void>
42
+ recoverOnStartup(): Promise<void>
43
+ tickTimeouts(): Promise<void>
44
+ }
45
+
46
+ export function createToolCallbackService(opts: ToolCallbackServiceArgs): ToolCallbackService {
47
+ interface PendingWaiter {
48
+ resolve: (r: ToolCallbackResult) => void
49
+ expiresAt: number
50
+ }
51
+ const waiters = new Map<string, PendingWaiter[]>()
52
+ // Tracks the canonical (toolName, canonicalArgsHash) per (chatId, sessionId,
53
+ // toolUseId) so we detect mismatched retries. toolUseId is generated by the
54
+ // claude CLI per-session and restarts at "1" for each new spawn, so keying
55
+ // by toolUseId alone collides across chats / restarts.
56
+ const seenToolUseIds = new Map<string, { id: string; toolName: string; canonicalArgsHash: string }>()
57
+ function seenKey(s: { chatId: string; sessionId: string; toolUseId: string }): string {
58
+ return `${s.chatId}|${s.sessionId}|${s.toolUseId}`
59
+ }
60
+ // In-memory mirror of persisted records keyed by id — lets submit() check
61
+ // existing state synchronously (before the first await) so that concurrent
62
+ // calls within the same event-loop turn see the correct state.
63
+ const inMemory = new Map<string, ToolRequest>()
64
+
65
+ function hmacId(s: ToolCallbackSubmitArgs, hash: string): string {
66
+ const h = createHmac("sha256", opts.serverSecret)
67
+ h.update(`${s.chatId}|${s.sessionId}|${s.toolUseId}|${s.toolName}|${hash}`)
68
+ return h.digest("hex")
69
+ }
70
+
71
+ function resolveWaiters(id: string, result: ToolCallbackResult) {
72
+ const ws = waiters.get(id) ?? []
73
+ waiters.delete(id)
74
+ for (const w of ws) w.resolve(result)
75
+ }
76
+
77
+ async function persistPut(req: ToolRequest): Promise<void> {
78
+ inMemory.set(req.id, { ...req })
79
+ await opts.store.putToolRequest(req)
80
+ }
81
+
82
+ async function persistResolve(
83
+ id: string,
84
+ update: { status: ToolRequestStatus; decision: ToolRequestDecision; resolvedAt: number; mismatchReason?: string },
85
+ ): Promise<void> {
86
+ const existing = inMemory.get(id)
87
+ if (existing) {
88
+ inMemory.set(id, { ...existing, ...update })
89
+ }
90
+ await opts.store.resolveToolRequest(id, update)
91
+ }
92
+
93
+ const svc: ToolCallbackService = {
94
+ submit(args) {
95
+ const hash = canonicalArgsHash(args.args)
96
+ const id = hmacId(args, hash)
97
+
98
+ // ── Arg-mismatch check (synchronous, no I/O) ──────────────────────────
99
+ const seen = seenToolUseIds.get(seenKey(args))
100
+ if (seen && (seen.toolName !== args.toolName || seen.canonicalArgsHash !== hash)) {
101
+ const reason = `argument_mismatch: canonicalArgsHash differs from prior submission for toolUseId=${args.toolUseId}`
102
+ const decision: ToolRequestDecision = { kind: "deny", reason }
103
+ const now = opts.now()
104
+ const mismatchReq: ToolRequest = {
105
+ id,
106
+ chatId: args.chatId,
107
+ sessionId: args.sessionId,
108
+ toolUseId: args.toolUseId,
109
+ toolName: args.toolName,
110
+ arguments: args.args,
111
+ canonicalArgsHash: hash,
112
+ policyVerdict: "auto-deny",
113
+ status: "arg_mismatch",
114
+ decision,
115
+ mismatchReason: reason,
116
+ createdAt: now,
117
+ resolvedAt: now,
118
+ expiresAt: now,
119
+ }
120
+ // Await persistence so a caller scanning the store after submit() sees the record.
121
+ return persistPut(mismatchReq).then(() => ({ status: "arg_mismatch" as const, decision, mismatchReason: reason }))
122
+ }
123
+
124
+ // ── Idempotency: check in-memory mirror (synchronous) ─────────────────
125
+ const existing = inMemory.get(id)
126
+ if (existing && POLICY_TERMINAL_STATUSES.has(existing.status)) {
127
+ return Promise.resolve({
128
+ status: existing.status,
129
+ decision: existing.decision ?? { kind: "deny", reason: "unknown" },
130
+ mismatchReason: existing.mismatchReason,
131
+ })
132
+ }
133
+ if (existing) {
134
+ // Already pending — attach a new waiter.
135
+ return new Promise<ToolCallbackResult>((resolve) => {
136
+ const list = waiters.get(id) ?? []
137
+ list.push({ resolve, expiresAt: existing.expiresAt })
138
+ waiters.set(id, list)
139
+ })
140
+ }
141
+
142
+ // ── New request ───────────────────────────────────────────────────────
143
+ const verdict = policy.evaluate({
144
+ toolName: args.toolName,
145
+ args: args.args,
146
+ chatPolicy: args.chatPolicy,
147
+ cwd: args.cwd,
148
+ })
149
+ const now = opts.now()
150
+ const expiresAt = now + opts.timeoutMs
151
+ const req: ToolRequest = {
152
+ id,
153
+ chatId: args.chatId,
154
+ sessionId: args.sessionId,
155
+ toolUseId: args.toolUseId,
156
+ toolName: args.toolName,
157
+ arguments: args.args,
158
+ canonicalArgsHash: hash,
159
+ policyVerdict: verdict.verdict,
160
+ status: "pending",
161
+ createdAt: now,
162
+ expiresAt,
163
+ }
164
+
165
+ // Register synchronously so subsequent calls within the same tick see it.
166
+ inMemory.set(id, { ...req })
167
+ seenToolUseIds.set(seenKey(args), { id, toolName: args.toolName, canonicalArgsHash: hash })
168
+
169
+ if (verdict.verdict === "auto-allow" || verdict.verdict === "auto-deny") {
170
+ const decision: ToolRequestDecision = verdict.verdict === "auto-allow"
171
+ ? { kind: "allow", reason: verdict.reason }
172
+ : { kind: "deny", reason: verdict.reason }
173
+ const resolvedReq: ToolRequest = { ...req, status: "answered", decision, resolvedAt: now }
174
+ inMemory.set(id, resolvedReq)
175
+ // Persist in background; caller gets immediate result.
176
+ void (async () => {
177
+ await opts.store.putToolRequest(req)
178
+ await opts.store.resolveToolRequest(id, { status: "answered", decision, resolvedAt: now })
179
+ })()
180
+ return Promise.resolve({ status: "answered", decision })
181
+ }
182
+
183
+ // "ask" verdict → persist then wait for external answer.
184
+ const pendingPromise = new Promise<ToolCallbackResult>((resolve) => {
185
+ const list = waiters.get(id) ?? []
186
+ list.push({ resolve, expiresAt })
187
+ waiters.set(id, list)
188
+ })
189
+ void opts.store.putToolRequest(req)
190
+ return pendingPromise
191
+ },
192
+
193
+ async answer(id, decision) {
194
+ const existing = inMemory.get(id) ?? opts.store.getToolRequest(id)
195
+ if (!existing || POLICY_TERMINAL_STATUSES.has(existing.status)) return
196
+ await persistResolve(id, { status: "answered", decision, resolvedAt: opts.now() })
197
+ resolveWaiters(id, { status: "answered", decision })
198
+ },
199
+
200
+ async cancel(id, reason) {
201
+ const existing = inMemory.get(id) ?? opts.store.getToolRequest(id)
202
+ if (!existing || POLICY_TERMINAL_STATUSES.has(existing.status)) return
203
+ const decision: ToolRequestDecision = { kind: "deny", reason: `canceled: ${reason}` }
204
+ await persistResolve(id, { status: "canceled", decision, resolvedAt: opts.now() })
205
+ resolveWaiters(id, { status: "canceled", decision })
206
+ },
207
+
208
+ async cancelAllForChat(chatId, reason) {
209
+ // Collect pending ids from in-memory mirror first (synchronous), then
210
+ // also check store for any records loaded before this service started.
211
+ const pendingIds = new Set<string>()
212
+ for (const [id, req] of inMemory.entries()) {
213
+ if (req.chatId === chatId && req.status === "pending") pendingIds.add(id)
214
+ }
215
+ const storeList = opts.store.listPendingToolRequests(chatId)
216
+ for (const req of storeList) pendingIds.add(req.id)
217
+ for (const id of pendingIds) await svc.cancel(id, reason)
218
+ },
219
+
220
+ async cancelAllForSession(sessionId, reason) {
221
+ const ids = Array.from(waiters.keys())
222
+ for (const id of ids) {
223
+ const req = inMemory.get(id) ?? opts.store.getToolRequest(id)
224
+ if (req && req.sessionId === sessionId) await svc.cancel(id, reason)
225
+ }
226
+ },
227
+
228
+ async recoverOnStartup() {
229
+ const all = opts.store.scanAllToolRequests()
230
+ for (const req of all) {
231
+ if (req.status !== "pending") continue
232
+ const decision: ToolRequestDecision = { kind: "deny", reason: "server_restarted" }
233
+ await persistResolve(req.id, { status: "session_closed", decision, resolvedAt: opts.now() })
234
+ }
235
+ },
236
+
237
+ async tickTimeouts() {
238
+ const now = opts.now()
239
+ for (const [id, list] of waiters.entries()) {
240
+ if (list.length === 0) continue
241
+ if (list[0].expiresAt > now) continue
242
+ const decision: ToolRequestDecision = { kind: "deny", reason: "timeout" }
243
+ await persistResolve(id, { status: "timeout", decision, resolvedAt: now })
244
+ resolveWaiters(id, { status: "timeout", decision })
245
+ }
246
+ },
247
+ }
248
+
249
+ return svc
250
+ }
251
+
252
+ /**
253
+ * Creates a ToolCallbackService and immediately calls recoverOnStartup()
254
+ * to fail-close any pending tool requests left over from a previous server
255
+ * run. KANNA_SERVER_SECRET should be set in the environment for stable
256
+ * HMAC ids within a process lifetime; if unset, a fresh random UUID is used
257
+ * (cross-restart idempotency is not required because recoverOnStartup()
258
+ * already closes all pending records).
259
+ */
260
+ export async function initToolCallbackOnBoot(args: {
261
+ store: EventStore
262
+ serverSecret: string
263
+ now?: () => number
264
+ timeoutMs?: number
265
+ }): Promise<ToolCallbackService> {
266
+ const svc = createToolCallbackService({
267
+ store: args.store,
268
+ serverSecret: args.serverSecret,
269
+ now: args.now ?? (() => Date.now()),
270
+ timeoutMs: args.timeoutMs ?? 600_000,
271
+ })
272
+ await svc.recoverOnStartup()
273
+ return svc
274
+ }
@@ -0,0 +1,272 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import {
3
+ sendUserPrompt,
4
+ sendExitCommand,
5
+ dismissTrustDialogIfPresent,
6
+ waitForTuiReady,
7
+ waitForTuiReadyWithTrustDismiss,
8
+ TRUST_DIALOG_MARKER,
9
+ TUI_READY_MARKER,
10
+ } from "./tui-control"
11
+ import { OutputRing } from "./output-ring"
12
+ import type { PtyProcess } from "./pty-process.adapter"
13
+
14
+ function fakePty(): PtyProcess & { sent: string[] } {
15
+ const sent: string[] = []
16
+ return {
17
+ sent,
18
+ pid: 99997,
19
+ async sendInput(data: string) { sent.push(data) },
20
+ resize() { /* noop */ },
21
+ exited: new Promise<number>(() => { /* never */ }),
22
+ close() { /* noop */ },
23
+ kill() { /* noop */ },
24
+ } as PtyProcess & { sent: string[] }
25
+ }
26
+
27
+ describe("sendUserPrompt", () => {
28
+ test("writes bracketed-paste wrapped text then separate carriage return once ring grows", async () => {
29
+ const pty = fakePty()
30
+ const ring = new OutputRing()
31
+ // Simulate the TUI rendering the paste preview shortly after the paste write.
32
+ setTimeout(() => ring.append("[Pasted text #1 +56 lines]"), 5)
33
+ await sendUserPrompt(pty, ring, "say hi", { commitTimeoutMs: 500, pollMs: 1 })
34
+ expect(pty.sent).toEqual(["\x1b[200~say hi\x1b[201~", "\r"])
35
+ })
36
+
37
+ test("empty string still emits paste markers + carriage return after commit", async () => {
38
+ const pty = fakePty()
39
+ const ring = new OutputRing()
40
+ setTimeout(() => ring.append("x"), 5)
41
+ await sendUserPrompt(pty, ring, "", { commitTimeoutMs: 500, pollMs: 1 })
42
+ expect(pty.sent).toEqual(["\x1b[200~\x1b[201~", "\r"])
43
+ })
44
+
45
+ test("sends Enter after commitTimeoutMs even if the ring never grows (degraded fallback)", async () => {
46
+ const pty = fakePty()
47
+ const ring = new OutputRing() // never grows
48
+ const start = Date.now()
49
+ await sendUserPrompt(pty, ring, "no echo", { commitTimeoutMs: 50, pollMs: 5 })
50
+ const elapsed = Date.now() - start
51
+ expect(pty.sent).toEqual(["\x1b[200~no echo\x1b[201~", "\r"])
52
+ expect(elapsed).toBeGreaterThanOrEqual(45)
53
+ })
54
+ })
55
+
56
+ describe("sendExitCommand", () => {
57
+ test("writes /exit + carriage return", async () => {
58
+ const pty = fakePty()
59
+ await sendExitCommand(pty)
60
+ expect(pty.sent).toEqual(["/exit\r"])
61
+ })
62
+ })
63
+
64
+ describe("dismissTrustDialogIfPresent", () => {
65
+ test("sends carriage return when ringbuf contains trust marker", async () => {
66
+ const pty = fakePty()
67
+ const ring = new OutputRing()
68
+ ring.append("Quick safety check: Do you trust this folder? trust this folder")
69
+ const dismissed = await dismissTrustDialogIfPresent(pty, ring)
70
+ expect(dismissed).toBe(true)
71
+ expect(pty.sent).toEqual(["\r"])
72
+ })
73
+
74
+ test("does nothing when ringbuf lacks trust marker", async () => {
75
+ const pty = fakePty()
76
+ const ring = new OutputRing()
77
+ ring.append("Welcome back c!")
78
+ const dismissed = await dismissTrustDialogIfPresent(pty, ring)
79
+ expect(dismissed).toBe(false)
80
+ expect(pty.sent).toEqual([])
81
+ })
82
+
83
+ test("exported TRUST_DIALOG_MARKER is the substring matched", () => {
84
+ expect(TRUST_DIALOG_MARKER).toBe("trust this folder")
85
+ })
86
+ })
87
+
88
+ describe("waitForTuiReady", () => {
89
+ test("returns 'marker' when ringbuf already contains the input-box marker", async () => {
90
+ const ring = new OutputRing()
91
+ ring.append("❯ ")
92
+ const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10, quietPeriodMs: 0 })
93
+ expect(result).toBe("marker")
94
+ })
95
+
96
+ test("returns 'timeout' when no marker appears within hardCapMs", async () => {
97
+ const ring = new OutputRing()
98
+ const result = await waitForTuiReady(ring, { hardCapMs: 200, pollMs: 10, quietPeriodMs: 0 })
99
+ expect(result).toBe("timeout")
100
+ })
101
+
102
+ test("polls until marker appears", async () => {
103
+ const ring = new OutputRing()
104
+ setTimeout(() => ring.append("❯ "), 50)
105
+ const start = Date.now()
106
+ const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10, quietPeriodMs: 0 })
107
+ const elapsed = Date.now() - start
108
+ expect(result).toBe("marker")
109
+ expect(elapsed).toBeLessThan(300)
110
+ })
111
+
112
+ // Regression: claude TUI v2.1.146 leaks `❯ ` during splash/trust/MCP render
113
+ // before Ink mounts the keyboard handler. Returning "marker" on first hit
114
+ // makes the driver send the first prompt into a TUI that discards stdin.
115
+ // The quiet-period gate waits for ring growth to settle as a proxy for
116
+ // "input handler attached" — drops the race that causes PTY chats to hang
117
+ // with no transcript file ever created.
118
+ test("waits for ring to stay quiet for quietPeriodMs after marker hit", async () => {
119
+ const ring = new OutputRing()
120
+ ring.append("splash banner ❯ ")
121
+ // Keep appending bytes for 200 ms after the marker first appears — the
122
+ // gate must NOT resolve while the TUI is still rendering.
123
+ const interval = setInterval(() => ring.append("."), 30)
124
+ setTimeout(() => clearInterval(interval), 200)
125
+ const start = Date.now()
126
+ const result = await waitForTuiReady(ring, { hardCapMs: 2000, pollMs: 10, quietPeriodMs: 150 })
127
+ const elapsed = Date.now() - start
128
+ expect(result).toBe("marker")
129
+ // Render bursts (200 ms) + quiet period (150 ms) = at least 350 ms.
130
+ expect(elapsed).toBeGreaterThanOrEqual(300)
131
+ })
132
+
133
+ test("resolves immediately when ring stays quiet from the start", async () => {
134
+ const ring = new OutputRing()
135
+ ring.append("❯ ")
136
+ const start = Date.now()
137
+ const result = await waitForTuiReady(ring, { hardCapMs: 2000, pollMs: 10, quietPeriodMs: 80 })
138
+ const elapsed = Date.now() - start
139
+ expect(result).toBe("marker")
140
+ expect(elapsed).toBeGreaterThanOrEqual(70)
141
+ expect(elapsed).toBeLessThan(250)
142
+ })
143
+
144
+ test("exported TUI_READY_MARKER is the input-box prompt", () => {
145
+ expect(TUI_READY_MARKER).toBe("❯ ")
146
+ })
147
+
148
+ test("returns 'marker' when TUI renders ❯ with \\x1b[1C instead of space", async () => {
149
+ const ring = new OutputRing()
150
+ // Real TUI output: space after ❯ is cursor-forward-1, not a literal space
151
+ ring.append("❯\x1b[1C")
152
+ const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10 })
153
+ expect(result).toBe("marker")
154
+ })
155
+
156
+ test("returns 'marker' when TUI renders ❯ followed by U+00A0 (non-breaking space)", async () => {
157
+ const ring = new OutputRing()
158
+ // Real TUI output: ❯ is followed by NBSP (U+00A0), not a regular space
159
+ ring.append("❯ ")
160
+ const result = await waitForTuiReady(ring, { hardCapMs: 1000, pollMs: 10 })
161
+ expect(result).toBe("marker")
162
+ })
163
+ })
164
+
165
+ describe("dismissTrustDialogIfPresent (ANSI-encoded ring)", () => {
166
+ test("detects trust dialog when words separated by \\x1b[1C (TUI rendering)", async () => {
167
+ const pty = fakePty()
168
+ const ring = new OutputRing()
169
+ // Real TUI output: spaces rendered as cursor-forward-1 escape sequences
170
+ ring.append("\x1b[1C❯\x1b[1C1.\x1b[1CYes,\x1b[1CI\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder\r\n")
171
+ const dismissed = await dismissTrustDialogIfPresent(pty, ring)
172
+ expect(dismissed).toBe(true)
173
+ expect(pty.sent).toEqual(["\r"])
174
+ })
175
+
176
+ test("does not false-trigger on plain text without trust marker", async () => {
177
+ const pty = fakePty()
178
+ const ring = new OutputRing()
179
+ ring.append("\x1b[1CWelcome\x1b[1Cback!\x1b[0m❯ ")
180
+ const dismissed = await dismissTrustDialogIfPresent(pty, ring)
181
+ expect(dismissed).toBe(false)
182
+ expect(pty.sent).toEqual([])
183
+ })
184
+ })
185
+
186
+ describe("waitForTuiReadyWithTrustDismiss", () => {
187
+ test("returns 'ready' immediately when input box already present", async () => {
188
+ const pty = fakePty()
189
+ const ring = new OutputRing()
190
+ ring.append("❯ ")
191
+ const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 500, pollMs: 10 })
192
+ expect(result).toBe("ready")
193
+ expect(pty.sent).toEqual([])
194
+ })
195
+
196
+ test("dismisses ANSI trust dialog then resolves when input box appears", async () => {
197
+ const pty = fakePty()
198
+ const ring = new OutputRing()
199
+ // Trust dialog with ANSI-encoded text appears first
200
+ ring.append("\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder")
201
+ // After 50ms simulate TUI loading: input box appears after dismiss sends \r
202
+ setTimeout(() => ring.append("❯ "), 80)
203
+ const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 1000, pollMs: 10 })
204
+ expect(result).toBe("ready")
205
+ expect(pty.sent).toContain("\r")
206
+ })
207
+
208
+ test("returns 'timeout' when neither marker appears", async () => {
209
+ const pty = fakePty()
210
+ const ring = new OutputRing()
211
+ const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 150, pollMs: 10 })
212
+ expect(result).toBe("timeout")
213
+ expect(pty.sent).toEqual([])
214
+ })
215
+
216
+ test("returns 'ready' when TUI renders ❯ with \\x1b[1C (ANSI cursor-forward)", async () => {
217
+ const pty = fakePty()
218
+ const ring = new OutputRing()
219
+ ring.append("❯\x1b[1C")
220
+ const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 500, pollMs: 10 })
221
+ expect(result).toBe("ready")
222
+ expect(pty.sent).toEqual([])
223
+ })
224
+
225
+ test("does not false-trigger on trust dialog's own ❯ selection cursor", async () => {
226
+ // Trust dialog renders "❯ 1. Yes, I trust this folder" as ANSI:
227
+ // "❯\x1b[1C1.\x1b[1CYes,...trust\x1b[1Cthis\x1b[1Cfolder"
228
+ // stripAnsi gives "❯ 1. Yes, I trust this folder" which contains "❯ "
229
+ // → must NOT trigger "ready" while trust dialog is present
230
+ const pty = fakePty()
231
+ const ring = new OutputRing()
232
+ ring.append("\x1b[1C❯\x1b[1C1.\x1b[1CYes,\x1b[1CI\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder\r\n")
233
+ setTimeout(() => ring.append("❯\x1b[1C"), 80)
234
+ const result = await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 1000, pollMs: 10 })
235
+ expect(result).toBe("ready")
236
+ expect(pty.sent).toContain("\r")
237
+ })
238
+
239
+ test("dismisses trust dialog only once even if marker persists in ring", async () => {
240
+ const pty = fakePty()
241
+ const ring = new OutputRing()
242
+ ring.append("\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder")
243
+ setTimeout(() => ring.append("❯ "), 80)
244
+ await waitForTuiReadyWithTrustDismiss(pty, ring, { hardCapMs: 1000, pollMs: 10 })
245
+ // \r should appear exactly once (dismiss sent once, not repeated each poll)
246
+ expect(pty.sent.filter((s) => s === "\r")).toHaveLength(1)
247
+ })
248
+
249
+ // Regression: same race as waitForTuiReady — after trust dismiss the input
250
+ // box marker can render before Ink's keyboard handler mounts. Quiet-period
251
+ // gate must apply on this path too so prompts don't land into a discarding
252
+ // TUI under load.
253
+ test("waits for ring to stay quiet for quietPeriodMs after post-dismiss marker hit", async () => {
254
+ const pty = fakePty()
255
+ const ring = new OutputRing()
256
+ ring.append("\x1b[1Ctrust\x1b[1Cthis\x1b[1Cfolder")
257
+ // After dismiss, marker arrives at t=50, then more bytes for 200 ms.
258
+ setTimeout(() => ring.append("❯ "), 50)
259
+ const interval = setInterval(() => ring.append("."), 30)
260
+ setTimeout(() => clearInterval(interval), 250)
261
+ const start = Date.now()
262
+ const result = await waitForTuiReadyWithTrustDismiss(pty, ring, {
263
+ hardCapMs: 2000,
264
+ pollMs: 10,
265
+ quietPeriodMs: 150,
266
+ })
267
+ const elapsed = Date.now() - start
268
+ expect(result).toBe("ready")
269
+ // Bursts end ~250 ms + quiet 150 ms.
270
+ expect(elapsed).toBeGreaterThanOrEqual(350)
271
+ })
272
+ })