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,234 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test"
2
+ import {
3
+ PushSubscriptionStore,
4
+ createPushRouter,
5
+ initVapid,
6
+ getVapidPublicKey,
7
+ type StoredPushSubscription,
8
+ } from "./push-notifications"
9
+ import { mkdtemp, rm } from "node:fs/promises"
10
+ import { tmpdir } from "node:os"
11
+ import path from "node:path"
12
+
13
+ describe("PushSubscriptionStore", () => {
14
+ let dir: string
15
+ let store: PushSubscriptionStore
16
+
17
+ beforeEach(async () => {
18
+ dir = await mkdtemp(path.join(tmpdir(), "push-test-"))
19
+ store = new PushSubscriptionStore(path.join(dir, "push-subscriptions.json"))
20
+ })
21
+
22
+ afterEach(async () => {
23
+ await rm(dir, { recursive: true, force: true })
24
+ })
25
+
26
+ test("add and list subscriptions", () => {
27
+ const sub: StoredPushSubscription = {
28
+ endpoint: "https://push.example.com/abc",
29
+ keys: { p256dh: "key1", auth: "auth1" },
30
+ }
31
+ store.add(sub)
32
+ expect(store.getAll()).toHaveLength(1)
33
+ expect(store.getAll()[0].endpoint).toBe("https://push.example.com/abc")
34
+ })
35
+
36
+ test("deduplicates by endpoint", () => {
37
+ const sub: StoredPushSubscription = {
38
+ endpoint: "https://push.example.com/abc",
39
+ keys: { p256dh: "key1", auth: "auth1" },
40
+ }
41
+ store.add(sub)
42
+ store.add(sub)
43
+ expect(store.getAll()).toHaveLength(1)
44
+ })
45
+
46
+ test("remove by endpoint", () => {
47
+ const sub: StoredPushSubscription = {
48
+ endpoint: "https://push.example.com/abc",
49
+ keys: { p256dh: "key1", auth: "auth1" },
50
+ }
51
+ store.add(sub)
52
+ store.remove("https://push.example.com/abc")
53
+ expect(store.getAll()).toHaveLength(0)
54
+ })
55
+
56
+ test("persists to disk and loads back", async () => {
57
+ const sub: StoredPushSubscription = {
58
+ endpoint: "https://push.example.com/abc",
59
+ keys: { p256dh: "key1", auth: "auth1" },
60
+ }
61
+ store.add(sub)
62
+ await store.save()
63
+
64
+ const store2 = new PushSubscriptionStore(path.join(dir, "push-subscriptions.json"))
65
+ await store2.load()
66
+ expect(store2.getAll()).toHaveLength(1)
67
+ expect(store2.getAll()[0].endpoint).toBe("https://push.example.com/abc")
68
+ })
69
+
70
+ test("load handles missing file gracefully", async () => {
71
+ const store2 = new PushSubscriptionStore(path.join(dir, "nonexistent.json"))
72
+ await store2.load()
73
+ expect(store2.getAll()).toHaveLength(0)
74
+ })
75
+ })
76
+
77
+ describe("createPushRouter", () => {
78
+ let dir: string
79
+ let store: PushSubscriptionStore
80
+
81
+ beforeEach(async () => {
82
+ dir = await mkdtemp(path.join(tmpdir(), "push-test-"))
83
+ store = new PushSubscriptionStore(path.join(dir, "push-subscriptions.json"))
84
+ })
85
+
86
+ afterEach(async () => {
87
+ await rm(dir, { recursive: true, force: true })
88
+ })
89
+
90
+ test("GET /api/push/vapid-key returns public key", async () => {
91
+ const savedPublic = process.env.VAPID_PUBLIC_KEY
92
+ const savedPrivate = process.env.VAPID_PRIVATE_KEY
93
+ // Use real VAPID keys (web-push validates key length)
94
+ const keys = (await import("web-push")).generateVAPIDKeys()
95
+ process.env.VAPID_PUBLIC_KEY = keys.publicKey
96
+ process.env.VAPID_PRIVATE_KEY = keys.privateKey
97
+
98
+ try {
99
+ initVapid()
100
+ const router = createPushRouter(store)
101
+ const req = new Request("http://localhost/api/push/vapid-key")
102
+ const res = await router(req)
103
+ expect(res.status).toBe(200)
104
+ const body = await res.json()
105
+ expect(body.publicKey).toBe(keys.publicKey)
106
+ } finally {
107
+ if (savedPublic) process.env.VAPID_PUBLIC_KEY = savedPublic
108
+ else delete process.env.VAPID_PUBLIC_KEY
109
+ if (savedPrivate) process.env.VAPID_PRIVATE_KEY = savedPrivate
110
+ else delete process.env.VAPID_PRIVATE_KEY
111
+ }
112
+ })
113
+
114
+ test("POST /api/push/subscribe adds subscription", async () => {
115
+ const router = createPushRouter(store)
116
+ const sub = {
117
+ endpoint: "https://push.example.com/xyz",
118
+ keys: { p256dh: "k", auth: "a" },
119
+ }
120
+ const req = new Request("http://localhost/api/push/subscribe", {
121
+ method: "POST",
122
+ headers: { "Content-Type": "application/json" },
123
+ body: JSON.stringify(sub),
124
+ })
125
+ const res = await router(req)
126
+ expect(res.status).toBe(201)
127
+ expect(store.getAll()).toHaveLength(1)
128
+ })
129
+
130
+ test("DELETE /api/push/subscribe removes subscription", async () => {
131
+ store.add({
132
+ endpoint: "https://push.example.com/xyz",
133
+ keys: { p256dh: "k", auth: "a" },
134
+ })
135
+ const router = createPushRouter(store)
136
+ const req = new Request("http://localhost/api/push/subscribe", {
137
+ method: "DELETE",
138
+ headers: { "Content-Type": "application/json" },
139
+ body: JSON.stringify({ endpoint: "https://push.example.com/xyz" }),
140
+ })
141
+ const res = await router(req)
142
+ expect(res.status).toBe(200)
143
+ expect(store.getAll()).toHaveLength(0)
144
+ })
145
+
146
+ test("GET /api/push/vapid-key returns 503 when not configured", async () => {
147
+ const savedPublic = process.env.VAPID_PUBLIC_KEY
148
+ const savedPrivate = process.env.VAPID_PRIVATE_KEY
149
+ delete process.env.VAPID_PUBLIC_KEY
150
+ delete process.env.VAPID_PRIVATE_KEY
151
+
152
+ try {
153
+ initVapid()
154
+ const router = createPushRouter(store)
155
+ const req = new Request("http://localhost/api/push/vapid-key")
156
+ const res = await router(req)
157
+ expect(res.status).toBe(503)
158
+ const body = await res.json()
159
+ expect(body.error).toBe("Push notifications not configured")
160
+ } finally {
161
+ if (savedPublic) process.env.VAPID_PUBLIC_KEY = savedPublic
162
+ if (savedPrivate) process.env.VAPID_PRIVATE_KEY = savedPrivate
163
+ }
164
+ })
165
+
166
+ test("POST /api/push/subscribe validates body - missing fields returns 400", async () => {
167
+ const router = createPushRouter(store)
168
+
169
+ // Missing endpoint entirely
170
+ const res1 = await router(new Request("http://localhost/api/push/subscribe", {
171
+ method: "POST",
172
+ headers: { "Content-Type": "application/json" },
173
+ body: JSON.stringify({ keys: { p256dh: "k", auth: "a" } }),
174
+ }))
175
+ expect(res1.status).toBe(400)
176
+
177
+ // Missing keys
178
+ const res2 = await router(new Request("http://localhost/api/push/subscribe", {
179
+ method: "POST",
180
+ headers: { "Content-Type": "application/json" },
181
+ body: JSON.stringify({ endpoint: "https://push.example.com/abc" }),
182
+ }))
183
+ expect(res2.status).toBe(400)
184
+
185
+ // Invalid JSON
186
+ const res3 = await router(new Request("http://localhost/api/push/subscribe", {
187
+ method: "POST",
188
+ headers: { "Content-Type": "application/json" },
189
+ body: "not json",
190
+ }))
191
+ expect(res3.status).toBe(400)
192
+
193
+ // Invalid endpoint URL
194
+ const res4 = await router(new Request("http://localhost/api/push/subscribe", {
195
+ method: "POST",
196
+ headers: { "Content-Type": "application/json" },
197
+ body: JSON.stringify({ endpoint: "not-a-url", keys: { p256dh: "k", auth: "a" } }),
198
+ }))
199
+ expect(res4.status).toBe(400)
200
+
201
+ // Missing key fields (p256dh/auth)
202
+ const res5 = await router(new Request("http://localhost/api/push/subscribe", {
203
+ method: "POST",
204
+ headers: { "Content-Type": "application/json" },
205
+ body: JSON.stringify({ endpoint: "https://push.example.com/abc", keys: { p256dh: 123, auth: "a" } }),
206
+ }))
207
+ expect(res5.status).toBe(400)
208
+ })
209
+
210
+ test("unknown route returns 404", async () => {
211
+ const router = createPushRouter(store)
212
+ const req = new Request("http://localhost/api/push/unknown")
213
+ const res = await router(req)
214
+ expect(res.status).toBe(404)
215
+ })
216
+ })
217
+
218
+ describe("initVapid", () => {
219
+ test("returns false when env vars not set", () => {
220
+ const savedPublic = process.env.VAPID_PUBLIC_KEY
221
+ const savedPrivate = process.env.VAPID_PRIVATE_KEY
222
+ delete process.env.VAPID_PUBLIC_KEY
223
+ delete process.env.VAPID_PRIVATE_KEY
224
+
225
+ try {
226
+ const result = initVapid()
227
+ expect(result).toBe(false)
228
+ expect(getVapidPublicKey()).toBeNull()
229
+ } finally {
230
+ if (savedPublic) process.env.VAPID_PUBLIC_KEY = savedPublic
231
+ if (savedPrivate) process.env.VAPID_PRIVATE_KEY = savedPrivate
232
+ }
233
+ })
234
+ })
@@ -0,0 +1,188 @@
1
+ import webpush from "web-push"
2
+ import { LOG_PREFIX } from "../shared/branding"
3
+
4
+ // ── Types ──────────────────────────────────────────────────────────
5
+
6
+ export interface StoredPushSubscription {
7
+ endpoint: string
8
+ keys: { p256dh: string; auth: string }
9
+ }
10
+
11
+ export interface PushPayload {
12
+ title: string
13
+ body: string
14
+ url?: string
15
+ tag?: string
16
+ }
17
+
18
+ // ── VAPID ──────────────────────────────────────────────────────────
19
+
20
+ let vapidConfigured = false
21
+ let vapidPublicKey: string | null = null
22
+
23
+ export function initVapid(): boolean {
24
+ const publicKey = process.env.VAPID_PUBLIC_KEY
25
+ const privateKey = process.env.VAPID_PRIVATE_KEY
26
+ const subject = process.env.VAPID_SUBJECT ?? "mailto:tinkaria@localhost"
27
+
28
+ if (!publicKey || !privateKey) {
29
+ console.warn(LOG_PREFIX, "VAPID keys not configured — push notifications disabled")
30
+ vapidConfigured = false
31
+ vapidPublicKey = null
32
+ return false
33
+ }
34
+
35
+ webpush.setVapidDetails(subject, publicKey, privateKey)
36
+ vapidConfigured = true
37
+ vapidPublicKey = publicKey
38
+ console.warn(LOG_PREFIX, "Push notifications enabled (VAPID configured)")
39
+ return true
40
+ }
41
+
42
+ export function getVapidPublicKey(): string | null {
43
+ return vapidPublicKey
44
+ }
45
+
46
+ // ── Subscription Store ─────────────────────────────────────────────
47
+
48
+ export class PushSubscriptionStore {
49
+ private subscriptions = new Map<string, StoredPushSubscription>()
50
+ private readonly filePath: string
51
+
52
+ constructor(filePath: string) {
53
+ this.filePath = filePath
54
+ }
55
+
56
+ add(sub: StoredPushSubscription): void {
57
+ this.subscriptions.set(sub.endpoint, sub)
58
+ }
59
+
60
+ remove(endpoint: string): void {
61
+ this.subscriptions.delete(endpoint)
62
+ }
63
+
64
+ getAll(): StoredPushSubscription[] {
65
+ return [...this.subscriptions.values()]
66
+ }
67
+
68
+ async save(): Promise<void> {
69
+ const data = JSON.stringify(this.getAll(), null, 2)
70
+ await Bun.write(this.filePath, data)
71
+ }
72
+
73
+ async load(): Promise<void> {
74
+ try {
75
+ const file = Bun.file(this.filePath)
76
+ if (!(await file.exists())) return
77
+ const data = await file.json()
78
+ if (!Array.isArray(data)) return
79
+ for (const sub of data) {
80
+ if (sub.endpoint && sub.keys?.p256dh && sub.keys?.auth) {
81
+ this.subscriptions.set(sub.endpoint, sub)
82
+ }
83
+ }
84
+ } catch (err: unknown) {
85
+ console.warn(LOG_PREFIX, "Failed to load push subscriptions:", err instanceof Error ? err.message : String(err))
86
+ }
87
+ }
88
+ }
89
+
90
+ // ── Push Sender ────────────────────────────────────────────────────
91
+
92
+ export async function sendPushToAll(
93
+ store: PushSubscriptionStore,
94
+ payload: PushPayload,
95
+ ): Promise<void> {
96
+ if (!vapidConfigured) return
97
+
98
+ const subscriptions = store.getAll()
99
+ if (subscriptions.length === 0) return
100
+
101
+ const body = JSON.stringify(payload)
102
+ const expiredEndpoints: string[] = []
103
+
104
+ await Promise.allSettled(
105
+ subscriptions.map(async (sub) => {
106
+ try {
107
+ await webpush.sendNotification(
108
+ { endpoint: sub.endpoint, keys: sub.keys },
109
+ body,
110
+ )
111
+ } catch (err: unknown) {
112
+ const statusCode = err instanceof Error && "statusCode" in err
113
+ ? (err as { statusCode: number }).statusCode
114
+ : 0
115
+ if (statusCode === 404 || statusCode === 410) {
116
+ expiredEndpoints.push(sub.endpoint)
117
+ } else {
118
+ console.warn(
119
+ LOG_PREFIX,
120
+ `Push failed for ${sub.endpoint}:`,
121
+ err instanceof Error ? err.message : String(err),
122
+ )
123
+ }
124
+ }
125
+ }),
126
+ )
127
+
128
+ // Clean up expired subscriptions
129
+ for (const endpoint of expiredEndpoints) {
130
+ store.remove(endpoint)
131
+ }
132
+ if (expiredEndpoints.length > 0) {
133
+ await store.save()
134
+ }
135
+ }
136
+
137
+ // ── HTTP Router ────────────────────────────────────────────────────
138
+
139
+ export function createPushRouter(
140
+ store: PushSubscriptionStore,
141
+ ): (req: Request) => Promise<Response> {
142
+ return async (req: Request): Promise<Response> => {
143
+ const url = new URL(req.url)
144
+
145
+ if (url.pathname === "/api/push/vapid-key" && req.method === "GET") {
146
+ const key = getVapidPublicKey()
147
+ if (!key) {
148
+ return Response.json({ error: "Push notifications not configured" }, { status: 503 })
149
+ }
150
+ return Response.json({ publicKey: key })
151
+ }
152
+
153
+ if (url.pathname === "/api/push/subscribe" && req.method === "POST") {
154
+ let body: Record<string, unknown>
155
+ try { body = await req.json() } catch {
156
+ return Response.json({ error: "Invalid JSON" }, { status: 400 })
157
+ }
158
+ if (!body.endpoint || typeof body.endpoint !== "string" || !body.keys) {
159
+ return Response.json({ error: "Invalid subscription" }, { status: 400 })
160
+ }
161
+ try { new URL(body.endpoint) } catch {
162
+ return Response.json({ error: "Invalid endpoint URL" }, { status: 400 })
163
+ }
164
+ const keys = body.keys as Record<string, unknown>
165
+ if (typeof keys.p256dh !== "string" || typeof keys.auth !== "string") {
166
+ return Response.json({ error: "Invalid subscription keys" }, { status: 400 })
167
+ }
168
+ store.add({ endpoint: body.endpoint, keys: { p256dh: keys.p256dh, auth: keys.auth } })
169
+ await store.save()
170
+ return Response.json({ ok: true }, { status: 201 })
171
+ }
172
+
173
+ if (url.pathname === "/api/push/subscribe" && req.method === "DELETE") {
174
+ let body: Record<string, unknown>
175
+ try { body = await req.json() } catch {
176
+ return Response.json({ error: "Invalid JSON" }, { status: 400 })
177
+ }
178
+ if (!body.endpoint || typeof body.endpoint !== "string") {
179
+ return Response.json({ error: "Missing endpoint" }, { status: 400 })
180
+ }
181
+ store.remove(body.endpoint)
182
+ await store.save()
183
+ return Response.json({ ok: true })
184
+ }
185
+
186
+ return Response.json({ error: "Not found" }, { status: 404 })
187
+ }
188
+ }
@@ -0,0 +1,196 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { generateTitleForChat } from "./generate-title"
3
+ import { generateForkPromptForChat } from "./generate-fork-context"
4
+ import { QuickResponseAdapter } from "./quick-response"
5
+
6
+ describe("QuickResponseAdapter", () => {
7
+ test("returns the Claude structured result when it validates", async () => {
8
+ const adapter = new QuickResponseAdapter({
9
+ runClaudeStructured: async () => ({ title: "Claude title" }),
10
+ runCodexStructured: async () => ({ title: "Codex title" }),
11
+ })
12
+
13
+ const result = await adapter.generateStructured({
14
+ cwd: "/tmp/project",
15
+ task: "title generation",
16
+ prompt: "Generate a title",
17
+ schema: {
18
+ type: "object",
19
+ properties: {
20
+ title: { type: "string" },
21
+ },
22
+ required: ["title"],
23
+ additionalProperties: false,
24
+ },
25
+ parse: (value) => {
26
+ const output = value && typeof value === "object" ? value as { title?: unknown } : {}
27
+ return typeof output.title === "string" ? output.title : null
28
+ },
29
+ })
30
+
31
+ expect(result).toBe("Claude title")
32
+ })
33
+
34
+ test("falls back to Codex when Claude fails validation", async () => {
35
+ const adapter = new QuickResponseAdapter({
36
+ runClaudeStructured: async () => ({ bad: true }),
37
+ runCodexStructured: async () => ({ title: "Codex title" }),
38
+ })
39
+
40
+ const result = await adapter.generateStructured({
41
+ cwd: "/tmp/project",
42
+ task: "title generation",
43
+ prompt: "Generate a title",
44
+ schema: {
45
+ type: "object",
46
+ properties: {
47
+ title: { type: "string" },
48
+ },
49
+ required: ["title"],
50
+ additionalProperties: false,
51
+ },
52
+ parse: (value) => {
53
+ const output = value && typeof value === "object" ? value as { title?: unknown } : {}
54
+ return typeof output.title === "string" ? output.title : null
55
+ },
56
+ })
57
+
58
+ expect(result).toBe("Codex title")
59
+ })
60
+
61
+ test("falls back to Codex when Claude throws", async () => {
62
+ const adapter = new QuickResponseAdapter({
63
+ runClaudeStructured: async () => {
64
+ throw new Error("Not authenticated")
65
+ },
66
+ runCodexStructured: async () => ({ title: "Codex title" }),
67
+ })
68
+
69
+ const result = await adapter.generateStructured({
70
+ cwd: "/tmp/project",
71
+ task: "title generation",
72
+ prompt: "Generate a title",
73
+ schema: {
74
+ type: "object",
75
+ properties: {
76
+ title: { type: "string" },
77
+ },
78
+ required: ["title"],
79
+ additionalProperties: false,
80
+ },
81
+ parse: (value) => {
82
+ const output = value && typeof value === "object" ? value as { title?: unknown } : {}
83
+ return typeof output.title === "string" ? output.title : null
84
+ },
85
+ })
86
+
87
+ expect(result).toBe("Codex title")
88
+ })
89
+ })
90
+
91
+ describe("generateTitleForChat", () => {
92
+ test("sanitizes generated titles", async () => {
93
+ const title = await generateTitleForChat(
94
+ "hello",
95
+ "/tmp/project",
96
+ new QuickResponseAdapter({
97
+ runClaudeStructured: async () => ({ title: " Example\nTitle " }),
98
+ })
99
+ )
100
+
101
+ expect(title).toBe("Example Title")
102
+ })
103
+
104
+ test("rejects invalid generated titles", async () => {
105
+ const title = await generateTitleForChat(
106
+ "hello",
107
+ "/tmp/project",
108
+ new QuickResponseAdapter({
109
+ runClaudeStructured: async () => ({ title: " " }),
110
+ runCodexStructured: async () => ({ title: "New Chat" }),
111
+ })
112
+ )
113
+
114
+ expect(title).toBeNull()
115
+ })
116
+ })
117
+
118
+ describe("generateForkPromptForChat", () => {
119
+ test("builds a dedicated-session brief from analyzed intent and compacted context", async () => {
120
+ const prompt = await generateForkPromptForChat(
121
+ "Focus on the auth race fix",
122
+ [],
123
+ "/tmp/project",
124
+ undefined,
125
+ new QuickResponseAdapter({
126
+ runClaudeStructured: async (args) => {
127
+ if (args.task.includes("analysis")) {
128
+ return {
129
+ compactInstruction: "Keep the auth-race evidence only.",
130
+ nextInstruction: "Fix the auth race.",
131
+ }
132
+ }
133
+ return {
134
+ summary: "## Relevant Context\nKeep the existing API.",
135
+ }
136
+ },
137
+ }),
138
+ )
139
+
140
+ expect(prompt).toBe([
141
+ "## Objective",
142
+ "Fix the auth race.",
143
+ "",
144
+ "## Relevant Context",
145
+ "## Relevant Context",
146
+ "Keep the existing API.",
147
+ "",
148
+ "## Constraints",
149
+ "Preserve proven constraints from the context above. Call out contradictions or missing evidence before making risky changes.",
150
+ "",
151
+ "## Next Step",
152
+ "Start directly on the objective using the compacted context above.",
153
+ ].join("\n"))
154
+ })
155
+
156
+ test("falls back to a composed brief when structured output is invalid", async () => {
157
+ const prompt = await generateForkPromptForChat(
158
+ " Continue the mobile keyboard fix ",
159
+ [],
160
+ "/tmp/project",
161
+ undefined,
162
+ new QuickResponseAdapter({
163
+ runClaudeStructured: async () => ({ nope: true }),
164
+ runCodexStructured: async () => ({ prompt: " " }),
165
+ }),
166
+ )
167
+
168
+ expect(prompt).toContain("## Objective\nContinue the mobile keyboard fix")
169
+ expect(prompt).toContain("## Relevant Context\nNo prior transcript context was available.")
170
+ })
171
+
172
+ test("includes preset guidance in the analysis prompt", async () => {
173
+ let capturedPrompt = ""
174
+ await generateForkPromptForChat(
175
+ "Focus on an alternative design",
176
+ [],
177
+ "/tmp/project",
178
+ "alternative_approach",
179
+ new QuickResponseAdapter({
180
+ runClaudeStructured: async (args) => {
181
+ if (args.task.includes("analysis")) {
182
+ capturedPrompt = args.prompt
183
+ return {
184
+ compactInstruction: "Preserve only the key constraints.",
185
+ nextInstruction: "Explore the alternative.",
186
+ }
187
+ }
188
+ return { summary: "## Relevant Context\nAlternative-ready constraints." }
189
+ },
190
+ }),
191
+ )
192
+
193
+ expect(capturedPrompt).toContain("Selected preset: Alternative approach.")
194
+ expect(capturedPrompt).toContain("exploring a different solution path")
195
+ })
196
+ })