oh-my-agent-web 0.2.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.
- package/README.md +86 -447
- package/dist/server/approval/read-only.js +107 -0
- package/dist/server/approval/rules.js +85 -0
- package/dist/server/approval/scope.js +150 -0
- package/dist/server/brand.js +2 -0
- package/dist/server/cli-config.js +52 -0
- package/dist/server/cli-info.js +101 -0
- package/dist/server/cli.js +304 -0
- package/dist/server/engine/builtin.js +61 -0
- package/dist/server/engine/catalog.js +8 -0
- package/dist/server/engine/codex-approvals.js +45 -0
- package/dist/server/engine/codex-map.js +298 -0
- package/dist/server/engine/codex-sessions.js +159 -0
- package/dist/server/engine/codex-transport.js +49 -0
- package/dist/server/engine/codex.js +314 -0
- package/dist/server/engine/dsh-history.js +195 -0
- package/dist/server/engine/dsh-log.js +245 -0
- package/dist/server/engine/dsh-map.js +221 -0
- package/dist/server/engine/dsh-sessions.js +207 -0
- package/dist/server/engine/dsh-transport.js +37 -0
- package/dist/server/engine/dsh.js +284 -0
- package/dist/server/engine/fake.js +231 -0
- package/dist/server/engine/jsonrpc-stdio.js +241 -0
- package/dist/server/engine/model-registry-reload.js +115 -0
- package/dist/server/engine/pi-events.js +148 -0
- package/dist/server/engine/pi-rename.js +14 -0
- package/dist/server/engine/pi-sdk.js +343 -0
- package/dist/server/engine/pi-session-model.js +23 -0
- package/dist/server/engine/pi-thinking.js +34 -0
- package/dist/server/engine/pi-transcript.js +225 -0
- package/dist/server/engine/pi-version.js +11 -0
- package/dist/server/engine/port.js +1 -0
- package/dist/server/engine/registry.js +47 -0
- package/dist/server/engine/scratch.js +13 -0
- package/dist/server/engine/suggestion-guidance.js +9 -0
- package/dist/server/engine/switchable.js +22 -0
- package/dist/server/engine/wiring.js +107 -0
- package/dist/server/files/dir-routes.js +40 -0
- package/dist/server/files/file-search.js +63 -0
- package/dist/server/files/file-service.js +232 -0
- package/dist/server/files/files-routes.js +182 -0
- package/dist/server/files/git-info.js +186 -0
- package/dist/server/http/auth-routes.js +99 -0
- package/dist/server/http/auth.js +191 -0
- package/dist/server/http/aux-routes.js +24 -0
- package/dist/server/http/compress.js +75 -0
- package/dist/server/http/health-and-static.js +33 -0
- package/dist/server/http/live-routes.js +113 -0
- package/dist/server/http/login-page.js +200 -0
- package/dist/server/index.js +342 -520
- package/dist/server/markers/actions.js +262 -0
- package/dist/server/protocol-parse.js +201 -0
- package/dist/server/protocol-settings.js +1 -0
- package/dist/server/protocol.js +6 -0
- package/dist/server/providers/catalog-store.js +85 -0
- package/dist/server/providers/catalog-writes.js +197 -0
- package/dist/server/providers/catalog.js +77 -0
- package/dist/server/providers/enabled-models.js +118 -0
- package/dist/server/providers/import-models.js +69 -0
- package/dist/server/providers/import-routes.js +136 -0
- package/dist/server/providers/model-test.js +52 -0
- package/dist/server/providers/probe.js +64 -0
- package/dist/server/providers/provider-handlers.js +203 -0
- package/dist/server/providers/provider-store.js +327 -0
- package/dist/server/providers/request-headers.js +7 -0
- package/dist/server/providers/validate.js +241 -0
- package/dist/server/push/push-routes.js +114 -0
- package/dist/server/push/push.js +120 -0
- package/dist/server/session/approval-bridge.js +103 -0
- package/dist/server/session/attachment-resolve.js +21 -0
- package/dist/server/session/client-view.js +82 -0
- package/dist/server/session/conversation-create.js +65 -0
- package/dist/server/session/delete-session.js +84 -0
- package/dist/server/session/engine-switch.js +108 -0
- package/dist/server/session/lock.js +82 -0
- package/dist/server/session/model-controller.js +111 -0
- package/dist/server/session/notices.js +31 -0
- package/dist/server/session/pi-opener.js +123 -0
- package/dist/server/session/pi-refresh.js +51 -0
- package/dist/server/session/pi-sessions-provider.js +46 -0
- package/dist/server/session/pi-sessions.js +272 -0
- package/dist/server/session/refresh-actions.js +42 -0
- package/dist/server/session/rename-session.js +23 -0
- package/dist/server/session/runtime-services.js +28 -0
- package/dist/server/session/runtime-types.js +1 -0
- package/dist/server/session/runtime.js +358 -0
- package/dist/server/session/session-stats.js +88 -0
- package/dist/server/session/settings.js +367 -0
- package/dist/server/session/snapshot-for.js +7 -0
- package/dist/server/session/stats-refresher.js +50 -0
- package/dist/server/session/switch-conversation.js +86 -0
- package/dist/server/session/throttle.js +14 -0
- package/dist/server/session/turn-events.js +126 -0
- package/dist/server/session/turn-finish.js +79 -0
- package/dist/server/session/turn-markers.js +26 -0
- package/dist/server/session/turn-queue.js +111 -0
- package/dist/server/session/turn-send.js +88 -0
- package/dist/server/session/turn-watchdog.js +77 -0
- package/dist/server/sessions/session-routes.js +17 -0
- package/dist/server/skills/skill-routes.js +28 -0
- package/dist/server/skills/skill-store.js +134 -0
- package/dist/server/snapshot/fan-out.js +10 -0
- package/dist/server/snapshot/messages.js +75 -0
- package/dist/server/snapshot/sender.js +62 -0
- package/dist/server/snapshot/state.js +193 -0
- package/dist/server/terminal/terminal-routes.js +176 -0
- package/dist/server/terminal/terminal-session.js +140 -0
- package/dist/server/uploads/upload-routes.js +64 -0
- package/dist/server/uploads/upload-store.js +115 -0
- package/dist/server/util/data-dir.js +36 -0
- package/dist/server/util/env.js +140 -0
- package/dist/server/util/ids.js +28 -0
- package/dist/server/util/log-file.js +67 -0
- package/dist/server/util/log.js +52 -0
- package/dist/server/util/origin.js +75 -0
- package/dist/server/util/paths.js +60 -0
- package/dist/server/util/pricing-index.js +121 -0
- package/dist/server/util/pricing.js +214 -0
- package/dist/server/util/startup.js +34 -0
- package/dist/server/util/upstream-version.js +140 -0
- package/dist/server/util/usage.js +19 -0
- package/dist/server/util/version.js +50 -0
- package/dist/server/ws/handle-message.js +95 -0
- package/package.json +49 -63
- package/web/dist/apple-touch-icon-precomposed.png +0 -0
- package/web/dist/apple-touch-icon.png +0 -0
- package/web/dist/assets/abnfDiagram-WEYOIA3C-NLp09iEC.js +1 -0
- package/web/dist/assets/arc-DviL9o3c.js +1 -0
- package/web/dist/assets/architecture-WOLXFQ4H-fjsM_Sy3.js +1 -0
- package/web/dist/assets/architectureDiagram-ZHVYKT53-Cn3B4TE8.js +36 -0
- package/web/dist/assets/array-BifhSqXX.js +1 -0
- package/web/dist/assets/blockDiagram-RJAE7QA3-CIuvxWvi.js +140 -0
- package/web/dist/assets/c4Diagram-XDPPPE54-DvdKmEpF.js +38 -0
- package/web/dist/assets/channel-C3AQ6nAs.js +1 -0
- package/web/dist/assets/chunk-2E7WHVMD-HNNASGCS.js +88 -0
- package/web/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/web/dist/assets/chunk-4EMNKR2T-Cg-eRwBE.js +1 -0
- package/web/dist/assets/chunk-55XCU76K-BweiisZL.js +1 -0
- package/web/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/web/dist/assets/chunk-7S6XSTFO-BCzIiP-6.js +2 -0
- package/web/dist/assets/chunk-AQDTSKZB-Dl0NRFuU.js +72 -0
- package/web/dist/assets/chunk-DSNLSHJZ-zhHCjjN9.js +129 -0
- package/web/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/web/dist/assets/chunk-HAGFOTED-BDQqU2EH.js +62 -0
- package/web/dist/assets/chunk-HNE4Q36P-B-qRzMjX.js +1 -0
- package/web/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/web/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/web/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
- package/web/dist/assets/chunk-NQOKRVIF-CNcvhMn_.js +213 -0
- package/web/dist/assets/chunk-UHW7PFBP-Dwr8LUzr.js +1 -0
- package/web/dist/assets/chunk-VQ5W62QI-O8sN6jJu.js +1 -0
- package/web/dist/assets/chunk-WQQL4CDK-B6-59Zg2.js +10 -0
- package/web/dist/assets/chunk-XXDRQBXY-DvSbeUlZ.js +1 -0
- package/web/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/web/dist/assets/chunk-YZO67HFG-BXpKYKsw.js +1 -0
- package/web/dist/assets/chunk-ZAUX2LTX-B1Z5VSkz.js +1 -0
- package/web/dist/assets/chunk-ZCHMQSOR-h_5mBLIA.js +2 -0
- package/web/dist/assets/classDiagram-v2-4MYWR3UA-Dr6JnaaV.js +217 -0
- package/web/dist/assets/cose-bilkent-JH36ORCC-36ShkR7R.js +1 -0
- package/web/dist/assets/cynefin-EF2NZ3EQ-Dg6bI7pC.js +1 -0
- package/web/dist/assets/cynefinDiagram-WF24UDRP-B0YHpBlR.js +62 -0
- package/web/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
- package/web/dist/assets/dagre-NK4GIZVX-Cx3mn7YS.js +4 -0
- package/web/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/web/dist/assets/diagram-CBK3MWTC-l8aZG_Bm.js +24 -0
- package/web/dist/assets/diagram-I5XIIV2D-Bd5f14_h.js +24 -0
- package/web/dist/assets/diagram-ODIL2UCW-hBsxCYUj.js +30 -0
- package/web/dist/assets/diagram-PFBEJUXE-BgpIBUYl.js +3 -0
- package/web/dist/assets/diagram-RJA3I4VV-CDU1gUVD.js +41 -0
- package/web/dist/assets/diagram-TYUNFSNR-Iuxh7Tqf.js +200 -0
- package/web/dist/assets/dist-C86cKTuE.js +1 -0
- package/web/dist/assets/ebnfDiagram-V4P3TTPT-Cms0HFKh.js +1 -0
- package/web/dist/assets/elk-QONTBCTQ-B7yEy0PQ.js +27 -0
- package/web/dist/assets/erDiagram-KYQIGQAA-CL48j4aU.js +99 -0
- package/web/dist/assets/eventmodeling-K75KTNOO-CFvkaejh.js +1 -0
- package/web/dist/assets/flowDiagram-IUGLLWBU-6UyKXpqG.js +1 -0
- package/web/dist/assets/ganttDiagram-R7GPNTJY-vdMr-0ZV.js +292 -0
- package/web/dist/assets/gitGraph-VSP46ZUC-2z85Wuwo.js +1 -0
- package/web/dist/assets/gitGraphDiagram-PDYL7XHY-BY4GPiqC.js +106 -0
- package/web/dist/assets/graphlib-DS17s2tU.js +1 -0
- package/web/dist/assets/index-Bjn56a2i.js +76 -0
- package/web/dist/assets/index-Drp6e5iU.css +1 -0
- package/web/dist/assets/info-OHQRW6UA-ECzkYTx-.js +1 -0
- package/web/dist/assets/infoDiagram-MVDRKAX5-DDk5EAeb.js +2 -0
- package/web/dist/assets/init-C-OQMol4.js +1 -0
- package/web/dist/assets/ishikawaDiagram-HPEHV2GE-9yPH4J94.js +70 -0
- package/web/dist/assets/journeyDiagram-BOSHW7O7-BPTIap3l.js +139 -0
- package/web/dist/assets/kanban-definition-FNSA4HC5-BbADHrA8.js +89 -0
- package/web/dist/assets/katex-B_JcIwqj.js +257 -0
- package/web/dist/assets/line-CHV9_4lL.js +1 -0
- package/web/dist/assets/linear-C-eZsBTv.js +1 -0
- package/web/dist/assets/mermaid-parser.core-BiAhX1l1.js +7 -0
- package/web/dist/assets/mermaid.core-BxP_A_zj.js +44 -0
- package/web/dist/assets/mindmap-definition-6OY57UG3-BMWlit95.js +98 -0
- package/web/dist/assets/ordinal-BDEzSJ7C.js +1 -0
- package/web/dist/assets/packet-JDAUHWVQ-FVApuKOp.js +1 -0
- package/web/dist/assets/path-fybaL0A-.js +1 -0
- package/web/dist/assets/pegDiagram-ECIUJNBX-WjWJvDHM.js +1 -0
- package/web/dist/assets/pie-XZMESJXO-BGCZnvhp.js +1 -0
- package/web/dist/assets/pieDiagram-GNMPY74T-DJpAC_JC.js +39 -0
- package/web/dist/assets/quadrantDiagram-D57K4MC3-hxx5c7oq.js +7 -0
- package/web/dist/assets/radar-ABXABTNO-BU9Shc-O.js +1 -0
- package/web/dist/assets/railroad-I3PHUGI6-pWj8d-G4.js +1 -0
- package/web/dist/assets/railroad-abnf-LNEFI6M7-D7HZBTAj.js +1 -0
- package/web/dist/assets/railroad-ebnf-DWYD2UWJ-CKiXJSuM.js +1 -0
- package/web/dist/assets/railroad-peg-JR7BVNR7-DB3VJHgR.js +1 -0
- package/web/dist/assets/railroadDiagram-Y72NRBL7-Cev7Y65k.js +1 -0
- package/web/dist/assets/requirementDiagram-EQYSW5RA-CqbwMLD8.js +84 -0
- package/web/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/web/dist/assets/sankeyDiagram-SY24JHO5-V2cyVi8q.js +40 -0
- package/web/dist/assets/sequenceDiagram-BLEHNHQU-By6NJOPM.js +169 -0
- package/web/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/web/dist/assets/src-Da91fG3g.js +1 -0
- package/web/dist/assets/stateDiagram-v2-A6IESKWZ-BnPGX5UI.js +291 -0
- package/web/dist/assets/swimlanes-B3ENRKFQ-C58BpIpm.js +1 -0
- package/web/dist/assets/swimlanesDiagram-AYQRV4AK-RaivxSsc.js +8 -0
- package/web/dist/assets/timeline-definition-JXYKGLGB-Hgo1DhnS.js +120 -0
- package/web/dist/assets/treeView-D4JQ5SDB-DF0dfjqB.js +1 -0
- package/web/dist/assets/treemap-SAJKECNS-CSSM5FqB.js +1 -0
- package/web/dist/assets/usecaseDiagram-WPOLBNNX-BkBBD1NY.js +328 -0
- package/web/dist/assets/vennDiagram-Q3Y3IDCW-Bm28keX_.js +34 -0
- package/web/dist/assets/wardley-7MLQ67FV-By7KgEQX.js +1 -0
- package/web/dist/assets/wardleyDiagram-IVAIDKWA-oVtIrnGa.js +78 -0
- package/web/dist/assets/xychartDiagram-4URNHDU4-aI8FH4g2.js +7 -0
- package/web/dist/favicon-32.png +0 -0
- package/web/dist/favicon.ico +0 -0
- package/web/dist/favicon.svg +20 -8
- package/web/dist/icons/apple-touch-icon-180.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/icon-maskable-512.png +0 -0
- package/web/dist/index.html +21 -27
- package/web/dist/manifest.webmanifest +17 -0
- package/web/dist/maskable.svg +25 -0
- package/web/dist/sw.js +40 -0
- package/bin/oh-my-agent-web-ps.mjs +0 -203
- package/bin/oh-my-agent-web-ws.mjs +0 -593
- package/bin/oh-my-agent-web.mjs +0 -321
- package/bin/update-check.mjs +0 -142
- package/dist/server/codex-client.js +0 -310
- package/dist/server/codex-supervisor.js +0 -446
- package/dist/server/fs-service.js +0 -144
- package/dist/server/request-inspector.js +0 -458
- package/dist/shared/protocol.js +0 -112
- package/scripts/manage-server.mjs +0 -155
- package/scripts/omaw.mjs +0 -74
- package/shared/protocol.ts +0 -228
- package/web/dist/assets/index-B8-VzDf2.css +0 -1
- package/web/dist/assets/index-DeC2eiiq.js +0 -42
- package/web/dist/assets/markdown-CYXe6JZx.js +0 -55
package/dist/server/index.js
CHANGED
|
@@ -1,543 +1,365 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* browser <--ws /ws--> [this] <--ws--> codex app-server (ws://127.0.0.1:25258)
|
|
5
|
-
*
|
|
6
|
-
* 我们是「透明 JSON-RPC 代理 + 进程守护」:
|
|
7
|
-
* - 浏览器可以按名字调用任意 codex 方法;所有 codex 通知原样广播。
|
|
8
|
-
* - `omaw/*` 是本地方法(状态 / 日志 / 文件系统 / 路径),由本进程处理。
|
|
9
|
-
* - codex 反向请求(审批 / user input / 动态工具)广播给所有标签页,谁先 reply 谁赢,
|
|
10
|
-
* 随后广播 `serverRequest/resolved` 让其他标签页关掉弹窗。
|
|
11
|
-
* - 同一 `dist/server/index.js` 既能被 CLI `startServer()` 调用,也能直接 `node` 运行。
|
|
2
|
+
* 入口:一个 HTTP 端口 + 实时通道(WS 或 SSE/RPC,ADR-0032)。
|
|
12
3
|
*/
|
|
13
|
-
import {
|
|
4
|
+
import { homedir, networkInterfaces } from "node:os";
|
|
14
5
|
import { createServer } from "node:http";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
18
8
|
import express from "express";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
9
|
+
import { WebSocketServer } from "ws";
|
|
10
|
+
import { createEngineWiring } from "./engine/wiring.js";
|
|
11
|
+
import { createFileRuleStore } from "./approval/rules.js";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
14
|
+
import { acquireLock, describeHolder, releaseLock } from "./session/lock.js";
|
|
15
|
+
import { createRuntime } from "./session/runtime.js";
|
|
16
|
+
import { createFileSettingsStore } from "./session/settings.js";
|
|
17
|
+
import { createSender } from "./snapshot/sender.js";
|
|
18
|
+
import { createPiSessionsProvider, piAgentDir } from "./session/pi-sessions-provider.js";
|
|
19
|
+
import { createUploadStore, uploadsDir } from "./uploads/upload-store.js";
|
|
20
|
+
import { builtinProviderGroups, createProviderWiring } from "./providers/provider-handlers.js";
|
|
21
|
+
import { probeProvider } from "./providers/probe.js";
|
|
22
|
+
import { createProviderStore } from "./providers/provider-store.js";
|
|
23
|
+
import { createEnabledStore } from "./providers/enabled-models.js";
|
|
24
|
+
import { migrateStoreToCatalog } from "./providers/catalog-writes.js";
|
|
25
|
+
import { createPushStore, notifyTurnFinished } from "./push/push.js";
|
|
26
|
+
import { registerAuxRoutes } from "./http/aux-routes.js";
|
|
27
|
+
import { handleMessage } from "./ws/handle-message.js";
|
|
28
|
+
import { registerHealthAndStatic } from "./http/health-and-static.js";
|
|
29
|
+
import { registerUploadRoutes } from "./uploads/upload-routes.js";
|
|
30
|
+
import { readConfig, validateConfig } from "./util/env.js";
|
|
31
|
+
import { createLogger } from "./util/log.js";
|
|
32
|
+
import { createRotatingFileSink, logFileName } from "./util/log-file.js";
|
|
33
|
+
import { refreshPriceIndex } from "./util/pricing-index.js";
|
|
34
|
+
import { describePolicy, isAllowedHost, isAllowedOrigin } from "./util/origin.js";
|
|
35
|
+
import { pickExisting, webDistCandidates } from "./util/paths.js";
|
|
36
|
+
import { authMiddleware, decide, effectiveAddress } from "./http/auth.js";
|
|
37
|
+
import { registerAuthRoutes } from "./http/auth-routes.js";
|
|
38
|
+
import { BRAND_NAME } from "./brand.js";
|
|
39
|
+
import { registerLiveRoutes } from "./http/live-routes.js";
|
|
40
|
+
import { compressText, immutableAssets } from "./http/compress.js";
|
|
41
|
+
import { fanOutSnapshot } from "./snapshot/fan-out.js";
|
|
42
|
+
import { checkDataDirWritable, describeListenError } from "./util/startup.js";
|
|
43
|
+
import { migrateLegacyDataDir } from "./util/data-dir.js";
|
|
44
|
+
const HERE = fileURLToPath(new URL(".", import.meta.url));
|
|
45
|
+
/** 开发态对外端口(Vite);dev 脚本用它告诉后端可信来源 */
|
|
46
|
+
const DEV_WEB_PORT = Number(process.env.AGENT_WEB_DEV_PORT ?? 25257);
|
|
47
|
+
/** 快照节流窗口(ADR-0004 §3) */
|
|
48
|
+
const SNAPSHOT_WINDOW_MS = 60;
|
|
49
|
+
const TICK_MS = 20;
|
|
50
|
+
/** socket 积压阈值:超过就只发事件、丢快照(ADR-0004 §4) */
|
|
51
|
+
const BACKPRESSURE_BYTES = 256 * 1024;
|
|
52
|
+
const { config, problems: envProblems } = readConfig(process.env, { home: homedir(), cwd: process.cwd() });
|
|
53
|
+
let fileSink = null;
|
|
54
|
+
const logger = createLogger({
|
|
55
|
+
level: config.logLevel,
|
|
56
|
+
scope: "app",
|
|
57
|
+
sink: (line) => {
|
|
58
|
+
process.stdout.write(`${line}\n`);
|
|
59
|
+
fileSink?.write(line);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
// 单用户工作台:一处 fire-and-forget 的 Promise 拒绝(推送写盘失败、模型热重载失败……)
|
|
63
|
+
// 不该把整个服务带走(Node ≥15 默认会直接退出)。记日志、继续跑。
|
|
64
|
+
process.on("unhandledRejection", (reason) => {
|
|
65
|
+
logger.error("未处理的 Promise 拒绝(已记录,进程继续)", {
|
|
66
|
+
message: reason instanceof Error ? (reason.stack ?? reason.message) : String(reason),
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
/** 局域网地址自动放行;域名仍要显式配 */
|
|
70
|
+
const localAddresses = Object.values(networkInterfaces())
|
|
71
|
+
.flat()
|
|
72
|
+
.filter((item) => Boolean(item) && item?.internal === false)
|
|
73
|
+
.map((item) => item.address);
|
|
74
|
+
const policy = {
|
|
75
|
+
port: config.port,
|
|
76
|
+
devPorts: [DEV_WEB_PORT],
|
|
77
|
+
extraHosts: [...new Set([...localAddresses, ...config.allowedHosts])],
|
|
78
|
+
};
|
|
79
|
+
const sockets = new Set();
|
|
80
|
+
let shuttingDown = false;
|
|
81
|
+
const sender = createSender({
|
|
82
|
+
windowMs: SNAPSHOT_WINDOW_MS,
|
|
83
|
+
send: (frame) => {
|
|
84
|
+
if (frame.kind === "snapshot")
|
|
85
|
+
fanOut(frame.snapshot);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const piSessions = createPiSessionsProvider({ agentDir: piAgentDir(), dataDir: config.dataDir, logger });
|
|
89
|
+
/** 引擎接线:注册表 + codex 客户端 + 每个引擎的会话来源(engine/wiring.ts) */
|
|
90
|
+
const engineWiring = createEngineWiring({
|
|
91
|
+
engineName: config.engine,
|
|
92
|
+
codexBin: config.codexBin,
|
|
93
|
+
codexHome: config.codexHome,
|
|
94
|
+
dshBin: config.dshBin,
|
|
95
|
+
dshHome: config.dshHome,
|
|
96
|
+
dshModel: config.dshModel,
|
|
97
|
+
dshPatches: config.dshPatches,
|
|
98
|
+
dshPatchDir: join(config.dataDir, "dsh-patches"),
|
|
99
|
+
piSessions,
|
|
100
|
+
onNotice: (text, level) => runtimeRef.current?.notify(text, level),
|
|
101
|
+
onLog: (text, meta) => logger.warn(text, meta),
|
|
102
|
+
});
|
|
103
|
+
const engineCatalog = engineWiring.catalog;
|
|
104
|
+
const resolved = engineWiring.resolved;
|
|
105
|
+
const engine = resolved.engine;
|
|
106
|
+
/** 引擎在 runtime 建好之前就会创建,用一个 ref 把「通知」转发给界面 */
|
|
107
|
+
/** provider 配置存储(ADR-0025):复用 pi 自己的 models-store.json / auth.json */
|
|
108
|
+
const providerStore = createProviderStore({ agentDir: piAgentDir(), now: Date.now });
|
|
109
|
+
/** 启动时的配置补齐(main 里会 await 它:避免和价格索引抢着读同一个文件) */
|
|
110
|
+
const catalogMigration = migrateStoreToCatalog(piAgentDir()).then((migrated) => {
|
|
111
|
+
if (migrated.length > 0)
|
|
112
|
+
logger.info("已把老 provider 补进 models.json", { providers: migrated });
|
|
113
|
+
});
|
|
114
|
+
const enabledStore = createEnabledStore({ agentDir: piAgentDir() });
|
|
115
|
+
const pushStore = createPushStore({ dataDir: config.dataDir, now: Date.now });
|
|
116
|
+
const runProviderRequest = createProviderWiring({
|
|
117
|
+
store: providerStore,
|
|
118
|
+
logger,
|
|
119
|
+
probe: probeProvider,
|
|
120
|
+
enabled: enabledStore,
|
|
121
|
+
getRuntime: () => runtime,
|
|
122
|
+
});
|
|
123
|
+
const runtimeRef = {
|
|
124
|
+
current: null,
|
|
125
|
+
};
|
|
126
|
+
/** 懒读:provider 列表里要带上内置 provider 分组,而那需要 runtime 里的模型表 */
|
|
127
|
+
const providerList = async () => {
|
|
128
|
+
const list = await providerStore.list(builtinProviderGroups(runtime?.state().models ?? []));
|
|
129
|
+
const live = runtime?.state();
|
|
130
|
+
if (live)
|
|
131
|
+
live.enabledModels = await enabledStore.read();
|
|
132
|
+
return list;
|
|
133
|
+
};
|
|
134
|
+
const runtime = createRuntime({
|
|
135
|
+
engine,
|
|
136
|
+
// 自定义 provider 来自 models-store.json;不主动读一次,界面上就只剩内置的那几个
|
|
137
|
+
loadProviders: providerList,
|
|
138
|
+
onTurnFinished: (info) => notifyTurnFinished(pushStore, info),
|
|
139
|
+
rules: createFileRuleStore(config.dataDir),
|
|
140
|
+
// 启动信息只进日志,不进对话流
|
|
141
|
+
onStartupInfo: (text) => logger.info(text),
|
|
142
|
+
workspace: config.workspace,
|
|
143
|
+
engineName: resolved.descriptor.name,
|
|
144
|
+
engineCapabilities: resolved.descriptor.capabilities,
|
|
145
|
+
turnTimeoutMs: config.turnTimeoutMs,
|
|
146
|
+
settings: createFileSettingsStore(config.dataDir),
|
|
147
|
+
uploadStore: {
|
|
148
|
+
read: (id) => uploadStore.read(id),
|
|
149
|
+
pathOf: (id) => join(uploadsDir(config.dataDir), id),
|
|
150
|
+
},
|
|
151
|
+
piSessions: engineCatalog.sessionsFor(resolved.descriptor.name) ?? undefined,
|
|
152
|
+
engines: engineCatalog,
|
|
153
|
+
emit: {
|
|
154
|
+
snapshot: (snapshot) => sender.pushSnapshot(snapshot),
|
|
155
|
+
event: (event) => {
|
|
156
|
+
broadcast(JSON.stringify(event));
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
runtimeRef.current = runtime;
|
|
161
|
+
/** SSE 连接(纯 HTTP 通道):clientId → 写入函数(每客户端一份快照,ADR-0033) */
|
|
162
|
+
const sinks = new Map();
|
|
163
|
+
const fanOut = (snapshot) => fanOutSnapshot(snapshot, { sockets, sinks, snapshotFor: (id) => runtime.snapshotFor(id) });
|
|
164
|
+
function broadcast(payload) {
|
|
165
|
+
for (const socket of sockets) {
|
|
166
|
+
if (socket.readyState === socket.OPEN)
|
|
167
|
+
socket.send(payload);
|
|
131
168
|
}
|
|
132
|
-
const
|
|
133
|
-
|
|
169
|
+
for (const sink of sinks.values())
|
|
170
|
+
sink(payload);
|
|
134
171
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
172
|
+
async function dispatchRequest(socket, raw, clientId) {
|
|
173
|
+
await handleMessage(socket, raw, {
|
|
174
|
+
runtime,
|
|
175
|
+
...(clientId ? { clientId } : {}),
|
|
176
|
+
reply,
|
|
177
|
+
sender,
|
|
178
|
+
logger,
|
|
179
|
+
runProviderRequest: (request, respond) => runProviderRequest(request, (result) => respond(result)),
|
|
180
|
+
});
|
|
140
181
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
code: typeof err.code === "number" ? err.code : -32000,
|
|
147
|
-
message: typeof err.message === "string" ? err.message : "internal error",
|
|
148
|
-
data: err.data,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
return { code: -32000, message: err instanceof Error ? err.message : String(err) };
|
|
182
|
+
function reply(socket, id, result) {
|
|
183
|
+
const message = result.ok
|
|
184
|
+
? { type: "reply", id, ok: true, ...(result.result === undefined ? {} : { result: result.result }) }
|
|
185
|
+
: { type: "reply", id, ok: false, error: result.error };
|
|
186
|
+
socket.send(JSON.stringify(message));
|
|
152
187
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
// ------------------------------ HTTP ------------------------------
|
|
189
|
+
const app = express();
|
|
190
|
+
// 登录端点必须挂在鉴权**之前**:此刻还没有 cookie,不能要求先过鉴权(ADR-0031 修订)
|
|
191
|
+
registerAuthRoutes(app, { password: config.password, brandName: BRAND_NAME });
|
|
192
|
+
// 鉴权其次:本机放行,其它来源必须过密码;页面请求给登录页,接口请求给 401
|
|
193
|
+
app.use(authMiddleware({ password: config.password, brandName: BRAND_NAME }));
|
|
194
|
+
app.use(express.json()); // 终端/技能/导入要读 body
|
|
195
|
+
const webDist = pickExisting(webDistCandidates(HERE), existsSync);
|
|
196
|
+
// 压缩 + 缓存头必须在 **API 路由注册之前** 挂:Express 按注册顺序匹配中间件,
|
|
197
|
+
// 挂在后面就只影响之后注册的路由 —— `/api/rpc`(快照帧)这类大包会一直裸奔。
|
|
198
|
+
// SSE / 终端流 / Range 媒体在 compress.ts 里显式跳过(不能攒、不能压)。
|
|
199
|
+
app.use(compressText());
|
|
200
|
+
app.use(immutableAssets());
|
|
201
|
+
const uploadStore = createUploadStore({ dataDir: config.dataDir });
|
|
202
|
+
registerUploadRoutes(app, { uploadStore, logger }); // 附件(ADR-0020 §P3)
|
|
203
|
+
const workspaceOf = () => runtime.state().files?.root ?? config.workspace;
|
|
204
|
+
registerAuxRoutes(app, {
|
|
205
|
+
providerStore,
|
|
206
|
+
workspaceOf,
|
|
207
|
+
projects: () => runtime.state().piProjects,
|
|
208
|
+
deleteSession: (id) => runtime.deleteSession(id),
|
|
209
|
+
pushStore,
|
|
210
|
+
onChange: () => void runProviderRequest({ type: "list_providers" }, () => { }),
|
|
211
|
+
});
|
|
212
|
+
registerLiveRoutes(app, {
|
|
213
|
+
sinks,
|
|
214
|
+
snapshotFor: (clientId) => runtime.snapshotFor(clientId),
|
|
215
|
+
clientTracker: { setSlim: runtime.clientView.setSlim, drop: runtime.clientView.drop },
|
|
216
|
+
logger,
|
|
217
|
+
handleMessage: async (clientId, shim, raw) => {
|
|
218
|
+
await dispatchRequest(shim, raw, clientId);
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
registerHealthAndStatic(app, {
|
|
222
|
+
workspace: config.workspace,
|
|
223
|
+
engine: config.engine,
|
|
224
|
+
webDist,
|
|
225
|
+
devWebPort: DEV_WEB_PORT,
|
|
226
|
+
counts: () => ({
|
|
227
|
+
sessions: runtime.state().piProjects.reduce((total, project) => total + project.sessions.length, 0),
|
|
228
|
+
conversations: runtime.state().conversations.length,
|
|
229
|
+
}),
|
|
230
|
+
});
|
|
231
|
+
const httpServer = createServer(app);
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// WebSocket(握手校验是 ADR-0006 的核心条目)
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
236
|
+
httpServer.on("upgrade", (request, socket, head) => {
|
|
237
|
+
// WS 不走 Express 中间件,得在这里单独判一次
|
|
238
|
+
const forwarded = request.headers["x-forwarded-for"];
|
|
239
|
+
const auth = decide({
|
|
240
|
+
password: config.password,
|
|
241
|
+
remoteAddress: effectiveAddress(request.socket.remoteAddress, typeof forwarded === "string" ? forwarded : undefined),
|
|
242
|
+
authorization: request.headers.authorization,
|
|
243
|
+
cookie: request.headers.cookie,
|
|
188
244
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// 先建 http server:Vite 的 HMR 要挂到它上面(见下面的 middleware mode)。
|
|
195
|
-
// 路由按注册顺序匹配,所以这里提前 createServer 不影响 /api 的优先级。
|
|
196
|
-
const httpServer = createServer(app);
|
|
197
|
-
app.get("/api/health", (_req, res) => {
|
|
198
|
-
const body = { ok: true, codex: supervisor.status(), version };
|
|
199
|
-
res.json(body);
|
|
200
|
-
});
|
|
201
|
-
app.get("/api/version", (_req, res) => {
|
|
202
|
-
const hs = client.handshake();
|
|
203
|
-
res.json({
|
|
204
|
-
name: "oh-my-agent-web",
|
|
205
|
-
version,
|
|
206
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
207
|
-
codexVersion: codexVersionFromHandshake(hs?.userAgent),
|
|
208
|
-
codexUserAgent: hs?.userAgent ?? null,
|
|
209
|
-
codexUrl: supervisor.url,
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
// 前端:dev 把 Vite 当中间件挂进本进程(源码 + HMR);生产发 web/dist。
|
|
213
|
-
// 两种情况下对外都只有 http://127.0.0.1:<port> 一个源,页面与 /ws 同源。
|
|
214
|
-
if (DEV_SOURCE) {
|
|
215
|
-
// devDependency,且只在源码模式下加载。
|
|
216
|
-
const { createServer: createViteServer } = await import("vite");
|
|
217
|
-
const vite = await createViteServer({
|
|
218
|
-
configFile: join(PACKAGE_ROOT, "web", "vite.config.ts"),
|
|
219
|
-
// spa:index.html 的转换 + history fallback 都交给 Vite,
|
|
220
|
-
// 替代生产分支里那段 express 的 SPA fallback。
|
|
221
|
-
appType: "spa",
|
|
222
|
-
server: { middlewareMode: true, hmr: { server: httpServer, path: DEV_HMR_PATH } },
|
|
223
|
-
});
|
|
224
|
-
app.use(vite.middlewares);
|
|
225
|
-
httpServer.once("close", () => void vite.close());
|
|
226
|
-
log(`serving web UI from source via Vite (middleware mode, hmr=${DEV_HMR_PATH}, no build needed)`);
|
|
245
|
+
if (auth !== "allow") {
|
|
246
|
+
// 不发 WWW-Authenticate:那会让浏览器弹原生登录框,与自建登录页重复
|
|
247
|
+
socket.write(auth === "forbid" ? "HTTP/1.1 403 Forbidden\r\n\r\n" : "HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
248
|
+
socket.destroy();
|
|
249
|
+
return;
|
|
227
250
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
log(`serving web UI from ${webDist}`);
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
log(`web/dist not found — dev 直接跑 \`npm run dev\`(源码模式,无需 build),生产先 \`npm run build\``);
|
|
239
|
-
app.get(/^\/(?!api\/|ws).*/, (_req, res) => {
|
|
240
|
-
res.status(404).type("text/plain").send("web/dist 不存在。dev 请跑 npm run dev(源码模式,无需 build);生产请先 npm run build。");
|
|
241
|
-
});
|
|
242
|
-
}
|
|
251
|
+
const origin = request.headers.origin;
|
|
252
|
+
const host = request.headers.host;
|
|
253
|
+
if (!isAllowedOrigin(origin, policy) || !isAllowedHost(host, policy)) {
|
|
254
|
+
logger.warn("拒绝 WS 握手:来源不在白名单", { origin, host, policy: describePolicy(policy) });
|
|
255
|
+
socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
|
|
256
|
+
socket.destroy();
|
|
257
|
+
return;
|
|
243
258
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
/* ignore */
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
const broadcast = (msg) => {
|
|
258
|
-
for (const ws of browsers)
|
|
259
|
-
send(ws, msg);
|
|
260
|
-
};
|
|
261
|
-
const broadcastStatus = () => {
|
|
262
|
-
broadcast({ type: "status", codex: supervisor.status() });
|
|
263
|
-
};
|
|
264
|
-
let prevPhase = supervisor.status().phase;
|
|
265
|
-
supervisor.onStatus((status) => {
|
|
266
|
-
broadcastStatus();
|
|
267
|
-
// supervisor 重新就绪后主动拉一次 codex 连接,避免等满 client 自己的退避。
|
|
268
|
-
if (status.phase === "ready" && prevPhase !== "ready" && !client.isReady())
|
|
269
|
-
client.reconnect();
|
|
270
|
-
prevPhase = status.phase;
|
|
271
|
-
});
|
|
272
|
-
client.on("state", (state) => {
|
|
273
|
-
supervisor.setConnected(state.connected, state.error);
|
|
259
|
+
wss.handleUpgrade(request, socket, head, (websocket) => wss.emit("connection", websocket));
|
|
260
|
+
});
|
|
261
|
+
wss.on("connection", (socket) => {
|
|
262
|
+
sockets.add(socket);
|
|
263
|
+
logger.info("客户端已连接", { clients: sockets.size });
|
|
264
|
+
// 首屏:立刻给一份快照,不等节流窗口
|
|
265
|
+
sender.pushSnapshot(runtime.snapshot());
|
|
266
|
+
sender.flush(Date.now());
|
|
267
|
+
socket.on("message", (raw) => {
|
|
268
|
+
void dispatchRequest(socket, raw.toString());
|
|
274
269
|
});
|
|
275
|
-
|
|
276
|
-
|
|
270
|
+
socket.on("close", () => {
|
|
271
|
+
sockets.delete(socket);
|
|
272
|
+
logger.info("客户端已断开", { clients: sockets.size });
|
|
277
273
|
});
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
client.on("serverRequest", (evt) => {
|
|
282
|
-
const key = String(evt.id);
|
|
283
|
-
const threadId = isRecord(evt.params) && typeof evt.params.threadId === "string" ? evt.params.threadId : null;
|
|
284
|
-
pendingServerRequests.set(key, { method: evt.method, threadId });
|
|
285
|
-
broadcast({ type: "serverRequest", id: evt.id, method: evt.method, params: evt.params });
|
|
274
|
+
socket.on("error", (error) => {
|
|
275
|
+
logger.warn("socket 错误", { message: error.message });
|
|
276
|
+
sockets.delete(socket);
|
|
286
277
|
});
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
278
|
+
});
|
|
279
|
+
// 客户端请求分发在 ws/handle-message.ts(index 只负责装配)
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// 启动
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
async function main() {
|
|
284
|
+
// 旧数据目录(~/.agent-web)自动搬到新目录:dev 直接跑本文件时也要搬
|
|
285
|
+
migrateLegacyDataDir({ dataDir: config.dataDir, log: (line) => logger.info(line) });
|
|
286
|
+
const problems = [...envProblems, ...validateConfig(config, { home: homedir() })];
|
|
287
|
+
if (problems.length > 0) {
|
|
288
|
+
for (const problem of problems)
|
|
289
|
+
logger.error(`启动自检失败:${problem}`);
|
|
290
|
+
process.exit(1);
|
|
301
291
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
case LOCAL_METHODS.codexStatus:
|
|
307
|
-
return { status: supervisor.status() };
|
|
308
|
-
case LOCAL_METHODS.codexRestart:
|
|
309
|
-
await supervisor.restart();
|
|
310
|
-
client.reconnect();
|
|
311
|
-
return { ok: true };
|
|
312
|
-
case LOCAL_METHODS.codexLog:
|
|
313
|
-
return { lines: supervisor.logLines() };
|
|
314
|
-
case LOCAL_METHODS.requestLogsList:
|
|
315
|
-
return inspector.list(typeof p.threadId === "string" ? p.threadId : undefined, typeof p.limit === "number" ? p.limit : undefined);
|
|
316
|
-
case LOCAL_METHODS.requestLogsDetail: {
|
|
317
|
-
if (typeof p.id !== "string" || !p.id)
|
|
318
|
-
throw new FsError(-32602, "omaw/request-logs/detail requires { id: string }");
|
|
319
|
-
const entry = await inspector.get(p.id);
|
|
320
|
-
if (!entry)
|
|
321
|
-
throw new FsError(-32004, "request log not found");
|
|
322
|
-
return entry;
|
|
323
|
-
}
|
|
324
|
-
case LOCAL_METHODS.fsList:
|
|
325
|
-
return listDirectory({
|
|
326
|
-
path: typeof p.path === "string" ? p.path : undefined,
|
|
327
|
-
cwd,
|
|
328
|
-
maxEntries: typeof p.maxEntries === "number" ? p.maxEntries : undefined,
|
|
329
|
-
});
|
|
330
|
-
case LOCAL_METHODS.fsRead:
|
|
331
|
-
if (typeof p.path !== "string")
|
|
332
|
-
throw new FsError(-32602, "omaw/fs/read requires { path: string }");
|
|
333
|
-
return readTextFile({
|
|
334
|
-
path: p.path,
|
|
335
|
-
cwd,
|
|
336
|
-
maxBytes: typeof p.maxBytes === "number" ? p.maxBytes : undefined,
|
|
337
|
-
});
|
|
338
|
-
case LOCAL_METHODS.paths: {
|
|
339
|
-
const codexHome = client.handshake()?.codexHome || process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
340
|
-
return resolvePaths(cwd, codexHome);
|
|
341
|
-
}
|
|
342
|
-
default:
|
|
343
|
-
throw { code: -32601, message: `unknown local method: ${method}` };
|
|
344
|
-
}
|
|
292
|
+
const writable = await checkDataDirWritable(config.dataDir);
|
|
293
|
+
if (!writable.ok) {
|
|
294
|
+
logger.error(`启动自检失败:${writable.reason}`);
|
|
295
|
+
process.exit(1);
|
|
345
296
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
// client's first requests; only report -32001 if it never comes up.
|
|
357
|
-
if (!client.isReady() && !(await client.waitUntilReady(CODEX_READY_WAIT_MS))) {
|
|
358
|
-
throw { code: -32001, message: "codex app-server not connected" };
|
|
359
|
-
}
|
|
360
|
-
result = await client.request(method, params);
|
|
361
|
-
}
|
|
362
|
-
send(ws, { type: "rpcResult", requestId, ok: true, result });
|
|
363
|
-
}
|
|
364
|
-
catch (err) {
|
|
365
|
-
send(ws, { type: "rpcResult", requestId, ok: false, error: asJsonRpcError(err) });
|
|
366
|
-
}
|
|
297
|
+
// 日志自己也住在数据目录里 → 确认可写之后再挂文件 sink
|
|
298
|
+
fileSink = createRotatingFileSink({ file: logFileName(config.dataDir), onError: (message) => logger.warn(message) });
|
|
299
|
+
// 单实例锁(ADR-0007):必须在读/写任何会话数据之前拿到
|
|
300
|
+
const acquired = await acquireLock(config.dataDir, { pid: process.pid, port: config.port, startedAt: Date.now() });
|
|
301
|
+
if (!acquired.ok) {
|
|
302
|
+
logger.error("已经有一个 agent-web 在跑,本进程退出", { holder: describeHolder(acquired.holder) });
|
|
303
|
+
console.error(`\n已经有一个 agent-web 在这个数据目录上跑着:\n ${describeHolder(acquired.holder)}\n`);
|
|
304
|
+
console.error(` → 打开它:http://127.0.0.1:${acquired.holder.port}`);
|
|
305
|
+
console.error(" → 或者先停掉它(前台运行时 Ctrl-C 即可)\n");
|
|
306
|
+
process.exit(1);
|
|
367
307
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (answeredServerRequests.has(key))
|
|
371
|
-
return; // 其他标签页已答复,忽略后来者
|
|
372
|
-
rememberAnswered(key);
|
|
373
|
-
const pending = pendingServerRequests.get(key);
|
|
374
|
-
pendingServerRequests.delete(key);
|
|
375
|
-
if (error)
|
|
376
|
-
client.respondError(id, error);
|
|
377
|
-
else
|
|
378
|
-
client.respond(id, result);
|
|
379
|
-
// 让其他标签页关闭审批弹窗;复用 codex 自己的通知名。
|
|
380
|
-
broadcast({
|
|
381
|
-
type: "event",
|
|
382
|
-
method: "serverRequest/resolved",
|
|
383
|
-
params: { threadId: pending?.threadId ?? null, requestId: id },
|
|
384
|
-
});
|
|
308
|
+
if (acquired.stale && acquired.previous) {
|
|
309
|
+
logger.warn("接管陈旧锁(上一个进程已不在)", { previous: describeHolder(acquired.previous) });
|
|
385
310
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (!isRecord(parsed) || typeof parsed.type !== "string")
|
|
396
|
-
return;
|
|
397
|
-
const msg = parsed;
|
|
398
|
-
switch (msg.type) {
|
|
399
|
-
case "rpc": {
|
|
400
|
-
if (typeof msg.requestId !== "string" || typeof msg.method !== "string")
|
|
401
|
-
return;
|
|
402
|
-
await handleRpc(ws, msg.requestId, msg.method, msg.params);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
case "reply": {
|
|
406
|
-
if (msg.id === undefined || msg.id === null)
|
|
407
|
-
return;
|
|
408
|
-
const hasError = "error" in msg && msg.error !== undefined;
|
|
409
|
-
const error = hasError ? asJsonRpcError(msg.error) : undefined;
|
|
410
|
-
handleReply(msg.id, msg.result, error);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
case "ping":
|
|
414
|
-
send(ws, { type: "pong" });
|
|
415
|
-
return;
|
|
416
|
-
default:
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
wss.on("connection", (ws) => {
|
|
421
|
-
ws.__alive = true;
|
|
422
|
-
browsers.add(ws);
|
|
423
|
-
ws.on("pong", () => {
|
|
424
|
-
ws.__alive = true;
|
|
425
|
-
});
|
|
426
|
-
ws.on("message", (raw) => {
|
|
427
|
-
void onBrowserMessage(ws, raw).catch((err) => log("browser message handler error:", err));
|
|
428
|
-
});
|
|
429
|
-
ws.on("error", (err) => log("browser socket error:", err.message));
|
|
430
|
-
ws.on("close", () => browsers.delete(ws));
|
|
431
|
-
const info = {
|
|
432
|
-
version,
|
|
433
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
434
|
-
cwd,
|
|
435
|
-
codexUrl: supervisor.url,
|
|
436
|
-
codexVersion: codexVersionFromHandshake(client.handshake()?.userAgent),
|
|
437
|
-
platform: process.platform,
|
|
438
|
-
};
|
|
439
|
-
send(ws, { type: "welcome", info });
|
|
440
|
-
send(ws, { type: "status", codex: supervisor.status() });
|
|
311
|
+
const { conversations } = await runtime.init().then(() => ({ conversations: runtime.state().conversations }));
|
|
312
|
+
// 分时价格索引(ADR-0026 D 期):要在读历史 / 算账之前就绪;读不到就是空表 = 全部走引擎给的 cost
|
|
313
|
+
await catalogMigration.catch(() => undefined);
|
|
314
|
+
await refreshPriceIndex(piAgentDir());
|
|
315
|
+
void runProviderRequest({ type: "list_providers" }, () => { }); // 启动先填一次 provider 列表
|
|
316
|
+
logger.info("会话索引已装载", { conversations: conversations.length });
|
|
317
|
+
httpServer.on("error", (error) => {
|
|
318
|
+
logger.error(describeListenError(error, config.port, `可能是另一个 agent-web 或别的程序占着这个端口`));
|
|
319
|
+
void releaseLock(config.dataDir, process.pid).finally(() => process.exit(1));
|
|
441
320
|
});
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
/* fall through */
|
|
451
|
-
}
|
|
452
|
-
if (DEV_SOURCE && pathname === DEV_HMR_PATH)
|
|
453
|
-
return;
|
|
454
|
-
if (pathname !== "/ws") {
|
|
455
|
-
socket.destroy();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (!originAllowed(req)) {
|
|
459
|
-
socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
460
|
-
socket.destroy();
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
|
|
321
|
+
await new Promise((resolve) => httpServer.listen(config.port, config.host, resolve));
|
|
322
|
+
logger.info("agent-web 已启动", {
|
|
323
|
+
url: `http://${config.host}:${config.port}`,
|
|
324
|
+
workspace: config.workspace,
|
|
325
|
+
dataDir: config.dataDir,
|
|
326
|
+
engine: config.engine,
|
|
327
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
328
|
+
conversations: runtime.state().conversations.length,
|
|
464
329
|
});
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
for (const ws of browsers) {
|
|
468
|
-
if (ws.__alive === false) {
|
|
469
|
-
ws.terminate();
|
|
470
|
-
browsers.delete(ws);
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
ws.__alive = false;
|
|
474
|
-
try {
|
|
475
|
-
ws.ping();
|
|
476
|
-
}
|
|
477
|
-
catch {
|
|
478
|
-
/* ignore */
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}, HEARTBEAT_MS);
|
|
482
|
-
heartbeat.unref?.();
|
|
483
|
-
/* -------- listen -------- */
|
|
484
|
-
await new Promise((resolvePromise, rejectPromise) => {
|
|
485
|
-
httpServer.once("error", rejectPromise);
|
|
486
|
-
httpServer.listen(port, host, () => resolvePromise());
|
|
487
|
-
});
|
|
488
|
-
const address = httpServer.address();
|
|
489
|
-
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
490
|
-
const url = `http://${host}:${actualPort}`;
|
|
491
|
-
log(`listening on ${url} (ws://${host}:${actualPort}/ws)`);
|
|
492
|
-
log(`codex app-server: ${supervisor.url} (${supervisor.isExternal() ? "external" : "managed"})`);
|
|
493
|
-
/* -------- close -------- */
|
|
494
|
-
let closed = false;
|
|
495
|
-
async function close() {
|
|
496
|
-
if (closed)
|
|
497
|
-
return;
|
|
498
|
-
closed = true;
|
|
499
|
-
clearInterval(heartbeat);
|
|
500
|
-
for (const ws of browsers) {
|
|
501
|
-
try {
|
|
502
|
-
ws.close(1001, "server shutting down");
|
|
503
|
-
}
|
|
504
|
-
catch {
|
|
505
|
-
/* ignore */
|
|
506
|
-
}
|
|
507
|
-
ws.terminate();
|
|
508
|
-
}
|
|
509
|
-
browsers.clear();
|
|
510
|
-
client.close();
|
|
511
|
-
await supervisor.stop();
|
|
512
|
-
await inspector.close();
|
|
513
|
-
await new Promise((resolvePromise) => {
|
|
514
|
-
wss.close(() => resolvePromise());
|
|
515
|
-
// 没有活跃连接时 wss.close 回调可能不触发;兜底。
|
|
516
|
-
setTimeout(resolvePromise, 500);
|
|
517
|
-
});
|
|
518
|
-
await new Promise((resolvePromise) => {
|
|
519
|
-
httpServer.close(() => resolvePromise());
|
|
520
|
-
setTimeout(resolvePromise, 500);
|
|
521
|
-
});
|
|
330
|
+
if (!webDist) {
|
|
331
|
+
logger.info("前端未构建,开发态请访问", { vite: `http://127.0.0.1:${DEV_WEB_PORT}` });
|
|
522
332
|
}
|
|
523
|
-
|
|
333
|
+
setInterval(() => {
|
|
334
|
+
const buffered = [...sockets].reduce((max, socket) => Math.max(max, socket.bufferedAmount), 0);
|
|
335
|
+
sender.setBusy(buffered > BACKPRESSURE_BYTES);
|
|
336
|
+
sender.tick(Date.now());
|
|
337
|
+
}, TICK_MS);
|
|
338
|
+
setInterval(() => {
|
|
339
|
+
for (const socket of sockets) {
|
|
340
|
+
if (socket.readyState === socket.OPEN)
|
|
341
|
+
socket.ping();
|
|
342
|
+
}
|
|
343
|
+
}, 25_000);
|
|
524
344
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
.catch((err) => {
|
|
540
|
-
console.error("[oh-my-agent-web] failed to start:", err);
|
|
541
|
-
process.exit(1);
|
|
542
|
-
});
|
|
345
|
+
async function shutdown(signal) {
|
|
346
|
+
if (shuttingDown)
|
|
347
|
+
return;
|
|
348
|
+
shuttingDown = true;
|
|
349
|
+
logger.info("收到退出信号,正在关闭", { signal });
|
|
350
|
+
sender.flush(Date.now());
|
|
351
|
+
engineWiring.close(); // 收掉 codex app-server 子进程(不给它留孤儿)
|
|
352
|
+
for (const socket of sockets)
|
|
353
|
+
socket.close(1001, "server shutting down");
|
|
354
|
+
wss.close();
|
|
355
|
+
httpServer.close();
|
|
356
|
+
// 锁要还回去:只有还属于自己时才删(避免删掉接管者的锁)
|
|
357
|
+
await releaseLock(config.dataDir, process.pid);
|
|
358
|
+
process.exit(0);
|
|
543
359
|
}
|
|
360
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
361
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
362
|
+
main().catch((error) => {
|
|
363
|
+
logger.error("启动失败", { message: error instanceof Error ? error.message : String(error) });
|
|
364
|
+
process.exit(1);
|
|
365
|
+
});
|