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
@@ -5,12 +5,12 @@
5
5
  * 一个包可以同时带扩展、技能、提示词模板、主题;另外还有散装放在
6
6
  * `~/.pi/agent/extensions/`(或项目 `.pi/extensions/`)里的单文件扩展。
7
7
  *
8
- * 与参考实现 `/data/git/pi-web`(`app/api/plugins/route.ts`)对齐的几条:
8
+ * 几条约定:
9
9
  * - **谁说了算:pi 自己**。哪些资源、开没开,全部取自 `DefaultPackageManager.resolve()`;
10
10
  * `onMissing` 一律回 `skip` —— 查询时绝不顺手跑 npm install。
11
11
  * - **两个作用域**:全局(`~/.pi/agent/settings.json`)+ 项目(`<workspace>/.pi/settings.json`),
12
12
  * 跟会话加载时用的 cwd 是同一个,所以面板显示的就是这个项目里真正加载的东西。
13
- * OMAW 没有「项目信任」的交互(ADR 未引入),与 SDK 的默认值一致:项目资源按已信任处理。
13
+ * oh-my-agent-web 没有「项目信任」的交互(ADR 未引入),与 SDK 的默认值一致:项目资源按已信任处理。
14
14
  * - **停用 ≠ 卸载**:写 `autoload: false`(pi 文档:先清空、再按 patterns 加回),
15
15
  * 不删文件、不动 npm 目录、**不丢用户手写的资源过滤**;开回来只删这一个键。
16
16
  * `pi config` 那种「四类数组都置空」的停用也认,一并显示成已停用。
@@ -20,7 +20,7 @@
20
20
  import { readFile } from "node:fs/promises";
21
21
  import { basename, dirname, extname, join, relative, sep } from "node:path";
22
22
  import { DefaultPackageManager, SettingsManager } from "@earendil-works/pi-coding-agent";
23
- export const RESOURCE_KEYS = ["extensions", "skills", "prompts", "themes"];
23
+ const RESOURCE_KEYS = ["extensions", "skills", "prompts", "themes"];
24
24
  const EMPTY_COUNTS = { extensions: 0, skills: 0, prompts: 0, themes: 0 };
25
25
  const KIND_OF = {
26
26
  extensions: "extension",
@@ -107,7 +107,7 @@ function toResourceInfo(resource, key) {
107
107
  };
108
108
  }
109
109
  /** 插件面板的 key:scope + source(同一个包可能在两个作用域各配一份) */
110
- export function pluginKey(source, scope) {
110
+ function pluginKey(source, scope) {
111
111
  return `${scope}\0${source}`;
112
112
  }
113
113
  export function createPluginStore(options) {
@@ -230,7 +230,9 @@ export function createPluginStore(options) {
230
230
  }
231
231
  /** 改一条 package 的启停;只动目标条目,其它条目与管理器里的其它键原样保留 */
232
232
  function setDisabled(settings, source, scope, wanted) {
233
- const current = scope === "global" ? [...(settings.getGlobalSettings().packages ?? [])] : [...(settings.getProjectSettings().packages ?? [])];
233
+ const current = scope === "global"
234
+ ? [...(settings.getGlobalSettings().packages ?? [])]
235
+ : [...(settings.getProjectSettings().packages ?? [])];
234
236
  const index = current.findIndex((entry) => sourceOf(entry) === source);
235
237
  if (index === -1)
236
238
  return false;
@@ -240,7 +242,7 @@ export function createPluginStore(options) {
240
242
  let next;
241
243
  if (wanted) {
242
244
  // 停用:autoload:false = 「先清空,再按 patterns 加回」。保留用户手写的过滤规则,
243
- // 否则「停一下再开」会把过滤弄丢(参考实现在这点上会丢,这里刻意不跟)。
245
+ // 否则「停一下再开」会把过滤弄丢。
244
246
  next = { ...(typeof entry === "string" ? { source: entry } : entry), autoload: false };
245
247
  }
246
248
  else if (typeof entry === "string") {
@@ -6,7 +6,8 @@
6
6
  *
7
7
  * 与 SDK 的对接方式(M3 侦察结论):
8
8
  */
9
- import { DefaultResourceLoader, SessionManager, createAgentSession, getAgentDir, } from "@earendil-works/pi-coding-agent";
9
+ import { DefaultResourceLoader, SessionManager, createAgentSession, getAgentDir, initTheme, } from "@earendil-works/pi-coding-agent";
10
+ import { dirname } from "node:path";
10
11
  import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
11
12
  import { classifyScope } from "../approval/scope.js";
12
13
  import { mapSessionEvent } from "./pi-events.js";
@@ -15,6 +16,28 @@ import { makeSubagentToolExtension } from "./pi-subagent-tool.js";
15
16
  import { enabledSubagentSpecs, readSubagentSettings } from "../subagents/profile-store.js";
16
17
  import { readSessionModel } from "./pi-session-model.js";
17
18
  import { entriesToUiMessages } from "./pi-transcript.js";
19
+ /**
20
+ * 把界面上那条消息换算成**真正的分叉点**。
21
+ *
22
+ * 助手气泡是多条 entry 合并出来的(一次工具轮次 = 一条),它身上的 `UiMessage.id` 只是其中
23
+ * 第一条;直接按它切会把这一轮的思考和工具块全丢掉。所以从这条消息往下走,走到
24
+ * 「下一个用户消息之前」的最后一个 entry —— 那才是「这条回复真的说完了」的位置。
25
+ */
26
+ function resolveForkLeaf(manager, messageId) {
27
+ if (!manager.getEntry(messageId))
28
+ return null;
29
+ let leaf = messageId;
30
+ // 沿树往下(只认最后一个孩子);碰到下一条用户消息就停:那是下一轮了
31
+ for (let guard = 0; guard < 10_000; guard += 1) {
32
+ const next = manager.getChildren(leaf).at(-1);
33
+ if (!next)
34
+ break;
35
+ if (next.type === "message" && asRecord(next.message).role === "user")
36
+ break;
37
+ leaf = next.id;
38
+ }
39
+ return leaf;
40
+ }
18
41
  function asRecord(value) {
19
42
  return typeof value === "object" && value !== null ? value : {};
20
43
  }
@@ -42,7 +65,7 @@ const STEER_SETTLE_FALLBACK_MS = 20 * 60 * 1000;
42
65
  */
43
66
  const SETTLE_GRACE_MS = 800;
44
67
  /**
45
- * provider 的显示名:只覆盖 SDK 名字太长/不直观的两个(和 pi-web 的选择一致),
68
+ * provider 的显示名:只覆盖 SDK 名字太长/不直观的两个,
46
69
  * 其余直接用引擎给的 OAuth 名称(`Anthropic (Claude Pro/Max)`、`xAI (Grok/X subscription)` 等)。
47
70
  */
48
71
  const OAUTH_LABELS = {
@@ -108,13 +131,26 @@ function toAuthEvent(event) {
108
131
  /** 教模型怎么写「下一步建议」(ADR-0022):刻意写得短(每次请求都付 token) */
109
132
  import { reloadModelRegistry } from "./model-registry-reload.js";
110
133
  import { NARRATE_GUIDANCE } from "./narrate-guidance.js";
111
- import { SUGGESTION_GUIDANCE } from "./suggestion-guidance.js";
134
+ import { SUGGESTION_GUIDANCE, suggestionGuidance } from "./suggestion-guidance.js";
112
135
  import { SUBAGENT_GUIDANCE } from "./subagent-guidance.js";
113
136
  export { NARRATE_GUIDANCE, SUGGESTION_GUIDANCE, SUBAGENT_GUIDANCE };
114
137
  import { createPiThinking } from "./pi-thinking.js";
115
138
  import { piSdkVersion } from "./pi-version.js";
116
139
  import { renameSessionIn } from "./pi-rename.js";
117
140
  export function createPiSdkEngine(options = {}) {
141
+ /**
142
+ * pi 的 `theme` 是个全局 Proxy:没调过 `initTheme()` 就抛
143
+ * `Theme not initialized. Call initTheme() first.`(import 本身**不会**初始化它)。
144
+ *
145
+ * TUI 启动时自己会初始化,而这个无界面的工作台不会 —— 但扩展里那些「给终端画东西」的代码
146
+ * (`getMarkdownTheme().heading()`、`new Text(..., theme.fg(...))`)照样会被调用,尤其是
147
+ * 塞在 `setTimeout` 里的渲染:那个异常没人接,**直接把这个服务进程带走**(同事机器上真踩过:
148
+ * 扩展 `token-speed.ts` 的定时渲染崩了整个 agent-web)。
149
+ *
150
+ * 所以宿主这边先初始化一次,让这类渲染退化成「画了个没人看的东西」,而不是崩服务。
151
+ * 顺带也是所有 `@earendil-works/*` 导入的唯一出口(本文件)。
152
+ */
153
+ initTheme();
118
154
  const sessions = new Map();
119
155
  const thinking = createPiThinking(sessions);
120
156
  let approvalHandler = null;
@@ -168,6 +204,22 @@ export function createPiSdkEngine(options = {}) {
168
204
  },
169
205
  };
170
206
  }
207
+ /**
208
+ * 算一个新会话文件的路径 + 建好目录(**不写文件**)。
209
+ *
210
+ * 借用一次 `SessionManager.create` 来算:文件名格式(`<ISO 时间>_<id>.jsonl`)与 pi 自己写的
211
+ * 完全一致。自己拼文件名的话,一旦 pi 改了格式,侧栏扫描器就找不到这些会话。
212
+ */
213
+ function sessionFileFor(workspace, id) {
214
+ const file = SessionManager.create(workspace, options.sessionDir, { id }).getSessionFile();
215
+ if (!file)
216
+ throw new Error("无法确定会话文件路径(会话目录不可写)");
217
+ return file;
218
+ }
219
+ /** 这个工作区的会话目录(布局跟 pi 自己的完全一致) */
220
+ function sessionDirFor(workspace) {
221
+ return dirname(sessionFileFor(workspace, "probe"));
222
+ }
171
223
  /** 建 resourceLoader 并拉起一个 AgentSession(createSession 与 openSession 共用) */
172
224
  async function startSession(sessionId, workspace, sessionManager, sessionPath = null) {
173
225
  const agentDir = options.agentDir ?? getAgentDir();
@@ -195,7 +247,11 @@ export function createPiSdkEngine(options = {}) {
195
247
  extensionFactories,
196
248
  // 教模型「下一步建议」的写法(ADR-0022):纯文本标记,不需要调用任何工具
197
249
  // 「动手前先说话」:过程默认折叠,不写正文用户就不知道它在干什么
198
- appendSystemPrompt: [SUGGESTION_GUIDANCE, NARRATE_GUIDANCE, ...(subagentReady ? [SUBAGENT_GUIDANCE] : [])],
250
+ appendSystemPrompt: [
251
+ suggestionGuidance(options.thinkCount?.() ?? null),
252
+ NARRATE_GUIDANCE,
253
+ ...(subagentReady ? [SUBAGENT_GUIDANCE] : []),
254
+ ],
199
255
  });
200
256
  await resourceLoader.reload();
201
257
  const { session } = await createAgentSession({
@@ -378,14 +434,80 @@ export function createPiSdkEngine(options = {}) {
378
434
  },
379
435
  },
380
436
  async createSession(sessionOptions) {
437
+ const { sessionId, workspace, ephemeral, fork } = sessionOptions;
438
+ // ── 派生(分支):与 pi 自己 `/fork` 的做法一致(见 SDK 的 agent-session-runtime)──
439
+ // 关键是**另开一个 manager 读同一个文件**:`createBranchedSession` 会把它自己指向新文件,
440
+ // 而源会话那个 manager 完全不受影响 —— 所以原对话一个字不动。
441
+ if (fork) {
442
+ const source = sessions.get(fork.sourceSessionId);
443
+ // pi 原生的拒绝理由就是这句:没落盘的会话不能 fork(`/clone` 前先说话)
444
+ if (!source?.sessionPath)
445
+ throw new Error("这条会话还没存过盘(临时对话),不能从它派生分支");
446
+ const leafId = resolveForkLeaf(source.sessionManager, fork.leafId);
447
+ if (!leafId)
448
+ throw new Error("找不到要分叉的那条消息(它可能不在当前分支上)");
449
+ // 新会话的目录跟新工作区走(布局与 pi 自己的完全一致)
450
+ const sessionManager = SessionManager.open(source.sessionPath, sessionDirFor(workspace), workspace);
451
+ const forkedPath = sessionManager.createBranchedSession(leafId);
452
+ if (!forkedPath)
453
+ throw new Error("派生会话文件创建失败");
454
+ // `createBranchedSession` 会给派生会话**另生一个 id** 并写进新文件头(pi 自己的 `/fork` 也这样):
455
+ // 句柄必须按**它**登记,不能按传进来的 `sessionId` —— 宿主要拿它当会话 id(见
456
+ // `conversation-create.ts` 的 `engineAssignedId ?? planned`),这里不跟上,之后发消息就是
457
+ // `engine.no_session`(引擎的表里只有那个没被采用的 id)。
458
+ const forkedId = sessionManager.getSessionId();
459
+ await startSession(forkedId, workspace, sessionManager, forkedPath);
460
+ return { sessionPath: forkedPath, sessionId: forkedId };
461
+ }
462
+ // ── 临时会话 = pi 的 `--no-session`:纯内存,不建文件、不进历史 ──
463
+ if (ephemeral) {
464
+ const sessionManager = SessionManager.inMemory(workspace, { id: sessionId });
465
+ await startSession(sessionId, workspace, sessionManager);
466
+ return { sessionPath: null };
467
+ }
381
468
  // ADR-0021 §2:新建会话就落在 **pi 的会话存储**里(不再用 inMemory + 自建 jsonl),
382
469
  // 用我们的 sessionId 当 SDK 的会话 id → 两边 id 空间完全一致。
383
- const sessionManager = SessionManager.create(sessionOptions.workspace, options.sessionDir, {
470
+ const sessionManager = SessionManager.create(workspace, options.sessionDir, {
384
471
  id: sessionOptions.sessionId,
385
472
  });
386
473
  const handle = await startSession(sessionOptions.sessionId, sessionOptions.workspace, sessionManager);
387
474
  return { sessionPath: handle.sessionPath };
388
475
  },
476
+ /**
477
+ * 临时会话转正:用 pi 原生导出让文件落盘(id 不变),再关掉重开。
478
+ *
479
+ * 序列化走 `session.exportToJsonl()`—— pi 自己的 `/export` 用的就是它,我们一个字不自己拼。
480
+ * 为什么必须「重开」:内存句柄对那个文件一无所知,不换掉它的话,之后说的话还是不会落盘
481
+ * (看着已经存了,其实一个字没进文件)。重开后上下文不变:历史就在文件里。
482
+ */
483
+ async persistSession(sessionId) {
484
+ const handle = sessions.get(sessionId);
485
+ if (!handle)
486
+ return { sessionPath: null };
487
+ const manager = handle.sessionManager;
488
+ // 已经是落盘的会话:直接回它的文件(幂等:重复点「保存为正式对话」不会写出两份)
489
+ if (manager.isPersisted())
490
+ return { sessionPath: manager.getSessionFile() ?? null };
491
+ const entries = manager.getEntries();
492
+ // 一个字都还没说:不值得落盘(空会话进历史只会变成一个没法看的空行)
493
+ if (!entries.some((entry) => asRecord(entry).type === "message"))
494
+ return { sessionPath: null };
495
+ const workspace = handle.workspace;
496
+ const sessionPath = sessionFileFor(workspace, sessionId);
497
+ // 原生导出:header + 当前分支的全部条目(id 就是这条会话原来的 id)
498
+ handle.session.exportToJsonl(sessionPath);
499
+ handle.unsubscribe();
500
+ try {
501
+ handle.session.dispose();
502
+ }
503
+ catch {
504
+ // 已经关掉了
505
+ }
506
+ sessions.delete(sessionId);
507
+ const reopened = SessionManager.open(sessionPath, options.sessionDir, workspace);
508
+ await startSession(sessionId, workspace, reopened, sessionPath);
509
+ return { sessionPath };
510
+ },
389
511
  async openSession(openOptions) {
390
512
  const sessionPath = openOptions.sessionPath ?? openOptions.engineSessionId;
391
513
  if (!sessionPath)
@@ -395,6 +517,58 @@ export function createPiSdkEngine(options = {}) {
395
517
  await startSession(openOptions.sessionId, workspace, sessionManager, sessionPath);
396
518
  },
397
519
  renameSession: (sessionPath, name) => renameSessionIn(sessions, sessionPath, name, options.sessionDir),
520
+ /**
521
+ * 「按会话内容起个名」:一次**独立**的一次性问答。
522
+ *
523
+ * 为什么自己开一个会话而不是往当前会话里塞一句提示词:起名不该出现在用户的
524
+ * 对话里(不能污染历史、不能算一轮、不能被用户看到)。所以用 `inMemory()` 的
525
+ * SessionManager(不落盘、不进 session 列表)+ `noTools`(不调工具)。
526
+ *
527
+ * 模型跟**当前全局选择**走:拿预热会话的 `model` / `modelRuntime` 直接用,
528
+ * 不重新解析凭据 —— 因此「没有可用模型」时返回 null,交给上层提示文案。
529
+ */
530
+ async suggestTitle(prompt) {
531
+ const handle = sessions.get(PREWARM_ID) ?? [...sessions.values()].at(-1);
532
+ const source = handle?.session;
533
+ if (!source?.modelRuntime || !source.model) {
534
+ // 没有预热会话(从未发过消息 / 刚启动)→ 现场预热一次再说
535
+ await warmUp();
536
+ }
537
+ const warm = sessions.get(PREWARM_ID) ?? handle;
538
+ const from = warm?.session;
539
+ if (!from?.modelRuntime || !from.model)
540
+ return null;
541
+ const disposable = await createAgentSession({
542
+ cwd: process.cwd(),
543
+ sessionManager: SessionManager.inMemory(),
544
+ noTools: "all",
545
+ /**
546
+ * 故意用一只「空资源」的 loader:起名不需要技能 / 扩展 / 上下文文件 / 主题,
547
+ * 全关掉只剩一句系统提示词 —— 否则点一下图标就要白扫一遍 skill 与 AGENTS.md,
548
+ * 既慢又白烧 token。
549
+ */
550
+ resourceLoader: new DefaultResourceLoader({
551
+ cwd: process.cwd(),
552
+ agentDir: options.agentDir ?? getAgentDir(),
553
+ noExtensions: true,
554
+ noSkills: true,
555
+ noPromptTemplates: true,
556
+ noThemes: true,
557
+ noContextFiles: true,
558
+ systemPrompt: "你是一个给会话起标题的助手。只输出标题本身,不要解释。",
559
+ }),
560
+ modelRuntime: from.modelRuntime,
561
+ model: from.model,
562
+ });
563
+ try {
564
+ await disposable.session.prompt(prompt);
565
+ return disposable.session.getLastAssistantText() ?? null;
566
+ }
567
+ finally {
568
+ // 一次性会话:问完就丢(dispose 会 abort 掉还在跑的东西)
569
+ disposable.session.dispose();
570
+ }
571
+ },
398
572
  async readUiMessages(sessionId) {
399
573
  const handle = sessions.get(sessionId);
400
574
  if (!handle)
@@ -43,10 +43,10 @@ function resultText(run) {
43
43
  /**
44
44
  * 等这个运行进入终态;返回最后一次看到的记录。
45
45
  *
46
- * 导出是为了单测(`tests/unit/subagent-wait.test.ts`):这里有一个「先查再订阅」的竞态,
46
+ * 导出是为了能单独调:这里有一个「先查再订阅」的竞态,
47
47
  * 只有把函数单独拿出来才能构造出那个窗口。
48
48
  */
49
- export function waitForRun(port, id, timeoutMs) {
49
+ function waitForRun(port, id, timeoutMs) {
50
50
  // 极快的任务可能在 start() 返回前就结束了:先看一眼,再订阅
51
51
  const current = port.get(id);
52
52
  if (current && TERMINAL.has(current.status))
@@ -35,14 +35,14 @@ const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
35
35
  * 在子会话里就找不到 —— SDK 只会甩一句英文「No API provider registered for api: xxx」。
36
36
  * 这里补一句人话,否则用户看到一屏英文完全不知道下一步该干嘛。
37
37
  */
38
- export function explainError(message) {
38
+ function explainError(message) {
39
39
  if (/No API provider registered for api:/i.test(message)) {
40
40
  return `${message}\n(子代理会话不加载扩展:这个模型是由扩展提供的。换一个内置 provider 的模型,或在子代理模板里指定模型。)`;
41
41
  }
42
42
  return message;
43
43
  }
44
44
  /** 给定父会话文件,算出子代理的会话目录(与父会话并列,不进项目清单) */
45
- export function subagentSessionDir(parentSessionPath, fallbackDir) {
45
+ function subagentSessionDir(parentSessionPath, fallbackDir) {
46
46
  if (!parentSessionPath)
47
47
  return fallbackDir;
48
48
  return join(dirname(parentSessionPath), basename(parentSessionPath, ".jsonl"), "subagents");
@@ -7,7 +7,7 @@
7
7
  * 输出是我们的 `UiMessage[]`(text / tool / error 三类块)。
8
8
  *
9
9
  * 刻意**不 import SDK 类型**:只用最小结构描述(TS 里用 unknown + 收窄),
10
- * 这样单测可以用合成条目直接覆盖,不需要拉起 SDK。
10
+ * 这样合成条目就能直接驱动,不需要拉起 SDK。
11
11
  */
12
12
  import { TOOL_EXCERPT_CHARS } from "../protocol.js";
13
13
  import { fullArgs, summarizeArgs } from "./pi-events.js";
@@ -38,11 +38,21 @@ export function createEngineRegistry(descriptors, fallbackName = "pi") {
38
38
  }
39
39
  /** 假引擎的能力:**没有审批**(它只是剧本,不需要拦),其余从简 */
40
40
  export const FAKE_CAPABILITIES = {
41
- approvals: true, // 假引擎仍会走审批剧本(approve 触发词),保留 true 便于测试
41
+ approvals: true, // 假引擎仍会走审批剧本(approve 触发词),保留 true
42
42
  tools: true,
43
43
  modelSwitch: true,
44
44
  thinkingLevel: false,
45
45
  resume: true,
46
+ // 也实现了(内存会话 / 转录截断),用它就能走一遍「临时对话 + 派生分支」的宿主流程
47
+ ephemeralChat: true,
48
+ forkChat: true,
46
49
  };
47
50
  /** pi:能力全开,而且**只有它真能插队**(`streamingBehavior: "steer"`,见 ADR-0043) */
48
- export const PI_CAPABILITIES = { ...ALL_ON, subagents: true, steer: true };
51
+ export const PI_CAPABILITIES = {
52
+ ...ALL_ON,
53
+ subagents: true,
54
+ steer: true,
55
+ // 临时会话(`SessionManager.inMemory`)与派生分支(按 entry 剪历史另存)都是 pi 会话存储的能力
56
+ ephemeralChat: true,
57
+ forkChat: true,
58
+ };
@@ -1,9 +1,22 @@
1
- /** 教模型「下一步建议」的写法(ADR-0022):纯文本标记,不需要调用任何工具 */
2
- export const SUGGESTION_GUIDANCE = [
3
- "## Suggested next actions",
4
- "When the user must choose what to do next, end the reply with 4 to 5 lines of:",
5
- "[[action:suggest:<button text>]]",
6
- "Clicking a button sends that text as the next user message.",
7
- "Use [[action:suggest:<short label>,prompt=<text to send>]] when the label should differ from the sent text.",
8
- "Put them on their own lines at the very end, and never mention this syntax in prose.",
9
- ].join("\n");
1
+ /**
2
+ * 教模型「下一步建议」的写法(ADR-0022):纯文本标记,不需要调用任何工具。
3
+ *
4
+ * 条数由调用方给(设置里「想想」那一个 `settings.thinkCount`,取值 1~5):
5
+ * 界面上「再给一批」按几条问,模型结尾就写几条 —— 两边用同一个数,不然按钮数和
6
+ * 「想想」的结果数对不上。拿不到就退回默认区间 4~5(与 ADR-0039 一致)。
7
+ */
8
+ export function suggestionGuidance(count) {
9
+ const wanted = typeof count === "number" && Number.isInteger(count) && count >= 1 && count <= 5 ? count : null;
10
+ const amount = wanted === null ? "4 to 5" : String(wanted);
11
+ const unit = wanted === 1 ? "line" : "lines";
12
+ return [
13
+ "## Suggested next actions",
14
+ `When the user must choose what to do next, end the reply with ${amount} ${unit} of:`,
15
+ "[[action:suggest:<button text>]]",
16
+ "Clicking a button sends that text as the next user message.",
17
+ "Use [[action:suggest:<short label>,prompt=<text to send>]] when the label should differ from the sent text.",
18
+ "Put them on their own lines at the very end, and never mention this syntax in prose.",
19
+ ].join("\n");
20
+ }
21
+ /** 默认版(4~5 条):给拿不到设置的地方用(测试、临时会话、预热) */
22
+ export const SUGGESTION_GUIDANCE = suggestionGuidance();
@@ -9,12 +9,18 @@
9
9
  * codex 客户端构造时不 spawn:机器上没装 codex 也能正常启动,只有在切到它时才报错。
10
10
  */
11
11
  import { existsSync, readFileSync, readdirSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
12
13
  import { join } from "node:path";
13
14
  import { createBuiltinEngineRegistry } from "./builtin.js";
15
+ import { enabledSubagentSpecs, readSubagentSettings } from "../subagents/profile-store.js";
14
16
  import { createEngineCatalog } from "./catalog.js";
17
+ import { createClaudeSessionsProvider } from "./claude-sessions.js";
18
+ import { createClaudeClient } from "./claude-transport.js";
15
19
  import { createCodexSessionsProvider } from "./codex-sessions.js";
16
20
  import { createCodexClient } from "./codex-transport.js";
17
21
  import { createDshClient } from "./dsh-transport.js";
22
+ import { writeDshSuggestionPatch } from "./dsh-prompt-patch.js";
23
+ import { suggestionGuidance } from "./suggestion-guidance.js";
18
24
  import { createDshSessionsProvider } from "./dsh-sessions.js";
19
25
  import { createOpencodeSessionsProvider } from "./opencode-sessions.js";
20
26
  import { readOpencodeSessionDirs } from "./opencode-projects.js";
@@ -28,7 +34,7 @@ const OPENCODE_DIRS_TTL_MS = 30_000;
28
34
  * 仍然按它自己的默认路由给 `currentValue`(实测 `agent-default-model` 不生效),
29
35
  * 于是「新会话默认用哪个模型」必须由我们自己指定。
30
36
  */
31
- export function readDshDefaultModel(dir) {
37
+ function readDshDefaultModel(dir) {
32
38
  const file = join(dir, "default-model.json");
33
39
  if (!existsSync(file))
34
40
  return null;
@@ -56,7 +62,14 @@ export function createEngineWiring(options) {
56
62
  * 顺序 = 环境变量里显式给的 → 用户目录里的 `*.yml`(按文件名)。
57
63
  */
58
64
  const patchDir = options.dshPatchDir;
65
+ /**
66
+ * 「下一步建议」的写法:dsh 只能靠 patch 叠加层带进去(ACP 的 session/new 没有系统提示字段)。
67
+ * 写在工作台自己的临时目录,**不**混进用户的 `dsh-patches`;排在最前面,
68
+ * 用户自己写的 patch 仍有最终决定权。
69
+ */
70
+ const suggestionPatchFile = writeDshSuggestionPatch(join(tmpdir(), "oh-my-agent-web"), suggestionGuidance(options.thinkCount?.() ?? null));
59
71
  const patchFiles = [
72
+ suggestionPatchFile,
60
73
  ...(options.dshPatches ?? []),
61
74
  ...(patchDir && existsSync(patchDir)
62
75
  ? readdirSync(patchDir)
@@ -117,18 +130,55 @@ export function createEngineWiring(options) {
117
130
  }
118
131
  return dirs;
119
132
  };
133
+ /**
134
+ * Claude Code 的 ACP 客户端(同样懒启动:没装适配器也能正常起工作台)。
135
+ * 历史会话清单不走它(直接读 `~/.claude/projects`),所以这里只给引擎用。
136
+ *
137
+ * `claudeBin` 空串 = 自动探测(老包 `claude-code-acp` / 新包 `claude-agent-acp`)。
138
+ */
139
+ const claudeClient = createClaudeClient({
140
+ bin: options.claudeBin || undefined,
141
+ args: options.claudeArgs,
142
+ cwd: options.workspace,
143
+ log: (text) => options.onLog(text, { engine: "claude" }),
144
+ });
145
+ /**
146
+ * codex 的子代理策略:**每次建会话现读**模板库(面板改完,下一条新会话就生效)。
147
+ *
148
+ * 读不到模板目录(精简部署)= 关掉:宁可不给 spawn 工具,也不要让模型
149
+ * 拿着一堆它看不见的「类型」乱派活。
150
+ */
151
+ const codexSubagentPolicy = () => {
152
+ const dir = options.subagentAgentDir;
153
+ if (!dir)
154
+ return { enabled: false, concurrency: 4, profiles: [] };
155
+ const settings = readSubagentSettings(dir);
156
+ const profiles = enabledSubagentSpecs(dir).map((profile) => ({
157
+ id: profile.id,
158
+ label: profile.label,
159
+ description: profile.description,
160
+ }));
161
+ return { enabled: settings.enabled, concurrency: settings.concurrency, profiles };
162
+ };
120
163
  const registry = createBuiltinEngineRegistry({
121
164
  onNotice: options.onNotice,
122
165
  ...(options.skillPaths ? { skillPaths: options.skillPaths } : {}),
123
166
  onLog: (text) => options.onLog(text),
124
- codex: { client: codexClient },
125
- dsh: { client: dshClient, model: defaultModel },
167
+ ...(options.thinkCount ? { thinkCount: options.thinkCount } : {}),
168
+ codex: { client: codexClient, bin: options.codexBin, subagentPolicy: codexSubagentPolicy },
169
+ dsh: { client: dshClient, bin: options.dshBin, model: defaultModel },
126
170
  opencode: {
127
171
  client: opencodeClient,
128
172
  bin: options.opencodeBin,
129
173
  args: options.opencodeArgs,
130
174
  model: options.opencodeModel ?? null,
131
175
  },
176
+ claude: {
177
+ client: claudeClient,
178
+ bin: options.claudeBin || undefined,
179
+ args: options.claudeArgs,
180
+ model: options.claudeModel ?? null,
181
+ },
132
182
  });
133
183
  const catalog = createEngineCatalog({
134
184
  registry,
@@ -145,6 +195,8 @@ export function createEngineWiring(options) {
145
195
  workspaces: opencodeWorkspaces,
146
196
  log: (text) => options.onLog(text),
147
197
  }),
198
+ // claude 的历史会话清单:直接读 `~/.claude/projects/*/*.jsonl`(标题/时间/模型都在文件里)
199
+ claude: createClaudeSessionsProvider({ log: (text) => options.onLog(text) }),
148
200
  },
149
201
  });
150
202
  const resolved = registry.resolve(options.engineName);
@@ -157,11 +209,13 @@ export function createEngineWiring(options) {
157
209
  registry,
158
210
  catalog,
159
211
  resolved,
212
+ codexClient,
160
213
  // 收掉外部引擎的子进程;pi 是进程内 SDK,不需要额外收
161
214
  close: () => {
162
215
  codexClient.close();
163
216
  dshClient.close();
164
217
  opencodeClient.close();
218
+ claudeClient.close();
165
219
  },
166
220
  };
167
221
  }