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,454 @@
1
+ import { Kvm, type KV } from "@nats-io/kv"
2
+ import type { NatsConnection } from "@nats-io/nats-core"
3
+ import type {
4
+ AgentProvider,
5
+ DelegationMode,
6
+ DelegationResumeMode,
7
+ DelegationStatus,
8
+ DurableDelegation,
9
+ AgentResultEntry,
10
+ TranscriptEntry,
11
+ } from "../shared/types"
12
+ import { LOG_PREFIX } from "../shared/branding"
13
+ import { toTranscriptLine } from "./transcript-utils"
14
+
15
+ const DELEGATIONS_BUCKET = "delegations"
16
+ const MAX_DELEGATION_DEPTH = 2
17
+ const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000 // 24h
18
+
19
+ const MAX_RESUME_HINT_ENTRIES = 24
20
+ const MAX_RESUME_HINT_CHARS = 12_000
21
+ const MAX_RESUME_HINT_LINE_CHARS = 600
22
+
23
+ const encoder = new TextEncoder()
24
+ const decoder = new TextDecoder()
25
+
26
+ function primaryKey(workspaceId: string, delegationId: string): string {
27
+ return `delegation.${workspaceId}.${delegationId}`
28
+ }
29
+
30
+ function secondaryKey(workspaceId: string, childChatId: string, delegationId: string): string {
31
+ return `delegation_by_child.${workspaceId}.${childChatId}.${delegationId}`
32
+ }
33
+
34
+ function secondaryPrefix(workspaceId: string, childChatId: string): string {
35
+ return `delegation_by_child.${workspaceId}.${childChatId}.`
36
+ }
37
+
38
+ function primaryPrefix(workspaceId: string): string {
39
+ return `delegation.${workspaceId}.`
40
+ }
41
+
42
+ function encode(data: unknown): Uint8Array {
43
+ return encoder.encode(JSON.stringify(data))
44
+ }
45
+
46
+ function decode<T>(data: Uint8Array): T {
47
+ return JSON.parse(decoder.decode(data)) as T
48
+ }
49
+
50
+ /** Iterate all KV keys matching a dot-prefix (avoids NATS wildcard filter quirks). */
51
+ async function* keysWithPrefix(kv: KV, prefix: string): AsyncIterable<string> {
52
+ const iter = await kv.keys()
53
+ for await (const key of iter) {
54
+ if (key.startsWith(prefix)) yield key
55
+ }
56
+ }
57
+
58
+ export interface DelegationStore {
59
+ appendMessage(chatId: string, entry: TranscriptEntry): void
60
+ chatExists(chatId: string): boolean
61
+ getChatWorkspaceId(chatId: string): string | undefined
62
+ getLastTurnOutcome(chatId: string): string | undefined
63
+ }
64
+
65
+ export interface CreateDelegationArgs {
66
+ workspaceId: string
67
+ parentChatId: string
68
+ childChatId: string
69
+ childProvider: AgentProvider
70
+ instructionPreview: string
71
+ mode: DelegationMode
72
+ resume: DelegationResumeMode
73
+ depth: number
74
+ resumeHint?: string
75
+ }
76
+
77
+ export interface TerminalOutcome {
78
+ outcome: "success" | "failed" | "cancelled"
79
+ resultSummary?: string
80
+ }
81
+
82
+ export type ReconcileResult =
83
+ | { delegationId: string; parentChatId: string; injectedEntryId: string; resumeEligible: boolean }
84
+ | { alreadyReconciled: true }
85
+
86
+ export class DelegationCoordinator {
87
+ private kv: KV | null = null
88
+ private readonly nc: NatsConnection
89
+ private readonly store: DelegationStore
90
+ /** Sync cache: parentChatId → count of active blocking delegations. */
91
+ private readonly activeBlockingCount = new Map<string, number>()
92
+
93
+ constructor(nc: NatsConnection, store: DelegationStore) {
94
+ this.nc = nc
95
+ this.store = store
96
+ }
97
+
98
+ async initialize(): Promise<void> {
99
+ const kvm = new Kvm(this.nc)
100
+ this.kv = await kvm.create(DELEGATIONS_BUCKET)
101
+ console.warn(LOG_PREFIX, `KV bucket "${DELEGATIONS_BUCKET}" ready`)
102
+ }
103
+
104
+ private requireKv(): KV {
105
+ if (!this.kv) throw new Error("DelegationCoordinator not initialized")
106
+ return this.kv
107
+ }
108
+
109
+ async createDelegation(args: CreateDelegationArgs): Promise<{ delegationId: string }> {
110
+ if (args.depth > MAX_DELEGATION_DEPTH) {
111
+ throw new Error(`Delegation depth ${args.depth} exceeds maximum ${MAX_DELEGATION_DEPTH}`)
112
+ }
113
+
114
+ const kv = this.requireKv()
115
+ const delegationId = crypto.randomUUID()
116
+ const now = Date.now()
117
+
118
+ const record: DurableDelegation = {
119
+ delegationId,
120
+ workspaceId: args.workspaceId,
121
+ parentChatId: args.parentChatId,
122
+ childChatId: args.childChatId,
123
+ childProvider: args.childProvider,
124
+ instructionPreview: args.instructionPreview.slice(0, 120),
125
+ mode: args.mode,
126
+ resume: args.resume,
127
+ status: "active",
128
+ depth: args.depth,
129
+ resumeHint: args.resumeHint,
130
+ createdAt: now,
131
+ updatedAt: now,
132
+ }
133
+
134
+ // Primary first, secondary second (ordering guarantee per ADR)
135
+ await kv.put(primaryKey(args.workspaceId, delegationId), encode(record))
136
+ await kv.put(
137
+ secondaryKey(args.workspaceId, args.childChatId, delegationId),
138
+ encode({ delegationId, parentChatId: args.parentChatId }),
139
+ )
140
+
141
+ // Update sync cache
142
+ if (args.mode === "blocking") {
143
+ this.activeBlockingCount.set(
144
+ args.parentChatId,
145
+ (this.activeBlockingCount.get(args.parentChatId) ?? 0) + 1,
146
+ )
147
+ }
148
+
149
+ return { delegationId }
150
+ }
151
+
152
+ async getDelegation(workspaceId: string, delegationId: string): Promise<DurableDelegation | null> {
153
+ const kv = this.requireKv()
154
+ try {
155
+ const entry = await kv.get(primaryKey(workspaceId, delegationId))
156
+ if (!entry?.value) return null
157
+ return decode<DurableDelegation>(entry.value)
158
+ } catch (_err) {
159
+ return null
160
+ }
161
+ }
162
+
163
+ async getDelegationsForChild(workspaceId: string, childChatId: string): Promise<DurableDelegation[]> {
164
+ const kv = this.requireKv()
165
+ const prefix = secondaryPrefix(workspaceId, childChatId)
166
+ const results: DurableDelegation[] = []
167
+
168
+ for await (const key of keysWithPrefix(kv, prefix)) {
169
+ const idxEntry = await kv.get(key)
170
+ if (!idxEntry?.value) continue
171
+ const idx = decode<{ delegationId: string; parentChatId: string }>(idxEntry.value)
172
+ const record = await this.getDelegation(workspaceId, idx.delegationId)
173
+ if (record) results.push(record)
174
+ }
175
+
176
+ return results
177
+ }
178
+
179
+ async getBlockingDelegationsForParent(workspaceId: string, parentChatId: string): Promise<DurableDelegation[]> {
180
+ const kv = this.requireKv()
181
+ const prefix = primaryPrefix(workspaceId)
182
+ const results: DurableDelegation[] = []
183
+
184
+ for await (const key of keysWithPrefix(kv, prefix)) {
185
+ const entry = await kv.get(key)
186
+ if (!entry?.value) continue
187
+ const record = decode<DurableDelegation>(entry.value)
188
+ if (record.parentChatId === parentChatId && record.mode === "blocking" && record.status === "active") {
189
+ results.push(record)
190
+ }
191
+ }
192
+
193
+ return results
194
+ }
195
+
196
+ hasActiveBlockingDelegations(chatId: string): boolean {
197
+ return (this.activeBlockingCount.get(chatId) ?? 0) > 0
198
+ }
199
+
200
+ async reconcileChildTerminal(
201
+ workspaceId: string,
202
+ childChatId: string,
203
+ outcome: TerminalOutcome,
204
+ ): Promise<ReconcileResult | null> {
205
+ const delegations = await this.getDelegationsForChild(workspaceId, childChatId)
206
+ if (delegations.length === 0) return null
207
+
208
+ // Process first active delegation for this child
209
+ const delegation = delegations.find((d) => d.status === "active" || d.status === "completing")
210
+ if (!delegation) return { alreadyReconciled: true }
211
+
212
+ if (delegation.status === "completed" || delegation.status === "failed" || delegation.status === "orphaned" || delegation.status === "stale") {
213
+ return { alreadyReconciled: true }
214
+ }
215
+
216
+ const kv = this.requireKv()
217
+ const key = primaryKey(workspaceId, delegation.delegationId)
218
+
219
+ // CAS transition: active → completing
220
+ const currentEntry = await kv.get(key)
221
+ if (!currentEntry?.value) return null
222
+
223
+ const current = decode<DurableDelegation>(currentEntry.value)
224
+ if (current.status !== "active") {
225
+ return { alreadyReconciled: true }
226
+ }
227
+
228
+ const isError = outcome.outcome === "failed" || outcome.outcome === "cancelled"
229
+ const now = Date.now()
230
+ const entryId = crypto.randomUUID()
231
+
232
+ // Build agent_result entry
233
+ const agentResultEntry: AgentResultEntry = {
234
+ _id: entryId,
235
+ createdAt: now,
236
+ kind: "agent_result",
237
+ delegationId: delegation.delegationId,
238
+ childChatId,
239
+ childProvider: delegation.childProvider,
240
+ mode: delegation.mode,
241
+ instructionPreview: delegation.instructionPreview,
242
+ resumeHint: delegation.resumeHint,
243
+ resultSummary: outcome.resultSummary ?? (isError ? "Agent failed" : "Agent completed"),
244
+ isError,
245
+ completedAt: now,
246
+ }
247
+
248
+ // CAS update: active → completed (combining completing step for simplicity)
249
+ const updatedRecord: DurableDelegation = {
250
+ ...current,
251
+ status: isError ? "failed" : "completed",
252
+ resultSummary: agentResultEntry.resultSummary,
253
+ isError,
254
+ agentResultEntryId: entryId,
255
+ updatedAt: now,
256
+ }
257
+
258
+ try {
259
+ await kv.update(key, encode(updatedRecord), currentEntry.revision)
260
+ } catch (_casConflict) {
261
+ // CAS conflict — another reconciliation already handled this
262
+ return { alreadyReconciled: true }
263
+ }
264
+
265
+ // Update sync cache for blocking delegations
266
+ if (delegation.mode === "blocking") {
267
+ const prev = this.activeBlockingCount.get(delegation.parentChatId) ?? 0
268
+ if (prev <= 1) {
269
+ this.activeBlockingCount.delete(delegation.parentChatId)
270
+ } else {
271
+ this.activeBlockingCount.set(delegation.parentChatId, prev - 1)
272
+ }
273
+ }
274
+
275
+ // Inject agent_result into parent transcript
276
+ this.store.appendMessage(delegation.parentChatId, agentResultEntry)
277
+
278
+ // Determine resume eligibility
279
+ const resumeEligible = await this.checkResumeEligibility(delegation)
280
+
281
+ return {
282
+ delegationId: delegation.delegationId,
283
+ parentChatId: delegation.parentChatId,
284
+ injectedEntryId: entryId,
285
+ resumeEligible,
286
+ }
287
+ }
288
+
289
+ private async checkResumeEligibility(delegation: DurableDelegation): Promise<boolean> {
290
+ if (delegation.mode === "background") return false
291
+
292
+ if (delegation.resume === "immediate") return true
293
+
294
+ // gate mode: check all blocking delegations for this parent
295
+ const siblings = await this.getBlockingDelegationsForParent(delegation.workspaceId, delegation.parentChatId)
296
+ // If no active blocking siblings remain, all are terminal → eligible
297
+ return siblings.length === 0
298
+ }
299
+
300
+ generateResumeHint(entries: TranscriptEntry[]): string | undefined {
301
+ const relevantEntries = entries
302
+ .map((entry) => toTranscriptLine(entry, MAX_RESUME_HINT_LINE_CHARS))
303
+ .filter((line): line is string => Boolean(line))
304
+
305
+ if (relevantEntries.length === 0) return undefined
306
+
307
+ const selected = relevantEntries.slice(-MAX_RESUME_HINT_ENTRIES)
308
+ const omittedCount = relevantEntries.length - selected.length
309
+ const headerLines = [
310
+ "Delegation context from parent chat:",
311
+ "This is background context from before the delegation. Use it to continue your work.",
312
+ ]
313
+ if (omittedCount > 0) {
314
+ headerLines.push(`Older transcript lines omitted: ${omittedCount}.`)
315
+ }
316
+
317
+ const lines = [...headerLines, ...selected]
318
+ let serialized = lines.join("\n")
319
+
320
+ if (serialized.length <= MAX_RESUME_HINT_CHARS) {
321
+ return serialized
322
+ }
323
+
324
+ // Trim from the front to stay within budget
325
+ const trimmedSelected: string[] = []
326
+ let remaining = MAX_RESUME_HINT_CHARS - headerLines.join("\n").length - 1
327
+ for (let index = selected.length - 1; index >= 0; index -= 1) {
328
+ const line = selected[index]!
329
+ const cost = line.length + 1
330
+ if (remaining - cost < 0) break
331
+ trimmedSelected.unshift(line)
332
+ remaining -= cost
333
+ }
334
+
335
+ if (trimmedSelected.length === 0) {
336
+ return headerLines.join("\n")
337
+ }
338
+
339
+ return [...headerLines, ...trimmedSelected].join("\n")
340
+ }
341
+
342
+ async bootReconciliation(): Promise<void> {
343
+ const kv = this.requireKv()
344
+ const prefix = "delegation."
345
+ const now = Date.now()
346
+ const secondaryKeysToRebuild: Array<{ wsId: string; childId: string; delId: string }> = []
347
+
348
+ for await (const key of keysWithPrefix(kv, prefix)) {
349
+ // Skip secondary index keys
350
+ if (key.startsWith("delegation_by_child.")) continue
351
+
352
+ const entry = await kv.get(key)
353
+ if (!entry?.value) continue
354
+
355
+ const record = decode<DurableDelegation>(entry.value)
356
+ if (record.status === "completed" || record.status === "failed" || record.status === "orphaned" || record.status === "stale") {
357
+ continue
358
+ }
359
+
360
+ let newStatus: DelegationStatus | null = null
361
+
362
+ // Check parent existence
363
+ if (!this.store.chatExists(record.parentChatId)) {
364
+ newStatus = "orphaned"
365
+ }
366
+ // Check child existence
367
+ else if (!this.store.chatExists(record.childChatId)) {
368
+ newStatus = "orphaned"
369
+ }
370
+ // Check stuck completing state
371
+ else if (record.status === "completing") {
372
+ if (record.agentResultEntryId) {
373
+ newStatus = "completed"
374
+ } else {
375
+ newStatus = "failed"
376
+ }
377
+ }
378
+ // Check child already terminal
379
+ else if (record.status === "active") {
380
+ const childOutcome = this.store.getLastTurnOutcome(record.childChatId)
381
+ if (childOutcome === "success") {
382
+ // Complete + inject
383
+ const entryId = crypto.randomUUID()
384
+ const agentResultEntry: AgentResultEntry = {
385
+ _id: entryId,
386
+ createdAt: now,
387
+ kind: "agent_result",
388
+ delegationId: record.delegationId,
389
+ childChatId: record.childChatId,
390
+ childProvider: record.childProvider,
391
+ mode: record.mode,
392
+ instructionPreview: record.instructionPreview,
393
+ resumeHint: record.resumeHint,
394
+ resultSummary: "Agent completed (recovered on boot)",
395
+ isError: false,
396
+ completedAt: now,
397
+ }
398
+ this.store.appendMessage(record.parentChatId, agentResultEntry)
399
+ newStatus = "completed"
400
+ } else if (childOutcome === "failed") {
401
+ newStatus = "failed"
402
+ } else if (now - record.createdAt > STALE_THRESHOLD_MS) {
403
+ newStatus = "stale"
404
+ console.warn(LOG_PREFIX, `Marking delegation ${record.delegationId} as stale (age: ${Math.round((now - record.createdAt) / 3600000)}h)`)
405
+ }
406
+ // else: child still active, keep as-is
407
+ }
408
+
409
+ if (newStatus) {
410
+ const updated: DurableDelegation = {
411
+ ...record,
412
+ status: newStatus,
413
+ updatedAt: now,
414
+ }
415
+ try {
416
+ await kv.update(key, encode(updated), entry.revision)
417
+ } catch (_casConflict) {
418
+ console.warn(LOG_PREFIX, `CAS conflict during boot reconciliation for delegation ${record.delegationId}`)
419
+ }
420
+ }
421
+
422
+ // Track for secondary index rebuild
423
+ secondaryKeysToRebuild.push({
424
+ wsId: record.workspaceId,
425
+ childId: record.childChatId,
426
+ delId: record.delegationId,
427
+ })
428
+ }
429
+
430
+ // Rebuild secondary indexes (idempotent upsert — simpler than checking tombstones)
431
+ for (const { wsId, childId, delId } of secondaryKeysToRebuild) {
432
+ const secKey = secondaryKey(wsId, childId, delId)
433
+ const record = await this.getDelegation(wsId, delId)
434
+ if (record) {
435
+ await kv.put(secKey, encode({ delegationId: delId, parentChatId: record.parentChatId }))
436
+ }
437
+ }
438
+
439
+ // Rebuild activeBlockingCount cache from final KV state
440
+ this.activeBlockingCount.clear()
441
+ for await (const key of keysWithPrefix(kv, prefix)) {
442
+ if (key.startsWith("delegation_by_child.")) continue
443
+ const entry = await kv.get(key)
444
+ if (!entry?.value) continue
445
+ const record = decode<DurableDelegation>(entry.value)
446
+ if (record.status === "active" && record.mode === "blocking") {
447
+ this.activeBlockingCount.set(
448
+ record.parentChatId,
449
+ (this.activeBlockingCount.get(record.parentChatId) ?? 0) + 1,
450
+ )
451
+ }
452
+ }
453
+ }
454
+ }
@@ -0,0 +1,211 @@
1
+ import { afterEach, describe, expect, test } from "bun:test"
2
+ import { mkdtempSync, mkdirSync, rmSync, utimesSync, writeFileSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import {
6
+ ClaudeProjectDiscoveryAdapter,
7
+ CodexProjectDiscoveryAdapter,
8
+ discoverProjects,
9
+ type ProjectDiscoveryAdapter,
10
+ } from "./discovery"
11
+
12
+ const tempDirs: string[] = []
13
+
14
+ function makeTempDir() {
15
+ const directory = mkdtempSync(path.join(tmpdir(), "kanna-discovery-"))
16
+ tempDirs.push(directory)
17
+ return directory
18
+ }
19
+
20
+ function encodeClaudeProjectPath(localPath: string) {
21
+ return `-${localPath.replace(/\//g, "-")}`
22
+ }
23
+
24
+ afterEach(() => {
25
+ for (const directory of tempDirs.splice(0)) {
26
+ rmSync(directory, { recursive: true, force: true })
27
+ }
28
+ })
29
+
30
+ describe("project discovery", () => {
31
+ test("Claude adapter decodes saved project paths", () => {
32
+ const homeDir = makeTempDir()
33
+ const projectDir = path.join(homeDir, "workspace", "alpha-project")
34
+ const claudeProjectsDir = path.join(homeDir, ".claude", "projects")
35
+ const projectMarkerDir = path.join(claudeProjectsDir, encodeClaudeProjectPath(projectDir))
36
+
37
+ mkdirSync(projectDir, { recursive: true })
38
+ mkdirSync(projectMarkerDir, { recursive: true })
39
+ utimesSync(projectMarkerDir, new Date("2026-03-16T10:00:00.000Z"), new Date("2026-03-16T10:00:00.000Z"))
40
+
41
+ const projects = new ClaudeProjectDiscoveryAdapter().scan(homeDir)
42
+
43
+ expect(projects).toEqual([
44
+ {
45
+ provider: "claude",
46
+ localPath: projectDir,
47
+ title: "alpha-project",
48
+ modifiedAt: new Date("2026-03-16T10:00:00.000Z").getTime(),
49
+ },
50
+ ])
51
+ })
52
+
53
+ test("Codex adapter reads cwd from session metadata and ignores stale or invalid entries", () => {
54
+ const homeDir = makeTempDir()
55
+ const sessionsDir = path.join(homeDir, ".codex", "sessions", "2026", "03", "16")
56
+ const liveProjectDir = path.join(homeDir, "workspace", "kanna")
57
+ const missingProjectDir = path.join(homeDir, "workspace", "missing-project")
58
+ mkdirSync(liveProjectDir, { recursive: true })
59
+ mkdirSync(sessionsDir, { recursive: true })
60
+
61
+ writeFileSync(path.join(homeDir, ".codex", "session_index.jsonl"), [
62
+ JSON.stringify({
63
+ id: "session-live",
64
+ updated_at: "2026-03-16T23:05:58.940134Z",
65
+ }),
66
+ JSON.stringify({
67
+ id: "session-missing",
68
+ updated_at: "2026-03-16T20:05:58.940134Z",
69
+ }),
70
+ JSON.stringify({
71
+ id: "session-relative",
72
+ updated_at: "2026-03-16T21:05:58.940134Z",
73
+ }),
74
+ ].join("\n"))
75
+
76
+ writeFileSync(path.join(sessionsDir, "rollout-2026-03-16T23-05-52-session-live.jsonl"), [
77
+ JSON.stringify({
78
+ timestamp: "2026-03-16T23:05:52.000Z",
79
+ type: "session_meta",
80
+ payload: {
81
+ id: "session-live",
82
+ cwd: liveProjectDir,
83
+ },
84
+ }),
85
+ ].join("\n"))
86
+
87
+ writeFileSync(path.join(sessionsDir, "rollout-2026-03-16T20-05-52-session-missing.jsonl"), [
88
+ JSON.stringify({
89
+ timestamp: "2026-03-16T20:05:52.000Z",
90
+ type: "session_meta",
91
+ payload: {
92
+ id: "session-missing",
93
+ cwd: missingProjectDir,
94
+ },
95
+ }),
96
+ ].join("\n"))
97
+
98
+ writeFileSync(path.join(sessionsDir, "rollout-2026-03-16T21-05-52-session-relative.jsonl"), [
99
+ JSON.stringify({
100
+ timestamp: "2026-03-16T21:05:52.000Z",
101
+ type: "session_meta",
102
+ payload: {
103
+ id: "session-relative",
104
+ cwd: "./relative-path",
105
+ },
106
+ }),
107
+ ].join("\n"))
108
+
109
+ const projects = new CodexProjectDiscoveryAdapter().scan(homeDir)
110
+
111
+ expect(projects).toEqual([
112
+ {
113
+ provider: "codex",
114
+ localPath: liveProjectDir,
115
+ title: "kanna",
116
+ modifiedAt: Date.parse("2026-03-16T23:05:58.940134Z"),
117
+ },
118
+ ])
119
+ })
120
+
121
+ test("Codex adapter falls back to session timestamps and config projects when session index misses CLI entries", () => {
122
+ const homeDir = makeTempDir()
123
+ const sessionsDir = path.join(homeDir, ".codex", "sessions", "2026", "03", "16")
124
+ const cliProjectDir = path.join(homeDir, "workspace", "codex-test-2")
125
+ const configOnlyProjectDir = path.join(homeDir, "workspace", "config-only")
126
+ mkdirSync(cliProjectDir, { recursive: true })
127
+ mkdirSync(configOnlyProjectDir, { recursive: true })
128
+ mkdirSync(sessionsDir, { recursive: true })
129
+
130
+ writeFileSync(path.join(homeDir, ".codex", "session_index.jsonl"), "")
131
+ writeFileSync(path.join(homeDir, ".codex", "config.toml"), [
132
+ `personality = "pragmatic"`,
133
+ `[projects."${configOnlyProjectDir}"]`,
134
+ `trust_level = "trusted"`,
135
+ ].join("\n"))
136
+
137
+ writeFileSync(path.join(sessionsDir, "rollout-2026-03-16T23-42-24-cli-session.jsonl"), [
138
+ JSON.stringify({
139
+ timestamp: "2026-03-17T03:42:25.751Z",
140
+ type: "session_meta",
141
+ payload: {
142
+ id: "cli-session",
143
+ timestamp: "2026-03-17T03:42:24.578Z",
144
+ cwd: cliProjectDir,
145
+ originator: "codex-tui",
146
+ source: "cli",
147
+ },
148
+ }),
149
+ ].join("\n"))
150
+
151
+ const projects = new CodexProjectDiscoveryAdapter().scan(homeDir)
152
+
153
+ expect(projects.map((project) => project.localPath).sort()).toEqual([
154
+ cliProjectDir,
155
+ configOnlyProjectDir,
156
+ ].sort())
157
+ expect(projects.find((project) => project.localPath === cliProjectDir)?.modifiedAt).toBe(
158
+ Date.parse("2026-03-17T03:42:25.751Z")
159
+ )
160
+ })
161
+
162
+ test("discoverProjects de-dupes provider results by normalized path and keeps the newest timestamp", () => {
163
+ const adapters: ProjectDiscoveryAdapter[] = [
164
+ {
165
+ provider: "claude",
166
+ scan() {
167
+ return [
168
+ {
169
+ provider: "claude",
170
+ localPath: "/tmp/project",
171
+ title: "Claude Project",
172
+ modifiedAt: 10,
173
+ },
174
+ ]
175
+ },
176
+ },
177
+ {
178
+ provider: "codex",
179
+ scan() {
180
+ return [
181
+ {
182
+ provider: "codex",
183
+ localPath: "/tmp/project",
184
+ title: "Codex Project",
185
+ modifiedAt: 20,
186
+ },
187
+ {
188
+ provider: "codex",
189
+ localPath: "/tmp/other-project",
190
+ title: "Other Project",
191
+ modifiedAt: 15,
192
+ },
193
+ ]
194
+ },
195
+ },
196
+ ]
197
+
198
+ expect(discoverProjects("/unused-home", adapters)).toEqual([
199
+ {
200
+ localPath: "/tmp/project",
201
+ title: "Codex Project",
202
+ modifiedAt: 20,
203
+ },
204
+ {
205
+ localPath: "/tmp/other-project",
206
+ title: "Other Project",
207
+ modifiedAt: 15,
208
+ },
209
+ ])
210
+ })
211
+ })