oh-my-agent-web 0.3.0 → 0.5.0

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 (279) hide show
  1. package/README.md +108 -450
  2. package/dist/server/approval/read-only.js +107 -0
  3. package/dist/server/approval/rules.js +85 -0
  4. package/dist/server/approval/scope.js +150 -0
  5. package/dist/server/brand.js +2 -0
  6. package/dist/server/cli-config.js +52 -0
  7. package/dist/server/cli-info.js +101 -0
  8. package/dist/server/cli.js +304 -0
  9. package/dist/server/engine/builtin.js +84 -0
  10. package/dist/server/engine/catalog.js +8 -0
  11. package/dist/server/engine/codex-approvals.js +45 -0
  12. package/dist/server/engine/codex-map.js +298 -0
  13. package/dist/server/engine/codex-sessions.js +159 -0
  14. package/dist/server/engine/codex-transport.js +49 -0
  15. package/dist/server/engine/codex.js +314 -0
  16. package/dist/server/engine/dsh-history.js +195 -0
  17. package/dist/server/engine/dsh-log.js +245 -0
  18. package/dist/server/engine/dsh-map.js +221 -0
  19. package/dist/server/engine/dsh-sessions.js +207 -0
  20. package/dist/server/engine/dsh-transport.js +37 -0
  21. package/dist/server/engine/dsh.js +284 -0
  22. package/dist/server/engine/fake.js +231 -0
  23. package/dist/server/engine/jsonrpc-stdio.js +241 -0
  24. package/dist/server/engine/model-registry-reload.js +115 -0
  25. package/dist/server/engine/narrate-guidance.js +22 -0
  26. package/dist/server/engine/opencode-map.js +499 -0
  27. package/dist/server/engine/opencode-projects.js +61 -0
  28. package/dist/server/engine/opencode-sessions.js +195 -0
  29. package/dist/server/engine/opencode-transport.js +48 -0
  30. package/dist/server/engine/opencode.js +394 -0
  31. package/dist/server/engine/pi-events.js +148 -0
  32. package/dist/server/engine/pi-plugins.js +310 -0
  33. package/dist/server/engine/pi-rename.js +14 -0
  34. package/dist/server/engine/pi-sdk.js +503 -0
  35. package/dist/server/engine/pi-session-model.js +23 -0
  36. package/dist/server/engine/pi-subagent-tool.js +166 -0
  37. package/dist/server/engine/pi-subagents.js +372 -0
  38. package/dist/server/engine/pi-thinking.js +34 -0
  39. package/dist/server/engine/pi-transcript.js +225 -0
  40. package/dist/server/engine/pi-version.js +11 -0
  41. package/dist/server/engine/port.js +1 -0
  42. package/dist/server/engine/registry.js +47 -0
  43. package/dist/server/engine/scratch.js +13 -0
  44. package/dist/server/engine/subagent-guidance.js +22 -0
  45. package/dist/server/engine/suggestion-guidance.js +9 -0
  46. package/dist/server/engine/switchable.js +22 -0
  47. package/dist/server/engine/wiring.js +166 -0
  48. package/dist/server/files/dir-routes.js +40 -0
  49. package/dist/server/files/file-search.js +63 -0
  50. package/dist/server/files/file-service.js +232 -0
  51. package/dist/server/files/files-routes.js +210 -0
  52. package/dist/server/files/git-info.js +361 -0
  53. package/dist/server/http/auth-routes.js +99 -0
  54. package/dist/server/http/auth.js +191 -0
  55. package/dist/server/http/aux-routes.js +40 -0
  56. package/dist/server/http/compress.js +77 -0
  57. package/dist/server/http/health-and-static.js +33 -0
  58. package/dist/server/http/live-routes.js +113 -0
  59. package/dist/server/http/login-page.js +200 -0
  60. package/dist/server/index.js +363 -520
  61. package/dist/server/markers/actions.js +262 -0
  62. package/dist/server/mcp/mcp-auth.js +184 -0
  63. package/dist/server/mcp/mcp-command.js +94 -0
  64. package/dist/server/mcp/mcp-import.js +290 -0
  65. package/dist/server/mcp/mcp-market-sources.js +192 -0
  66. package/dist/server/mcp/mcp-market-types.js +5 -0
  67. package/dist/server/mcp/mcp-market.js +175 -0
  68. package/dist/server/mcp/mcp-probe.js +134 -0
  69. package/dist/server/mcp/mcp-routes.js +70 -0
  70. package/dist/server/mcp/mcp-store.js +286 -0
  71. package/dist/server/mcp/pi-mcp-config.js +280 -0
  72. package/dist/server/plugins/plugin-routes.js +21 -0
  73. package/dist/server/protocol-parse.js +262 -0
  74. package/dist/server/protocol-settings.js +1 -0
  75. package/dist/server/protocol.js +6 -0
  76. package/dist/server/providers/catalog-store.js +85 -0
  77. package/dist/server/providers/catalog-writes.js +197 -0
  78. package/dist/server/providers/catalog.js +77 -0
  79. package/dist/server/providers/codex-models.js +96 -0
  80. package/dist/server/providers/codex-test.js +124 -0
  81. package/dist/server/providers/enabled-models.js +118 -0
  82. package/dist/server/providers/import-models.js +69 -0
  83. package/dist/server/providers/import-routes.js +203 -0
  84. package/dist/server/providers/model-test.js +52 -0
  85. package/dist/server/providers/probe.js +64 -0
  86. package/dist/server/providers/provider-handlers.js +223 -0
  87. package/dist/server/providers/provider-store.js +359 -0
  88. package/dist/server/providers/request-headers.js +7 -0
  89. package/dist/server/providers/subscription-routes.js +181 -0
  90. package/dist/server/providers/validate.js +241 -0
  91. package/dist/server/push/push-routes.js +114 -0
  92. package/dist/server/push/push.js +120 -0
  93. package/dist/server/session/approval-bridge.js +103 -0
  94. package/dist/server/session/attachment-resolve.js +21 -0
  95. package/dist/server/session/client-view.js +82 -0
  96. package/dist/server/session/conversation-create.js +65 -0
  97. package/dist/server/session/delete-session.js +84 -0
  98. package/dist/server/session/engine-switch.js +108 -0
  99. package/dist/server/session/lock.js +82 -0
  100. package/dist/server/session/model-controller.js +119 -0
  101. package/dist/server/session/notices.js +31 -0
  102. package/dist/server/session/pi-opener.js +123 -0
  103. package/dist/server/session/pi-refresh.js +51 -0
  104. package/dist/server/session/pi-sessions-provider.js +46 -0
  105. package/dist/server/session/pi-sessions.js +272 -0
  106. package/dist/server/session/refresh-actions.js +42 -0
  107. package/dist/server/session/rename-session.js +23 -0
  108. package/dist/server/session/runtime-services.js +28 -0
  109. package/dist/server/session/runtime-types.js +1 -0
  110. package/dist/server/session/runtime.js +380 -0
  111. package/dist/server/session/session-stats.js +91 -0
  112. package/dist/server/session/settings.js +367 -0
  113. package/dist/server/session/snapshot-for.js +7 -0
  114. package/dist/server/session/stats-refresher.js +50 -0
  115. package/dist/server/session/subagents.js +116 -0
  116. package/dist/server/session/switch-conversation.js +86 -0
  117. package/dist/server/session/throttle.js +14 -0
  118. package/dist/server/session/turn-events.js +126 -0
  119. package/dist/server/session/turn-finish.js +79 -0
  120. package/dist/server/session/turn-markers.js +26 -0
  121. package/dist/server/session/turn-queue.js +111 -0
  122. package/dist/server/session/turn-send.js +108 -0
  123. package/dist/server/session/turn-watchdog.js +77 -0
  124. package/dist/server/sessions/session-routes.js +17 -0
  125. package/dist/server/skills/skill-install.js +186 -0
  126. package/dist/server/skills/skill-routes.js +134 -0
  127. package/dist/server/skills/skill-store.js +163 -0
  128. package/dist/server/skills/skillhub.js +214 -0
  129. package/dist/server/skills/zip.js +121 -0
  130. package/dist/server/snapshot/fan-out.js +10 -0
  131. package/dist/server/snapshot/messages.js +75 -0
  132. package/dist/server/snapshot/sender.js +62 -0
  133. package/dist/server/snapshot/state.js +202 -0
  134. package/dist/server/subagents/profile-store.js +287 -0
  135. package/dist/server/terminal/terminal-routes.js +176 -0
  136. package/dist/server/terminal/terminal-session.js +140 -0
  137. package/dist/server/uploads/upload-routes.js +64 -0
  138. package/dist/server/uploads/upload-store.js +115 -0
  139. package/dist/server/util/data-dir.js +36 -0
  140. package/dist/server/util/env.js +146 -0
  141. package/dist/server/util/ids.js +28 -0
  142. package/dist/server/util/log-file.js +67 -0
  143. package/dist/server/util/log.js +52 -0
  144. package/dist/server/util/origin.js +75 -0
  145. package/dist/server/util/paths.js +60 -0
  146. package/dist/server/util/pricing-index.js +121 -0
  147. package/dist/server/util/pricing.js +214 -0
  148. package/dist/server/util/startup.js +34 -0
  149. package/dist/server/util/upstream-version.js +140 -0
  150. package/dist/server/util/usage.js +19 -0
  151. package/dist/server/util/version.js +50 -0
  152. package/dist/server/ws/handle-message.js +107 -0
  153. package/package.json +50 -63
  154. package/web/dist/apple-touch-icon-precomposed.png +0 -0
  155. package/web/dist/apple-touch-icon.png +0 -0
  156. package/web/dist/assets/abnfDiagram-WEYOIA3C-VUKLHHSn.js +1 -0
  157. package/web/dist/assets/arc-BeUkaHhk.js +1 -0
  158. package/web/dist/assets/architecture-WOLXFQ4H-B9I4s0NV.js +1 -0
  159. package/web/dist/assets/architectureDiagram-ZHVYKT53-CaEkLhfq.js +36 -0
  160. package/web/dist/assets/array-BifhSqXX.js +1 -0
  161. package/web/dist/assets/blockDiagram-RJAE7QA3-IR49UNE_.js +140 -0
  162. package/web/dist/assets/c4Diagram-XDPPPE54-B-81CloB.js +38 -0
  163. package/web/dist/assets/channel-B_mTTjGt.js +1 -0
  164. package/web/dist/assets/chunk-2E7WHVMD-CaWVF7qv.js +88 -0
  165. package/web/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  166. package/web/dist/assets/chunk-4EMNKR2T-BvXf2tq-.js +1 -0
  167. package/web/dist/assets/chunk-55XCU76K-Bz_d2e2D.js +1 -0
  168. package/web/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  169. package/web/dist/assets/chunk-7S6XSTFO-B_SPMiU8.js +2 -0
  170. package/web/dist/assets/chunk-AQDTSKZB-Cen-_xej.js +72 -0
  171. package/web/dist/assets/chunk-DSNLSHJZ-DEw1kFwj.js +129 -0
  172. package/web/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  173. package/web/dist/assets/chunk-HAGFOTED-BwpOOcw8.js +62 -0
  174. package/web/dist/assets/chunk-HNE4Q36P-BjCwi2uW.js +1 -0
  175. package/web/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  176. package/web/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  177. package/web/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  178. package/web/dist/assets/chunk-NQOKRVIF-WcqhWFS5.js +213 -0
  179. package/web/dist/assets/chunk-UHW7PFBP-quDvQby8.js +1 -0
  180. package/web/dist/assets/chunk-VQ5W62QI-DZBwmhzq.js +1 -0
  181. package/web/dist/assets/chunk-WQQL4CDK-wYyHYuui.js +10 -0
  182. package/web/dist/assets/chunk-XXDRQBXY-DfJzC7nt.js +1 -0
  183. package/web/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  184. package/web/dist/assets/chunk-YZO67HFG-CWLraGk_.js +1 -0
  185. package/web/dist/assets/chunk-ZAUX2LTX-ChBUt_FJ.js +1 -0
  186. package/web/dist/assets/chunk-ZCHMQSOR-C57UFT60.js +2 -0
  187. package/web/dist/assets/classDiagram-v2-4MYWR3UA-Bo4i7SNY.js +217 -0
  188. package/web/dist/assets/cose-bilkent-JH36ORCC-CK360NdH.js +1 -0
  189. package/web/dist/assets/cynefin-EF2NZ3EQ-DlXFjIFm.js +1 -0
  190. package/web/dist/assets/cynefinDiagram-WF24UDRP-zvZIqA1c.js +62 -0
  191. package/web/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
  192. package/web/dist/assets/dagre-NK4GIZVX-CnKhCu-v.js +4 -0
  193. package/web/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  194. package/web/dist/assets/diagram-CBK3MWTC-3QTR8Fjx.js +24 -0
  195. package/web/dist/assets/diagram-I5XIIV2D-D9lSd7z6.js +24 -0
  196. package/web/dist/assets/diagram-ODIL2UCW-DWJD6lPM.js +30 -0
  197. package/web/dist/assets/diagram-PFBEJUXE-CWJmoUg4.js +3 -0
  198. package/web/dist/assets/diagram-RJA3I4VV-D1SZHQcQ.js +41 -0
  199. package/web/dist/assets/diagram-TYUNFSNR-DCCjGi1x.js +200 -0
  200. package/web/dist/assets/dist-VOk_Iv17.js +1 -0
  201. package/web/dist/assets/ebnfDiagram-V4P3TTPT-CQLdqEiW.js +1 -0
  202. package/web/dist/assets/elk-QONTBCTQ-Dp2pEp6b.js +27 -0
  203. package/web/dist/assets/erDiagram-KYQIGQAA-BFGNoFQ-.js +99 -0
  204. package/web/dist/assets/eventmodeling-K75KTNOO-CFfAemGw.js +1 -0
  205. package/web/dist/assets/flowDiagram-IUGLLWBU-DmSp3cbW.js +1 -0
  206. package/web/dist/assets/ganttDiagram-R7GPNTJY-vK3jPGDq.js +292 -0
  207. package/web/dist/assets/gitGraph-VSP46ZUC-rXBgHTdq.js +1 -0
  208. package/web/dist/assets/gitGraphDiagram-PDYL7XHY-CBbGhago.js +106 -0
  209. package/web/dist/assets/graphlib-DS17s2tU.js +1 -0
  210. package/web/dist/assets/index-DBOF_OJe.js +104 -0
  211. package/web/dist/assets/index-nlY_-OaR.css +1 -0
  212. package/web/dist/assets/info-OHQRW6UA-Cd9vp-6C.js +1 -0
  213. package/web/dist/assets/infoDiagram-MVDRKAX5-Ce-Sg_qu.js +2 -0
  214. package/web/dist/assets/init-C-OQMol4.js +1 -0
  215. package/web/dist/assets/ishikawaDiagram-HPEHV2GE-DWlmR6BY.js +70 -0
  216. package/web/dist/assets/journeyDiagram-BOSHW7O7-DmDG97ae.js +139 -0
  217. package/web/dist/assets/kanban-definition-FNSA4HC5-1Z4sDrgV.js +89 -0
  218. package/web/dist/assets/katex-B_JcIwqj.js +257 -0
  219. package/web/dist/assets/line-DGM1-9KW.js +1 -0
  220. package/web/dist/assets/linear-B6hyeGHu.js +1 -0
  221. package/web/dist/assets/mermaid-parser.core-D1HmdkA4.js +7 -0
  222. package/web/dist/assets/mermaid.core-DeUJrhO1.js +44 -0
  223. package/web/dist/assets/mindmap-definition-6OY57UG3-C3-FByy8.js +98 -0
  224. package/web/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  225. package/web/dist/assets/packet-JDAUHWVQ-BoOr7DaW.js +1 -0
  226. package/web/dist/assets/path-fybaL0A-.js +1 -0
  227. package/web/dist/assets/pegDiagram-ECIUJNBX-Bn7m75TZ.js +1 -0
  228. package/web/dist/assets/pie-XZMESJXO-CGuMTDBp.js +1 -0
  229. package/web/dist/assets/pieDiagram-GNMPY74T-DHhaPwZe.js +39 -0
  230. package/web/dist/assets/quadrantDiagram-D57K4MC3-DxTgFHLK.js +7 -0
  231. package/web/dist/assets/radar-ABXABTNO-BiKxmALL.js +1 -0
  232. package/web/dist/assets/railroad-I3PHUGI6-BKYufgg4.js +1 -0
  233. package/web/dist/assets/railroad-abnf-LNEFI6M7-DmOrjlyL.js +1 -0
  234. package/web/dist/assets/railroad-ebnf-DWYD2UWJ-BgMyVDd1.js +1 -0
  235. package/web/dist/assets/railroad-peg-JR7BVNR7-BN6NL8S-.js +1 -0
  236. package/web/dist/assets/railroadDiagram-Y72NRBL7-BsbA-9gP.js +1 -0
  237. package/web/dist/assets/requirementDiagram-EQYSW5RA-HUw4YqWd.js +84 -0
  238. package/web/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  239. package/web/dist/assets/sankeyDiagram-SY24JHO5-Dx1MOFRu.js +40 -0
  240. package/web/dist/assets/sequenceDiagram-BLEHNHQU-C0-Rzg3g.js +169 -0
  241. package/web/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  242. package/web/dist/assets/src-CuHygcpj.js +1 -0
  243. package/web/dist/assets/stateDiagram-v2-A6IESKWZ-BnEICGtg.js +291 -0
  244. package/web/dist/assets/swimlanes-B3ENRKFQ-CwDRCHZD.js +1 -0
  245. package/web/dist/assets/swimlanesDiagram-AYQRV4AK-DZ_aAXmG.js +8 -0
  246. package/web/dist/assets/timeline-definition-JXYKGLGB-C4BVpgzc.js +120 -0
  247. package/web/dist/assets/treeView-D4JQ5SDB-BBsvEMP3.js +1 -0
  248. package/web/dist/assets/treemap-SAJKECNS-BYw6cxC8.js +1 -0
  249. package/web/dist/assets/usecaseDiagram-WPOLBNNX-BkOXLgwP.js +328 -0
  250. package/web/dist/assets/vennDiagram-Q3Y3IDCW-Kgjk_n6B.js +34 -0
  251. package/web/dist/assets/wardley-7MLQ67FV-BGVObhvJ.js +1 -0
  252. package/web/dist/assets/wardleyDiagram-IVAIDKWA-i97uD6tL.js +78 -0
  253. package/web/dist/assets/xychartDiagram-4URNHDU4-awJi5TfJ.js +7 -0
  254. package/web/dist/favicon-32.png +0 -0
  255. package/web/dist/favicon.ico +0 -0
  256. package/web/dist/favicon.svg +20 -8
  257. package/web/dist/icons/apple-touch-icon-180.png +0 -0
  258. package/web/dist/icons/icon-192.png +0 -0
  259. package/web/dist/icons/icon-512.png +0 -0
  260. package/web/dist/icons/icon-maskable-512.png +0 -0
  261. package/web/dist/index.html +31 -24
  262. package/web/dist/manifest.webmanifest +17 -0
  263. package/web/dist/maskable.svg +25 -0
  264. package/web/dist/sw.js +40 -0
  265. package/bin/oh-my-agent-web-ps.mjs +0 -203
  266. package/bin/oh-my-agent-web-ws.mjs +0 -782
  267. package/bin/oh-my-agent-web.mjs +0 -321
  268. package/bin/update-check.mjs +0 -142
  269. package/dist/server/codex-client.js +0 -310
  270. package/dist/server/codex-supervisor.js +0 -446
  271. package/dist/server/fs-service.js +0 -144
  272. package/dist/server/request-inspector.js +0 -458
  273. package/dist/shared/protocol.js +0 -112
  274. package/scripts/manage-server.mjs +0 -155
  275. package/scripts/omaw.mjs +0 -74
  276. package/shared/protocol.ts +0 -228
  277. package/web/dist/assets/index-5LJ5fRcO.js +0 -42
  278. package/web/dist/assets/index-QiDfPKFo.css +0 -1
  279. package/web/dist/assets/markdown-CYXe6JZx.js +0 -55
@@ -0,0 +1,241 @@
1
+ /**
2
+ * 通用「子进程 + stdio JSON-RPC」客户端(codex / dsh 共用)。
3
+ *
4
+ * 为什么需要它:codex 的 `app-server` 和 dsh 的 `--profile acp` 是**同一种东西**——
5
+ * 每行一个 JSON-RPC 帧、stdout 只走协议、服务端会反过来向我们发请求(审批/权限)。
6
+ * 差异只有三处,全部参数化:
7
+ * 1. 启动命令(`codex app-server` / `dsh --profile acp`);
8
+ * 2. 握手方法(都是 `initialize`,但参数/返回值不同);
9
+ * 3. 帧里带不带 `"jsonrpc":"2.0"`(ACP 要求;codex 不带)。
10
+ *
11
+ * 生命周期纪律(两个引擎一致):
12
+ * - **懒启动**:第一次 request 才 spawn —— 机器上没装某个 CLI 不该让工作台起不来;
13
+ * - **挂了就重来**:子进程退出 → 拒掉所有在飞请求 → 下次请求重新拉起;
14
+ * - **退出即收**:`close()` 杀掉子进程(codex 由此释放它持有的全部 thread 写锁)。
15
+ *
16
+ * 帧格式:
17
+ * 请求 → `{"id":1,"method":"…","params":{…}}\n`
18
+ * 响应 → `{"id":1,"result":{…}}` / `{"id":1,"error":{"code":-32602,"message":"…"}}`
19
+ * 通知 → `{"method":"…","params":{…}}`(无 id)
20
+ * 服务端请求 → `{"id":9,"method":"…","params":{…}}`(有 id + method,**必须回包**否则对端一直等)
21
+ */
22
+ import { spawn } from "node:child_process";
23
+ export function createJsonRpcStdioClient(options) {
24
+ const baseTimeout = options.timeoutMs ?? 30_000;
25
+ const prefix = options.name;
26
+ let child = null;
27
+ let starting = null;
28
+ let initialized = false;
29
+ let seq = 0;
30
+ let version = null;
31
+ let buffer = "";
32
+ const pending = new Map();
33
+ const notificationListeners = new Set();
34
+ const serverRequestListeners = new Set();
35
+ const exitListeners = new Set();
36
+ const rejectAll = (error) => {
37
+ for (const [id, entry] of pending) {
38
+ clearTimeout(entry.timer);
39
+ entry.reject(error);
40
+ pending.delete(id);
41
+ }
42
+ };
43
+ const send = (payload) => {
44
+ if (!child)
45
+ throw new Error(`${prefix} 未连接`);
46
+ child.stdin.write(`${JSON.stringify(options.jsonrpc ? { jsonrpc: "2.0", ...payload } : payload)}\n`);
47
+ };
48
+ const respond = (id, result) => {
49
+ try {
50
+ send({ id, result });
51
+ }
52
+ catch (error) {
53
+ options.log?.(`${prefix}: 回包失败(${id}):${error instanceof Error ? error.message : String(error)}`);
54
+ }
55
+ };
56
+ const handleLine = (line) => {
57
+ if (line.trim() === "")
58
+ return;
59
+ let message;
60
+ try {
61
+ message = JSON.parse(line);
62
+ }
63
+ catch {
64
+ options.log?.(`${prefix}: 收到非 JSON 帧(已忽略):${line.slice(0, 120)}`);
65
+ return;
66
+ }
67
+ const id = typeof message.id === "number" ? message.id : null;
68
+ const method = typeof message.method === "string" ? message.method : null;
69
+ if (method && id !== null) {
70
+ const request = { id, method, params: (message.params ?? {}) };
71
+ if (serverRequestListeners.size === 0) {
72
+ // 没人接也得回一个:不回包对端会一直等下去
73
+ respond(id, { decision: "decline" });
74
+ return;
75
+ }
76
+ for (const listener of serverRequestListeners)
77
+ listener(request);
78
+ return;
79
+ }
80
+ if (method) {
81
+ const notification = { method, params: (message.params ?? {}) };
82
+ for (const listener of notificationListeners)
83
+ listener(notification);
84
+ return;
85
+ }
86
+ if (id === null)
87
+ return;
88
+ const entry = pending.get(id);
89
+ if (!entry)
90
+ return;
91
+ pending.delete(id);
92
+ clearTimeout(entry.timer);
93
+ const error = message.error;
94
+ if (error) {
95
+ const detail = error.data && typeof error.data === "object" && "details" in error.data
96
+ ? `:${String(error.data.details)}`
97
+ : "";
98
+ entry.reject(new Error(`${error.message ?? `${prefix} 请求失败`}${detail}`));
99
+ }
100
+ else
101
+ entry.resolve(message.result);
102
+ };
103
+ const wire = (process) => {
104
+ process.stdout.setEncoding("utf8");
105
+ process.stdout.on("data", (chunk) => {
106
+ buffer += chunk;
107
+ let index;
108
+ while ((index = buffer.indexOf("\n")) >= 0) {
109
+ const line = buffer.slice(0, index);
110
+ buffer = buffer.slice(index + 1);
111
+ handleLine(line);
112
+ }
113
+ });
114
+ process.stderr.setEncoding("utf8");
115
+ process.stderr.on("data", (chunk) => {
116
+ const text = chunk.trim();
117
+ // 两边都会在 stderr 打启动信息,别把日志刷满
118
+ if (text !== "" && !/^(WARNING|note:)/.test(text))
119
+ options.log?.(`${prefix}: ${text.slice(0, 400)}`);
120
+ });
121
+ const onGone = (reason) => {
122
+ if (child !== process)
123
+ return;
124
+ child = null;
125
+ initialized = false;
126
+ version = null;
127
+ starting = null;
128
+ buffer = "";
129
+ const error = new Error(reason);
130
+ rejectAll(error);
131
+ for (const listener of exitListeners)
132
+ listener(error);
133
+ };
134
+ process.on("exit", (code) => onGone(`${prefix} 已退出(code ${code ?? "?"})`));
135
+ process.on("error", (error) => onGone(`${prefix} 启动失败:${error.message}`));
136
+ };
137
+ const requestRaw = (method, params, timeoutMs = baseTimeout) => {
138
+ const id = ++seq;
139
+ return new Promise((resolve, reject) => {
140
+ const timer = setTimeout(() => {
141
+ pending.delete(id);
142
+ reject(new Error(`${prefix} 请求超时:${method}`));
143
+ }, timeoutMs);
144
+ pending.set(id, { method, resolve: (value) => resolve(value), reject, timer });
145
+ try {
146
+ send({ id, method, params: params ?? {} });
147
+ }
148
+ catch (error) {
149
+ pending.delete(id);
150
+ clearTimeout(timer);
151
+ reject(error instanceof Error ? error : new Error(String(error)));
152
+ }
153
+ });
154
+ };
155
+ const ensureStarted = async () => {
156
+ if (initialized && child)
157
+ return;
158
+ if (starting)
159
+ return starting;
160
+ starting = (async () => {
161
+ if (!child) {
162
+ const env = { ...process.env };
163
+ for (const [key, value] of Object.entries(options.env ?? {})) {
164
+ if (value === undefined)
165
+ delete env[key];
166
+ else
167
+ env[key] = value;
168
+ }
169
+ const next = spawn(options.bin, options.args, { env, cwd: options.cwd, stdio: ["pipe", "pipe", "pipe"] });
170
+ child = next;
171
+ wire(next);
172
+ }
173
+ const result = await options.handshake((method, params) => requestRaw(method, params));
174
+ version = result.version ?? null;
175
+ initialized = true;
176
+ })();
177
+ try {
178
+ await starting;
179
+ }
180
+ catch (error) {
181
+ const process = child;
182
+ child = null;
183
+ initialized = false;
184
+ version = null;
185
+ process?.kill("SIGTERM");
186
+ throw error instanceof Error ? error : new Error(String(error));
187
+ }
188
+ finally {
189
+ starting = null;
190
+ }
191
+ };
192
+ return {
193
+ async request(method, params, requestOptions) {
194
+ await ensureStarted();
195
+ return requestRaw(method, params, requestOptions?.timeoutMs);
196
+ },
197
+ notify: (method, params) => {
198
+ try {
199
+ // 通知也要求「已连接」:没连上就没人收,静默丢掉即可
200
+ if (!child)
201
+ return;
202
+ send({ method, params: params ?? {} });
203
+ }
204
+ catch (error) {
205
+ options.log?.(`${prefix}: 通知发送失败(${method}):${error instanceof Error ? error.message : String(error)}`);
206
+ }
207
+ },
208
+ respond,
209
+ respondError: (id, code, message) => {
210
+ try {
211
+ send({ id, error: { code, message } });
212
+ }
213
+ catch {
214
+ /* 进程没了就算了 */
215
+ }
216
+ },
217
+ onNotification: (listener) => {
218
+ notificationListeners.add(listener);
219
+ return () => notificationListeners.delete(listener);
220
+ },
221
+ onServerRequest: (listener) => {
222
+ serverRequestListeners.add(listener);
223
+ return () => serverRequestListeners.delete(listener);
224
+ },
225
+ onExit: (listener) => {
226
+ exitListeners.add(listener);
227
+ return () => exitListeners.delete(listener);
228
+ },
229
+ ensureStarted,
230
+ version: () => version,
231
+ isReady: () => initialized && child !== null,
232
+ close: () => {
233
+ const process = child;
234
+ child = null;
235
+ initialized = false;
236
+ version = null;
237
+ rejectAll(new Error(`${prefix} 已关闭`));
238
+ process?.kill("SIGTERM");
239
+ },
240
+ };
241
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * 让「刚加进去的自定义 provider / 模型」立刻对 pi 生效。
3
+ *
4
+ * 为什么需要这一层(实测出来的):
5
+ * - pi 的模型注册表是**进程内缓存**;
6
+ * - `ModelRuntime.refresh()` 只重新拉「动态 provider 的远端目录」,
7
+ * 不会重新读 `models.json` 里的 provider 定义;
8
+ * - 于是「界面上加了 abc,但模型选择器里没有」—— 因为活着的会话里根本没有这个 provider。
9
+ *
10
+ * 所以除了重建预热会话,还要对**每个活着的会话**调 `registerProvider()` 把配置推过去。
11
+ * 这些都是 pi 的公开 API(registerProvider / unregisterProvider)。
12
+ */
13
+ import { readFile } from "node:fs/promises";
14
+ import { join } from "node:path";
15
+ import { setPriceIndex } from "../util/pricing-index.js";
16
+ async function readJson(path, fallback) {
17
+ try {
18
+ return JSON.parse(await readFile(path, "utf8"));
19
+ }
20
+ catch {
21
+ return fallback;
22
+ }
23
+ }
24
+ /** 把 pi 的两份配置读成「可直接 registerProvider 的形状」 */
25
+ export async function readProviderConfigs(agentDir) {
26
+ const catalog = await readJson(join(agentDir, "models.json"), {});
27
+ const store = await readJson(join(agentDir, "models-store.json"), {});
28
+ const auth = await readJson(join(agentDir, "auth.json"), {});
29
+ const configs = [];
30
+ for (const [id, entry] of Object.entries(catalog.providers ?? {})) {
31
+ configs.push({
32
+ id,
33
+ ...(typeof entry.api === "string" ? { api: entry.api } : {}),
34
+ ...(typeof entry.baseUrl === "string" ? { baseUrl: entry.baseUrl } : {}),
35
+ ...(typeof entry.apiKey === "string" ? { apiKey: entry.apiKey } : {}),
36
+ ...(entry.headers ? { headers: entry.headers } : {}),
37
+ models: entry.models ?? [],
38
+ });
39
+ }
40
+ for (const [id, entry] of Object.entries(store)) {
41
+ const models = Array.isArray(entry.models) ? entry.models : [];
42
+ const template = models.find((model) => model.api && model.baseUrl);
43
+ if (!template)
44
+ continue;
45
+ configs.push({
46
+ id,
47
+ api: template.api,
48
+ baseUrl: template.baseUrl,
49
+ ...(auth[id]?.key ? { apiKey: auth[id].key } : {}),
50
+ ...(template.headers ? { headers: template.headers } : {}),
51
+ models,
52
+ });
53
+ }
54
+ return configs;
55
+ }
56
+ /** pi 认的模型字段(缺省给安全值,避免 provider 被拒) */
57
+ function toProviderModel(model) {
58
+ return {
59
+ id: model.id,
60
+ name: model.name ?? model.id,
61
+ reasoning: model.reasoning === true,
62
+ input: Array.isArray(model.input) ? model.input : ["text"],
63
+ contextWindow: model.contextWindow ?? 128_000,
64
+ maxTokens: model.maxTokens ?? 8_192,
65
+ cost: model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
66
+ ...(model.headers ? { headers: model.headers } : {}),
67
+ };
68
+ }
69
+ /** 我们通过 registerProvider 推给运行时的 provider(用来在改名/删除后注销旧的) */
70
+ const pushed = new Set();
71
+ export async function reloadModelRegistry(sessions, prewarmId, prewarm, agentDir) {
72
+ const warm = sessions.get(prewarmId);
73
+ if (warm) {
74
+ try {
75
+ warm.unsubscribe();
76
+ warm.session.dispose();
77
+ }
78
+ catch {
79
+ // 预热会话无关紧要,dispose 失败忽略
80
+ }
81
+ sessions.delete(prewarmId);
82
+ }
83
+ await prewarm();
84
+ const configs = await readProviderConfigs(agentDir);
85
+ // 分时价格索引(ADR-0026 D 期):反正这两份配置刚读过,顺手把「时段 → 价格」也刷一遍,
86
+ // 不然用户刚改完价格,下一轮的消息花费还是按旧价算的。
87
+ setPriceIndex(configs.map((config) => ({ provider: config.id, models: config.models })));
88
+ for (const handle of sessions.values()) {
89
+ const runtime = handle.session.modelRuntime;
90
+ await runtime?.refresh?.({ allowNetwork: false }).catch(() => undefined);
91
+ // 配置里已经没有的(改名 / 删除):从运行时注销,否则旧名字会一直挂在模型表里。
92
+ // 先收集再删:在遍历 `pushed` 时 delete 会让后续会话跳过这些 id(旧的注销不干净)。
93
+ const stale = [...pushed].filter((id) => !configs.some((config) => config.id === id));
94
+ for (const id of stale)
95
+ runtime?.unregisterProvider?.(id);
96
+ if (sessions.size > 0)
97
+ for (const id of stale)
98
+ pushed.delete(id);
99
+ for (const config of configs) {
100
+ try {
101
+ runtime?.registerProvider?.(config.id, {
102
+ ...(config.api ? { api: config.api } : {}),
103
+ ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
104
+ ...(config.apiKey ? { apiKey: config.apiKey } : {}),
105
+ ...(config.headers ? { headers: config.headers } : {}),
106
+ models: config.models.map(toProviderModel),
107
+ });
108
+ pushed.add(config.id);
109
+ }
110
+ catch {
111
+ // 单个 provider 注册失败不该拖垮整次刷新(它只是不出现在列表里)
112
+ }
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 教模型「动手之前先说人话」。
3
+ *
4
+ * 为什么必须有:对话区里的「过程」容器默认是**折叠**的 —— 用户一眼只看到
5
+ * 「1 段思考 1 次工具 过程 2 步」。模型要是连着调五个工具、一句正文都不写,
6
+ * 用户全程不知道它在查什么、改什么、碰到什么问题,只能干等。
7
+ *
8
+ * 所以:每一步动作前先给一两句「我发现了什么 / 我准备做什么」,
9
+ * 工具结果推翻计划时也说一句。界面侧同时有「正在运行 bash…」那行实时状态(见 LiveActivity),
10
+ * 两者合起来才是「看得懂在跑什么」。
11
+ *
12
+ * 同样写得短:这段会在**每一次**请求里重复计费。
13
+ */
14
+ export const NARRATE_GUIDANCE = [
15
+ "## Narrate before acting",
16
+ "The user watches this conversation live, and tool cards are collapsed by default — they only see",
17
+ '"1 thinking segment, 1 tool call" unless you write prose. So:',
18
+ "- Before the first tool call of a task, write 1-2 sentences: what you found and what you are about to do.",
19
+ "- Before modifying, writing or deleting files, say which files and why (1-2 sentences), then call the tools.",
20
+ "- When a tool result changes your plan (failure, unexpected output, a discovery), say what it means before the next call.",
21
+ "- Keep it short and in the user's language; never paste tool arguments back as prose.",
22
+ ].join("\n");