oh-my-agent-web 0.3.0 → 0.4.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 (250) hide show
  1. package/README.md +86 -455
  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 +61 -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/pi-events.js +148 -0
  26. package/dist/server/engine/pi-rename.js +14 -0
  27. package/dist/server/engine/pi-sdk.js +343 -0
  28. package/dist/server/engine/pi-session-model.js +23 -0
  29. package/dist/server/engine/pi-thinking.js +34 -0
  30. package/dist/server/engine/pi-transcript.js +225 -0
  31. package/dist/server/engine/pi-version.js +11 -0
  32. package/dist/server/engine/port.js +1 -0
  33. package/dist/server/engine/registry.js +47 -0
  34. package/dist/server/engine/scratch.js +13 -0
  35. package/dist/server/engine/suggestion-guidance.js +9 -0
  36. package/dist/server/engine/switchable.js +22 -0
  37. package/dist/server/engine/wiring.js +107 -0
  38. package/dist/server/files/dir-routes.js +40 -0
  39. package/dist/server/files/file-search.js +63 -0
  40. package/dist/server/files/file-service.js +232 -0
  41. package/dist/server/files/files-routes.js +182 -0
  42. package/dist/server/files/git-info.js +186 -0
  43. package/dist/server/http/auth-routes.js +99 -0
  44. package/dist/server/http/auth.js +191 -0
  45. package/dist/server/http/aux-routes.js +24 -0
  46. package/dist/server/http/compress.js +75 -0
  47. package/dist/server/http/health-and-static.js +33 -0
  48. package/dist/server/http/live-routes.js +113 -0
  49. package/dist/server/http/login-page.js +200 -0
  50. package/dist/server/index.js +342 -520
  51. package/dist/server/markers/actions.js +262 -0
  52. package/dist/server/protocol-parse.js +201 -0
  53. package/dist/server/protocol-settings.js +1 -0
  54. package/dist/server/protocol.js +6 -0
  55. package/dist/server/providers/catalog-store.js +85 -0
  56. package/dist/server/providers/catalog-writes.js +197 -0
  57. package/dist/server/providers/catalog.js +77 -0
  58. package/dist/server/providers/enabled-models.js +118 -0
  59. package/dist/server/providers/import-models.js +69 -0
  60. package/dist/server/providers/import-routes.js +136 -0
  61. package/dist/server/providers/model-test.js +52 -0
  62. package/dist/server/providers/probe.js +64 -0
  63. package/dist/server/providers/provider-handlers.js +203 -0
  64. package/dist/server/providers/provider-store.js +327 -0
  65. package/dist/server/providers/request-headers.js +7 -0
  66. package/dist/server/providers/validate.js +241 -0
  67. package/dist/server/push/push-routes.js +114 -0
  68. package/dist/server/push/push.js +120 -0
  69. package/dist/server/session/approval-bridge.js +103 -0
  70. package/dist/server/session/attachment-resolve.js +21 -0
  71. package/dist/server/session/client-view.js +82 -0
  72. package/dist/server/session/conversation-create.js +65 -0
  73. package/dist/server/session/delete-session.js +84 -0
  74. package/dist/server/session/engine-switch.js +108 -0
  75. package/dist/server/session/lock.js +82 -0
  76. package/dist/server/session/model-controller.js +111 -0
  77. package/dist/server/session/notices.js +31 -0
  78. package/dist/server/session/pi-opener.js +123 -0
  79. package/dist/server/session/pi-refresh.js +51 -0
  80. package/dist/server/session/pi-sessions-provider.js +46 -0
  81. package/dist/server/session/pi-sessions.js +272 -0
  82. package/dist/server/session/refresh-actions.js +42 -0
  83. package/dist/server/session/rename-session.js +23 -0
  84. package/dist/server/session/runtime-services.js +28 -0
  85. package/dist/server/session/runtime-types.js +1 -0
  86. package/dist/server/session/runtime.js +358 -0
  87. package/dist/server/session/session-stats.js +88 -0
  88. package/dist/server/session/settings.js +367 -0
  89. package/dist/server/session/snapshot-for.js +7 -0
  90. package/dist/server/session/stats-refresher.js +50 -0
  91. package/dist/server/session/switch-conversation.js +86 -0
  92. package/dist/server/session/throttle.js +14 -0
  93. package/dist/server/session/turn-events.js +126 -0
  94. package/dist/server/session/turn-finish.js +79 -0
  95. package/dist/server/session/turn-markers.js +26 -0
  96. package/dist/server/session/turn-queue.js +111 -0
  97. package/dist/server/session/turn-send.js +88 -0
  98. package/dist/server/session/turn-watchdog.js +77 -0
  99. package/dist/server/sessions/session-routes.js +17 -0
  100. package/dist/server/skills/skill-routes.js +28 -0
  101. package/dist/server/skills/skill-store.js +134 -0
  102. package/dist/server/snapshot/fan-out.js +10 -0
  103. package/dist/server/snapshot/messages.js +75 -0
  104. package/dist/server/snapshot/sender.js +62 -0
  105. package/dist/server/snapshot/state.js +193 -0
  106. package/dist/server/terminal/terminal-routes.js +176 -0
  107. package/dist/server/terminal/terminal-session.js +140 -0
  108. package/dist/server/uploads/upload-routes.js +64 -0
  109. package/dist/server/uploads/upload-store.js +115 -0
  110. package/dist/server/util/data-dir.js +36 -0
  111. package/dist/server/util/env.js +140 -0
  112. package/dist/server/util/ids.js +28 -0
  113. package/dist/server/util/log-file.js +67 -0
  114. package/dist/server/util/log.js +52 -0
  115. package/dist/server/util/origin.js +75 -0
  116. package/dist/server/util/paths.js +60 -0
  117. package/dist/server/util/pricing-index.js +121 -0
  118. package/dist/server/util/pricing.js +214 -0
  119. package/dist/server/util/startup.js +34 -0
  120. package/dist/server/util/upstream-version.js +140 -0
  121. package/dist/server/util/usage.js +19 -0
  122. package/dist/server/util/version.js +50 -0
  123. package/dist/server/ws/handle-message.js +95 -0
  124. package/package.json +49 -63
  125. package/web/dist/apple-touch-icon-precomposed.png +0 -0
  126. package/web/dist/apple-touch-icon.png +0 -0
  127. package/web/dist/assets/abnfDiagram-WEYOIA3C-NLp09iEC.js +1 -0
  128. package/web/dist/assets/arc-DviL9o3c.js +1 -0
  129. package/web/dist/assets/architecture-WOLXFQ4H-fjsM_Sy3.js +1 -0
  130. package/web/dist/assets/architectureDiagram-ZHVYKT53-Cn3B4TE8.js +36 -0
  131. package/web/dist/assets/array-BifhSqXX.js +1 -0
  132. package/web/dist/assets/blockDiagram-RJAE7QA3-CIuvxWvi.js +140 -0
  133. package/web/dist/assets/c4Diagram-XDPPPE54-DvdKmEpF.js +38 -0
  134. package/web/dist/assets/channel-C3AQ6nAs.js +1 -0
  135. package/web/dist/assets/chunk-2E7WHVMD-HNNASGCS.js +88 -0
  136. package/web/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  137. package/web/dist/assets/chunk-4EMNKR2T-Cg-eRwBE.js +1 -0
  138. package/web/dist/assets/chunk-55XCU76K-BweiisZL.js +1 -0
  139. package/web/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  140. package/web/dist/assets/chunk-7S6XSTFO-BCzIiP-6.js +2 -0
  141. package/web/dist/assets/chunk-AQDTSKZB-Dl0NRFuU.js +72 -0
  142. package/web/dist/assets/chunk-DSNLSHJZ-zhHCjjN9.js +129 -0
  143. package/web/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  144. package/web/dist/assets/chunk-HAGFOTED-BDQqU2EH.js +62 -0
  145. package/web/dist/assets/chunk-HNE4Q36P-B-qRzMjX.js +1 -0
  146. package/web/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  147. package/web/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  148. package/web/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  149. package/web/dist/assets/chunk-NQOKRVIF-CNcvhMn_.js +213 -0
  150. package/web/dist/assets/chunk-UHW7PFBP-Dwr8LUzr.js +1 -0
  151. package/web/dist/assets/chunk-VQ5W62QI-O8sN6jJu.js +1 -0
  152. package/web/dist/assets/chunk-WQQL4CDK-B6-59Zg2.js +10 -0
  153. package/web/dist/assets/chunk-XXDRQBXY-DvSbeUlZ.js +1 -0
  154. package/web/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  155. package/web/dist/assets/chunk-YZO67HFG-BXpKYKsw.js +1 -0
  156. package/web/dist/assets/chunk-ZAUX2LTX-B1Z5VSkz.js +1 -0
  157. package/web/dist/assets/chunk-ZCHMQSOR-h_5mBLIA.js +2 -0
  158. package/web/dist/assets/classDiagram-v2-4MYWR3UA-Dr6JnaaV.js +217 -0
  159. package/web/dist/assets/cose-bilkent-JH36ORCC-36ShkR7R.js +1 -0
  160. package/web/dist/assets/cynefin-EF2NZ3EQ-Dg6bI7pC.js +1 -0
  161. package/web/dist/assets/cynefinDiagram-WF24UDRP-B0YHpBlR.js +62 -0
  162. package/web/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
  163. package/web/dist/assets/dagre-NK4GIZVX-Cx3mn7YS.js +4 -0
  164. package/web/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  165. package/web/dist/assets/diagram-CBK3MWTC-l8aZG_Bm.js +24 -0
  166. package/web/dist/assets/diagram-I5XIIV2D-Bd5f14_h.js +24 -0
  167. package/web/dist/assets/diagram-ODIL2UCW-hBsxCYUj.js +30 -0
  168. package/web/dist/assets/diagram-PFBEJUXE-BgpIBUYl.js +3 -0
  169. package/web/dist/assets/diagram-RJA3I4VV-CDU1gUVD.js +41 -0
  170. package/web/dist/assets/diagram-TYUNFSNR-Iuxh7Tqf.js +200 -0
  171. package/web/dist/assets/dist-C86cKTuE.js +1 -0
  172. package/web/dist/assets/ebnfDiagram-V4P3TTPT-Cms0HFKh.js +1 -0
  173. package/web/dist/assets/elk-QONTBCTQ-B7yEy0PQ.js +27 -0
  174. package/web/dist/assets/erDiagram-KYQIGQAA-CL48j4aU.js +99 -0
  175. package/web/dist/assets/eventmodeling-K75KTNOO-CFvkaejh.js +1 -0
  176. package/web/dist/assets/flowDiagram-IUGLLWBU-6UyKXpqG.js +1 -0
  177. package/web/dist/assets/ganttDiagram-R7GPNTJY-vdMr-0ZV.js +292 -0
  178. package/web/dist/assets/gitGraph-VSP46ZUC-2z85Wuwo.js +1 -0
  179. package/web/dist/assets/gitGraphDiagram-PDYL7XHY-BY4GPiqC.js +106 -0
  180. package/web/dist/assets/graphlib-DS17s2tU.js +1 -0
  181. package/web/dist/assets/index-Bjn56a2i.js +76 -0
  182. package/web/dist/assets/index-Drp6e5iU.css +1 -0
  183. package/web/dist/assets/info-OHQRW6UA-ECzkYTx-.js +1 -0
  184. package/web/dist/assets/infoDiagram-MVDRKAX5-DDk5EAeb.js +2 -0
  185. package/web/dist/assets/init-C-OQMol4.js +1 -0
  186. package/web/dist/assets/ishikawaDiagram-HPEHV2GE-9yPH4J94.js +70 -0
  187. package/web/dist/assets/journeyDiagram-BOSHW7O7-BPTIap3l.js +139 -0
  188. package/web/dist/assets/kanban-definition-FNSA4HC5-BbADHrA8.js +89 -0
  189. package/web/dist/assets/katex-B_JcIwqj.js +257 -0
  190. package/web/dist/assets/line-CHV9_4lL.js +1 -0
  191. package/web/dist/assets/linear-C-eZsBTv.js +1 -0
  192. package/web/dist/assets/mermaid-parser.core-BiAhX1l1.js +7 -0
  193. package/web/dist/assets/mermaid.core-BxP_A_zj.js +44 -0
  194. package/web/dist/assets/mindmap-definition-6OY57UG3-BMWlit95.js +98 -0
  195. package/web/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  196. package/web/dist/assets/packet-JDAUHWVQ-FVApuKOp.js +1 -0
  197. package/web/dist/assets/path-fybaL0A-.js +1 -0
  198. package/web/dist/assets/pegDiagram-ECIUJNBX-WjWJvDHM.js +1 -0
  199. package/web/dist/assets/pie-XZMESJXO-BGCZnvhp.js +1 -0
  200. package/web/dist/assets/pieDiagram-GNMPY74T-DJpAC_JC.js +39 -0
  201. package/web/dist/assets/quadrantDiagram-D57K4MC3-hxx5c7oq.js +7 -0
  202. package/web/dist/assets/radar-ABXABTNO-BU9Shc-O.js +1 -0
  203. package/web/dist/assets/railroad-I3PHUGI6-pWj8d-G4.js +1 -0
  204. package/web/dist/assets/railroad-abnf-LNEFI6M7-D7HZBTAj.js +1 -0
  205. package/web/dist/assets/railroad-ebnf-DWYD2UWJ-CKiXJSuM.js +1 -0
  206. package/web/dist/assets/railroad-peg-JR7BVNR7-DB3VJHgR.js +1 -0
  207. package/web/dist/assets/railroadDiagram-Y72NRBL7-Cev7Y65k.js +1 -0
  208. package/web/dist/assets/requirementDiagram-EQYSW5RA-CqbwMLD8.js +84 -0
  209. package/web/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  210. package/web/dist/assets/sankeyDiagram-SY24JHO5-V2cyVi8q.js +40 -0
  211. package/web/dist/assets/sequenceDiagram-BLEHNHQU-By6NJOPM.js +169 -0
  212. package/web/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  213. package/web/dist/assets/src-Da91fG3g.js +1 -0
  214. package/web/dist/assets/stateDiagram-v2-A6IESKWZ-BnPGX5UI.js +291 -0
  215. package/web/dist/assets/swimlanes-B3ENRKFQ-C58BpIpm.js +1 -0
  216. package/web/dist/assets/swimlanesDiagram-AYQRV4AK-RaivxSsc.js +8 -0
  217. package/web/dist/assets/timeline-definition-JXYKGLGB-Hgo1DhnS.js +120 -0
  218. package/web/dist/assets/treeView-D4JQ5SDB-DF0dfjqB.js +1 -0
  219. package/web/dist/assets/treemap-SAJKECNS-CSSM5FqB.js +1 -0
  220. package/web/dist/assets/usecaseDiagram-WPOLBNNX-BkBBD1NY.js +328 -0
  221. package/web/dist/assets/vennDiagram-Q3Y3IDCW-Bm28keX_.js +34 -0
  222. package/web/dist/assets/wardley-7MLQ67FV-By7KgEQX.js +1 -0
  223. package/web/dist/assets/wardleyDiagram-IVAIDKWA-oVtIrnGa.js +78 -0
  224. package/web/dist/assets/xychartDiagram-4URNHDU4-aI8FH4g2.js +7 -0
  225. package/web/dist/favicon-32.png +0 -0
  226. package/web/dist/favicon.ico +0 -0
  227. package/web/dist/favicon.svg +20 -8
  228. package/web/dist/icons/apple-touch-icon-180.png +0 -0
  229. package/web/dist/icons/icon-192.png +0 -0
  230. package/web/dist/icons/icon-512.png +0 -0
  231. package/web/dist/icons/icon-maskable-512.png +0 -0
  232. package/web/dist/index.html +21 -27
  233. package/web/dist/manifest.webmanifest +17 -0
  234. package/web/dist/maskable.svg +25 -0
  235. package/web/dist/sw.js +40 -0
  236. package/bin/oh-my-agent-web-ps.mjs +0 -203
  237. package/bin/oh-my-agent-web-ws.mjs +0 -782
  238. package/bin/oh-my-agent-web.mjs +0 -321
  239. package/bin/update-check.mjs +0 -142
  240. package/dist/server/codex-client.js +0 -310
  241. package/dist/server/codex-supervisor.js +0 -446
  242. package/dist/server/fs-service.js +0 -144
  243. package/dist/server/request-inspector.js +0 -458
  244. package/dist/shared/protocol.js +0 -112
  245. package/scripts/manage-server.mjs +0 -155
  246. package/scripts/omaw.mjs +0 -74
  247. package/shared/protocol.ts +0 -228
  248. package/web/dist/assets/index-5LJ5fRcO.js +0 -42
  249. package/web/dist/assets/index-QiDfPKFo.css +0 -1
  250. package/web/dist/assets/markdown-CYXe6JZx.js +0 -55
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 命令行入口(ADR-0031):`oh-my-agent-web` / 简写 **`omaw`** / **`agent-web`** [命令] [选项]。
4
+ * 三个 bin 指向同一个文件,完全等价(`agent-web` 给不想记缩写的人用)。
5
+ *
6
+ * 命令:start(默认)/ stop / restart / status / version / upgrade / help
7
+ *
8
+ * 为什么要有它:以前只能用 `npm start`,进程一多就「端口被占」,而停掉旧进程得自己
9
+ * `lsof`/`kill`。这里把常用三件事收进一条命令,并且复用 `server.lock`(ADR-0007)
10
+ * 里记的 pid —— 不去猜、不去扫端口。
11
+ *
12
+ * 选项(也可用同名环境变量,命令行优先):
13
+ * -p, --port <n> AGENT_WEB_PORT 默认 25257
14
+ * -H, --host <addr> AGENT_WEB_HOST 默认 127.0.0.1
15
+ * -P, --password <pwd> AGENT_WEB_PASSWORD 非本机访问时的登录密码
16
+ * --allow-host <h> AGENT_WEB_ALLOWED_HOSTS 额外允许的域名(可重复)
17
+ * --data-dir <dir> AGENT_WEB_DATA_DIR 默认 ~/.oh-my-agent-web
18
+ * -d, --detach 启动后转入后台(关掉终端也继续跑)
19
+ *
20
+ * 后台模式只是「spawn 一个脱离终端的自己 + 等它写好 server.lock」:
21
+ * 真正的事实源仍然是锁文件,所以 stop / restart 对前台、后台一视同仁。
22
+ *
23
+ * 没给的选项会从 `<dataDir>/password`、`<dataDir>/allowed-hosts` 兜底(见 cli-config.ts):
24
+ * 这样 `restart` 少带参数也不会把远程访问关掉。
25
+ */
26
+ import { spawn } from "node:child_process";
27
+ import { closeSync, mkdirSync, openSync, readFileSync, realpathSync } from "node:fs";
28
+ import { homedir } from "node:os";
29
+ import { dirname, join, resolve } from "node:path";
30
+ import process from "node:process";
31
+ import { fileURLToPath } from "node:url";
32
+ import { applyStoredOptions } from "./cli-config.js";
33
+ import { migrateLegacyDataDir } from "./util/data-dir.js";
34
+ import { DEFAULT_DATA_DIR_NAME } from "./util/env.js";
35
+ import { fetchHealth, showStatus, showVersion, upgrade } from "./cli-info.js";
36
+ import { defaultIsAlive, readLock } from "./session/lock.js";
37
+ const USAGE = `用法: agent-web [命令] [选项] (agent-web / omaw / oh-my-agent-web 三个命令完全等价)
38
+
39
+ 命令:
40
+ start 启动(默认;前台运行,Ctrl-C 退出)
41
+ start -d 启动并转入后台(关掉终端也继续跑,日志写到 <dataDir>/daemon.log)
42
+ stop 停止正在跑的实例(读 dataDir/server.lock 里的 pid)
43
+ restart 先停再起(带 -d 则后台起)
44
+ status 看状态:pid / 端口 / 已运行多久 / 引擎 / 会话数 / 工作区
45
+ version 看版本号(-v / --version 同义)
46
+ upgrade 升级全局安装(npm i -g <包名>@latest;private 包会拒绝并提示怎么做)
47
+ help 显示本帮助(-h / --help 同义)
48
+
49
+ 选项(也可以用环境变量):
50
+ -p, --port <n> 监听端口 AGENT_WEB_PORT(默认 25257)
51
+ -H, --host <addr> 监听地址 AGENT_WEB_HOST(默认 127.0.0.1)
52
+ -P, --password <pwd> 访问密码 AGENT_WEB_PASSWORD
53
+ --allow-host <h> 额外允许的域名 AGENT_WEB_ALLOWED_HOSTS(可多次 / 逗号分隔)
54
+ --data-dir <dir> 数据目录 AGENT_WEB_DATA_DIR(默认 ~/.oh-my-agent-web)
55
+ -d, --detach 启动后转入后台(日志写到 <dataDir>/daemon.log)
56
+
57
+ 没给 --password / --allow-host 时,会读数据目录里的 password / allowed-hosts 兜底
58
+ (设一次就一直在,重启不必重复输入)。
59
+
60
+ 例:
61
+ omaw -d -H 0.0.0.0 -P 你的密码 --allow-host agent.example.com # 后台起,供外网访问
62
+ omaw status && omaw restart -d # 看完状态再重启
63
+ omaw stop # 停掉(前台/后台都行)`;
64
+ export function parseArgs(argv) {
65
+ const env = {};
66
+ let command = "start";
67
+ let help = false;
68
+ let detach = false;
69
+ for (let i = 0; i < argv.length; i += 1) {
70
+ const arg = argv[i] ?? "";
71
+ const takeValue = () => {
72
+ const value = argv[i + 1];
73
+ if (value === undefined || value.startsWith("-"))
74
+ throw new Error(`${arg} 缺少参数值`);
75
+ i += 1;
76
+ return value;
77
+ };
78
+ if (arg === "start" || arg === "stop" || arg === "restart")
79
+ command = arg;
80
+ else if (arg === "status" || arg === "stat" || arg === "ps")
81
+ command = "status";
82
+ else if (arg === "version" || arg === "-v" || arg === "--version")
83
+ command = "version";
84
+ else if (arg === "upgrade" || arg === "up")
85
+ command = "upgrade";
86
+ else if (arg === "-h" || arg === "--help" || arg === "help")
87
+ help = true;
88
+ else if (arg === "-d" || arg === "--detach" || arg === "--daemon")
89
+ detach = true;
90
+ else if (arg === "-p" || arg === "--port")
91
+ env.AGENT_WEB_PORT = takeValue();
92
+ else if (arg === "-H" || arg === "--host")
93
+ env.AGENT_WEB_HOST = takeValue();
94
+ else if (arg === "-P" || arg === "--password")
95
+ env.AGENT_WEB_PASSWORD = takeValue();
96
+ else if (arg === "--allow-host") {
97
+ const value = takeValue();
98
+ env.AGENT_WEB_ALLOWED_HOSTS = [env.AGENT_WEB_ALLOWED_HOSTS, value].filter(Boolean).join(",");
99
+ }
100
+ else if (arg === "--data-dir")
101
+ env.AGENT_WEB_DATA_DIR = resolve(takeValue());
102
+ else if (arg.startsWith("-"))
103
+ throw new Error(`未知选项:${arg}`);
104
+ else
105
+ throw new Error(`未知命令:${arg}`);
106
+ }
107
+ return { command, env, help, detach };
108
+ }
109
+ function dataDir() {
110
+ const fromEnv = process.env.AGENT_WEB_DATA_DIR;
111
+ return fromEnv && fromEnv !== "" ? resolve(fromEnv) : join(homedir(), DEFAULT_DATA_DIR_NAME);
112
+ }
113
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
114
+ /** 停掉锁文件里记录的进程;返回是否真的停了一个 */
115
+ async function stopServer() {
116
+ const lock = await readLock(dataDir());
117
+ if (!lock) {
118
+ console.log("没有找到运行中的实例(没有 server.lock)");
119
+ return false;
120
+ }
121
+ if (!defaultIsAlive(lock.pid)) {
122
+ console.log(`锁里的进程 ${lock.pid} 已经不在(陈旧锁,下次启动会自动接管)`);
123
+ return false;
124
+ }
125
+ console.log(`正在停止 pid ${lock.pid}(端口 ${lock.port})…`);
126
+ try {
127
+ process.kill(lock.pid, "SIGTERM");
128
+ }
129
+ catch (error) {
130
+ console.error(`发送 SIGTERM 失败:${error instanceof Error ? error.message : String(error)}`);
131
+ return false;
132
+ }
133
+ // 给它 8 秒优雅退出(回合可能正在落盘转录)
134
+ for (let i = 0; i < 80; i += 1) {
135
+ await sleep(100);
136
+ if (!defaultIsAlive(lock.pid)) {
137
+ console.log("已停止");
138
+ return true;
139
+ }
140
+ }
141
+ console.log("8 秒内没退出,强制结束");
142
+ try {
143
+ process.kill(lock.pid, "SIGKILL");
144
+ }
145
+ catch {
146
+ // 已经不在了就算了
147
+ }
148
+ return true;
149
+ }
150
+ /** 后台模式的日志文件(前台日志另有轮转文件,这个只兜住 stdout/stderr) */
151
+ function daemonLogPath() {
152
+ return join(dataDir(), "daemon.log");
153
+ }
154
+ /** 读文件末尾若干行(后台启动失败时把原因打给用户看) */
155
+ function tailOf(file, lines) {
156
+ try {
157
+ const text = readFileSync(file, "utf8").trimEnd().split("\n");
158
+ return text.slice(-lines).join("\n");
159
+ }
160
+ catch {
161
+ return "(没有日志)";
162
+ }
163
+ }
164
+ /**
165
+ * 后台启动:spawn 一个**脱离终端**的自己,然后等它把 `server.lock` 写出来。
166
+ *
167
+ * 为什么不是 `child.unref()` 就完事:用户需要知道「到底起没起来 / 端口是多少 /
168
+ * 没起来是为什么」,所以这里等锁出现才返回;失败时把 daemon.log 的尾巴打出来。
169
+ * 锁仍然是唯一事实源 —— 所以 stop / restart 对前台、后台一视同仁。
170
+ */
171
+ /** 探活用:0.0.0.0 / :: 这类通配地址要换成能真正连上的本机地址 */
172
+ function probeHostOf() {
173
+ const host = process.env.AGENT_WEB_HOST ?? "127.0.0.1";
174
+ return host === "0.0.0.0" || host === "::" || host === "" ? "127.0.0.1" : host;
175
+ }
176
+ /** 探活:等服务真的开始接请求(拿到锁 ≠ 端口已经在听,中间还要装载会话索引) */
177
+ async function waitForHttp(host, port, deadline) {
178
+ while (Date.now() < deadline) {
179
+ if (await fetchHealth(host, port, 1000))
180
+ return true;
181
+ await sleep(200);
182
+ }
183
+ return false;
184
+ }
185
+ /** 拼出「停止」的准确命令:沿用本次参数(去掉 -d 与命令词),密码一律打成 *** */
186
+ function stopHint(rawArgv) {
187
+ const rest = [];
188
+ for (let i = 0; i < rawArgv.length; i += 1) {
189
+ const arg = rawArgv[i] ?? "";
190
+ if (["-d", "--detach", "--daemon", "start", "restart"].includes(arg))
191
+ continue;
192
+ // 行命令会留在终端滚动区和 shell 历史里,密码不能跟着回显
193
+ if (arg === "-P" || arg === "--password") {
194
+ rest.push(arg, "***");
195
+ i += 1;
196
+ continue;
197
+ }
198
+ rest.push(arg);
199
+ }
200
+ const entry = process.argv[1] ?? "";
201
+ const base = entry.split("/").pop() ?? "oh-my-agent-web";
202
+ const prefix = base === "cli.js" ? `node ${entry}` : base;
203
+ return [prefix, "stop", ...rest].join(" ");
204
+ }
205
+ async function startDetached(rawArgv) {
206
+ const script = fileURLToPath(import.meta.url);
207
+ // 把自己再起一遍,但去掉 -d(否则子进程会继续往后台上套娃)
208
+ const args = rawArgv.filter((arg) => arg !== "-d" && arg !== "--detach" && arg !== "--daemon");
209
+ const log = daemonLogPath();
210
+ mkdirSync(dirname(log), { recursive: true });
211
+ const fd = openSync(log, "a");
212
+ const child = spawn(process.execPath, [script, ...args], {
213
+ detached: true, // 脱离当前终端:关掉终端不会把它一起收走
214
+ stdio: ["ignore", fd, fd],
215
+ env: process.env,
216
+ cwd: process.cwd(),
217
+ });
218
+ child.unref();
219
+ closeSync(fd);
220
+ let exited = false;
221
+ child.once("exit", () => {
222
+ exited = true;
223
+ });
224
+ const probeHost = probeHostOf();
225
+ const deadline = Date.now() + 20_000;
226
+ while (Date.now() < deadline) {
227
+ await sleep(150);
228
+ if (exited)
229
+ break;
230
+ const lock = await readLock(dataDir());
231
+ if (lock && lock.pid === child.pid && defaultIsAlive(lock.pid)) {
232
+ // 拿到锁之后还要等它真的开始接请求(中间要装载 pi 会话索引)
233
+ if (!(await waitForHttp(probeHost, lock.port, deadline)))
234
+ break;
235
+ console.log(`已在后台启动:pid ${child.pid},端口 ${lock.port}`);
236
+ console.log(` → 打开 http://${probeHost}:${lock.port}`);
237
+ console.log(` → 日志 ${log}`);
238
+ console.log(` → 停止:${stopHint(rawArgv)}`);
239
+ return;
240
+ }
241
+ }
242
+ console.error(exited ? "后台启动失败(进程已退出),日志最后几行:" : "后台启动失败(20 秒内没写好 server.lock),日志最后几行:");
243
+ console.error(tailOf(log, 20));
244
+ process.exitCode = 1;
245
+ }
246
+ /** 是否被当脚本执行(npx / 全局安装时 argv[1] 是 bin 软链,必须比 realpath) */
247
+ function invokedDirectly() {
248
+ const entry = process.argv[1];
249
+ if (!entry)
250
+ return false;
251
+ try {
252
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
253
+ }
254
+ catch {
255
+ return false;
256
+ }
257
+ }
258
+ async function main() {
259
+ const parsed = parseArgs(process.argv.slice(2));
260
+ if (parsed.help) {
261
+ console.log(USAGE);
262
+ return;
263
+ }
264
+ Object.assign(process.env, parsed.env);
265
+ // 旧目录(~/.agent-web)→ 新目录(~/.oh-my-agent-web):搬的是密码/订阅/设置/日志/锁
266
+ migrateLegacyDataDir({ dataDir: dataDir(), log: (line) => console.log(line) });
267
+ // 命令行没给的:用数据目录里存的那份补上(远程密码忘了带 = 远程直接 403,踩过)
268
+ await applyStoredOptions(process.env, dataDir());
269
+ if (parsed.command === "status") {
270
+ await showStatus({ dataDir: dataDir(), probeHost: probeHostOf() });
271
+ return;
272
+ }
273
+ if (parsed.command === "version") {
274
+ showVersion();
275
+ return;
276
+ }
277
+ if (parsed.command === "upgrade") {
278
+ process.exitCode = await upgrade();
279
+ return;
280
+ }
281
+ if (parsed.command === "stop") {
282
+ await stopServer();
283
+ return;
284
+ }
285
+ if (parsed.command === "restart") {
286
+ await stopServer();
287
+ // 等端口真正释放再起,否则会撞 EADDRINUSE
288
+ await sleep(400);
289
+ }
290
+ // start / restart:默认**在前台**跑(日志与退出码都在同一条线上)
291
+ if (parsed.detach) {
292
+ await startDetached(process.argv.slice(2));
293
+ return;
294
+ }
295
+ await import("./index.js");
296
+ }
297
+ // 被当脚本执行时才 main(被 import 时什么都不做)
298
+ if (invokedDirectly()) {
299
+ main().catch((error) => {
300
+ console.error(error instanceof Error ? error.message : String(error));
301
+ process.exitCode = 1;
302
+ });
303
+ }
304
+ export { stopServer, USAGE };
@@ -0,0 +1,61 @@
1
+ import { createCodexEngine } from "./codex.js";
2
+ import { createDshEngine } from "./dsh.js";
3
+ import { createFakeEngine } from "./fake.js";
4
+ import { createPiSdkEngine } from "./pi-sdk.js";
5
+ import { createEngineRegistry, FAKE_CAPABILITIES, PI_CAPABILITIES } from "./registry.js";
6
+ export function createBuiltinEngineRegistry(options) {
7
+ return createEngineRegistry([
8
+ {
9
+ name: "pi",
10
+ label: "pi 原生引擎",
11
+ badge: "PI",
12
+ description: "完整插件与技能生态、工具延迟加载、审批门禁、目标模式与子代理",
13
+ providerConfig: true, // 模型 / provider 配置由本工作台管理(models-store.json + auth.json)
14
+ capabilities: PI_CAPABILITIES,
15
+ create: () => createPiSdkEngine({
16
+ onNotice: (text, level) => {
17
+ options.onLog?.(text);
18
+ options.onNotice(text, level);
19
+ },
20
+ }),
21
+ },
22
+ ...(options.codex
23
+ ? [
24
+ {
25
+ name: "codex",
26
+ label: "Codex",
27
+ badge: "CODEX",
28
+ // 能力如实写:审批是命令/文件级的(比 pi 粗一档),没有 provider 面板
29
+ description: options.codex.description ?? "codex 官方 agent:独立 loop 与沙箱,会话与 Codex Desktop / CLI 共用",
30
+ providerConfig: false,
31
+ capabilities: { approvals: true, tools: true, modelSwitch: true, thinkingLevel: true, resume: true },
32
+ create: () => createCodexEngine({ client: options.codex?.client, log: options.onLog }),
33
+ },
34
+ ]
35
+ : []),
36
+ ...(options.dsh
37
+ ? [
38
+ {
39
+ name: "dsh",
40
+ label: "DeepSeek Harness",
41
+ badge: "DSH",
42
+ description: options.dsh.description ??
43
+ "DeepSeek 官方 agent(ACP):模型与思考档可切、权限逐次确认,会话与 dsh web/tui 共用",
44
+ providerConfig: false,
45
+ capabilities: { approvals: true, tools: true, modelSwitch: true, thinkingLevel: true, resume: true },
46
+ create: () => createDshEngine({ client: options.dsh?.client, model: options.dsh?.model, log: options.onLog }),
47
+ },
48
+ ]
49
+ : []),
50
+ {
51
+ name: "fake",
52
+ label: "假引擎",
53
+ badge: "FAKE",
54
+ description: "零 token 的脚本化引擎:没有真实模型,只用来验证协议、工具与审批流程",
55
+ providerConfig: false,
56
+ hidden: true, // 测试引擎:AGENT_WEB_ENGINE=fake 仍可选,但不进普通用户的切换面板
57
+ capabilities: FAKE_CAPABILITIES,
58
+ create: () => createFakeEngine(),
59
+ },
60
+ ]);
61
+ }
@@ -0,0 +1,8 @@
1
+ export function createEngineCatalog(options) {
2
+ const sessions = options.sessions ?? {};
3
+ return {
4
+ descriptors: () => options.registry.descriptors(),
5
+ get: (name) => options.registry.get(name),
6
+ sessionsFor: (name) => sessions[name] ?? null,
7
+ };
8
+ }
@@ -0,0 +1,45 @@
1
+ import { approvalDecision, commandApprovalRequest, fileChangeApprovalRequest } from "./codex-map.js";
2
+ export function registerCodexApprovals(options) {
3
+ const { client } = options;
4
+ client.onServerRequest((request) => {
5
+ const method = request.method;
6
+ const ask = async (build, kind) => {
7
+ if (!options.getHandler()) {
8
+ client.respond(request.id, { decision: "decline" });
9
+ return;
10
+ }
11
+ let decision = "deny";
12
+ try {
13
+ const handler = options.getHandler();
14
+ decision = handler ? await handler(build()) : "deny";
15
+ }
16
+ catch {
17
+ decision = "deny";
18
+ }
19
+ client.respond(request.id, { decision: approvalDecision(decision, kind) });
20
+ };
21
+ if (method === "item/commandExecution/requestApproval") {
22
+ void ask(() => commandApprovalRequest(request.params), "command");
23
+ return;
24
+ }
25
+ if (method === "item/fileChange/requestApproval") {
26
+ void ask(() => fileChangeApprovalRequest(request.params), "file");
27
+ return;
28
+ }
29
+ if (method === "item/tool/requestUserInput") {
30
+ // 工作台还没有「结构化提问」的界面:给空答案,让这一轮继续往下走
31
+ client.respond(request.id, { answers: {} });
32
+ return;
33
+ }
34
+ if (method === "item/tool/call") {
35
+ client.respond(request.id, { contentItems: [], success: false });
36
+ return;
37
+ }
38
+ if (method === "mcpServer/elicitation/request") {
39
+ client.respond(request.id, { action: "decline", content: null, _meta: null });
40
+ return;
41
+ }
42
+ // 认不出的服务端请求必须回包,否则 codex 会一直等它
43
+ client.respondError(request.id, -32601, `agent-web 未实现的服务端请求:${method}`);
44
+ });
45
+ }