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,155 @@
1
+ import { describe, test, expect, afterEach } from "bun:test"
2
+ import { mkdtemp, writeFile, rm } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import { WorkflowStore, WorkflowParseError } from "./workflow-store"
6
+
7
+
8
+ let tmpDirs: string[] = []
9
+
10
+ async function makeTmpDir(): Promise<string> {
11
+ const dir = await mkdtemp(join(tmpdir(), "wf-store-test-"))
12
+ tmpDirs.push(dir)
13
+ return dir
14
+ }
15
+
16
+ async function writeYaml(dir: string, filename: string, content: string): Promise<void> {
17
+ await writeFile(join(dir, filename), content, "utf-8")
18
+ }
19
+
20
+ afterEach(async () => {
21
+ for (const dir of tmpDirs) {
22
+ await rm(dir, { recursive: true, force: true }).catch(() => {})
23
+ }
24
+ tmpDirs = []
25
+ })
26
+
27
+ describe("WorkflowStore", () => {
28
+ test("list() returns all .yaml files parsed and sorted by name", async () => {
29
+ const dir = await makeTmpDir()
30
+ await writeYaml(dir, "beta.yaml", `name: Beta\nsteps:\n - mcp_tool: do_thing`)
31
+ await writeYaml(dir, "alpha.yaml", `name: Alpha\nsteps:\n - mcp_tool: do_other`)
32
+ // non-yaml file should be ignored
33
+ await writeFile(join(dir, "readme.txt"), "not a workflow")
34
+
35
+ const store = new WorkflowStore(dir)
36
+ const results = await store.list()
37
+
38
+ expect(results).toHaveLength(2)
39
+ expect(results[0].name).toBe("Alpha")
40
+ expect(results[0].id).toBe("alpha")
41
+ expect(results[1].name).toBe("Beta")
42
+ expect(results[1].id).toBe("beta")
43
+ })
44
+
45
+ test("get(id) returns specific workflow by id", async () => {
46
+ const dir = await makeTmpDir()
47
+ await writeYaml(dir, "deploy.yaml", `name: Deploy\nsteps:\n - mcp_tool: run_deploy`)
48
+
49
+ const store = new WorkflowStore(dir)
50
+ const wf = await store.get("deploy")
51
+
52
+ expect(wf).not.toBeNull()
53
+ expect(wf!.id).toBe("deploy")
54
+ expect(wf!.name).toBe("Deploy")
55
+ expect(wf!.steps[0].mcp_tool).toBe("run_deploy")
56
+ })
57
+
58
+ test("get(id) returns null for non-existent workflow", async () => {
59
+ const dir = await makeTmpDir()
60
+ const store = new WorkflowStore(dir)
61
+ const wf = await store.get("nope")
62
+ expect(wf).toBeNull()
63
+ })
64
+
65
+ test("parses valid YAML with manual trigger", async () => {
66
+ const dir = await makeTmpDir()
67
+ await writeYaml(dir, "m.yaml", `name: Manual\ntrigger: manual\nsteps:\n - mcp_tool: x`)
68
+
69
+ const store = new WorkflowStore(dir)
70
+ const wf = await store.get("m")
71
+ expect(wf!.trigger).toBe("manual")
72
+ })
73
+
74
+ test("parses valid YAML with cron trigger", async () => {
75
+ const dir = await makeTmpDir()
76
+ await writeYaml(dir, "c.yaml", `name: Cron\ntrigger:\n cron: "*/5 * * * *"\nsteps:\n - mcp_tool: x`)
77
+
78
+ const store = new WorkflowStore(dir)
79
+ const wf = await store.get("c")
80
+ expect(wf!.trigger).toEqual({ cron: "*/5 * * * *" })
81
+ })
82
+
83
+ test("parses valid YAML with on_event trigger", async () => {
84
+ const dir = await makeTmpDir()
85
+ await writeYaml(dir, "e.yaml", `name: Event\ntrigger:\n on_event: push\nsteps:\n - mcp_tool: x`)
86
+
87
+ const store = new WorkflowStore(dir)
88
+ const wf = await store.get("e")
89
+ expect(wf!.trigger).toEqual({ on_event: "push" })
90
+ })
91
+
92
+ test("rejects YAML missing name", async () => {
93
+ const dir = await makeTmpDir()
94
+ await writeYaml(dir, "bad.yaml", `steps:\n - mcp_tool: x`)
95
+
96
+ const store = new WorkflowStore(dir)
97
+ await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
98
+ })
99
+
100
+ test("rejects YAML missing steps", async () => {
101
+ const dir = await makeTmpDir()
102
+ await writeYaml(dir, "bad.yaml", `name: Bad`)
103
+
104
+ const store = new WorkflowStore(dir)
105
+ await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
106
+ })
107
+
108
+ test("rejects YAML with empty steps array", async () => {
109
+ const dir = await makeTmpDir()
110
+ await writeYaml(dir, "bad.yaml", `name: Bad\nsteps: []`)
111
+
112
+ const store = new WorkflowStore(dir)
113
+ await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
114
+ })
115
+
116
+ test("rejects YAML with invalid on_failure value", async () => {
117
+ const dir = await makeTmpDir()
118
+ await writeYaml(dir, "bad.yaml", `name: Bad\nsteps:\n - mcp_tool: x\non_failure: explode`)
119
+
120
+ const store = new WorkflowStore(dir)
121
+ await expect(store.get("bad")).rejects.toBeInstanceOf(WorkflowParseError)
122
+ })
123
+
124
+ test("applies default values: trigger=manual, target=all, on_failure=stop, params={}", async () => {
125
+ const dir = await makeTmpDir()
126
+ await writeYaml(dir, "defaults.yaml", `name: Defaults\nsteps:\n - mcp_tool: foo`)
127
+
128
+ const store = new WorkflowStore(dir)
129
+ const wf = await store.get("defaults")
130
+
131
+ expect(wf!.trigger).toBe("manual")
132
+ expect(wf!.target).toBe("all")
133
+ expect(wf!.on_failure).toBe("stop")
134
+ expect(wf!.steps[0].params).toEqual({})
135
+ })
136
+
137
+ test("list() skips files that fail parsing and warns", async () => {
138
+ const dir = await makeTmpDir()
139
+ await writeYaml(dir, "good.yaml", `name: Good\nsteps:\n - mcp_tool: x`)
140
+ await writeYaml(dir, "bad.yaml", `steps: []`) // missing name AND empty steps
141
+
142
+ const warnings: string[] = []
143
+ const origWarn = console.warn
144
+ console.warn = (...args: unknown[]) => { warnings.push(String(args[0])) }
145
+
146
+ const store = new WorkflowStore(dir)
147
+ const results = await store.list()
148
+
149
+ console.warn = origWarn
150
+
151
+ expect(results).toHaveLength(1)
152
+ expect(results[0].name).toBe("Good")
153
+ expect(warnings.length).toBeGreaterThan(0)
154
+ })
155
+ })
@@ -0,0 +1,139 @@
1
+ import { readdir, readFile } from "node:fs/promises"
2
+ import { join, basename } from "node:path"
3
+ import yaml from "js-yaml"
4
+ import type { WorkflowDefinition, WorkflowStep, WorkflowTrigger } from "../shared/workflow-types"
5
+
6
+ const LOG_PREFIX = "[workflow-store]"
7
+
8
+ export class WorkflowParseError extends Error {
9
+ constructor(
10
+ public readonly workflowId: string,
11
+ public readonly field: string,
12
+ message: string,
13
+ ) {
14
+ super(`[${workflowId}] ${field}: ${message}`)
15
+ this.name = "WorkflowParseError"
16
+ }
17
+ }
18
+
19
+ export class WorkflowStore {
20
+ constructor(private readonly workflowsDir: string) {}
21
+
22
+ async list(): Promise<WorkflowDefinition[]> {
23
+ let entries: string[]
24
+ try {
25
+ entries = await readdir(this.workflowsDir)
26
+ } catch (err) {
27
+ console.warn(`${LOG_PREFIX} Cannot read workflows dir: ${err instanceof Error ? err.message : String(err)}`)
28
+ return []
29
+ }
30
+
31
+ const yamlFiles = entries.filter((f) => f.endsWith(".yaml")).sort()
32
+ const results: WorkflowDefinition[] = []
33
+
34
+ for (const file of yamlFiles) {
35
+ const id = basename(file, ".yaml")
36
+ try {
37
+ const content = await readFile(join(this.workflowsDir, file), "utf-8")
38
+ const raw = yaml.load(content)
39
+ results.push(this.parseYaml(id, raw))
40
+ } catch (err) {
41
+ console.warn(`${LOG_PREFIX} Skipping ${file}: ${err instanceof Error ? err.message : String(err)}`)
42
+ }
43
+ }
44
+
45
+ return results.sort((a, b) => a.name.localeCompare(b.name))
46
+ }
47
+
48
+ async get(id: string): Promise<WorkflowDefinition | null> {
49
+ const filePath = join(this.workflowsDir, `${id}.yaml`)
50
+ let content: string
51
+ try {
52
+ content = await readFile(filePath, "utf-8")
53
+ } catch {
54
+ return null
55
+ }
56
+ const raw = yaml.load(content)
57
+ return this.parseYaml(id, raw)
58
+ }
59
+
60
+ private parseYaml(id: string, raw: unknown): WorkflowDefinition {
61
+ if (typeof raw !== "object" || raw === null) {
62
+ throw new WorkflowParseError(id, "root", "must be an object")
63
+ }
64
+
65
+ const obj = raw as Record<string, unknown>
66
+
67
+ // name
68
+ if (typeof obj.name !== "string" || obj.name.trim() === "") {
69
+ throw new WorkflowParseError(id, "name", "must be a non-empty string")
70
+ }
71
+
72
+ // trigger
73
+ let trigger: WorkflowTrigger = "manual"
74
+ if (obj.trigger !== undefined) {
75
+ if (obj.trigger === "manual") {
76
+ trigger = "manual"
77
+ } else if (typeof obj.trigger === "object" && obj.trigger !== null) {
78
+ const t = obj.trigger as Record<string, unknown>
79
+ if (typeof t.cron === "string") {
80
+ trigger = { cron: t.cron }
81
+ } else if (typeof t.on_event === "string") {
82
+ trigger = { on_event: t.on_event }
83
+ } else {
84
+ throw new WorkflowParseError(id, "trigger", "must be 'manual', {cron: string}, or {on_event: string}")
85
+ }
86
+ } else {
87
+ throw new WorkflowParseError(id, "trigger", "must be 'manual', {cron: string}, or {on_event: string}")
88
+ }
89
+ }
90
+
91
+ // target
92
+ let target: string = "all"
93
+ if (obj.target !== undefined) {
94
+ if (typeof obj.target !== "string" || obj.target.trim() === "") {
95
+ throw new WorkflowParseError(id, "target", "must be 'all' or a non-empty string")
96
+ }
97
+ target = obj.target
98
+ }
99
+
100
+ // steps
101
+ if (!Array.isArray(obj.steps) || obj.steps.length === 0) {
102
+ throw new WorkflowParseError(id, "steps", "must be a non-empty array")
103
+ }
104
+
105
+ const steps: WorkflowStep[] = obj.steps.map((s: unknown, i: number) => {
106
+ if (typeof s !== "object" || s === null) {
107
+ throw new WorkflowParseError(id, `steps[${i}]`, "must be an object")
108
+ }
109
+ const step = s as Record<string, unknown>
110
+ if (typeof step.mcp_tool !== "string") {
111
+ throw new WorkflowParseError(id, `steps[${i}].mcp_tool`, "must be a string")
112
+ }
113
+ return {
114
+ mcp_tool: step.mcp_tool,
115
+ params: (typeof step.params === "object" && step.params !== null ? step.params : {}) as Record<string, unknown>,
116
+ ...(typeof step.label === "string" ? { label: step.label } : {}),
117
+ }
118
+ })
119
+
120
+ // on_failure
121
+ const validFailures = ["stop", "continue", "rollback"] as const
122
+ let onFailure: "stop" | "continue" | "rollback" = "stop"
123
+ if (obj.on_failure !== undefined) {
124
+ if (!validFailures.includes(obj.on_failure as typeof validFailures[number])) {
125
+ throw new WorkflowParseError(id, "on_failure", `must be one of: ${validFailures.join(", ")}`)
126
+ }
127
+ onFailure = obj.on_failure as "stop" | "continue" | "rollback"
128
+ }
129
+
130
+ return {
131
+ id,
132
+ name: obj.name,
133
+ trigger,
134
+ target,
135
+ steps,
136
+ on_failure: onFailure,
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,167 @@
1
+ import { describe, expect, test, afterEach } from "bun:test"
2
+ import { SessionIndex } from "./session-index"
3
+ import { EventStore } from "./event-store"
4
+ import { TranscriptSearchIndex } from "./transcript-search"
5
+ import { WorkspaceAgent } from "./workspace-agent"
6
+ import { createWorkspaceAgentRouter } from "./workspace-agent-routes"
7
+ import type { TranscriptEntry } from "../shared/types"
8
+ import type { StoreState, ChatRecord } from "./events"
9
+ import { mkdtemp, rm } from "node:fs/promises"
10
+ import { tmpdir } from "node:os"
11
+ import path from "node:path"
12
+
13
+ let tempDirs: string[] = []
14
+
15
+ function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
16
+ entry: T,
17
+ ): TranscriptEntry {
18
+ return { _id: crypto.randomUUID(), createdAt: Date.now(), ...entry } as TranscriptEntry
19
+ }
20
+
21
+ async function createIntegration() {
22
+ const dir = await mkdtemp(path.join(tmpdir(), "pa-integration-test-"))
23
+ tempDirs.push(dir)
24
+ const store = new EventStore(dir)
25
+ await store.initialize()
26
+ const sessions = new SessionIndex()
27
+ const search = new TranscriptSearchIndex()
28
+ const project = await store.openProject("/tmp/test-integration", "Integration Test")
29
+ const workspaceId = project.id
30
+ const agent = new WorkspaceAgent({ sessions, store, search, workspaceId })
31
+ const router = createWorkspaceAgentRouter(agent)
32
+ return { store, sessions, search, agent, router, workspaceId }
33
+ }
34
+
35
+ afterEach(async () => {
36
+ for (const d of tempDirs) await rm(d, { recursive: true, force: true })
37
+ tempDirs = []
38
+ })
39
+
40
+ function makeState(workspaceId: string): StoreState {
41
+ const workspacesById = new Map([[workspaceId, { id: workspaceId, localPath: "/tmp/p", title: "Test", createdAt: 0, updatedAt: 0 }]])
42
+ const workspaceIdsByPath = new Map<string, string>()
43
+ const chatsById = new Map<string, ChatRecord>([
44
+ ["c1", {
45
+ id: "c1", workspaceId, repoId: null, title: "Chat 1", createdAt: Date.now(), updatedAt: Date.now(),
46
+ unread: false, provider: "claude", planMode: false, sessionToken: null, lastTurnOutcome: null,
47
+ }],
48
+ ["c2", {
49
+ id: "c2", workspaceId, repoId: null, title: "Chat 2", createdAt: Date.now(), updatedAt: Date.now(),
50
+ unread: false, provider: "codex", planMode: false, sessionToken: null, lastTurnOutcome: null,
51
+ }],
52
+ ])
53
+ return { workspacesById, workspaceIdsByPath, independentWorkspacesById: new Map(), chatsById, queuedTurnsByChat: new Map(), coordinationByWorkspace: new Map(), agentConfigsByWorkspace: new Map(), reposById: new Map(), reposByPath: new Map(), workflowRunsByWorkspace: new Map(), sandboxByWorkspace: new Map(), providerProfiles: new Map(), workspaceProfileOverrides: new Map(), extensionPreferences: new Map(), teamMembers: new Map(), runnerLabels: new Map() }
54
+ }
55
+
56
+ describe("project agent integration", () => {
57
+ test("end-to-end: messages → indexes → query via HTTP routes", async () => {
58
+ const { sessions, search, router, workspaceId } = await createIntegration()
59
+ const state = makeState(workspaceId)
60
+
61
+ // Simulate two sessions sending messages
62
+ const e1 = timestamped({ kind: "user_prompt", content: "implement auth middleware with JWT" })
63
+ const e2 = timestamped({ kind: "user_prompt", content: "fix CSS styling on the sidebar component" })
64
+ sessions.onMessageAppended("c1", e1, state)
65
+ sessions.onMessageAppended("c2", e2, state)
66
+ search.addEntry("c1", e1)
67
+ search.addEntry("c2", e2)
68
+
69
+ // Query sessions via HTTP
70
+ const sessionsRes = await router(new Request(`http://localhost/api/workspace/sessions?workspaceId=${workspaceId}`))
71
+ const sessionsBody = await sessionsRes.json() as Array<Record<string, unknown>>
72
+ expect(sessionsRes.status).toBe(200)
73
+ expect(sessionsBody.length).toBe(2)
74
+
75
+ // Search transcripts via HTTP
76
+ const searchRes = await router(new Request("http://localhost/api/workspace/search", {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({ query: "auth JWT middleware", limit: 5 }),
80
+ }))
81
+ const searchBody = await searchRes.json() as Array<Record<string, unknown>>
82
+ expect(searchRes.status).toBe(200)
83
+ expect(searchBody.length).toBeGreaterThanOrEqual(1)
84
+ expect(searchBody[0].chatId).toBe("c1")
85
+
86
+ // Claim task via HTTP
87
+ const claimRes = await router(new Request("http://localhost/api/workspace/claim", {
88
+ method: "POST",
89
+ headers: { "Content-Type": "application/json" },
90
+ body: JSON.stringify({ description: "implement auth middleware", session: "c1", branch: "feat/auth" }),
91
+ }))
92
+ const claimBody = await claimRes.json() as Record<string, unknown>
93
+ expect(claimRes.status).toBe(200)
94
+ expect(claimBody.status).toBe("claimed")
95
+
96
+ // List tasks via HTTP
97
+ const tasksRes = await router(new Request("http://localhost/api/workspace/tasks"))
98
+ const tasksBody = await tasksRes.json() as Array<Record<string, unknown>>
99
+ expect(tasksBody.length).toBe(1)
100
+
101
+ // Delegate via HTTP
102
+ const delegateRes = await router(new Request("http://localhost/api/workspace/delegate", {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify({ request: "who is working on auth?" }),
106
+ }))
107
+ const delegateBody = await delegateRes.json() as Record<string, unknown>
108
+ expect(delegateBody.status).toBe("ok")
109
+ expect((delegateBody.message as string).toLowerCase()).toContain("auth")
110
+ })
111
+
112
+ test("tasks survive EventStore reload (durability)", async () => {
113
+ const dir = await mkdtemp(path.join(tmpdir(), "durable-"))
114
+ tempDirs.push(dir)
115
+
116
+ // First store instance: create a task
117
+ const store1 = new EventStore(dir)
118
+ await store1.initialize()
119
+ const project1 = await store1.openProject("/tmp/durable", "Durable Test")
120
+ const agent1 = new WorkspaceAgent({
121
+ sessions: new SessionIndex(),
122
+ store: store1,
123
+ search: new TranscriptSearchIndex(),
124
+ workspaceId: project1.id,
125
+ })
126
+ const task = await agent1.claimTask("durable task", "session-1", null)
127
+
128
+ // Second store instance: reload from same directory
129
+ const store2 = new EventStore(dir)
130
+ await store2.initialize()
131
+ const agent2 = new WorkspaceAgent({
132
+ sessions: new SessionIndex(),
133
+ store: store2,
134
+ search: new TranscriptSearchIndex(),
135
+ workspaceId: project1.id,
136
+ })
137
+
138
+ const tasks = agent2.listTasks()
139
+ expect(tasks.length).toBe(1)
140
+ expect(tasks[0].description).toBe("durable task")
141
+ expect(tasks[0].status).toBe("claimed")
142
+ expect(tasks[0].id).toBe(task.id)
143
+ })
144
+
145
+ test("complete task lifecycle via HTTP", async () => {
146
+ const { router } = await createIntegration()
147
+
148
+ // Claim
149
+ const claimRes = await router(new Request("http://localhost/api/workspace/claim", {
150
+ method: "POST",
151
+ headers: { "Content-Type": "application/json" },
152
+ body: JSON.stringify({ description: "setup database", session: "c1", branch: null }),
153
+ }))
154
+ const claimed = await claimRes.json() as Record<string, unknown>
155
+ expect(claimed.status).toBe("claimed")
156
+
157
+ // Complete
158
+ const completeRes = await router(new Request("http://localhost/api/workspace/complete", {
159
+ method: "POST",
160
+ headers: { "Content-Type": "application/json" },
161
+ body: JSON.stringify({ taskId: claimed.id, outputs: ["migrations/001.sql"] }),
162
+ }))
163
+ const completed = await completeRes.json() as Record<string, unknown>
164
+ expect(completed.status).toBe("complete")
165
+ expect(completed.outputs).toEqual(["migrations/001.sql"])
166
+ })
167
+ })
@@ -0,0 +1,127 @@
1
+ // src/server/project-agent-routes.test.ts
2
+ import { describe, expect, test, afterEach } from "bun:test"
3
+ import { createWorkspaceAgentRouter } from "./workspace-agent-routes"
4
+ import { SessionIndex } from "./session-index"
5
+ import { EventStore } from "./event-store"
6
+ import { TranscriptSearchIndex } from "./transcript-search"
7
+ import { WorkspaceAgent } from "./workspace-agent"
8
+ import { mkdtemp, rm } from "node:fs/promises"
9
+ import { tmpdir } from "node:os"
10
+ import path from "node:path"
11
+
12
+ let tempDirs: string[] = []
13
+
14
+ async function createRouter() {
15
+ const dir = await mkdtemp(path.join(tmpdir(), "pa-routes-test-"))
16
+ tempDirs.push(dir)
17
+ const store = new EventStore(dir)
18
+ await store.initialize()
19
+ const sessions = new SessionIndex()
20
+ const search = new TranscriptSearchIndex()
21
+ const project = await store.openProject("/tmp/test", "Test Project")
22
+ const workspaceId = project.id
23
+ const agent = new WorkspaceAgent({ sessions, store, search, workspaceId })
24
+ const router = createWorkspaceAgentRouter(agent)
25
+ return { router, agent, sessions, store, search, workspaceId }
26
+ }
27
+
28
+ afterEach(async () => {
29
+ for (const d of tempDirs) await rm(d, { recursive: true, force: true })
30
+ tempDirs = []
31
+ })
32
+
33
+ describe("project-agent-routes", () => {
34
+ test("GET /api/workspace/sessions returns JSON array", async () => {
35
+ const { router, workspaceId } = await createRouter()
36
+ const req = new Request(`http://localhost/api/workspace/sessions?workspaceId=${workspaceId}`)
37
+ const res = await router(req)
38
+ expect(res.status).toBe(200)
39
+ const body = await res.json()
40
+ expect(Array.isArray(body)).toBe(true)
41
+ })
42
+
43
+ test("POST /api/workspace/search returns results", async () => {
44
+ const { router, search } = await createRouter()
45
+ search.addEntry("c1", { _id: "1", createdAt: Date.now(), kind: "user_prompt", content: "auth setup" } as never)
46
+
47
+ const req = new Request("http://localhost/api/workspace/search", {
48
+ method: "POST",
49
+ headers: { "Content-Type": "application/json" },
50
+ body: JSON.stringify({ query: "auth", limit: 10 }),
51
+ })
52
+ const res = await router(req)
53
+ expect(res.status).toBe(200)
54
+ const body = await res.json()
55
+ expect(Array.isArray(body)).toBe(true)
56
+ })
57
+
58
+ test("GET /api/workspace/tasks returns task list", async () => {
59
+ const { router, agent } = await createRouter()
60
+ await agent.claimTask("test task", "c1", null)
61
+
62
+ const req = new Request("http://localhost/api/workspace/tasks")
63
+ const res = await router(req)
64
+ expect(res.status).toBe(200)
65
+ const body = await res.json()
66
+ expect(body.length).toBe(1)
67
+ })
68
+
69
+ test("POST /api/workspace/claim creates a task", async () => {
70
+ const { router } = await createRouter()
71
+ const req = new Request("http://localhost/api/workspace/claim", {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify({ description: "implement auth", session: "c1", branch: "feat/auth" }),
75
+ })
76
+ const res = await router(req)
77
+ expect(res.status).toBe(200)
78
+ const body = await res.json()
79
+ expect(body.status).toBe("claimed")
80
+ })
81
+
82
+ test("POST /api/workspace/complete marks task done", async () => {
83
+ const { router, agent } = await createRouter()
84
+ const task = await agent.claimTask("task", "c1", null)
85
+
86
+ const req = new Request("http://localhost/api/workspace/complete", {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/json" },
89
+ body: JSON.stringify({ taskId: task.id, outputs: ["file.ts"] }),
90
+ })
91
+ const res = await router(req)
92
+ expect(res.status).toBe(200)
93
+ const body = await res.json()
94
+ expect(body.status).toBe("complete")
95
+ })
96
+
97
+ test("POST /api/workspace/delegate returns delegation result", async () => {
98
+ const { router } = await createRouter()
99
+ const req = new Request("http://localhost/api/workspace/delegate", {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify({ request: "what is going on?" }),
103
+ })
104
+ const res = await router(req)
105
+ expect(res.status).toBe(200)
106
+ const body = await res.json()
107
+ expect(body.status).toBe("ok")
108
+ })
109
+
110
+ test("returns 404 for unknown routes", async () => {
111
+ const { router } = await createRouter()
112
+ const req = new Request("http://localhost/api/workspace/nonexistent")
113
+ const res = await router(req)
114
+ expect(res.status).toBe(404)
115
+ })
116
+
117
+ test("returns 400 for missing required fields", async () => {
118
+ const { router } = await createRouter()
119
+ const req = new Request("http://localhost/api/workspace/claim", {
120
+ method: "POST",
121
+ headers: { "Content-Type": "application/json" },
122
+ body: JSON.stringify({}),
123
+ })
124
+ const res = await router(req)
125
+ expect(res.status).toBe(400)
126
+ })
127
+ })
@@ -0,0 +1,89 @@
1
+ // src/server/project-agent-routes.ts
2
+ import { LOG_PREFIX } from "../shared/branding"
3
+ import type { WorkspaceAgent } from "./workspace-agent"
4
+
5
+ function jsonResponse(data: unknown, status = 200): Response {
6
+ return new Response(JSON.stringify(data), {
7
+ status,
8
+ headers: { "Content-Type": "application/json" },
9
+ })
10
+ }
11
+
12
+ function errorResponse(error: string, code: number, detail?: string): Response {
13
+ return jsonResponse({ error, code, detail }, code)
14
+ }
15
+
16
+ async function readBody(req: Request): Promise<Record<string, unknown>> {
17
+ try {
18
+ return (await req.json()) as Record<string, unknown>
19
+ } catch (err: unknown) {
20
+ const message = err instanceof Error ? err.message : String(err)
21
+ console.warn(`${LOG_PREFIX} readBody: invalid JSON — ${message}`)
22
+ return {}
23
+ }
24
+ }
25
+
26
+ export function createWorkspaceAgentRouter(agent: WorkspaceAgent): (req: Request) => Promise<Response> {
27
+ return async (req: Request): Promise<Response> => {
28
+ const url = new URL(req.url)
29
+ const path = url.pathname.replace(/^\/api\/workspace/, "")
30
+
31
+ if (req.method === "GET" && path === "/sessions") {
32
+ const workspaceId = url.searchParams.get("workspaceId") ?? ""
33
+ return jsonResponse(agent.querySessions(workspaceId))
34
+ }
35
+
36
+ if (req.method === "GET" && path.startsWith("/sessions/")) {
37
+ const chatId = path.replace("/sessions/", "")
38
+ const session = agent.getSessionSummary(chatId)
39
+ return session ? jsonResponse(session) : errorResponse("Session not found", 404)
40
+ }
41
+
42
+ if (req.method === "POST" && path === "/search") {
43
+ const body = await readBody(req)
44
+ const query = body.query as string | undefined
45
+ if (!query) return errorResponse("Missing 'query'", 400)
46
+ const rawLimit = body.limit
47
+ const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? rawLimit : 10
48
+ return jsonResponse(agent.searchWork(query, limit))
49
+ }
50
+
51
+ if (req.method === "GET" && path === "/tasks") {
52
+ return jsonResponse(agent.listTasks())
53
+ }
54
+
55
+ if (req.method === "GET" && path.startsWith("/tasks/")) {
56
+ const taskId = path.replace("/tasks/", "")
57
+ const task = agent.getTask(taskId)
58
+ return task ? jsonResponse(task) : errorResponse("Task not found", 404)
59
+ }
60
+
61
+ if (req.method === "POST" && path === "/claim") {
62
+ const body = await readBody(req)
63
+ const description = body.description as string | undefined
64
+ const session = body.session as string | undefined
65
+ if (!description || !session) return errorResponse("Missing 'description' or 'session'", 400)
66
+ const branch = (body.branch as string) ?? null
67
+ return jsonResponse(await agent.claimTask(description, session, branch))
68
+ }
69
+
70
+ if (req.method === "POST" && path === "/complete") {
71
+ const body = await readBody(req)
72
+ const taskId = body.taskId as string | undefined
73
+ if (!taskId) return errorResponse("Missing 'taskId'", 400)
74
+ const outputs = Array.isArray(body.outputs) ? (body.outputs as string[]) : []
75
+ const task = await agent.completeTask(taskId, outputs)
76
+ return task ? jsonResponse(task) : errorResponse("Task not found", 404)
77
+ }
78
+
79
+ if (req.method === "POST" && path === "/delegate") {
80
+ const body = await readBody(req)
81
+ const request = body.request as string | undefined
82
+ if (!request) return errorResponse("Missing 'request'", 400)
83
+ const result = await agent.delegate(request)
84
+ return jsonResponse(result)
85
+ }
86
+
87
+ return errorResponse("Not found", 404)
88
+ }
89
+ }