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
@@ -1,782 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * oh-my-agent-web ws — 连接 codex app-server WebSocket 的命令行客户端。
4
- *
5
- * 参考 /Users/dds/data/ai/work/codex-ws-client.mjs,整理为 oh-my-agent-web 的子命令:
6
- * oh-my-agent-web ws # 交互模式(Ctrl-C 中断当前 turn / Ctrl-D 退出)
7
- * oh-my-agent-web ws "帮我看看这个目录" # 单次提问后退出
8
- * echo "写个 hello world" | oh-my-agent-web ws # 从管道读
9
- * oh-my-agent-web ws --port 25259 # 指定端口
10
- * oh-my-agent-web ws --url ws://host:port # 指定完整地址
11
- *
12
- * 退出语义:turn 跑在 app-server 上而不在本进程里,所以直接掉线会留下一个没人看的
13
- * 生成过程(继续烧 token)。因此所有退出路径都走 shutdown():先 `turn/interrupt`,
14
- * 再关连接。Ctrl-C 第一次 = 中断+关闭,第二次 = 立即退出。
15
- *
16
- * 仅用 Node 内置能力(WebSocket 需要 Node >= 22),无额外依赖。
17
- */
18
- import readline from "node:readline";
19
- import { pathToFileURL } from "node:url";
20
-
21
- const DEFAULT_HOST = "127.0.0.1";
22
- const DEFAULT_PORT = 25258;
23
-
24
- const HELP = `oh-my-agent-web ws — connect to the codex app-server over WebSocket
25
-
26
- Usage:
27
- oh-my-agent-web ws [options] ["message" ...]
28
-
29
- Connection:
30
- --url <ws://host:port> Full address (also accepts bare host:port). Default ws://${DEFAULT_HOST}:${DEFAULT_PORT}
31
- --addr <host:port> Address + port (same as --host + --port)
32
- --host <host> Host, default ${DEFAULT_HOST}
33
- -p, --port <n> Port, default ${DEFAULT_PORT}
34
- Env: CODEX_WS_URL / CODEX_WS_HOST / CODEX_WS_PORT
35
-
36
- Session:
37
- --cwd <dir> Working directory (default: current dir)
38
- -t, --thread <id> Resume an existing thread
39
- --model <name> Model id (use /model interactively to list/switch)
40
- --sandbox <mode> read-only | workspace-write | danger-full-access
41
- --approval <mode> untrusted | on-request | on-failure | never
42
- -y, --auto-approve Auto-accept approvals (acceptForSession)
43
- --decline Auto-decline approvals
44
- -v, --verbose Print reasoning deltas
45
- --json Print raw event JSON only
46
- --timeout <sec> Max wait for a turn, default 1800
47
- --retry <n> Retry a flagged/transient turn up to n times, default 1 (0 = off)
48
- --retry-delay <ms> Base delay between retries, default 1000 (grows per attempt)
49
- -h, --help Show this help
50
-
51
- Interactive commands (type at the prompt):
52
- /model Pick a model (↑/↓ + Enter), or list available ones
53
- /model <name> Switch the model for subsequent turns
54
- /help Print this help
55
- /quit Exit
56
- `;
57
-
58
- /** Parse `oh-my-agent-web ws [ ... ]` arguments. */
59
- export function parseWsArgs(argv) {
60
- const opts = {
61
- url: null,
62
- host: process.env.CODEX_WS_HOST || DEFAULT_HOST,
63
- port: Number(process.env.CODEX_WS_PORT) || DEFAULT_PORT,
64
- hostExplicit: false,
65
- portExplicit: false,
66
- cwd: process.cwd(),
67
- thread: null,
68
- model: null,
69
- sandbox: null,
70
- approval: null,
71
- autoApprove: false,
72
- autoDecline: false,
73
- verbose: false,
74
- json: false,
75
- timeout: 1800,
76
- retry: 1,
77
- retryDelayMs: 1000,
78
- messages: [],
79
- help: false,
80
- };
81
- for (let i = 0; i < argv.length; i++) {
82
- const a = argv[i];
83
- const next = () => argv[++i];
84
- switch (a) {
85
- case "--url":
86
- opts.url = next();
87
- break;
88
- case "--host":
89
- case "--addr": {
90
- const v = String(next() ?? "").trim().replace(/^\[|\]$/g, "");
91
- const m = v.match(/^(\[?[^\]]+\]?):(\d+)$/);
92
- if (m) {
93
- opts.host = m[1].replace(/^\[|\]$/g, "");
94
- opts.port = Number(m[2]);
95
- opts.hostExplicit = true;
96
- opts.portExplicit = true;
97
- } else if (/^\d+$/.test(v)) {
98
- opts.port = Number(v);
99
- opts.portExplicit = true;
100
- } else {
101
- opts.host = v;
102
- opts.hostExplicit = true;
103
- }
104
- break;
105
- }
106
- case "-p":
107
- case "--port":
108
- opts.port = Number(next());
109
- opts.portExplicit = true;
110
- break;
111
- case "--cwd":
112
- opts.cwd = next();
113
- break;
114
- case "-t":
115
- case "--thread":
116
- opts.thread = next();
117
- break;
118
- case "--model":
119
- opts.model = next();
120
- break;
121
- case "--sandbox":
122
- opts.sandbox = next();
123
- break;
124
- case "--approval":
125
- opts.approval = next();
126
- break;
127
- case "--auto-approve":
128
- case "-y":
129
- opts.autoApprove = true;
130
- break;
131
- case "--decline":
132
- opts.autoDecline = true;
133
- break;
134
- case "--verbose":
135
- case "-v":
136
- opts.verbose = true;
137
- break;
138
- case "--json":
139
- opts.json = true;
140
- break;
141
- case "--timeout":
142
- opts.timeout = Number(next());
143
- break;
144
- case "--retry":
145
- opts.retry = Number(next());
146
- break;
147
- case "--no-retry":
148
- opts.retry = 0;
149
- break;
150
- case "--retry-delay":
151
- opts.retryDelayMs = Number(next());
152
- break;
153
- case "-h":
154
- case "--help":
155
- opts.help = true;
156
- break;
157
- default:
158
- opts.messages.push(a);
159
- }
160
- }
161
- return opts;
162
- }
163
-
164
- /** Normalise a raw address into a ws:// URL (accepts ws/wss/http/https/bare host:port). */
165
- function normalizeWsUrl(raw) {
166
- if (/^wss?:\/\//i.test(raw)) return raw;
167
- if (/^https?:\/\//i.test(raw)) return raw.replace(/^http/i, "ws");
168
- return "ws://" + raw;
169
- }
170
-
171
- /** Combine --url / --host / --port / env into a single ws:// URL.
172
- *
173
- * Priority: --url > --host/--port > env (CODEX_WS_URL / CODEX_WS_HOST / CODEX_WS_PORT) > defaults.
174
- */
175
- export function resolveWsUrl(o) {
176
- const flagUrl = String(o.url ?? "").trim().replace(/^["']|["']$/g, "");
177
- if (flagUrl) return normalizeWsUrl(flagUrl);
178
-
179
- // An explicit --host/--port (or --addr) must beat an env URL.
180
- const explicitHostPort = Boolean(o.hostExplicit || o.portExplicit);
181
- if (!explicitHostPort) {
182
- const envUrl = String(process.env.CODEX_WS_URL ?? "").trim().replace(/^["']|["']$/g, "");
183
- if (envUrl) return normalizeWsUrl(envUrl);
184
- }
185
-
186
- if (!Number.isInteger(o.port) || o.port < 1 || o.port > 65535) {
187
- throw new Error(`invalid port: ${o.port} (expected 1-65535)`);
188
- }
189
- const host = o.host.includes(":") && !o.host.startsWith("[") ? `[${o.host}]` : o.host;
190
- return `ws://${host}:${o.port}`;
191
- }
192
-
193
- class CodexClient {
194
- #listeners = new Map();
195
-
196
- constructor(url) {
197
- this.url = url;
198
- this.ws = null;
199
- this.nextId = 1;
200
- this.pending = new Map();
201
- }
202
-
203
- on(name, fn, { once = false } = {}) {
204
- const list = this.#listeners.get(name) ?? [];
205
- list.push({ fn, once });
206
- this.#listeners.set(name, list);
207
- return () => this.off(name, fn);
208
- }
209
-
210
- off(name, fn) {
211
- const list = this.#listeners.get(name) ?? [];
212
- this.#listeners.set(name, list.filter((l) => l.fn !== fn));
213
- }
214
-
215
- emit(name, detail) {
216
- for (const l of [...(this.#listeners.get(name) ?? [])]) {
217
- if (l.once) this.off(name, l.fn);
218
- try {
219
- l.fn({ detail });
220
- } catch (e) {
221
- log(`[listener error] ${e.stack}`);
222
- }
223
- }
224
- }
225
-
226
- connect() {
227
- return new Promise((resolve, reject) => {
228
- const ws = new WebSocket(this.url);
229
- this.ws = ws;
230
- ws.onopen = () => {
231
- log(`[connected] ${this.url}`);
232
- resolve();
233
- };
234
- ws.onerror = (e) => reject(new Error(`WebSocket connect failed: ${e.message ?? e.type}`));
235
- ws.onclose = (e) => {
236
- this.emit("closed", { code: e.code, reason: e.reason });
237
- for (const { reject: rj } of this.pending.values()) rj(new Error("connection closed"));
238
- this.pending.clear();
239
- };
240
- ws.onmessage = (e) => this.#onMessage(String(e.data));
241
- });
242
- }
243
-
244
- #onMessage(raw) {
245
- let msg;
246
- try {
247
- msg = JSON.parse(raw);
248
- } catch {
249
- return this.emit("raw", raw);
250
- }
251
- // 1) response to one of our requests
252
- if (msg.id !== undefined && msg.method === undefined) {
253
- const p = this.pending.get(msg.id);
254
- if (p) {
255
- this.pending.delete(msg.id);
256
- msg.error ? p.reject(new Error(JSON.stringify(msg.error))) : p.resolve(msg.result);
257
- return;
258
- }
259
- }
260
- // 2) server -> client request (needs a reply)
261
- if (msg.id !== undefined && msg.method) return this.emit("serverRequest", msg);
262
- // 3) notification
263
- this.emit("notification", msg);
264
- }
265
-
266
- send(msg) {
267
- this.ws.send(JSON.stringify(msg));
268
- }
269
-
270
- request(method, params) {
271
- const id = this.nextId++;
272
- return new Promise((resolve, reject) => {
273
- this.pending.set(id, { resolve, reject });
274
- this.send({ method, id, params });
275
- });
276
- }
277
-
278
- respond(id, result) {
279
- this.send({ id, result });
280
- }
281
-
282
- close() {
283
- try {
284
- this.ws?.close();
285
- } catch {
286
- /* ignore */
287
- }
288
- }
289
- }
290
-
291
- const log = (...a) => process.stderr.write(a.join(" ") + "\n");
292
-
293
- /** Run the ws client. Resolves when the session ends. */
294
- export async function runWsClient(argv) {
295
- const opts = parseWsArgs(argv);
296
-
297
- if (opts.help) {
298
- process.stdout.write(HELP);
299
- return;
300
- }
301
-
302
- let url;
303
- try {
304
- url = resolveWsUrl(opts);
305
- } catch (e) {
306
- console.error(`[oh-my-agent-web ws] ${e.message}`);
307
- process.exitCode = 2;
308
- return;
309
- }
310
-
311
- const client = new CodexClient(url);
312
-
313
- /* -------- exit / Ctrl-C --------
314
- * A turn lives on the app-server, not in this process: dropping the socket
315
- * mid-turn would leave the agent generating (and billing) with nobody
316
- * watching — and it stays that way, since nothing tells codex to stop. So
317
- * every exit path funnels through `shutdown()`: interrupt the in-flight turn
318
- * first, then close the socket. A second Ctrl-C stops waiting for the
319
- * interrupt round-trip and exits immediately.
320
- */
321
- let activeTurn = null; // { threadId, turnId } while a turn is in flight
322
- let socketClosed = false;
323
- let shuttingDown = false;
324
- let interrupts = 0;
325
-
326
- const waitForSocketClose = (timeoutMs = 500) =>
327
- new Promise((resolve) => {
328
- if (socketClosed) return resolve();
329
- const done = () => {
330
- clearTimeout(timer);
331
- resolve();
332
- };
333
- const timer = setTimeout(done, timeoutMs);
334
- client.on("closed", done, { once: true });
335
- });
336
-
337
- client.on("closed", (ev) => {
338
- socketClosed = true;
339
- if (shuttingDown) return;
340
- const { code, reason } = ev.detail ?? {};
341
- const detail = code ? ` (code=${code})` : "";
342
- log(`\x1b[31m[closed] connection closed${detail}${reason ? ` ${reason}` : ""}\x1b[0m`);
343
- void shutdown(1);
344
- });
345
-
346
- /** Interrupt whatever is running, close the socket, exit. Idempotent. */
347
- async function shutdown(code, reason) {
348
- if (shuttingDown) return;
349
- shuttingDown = true;
350
- if (reason) log(reason);
351
- const turn = activeTurn;
352
- if (turn?.turnId && !socketClosed) {
353
- try {
354
- await client.request("turn/interrupt", { threadId: turn.threadId, turnId: turn.turnId });
355
- log(`\x1b[2m[interrupt] turn ${turn.turnId} interrupted\x1b[0m`);
356
- } catch (e) {
357
- log(`\x1b[31m[interrupt] failed: ${e.message}\x1b[0m`);
358
- }
359
- }
360
- activeTurn = null;
361
- client.close();
362
- await waitForSocketClose();
363
- process.exit(code);
364
- }
365
-
366
- function handleInterrupt(signal) {
367
- interrupts += 1;
368
- if (interrupts > 1) {
369
- log(`\n\x1b[31m[${signal}] again — exiting now\x1b[0m`);
370
- client.close();
371
- process.exit(130);
372
- }
373
- void shutdown(130, `\n\x1b[2m[${signal}] interrupting current turn and closing…\x1b[0m`);
374
- }
375
-
376
- // readline 在 TTY 下会自己接管 Ctrl-C(raw mode 下不会产生进程级 SIGINT),
377
- // 所以进程级和 rl 级都要挂,靠 shuttingDown / interrupts 去重。
378
- process.on("SIGINT", () => handleInterrupt("SIGINT"));
379
- process.on("SIGTERM", () => handleInterrupt("SIGTERM"));
380
- try {
381
- await client.connect();
382
- } catch (e) {
383
- console.error(`[oh-my-agent-web ws] failed to connect ${url} — ${e.message}`);
384
- console.error(" make sure the app-server is running, e.g. `oh-my-agent-web` (default ws://127.0.0.1:25258)");
385
- process.exitCode = 1;
386
- return;
387
- }
388
-
389
- const init = await client.request("initialize", {
390
- clientInfo: { name: "oh-my-agent-web-ws", title: "Oh My Agent Web WS Client", version: "1.0.0" },
391
- capabilities: { experimentalApi: true, requestAttestation: false },
392
- });
393
- log(`[server] ${init.userAgent} codexHome=${init.codexHome}`);
394
-
395
- const threadParams = {
396
- ...(opts.model ? { model: opts.model } : {}),
397
- ...(opts.cwd ? { cwd: opts.cwd } : {}),
398
- ...(opts.approval ? { approvalPolicy: opts.approval } : {}),
399
- ...(opts.sandbox ? { sandbox: opts.sandbox } : {}),
400
- };
401
- const threadResp = opts.thread
402
- ? await client.request("thread/resume", { threadId: opts.thread, ...threadParams })
403
- : await client.request("thread/start", threadParams);
404
-
405
- const threadId = threadResp.thread.id;
406
- let currentModel = threadResp.model ?? opts.model ?? null;
407
- log(
408
- `[thread] ${threadId} cwd=${threadResp.cwd} sandbox=${JSON.stringify(threadResp.sandbox)}`,
409
- );
410
- log(`[model] ${currentModel ?? "(server default)"} \x1b[2m(/model to list or switch)\x1b[0m`);
411
-
412
- /** Lazily fetch the server's model catalog (id / model / displayName / …). */
413
- let modelsCache = null;
414
- async function loadModels() {
415
- if (modelsCache) return modelsCache;
416
- try {
417
- const resp = await client.request("model/list", {});
418
- modelsCache = Array.isArray(resp?.data) ? resp.data : [];
419
- } catch {
420
- modelsCache = [];
421
- }
422
- return modelsCache;
423
- }
424
-
425
- /* -------- event rendering -------- */
426
- let inAgentMessage = false;
427
- let inReasoning = false;
428
- let pendingApproval = null;
429
- let rl = null;
430
-
431
- client.on("notification", (ev) => {
432
- const { method, params } = ev.detail;
433
- if (opts.json) return void process.stdout.write(JSON.stringify(ev.detail) + "\n");
434
-
435
- switch (method) {
436
- case "item/agentMessage/delta":
437
- if (!inAgentMessage) {
438
- process.stdout.write("\n");
439
- inAgentMessage = true;
440
- }
441
- process.stdout.write(params.delta);
442
- break;
443
-
444
- case "item/reasoning/summaryTextDelta":
445
- case "item/reasoning/textDelta":
446
- if (!opts.verbose) break;
447
- if (inAgentMessage) {
448
- process.stdout.write("\n");
449
- inAgentMessage = false;
450
- }
451
- if (!inReasoning) {
452
- process.stdout.write("\x1b[2m💭 ");
453
- inReasoning = true;
454
- }
455
- process.stdout.write(params.delta);
456
- break;
457
-
458
- case "item/started": {
459
- const it = params.item ?? {};
460
- if (inAgentMessage) {
461
- process.stdout.write("\n");
462
- inAgentMessage = false;
463
- }
464
- if (inReasoning) {
465
- process.stdout.write("\x1b[0m\n");
466
- inReasoning = false;
467
- }
468
- if (it.type === "commandExecution") log(`\x1b[36m▶ exec\x1b[0m ${it.command ?? ""}`);
469
- else if (it.type === "fileChange") log(`\x1b[33m✎ patch\x1b[0m ${(it.changes ?? []).length} file(s)`);
470
- else if (it.type === "mcpToolCall" || it.type === "dynamicToolCall") log(`\x1b[35m⚙ tool\x1b[0m ${it.tool ?? it.name ?? ""}`);
471
- break;
472
- }
473
-
474
- case "item/completed": {
475
- const it = params.item ?? {};
476
- if (inReasoning) {
477
- process.stdout.write("\x1b[0m");
478
- inReasoning = false;
479
- }
480
- if (it.type === "commandExecution") log(`\x1b[36m◀ exec done\x1b[0m exit=${it.exitCode ?? "?"}`);
481
- break;
482
- }
483
-
484
- case "turn/completed": {
485
- if (inAgentMessage) {
486
- process.stdout.write("\n");
487
- inAgentMessage = false;
488
- }
489
- if (inReasoning) {
490
- process.stdout.write("\x1b[0m\n");
491
- inReasoning = false;
492
- }
493
- const t = params.turn ?? {};
494
- const err = t.error ? ` error=${JSON.stringify(t.error)}` : "";
495
- log(`\x1b[2m── turn ${t.status ?? "done"}${err} ──\x1b[0m`);
496
- client.emit("turnDone", t);
497
- break;
498
- }
499
-
500
- case "error":
501
- log(`\x1b[31m[error] ${JSON.stringify(params)}\x1b[0m`);
502
- break;
503
-
504
- default:
505
- break;
506
- }
507
- });
508
-
509
- function decide(id, decision) {
510
- pendingApproval = null;
511
- client.respond(id, { decision });
512
- log(` → ${decision}`);
513
- }
514
-
515
- client.on("serverRequest", async (ev) => {
516
- const { id, method, params } = ev.detail;
517
- if (opts.json) process.stdout.write(JSON.stringify(ev.detail) + "\n");
518
-
519
- if (opts.autoApprove) return decide(id, "acceptForSession");
520
- if (opts.autoDecline) return decide(id, "decline");
521
-
522
- if (method === "item/commandExecution/requestApproval") {
523
- log(`\x1b[33m⚠ approval needed:\x1b[0m ${params.command ?? ""} (cwd=${params.cwd ?? ""})${params.reason ? " reason: " + params.reason : ""}`);
524
- } else if (method === "item/fileChange/requestApproval") {
525
- log(`\x1b[33m⚠ file change approval\x1b[0m${params.reason ? " reason: " + params.reason : ""}`);
526
- } else {
527
- log(`\x1b[33m⚠ server request ${method}:\x1b[0m ${JSON.stringify(params).slice(0, 400)}`);
528
- }
529
-
530
- if (process.stdin.isTTY && rl) {
531
- pendingApproval = { id, method };
532
- rl.setPrompt("approve? [y=once / a=session / n=decline] > ");
533
- rl.prompt();
534
- return;
535
- }
536
- log(" non-interactive, declining by default (use --auto-approve / --decline)");
537
- decide(id, "decline");
538
- });
539
-
540
- /** Run one turn to completion and return its final state. */
541
- async function runTurn(text) {
542
- const done = new Promise((resolve) => client.on("turnDone", (ev) => resolve(ev.detail), { once: true }));
543
- const started = await client.request("turn/start", {
544
- threadId,
545
- input: [{ type: "text", text, text_elements: [] }],
546
- ...(currentModel ? { model: currentModel } : {}),
547
- });
548
- activeTurn = { threadId, turnId: started?.turn?.id ?? null };
549
- const timer = setTimeout(() => {
550
- log(`\x1b[31m[timeout] turn did not finish within ${opts.timeout}s\x1b[0m`);
551
- client.emit("turnDone", { status: "timeout" });
552
- }, opts.timeout * 1000);
553
- const turn = await done;
554
- activeTurn = null;
555
- clearTimeout(timer);
556
- return turn;
557
- }
558
-
559
- /**
560
- * A failed turn worth retrying. The upstream moderation layer sometimes flags
561
- * a perfectly innocent prompt ("Invalid prompt … potentially violating our
562
- * usage policy") and codex reports willRetry:false, so without this the turn is
563
- * simply lost. Re-sending the same input almost always succeeds.
564
- */
565
- function isRetryableTurn(turn) {
566
- if (!turn || turn.status !== "failed") return false;
567
- const msg = String(turn.error?.message ?? "");
568
- return /flagged as potentially violating|Invalid prompt/i.test(msg);
569
- }
570
-
571
- async function ask(text) {
572
- const maxRetries = Math.max(0, Math.floor(Number(opts.retry)) || 0);
573
- const baseDelay = Math.max(0, Number(opts.retryDelayMs) || 0);
574
- for (let attempt = 0; ; attempt++) {
575
- const turn = await runTurn(text);
576
- if (attempt >= maxRetries || !isRetryableTurn(turn)) return;
577
- const wait = baseDelay * (attempt + 1);
578
- log(
579
- `\x1b[33m[retry] retryable failure — retrying in ${wait}ms (attempt ${attempt + 2}/${maxRetries + 1})\x1b[0m`,
580
- );
581
- await new Promise((r) => setTimeout(r, wait));
582
- }
583
- }
584
-
585
- /** Arrow-key model chooser used by `/model`. Returns a chosen id, or null. */
586
- async function pickModel(models, activeId) {
587
- const rows = models.map((m) => {
588
- const id = m.id ?? m.model;
589
- const label = m.displayName && m.displayName !== id ? ` \x1b[2m${m.displayName}\x1b[0m` : "";
590
- return { id, text: `${id}${label}` };
591
- });
592
- if (!process.stdin.isTTY) {
593
- log(" available:");
594
- for (const r of rows) log(` ${r.text}`);
595
- log(" pick one with `/model <name>`");
596
- return null;
597
- }
598
- let index = Math.max(0, rows.findIndex((r) => r.id === activeId));
599
- const total = rows.length;
600
- const draw = (initial) => {
601
- if (!initial) process.stderr.write(`\x1b[${total + 1}A`);
602
- for (let i = 0; i < total; i++) {
603
- const pointer = i === index ? "\x1b[36m›\x1b[0m" : " ";
604
- const star = rows[i].id === activeId ? " \x1b[32m*\x1b[0m" : "";
605
- process.stderr.write(`\x1b[2K${pointer} ${rows[i].text}${star}\n`);
606
- }
607
- process.stderr.write("\x1b[2K\x1b[2m ↑/↓ move · Enter select · Esc cancel\x1b[0m\n");
608
- };
609
- log("[model] select a model:");
610
- draw(true);
611
- return await new Promise((resolve) => {
612
- const stdin = process.stdin;
613
- const wasRaw = Boolean(stdin.isRaw);
614
- rl?.pause();
615
- if (typeof stdin.setRawMode === "function") stdin.setRawMode(true);
616
- stdin.resume();
617
- const finish = (value) => {
618
- stdin.removeListener("data", onData);
619
- if (typeof stdin.setRawMode === "function") stdin.setRawMode(wasRaw);
620
- stdin.pause();
621
- rl?.resume();
622
- resolve(value);
623
- };
624
- const onData = (buf) => {
625
- const s = buf.toString("utf8");
626
- if (s === "\u0003") {
627
- finish(null);
628
- process.kill(process.pid, "SIGINT");
629
- } else if (s === "\u001b[A" || s === "k") {
630
- index = (index - 1 + total) % total;
631
- draw();
632
- } else if (s === "\u001b[B" || s === "j") {
633
- index = (index + 1) % total;
634
- draw();
635
- } else if (s === "\r" || s === "\n") {
636
- finish(rows[index].id);
637
- } else if (s === "\u001b" || s === "q") {
638
- finish(null);
639
- } else if (/^[1-9]$/.test(s)) {
640
- const n = Number(s) - 1;
641
- if (n < total) {
642
- index = n;
643
- draw();
644
- }
645
- }
646
- };
647
- stdin.on("data", onData);
648
- });
649
- }
650
-
651
- /** Interactive slash commands (/model, /help, /quit). */
652
- async function handleCommand(line) {
653
- const [cmd, ...rest] = line.slice(1).trim().split(/\s+/);
654
- switch (cmd) {
655
- case "help":
656
- process.stdout.write(HELP);
657
- return true;
658
- case "quit":
659
- case "exit":
660
- await shutdown(0);
661
- return true;
662
- case "model":
663
- case "models": {
664
- const models = await loadModels();
665
- if (!rest.length) {
666
- log(`[model] current: ${currentModel ?? "(server default)"}`);
667
- if (!models.length) {
668
- log(" (this server exposes no model list)");
669
- return true;
670
- }
671
- const picked = await pickModel(models, currentModel);
672
- if (picked && picked !== currentModel) {
673
- currentModel = picked;
674
- log(`[model] switched to ${currentModel} \x1b[2m(applies from the next turn)\x1b[0m`);
675
- updatePrompt();
676
- }
677
- return true;
678
- }
679
- const wanted = rest[0];
680
- const hit = models.find((m) => m.id === wanted || m.model === wanted || m.displayName === wanted);
681
- if (models.length && !hit) {
682
- log(`\x1b[31m[model] unknown model "${wanted}" — run /model to list\x1b[0m`);
683
- return true;
684
- }
685
- currentModel = hit ? (hit.id ?? hit.model) : wanted;
686
- log(`[model] switched to ${currentModel} \x1b[2m(applies from the next turn)\x1b[0m`);
687
- updatePrompt();
688
- return true;
689
- }
690
- default:
691
- log(`unknown command: /${cmd} — try /help`);
692
- return true;
693
- }
694
- }
695
-
696
- function updatePrompt() {
697
- if (rl && process.stdin.isTTY) rl.setPrompt(`\x1b[2m${currentModel ?? "model?"}\x1b[0m \x1b[36m›\x1b[0m `);
698
- }
699
-
700
- function setupRepl() {
701
- rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY });
702
- rl.on("line", async (line) => {
703
- const text = line.trim();
704
- if (!text) {
705
- if (process.stdin.isTTY) rl.prompt();
706
- return;
707
- }
708
- if (text.startsWith("/")) {
709
- try {
710
- await handleCommand(text);
711
- } catch (e) {
712
- log(`\x1b[31m[error] ${e.message}\x1b[0m`);
713
- }
714
- if (process.stdin.isTTY) rl.prompt();
715
- return;
716
- }
717
- if (pendingApproval) {
718
- if (/^(y|yes)$/i.test(text)) decide(pendingApproval.id, "accept");
719
- else if (/^(a|all)$/i.test(text)) decide(pendingApproval.id, "acceptForSession");
720
- else decide(pendingApproval.id, "decline");
721
- updatePrompt();
722
- if (process.stdin.isTTY) rl.prompt();
723
- return;
724
- }
725
- rl.pause();
726
- try {
727
- await ask(text);
728
- } catch (e) {
729
- log(`\x1b[31m[error] ${e.message}\x1b[0m`);
730
- }
731
- rl.resume();
732
- if (process.stdin.isTTY) rl.prompt();
733
- });
734
- rl.on("SIGINT", () => handleInterrupt("SIGINT"));
735
- rl.on("close", () => {
736
- void shutdown(0);
737
- });
738
- if (process.stdin.isTTY) {
739
- updatePrompt();
740
- log("\ntype a message and press Enter (Ctrl-C: interrupt & close / Ctrl-D: exit). approvals: y=once / a=session / n=decline\n /model to list or switch models · /help for all commands\n");
741
- rl.prompt();
742
- }
743
- }
744
-
745
- if (opts.messages.length) {
746
- for (const msg of opts.messages) {
747
- try {
748
- await ask(msg);
749
- } catch (e) {
750
- log(`\x1b[31m[error] ${e.message}\x1b[0m`);
751
- }
752
- }
753
- await shutdown(0);
754
- return;
755
- }
756
- if (process.stdin.isTTY) {
757
- setupRepl();
758
- return;
759
- }
760
- // piped stdin: the whole input is one message
761
- let buf = "";
762
- process.stdin.setEncoding("utf8");
763
- for await (const chunk of process.stdin) buf += chunk;
764
- const text = buf.trim();
765
- if (text) {
766
- try {
767
- await ask(text);
768
- } catch (e) {
769
- log(`\x1b[31m[error] ${e.message}\x1b[0m`);
770
- }
771
- }
772
- await shutdown(0);
773
- }
774
-
775
- // Allow running this file directly: `node bin/oh-my-agent-web-ws.mjs ...`
776
- const invoked = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
777
- if (invoked === import.meta.url) {
778
- runWsClient(process.argv.slice(2)).catch((err) => {
779
- console.error(`[oh-my-agent-web ws] ${err instanceof Error ? err.message : String(err)}`);
780
- process.exit(1);
781
- });
782
- }