oh-my-agent-web 0.7.0 → 1.0.2

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 (249) hide show
  1. package/README.en.md +394 -248
  2. package/README.md +387 -247
  3. package/dist/server/approval/rules.js +4 -16
  4. package/dist/server/approval/scope.js +3 -3
  5. package/dist/server/cli-config.js +3 -3
  6. package/dist/server/cli-info.js +9 -9
  7. package/dist/server/cli.js +108 -23
  8. package/dist/server/engine/builtin.js +64 -1
  9. package/dist/server/engine/catalog-probe.js +34 -0
  10. package/dist/server/engine/claude-map.js +296 -0
  11. package/dist/server/engine/claude-sessions.js +581 -0
  12. package/dist/server/engine/claude-transport.js +75 -0
  13. package/dist/server/engine/claude.js +414 -0
  14. package/dist/server/engine/cli-version.js +119 -0
  15. package/dist/server/engine/codex-map.js +19 -23
  16. package/dist/server/engine/codex-sessions.js +5 -5
  17. package/dist/server/engine/codex-subagents.js +349 -0
  18. package/dist/server/engine/codex.js +215 -2
  19. package/dist/server/engine/dsh-history.js +1 -1
  20. package/dist/server/engine/dsh-log.js +47 -36
  21. package/dist/server/engine/dsh-map.js +3 -5
  22. package/dist/server/engine/dsh-prompt-patch.js +48 -0
  23. package/dist/server/engine/dsh-sessions.js +10 -11
  24. package/dist/server/engine/dsh-transport.js +2 -2
  25. package/dist/server/engine/dsh.js +45 -2
  26. package/dist/server/engine/engine-versions.js +41 -0
  27. package/dist/server/engine/fake.js +38 -7
  28. package/dist/server/engine/model-registry-reload.js +1 -1
  29. package/dist/server/engine/opencode-map.js +2 -2
  30. package/dist/server/engine/opencode-projects.js +1 -1
  31. package/dist/server/engine/opencode-sessions.js +2 -2
  32. package/dist/server/engine/opencode.js +39 -1
  33. package/dist/server/engine/pi-events.js +4 -4
  34. package/dist/server/engine/pi-plugins.js +8 -6
  35. package/dist/server/engine/pi-sdk.js +179 -5
  36. package/dist/server/engine/pi-subagent-tool.js +2 -2
  37. package/dist/server/engine/pi-subagents.js +2 -2
  38. package/dist/server/engine/pi-transcript.js +1 -1
  39. package/dist/server/engine/registry.js +12 -2
  40. package/dist/server/engine/suggestion-guidance.js +22 -9
  41. package/dist/server/engine/wiring.js +57 -3
  42. package/dist/server/files/content-search.js +187 -0
  43. package/dist/server/files/file-search.js +3 -3
  44. package/dist/server/files/file-service.js +33 -15
  45. package/dist/server/files/files-routes.js +68 -7
  46. package/dist/server/files/git-info.js +51 -11
  47. package/dist/server/files/path-resolve.js +176 -0
  48. package/dist/server/files/pick-dir-routes.js +93 -0
  49. package/dist/server/files/pick-path-routes.js +126 -0
  50. package/dist/server/http/auth-routes.js +71 -4
  51. package/dist/server/http/auth.js +48 -30
  52. package/dist/server/http/aux-routes.js +20 -4
  53. package/dist/server/http/file-headers.js +3 -3
  54. package/dist/server/http/health-and-static.js +1 -1
  55. package/dist/server/http/live-routes.js +4 -4
  56. package/dist/server/http/login-page.js +128 -1
  57. package/dist/server/http/password-store.js +31 -0
  58. package/dist/server/index.js +93 -11
  59. package/dist/server/markers/actions.js +8 -8
  60. package/dist/server/mcp/codex-mcp-store.js +190 -0
  61. package/dist/server/mcp/json-mcp-store.js +276 -0
  62. package/dist/server/mcp/mcp-auth.js +1 -1
  63. package/dist/server/mcp/mcp-command.js +0 -2
  64. package/dist/server/mcp/mcp-engine-shared.js +112 -0
  65. package/dist/server/mcp/mcp-import.js +4 -5
  66. package/dist/server/mcp/mcp-market.js +2 -3
  67. package/dist/server/mcp/mcp-routes.js +4 -3
  68. package/dist/server/mcp/none-mcp-store.js +27 -0
  69. package/dist/server/mcp/pi-mcp-config.js +1 -1
  70. package/dist/server/plugins/engine-plugin-stores.js +265 -0
  71. package/dist/server/plugins/plugin-routes.js +4 -3
  72. package/dist/server/protocol-parse.js +52 -4
  73. package/dist/server/protocol.js +0 -1
  74. package/dist/server/providers/catalog-writes.js +1 -1
  75. package/dist/server/providers/catalog.js +2 -2
  76. package/dist/server/providers/codex-models.js +1 -1
  77. package/dist/server/providers/codex-test.js +1 -1
  78. package/dist/server/providers/enabled-models.js +1 -11
  79. package/dist/server/providers/import-routes.js +2 -2
  80. package/dist/server/providers/probe-routes.js +26 -0
  81. package/dist/server/providers/provider-handlers.js +3 -3
  82. package/dist/server/providers/provider-pool.js +2 -2
  83. package/dist/server/providers/provider-store.js +5 -2
  84. package/dist/server/providers/subscription-routes.js +4 -2
  85. package/dist/server/providers/validate.js +13 -14
  86. package/dist/server/push/push-routes.js +32 -3
  87. package/dist/server/push/push.js +43 -9
  88. package/dist/server/session/client-view.js +2 -2
  89. package/dist/server/session/close-ephemeral.js +23 -0
  90. package/dist/server/session/conversation-create.js +31 -9
  91. package/dist/server/session/delete-session.js +25 -14
  92. package/dist/server/session/engine-switch.js +12 -5
  93. package/dist/server/session/fork-session.js +38 -0
  94. package/dist/server/session/lock.js +4 -4
  95. package/dist/server/session/model-controller.js +14 -2
  96. package/dist/server/session/move-session.js +40 -0
  97. package/dist/server/session/persist-ephemeral.js +48 -0
  98. package/dist/server/session/pi-opener.js +2 -2
  99. package/dist/server/session/pi-refresh.js +1 -1
  100. package/dist/server/session/pi-sessions-provider.js +2 -2
  101. package/dist/server/session/pi-sessions.js +107 -18
  102. package/dist/server/session/runtime.js +144 -9
  103. package/dist/server/session/session-stats.js +2 -2
  104. package/dist/server/session/settings.js +45 -49
  105. package/dist/server/session/subagents.js +4 -1
  106. package/dist/server/session/suggest-title.js +33 -0
  107. package/dist/server/session/title-prompt.js +126 -0
  108. package/dist/server/session/turn-finish.js +2 -2
  109. package/dist/server/session/turn-queue.js +5 -5
  110. package/dist/server/session/turn-watchdog.js +2 -2
  111. package/dist/server/skills/skill-install.js +3 -5
  112. package/dist/server/skills/skill-store.js +3 -3
  113. package/dist/server/skills/skillhub.js +3 -4
  114. package/dist/server/snapshot/messages.js +2 -8
  115. package/dist/server/snapshot/sender.js +1 -1
  116. package/dist/server/snapshot/state.js +3 -5
  117. package/dist/server/subagents/profile-store.js +11 -9
  118. package/dist/server/terminal/terminal-session.js +9 -9
  119. package/dist/server/uploads/upload-routes.js +1 -1
  120. package/dist/server/uploads/upload-store.js +4 -6
  121. package/dist/server/util/env.js +27 -21
  122. package/dist/server/util/ids.js +1 -1
  123. package/dist/server/util/json-file.js +38 -0
  124. package/dist/server/util/log-file.js +3 -3
  125. package/dist/server/util/log.js +4 -4
  126. package/dist/server/util/origin.js +5 -24
  127. package/dist/server/util/paths.js +25 -3
  128. package/dist/server/util/pricing-index.js +2 -6
  129. package/dist/server/util/pricing.js +10 -10
  130. package/dist/server/util/spawn-cli.js +2 -2
  131. package/dist/server/util/upstream-version.js +10 -12
  132. package/dist/server/util/version.js +2 -2
  133. package/dist/server/web-proxy/proxy-routes.js +167 -0
  134. package/dist/server/web-proxy/proxy-url.js +60 -0
  135. package/dist/server/web-proxy/rewrite.js +84 -0
  136. package/dist/server/ws/handle-message.js +21 -2
  137. package/package.json +13 -14
  138. package/scripts/prune-prebuilds.mjs +1 -1
  139. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_abnfDiagram-WEYOIA3C-Dq-2fRb4.js +1 -0
  140. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_architectureDiagram-ZHVYKT53-Dh5CEs4k.js +1 -0
  141. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_c4Diagram-XDPPPE54-Cb19QmmE.js +1 -0
  142. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_cynefinDiagram-WF24UDRP-COk-IL9O.js +1 -0
  143. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_diagram-CBK3MWTC-Gp_0nfEJ.js +1 -0
  144. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_diagram-ODIL2UCW-rzPf_9IN.js +1 -0
  145. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_diagram-PFBEJUXE-B5EnLWwl.js +1 -0
  146. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_diagram-TYUNFSNR-DVpFKFwC.js +1 -0
  147. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_ebnfDiagram-V4P3TTPT-CDL1qiPY.js +1 -0
  148. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_ishikawaDiagram-HPEHV2GE-CSkoONzp.js +1 -0
  149. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_mindmap-definition-6OY57UG3-B9sdVB6_.js +1 -0
  150. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_pegDiagram-ECIUJNBX-B6P7baCF.js +1 -0
  151. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_railroadDiagram-Y72NRBL7-W_a8Vy2l.js +1 -0
  152. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_swimlanes-B3ENRKFQ-DvGif-RR.js +1 -0
  153. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_swimlanesDiagram-AYQRV4AK-D0gFG_EI.js +1 -0
  154. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_usecaseDiagram-WPOLBNNX-BjgUBJiR.js +1 -0
  155. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_vennDiagram-Q3Y3IDCW-DnVuWTpF.js +1 -0
  156. package/web/dist/assets/_oh-my-agent-web-mermaid-pruned_chunk_wardleyDiagram-IVAIDKWA-B2vDAqpl.js +1 -0
  157. package/web/dist/assets/{arc-BTAvbR16.js → arc-Df9SI9yh.js} +1 -1
  158. package/web/dist/assets/{architecture-WOLXFQ4H-fewLD5_7.js → architecture-WOLXFQ4H-iO49E8Y1.js} +1 -1
  159. package/web/dist/assets/{blockDiagram-RJAE7QA3-ClmgyxZl.js → blockDiagram-RJAE7QA3-D8d1ukIs.js} +1 -1
  160. package/web/dist/assets/channel-C2-OT56h.js +1 -0
  161. package/web/dist/assets/{chunk-55XCU76K-DdC5EhZG.js → chunk-55XCU76K-C5wOvqDb.js} +1 -1
  162. package/web/dist/assets/{chunk-7S6XSTFO-CgToWHK6.js → chunk-7S6XSTFO-WIrWcDbG.js} +1 -1
  163. package/web/dist/assets/{chunk-AQDTSKZB-D-rQMnp7.js → chunk-AQDTSKZB-OkzYqFsw.js} +1 -1
  164. package/web/dist/assets/{chunk-DSNLSHJZ-cuZoTGMH.js → chunk-DSNLSHJZ-CHEA9kYd.js} +2 -2
  165. package/web/dist/assets/{chunk-HNE4Q36P-CiKr9iSN.js → chunk-HNE4Q36P-CPsq34CI.js} +1 -1
  166. package/web/dist/assets/{chunk-VQ5W62QI-DbTfXAAT.js → chunk-VQ5W62QI-CamPnEuF.js} +1 -1
  167. package/web/dist/assets/{chunk-WQQL4CDK-DHanHfB5.js → chunk-WQQL4CDK-gNypBVAM.js} +1 -1
  168. package/web/dist/assets/{chunk-ZAUX2LTX-SrjFXpy2.js → chunk-ZAUX2LTX--4WyRBRV.js} +1 -1
  169. package/web/dist/assets/{chunk-ZCHMQSOR-7Wy8xXQ5.js → chunk-ZCHMQSOR-umv3Lkpn.js} +1 -1
  170. package/web/dist/assets/{classDiagram-v2-4MYWR3UA-CBe5ZDvZ.js → classDiagram-v2-4MYWR3UA-C1XldpO1.js} +1 -1
  171. package/web/dist/assets/{cose-bilkent-JH36ORCC-Dljdyvhw.js → cose-bilkent-JH36ORCC-BvugZzFU.js} +1 -1
  172. package/web/dist/assets/{cynefin-EF2NZ3EQ-CcZU7GFP.js → cynefin-EF2NZ3EQ-6qXB70s4.js} +1 -1
  173. package/web/dist/assets/{dagre-NK4GIZVX-Bq_6y8qp.js → dagre-NK4GIZVX-DDdlABYw.js} +1 -1
  174. package/web/dist/assets/{diagram-I5XIIV2D-C2lnnYVG.js → diagram-I5XIIV2D-T8oWy5CY.js} +1 -1
  175. package/web/dist/assets/{diagram-RJA3I4VV-Bqf8Z6wJ.js → diagram-RJA3I4VV-CkdHTtfV.js} +1 -1
  176. package/web/dist/assets/{elk-QONTBCTQ-Cdzq_cJ5.js → elk-QONTBCTQ-BiEUbJJX.js} +1 -1
  177. package/web/dist/assets/{erDiagram-KYQIGQAA-ZJLCjPFC.js → erDiagram-KYQIGQAA-q3WwhyzH.js} +1 -1
  178. package/web/dist/assets/{eventmodeling-K75KTNOO-DHqeIpes.js → eventmodeling-K75KTNOO-BO5gW_NC.js} +1 -1
  179. package/web/dist/assets/{flowDiagram-IUGLLWBU-AVgYvXKN.js → flowDiagram-IUGLLWBU-DubjP6UK.js} +1 -1
  180. package/web/dist/assets/{ganttDiagram-R7GPNTJY-CR1SG1M7.js → ganttDiagram-R7GPNTJY-lKNRH8CI.js} +1 -1
  181. package/web/dist/assets/{gitGraph-VSP46ZUC-CIZQPK0R.js → gitGraph-VSP46ZUC-Cwlv3omT.js} +1 -1
  182. package/web/dist/assets/{gitGraphDiagram-PDYL7XHY-BhOCWEh6.js → gitGraphDiagram-PDYL7XHY-Bo9GpFvW.js} +1 -1
  183. package/web/dist/assets/index-CeYhxqkk.js +113 -0
  184. package/web/dist/assets/index-GJgJwZ23.css +1 -0
  185. package/web/dist/assets/{info-OHQRW6UA-C2wuRxeQ.js → info-OHQRW6UA-g3MbTRmN.js} +1 -1
  186. package/web/dist/assets/{infoDiagram-MVDRKAX5-BP5doAB5.js → infoDiagram-MVDRKAX5-CmILYLoB.js} +1 -1
  187. package/web/dist/assets/{journeyDiagram-BOSHW7O7-BcIDRTPS.js → journeyDiagram-BOSHW7O7-Av8T0U1C.js} +1 -1
  188. package/web/dist/assets/{kanban-definition-FNSA4HC5-CRH_bcoT.js → kanban-definition-FNSA4HC5-B9NyWQYN.js} +1 -1
  189. package/web/dist/assets/{line-iA4GPA_S.js → line-BUrll1My.js} +1 -1
  190. package/web/dist/assets/{linear-BGUKwkTH.js → linear-DjET0phX.js} +1 -1
  191. package/web/dist/assets/{math-DgNW_bVP.js → math-BJAjkhlu.js} +1 -1
  192. package/web/dist/assets/{mermaid-parser.core-CsqmgpEv.js → mermaid-parser.core-CaTdkhkS.js} +3 -3
  193. package/web/dist/assets/{mermaid.core-C1yCGQK6.js → mermaid.core-Cktx937U.js} +4 -4
  194. package/web/dist/assets/{packet-JDAUHWVQ-2ggaoz2g.js → packet-JDAUHWVQ-DhG6l3PG.js} +1 -1
  195. package/web/dist/assets/{pie-XZMESJXO-h_yslQQs.js → pie-XZMESJXO-YPcOx7s0.js} +1 -1
  196. package/web/dist/assets/{pieDiagram-GNMPY74T-6-JZMFyk.js → pieDiagram-GNMPY74T-DesiZKUl.js} +1 -1
  197. package/web/dist/assets/{quadrantDiagram-D57K4MC3-K5r1HOgL.js → quadrantDiagram-D57K4MC3-Cn1kBVgu.js} +1 -1
  198. package/web/dist/assets/{radar-ABXABTNO-6fOJGGgq.js → radar-ABXABTNO-CtuRfYSz.js} +1 -1
  199. package/web/dist/assets/{railroad-I3PHUGI6-B_gUgQ3d.js → railroad-I3PHUGI6-D5Gnyy0R.js} +1 -1
  200. package/web/dist/assets/{railroad-abnf-LNEFI6M7-CJ_eqiXZ.js → railroad-abnf-LNEFI6M7-Kw4pmJEm.js} +1 -1
  201. package/web/dist/assets/{railroad-ebnf-DWYD2UWJ-BTsgjLTU.js → railroad-ebnf-DWYD2UWJ-D8Mk5dIe.js} +1 -1
  202. package/web/dist/assets/{railroad-peg-JR7BVNR7-D91uiLln.js → railroad-peg-JR7BVNR7-R5Jwzs8F.js} +1 -1
  203. package/web/dist/assets/{requirementDiagram-EQYSW5RA-uapnV_OO.js → requirementDiagram-EQYSW5RA-B9RDq-cL.js} +1 -1
  204. package/web/dist/assets/{sankeyDiagram-SY24JHO5-BQsbFgUb.js → sankeyDiagram-SY24JHO5-DoEatOm6.js} +1 -1
  205. package/web/dist/assets/{sequenceDiagram-BLEHNHQU-iRoSYX_e.js → sequenceDiagram-BLEHNHQU-DGE5Z9lT.js} +1 -1
  206. package/web/dist/assets/{src-BBrKcs9r.js → src-BnEL7pZw.js} +1 -1
  207. package/web/dist/assets/{stateDiagram-v2-A6IESKWZ-Bzj9z5KH.js → stateDiagram-v2-A6IESKWZ-_vn8W-k_.js} +1 -1
  208. package/web/dist/assets/{timeline-definition-JXYKGLGB-DLUUuJX0.js → timeline-definition-JXYKGLGB-BaSLTtgR.js} +1 -1
  209. package/web/dist/assets/{treeView-D4JQ5SDB-CphIQ3n5.js → treeView-D4JQ5SDB-DZElyFgL.js} +1 -1
  210. package/web/dist/assets/{treemap-SAJKECNS-DqU3insw.js → treemap-SAJKECNS-DWDO4K4G.js} +1 -1
  211. package/web/dist/assets/{wardley-7MLQ67FV-Ch7d8cmC.js → wardley-7MLQ67FV-DTOUsk6Q.js} +1 -1
  212. package/web/dist/assets/{xychartDiagram-4URNHDU4-DdxpWBeM.js → xychartDiagram-4URNHDU4-DiaCfgAB.js} +1 -1
  213. package/web/dist/index.html +2 -2
  214. package/docs/screenshots/01-chat.png +0 -0
  215. package/docs/screenshots/02-answer.png +0 -0
  216. package/docs/screenshots/03-files-diff.png +0 -0
  217. package/docs/screenshots/04-terminal.png +0 -0
  218. package/docs/screenshots/05-models.png +0 -0
  219. package/docs/screenshots/06-skills.png +0 -0
  220. package/docs/screenshots/07-skills-hub.png +0 -0
  221. package/docs/screenshots/08-mcp.png +0 -0
  222. package/docs/screenshots/09-subagents.png +0 -0
  223. package/docs/screenshots/10-settings.png +0 -0
  224. package/docs/screenshots/11-plugins.png +0 -0
  225. package/docs/screenshots/12-voice.png +0 -0
  226. package/docs/screenshots/13-push.png +0 -0
  227. package/docs/screenshots/README.md +0 -45
  228. package/web/dist/assets/_omaw-mermaid-pruned_chunk_abnfDiagram-WEYOIA3C-CvNbnJ75.js +0 -1
  229. package/web/dist/assets/_omaw-mermaid-pruned_chunk_architectureDiagram-ZHVYKT53-T7NZZ4ao.js +0 -1
  230. package/web/dist/assets/_omaw-mermaid-pruned_chunk_c4Diagram-XDPPPE54-ByuAPg39.js +0 -1
  231. package/web/dist/assets/_omaw-mermaid-pruned_chunk_cynefinDiagram-WF24UDRP-BY5DHttT.js +0 -1
  232. package/web/dist/assets/_omaw-mermaid-pruned_chunk_diagram-CBK3MWTC-D7ZYyrF7.js +0 -1
  233. package/web/dist/assets/_omaw-mermaid-pruned_chunk_diagram-ODIL2UCW-C6kcOZFc.js +0 -1
  234. package/web/dist/assets/_omaw-mermaid-pruned_chunk_diagram-PFBEJUXE-CzFPFq0D.js +0 -1
  235. package/web/dist/assets/_omaw-mermaid-pruned_chunk_diagram-TYUNFSNR-B4G4T8SL.js +0 -1
  236. package/web/dist/assets/_omaw-mermaid-pruned_chunk_ebnfDiagram-V4P3TTPT-BkQ4xwD0.js +0 -1
  237. package/web/dist/assets/_omaw-mermaid-pruned_chunk_ishikawaDiagram-HPEHV2GE-52xqckJv.js +0 -1
  238. package/web/dist/assets/_omaw-mermaid-pruned_chunk_mindmap-definition-6OY57UG3-7cQksbFn.js +0 -1
  239. package/web/dist/assets/_omaw-mermaid-pruned_chunk_pegDiagram-ECIUJNBX-udgtBpwv.js +0 -1
  240. package/web/dist/assets/_omaw-mermaid-pruned_chunk_railroadDiagram-Y72NRBL7-Cd2dcQWz.js +0 -1
  241. package/web/dist/assets/_omaw-mermaid-pruned_chunk_swimlanes-B3ENRKFQ-p34pL1Yk.js +0 -1
  242. package/web/dist/assets/_omaw-mermaid-pruned_chunk_swimlanesDiagram-AYQRV4AK-BslfPs4r.js +0 -1
  243. package/web/dist/assets/_omaw-mermaid-pruned_chunk_usecaseDiagram-WPOLBNNX-DJZoN9Xk.js +0 -1
  244. package/web/dist/assets/_omaw-mermaid-pruned_chunk_vennDiagram-Q3Y3IDCW-DPXo67cZ.js +0 -1
  245. package/web/dist/assets/_omaw-mermaid-pruned_chunk_wardleyDiagram-IVAIDKWA-BE6ukwkg.js +0 -1
  246. package/web/dist/assets/channel-DfaYIFFE.js +0 -1
  247. package/web/dist/assets/index-CZQjJbRg.js +0 -104
  248. package/web/dist/assets/index-ChDUWKZX.css +0 -1
  249. /package/web/dist/assets/{_omaw-mermaid-pruned_katex_katex-Zojedrce.js → _oh-my-agent-web-mermaid-pruned_katex_katex-Zojedrce.js} +0 -0
@@ -0,0 +1,581 @@
1
+ /**
2
+ * Claude Code 的**会话存储**(只读解析 + 侧栏清单):`<CLAUDE_CONFIG_DIR|~/.claude>/projects/<encoded-cwd>/<id>.jsonl`。
3
+ *
4
+ * 为什么要自己读 JSONL,而不是让 ACP 适配器回放:
5
+ * 1. `session/load` 是**会重放整段历史**的(每条历史消息都变成一条 `session/update`),
6
+ * 我们只是要看一看历史、不该为此拿一个活的 query 句柄(续接才需要,见 claude.ts 的 resume);
7
+ * 2. ACP 的 `session/list` 只回 `{sessionId, cwd, title, updatedAt}`,**没有模型和用量**,
8
+ * 而侧栏要显示模型、会话信息面板要显示 token —— 这些都在 JSONL 里;
9
+ * 3. 用量:`session/prompt` 只回 `stopReason`(**没有 usage**),唯一的账本就是这个 JSONL
10
+ * 里每条 assistant 的 `message.usage`。
11
+ *
12
+ * 文件格式(实测 Claude Code 2.1.301):每行一个 JSON 条目,我们只认这几种:
13
+ * - `{type:"user", message:{role:"user", content}, timestamp, uuid, cwd, sessionId, isSidechain}`
14
+ * `content` 是字符串(真人输入)或块数组(`text` / `tool_result`);
15
+ * - `{type:"assistant", message:{role:"assistant", content:[text|thinking|tool_use], model, usage}, ...}`
16
+ * **一次模型调用一条**:一个回合里有工具调用时会有多条,所以界面消息要把连续几条合成一条;
17
+ * - `{type:"system", subtype:"api_error", error:{message}}`:报错,映射成 error 块;
18
+ * - `{type:"cost-state", totalCostUSD}`:整段会话花费(最后一次为准);
19
+ * - 其余(`queue-operation` / `attachment` / `api-request*` / `last-prompt` / `atis-latch` …)
20
+ * 是内部记账,一律跳过。
21
+ *
22
+ * 目录名编码不可逆(`/a-b` 与 `/a/b` 都编成 `-a-b`),所以**只用它做定位预筛**,
23
+ * 真正的 cwd 一律取条目里的 `cwd` 字段 —— 与适配器 `unstable_listSessions` 的判法一致。
24
+ */
25
+ import { existsSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
26
+ import { homedir } from "node:os";
27
+ import { basename, join } from "node:path";
28
+ import { TOOL_EXCERPT_CHARS, } from "../protocol.js";
29
+ import { CLAUDE_PROVIDER, summarizeToolInput } from "./claude-map.js";
30
+ import { isScratchCwd } from "./scratch.js";
31
+ /** 标题上限:与适配器一致(超过就截断加省略号) */
32
+ const MAX_TITLE_LENGTH = 128;
33
+ /** Claude 的数据目录(`CLAUDE_CONFIG_DIR` 与 claude 本体一致) */
34
+ function claudeHome(home) {
35
+ return home?.trim() || process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
36
+ }
37
+ /** 与 claude-code-acp 的 `encodeProjectPath` 完全一致(不可逆,只用于定位目录) */
38
+ function encodeProjectPath(cwd) {
39
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
40
+ }
41
+ /** 逐行容忍解析:坏行(历史版本、被截断的行)跳过,不让整个会话消失 */
42
+ function parseTranscript(text) {
43
+ const out = [];
44
+ for (const line of text.split("\n")) {
45
+ const trimmed = line.trim();
46
+ if (trimmed === "")
47
+ continue;
48
+ try {
49
+ const parsed = JSON.parse(trimmed);
50
+ if (parsed !== null && typeof parsed === "object")
51
+ out.push(parsed);
52
+ }
53
+ catch {
54
+ continue;
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+ export function readTranscriptFile(file) {
60
+ try {
61
+ return parseTranscript(readFileSync(file, "utf8"));
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }
67
+ function timestampOf(entry) {
68
+ const parsed = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : Number.NaN;
69
+ return Number.isFinite(parsed) ? parsed : 0;
70
+ }
71
+ /** 会话 id → 文件(先按 cwd 猜路径,再扫全部项目目录;与适配器的 `findSessionFile` 同序) */
72
+ export function claudeSessionFile(sessionId, options = {}) {
73
+ if (sessionId.trim() === "")
74
+ return null;
75
+ const root = join(claudeHome(options.home), "projects");
76
+ const fileName = `${sessionId}.jsonl`;
77
+ if (options.cwd) {
78
+ const expected = join(root, encodeProjectPath(options.cwd), fileName);
79
+ if (existsSync(expected))
80
+ return expected;
81
+ }
82
+ let dirs;
83
+ try {
84
+ dirs = readdirSync(root).filter((name) => {
85
+ try {
86
+ return statSync(join(root, name)).isDirectory();
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ });
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ for (const dir of dirs) {
97
+ const candidate = join(root, dir, fileName);
98
+ if (existsSync(candidate))
99
+ return candidate;
100
+ }
101
+ return null;
102
+ }
103
+ /** 全部会话文件(跳过 `agent-*.jsonl`:那是内部子代理的日志,不是用户会话) */
104
+ function listClaudeSessionFiles(home) {
105
+ const root = join(claudeHome(home), "projects");
106
+ let dirs;
107
+ try {
108
+ dirs = readdirSync(root, { withFileTypes: true })
109
+ .filter((entry) => entry.isDirectory())
110
+ .map((entry) => entry.name);
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ const out = [];
116
+ for (const dir of dirs) {
117
+ let files;
118
+ try {
119
+ files = readdirSync(join(root, dir));
120
+ }
121
+ catch {
122
+ continue;
123
+ }
124
+ for (const file of files) {
125
+ if (!file.endsWith(".jsonl") || file.startsWith("agent-"))
126
+ continue;
127
+ const full = join(root, dir, file);
128
+ let mtimeMs = 0;
129
+ try {
130
+ mtimeMs = statSync(full).mtimeMs;
131
+ }
132
+ catch {
133
+ continue;
134
+ }
135
+ out.push({ id: file.slice(0, -".jsonl".length), file: full, mtimeMs });
136
+ }
137
+ }
138
+ return out;
139
+ }
140
+ function sanitizeTitle(text) {
141
+ const sanitized = text
142
+ .replace(/[\r\n]+/g, " ")
143
+ .replace(/\s+/g, " ")
144
+ .trim();
145
+ return sanitized.length <= MAX_TITLE_LENGTH ? sanitized : `${sanitized.slice(0, MAX_TITLE_LENGTH)}…`;
146
+ }
147
+ /** 这块内容算不算「真人说的话」(排除工具结果、system-reminder、命令回显) */
148
+ function userTextOf(content) {
149
+ const candidates = typeof content === "string"
150
+ ? [content]
151
+ : (content ?? [])
152
+ .filter((block) => block.type === "text" && typeof block.text === "string")
153
+ .map((block) => block.text);
154
+ for (const text of candidates) {
155
+ const trimmed = text.trim();
156
+ if (trimmed === "")
157
+ continue;
158
+ // 注入的上下文(<system-reminder> / <local-command-stdout> / 工具结果回显)不是用户输入
159
+ if (trimmed.startsWith("<"))
160
+ continue;
161
+ return sanitizeTitle(trimmed);
162
+ }
163
+ return null;
164
+ }
165
+ /** 一条会话的元数据(cwd / 标题 / 起止时间 / 模型);判不出 cwd 或落在临时目录的返回 null */
166
+ function sessionMetaOf(id, file, entries, updatedAt) {
167
+ let cwd = "";
168
+ let title = null;
169
+ let startedAt = 0;
170
+ let model = null;
171
+ for (const entry of entries) {
172
+ if (entry.isSidechain === true)
173
+ continue;
174
+ if (cwd === "" && typeof entry.cwd === "string")
175
+ cwd = entry.cwd;
176
+ if (startedAt === 0)
177
+ startedAt = timestampOf(entry);
178
+ if (entry.type === "user" && title === null)
179
+ title = userTextOf(entry.message?.content);
180
+ if (entry.type === "assistant") {
181
+ const found = entry.message?.model;
182
+ if (typeof found === "string" && found !== "" && found !== "<synthetic>")
183
+ model = found;
184
+ }
185
+ }
186
+ if (cwd === "" || isScratchCwd(cwd))
187
+ return null;
188
+ return {
189
+ id,
190
+ file,
191
+ cwd,
192
+ title: title ?? id,
193
+ startedAt: startedAt || updatedAt,
194
+ updatedAt,
195
+ model,
196
+ };
197
+ }
198
+ /** 纯分组:会话 → 项目(按 cwd;项目名取目录名) */
199
+ function groupClaudeSessions(sessions) {
200
+ const groups = new Map();
201
+ for (const session of sessions) {
202
+ const group = groups.get(session.cwd) ??
203
+ { cwd: session.cwd, name: basename(session.cwd) || session.cwd, sessions: [] };
204
+ const summary = {
205
+ id: session.id,
206
+ engineSessionId: session.id,
207
+ title: session.title,
208
+ startedAt: session.startedAt,
209
+ updatedAt: session.updatedAt,
210
+ cwd: session.cwd,
211
+ model: session.model,
212
+ // 会话的事实源就是这个文件(读历史 / 删除都用它)
213
+ sessionPath: session.file,
214
+ };
215
+ group.sessions.push(summary);
216
+ groups.set(session.cwd, group);
217
+ }
218
+ return [...groups.values()]
219
+ .map((group) => ({
220
+ ...group,
221
+ sessions: [...group.sessions].sort((left, right) => right.updatedAt - left.updatedAt),
222
+ }))
223
+ .sort((left, right) => (right.sessions[0]?.updatedAt ?? 0) - (left.sessions[0]?.updatedAt ?? 0));
224
+ }
225
+ /**
226
+ * 侧栏的会话清单(读文件,不开子进程)。
227
+ *
228
+ * 每个文件按 mtime 缓存解析结果:刷新会话列表是高频动作(每次回合结束都会来一次),
229
+ * 但历史文件几乎不变 —— 没有这层缓存就会把几十条会话每条都重新 parse 一遍。
230
+ */
231
+ export function createClaudeSessionsProvider(options) {
232
+ const home = options.home;
233
+ const cacheMs = options.cacheMs ?? 5000;
234
+ const now = options.now ?? Date.now;
235
+ let cached = null;
236
+ const fileCache = new Map();
237
+ return async () => {
238
+ if (cached && now() - cached.at < cacheMs)
239
+ return cached.value;
240
+ try {
241
+ const sessions = [];
242
+ for (const session of listClaudeSessionFiles(home)) {
243
+ const known = fileCache.get(session.file);
244
+ if (known && known.mtimeMs === session.mtimeMs) {
245
+ if (known.meta)
246
+ sessions.push(known.meta);
247
+ continue;
248
+ }
249
+ const meta = sessionMetaOf(session.id, session.file, readTranscriptFile(session.file), session.mtimeMs);
250
+ fileCache.set(session.file, { mtimeMs: session.mtimeMs, meta });
251
+ if (meta)
252
+ sessions.push(meta);
253
+ }
254
+ cached = { at: now(), value: groupClaudeSessions(sessions) };
255
+ return cached.value;
256
+ }
257
+ catch (error) {
258
+ options.log?.(`读取 claude 会话清单失败:${error instanceof Error ? error.message : String(error)}`);
259
+ return cached?.value ?? [];
260
+ }
261
+ };
262
+ }
263
+ // ---------------------------------------------------------------------------
264
+ // JSONL → 界面消息 / 转录 / 统计
265
+ // ---------------------------------------------------------------------------
266
+ function toolBlockOf(block, ts) {
267
+ const name = typeof block.name === "string" && block.name !== "" ? block.name : "tool";
268
+ const summary = summarizeToolInput(name, block.input);
269
+ const full = block.input === undefined ? "" : safeJson(block.input);
270
+ return {
271
+ kind: "tool",
272
+ toolCallId: typeof block.id === "string" ? block.id : `tool-${ts}-${name}`,
273
+ name,
274
+ argsSummary: summary.length > 300 ? `${summary.slice(0, 300)}…` : summary,
275
+ ...(full !== "" && full !== summary ? { argsFull: full } : {}),
276
+ status: "running",
277
+ outputExcerpt: "",
278
+ truncated: false,
279
+ startedAt: ts,
280
+ };
281
+ }
282
+ /** 工具结果回填(Claude 把 tool_result 放在**下一条 user 条目**里) */
283
+ function applyToolResult(block, result, ts) {
284
+ if (!block)
285
+ return;
286
+ const output = outputTextOf(result.content);
287
+ block.status = result.is_error === true ? "failed" : "ok";
288
+ block.outputExcerpt = output.length > TOOL_EXCERPT_CHARS ? `${output.slice(0, TOOL_EXCERPT_CHARS)}…` : output;
289
+ block.truncated = output.length > TOOL_EXCERPT_CHARS;
290
+ block.endedAt = ts || block.startedAt;
291
+ }
292
+ function outputTextOf(value) {
293
+ if (typeof value === "string")
294
+ return value;
295
+ if (Array.isArray(value)) {
296
+ return value
297
+ .map((item) => outputTextOf(item))
298
+ .filter((text) => text !== "")
299
+ .join("\n");
300
+ }
301
+ if (value !== null && typeof value === "object") {
302
+ const record = value;
303
+ if (typeof record.text === "string")
304
+ return record.text;
305
+ if (record.content !== undefined)
306
+ return outputTextOf(record.content);
307
+ return safeJson(value);
308
+ }
309
+ return "";
310
+ }
311
+ function safeJson(value) {
312
+ try {
313
+ return JSON.stringify(value) ?? "";
314
+ }
315
+ catch {
316
+ return String(value);
317
+ }
318
+ }
319
+ /** 条目的内容块(字符串内容 = 纯文本,没有块可数) */
320
+ function contentBlocksOf(entry) {
321
+ const content = entry.message?.content;
322
+ return Array.isArray(content) ? content : [];
323
+ }
324
+ /**
325
+ * 转录 → 界面消息。
326
+ *
327
+ * 分组规则:**一个回合 = 一条 assistant 消息**。Claude 每调一次模型就写一条 `assistant` 条目,
328
+ * 带工具的一轮会有好几条(调工具前后各一次),界面显示成分开的几条会把「一次回复」切碎;
329
+ * 所以把连续几条合成一条,token 求和,`endedAt` 取最后一条的时间。
330
+ * 中间的 `tool_result`(在 user 条目里)只回填工具卡片,不产生用户消息。
331
+ */
332
+ export function transcriptToUiMessages(entries) {
333
+ const out = [];
334
+ const tools = new Map();
335
+ let pending = null;
336
+ const flush = () => {
337
+ if (pending && pending.blocks.length > 0)
338
+ out.push(pending);
339
+ pending = null;
340
+ };
341
+ for (const entry of entries) {
342
+ if (entry.isSidechain === true)
343
+ continue;
344
+ const ts = timestampOf(entry);
345
+ // 报错条目没有 message 字段(只有 error),要在 message 守卫**之前**处理掉
346
+ if (entry.type === "system" && entry.subtype === "api_error") {
347
+ if (!pending)
348
+ pending = { id: `a-${entry.uuid ?? out.length}`, role: "assistant", ts, blocks: [] };
349
+ const text = entry.error?.message ?? "Claude Code 报了一个错";
350
+ pending.blocks.push({ kind: "error", code: "claude.api_error", message: text });
351
+ pending.endedAt = ts;
352
+ continue;
353
+ }
354
+ const message = entry.message;
355
+ if (!message)
356
+ continue;
357
+ if (entry.type === "user") {
358
+ const content = message.content;
359
+ let said = null;
360
+ if (typeof content === "string") {
361
+ said = userTextOf(content);
362
+ }
363
+ else if (Array.isArray(content)) {
364
+ for (const block of content) {
365
+ if (block.type === "tool_result") {
366
+ const id = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
367
+ applyToolResult(tools.get(id), block, ts);
368
+ continue;
369
+ }
370
+ if (block.type === "text" && said === null)
371
+ said = userTextOf([block]);
372
+ }
373
+ }
374
+ if (said !== null) {
375
+ flush();
376
+ out.push({
377
+ id: `u-${entry.uuid ?? out.length}`,
378
+ role: "user",
379
+ ts,
380
+ blocks: [{ kind: "text", text: said }],
381
+ });
382
+ }
383
+ continue;
384
+ }
385
+ if (entry.type !== "assistant")
386
+ continue;
387
+ if (!pending) {
388
+ pending = { id: `a-${entry.uuid ?? out.length}`, role: "assistant", ts, blocks: [] };
389
+ }
390
+ const blocks = pending.blocks;
391
+ for (const block of Array.isArray(message.content) ? message.content : []) {
392
+ if (block.type === "text" && typeof block.text === "string" && block.text !== "") {
393
+ blocks.push({ kind: "text", text: block.text });
394
+ continue;
395
+ }
396
+ if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking !== "") {
397
+ blocks.push({ kind: "thinking", text: block.thinking });
398
+ continue;
399
+ }
400
+ if ((block.type === "tool_use" || block.type === "server_tool_use") && block.name !== "TodoWrite") {
401
+ const tool = toolBlockOf(block, ts);
402
+ tools.set(tool.toolCallId, tool);
403
+ blocks.push(tool);
404
+ }
405
+ }
406
+ const usage = message.usage;
407
+ if (usage) {
408
+ const previous = pending.usage ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
409
+ pending.usage = {
410
+ input: previous.input + Number(usage.input_tokens ?? 0),
411
+ output: previous.output + Number(usage.output_tokens ?? 0),
412
+ cacheRead: previous.cacheRead + Number(usage.cache_read_input_tokens ?? 0),
413
+ cacheWrite: previous.cacheWrite + Number(usage.cache_creation_input_tokens ?? 0),
414
+ };
415
+ }
416
+ if (typeof message.model === "string" && message.model !== "" && message.model !== "<synthetic>") {
417
+ pending.model = `${CLAUDE_PROVIDER}/${message.model}`;
418
+ }
419
+ pending.endedAt = ts || pending.endedAt;
420
+ }
421
+ flush();
422
+ return out;
423
+ }
424
+ /** 转录 → 引擎转录(只留正文,给「续聊」这种只要文本的场合) */
425
+ export function transcriptToTranscript(entries) {
426
+ const out = [];
427
+ for (const entry of entries) {
428
+ if (entry.isSidechain === true)
429
+ continue;
430
+ const message = entry.message;
431
+ if (!message)
432
+ continue;
433
+ if (entry.type === "user") {
434
+ const text = userTextOf(message.content);
435
+ if (text !== null)
436
+ out.push({ role: "user", text, ts: timestampOf(entry) });
437
+ continue;
438
+ }
439
+ if (entry.type !== "assistant")
440
+ continue;
441
+ const parts = [];
442
+ for (const block of Array.isArray(message.content) ? message.content : []) {
443
+ if (block.type === "text" && typeof block.text === "string" && block.text !== "")
444
+ parts.push(block.text);
445
+ }
446
+ if (parts.length === 0)
447
+ continue;
448
+ out.push({ role: "assistant", text: parts.join("\n\n"), ts: timestampOf(entry) });
449
+ }
450
+ return out;
451
+ }
452
+ /**
453
+ * 转录 → 会话统计。
454
+ *
455
+ * 四桶用量 = Σ 每条 assistant 的 `message.usage`(Claude 按次记账);
456
+ * 上下文占用取**最后一条** assistant 的输入侧(含缓存读写),近似「现在上下文多大」;
457
+ * 花费只有 `cost-state` 条目给(`totalCostUSD`),没有就不给这个字段(不编 0)。
458
+ */
459
+ export function transcriptStats(entries) {
460
+ const stats = {
461
+ userMessages: 0,
462
+ assistantMessages: 0,
463
+ toolCalls: 0,
464
+ toolResults: 0,
465
+ totalMessages: 0,
466
+ inputTokens: 0,
467
+ outputTokens: 0,
468
+ cacheRead: 0,
469
+ cacheWrite: 0,
470
+ totalTokens: 0,
471
+ contextTokens: 0,
472
+ };
473
+ for (const entry of entries) {
474
+ if (entry.isSidechain === true)
475
+ continue;
476
+ if (entry.type === "user") {
477
+ if (userTextOf(entry.message?.content) === null) {
478
+ // 只有工具结果的 user 条目:计入工具结果,不算一条用户消息
479
+ for (const block of contentBlocksOf(entry)) {
480
+ if (block.type === "tool_result")
481
+ stats.toolResults += 1;
482
+ }
483
+ continue;
484
+ }
485
+ stats.userMessages += 1;
486
+ stats.totalMessages += 1;
487
+ continue;
488
+ }
489
+ if (entry.type !== "assistant")
490
+ continue;
491
+ stats.assistantMessages += 1;
492
+ stats.totalMessages += 1;
493
+ for (const block of contentBlocksOf(entry)) {
494
+ if (block.type === "tool_use" && block.name !== "TodoWrite")
495
+ stats.toolCalls += 1;
496
+ }
497
+ const usage = entry.message?.usage;
498
+ if (!usage)
499
+ continue;
500
+ const input = Number(usage.input_tokens ?? 0);
501
+ const output = Number(usage.output_tokens ?? 0);
502
+ const cacheRead = Number(usage.cache_read_input_tokens ?? 0);
503
+ const cacheWrite = Number(usage.cache_creation_input_tokens ?? 0);
504
+ stats.inputTokens += input;
505
+ stats.outputTokens += output;
506
+ stats.cacheRead += cacheRead;
507
+ stats.cacheWrite += cacheWrite;
508
+ stats.totalTokens += input + output + cacheRead + cacheWrite;
509
+ stats.contextTokens = input + cacheRead + cacheWrite;
510
+ }
511
+ for (const entry of entries) {
512
+ if (entry.type === "cost-state" && typeof entry.totalCostUSD === "number") {
513
+ stats.cost = entry.totalCostUSD;
514
+ }
515
+ }
516
+ return stats;
517
+ }
518
+ /**
519
+ * 这一轮的用量(最后一条真人输入之后的全部 assistant 之和)。
520
+ *
521
+ * 用在哪里:`session/prompt` 的返回里没有 usage,宿主却要在这轮结束时报一次账 ——
522
+ * 回合结束时读一遍 JSONL,把这段加起来推一个 `usage` 事件(界面上的 token 才会动)。
523
+ */
524
+ export function lastTurnUsage(entries) {
525
+ let start = -1;
526
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
527
+ const entry = entries[index];
528
+ if (entry?.type !== "user" || entry.isSidechain === true)
529
+ continue;
530
+ if (userTextOf(entry.message?.content) === null)
531
+ continue;
532
+ start = index;
533
+ break;
534
+ }
535
+ if (start < 0)
536
+ return null;
537
+ const total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
538
+ for (const entry of entries.slice(start + 1)) {
539
+ if (entry.type !== "assistant" || entry.isSidechain === true)
540
+ continue;
541
+ const usage = entry.message?.usage;
542
+ if (!usage)
543
+ continue;
544
+ total.input += Number(usage.input_tokens ?? 0);
545
+ total.output += Number(usage.output_tokens ?? 0);
546
+ total.cacheRead += Number(usage.cache_read_input_tokens ?? 0);
547
+ total.cacheWrite += Number(usage.cache_creation_input_tokens ?? 0);
548
+ }
549
+ const sum = total.input + total.output + total.cacheRead + total.cacheWrite;
550
+ return sum === 0 ? null : total;
551
+ }
552
+ /** 会话最后用的模型(历史会话打开时显示它,而不是「当前默认」) */
553
+ export function sessionModelOf(entries) {
554
+ let model = null;
555
+ for (const entry of entries) {
556
+ if (entry.type !== "assistant" || entry.isSidechain === true)
557
+ continue;
558
+ const found = entry.message?.model;
559
+ if (typeof found === "string" && found !== "" && found !== "<synthetic>")
560
+ model = found;
561
+ }
562
+ return model === null ? null : { provider: CLAUDE_PROVIDER, model };
563
+ }
564
+ /**
565
+ * 删除会话:删掉那个 JSONL。
566
+ *
567
+ * Claude 自己的 CLI 没有「删除会话」这个动作(`/clear` 只是开新的),所以这里直接删文件 ——
568
+ * 与「会话的事实源是引擎自己写的文件」一致:文件没了,侧栏与 `claude --resume` 也就找不到了。
569
+ */
570
+ export function deleteClaudeSession(sessionId, options = {}) {
571
+ const file = options.sessionPath || claudeSessionFile(sessionId, options);
572
+ if (!file || !existsSync(file))
573
+ return false;
574
+ try {
575
+ rmSync(file, { force: true });
576
+ return true;
577
+ }
578
+ catch {
579
+ return false;
580
+ }
581
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Claude Code 的 ACP 客户端(ADR-0044)= 通用 JSON-RPC stdio + ACP 握手。
3
+ *
4
+ * 为什么走 `claude-code-acp`(`@zed-industries/claude-code-acp`)而不是 SDK / `claude -p`:
5
+ * - `claude -p --output-format stream-json` 是**一次性回合**:权限问询要靠管道里的 control
6
+ * 帧自己实现,也没有 `session/list`(历史会话只能自己扫 `~/.claude/projects`),
7
+ * 换模型要重启进程 —— 只能当兜底,不能当引擎;
8
+ * - `@anthropic-ai/claude-agent-sdk` 能力最全,但它把 Claude Code 整个打包进宿主进程,
9
+ * 版本跟着本工作台的依赖走,用户本机的 `claude` 登录态 / 权限配置反而管不到;
10
+ * - 这个适配器是 Zed 维护的官方级 ACP 实现,对外给的就是我们要的那几个动作:
11
+ * `session/new|load|resume|list`、`session/set_model`、`session/set_mode`、
12
+ * `session/cancel`、`session/request_permission`。与 dsh / opencode 走同一套协议,
13
+ * 所以复用 `jsonrpc-stdio.ts`(换的只有可执行文件与握手参数)。
14
+ *
15
+ * 实测(claude-code-acp 0.16.2 + Claude Code 2.1.301):
16
+ * initialize → `{protocolVersion:1, agentCapabilities:{loadSession:true,
17
+ * sessionCapabilities:{list,resume,fork}, promptCapabilities:{image,embeddedContext}},
18
+ * agentInfo:{name:"@zed-industries/claude-code-acp", title:"Claude Code", version}}`
19
+ * session/new → `{sessionId, models:{availableModels:[{modelId,name,description}], currentModelId},
20
+ * modes:{currentModeId, availableModes}}`
21
+ *
22
+ * 与 dsh / opencode 的两处差异(决定了 claude.ts 的写法):
23
+ * 1. 没有 `configOptions`:模型是 `session/new` 返回的 `models`,切换走 `session/set_model`
24
+ * (不是 `session/set_config_option`),思考档位这套协议里压根没有;
25
+ * 2. `session/resume`(不重放历史)与 `session/load`(重放历史)都在,所以「续接」用前者,
26
+ * 历史回放交给 JSONL(见 claude-sessions.ts),不必让适配器把整段历史再推一遍。
27
+ */
28
+ import { findExecutable } from "../util/spawn-cli.js";
29
+ import { createJsonRpcStdioClient } from "./jsonrpc-stdio.js";
30
+ /**
31
+ * 这个适配器**改过名**,两个包都还在被装着,所以两个 bin 名都认:
32
+ * - `@zed-industries/claude-code-acp` → `claude-code-acp`(老包,npm 上已标记 deprecated);
33
+ * - `@agentclientprotocol/claude-agent-acp` → `claude-agent-acp`(改名后的新包)。
34
+ * 只认一个的话,用户按 deprecation 提示装完新包反而变成「没有找到适配器」。
35
+ */
36
+ const CLAUDE_ACP_BINS = ["claude-code-acp", "claude-agent-acp"];
37
+ /** 默认可执行文件:PATH 里先命中哪个用哪个;都没装就报第一个名字(错误信息里再教怎么装) */
38
+ export function defaultClaudeBin(env = process.env, find = (bin, e) => findExecutable(bin, e)) {
39
+ for (const bin of CLAUDE_ACP_BINS) {
40
+ if (find(bin, env))
41
+ return bin;
42
+ }
43
+ return CLAUDE_ACP_BINS[0];
44
+ }
45
+ /**
46
+ * 握手时声明的客户端能力。
47
+ *
48
+ * 全部 `false`:Claude 自己的工具(Bash / Read / Write …)在适配器进程里跑,
49
+ * 我们不当它的 fs / terminal 桥(与 dsh / opencode 一致)。
50
+ * `promptCapabilities.image` 是**它**收图,不需要我们声明。
51
+ */
52
+ const CLIENT_CAPABILITIES = {
53
+ fs: { readTextFile: false, writeTextFile: false },
54
+ terminal: false,
55
+ };
56
+ export function createClaudeClient(options = {}) {
57
+ return createJsonRpcStdioClient({
58
+ bin: options.bin ?? defaultClaudeBin(),
59
+ args: options.args ?? [],
60
+ env: options.env,
61
+ cwd: options.cwd,
62
+ name: "claude",
63
+ log: options.log ?? (() => { }),
64
+ timeoutMs: options.timeoutMs,
65
+ // ACP 是标准 JSON-RPC 2.0:帧里必须带 jsonrpc 字段
66
+ jsonrpc: true,
67
+ async handshake(call) {
68
+ const result = (await call("initialize", {
69
+ protocolVersion: 1,
70
+ clientCapabilities: CLIENT_CAPABILITIES,
71
+ }));
72
+ return { version: result?.agentInfo?.version ?? null };
73
+ },
74
+ });
75
+ }