chatccc 0.2.252 → 0.2.254
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 +10 -1
- package/bin/cccagent.mjs +12 -3
- package/bin/chatccc.mjs +15 -6
- package/config.sample.json +2 -1
- package/deepccc-agent/README.md +40 -35
- package/deepccc-agent/os-prompts/darwin.md +8 -8
- package/deepccc-agent/os-prompts/linux.md +8 -8
- package/deepccc-agent/os-prompts/win32.md +11 -11
- package/deepccc-agent/package.json +65 -65
- package/dist/deepccc-agent/src/cli.js +634 -0
- package/dist/deepccc-agent/src/config.js +81 -0
- package/dist/deepccc-agent/src/context.js +348 -0
- package/dist/deepccc-agent/src/file-log.js +34 -0
- package/dist/deepccc-agent/src/file-tools.js +1208 -0
- package/dist/deepccc-agent/src/index.js +601 -0
- package/dist/deepccc-agent/src/permissions.js +170 -0
- package/dist/deepccc-agent/src/privacy.js +124 -0
- package/dist/deepccc-agent/src/proc-tree-kill.js +60 -0
- package/dist/deepccc-agent/src/progress/cards-helpers.js +70 -0
- package/dist/deepccc-agent/src/progress/reducer.js +102 -0
- package/dist/deepccc-agent/src/progress/terminal-renderer.js +264 -0
- package/dist/deepccc-agent/src/progress/view.js +30 -0
- package/dist/deepccc-agent/src/raw-stream-log.js +106 -0
- package/dist/deepccc-agent/src/session-search.js +276 -0
- package/dist/deepccc-agent/src/session-select.js +23 -0
- package/dist/deepccc-agent/src/sigint.js +26 -0
- package/dist/deepccc-agent/src/skills.js +178 -0
- package/dist/deepccc-agent/src/web-tools.js +246 -0
- package/dist/src/adapters/adapter-interface.js +19 -0
- package/dist/src/adapters/ccc-adapter.js +112 -0
- package/dist/src/adapters/claude-adapter.js +497 -0
- package/dist/src/adapters/claude-session-meta-store.js +92 -0
- package/dist/src/adapters/codex-adapter.js +279 -0
- package/dist/src/adapters/codex-session-meta-store.js +94 -0
- package/dist/src/adapters/cursor-adapter.js +491 -0
- package/dist/src/adapters/cursor-session-meta-store.js +116 -0
- package/dist/src/adapters/jsonl-stream.js +104 -0
- package/{src/adapters/proc-tree-kill.ts → dist/src/adapters/proc-tree-kill.js} +94 -97
- package/dist/src/adapters/raw-stream-log.js +106 -0
- package/dist/src/adapters/resource-monitor.js +113 -0
- package/dist/src/agent-activity.js +133 -0
- package/dist/src/agent-delegate-task-rpc.js +129 -0
- package/dist/src/agent-delegate-task.js +48 -0
- package/dist/src/agent-file-rpc.js +152 -0
- package/dist/src/agent-image-rpc.js +148 -0
- package/dist/src/agent-platform-routing.js +13 -0
- package/dist/src/agent-reload-config-rpc.js +23 -0
- package/dist/src/agent-rpc-body.js +87 -0
- package/dist/src/agent-stop-stuck.js +110 -0
- package/dist/src/agent-team/application/board-service.js +142 -0
- package/dist/src/agent-team/application/main-agent-service.js +151 -0
- package/dist/src/agent-team/domain/board.js +69 -0
- package/dist/src/agent-team/http/board-routes.js +213 -0
- package/dist/src/agent-team/infrastructure/filesystem-browser.js +112 -0
- package/dist/src/agent-team/infrastructure/main-agent-session-runtime.js +30 -0
- package/dist/src/agent-team/main-agent-bootstrap.js +15 -0
- package/dist/src/agent-team/repositories/board-repository.js +10 -0
- package/dist/src/agent-team/repositories/feishu-p2p-contact-store.js +64 -0
- package/dist/src/agent-team/repositories/json-board-repository.js +209 -0
- package/dist/src/agent-team/repositories/main-agent-binding-repository.js +84 -0
- package/dist/src/agent-team/sync/board-sync-adapter.js +1 -0
- package/dist/src/agent-team/web/agent-team-page.js +219 -0
- package/dist/src/agent-tool.js +10 -0
- package/dist/src/card-action-routing.js +7 -0
- package/dist/src/card-plain-text.js +101 -0
- package/dist/src/cardkit.js +158 -0
- package/dist/src/cards.js +573 -0
- package/dist/src/chatgpt-subscription-rpc.js +18 -0
- package/dist/src/chatgpt-subscription.js +199 -0
- package/dist/src/chrome-devtools-guard.js +238 -0
- package/dist/src/claude-sdk-installer.js +249 -0
- package/dist/src/codex-reset-actions.js +143 -0
- package/dist/src/config-utils.js +149 -0
- package/dist/src/config.js +808 -0
- package/dist/src/cursor-usage.js +77 -0
- package/dist/src/exit-banner.js +28 -0
- package/dist/src/feishu-api.js +1404 -0
- package/dist/src/feishu-message-ingress.js +137 -0
- package/dist/src/feishu-platform.js +97 -0
- package/dist/src/format-message.js +252 -0
- package/dist/src/git-command.js +155 -0
- package/dist/src/im-skills.js +121 -0
- package/dist/src/index.js +835 -0
- package/dist/src/litellm-proxy.js +300 -0
- package/dist/src/orchestrator.js +2096 -0
- package/dist/src/package-root.js +26 -0
- package/dist/src/platform-adapter.js +7 -0
- package/dist/src/platform-startup.js +6 -0
- package/dist/src/privacy.js +100 -0
- package/dist/src/progress/reducer.js +102 -0
- package/dist/src/progress/terminal-renderer.js +264 -0
- package/dist/src/progress/view.js +30 -0
- package/dist/src/response-stall.js +14 -0
- package/dist/src/runtime-entry.js +13 -0
- package/dist/src/runtime-reload.js +19 -0
- package/dist/src/session-chat-binding.js +183 -0
- package/dist/src/session-name.js +7 -0
- package/dist/src/session.js +2147 -0
- package/dist/src/shared-prefix.js +16 -0
- package/dist/src/shared.js +493 -0
- package/dist/src/sim-agent.js +105 -0
- package/dist/src/sim-platform.js +142 -0
- package/dist/src/sim-store.js +231 -0
- package/dist/src/simplify.js +99 -0
- package/dist/src/startup-lifecycle.js +209 -0
- package/dist/src/stream-state.js +141 -0
- package/dist/src/terminal-error.js +100 -0
- package/dist/src/trace.js +50 -0
- package/dist/src/turn-cards.js +92 -0
- package/dist/src/update-command-guard.js +114 -0
- package/{src/web-ui.ts → dist/src/web-ui.js} +753 -839
- package/dist/src/wechat-platform.js +545 -0
- package/package.json +76 -74
- package/deepccc-agent/LICENSE +0 -201
- package/deepccc-agent/bin/deepccc.mjs +0 -26
- package/deepccc-agent/docs/cache-hit-rate-1.jpg +0 -0
- package/deepccc-agent/docs/cache-hit-rate-2.jpg +0 -0
- package/deepccc-agent/package-lock.json +0 -2027
- package/deepccc-agent/src/__tests__/chat-session.test.ts +0 -877
- package/deepccc-agent/src/__tests__/cli-json.test.ts +0 -49
- package/deepccc-agent/src/__tests__/config.test.ts +0 -34
- package/deepccc-agent/src/__tests__/context.test.ts +0 -341
- package/deepccc-agent/src/__tests__/file-tools.test.ts +0 -240
- package/deepccc-agent/src/__tests__/permissions.test.ts +0 -199
- package/deepccc-agent/src/__tests__/privacy.test.ts +0 -318
- package/deepccc-agent/src/__tests__/progress-reducer.test.ts +0 -121
- package/deepccc-agent/src/__tests__/session-search.test.ts +0 -262
- package/deepccc-agent/src/__tests__/session-select.test.ts +0 -116
- package/deepccc-agent/src/__tests__/sigint.test.ts +0 -56
- package/deepccc-agent/src/__tests__/skills.test.ts +0 -284
- package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +0 -247
- package/deepccc-agent/src/__tests__/web-tools.test.ts +0 -220
- package/deepccc-agent/src/cli.ts +0 -682
- package/deepccc-agent/src/config.ts +0 -101
- package/deepccc-agent/src/context.ts +0 -465
- package/deepccc-agent/src/file-log.ts +0 -38
- package/deepccc-agent/src/file-tools.ts +0 -1493
- package/deepccc-agent/src/index.ts +0 -710
- package/deepccc-agent/src/permissions.ts +0 -226
- package/deepccc-agent/src/privacy.ts +0 -141
- package/deepccc-agent/src/proc-tree-kill.ts +0 -61
- package/deepccc-agent/src/progress/cards-helpers.ts +0 -76
- package/deepccc-agent/src/progress/reducer.ts +0 -113
- package/deepccc-agent/src/progress/terminal-renderer.ts +0 -294
- package/deepccc-agent/src/progress/view.ts +0 -77
- package/deepccc-agent/src/raw-stream-log.ts +0 -124
- package/deepccc-agent/src/session-search.ts +0 -370
- package/deepccc-agent/src/session-select.ts +0 -48
- package/deepccc-agent/src/sigint.ts +0 -50
- package/deepccc-agent/src/skills.ts +0 -205
- package/deepccc-agent/src/web-tools.ts +0 -313
- package/deepccc-agent/tsconfig.build.json +0 -13
- package/deepccc-agent/tsconfig.json +0 -13
- package/deepccc-agent/vitest.config.ts +0 -7
- package/src/__tests__/adapter-interface.test.ts +0 -152
- package/src/__tests__/agent-activity.test.ts +0 -86
- package/src/__tests__/agent-delegate-task-rpc.test.ts +0 -165
- package/src/__tests__/agent-image-rpc.test.ts +0 -34
- package/src/__tests__/agent-platform-routing.test.ts +0 -26
- package/src/__tests__/agent-reload-config-rpc.test.ts +0 -99
- package/src/__tests__/agent-rpc-body.test.ts +0 -42
- package/src/__tests__/builtin-chat-session.test.ts +0 -532
- package/src/__tests__/builtin-cli-json.test.ts +0 -39
- package/src/__tests__/builtin-config.test.ts +0 -26
- package/src/__tests__/builtin-context.test.ts +0 -319
- package/src/__tests__/builtin-file-tools.test.ts +0 -240
- package/src/__tests__/builtin-permissions.test.ts +0 -219
- package/src/__tests__/builtin-session-search.test.ts +0 -262
- package/src/__tests__/builtin-session-select.test.ts +0 -116
- package/src/__tests__/builtin-sigint.test.ts +0 -56
- package/src/__tests__/builtin-skills.test.ts +0 -284
- package/src/__tests__/builtin-web-tools.test.ts +0 -220
- package/src/__tests__/card-action-routing.test.ts +0 -18
- package/src/__tests__/card-plain-text.test.ts +0 -45
- package/src/__tests__/cardkit.test.ts +0 -60
- package/src/__tests__/cards.test.ts +0 -607
- package/src/__tests__/ccc-adapter.test.ts +0 -194
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +0 -89
- package/src/__tests__/chatgpt-subscription.test.ts +0 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +0 -165
- package/src/__tests__/claude-adapter.test.ts +0 -614
- package/src/__tests__/claude-raw-stream-log.test.ts +0 -96
- package/src/__tests__/claude-sdk-installer.test.ts +0 -285
- package/src/__tests__/codex-adapter.test.ts +0 -331
- package/src/__tests__/codex-raw-stream-log.test.ts +0 -170
- package/src/__tests__/codex-reset-actions.test.ts +0 -146
- package/src/__tests__/config-reload.test.ts +0 -284
- package/src/__tests__/config-sample.test.ts +0 -97
- package/src/__tests__/config-utils.test.ts +0 -40
- package/src/__tests__/config.test.ts +0 -395
- package/src/__tests__/crash-logging.test.ts +0 -360
- package/src/__tests__/cursor-adapter.test.ts +0 -890
- package/src/__tests__/cursor-session-meta-store.test.ts +0 -212
- package/src/__tests__/feishu-api.test.ts +0 -60
- package/src/__tests__/feishu-avatar.test.ts +0 -504
- package/src/__tests__/feishu-message-ingress.test.ts +0 -138
- package/src/__tests__/feishu-platform.test.ts +0 -75
- package/src/__tests__/fixtures/codex_simple_text.jsonl +0 -4
- package/src/__tests__/fixtures/codex_with_tool.jsonl +0 -6
- package/src/__tests__/fixtures/cursor_partial_only.jsonl +0 -5
- package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +0 -13
- package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +0 -12
- package/src/__tests__/format-message.test.ts +0 -316
- package/src/__tests__/git-command.test.ts +0 -288
- package/src/__tests__/im-skills.test.ts +0 -125
- package/src/__tests__/jsonl-stream.test.ts +0 -79
- package/src/__tests__/orchestrator.test.ts +0 -1268
- package/src/__tests__/package-files.test.ts +0 -24
- package/src/__tests__/platform-startup.test.ts +0 -19
- package/src/__tests__/privacy.test.ts +0 -198
- package/src/__tests__/proc-tree-kill.test.ts +0 -108
- package/src/__tests__/progress-reducer.test.ts +0 -121
- package/src/__tests__/raw-stream-log.test.ts +0 -106
- package/src/__tests__/response-stall.test.ts +0 -49
- package/src/__tests__/restart.test.ts +0 -232
- package/src/__tests__/session-ccc-config.test.ts +0 -66
- package/src/__tests__/session.test.ts +0 -3004
- package/src/__tests__/shared-prefix.test.ts +0 -36
- package/src/__tests__/sim-agent.test.ts +0 -174
- package/src/__tests__/sim-platform.test.ts +0 -93
- package/src/__tests__/sim-store.test.ts +0 -214
- package/src/__tests__/simplify.test.ts +0 -283
- package/src/__tests__/startup-lifecycle.test.ts +0 -231
- package/src/__tests__/stop-session.test.ts +0 -162
- package/src/__tests__/stream-state.test.ts +0 -164
- package/src/__tests__/terminal-error.test.ts +0 -54
- package/src/__tests__/terminal-renderer.test.ts +0 -247
- package/src/__tests__/update-command-guard.test.ts +0 -144
- package/src/__tests__/web-ui.test.ts +0 -438
- package/src/__tests__/wechat-platform.test.ts +0 -111
- package/src/adapters/adapter-interface.ts +0 -217
- package/src/adapters/ccc-adapter.ts +0 -150
- package/src/adapters/claude-adapter.ts +0 -673
- package/src/adapters/claude-session-meta-store.ts +0 -120
- package/src/adapters/codex-adapter.ts +0 -426
- package/src/adapters/codex-session-meta-store.ts +0 -131
- package/src/adapters/cursor-adapter.ts +0 -681
- package/src/adapters/cursor-session-meta-store.ts +0 -154
- package/src/adapters/jsonl-stream.ts +0 -157
- package/src/adapters/raw-stream-log.ts +0 -124
- package/src/adapters/resource-monitor.ts +0 -141
- package/src/agent-activity.ts +0 -175
- package/src/agent-delegate-task-rpc.ts +0 -153
- package/src/agent-delegate-task.ts +0 -91
- package/src/agent-file-rpc.ts +0 -172
- package/src/agent-image-rpc.ts +0 -168
- package/src/agent-platform-routing.ts +0 -28
- package/src/agent-reload-config-rpc.ts +0 -34
- package/src/agent-rpc-body.ts +0 -92
- package/src/agent-stop-stuck.ts +0 -129
- package/src/card-action-routing.ts +0 -14
- package/src/card-plain-text.ts +0 -108
- package/src/cardkit.ts +0 -179
- package/src/cards.ts +0 -684
- package/src/chatgpt-subscription-rpc.ts +0 -27
- package/src/chatgpt-subscription.ts +0 -299
- package/src/chrome-devtools-guard.ts +0 -318
- package/src/claude-sdk-installer.ts +0 -324
- package/src/codex-reset-actions.ts +0 -184
- package/src/config-utils.ts +0 -211
- package/src/config.ts +0 -1063
- package/src/cursor-usage.ts +0 -128
- package/src/exit-banner.ts +0 -33
- package/src/feishu-api.ts +0 -1616
- package/src/feishu-message-ingress.ts +0 -195
- package/src/feishu-platform.ts +0 -159
- package/src/format-message.ts +0 -293
- package/src/git-command.ts +0 -202
- package/src/im-skills.ts +0 -149
- package/src/index.ts +0 -1089
- package/src/litellm-proxy.ts +0 -374
- package/src/orchestrator.ts +0 -2543
- package/src/platform-adapter.ts +0 -70
- package/src/platform-startup.ts +0 -16
- package/src/privacy.ts +0 -118
- package/src/progress/reducer.ts +0 -113
- package/src/progress/terminal-renderer.ts +0 -294
- package/src/progress/view.ts +0 -77
- package/src/response-stall.ts +0 -28
- package/src/runtime-reload.ts +0 -34
- package/src/session-chat-binding.ts +0 -292
- package/src/session-name.ts +0 -8
- package/src/session.ts +0 -2659
- package/src/shared-prefix.ts +0 -29
- package/src/shared.ts +0 -552
- package/src/sim-agent.ts +0 -167
- package/src/sim-platform.ts +0 -177
- package/src/sim-store.ts +0 -317
- package/src/simplify.ts +0 -120
- package/src/startup-lifecycle.ts +0 -250
- package/src/stream-state.ts +0 -177
- package/src/terminal-error.ts +0 -129
- package/src/trace.ts +0 -51
- package/src/turn-cards.ts +0 -118
- package/src/update-command-guard.ts +0 -165
- package/src/wechat-platform.ts +0 -680
|
@@ -1,700 +1,637 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// web-ui.ts — Setup Wizard & Management Dashboard HTTP Server
|
|
3
|
-
// =============================================================================
|
|
4
|
-
// Serves on the same port as the WebSocket relay (18080 default).
|
|
5
|
-
// - Setup mode: no config.json → show setup wizard, skip Feishu connection
|
|
6
|
-
// - Dashboard mode: config.json exists → serve management page alongside WS relay
|
|
7
|
-
// =============================================================================
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
try {
|
|
636
|
-
if (existsSync(ILINK_AUTH_PATH)) {
|
|
637
|
-
unlinkSync(ILINK_AUTH_PATH);
|
|
638
|
-
}
|
|
639
|
-
jsonReply(res, 200, { ok: true });
|
|
640
|
-
} catch (err) {
|
|
641
|
-
jsonReply(res, 500, { ok: false, error: (err as Error).message });
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
async function handleClaudeSdkStatus(_req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
646
|
-
const progress = getLastInstallProgress();
|
|
647
|
-
jsonReply(res, 200, {
|
|
648
|
-
installed: isClaudeSdkInstalled(),
|
|
649
|
-
version: getClaudeSdkInstalledVersion(),
|
|
650
|
-
running: isInstallRunning(),
|
|
651
|
-
// 展开到顶层:前端轮询 JS 读顶层 phase/message/percent/error(历史契约),
|
|
652
|
-
// 若只放在 progress 嵌套里,前端 s.phase 恒为 undefined → 进度条永不渲染。
|
|
653
|
-
...progress,
|
|
654
|
-
// 保留嵌套结构,兼容其它调用方。
|
|
655
|
-
progress,
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
async function handleClaudeSdkInstall(_req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
660
|
-
if (isInstallRunning()) {
|
|
661
|
-
jsonReply(res, 200, { ok: true, alreadyRunning: true });
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
try {
|
|
665
|
-
// 后台执行,不阻塞响应;前端通过 /api/claude-sdk/status 轮询进度
|
|
666
|
-
installClaudeSdk().catch((err: unknown) => {
|
|
667
|
-
console.error(`[WEB-UI] Claude SDK 安装失败: ${(err as Error).message}`);
|
|
668
|
-
});
|
|
669
|
-
jsonReply(res, 200, { ok: true });
|
|
670
|
-
} catch (err) {
|
|
671
|
-
jsonReply(res, 500, { ok: false, error: (err as Error).message });
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// ---------------------------------------------------------------------------
|
|
676
|
-
// HTML page (embedded template)
|
|
677
|
-
// ---------------------------------------------------------------------------
|
|
678
|
-
|
|
679
|
-
/** Agent Team 的首个占位页面:按产品约定只展示标题,不提前放入功能内容。 */
|
|
680
|
-
export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
|
|
681
|
-
<html lang="zh-CN">
|
|
682
|
-
<head>
|
|
683
|
-
<meta charset="UTF-8">
|
|
684
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
685
|
-
<title>Agent Team</title>
|
|
686
|
-
<style>
|
|
687
|
-
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
688
|
-
body{min-height:100vh;display:grid;place-items:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:radial-gradient(circle at 50% 20%,#312e81 0,#111827 42%,#020617 100%);color:#fff}
|
|
689
|
-
h1{font-size:clamp(40px,8vw,88px);font-weight:750;letter-spacing:-.04em;text-shadow:0 12px 42px rgba(129,140,248,.45)}
|
|
690
|
-
</style>
|
|
691
|
-
</head>
|
|
692
|
-
<body>
|
|
693
|
-
<h1>Agent Team</h1>
|
|
694
|
-
</body>
|
|
695
|
-
</html>`;
|
|
696
|
-
|
|
697
|
-
export const PAGE_HTML = `<!DOCTYPE html>
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// web-ui.ts — Setup Wizard & Management Dashboard HTTP Server
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Serves on the same port as the WebSocket relay (18080 default).
|
|
5
|
+
// - Setup mode: no config.json → show setup wizard, skip Feishu connection
|
|
6
|
+
// - Dashboard mode: config.json exists → serve management page alongside WS relay
|
|
7
|
+
// =============================================================================
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { spawn, execSync } from "node:child_process";
|
|
13
|
+
import { CHATCCC_PACKAGE_ROOT } from "./package-root.js";
|
|
14
|
+
import { resolveChatCccRuntimeSpawnSpec } from "./runtime-entry.js";
|
|
15
|
+
import { handleAgentTeamRequest } from "./agent-team/http/board-routes.js";
|
|
16
|
+
import { AGENT_TEAM_PAGE_HTML } from "./agent-team/web/agent-team-page.js";
|
|
17
|
+
export { AGENT_TEAM_PAGE_HTML } from "./agent-team/web/agent-team-page.js";
|
|
18
|
+
import { buildWebUiUrl, createInternalRestartEnv, openWebUiInDefaultBrowser, } from "./startup-lifecycle.js";
|
|
19
|
+
import { getClaudeSdkInstalledVersion, getLastInstallProgress, installClaudeSdk, isClaudeSdkInstalled, isInstallRunning, } from "./claude-sdk-installer.js";
|
|
20
|
+
const PROJECT_ROOT = CHATCCC_PACKAGE_ROOT;
|
|
21
|
+
const USER_DATA_DIR = join(homedir(), ".chatccc");
|
|
22
|
+
const CONFIG_FILE = join(USER_DATA_DIR, "config.json");
|
|
23
|
+
const CONFIG_SAMPLE_FILE = join(PROJECT_ROOT, "config.sample.json");
|
|
24
|
+
const PID_FILE = join(USER_DATA_DIR, "state", "runtime.pid");
|
|
25
|
+
const ILINK_AUTH_PATH = join(USER_DATA_DIR, "state", "ilink-auth.json");
|
|
26
|
+
export function chooseStartPath(input) {
|
|
27
|
+
if (input.hasInplaceActivateHook)
|
|
28
|
+
return "inplace";
|
|
29
|
+
if (input.isServiceRunning)
|
|
30
|
+
return "reload";
|
|
31
|
+
return "spawn";
|
|
32
|
+
}
|
|
33
|
+
/** 读取 cursor / codex 的 CLI 路径,优先新字段 path,回退旧字段 command */
|
|
34
|
+
function readToolPath(tool) {
|
|
35
|
+
if (!tool)
|
|
36
|
+
return "";
|
|
37
|
+
if (tool.path && tool.path.trim())
|
|
38
|
+
return tool.path;
|
|
39
|
+
if (tool.command && tool.command.trim())
|
|
40
|
+
return tool.command;
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
function loadConfig() {
|
|
44
|
+
if (!existsSync(CONFIG_FILE))
|
|
45
|
+
return {};
|
|
46
|
+
try {
|
|
47
|
+
let raw = readFileSync(CONFIG_FILE, "utf8");
|
|
48
|
+
// 移除可能意外写入的 UTF-8 BOM,避免 JSON.parse 失败导致返回空对象、
|
|
49
|
+
// UI 显示所有配置为空。
|
|
50
|
+
if (raw.charCodeAt(0) === 0xfeff)
|
|
51
|
+
raw = raw.slice(1);
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function saveConfig(cfg) {
|
|
59
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
|
|
60
|
+
}
|
|
61
|
+
function stringValue(value) {
|
|
62
|
+
return typeof value === "string" ? value.trim() : "";
|
|
63
|
+
}
|
|
64
|
+
function portValue(cfg) {
|
|
65
|
+
return Number.isInteger(cfg.port) && cfg.port >= 1 && cfg.port <= 65535 ? cfg.port : 18080;
|
|
66
|
+
}
|
|
67
|
+
function platformEnabled(cfg, platform) {
|
|
68
|
+
return cfg.platforms?.[platform]?.enabled !== false;
|
|
69
|
+
}
|
|
70
|
+
function feishuPlatformType(cfg) {
|
|
71
|
+
return cfg.platforms?.feishu?.platformType === "lark" ? "lark" : "feishu";
|
|
72
|
+
}
|
|
73
|
+
function ilinkReuseTokenOnStart(cfg) {
|
|
74
|
+
return cfg.platforms?.ilink?.reuseTokenOnStart !== false;
|
|
75
|
+
}
|
|
76
|
+
export function getRestartRequiredReasons(before, after) {
|
|
77
|
+
const reasons = [];
|
|
78
|
+
if (portValue(before) !== portValue(after))
|
|
79
|
+
reasons.push("port");
|
|
80
|
+
if (stringValue(before.feishu?.appId) !== stringValue(after.feishu?.appId))
|
|
81
|
+
reasons.push("feishu.appId");
|
|
82
|
+
if (stringValue(before.feishu?.appSecret) !== stringValue(after.feishu?.appSecret))
|
|
83
|
+
reasons.push("feishu.appSecret");
|
|
84
|
+
if (feishuPlatformType(before) !== feishuPlatformType(after))
|
|
85
|
+
reasons.push("platforms.feishu.platformType");
|
|
86
|
+
if (platformEnabled(before, "feishu") !== platformEnabled(after, "feishu"))
|
|
87
|
+
reasons.push("platforms.feishu.enabled");
|
|
88
|
+
if (platformEnabled(before, "ilink") !== platformEnabled(after, "ilink"))
|
|
89
|
+
reasons.push("platforms.ilink.enabled");
|
|
90
|
+
if (ilinkReuseTokenOnStart(before) !== ilinkReuseTokenOnStart(after)) {
|
|
91
|
+
reasons.push("platforms.ilink.reuseTokenOnStart");
|
|
92
|
+
}
|
|
93
|
+
return reasons;
|
|
94
|
+
}
|
|
95
|
+
async function applySavedConfigIfPossible(before, after) {
|
|
96
|
+
const setupMode = Boolean(setupActivateHook && setupHttpServer);
|
|
97
|
+
if (setupMode) {
|
|
98
|
+
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
99
|
+
}
|
|
100
|
+
const running = isServiceRunning();
|
|
101
|
+
if (!running) {
|
|
102
|
+
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
103
|
+
}
|
|
104
|
+
const restartReasons = getRestartRequiredReasons(before, after);
|
|
105
|
+
if (restartReasons.length > 0) {
|
|
106
|
+
return { mode: "restart-required", restartRequired: true, restartReasons };
|
|
107
|
+
}
|
|
108
|
+
if (!reloadConfigHook) {
|
|
109
|
+
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
110
|
+
}
|
|
111
|
+
await reloadConfigHook();
|
|
112
|
+
return { mode: "reload", restartRequired: false, restartReasons: [] };
|
|
113
|
+
}
|
|
114
|
+
function maskSecret(value) {
|
|
115
|
+
if (!value)
|
|
116
|
+
return "(未设置)";
|
|
117
|
+
if (value.length <= 8)
|
|
118
|
+
return "***";
|
|
119
|
+
return value.slice(0, 4) + "***" + value.slice(-4);
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Helpers — process management
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
function isServiceRunning() {
|
|
125
|
+
if (!existsSync(PID_FILE))
|
|
126
|
+
return false;
|
|
127
|
+
try {
|
|
128
|
+
const raw = readFileSync(PID_FILE, "utf8").trim();
|
|
129
|
+
const pid = parseInt(raw, 10);
|
|
130
|
+
if (isNaN(pid))
|
|
131
|
+
return false;
|
|
132
|
+
process.kill(pid, 0);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function getServicePid() {
|
|
140
|
+
if (!existsSync(PID_FILE))
|
|
141
|
+
return null;
|
|
142
|
+
try {
|
|
143
|
+
const pid = parseInt(readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
144
|
+
return isNaN(pid) ? null : pid;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function getServiceUptime() {
|
|
151
|
+
try {
|
|
152
|
+
const s = statSync(PID_FILE);
|
|
153
|
+
return Math.floor((Date.now() - s.mtimeMs) / 1000);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
import { statSync } from "node:fs";
|
|
160
|
+
function spawnService() {
|
|
161
|
+
const spec = resolveChatCccRuntimeSpawnSpec(PROJECT_ROOT);
|
|
162
|
+
if (!existsSync(spec.args[0]))
|
|
163
|
+
return { ok: false, error: `Entry not found: ${spec.args[0]}` };
|
|
164
|
+
try {
|
|
165
|
+
const child = spawn(spec.command, spec.args, {
|
|
166
|
+
cwd: PROJECT_ROOT,
|
|
167
|
+
detached: true,
|
|
168
|
+
stdio: "ignore",
|
|
169
|
+
shell: false,
|
|
170
|
+
env: createInternalRestartEnv(),
|
|
171
|
+
});
|
|
172
|
+
child.unref();
|
|
173
|
+
return { ok: true, pid: child.pid ?? undefined };
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
return { ok: false, error: err.message };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 安排一次真正的进程重启:先回复 HTTP 请求,再 spawn 一个延迟启动的子进程,
|
|
181
|
+
* 然后退出当前进程。延迟是为了让当前进程完全退出并释放端口。
|
|
182
|
+
*/
|
|
183
|
+
function scheduleRestart() {
|
|
184
|
+
const spec = resolveChatCccRuntimeSpawnSpec(PROJECT_ROOT);
|
|
185
|
+
if (!existsSync(spec.args[0]))
|
|
186
|
+
return;
|
|
187
|
+
const delayedLauncher = [
|
|
188
|
+
"const { spawn } = require('node:child_process');",
|
|
189
|
+
`const command = ${JSON.stringify(spec.command)};`,
|
|
190
|
+
`const args = ${JSON.stringify(spec.args)};`,
|
|
191
|
+
`const cwd = ${JSON.stringify(PROJECT_ROOT)};`,
|
|
192
|
+
"setTimeout(() => {",
|
|
193
|
+
" const child = spawn(command, args, { cwd, detached: true, stdio: 'ignore', shell: false, env: process.env });",
|
|
194
|
+
" child.unref();",
|
|
195
|
+
"}, 2000);",
|
|
196
|
+
].join("\n");
|
|
197
|
+
if (process.platform === "win32") {
|
|
198
|
+
// Windows keeps the helper window hidden; the helper itself provides the delay.
|
|
199
|
+
spawn(process.execPath, ["-e", delayedLauncher], {
|
|
200
|
+
cwd: PROJECT_ROOT,
|
|
201
|
+
detached: true,
|
|
202
|
+
stdio: "ignore",
|
|
203
|
+
windowsHide: true,
|
|
204
|
+
env: createInternalRestartEnv(),
|
|
205
|
+
}).unref();
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
spawn(process.execPath, ["-e", delayedLauncher], {
|
|
209
|
+
cwd: PROJECT_ROOT,
|
|
210
|
+
detached: true,
|
|
211
|
+
stdio: "ignore",
|
|
212
|
+
env: createInternalRestartEnv(),
|
|
213
|
+
}).unref();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function stopService() {
|
|
217
|
+
const pid = getServicePid();
|
|
218
|
+
if (!pid)
|
|
219
|
+
return { ok: false, error: "No PID file found" };
|
|
220
|
+
try {
|
|
221
|
+
if (process.platform === "win32") {
|
|
222
|
+
execSync(`taskkill /PID ${pid} /F /T`, { stdio: "pipe", windowsHide: true });
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
process.kill(pid, "SIGTERM");
|
|
226
|
+
}
|
|
227
|
+
return { ok: true };
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
return { ok: false, error: err.message };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function validateCli(tool) {
|
|
234
|
+
const cfg = loadConfig();
|
|
235
|
+
let cmd;
|
|
236
|
+
if (tool === "cursor") {
|
|
237
|
+
cmd = readToolPath(cfg.cursor) || detectCursorAgentPath();
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
cmd = readToolPath(cfg.codex) || "codex";
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const out = execSync(`"${cmd}" --version`, { encoding: "utf8", timeout: 10000, windowsHide: true }).trim();
|
|
244
|
+
return { ok: true, path: cmd, error: out.slice(0, 200) };
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
return { ok: false, path: cmd, error: err.message };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function detectCursorAgentPath() {
|
|
251
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
252
|
+
if (localAppData) {
|
|
253
|
+
const defaultPath = join(localAppData, "cursor-agent", "agent.cmd");
|
|
254
|
+
if (existsSync(defaultPath))
|
|
255
|
+
return defaultPath;
|
|
256
|
+
}
|
|
257
|
+
return "agent";
|
|
258
|
+
}
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Helpers — HTTP
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
function jsonReply(res, code, data) {
|
|
263
|
+
res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" });
|
|
264
|
+
res.end(JSON.stringify(data));
|
|
265
|
+
}
|
|
266
|
+
function readRequestBody(req) {
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
let body = "";
|
|
269
|
+
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
270
|
+
req.on("end", () => resolve(body));
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// API route handlers
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
async function handleApiCheck(_req, res) {
|
|
277
|
+
const hasConfig = existsSync(CONFIG_FILE);
|
|
278
|
+
let hasCreds = false;
|
|
279
|
+
if (hasConfig) {
|
|
280
|
+
const c = loadConfig();
|
|
281
|
+
const feishuEnabled = c.platforms?.feishu?.enabled !== false; // 默认 true(向后兼容)
|
|
282
|
+
const feishuOk = feishuEnabled && Boolean(c.feishu?.appId?.trim() && c.feishu?.appSecret?.trim());
|
|
283
|
+
hasCreds = feishuOk || !feishuEnabled;
|
|
284
|
+
}
|
|
285
|
+
jsonReply(res, 200, { hasConfig, hasCreds, configPath: CONFIG_FILE });
|
|
286
|
+
}
|
|
287
|
+
async function handleGetConfig(_req, res) {
|
|
288
|
+
const running = isServiceRunning();
|
|
289
|
+
const pid = getServicePid();
|
|
290
|
+
const vars = loadConfig();
|
|
291
|
+
let ilinkAuthExists = false;
|
|
292
|
+
if (existsSync(ILINK_AUTH_PATH)) {
|
|
293
|
+
try {
|
|
294
|
+
const auth = JSON.parse(readFileSync(ILINK_AUTH_PATH, "utf8"));
|
|
295
|
+
ilinkAuthExists = Boolean(auth.token);
|
|
296
|
+
}
|
|
297
|
+
catch { /* ignore parse errors */ }
|
|
298
|
+
}
|
|
299
|
+
jsonReply(res, 200, { vars, running, pid, ilinkAuthExists });
|
|
300
|
+
}
|
|
301
|
+
async function handlePostConfig(req, res) {
|
|
302
|
+
const body = await readRequestBody(req);
|
|
303
|
+
let updates;
|
|
304
|
+
try {
|
|
305
|
+
const parsed = JSON.parse(body);
|
|
306
|
+
updates = parsed.vars ?? {};
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
jsonReply(res, 400, { ok: false, error: "Invalid JSON" });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const existing = loadConfig();
|
|
313
|
+
// AppConfig 是闭合接口(无 index signature),但运行时本质就是 JSON 对象,
|
|
314
|
+
// 这里通过 unknown 桥接到 Record 让 deepMerge 能复用;写回前断言回 AppConfig。
|
|
315
|
+
const merged = deepMerge(existing, unflattenConfig(updates));
|
|
316
|
+
try {
|
|
317
|
+
saveConfig(merged);
|
|
318
|
+
const applyResult = await applySavedConfigIfPossible(existing, merged);
|
|
319
|
+
jsonReply(res, 200, { ok: true, ...applyResult });
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
jsonReply(res, 500, { ok: false, saved: true, error: err.message });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function deepMerge(target, source) {
|
|
326
|
+
const result = { ...target };
|
|
327
|
+
for (const [key, val] of Object.entries(source)) {
|
|
328
|
+
if (val !== null && typeof val === "object" && !Array.isArray(val) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
|
|
329
|
+
result[key] = deepMerge(result[key], val);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
result[key] = val;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
// Convert flat key-value pairs to nested config structure
|
|
338
|
+
export function unflattenConfig(flat) {
|
|
339
|
+
const result = {};
|
|
340
|
+
for (const [key, val] of Object.entries(flat)) {
|
|
341
|
+
if (key === "CHATCCC_APP_ID") {
|
|
342
|
+
result.feishu = result.feishu || {};
|
|
343
|
+
result.feishu.appId = val;
|
|
344
|
+
}
|
|
345
|
+
else if (key === "CHATCCC_APP_SECRET") {
|
|
346
|
+
result.feishu = result.feishu || {};
|
|
347
|
+
result.feishu.appSecret = val;
|
|
348
|
+
}
|
|
349
|
+
else if (key === "CHATCCC_FEISHU_ENABLED") {
|
|
350
|
+
result.platforms = result.platforms || {};
|
|
351
|
+
result.platforms.feishu = result.platforms.feishu || {};
|
|
352
|
+
result.platforms.feishu.enabled = val === true || val === "true";
|
|
353
|
+
}
|
|
354
|
+
else if (key === "CHATCCC_FEISHU_PLATFORM_TYPE") {
|
|
355
|
+
result.platforms = result.platforms || {};
|
|
356
|
+
result.platforms.feishu = result.platforms.feishu || {};
|
|
357
|
+
result.platforms.feishu.platformType = val;
|
|
358
|
+
}
|
|
359
|
+
else if (key === "CHATCCC_ILINK_ENABLED") {
|
|
360
|
+
result.platforms = result.platforms || {};
|
|
361
|
+
result.platforms.ilink = result.platforms.ilink || {};
|
|
362
|
+
result.platforms.ilink.enabled = val === true || val === "true";
|
|
363
|
+
}
|
|
364
|
+
else if (key === "CHATCCC_WEB_UI_OPEN_ON_START") {
|
|
365
|
+
result.webUi = result.webUi || {};
|
|
366
|
+
result.webUi.openOnStart = val === true || val === "true";
|
|
367
|
+
}
|
|
368
|
+
else if (key === "CHATCCC_CHROME_DEVTOOLS_ENABLED") {
|
|
369
|
+
result.chromeDevtools = result.chromeDevtools || {};
|
|
370
|
+
result.chromeDevtools.enabled = val === true || val === "true";
|
|
371
|
+
}
|
|
372
|
+
else if (key === "CHATCCC_CHROME_DEVTOOLS_PORT") {
|
|
373
|
+
result.chromeDevtools = result.chromeDevtools || {};
|
|
374
|
+
result.chromeDevtools.port = parseInt(val, 10) || 15166;
|
|
375
|
+
}
|
|
376
|
+
else if (key === "CHATCCC_CHROME_DEVTOOLS_PATH") {
|
|
377
|
+
result.chromeDevtools = result.chromeDevtools || {};
|
|
378
|
+
result.chromeDevtools.chromePath = val;
|
|
379
|
+
}
|
|
380
|
+
else if (key === "CHATCCC_PORT") {
|
|
381
|
+
result.port = parseInt(val, 10) || 18080;
|
|
382
|
+
}
|
|
383
|
+
else if (key === "CHATCCC_GIT_TIMEOUT_SECONDS") {
|
|
384
|
+
result.gitTimeoutSeconds = parseInt(val, 10) || 180;
|
|
385
|
+
}
|
|
386
|
+
else if (key === "CHATCCC_ANTHROPIC_MODEL") {
|
|
387
|
+
result.claude = result.claude || {};
|
|
388
|
+
result.claude.model = val;
|
|
389
|
+
}
|
|
390
|
+
else if (key === "CHATCCC_ANTHROPIC_SUBAGENT_MODEL") {
|
|
391
|
+
result.claude = result.claude || {};
|
|
392
|
+
result.claude.subagentModel = val;
|
|
393
|
+
}
|
|
394
|
+
else if (key === "CHATCCC_ANTHROPIC_EFFORT") {
|
|
395
|
+
result.claude = result.claude || {};
|
|
396
|
+
result.claude.effort = val;
|
|
397
|
+
}
|
|
398
|
+
else if (key === "CHATCCC_ANTHROPIC_API_KEY") {
|
|
399
|
+
result.claude = result.claude || {};
|
|
400
|
+
result.claude.apiKey = val;
|
|
401
|
+
}
|
|
402
|
+
else if (key === "CHATCCC_ANTHROPIC_BASE_URL") {
|
|
403
|
+
result.claude = result.claude || {};
|
|
404
|
+
result.claude.baseUrl = val;
|
|
405
|
+
}
|
|
406
|
+
else if (key === "CHATCCC_ANTHROPIC_MAX_TURN") {
|
|
407
|
+
result.claude = result.claude || {};
|
|
408
|
+
result.claude.maxTurn = (function (v) { var n = parseInt(v, 10); return isNaN(n) ? 0 : n; })(val);
|
|
409
|
+
}
|
|
410
|
+
else if (key === "CHATCCC_CLAUDE_ENABLED") {
|
|
411
|
+
result.claude = result.claude || {};
|
|
412
|
+
result.claude.enabled = val === true || val === "true";
|
|
413
|
+
}
|
|
414
|
+
else if (key === "CHATCCC_CLAUDE_DEFAULT_AGENT") {
|
|
415
|
+
result.claude = result.claude || {};
|
|
416
|
+
result.claude.defaultAgent = val === true || val === "true";
|
|
417
|
+
}
|
|
418
|
+
else if (key === "CHATCCC_CURSOR_PATH") {
|
|
419
|
+
result.cursor = result.cursor || {};
|
|
420
|
+
result.cursor.path = val;
|
|
421
|
+
}
|
|
422
|
+
else if (key === "CHATCCC_CURSOR_MODEL") {
|
|
423
|
+
result.cursor = result.cursor || {};
|
|
424
|
+
result.cursor.model = val;
|
|
425
|
+
}
|
|
426
|
+
else if (key === "CHATCCC_CURSOR_ALTERNATIVE_MODEL") {
|
|
427
|
+
result.cursor = result.cursor || {};
|
|
428
|
+
result.cursor.alternativeModel = val;
|
|
429
|
+
}
|
|
430
|
+
else if (key === "CHATCCC_CURSOR_AVATAR_BATTERY_MODE") {
|
|
431
|
+
result.cursor = result.cursor || {};
|
|
432
|
+
result.cursor.avatarBatteryMode = val;
|
|
433
|
+
}
|
|
434
|
+
else if (key === "CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET") {
|
|
435
|
+
result.cursor = result.cursor || {};
|
|
436
|
+
result.cursor.onDemandMonthlyBudget = Number(val);
|
|
437
|
+
}
|
|
438
|
+
else if (key === "CHATCCC_CURSOR_ENABLED") {
|
|
439
|
+
result.cursor = result.cursor || {};
|
|
440
|
+
result.cursor.enabled = val === true || val === "true";
|
|
441
|
+
}
|
|
442
|
+
else if (key === "CHATCCC_CURSOR_DEFAULT_AGENT") {
|
|
443
|
+
result.cursor = result.cursor || {};
|
|
444
|
+
result.cursor.defaultAgent = val === true || val === "true";
|
|
445
|
+
}
|
|
446
|
+
else if (key === "CHATCCC_CODEX_PATH") {
|
|
447
|
+
result.codex = result.codex || {};
|
|
448
|
+
result.codex.path = val;
|
|
449
|
+
}
|
|
450
|
+
else if (key === "CHATCCC_CODEX_MODEL") {
|
|
451
|
+
result.codex = result.codex || {};
|
|
452
|
+
result.codex.model = val;
|
|
453
|
+
}
|
|
454
|
+
else if (key === "CHATCCC_CODEX_ALTERNATIVE_MODEL") {
|
|
455
|
+
result.codex = result.codex || {};
|
|
456
|
+
result.codex.alternativeModel = val;
|
|
457
|
+
}
|
|
458
|
+
else if (key === "CHATCCC_CODEX_EFFORT") {
|
|
459
|
+
result.codex = result.codex || {};
|
|
460
|
+
result.codex.effort = val;
|
|
461
|
+
}
|
|
462
|
+
else if (key === "CHATCCC_CODEX_FAST_MODE") {
|
|
463
|
+
result.codex = result.codex || {};
|
|
464
|
+
result.codex.fastMode = val === true || val === "true";
|
|
465
|
+
}
|
|
466
|
+
else if (key === "CHATCCC_CODEX_ENABLED") {
|
|
467
|
+
result.codex = result.codex || {};
|
|
468
|
+
result.codex.enabled = val === true || val === "true";
|
|
469
|
+
}
|
|
470
|
+
else if (key === "CHATCCC_CODEX_DEFAULT_AGENT") {
|
|
471
|
+
result.codex = result.codex || {};
|
|
472
|
+
result.codex.defaultAgent = val === true || val === "true";
|
|
473
|
+
}
|
|
474
|
+
else if (key === "CHATCCC_CCC_API_KEY") {
|
|
475
|
+
result.ccc = result.ccc || {};
|
|
476
|
+
result.ccc.DEEPSEEK_API_KEY = val;
|
|
477
|
+
}
|
|
478
|
+
else if (key === "CHATCCC_CCC_BASE_URL") {
|
|
479
|
+
result.ccc = result.ccc || {};
|
|
480
|
+
result.ccc.DEEPSEEK_BASE_URL = val;
|
|
481
|
+
}
|
|
482
|
+
else if (key === "CHATCCC_CCC_MODEL") {
|
|
483
|
+
result.ccc = result.ccc || {};
|
|
484
|
+
result.ccc.model = val;
|
|
485
|
+
}
|
|
486
|
+
else if (key === "CHATCCC_CCC_ALTERNATIVE_MODEL") {
|
|
487
|
+
result.ccc = result.ccc || {};
|
|
488
|
+
result.ccc.alternativeModel = val;
|
|
489
|
+
}
|
|
490
|
+
else if (key === "CHATCCC_CCC_EFFORT") {
|
|
491
|
+
result.ccc = result.ccc || {};
|
|
492
|
+
result.ccc.effort = val;
|
|
493
|
+
}
|
|
494
|
+
else if (key === "CHATCCC_CCC_PROVIDER") {
|
|
495
|
+
result.ccc = result.ccc || {};
|
|
496
|
+
result.ccc.provider = val;
|
|
497
|
+
}
|
|
498
|
+
else if (key === "CHATCCC_CCC_ENABLED") {
|
|
499
|
+
result.ccc = result.ccc || {};
|
|
500
|
+
result.ccc.enabled = val === true || val === "true";
|
|
501
|
+
}
|
|
502
|
+
else if (key === "CHATCCC_CCC_DEFAULT_AGENT") {
|
|
503
|
+
result.ccc = result.ccc || {};
|
|
504
|
+
result.ccc.defaultAgent = val === true || val === "true";
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
async function handleGetStatus(_req, res) {
|
|
510
|
+
const running = isServiceRunning();
|
|
511
|
+
const pid = getServicePid();
|
|
512
|
+
const uptime = running ? getServiceUptime() : null;
|
|
513
|
+
jsonReply(res, 200, { running, pid, uptime });
|
|
514
|
+
}
|
|
515
|
+
async function handleStartService(_req, res) {
|
|
516
|
+
const path = chooseStartPath({
|
|
517
|
+
hasInplaceActivateHook: Boolean(setupActivateHook && setupHttpServer),
|
|
518
|
+
isServiceRunning: isServiceRunning(),
|
|
519
|
+
});
|
|
520
|
+
if (path === "inplace") {
|
|
521
|
+
// setup 模式:原地启动飞书 service,复用 setup HTTP server,不动 PID 文件。
|
|
522
|
+
// 注意 hook 和 setupHttpServer 在 chooseStartPath() 之后**绝不会被并发清空**
|
|
523
|
+
// —— init 时一次性赋值,且当前函数是 onActivate 唯一调用点;非 null 断言安全。
|
|
524
|
+
const hook = setupActivateHook;
|
|
525
|
+
const server = setupHttpServer;
|
|
526
|
+
const result = await hook(server);
|
|
527
|
+
if (result.ok) {
|
|
528
|
+
// 切换成功:清掉 hook 防止再次走 inplace 路径(service 已经在跑了)。
|
|
529
|
+
// setupHttpServer 不清 —— 它已经接管为 service server,dashboard 仍要用。
|
|
530
|
+
setupActivateHook = null;
|
|
531
|
+
jsonReply(res, 200, { ok: true, pid: process.pid, mode: "inplace" });
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
jsonReply(res, 500, { ok: false, error: result.error });
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (path === "reload") {
|
|
539
|
+
// service 已经在跑:只刷新进程内配置,让后续消息/新会话使用新值。
|
|
540
|
+
if (!reloadConfigHook) {
|
|
541
|
+
jsonReply(res, 200, { ok: true, pid: process.pid, mode: "saved" });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
await reloadConfigHook();
|
|
546
|
+
jsonReply(res, 200, { ok: true, pid: process.pid, mode: "reload" });
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
jsonReply(res, 500, { ok: false, error: err.message });
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const result = spawnService();
|
|
554
|
+
if (result.ok) {
|
|
555
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
556
|
+
jsonReply(res, 200, { ok: true, pid: result.pid, mode: "spawn" });
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
jsonReply(res, 500, result);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function handleStopService(req, res) {
|
|
563
|
+
const running = isServiceRunning();
|
|
564
|
+
if (!running) {
|
|
565
|
+
jsonReply(res, 200, { ok: false, error: "Service is not running" });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
jsonReply(res, 200, { ok: true });
|
|
569
|
+
setImmediate(() => {
|
|
570
|
+
stopService();
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
async function handleRestartService(_req, res) {
|
|
574
|
+
jsonReply(res, 200, { ok: true, message: "Restarting..." });
|
|
575
|
+
scheduleRestart();
|
|
576
|
+
process.exit(0);
|
|
577
|
+
}
|
|
578
|
+
async function handleValidate(req, res) {
|
|
579
|
+
const body = await readRequestBody(req);
|
|
580
|
+
let tool;
|
|
581
|
+
try {
|
|
582
|
+
tool = JSON.parse(body).tool;
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
jsonReply(res, 400, { ok: false, error: "Missing tool" });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const result = validateCli(tool);
|
|
589
|
+
jsonReply(res, 200, result);
|
|
590
|
+
}
|
|
591
|
+
async function handleForgetIlink(_req, res) {
|
|
592
|
+
try {
|
|
593
|
+
if (existsSync(ILINK_AUTH_PATH)) {
|
|
594
|
+
unlinkSync(ILINK_AUTH_PATH);
|
|
595
|
+
}
|
|
596
|
+
jsonReply(res, 200, { ok: true });
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
jsonReply(res, 500, { ok: false, error: err.message });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
async function handleClaudeSdkStatus(_req, res) {
|
|
603
|
+
const progress = getLastInstallProgress();
|
|
604
|
+
jsonReply(res, 200, {
|
|
605
|
+
installed: isClaudeSdkInstalled(),
|
|
606
|
+
version: getClaudeSdkInstalledVersion(),
|
|
607
|
+
running: isInstallRunning(),
|
|
608
|
+
// 展开到顶层:前端轮询 JS 读顶层 phase/message/percent/error(历史契约),
|
|
609
|
+
// 若只放在 progress 嵌套里,前端 s.phase 恒为 undefined → 进度条永不渲染。
|
|
610
|
+
...progress,
|
|
611
|
+
// 保留嵌套结构,兼容其它调用方。
|
|
612
|
+
progress,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
async function handleClaudeSdkInstall(_req, res) {
|
|
616
|
+
if (isInstallRunning()) {
|
|
617
|
+
jsonReply(res, 200, { ok: true, alreadyRunning: true });
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
// 后台执行,不阻塞响应;前端通过 /api/claude-sdk/status 轮询进度
|
|
622
|
+
installClaudeSdk().catch((err) => {
|
|
623
|
+
console.error(`[WEB-UI] Claude SDK 安装失败: ${err.message}`);
|
|
624
|
+
});
|
|
625
|
+
jsonReply(res, 200, { ok: true });
|
|
626
|
+
}
|
|
627
|
+
catch (err) {
|
|
628
|
+
jsonReply(res, 500, { ok: false, error: err.message });
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// ---------------------------------------------------------------------------
|
|
632
|
+
// HTML page (embedded template)
|
|
633
|
+
// ---------------------------------------------------------------------------
|
|
634
|
+
export const PAGE_HTML = `<!DOCTYPE html>
|
|
698
635
|
<html lang="zh-CN">
|
|
699
636
|
<head>
|
|
700
637
|
<meta charset="UTF-8">
|
|
@@ -2386,145 +2323,122 @@ init();
|
|
|
2386
2323
|
setTimeout(claudeEngineRefreshStatus, 300);
|
|
2387
2324
|
</script>
|
|
2388
2325
|
</body>
|
|
2389
|
-
</html>`;
|
|
2390
|
-
|
|
2391
|
-
//
|
|
2392
|
-
//
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
export
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
server.listen(port, "127.0.0.1", () => {
|
|
2510
|
-
const url = buildWebUiUrl(port);
|
|
2511
|
-
console.log("");
|
|
2512
|
-
console.log("=".repeat(60));
|
|
2513
|
-
console.log(" ChatCCC — 首次配置向导");
|
|
2514
|
-
console.log("=".repeat(60));
|
|
2515
|
-
console.log(" 未检测到已配置的飞书凭证,已启动配置界面。");
|
|
2516
|
-
if (options.openBrowser !== false) {
|
|
2517
|
-
console.log(` 已启用启动时自动打开浏览器: ${url}`);
|
|
2518
|
-
} else {
|
|
2519
|
-
console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
|
|
2520
|
-
}
|
|
2521
|
-
console.log(" 若浏览器未自动弹出,请手动访问上面的地址。");
|
|
2522
|
-
console.log("");
|
|
2523
|
-
console.log(" 在向导里填好 App ID / App Secret 后点「保存并启动」,");
|
|
2524
|
-
console.log(" 服务会在当前进程内直接激活,不需要重新运行 chatccc。");
|
|
2525
|
-
console.log("=".repeat(60));
|
|
2526
|
-
console.log("");
|
|
2527
|
-
if (options.openBrowser !== false) openWebUiInDefaultBrowser(port);
|
|
2528
|
-
});
|
|
2529
|
-
return server;
|
|
2530
|
-
}
|
|
2326
|
+
</html>`;
|
|
2327
|
+
// ---------------------------------------------------------------------------
|
|
2328
|
+
// Router
|
|
2329
|
+
// ---------------------------------------------------------------------------
|
|
2330
|
+
async function handleRequest(req, res) {
|
|
2331
|
+
const url = req.url ?? "/";
|
|
2332
|
+
const method = req.method ?? "GET";
|
|
2333
|
+
const pathname = url.split("?", 1)[0];
|
|
2334
|
+
if (extraApiHandler && await extraApiHandler(req, res))
|
|
2335
|
+
return;
|
|
2336
|
+
if (await handleAgentTeamRequest(req, res))
|
|
2337
|
+
return;
|
|
2338
|
+
// API routes
|
|
2339
|
+
if (url === "/api/check" && method === "GET")
|
|
2340
|
+
return handleApiCheck(req, res);
|
|
2341
|
+
if (url === "/api/config" && method === "GET")
|
|
2342
|
+
return handleGetConfig(req, res);
|
|
2343
|
+
if (url === "/api/config" && method === "POST")
|
|
2344
|
+
return handlePostConfig(req, res);
|
|
2345
|
+
if (url === "/api/status" && method === "GET")
|
|
2346
|
+
return handleGetStatus(req, res);
|
|
2347
|
+
if (url === "/api/start" && method === "POST")
|
|
2348
|
+
return handleStartService(req, res);
|
|
2349
|
+
if (url === "/api/stop" && method === "POST")
|
|
2350
|
+
return handleStopService(req, res);
|
|
2351
|
+
if (url === "/api/restart" && method === "POST")
|
|
2352
|
+
return handleRestartService(req, res);
|
|
2353
|
+
if (url === "/api/validate" && method === "POST")
|
|
2354
|
+
return handleValidate(req, res);
|
|
2355
|
+
if (url === "/api/ilink/forget" && method === "POST")
|
|
2356
|
+
return handleForgetIlink(req, res);
|
|
2357
|
+
if (url === "/api/claude-sdk/status" && method === "GET")
|
|
2358
|
+
return handleClaudeSdkStatus(req, res);
|
|
2359
|
+
if (url === "/api/claude-sdk/install" && method === "POST")
|
|
2360
|
+
return handleClaudeSdkInstall(req, res);
|
|
2361
|
+
if (method === "GET" && (pathname === "/agent-team" || pathname === "/agent-team/")) {
|
|
2362
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2363
|
+
res.end(AGENT_TEAM_PAGE_HTML);
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
// Serve HTML page for all other GET requests
|
|
2367
|
+
if (method === "GET") {
|
|
2368
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2369
|
+
res.end(PAGE_HTML);
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
jsonReply(res, 404, { error: "Not found" });
|
|
2373
|
+
}
|
|
2374
|
+
export function createUiRouter() {
|
|
2375
|
+
return (req, res) => {
|
|
2376
|
+
handleRequest(req, res).catch((err) => {
|
|
2377
|
+
console.error(`[WEB-UI] Unhandled error: ${err.message}`);
|
|
2378
|
+
if (!res.headersSent)
|
|
2379
|
+
jsonReply(res, 500, { error: "Internal error" });
|
|
2380
|
+
});
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
// setup HTTP server + onActivate 回调通过模块级变量暴露给 handleStartService。
|
|
2384
|
+
// 一旦 onActivate 成功执行,setupActivateHook 会被清空——避免 dashboard 模式下
|
|
2385
|
+
// 用户再点"启动"时还走 inplace 路径(service 已经在跑了)。
|
|
2386
|
+
let setupHttpServer = null;
|
|
2387
|
+
let setupActivateHook = null;
|
|
2388
|
+
// reload-config 回调:dashboard 模式下用户点"保存并启动"时,service 已经在跑,
|
|
2389
|
+
// 仅需要把磁盘上刚保存的 config.json 刷进进程内的 export let 常量(live binding)。
|
|
2390
|
+
// 由 index.ts 注入,因为 web-ui.ts 自身**不应**直接 import config.ts——后者顶层
|
|
2391
|
+
// 有 loadConfig 副作用,被 web-ui.ts 间接 import 会污染所有依赖 web-ui.ts 的单测。
|
|
2392
|
+
let reloadConfigHook = null;
|
|
2393
|
+
let extraApiHandler = null;
|
|
2394
|
+
/**
|
|
2395
|
+
* 注册"reload config"回调。约定:
|
|
2396
|
+
* - 由 index.ts 在 main() 中一次性调用,传入 () => reloadConfigFromDisk()。
|
|
2397
|
+
* - handleStartService 在 path="reload" 分支会 await 调用一次;hook 抛错会被
|
|
2398
|
+
* 捕获并以 500 回前端,避免 service 死锁。
|
|
2399
|
+
*/
|
|
2400
|
+
export function setReloadConfigHook(hook) {
|
|
2401
|
+
reloadConfigHook = hook;
|
|
2402
|
+
}
|
|
2403
|
+
export function setExtraApiHandler(handler) {
|
|
2404
|
+
extraApiHandler = handler;
|
|
2405
|
+
}
|
|
2406
|
+
export function startSetupMode(port, options = {}) {
|
|
2407
|
+
const router = createUiRouter();
|
|
2408
|
+
const server = createServer(router);
|
|
2409
|
+
setupHttpServer = server;
|
|
2410
|
+
setupActivateHook = options.onActivate ?? null;
|
|
2411
|
+
server.on("error", (err) => {
|
|
2412
|
+
if (err.code === "EADDRINUSE") {
|
|
2413
|
+
console.error(`\n[WEB-UI] 端口 ${port} 已被占用。请检查是否有其他 ChatCCC 实例在运行。`);
|
|
2414
|
+
console.error(" 可以先停止旧进程,或修改 config.json 中的 port 为其他端口。");
|
|
2415
|
+
}
|
|
2416
|
+
else {
|
|
2417
|
+
console.error(`\n[WEB-UI] HTTP 服务器错误: ${err.message}`);
|
|
2418
|
+
}
|
|
2419
|
+
process.exit(1);
|
|
2420
|
+
});
|
|
2421
|
+
server.listen(port, "127.0.0.1", () => {
|
|
2422
|
+
const url = buildWebUiUrl(port);
|
|
2423
|
+
console.log("");
|
|
2424
|
+
console.log("=".repeat(60));
|
|
2425
|
+
console.log(" ChatCCC — 首次配置向导");
|
|
2426
|
+
console.log("=".repeat(60));
|
|
2427
|
+
console.log(" 未检测到已配置的飞书凭证,已启动配置界面。");
|
|
2428
|
+
if (options.openBrowser !== false) {
|
|
2429
|
+
console.log(` 已启用启动时自动打开浏览器: ${url}`);
|
|
2430
|
+
}
|
|
2431
|
+
else {
|
|
2432
|
+
console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
|
|
2433
|
+
}
|
|
2434
|
+
console.log(" 若浏览器未自动弹出,请手动访问上面的地址。");
|
|
2435
|
+
console.log("");
|
|
2436
|
+
console.log(" 在向导里填好 App ID / App Secret 后点「保存并启动」,");
|
|
2437
|
+
console.log(" 服务会在当前进程内直接激活,不需要重新运行 chatccc。");
|
|
2438
|
+
console.log("=".repeat(60));
|
|
2439
|
+
console.log("");
|
|
2440
|
+
if (options.openBrowser !== false)
|
|
2441
|
+
openWebUiInDefaultBrowser(port);
|
|
2442
|
+
});
|
|
2443
|
+
return server;
|
|
2444
|
+
}
|