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,129 @@
1
+ import { afterEach, describe, expect, test } from "bun:test"
2
+ import { mkdir, writeFile, rm } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { homedir } from "node:os"
5
+ import { findSessionFile, inspectSessionRuntime, readSessionTranscript } from "./session-discovery"
6
+
7
+ const tempDirs: string[] = []
8
+
9
+ afterEach(async () => {
10
+ await Promise.all(
11
+ tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))
12
+ )
13
+ })
14
+
15
+
16
+ describe("findSessionFile", () => {
17
+ test("returns null when claude session file does not exist", async () => {
18
+ const result = await findSessionFile("nonexistent-session-id", "claude", "/nonexistent/path")
19
+ expect(result).toBeNull()
20
+ })
21
+
22
+ test("returns null when codex sessions directory does not exist", async () => {
23
+ const result = await findSessionFile("nonexistent-session-id", "codex", "/nonexistent/path")
24
+ expect(result).toBeNull()
25
+ })
26
+
27
+ test("finds a Claude session file at the encoded project dir path", async () => {
28
+ const sessionId = "find-claude-test-session"
29
+ const workspacePath = `/tmp/find-claude-test-${Date.now()}`
30
+
31
+ // encodeClaudeProjectDir uses homedir() which Bun doesn't override via process.env.HOME
32
+ // so we write directly to the real encoded path
33
+ const claudeProjectDir = join(homedir(), ".claude", "projects", workspacePath.replace(/\//g, "-"))
34
+ await mkdir(claudeProjectDir, { recursive: true })
35
+ tempDirs.push(claudeProjectDir)
36
+
37
+ const sessionFile = join(claudeProjectDir, `${sessionId}.jsonl`)
38
+ await writeFile(sessionFile, JSON.stringify({ type: "user", message: { content: "hello" } }) + "\n")
39
+
40
+ const result = await findSessionFile(sessionId, "claude", workspacePath)
41
+ expect(result).toBe(sessionFile)
42
+ })
43
+ })
44
+
45
+ describe("inspectSessionRuntime", () => {
46
+ test("returns null when session file does not exist", async () => {
47
+ const result = await inspectSessionRuntime("nonexistent-session-id", "claude", "/nonexistent/path")
48
+ expect(result).toBeNull()
49
+ })
50
+
51
+ test("extracts Claude runtime from session file", async () => {
52
+ const sessionId = `inspect-claude-${Date.now()}`
53
+ const workspacePath = `/tmp/inspect-claude-test-${Date.now()}`
54
+
55
+ const claudeProjectDir = join(homedir(), ".claude", "projects", workspacePath.replace(/\//g, "-"))
56
+ await mkdir(claudeProjectDir, { recursive: true })
57
+ tempDirs.push(claudeProjectDir)
58
+
59
+ await writeFile(join(claudeProjectDir, `${sessionId}.jsonl`), [
60
+ JSON.stringify({ type: "user", message: { content: "hello" } }),
61
+ JSON.stringify({
62
+ type: "assistant",
63
+ message: { model: "claude-opus-4-6", content: [{ type: "text", text: "hi" }] },
64
+ }),
65
+ JSON.stringify({
66
+ kind: "context_usage",
67
+ contextUsage: { percentage: 45, totalTokens: 50000, maxTokens: 128000 },
68
+ }),
69
+ ].join("\n") + "\n")
70
+
71
+ const runtime = await inspectSessionRuntime(sessionId, "claude", workspacePath)
72
+ expect(runtime).not.toBeNull()
73
+ expect(runtime?.model).toBe("claude-opus-4-6")
74
+ expect(runtime?.tokenUsage?.totalTokens).toBe(50000)
75
+ })
76
+
77
+ test("extracts Codex runtime from session file", async () => {
78
+ const sessionId = `inspect-codex-${Date.now()}`
79
+ const codexSessionsDir = join(homedir(), ".codex", "sessions")
80
+ await mkdir(codexSessionsDir, { recursive: true })
81
+
82
+ const sessionFile = join(codexSessionsDir, `${sessionId}.jsonl`)
83
+ await writeFile(sessionFile, [
84
+ JSON.stringify({ type: "session_meta", payload: { id: sessionId, cwd: "/some/path" } }),
85
+ JSON.stringify({ type: "turn_context", payload: { model: "gpt-5.4" } }),
86
+ JSON.stringify({
87
+ type: "event_msg",
88
+ payload: {
89
+ type: "token_count",
90
+ info: {
91
+ total_token_usage: { total_tokens: 4312 },
92
+ last_token_usage: { total_tokens: 2160 },
93
+ model_context_window: 272000,
94
+ },
95
+ },
96
+ }),
97
+ ].join("\n") + "\n")
98
+ tempDirs.push(sessionFile)
99
+
100
+ const filePath = await findSessionFile(sessionId, "codex", "/some/path")
101
+ expect(filePath).not.toBeNull()
102
+
103
+ const runtime = await inspectSessionRuntime(sessionId, "codex", "/some/path")
104
+ expect(runtime).not.toBeNull()
105
+ expect(runtime?.model).toBe("gpt-5.4")
106
+ expect(runtime?.tokenUsage?.totalTokens).toBe(4312)
107
+ })
108
+ })
109
+
110
+ describe("readSessionTranscript", () => {
111
+ test("reads codex session files into transcript entries", async () => {
112
+ const sessionId = `transcript-codex-${Date.now()}`
113
+ const codexSessionsDir = join(homedir(), ".codex", "sessions")
114
+ await mkdir(codexSessionsDir, { recursive: true })
115
+
116
+ const sessionFile = join(codexSessionsDir, `${sessionId}.jsonl`)
117
+ await writeFile(sessionFile, [
118
+ JSON.stringify({ type: "session_meta", payload: { id: sessionId, cwd: "/some/path" } }),
119
+ JSON.stringify({ timestamp: "2026-04-13T12:00:00.000Z", type: "event_msg", payload: { type: "user_message", message: "Check child transcript" } }),
120
+ JSON.stringify({ timestamp: "2026-04-13T12:00:01.000Z", type: "event_msg", payload: { type: "agent_message", message: "Found the issue." } }),
121
+ ].join("\n") + "\n")
122
+ tempDirs.push(sessionFile)
123
+
124
+ await expect(readSessionTranscript(sessionId, "codex", "/some/path")).resolves.toEqual([
125
+ expect.objectContaining({ kind: "user_prompt", content: "Check child transcript" }),
126
+ expect.objectContaining({ kind: "assistant_text", text: "Found the issue." }),
127
+ ])
128
+ })
129
+ })
@@ -0,0 +1,475 @@
1
+ import { readdir, stat, open } from "node:fs/promises"
2
+ import { join, extname } from "node:path"
3
+ import { homedir } from "node:os"
4
+ import type {
5
+ AgentProvider,
6
+ DiscoveredSessionRuntime,
7
+ DiscoveredSessionTokenUsage,
8
+ DiscoveredSessionUsageBucket,
9
+ TranscriptEntry,
10
+ } from "../shared/types"
11
+ import { timestamped } from "../shared/transcript-entries"
12
+
13
+ const TAIL_BYTES = 32 * 1024
14
+
15
+ function joinSnippetParts(parts: Array<string | null>, maxLength: number): string | null {
16
+ const combined = parts
17
+ .filter((part): part is string => Boolean(part && part.trim()))
18
+ .join(" ")
19
+ .trim()
20
+
21
+ if (!combined) return null
22
+ return combined.slice(0, maxLength)
23
+ }
24
+
25
+ function isRecord(value: unknown): value is Record<string, unknown> {
26
+ return typeof value === "object" && value !== null
27
+ }
28
+
29
+ function normalizeString(value: unknown): string | undefined {
30
+ return typeof value === "string" ? value : undefined
31
+ }
32
+
33
+ function normalizeNumber(value: unknown): number | undefined {
34
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined
35
+ }
36
+
37
+
38
+ function parseJsonLine(line: string): Record<string, unknown> | null {
39
+ try {
40
+ const parsed = JSON.parse(line)
41
+ return isRecord(parsed) ? parsed : null
42
+ } catch (_error: unknown) {
43
+ return null
44
+ }
45
+ }
46
+
47
+ export function extractContentText(value: unknown, maxLength: number): string | null {
48
+ if (typeof value === "string") {
49
+ const trimmed = value.trim()
50
+ return trimmed ? trimmed.slice(0, maxLength) : null
51
+ }
52
+
53
+ if (Array.isArray(value)) {
54
+ return joinSnippetParts(value.map((entry) => extractContentText(entry, maxLength)), maxLength)
55
+ }
56
+
57
+ if (!isRecord(value)) return null
58
+
59
+ if (typeof value.text === "string") {
60
+ const trimmed = value.text.trim()
61
+ if (trimmed) return trimmed.slice(0, maxLength)
62
+ }
63
+
64
+ if ("content" in value) {
65
+ const contentText = extractContentText(value.content, maxLength)
66
+ if (contentText) return contentText
67
+ }
68
+
69
+ if ("message" in value) {
70
+ const messageText = extractContentText(value.message, maxLength)
71
+ if (messageText) return messageText
72
+ }
73
+
74
+ return null
75
+ }
76
+
77
+ function formatUsageBucketLabel(windowMinutes: number, fallback: string): string {
78
+ if (windowMinutes % (60 * 24) === 0) return `${windowMinutes / (60 * 24)}d`
79
+ if (windowMinutes % 60 === 0) return `${windowMinutes / 60}h`
80
+ return fallback
81
+ }
82
+
83
+ function extractClaudeRuntime(tailContent: string): DiscoveredSessionRuntime | undefined {
84
+ const lines = tailContent.split("\n").filter(Boolean)
85
+ let model: string | undefined
86
+ let tokenUsage: DiscoveredSessionTokenUsage | undefined
87
+
88
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
89
+ if (model && tokenUsage) break
90
+
91
+ const parsed = parseJsonLine(lines[index])
92
+ if (!parsed) continue
93
+
94
+ if (!model && normalizeString(parsed.type) === "assistant") {
95
+ const message = isRecord(parsed.message) ? parsed.message : null
96
+ model = normalizeString(message?.model)
97
+ continue
98
+ }
99
+
100
+ if (!tokenUsage && normalizeString(parsed.kind) === "context_usage") {
101
+ const contextUsage = isRecord(parsed.contextUsage) ? parsed.contextUsage : null
102
+ const percentage = normalizeNumber(contextUsage?.percentage)
103
+ const totalTokens = normalizeNumber(contextUsage?.totalTokens)
104
+ const maxTokens = normalizeNumber(contextUsage?.maxTokens)
105
+ if (totalTokens !== undefined) {
106
+ tokenUsage = {
107
+ totalTokens,
108
+ ...(maxTokens !== undefined ? { contextWindow: maxTokens } : {}),
109
+ ...(percentage !== undefined ? { estimatedContextPercent: percentage } : {}),
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ if (!model && !tokenUsage) return undefined
116
+ return {
117
+ ...(model ? { model } : {}),
118
+ ...(tokenUsage ? { tokenUsage } : {}),
119
+ }
120
+ }
121
+
122
+ function extractCodexRuntime(tailContent: string): DiscoveredSessionRuntime | undefined {
123
+ const lines = tailContent.split("\n").filter(Boolean)
124
+ let model: string | undefined
125
+ let tokenUsage: DiscoveredSessionTokenUsage | undefined
126
+ let usageBuckets: DiscoveredSessionUsageBucket[] | undefined
127
+
128
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
129
+ const parsed = parseJsonLine(lines[index])
130
+ if (!parsed) continue
131
+
132
+ const type = normalizeString(parsed.type)
133
+ if (!model && type === "turn_context") {
134
+ const payload = isRecord(parsed.payload) ? parsed.payload : null
135
+ model = normalizeString(payload?.model)
136
+ continue
137
+ }
138
+
139
+ if ((tokenUsage || usageBuckets) || type !== "event_msg") continue
140
+
141
+ const payload = isRecord(parsed.payload) ? parsed.payload : null
142
+ if (normalizeString(payload?.type) !== "token_count") continue
143
+
144
+ const info = isRecord(payload?.info) ? payload.info : null
145
+ const totalTokenUsage = isRecord(info?.total_token_usage) ? info.total_token_usage : null
146
+ const totalTokens = normalizeNumber(totalTokenUsage?.total_tokens)
147
+ const contextWindow = normalizeNumber(info?.model_context_window)
148
+ const lastTokenUsage = isRecord(info?.last_token_usage) ? info.last_token_usage : null
149
+ const lastTokens = normalizeNumber(lastTokenUsage?.total_tokens)
150
+ const estimatedContextPercent = contextWindow !== undefined
151
+ ? Math.min(100, Math.round(((lastTokens ?? totalTokens ?? 0) / contextWindow) * 100))
152
+ : undefined
153
+
154
+ if (totalTokens !== undefined) {
155
+ tokenUsage = {
156
+ totalTokens,
157
+ ...(contextWindow !== undefined ? { contextWindow } : {}),
158
+ ...(contextWindow !== undefined && totalTokens <= contextWindow
159
+ ? { contextLeft: Math.max(contextWindow - totalTokens, 0) }
160
+ : {}),
161
+ ...(estimatedContextPercent !== undefined ? { estimatedContextPercent } : {}),
162
+ }
163
+ }
164
+
165
+ const rateLimits = isRecord(payload?.rate_limits) ? payload.rate_limits : null
166
+ if (rateLimits) {
167
+ const buckets: DiscoveredSessionUsageBucket[] = []
168
+ for (const [key, labelFallback] of [["primary", "5h"], ["secondary", "7d"]] as const) {
169
+ const bucket = isRecord(rateLimits[key]) ? rateLimits[key] : null
170
+ const usedPercent = normalizeNumber(bucket?.used_percent)
171
+ if (usedPercent === undefined) continue
172
+ const windowMinutes = normalizeNumber(bucket?.window_minutes)
173
+ buckets.push({
174
+ label: windowMinutes !== undefined ? formatUsageBucketLabel(windowMinutes, labelFallback) : labelFallback,
175
+ usedPercent,
176
+ })
177
+ }
178
+ if (buckets.length > 0) {
179
+ usageBuckets = buckets
180
+ }
181
+ }
182
+ }
183
+
184
+ if (!model && !tokenUsage && !usageBuckets) return undefined
185
+
186
+ return {
187
+ ...(model ? { model } : {}),
188
+ ...(tokenUsage ? { tokenUsage } : {}),
189
+ ...(usageBuckets ? { usageBuckets } : {}),
190
+ }
191
+ }
192
+
193
+ async function readTail(filePath: string, bytes: number): Promise<string> {
194
+ const fh = await open(filePath, "r")
195
+ try {
196
+ const fileStat = await fh.stat()
197
+ const size = fileStat.size
198
+ const readStart = Math.max(0, size - bytes)
199
+ const readLength = Math.min(bytes, size)
200
+ const buffer = Buffer.alloc(readLength)
201
+ await fh.read(buffer, 0, readLength, readStart)
202
+ return buffer.toString("utf-8")
203
+ } finally {
204
+ await fh.close()
205
+ }
206
+ }
207
+
208
+ async function inspectSessionRuntimeFile(
209
+ filePath: string,
210
+ provider: AgentProvider
211
+ ): Promise<DiscoveredSessionRuntime | null> {
212
+ const tailContent = await readTail(filePath, TAIL_BYTES)
213
+ if (provider === "claude") {
214
+ return extractClaudeRuntime(tailContent) ?? null
215
+ }
216
+ if (provider === "codex") {
217
+ return extractCodexRuntime(tailContent) ?? null
218
+ }
219
+ return null
220
+ }
221
+
222
+ async function readHead(filePath: string, lineCount: number): Promise<string[]> {
223
+ const fh = await open(filePath, "r")
224
+ try {
225
+ const chunkSize = 4096
226
+ const chunks: Buffer[] = []
227
+ let position = 0
228
+ let newlineCount = 0
229
+
230
+ while (newlineCount < lineCount) {
231
+ const buffer = Buffer.alloc(chunkSize)
232
+ const { bytesRead } = await fh.read(buffer, 0, chunkSize, position)
233
+ if (bytesRead === 0) break
234
+ const chunk = buffer.subarray(0, bytesRead)
235
+ chunks.push(chunk)
236
+ position += bytesRead
237
+
238
+ for (const byte of chunk) {
239
+ if (byte === 10) newlineCount += 1
240
+ }
241
+ }
242
+
243
+ const content = Buffer.concat(chunks).toString("utf-8")
244
+ return content.split("\n").slice(0, lineCount)
245
+ } finally {
246
+ await fh.close()
247
+ }
248
+ }
249
+
250
+ async function collectJsonlFiles(dir: string): Promise<string[]> {
251
+ const result: string[] = []
252
+ let dirEntries: import("node:fs").Dirent[]
253
+ try {
254
+ dirEntries = await readdir(dir, { withFileTypes: true })
255
+ } catch (_error: unknown) {
256
+ return []
257
+ }
258
+
259
+ for (const entry of dirEntries) {
260
+ const fullPath = join(dir, entry.name)
261
+ if (entry.isDirectory()) {
262
+ result.push(...(await collectJsonlFiles(fullPath)))
263
+ } else if (entry.isFile() && extname(entry.name) === ".jsonl") {
264
+ result.push(fullPath)
265
+ }
266
+ }
267
+ return result
268
+ }
269
+
270
+ function encodeClaudeProjectDir(workspacePath: string): string {
271
+ return join(homedir(), ".claude", "projects", workspacePath.replace(/\//g, "-"))
272
+ }
273
+
274
+ export async function findSessionFile(
275
+ sessionId: string,
276
+ provider: AgentProvider,
277
+ workspacePath: string
278
+ ): Promise<string | null> {
279
+ if (provider === "claude") {
280
+ const claudeDir = encodeClaudeProjectDir(workspacePath)
281
+ const filePath = join(claudeDir, `${sessionId}.jsonl`)
282
+ try {
283
+ await stat(filePath)
284
+ return filePath
285
+ } catch (_error: unknown) {
286
+ return null
287
+ }
288
+ }
289
+
290
+ if (provider === "codex") {
291
+ const sessionsDir = join(homedir(), ".codex", "sessions")
292
+ // Fast path: codex commonly stores a session flat as <sessionId>.jsonl.
293
+ // Hitting it avoids scanning the whole tree (O(n files)).
294
+ const directPath = join(sessionsDir, `${sessionId}.jsonl`)
295
+ try {
296
+ await stat(directPath)
297
+ return directPath
298
+ } catch (_error: unknown) {
299
+ // Not at the flat path — fall back to a recursive scan for nested layouts.
300
+ }
301
+ const files = await collectJsonlFiles(sessionsDir)
302
+ for (const filePath of files) {
303
+ const headLines = await readHead(filePath, 1)
304
+ try {
305
+ const parsed = JSON.parse(headLines[0])
306
+ if (parsed.type === "session_meta" && parsed.payload?.id === sessionId) {
307
+ return filePath
308
+ }
309
+ } catch (_error: unknown) {
310
+ continue
311
+ }
312
+ }
313
+ }
314
+
315
+ return null
316
+ }
317
+
318
+ export async function inspectSessionRuntime(
319
+ sessionId: string,
320
+ provider: AgentProvider,
321
+ workspacePath: string
322
+ ): Promise<DiscoveredSessionRuntime | null> {
323
+ const filePath = await findSessionFile(sessionId, provider, workspacePath)
324
+ if (!filePath) return null
325
+ return inspectSessionRuntimeFile(filePath, provider)
326
+ }
327
+
328
+ function sessionEntryTimestamp(record: Record<string, unknown>): number {
329
+ const rawTimestamp = normalizeString(record.timestamp)
330
+ const parsed = rawTimestamp ? Date.parse(rawTimestamp) : Number.NaN
331
+ return Number.isFinite(parsed) ? parsed : Date.now()
332
+ }
333
+
334
+ function pushSessionEntry(entries: TranscriptEntry[], entry: TranscriptEntry): void {
335
+ const previous = entries.at(-1)
336
+ if (!previous) {
337
+ entries.push(entry)
338
+ return
339
+ }
340
+
341
+ if (previous.kind !== entry.kind) {
342
+ entries.push(entry)
343
+ return
344
+ }
345
+
346
+ const previousText = previous.kind === "assistant_text"
347
+ ? previous.text
348
+ : previous.kind === "user_prompt"
349
+ ? previous.content
350
+ : null
351
+ const nextText = entry.kind === "assistant_text"
352
+ ? entry.text
353
+ : entry.kind === "user_prompt"
354
+ ? entry.content
355
+ : null
356
+
357
+ if (previousText !== null && previousText === nextText) {
358
+ return
359
+ }
360
+
361
+ entries.push(entry)
362
+ }
363
+
364
+ function parseClaudeSessionTranscript(lines: string[]): TranscriptEntry[] {
365
+ const entries: TranscriptEntry[] = []
366
+
367
+ for (const line of lines) {
368
+ const parsed = parseJsonLine(line)
369
+ if (!parsed) continue
370
+
371
+ const type = normalizeString(parsed.type)
372
+ const message = isRecord(parsed.message) ? parsed.message : null
373
+ if (type === "user") {
374
+ const content = extractContentText(message?.content ?? message, 100_000)
375
+ if (!content) continue
376
+ pushSessionEntry(entries, timestamped({
377
+ kind: "user_prompt",
378
+ content,
379
+ }, sessionEntryTimestamp(parsed)))
380
+ continue
381
+ }
382
+
383
+ if (type === "assistant") {
384
+ const text = extractContentText(message?.content ?? message, 100_000)
385
+ if (!text) continue
386
+ pushSessionEntry(entries, timestamped({
387
+ kind: "assistant_text",
388
+ text,
389
+ }, sessionEntryTimestamp(parsed)))
390
+ }
391
+ }
392
+
393
+ return entries
394
+ }
395
+
396
+ function parseCodexSessionTranscript(lines: string[]): TranscriptEntry[] {
397
+ const entries: TranscriptEntry[] = []
398
+
399
+ for (const line of lines) {
400
+ const parsed = parseJsonLine(line)
401
+ if (!parsed) continue
402
+
403
+ const lineType = normalizeString(parsed.type)
404
+ if (lineType === "event_msg") {
405
+ const payload = isRecord(parsed.payload) ? parsed.payload : null
406
+ const payloadType = normalizeString(payload?.type)
407
+ if (payloadType === "user_message") {
408
+ const content = extractContentText(payload?.message, 100_000)
409
+ if (!content) continue
410
+ pushSessionEntry(entries, timestamped({
411
+ kind: "user_prompt",
412
+ content,
413
+ }, sessionEntryTimestamp(parsed)))
414
+ continue
415
+ }
416
+
417
+ if (payloadType === "agent_message") {
418
+ const text = extractContentText(payload?.message, 100_000)
419
+ if (!text) continue
420
+ pushSessionEntry(entries, timestamped({
421
+ kind: "assistant_text",
422
+ text,
423
+ }, sessionEntryTimestamp(parsed)))
424
+ }
425
+ continue
426
+ }
427
+
428
+ if (lineType !== "response_item") continue
429
+ const payload = isRecord(parsed.payload) ? parsed.payload : null
430
+ if (normalizeString(payload?.type) !== "message") continue
431
+
432
+ const role = normalizeString(payload?.role)
433
+ const content = extractContentText(payload?.content, 100_000)
434
+ if (!content) continue
435
+
436
+ if (role === "user") {
437
+ pushSessionEntry(entries, timestamped({
438
+ kind: "user_prompt",
439
+ content,
440
+ }, sessionEntryTimestamp(parsed)))
441
+ continue
442
+ }
443
+
444
+ if (role === "assistant") {
445
+ pushSessionEntry(entries, timestamped({
446
+ kind: "assistant_text",
447
+ text: content,
448
+ }, sessionEntryTimestamp(parsed)))
449
+ }
450
+ }
451
+
452
+ return entries
453
+ }
454
+
455
+ export async function readSessionTranscript(
456
+ sessionId: string,
457
+ provider: AgentProvider,
458
+ workspacePath: string
459
+ ): Promise<TranscriptEntry[]> {
460
+ const filePath = await findSessionFile(sessionId, provider, workspacePath)
461
+ if (!filePath) return []
462
+
463
+ const raw = await Bun.file(filePath).text()
464
+ const lines = raw.split("\n").filter(Boolean)
465
+
466
+ if (provider === "claude") {
467
+ return parseClaudeSessionTranscript(lines)
468
+ }
469
+
470
+ if (provider === "codex") {
471
+ return parseCodexSessionTranscript(lines)
472
+ }
473
+
474
+ return []
475
+ }