chatccc 0.2.223 → 0.2.225

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/src/web-ui.ts CHANGED
@@ -12,12 +12,12 @@ import { readFile, writeFile, stat } from "node:fs/promises";
12
12
  import { homedir } from "node:os";
13
13
  import { join, dirname } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
- import { spawn, execSync } from "node:child_process";
16
- import {
17
- buildWebUiUrl,
18
- createInternalRestartEnv,
19
- openWebUiInDefaultBrowser,
20
- } from "./startup-lifecycle.ts";
15
+ import { spawn, execSync } from "node:child_process";
16
+ import {
17
+ buildWebUiUrl,
18
+ createInternalRestartEnv,
19
+ openWebUiInDefaultBrowser,
20
+ } from "./startup-lifecycle.ts";
21
21
 
22
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
23
23
  const PROJECT_ROOT = join(__dirname, "..");
@@ -31,16 +31,16 @@ const ILINK_AUTH_PATH = join(USER_DATA_DIR, "state", "ilink-auth.json");
31
31
  // Helpers — config.json parsing & generation
32
32
  // ---------------------------------------------------------------------------
33
33
 
34
- interface AppConfig {
35
- feishu?: { appId?: string; appSecret?: string };
36
- platforms?: {
37
- feishu?: { enabled?: boolean; platformType?: string };
38
- ilink?: { enabled?: boolean; reuseTokenOnStart?: boolean };
39
- };
40
- webUi?: { openOnStart?: boolean };
41
- chromeDevtools?: { enabled?: boolean; port?: number; chromePath?: string };
42
- port?: number;
43
- gitTimeoutSeconds?: number;
34
+ interface AppConfig {
35
+ feishu?: { appId?: string; appSecret?: string };
36
+ platforms?: {
37
+ feishu?: { enabled?: boolean; platformType?: string };
38
+ ilink?: { enabled?: boolean; reuseTokenOnStart?: boolean };
39
+ };
40
+ webUi?: { openOnStart?: boolean };
41
+ chromeDevtools?: { enabled?: boolean; port?: number; chromePath?: string };
42
+ port?: number;
43
+ gitTimeoutSeconds?: number;
44
44
  claude?: { enabled?: boolean; defaultAgent?: boolean; model?: string; subagentModel?: string; effort?: string; apiKey?: string; baseUrl?: string; maxTurn?: number };
45
45
  // `command` 是已废弃的旧字段名,保留只读以兼容升级前的 config.json
46
46
  cursor?: {
@@ -52,17 +52,17 @@ interface AppConfig {
52
52
  alternativeModel?: string;
53
53
  avatarBatteryMode?: string;
54
54
  onDemandMonthlyBudget?: number;
55
- };
56
- codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string; fastMode?: boolean };
57
- ccc?: {
58
- enabled?: boolean;
59
- defaultAgent?: boolean;
60
- DEEPSEEK_API_KEY?: string;
61
- DEEPSEEK_BASE_URL?: string;
62
- model?: string;
63
- alternativeModel?: string;
64
- };
65
- }
55
+ };
56
+ codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string; fastMode?: boolean };
57
+ ccc?: {
58
+ enabled?: boolean;
59
+ defaultAgent?: boolean;
60
+ DEEPSEEK_API_KEY?: string;
61
+ DEEPSEEK_BASE_URL?: string;
62
+ model?: string;
63
+ alternativeModel?: string;
64
+ };
65
+ }
66
66
 
67
67
  // ---------------------------------------------------------------------------
68
68
  // /api/start 路径选择(纯函数,便于单测护栏)
@@ -118,76 +118,76 @@ function loadConfig(): AppConfig {
118
118
  }
119
119
  }
120
120
 
121
- function saveConfig(cfg: AppConfig): void {
122
- writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
123
- }
124
-
125
- type ConfigApplyMode = "saved" | "reload" | "restart-required";
126
-
127
- interface ConfigApplyResult {
128
- mode: ConfigApplyMode;
129
- restartRequired: boolean;
130
- restartReasons: string[];
131
- }
132
-
133
- function stringValue(value: unknown): string {
134
- return typeof value === "string" ? value.trim() : "";
135
- }
136
-
137
- function portValue(cfg: AppConfig): number {
138
- return Number.isInteger(cfg.port) && cfg.port! >= 1 && cfg.port! <= 65535 ? cfg.port! : 18080;
139
- }
140
-
141
- function platformEnabled(cfg: AppConfig, platform: "feishu" | "ilink"): boolean {
142
- return cfg.platforms?.[platform]?.enabled !== false;
143
- }
144
-
145
- function feishuPlatformType(cfg: AppConfig): string {
146
- return cfg.platforms?.feishu?.platformType === "lark" ? "lark" : "feishu";
147
- }
148
-
149
- function ilinkReuseTokenOnStart(cfg: AppConfig): boolean {
150
- return cfg.platforms?.ilink?.reuseTokenOnStart !== false;
151
- }
152
-
153
- export function getRestartRequiredReasons(before: AppConfig, after: AppConfig): string[] {
154
- const reasons: string[] = [];
155
-
156
- if (portValue(before) !== portValue(after)) reasons.push("port");
157
- if (stringValue(before.feishu?.appId) !== stringValue(after.feishu?.appId)) reasons.push("feishu.appId");
158
- if (stringValue(before.feishu?.appSecret) !== stringValue(after.feishu?.appSecret)) reasons.push("feishu.appSecret");
159
- if (feishuPlatformType(before) !== feishuPlatformType(after)) reasons.push("platforms.feishu.platformType");
160
- if (platformEnabled(before, "feishu") !== platformEnabled(after, "feishu")) reasons.push("platforms.feishu.enabled");
161
- if (platformEnabled(before, "ilink") !== platformEnabled(after, "ilink")) reasons.push("platforms.ilink.enabled");
162
- if (ilinkReuseTokenOnStart(before) !== ilinkReuseTokenOnStart(after)) {
163
- reasons.push("platforms.ilink.reuseTokenOnStart");
164
- }
165
-
166
- return reasons;
167
- }
168
-
169
- async function applySavedConfigIfPossible(before: AppConfig, after: AppConfig): Promise<ConfigApplyResult> {
170
- const setupMode = Boolean(setupActivateHook && setupHttpServer);
171
- if (setupMode) {
172
- return { mode: "saved", restartRequired: false, restartReasons: [] };
173
- }
174
-
175
- const running = isServiceRunning();
176
- if (!running) {
177
- return { mode: "saved", restartRequired: false, restartReasons: [] };
178
- }
179
-
180
- const restartReasons = getRestartRequiredReasons(before, after);
181
- if (restartReasons.length > 0) {
182
- return { mode: "restart-required", restartRequired: true, restartReasons };
183
- }
184
- if (!reloadConfigHook) {
185
- return { mode: "saved", restartRequired: false, restartReasons: [] };
186
- }
187
-
188
- await reloadConfigHook();
189
- return { mode: "reload", restartRequired: false, restartReasons: [] };
190
- }
121
+ function saveConfig(cfg: AppConfig): void {
122
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
123
+ }
124
+
125
+ type ConfigApplyMode = "saved" | "reload" | "restart-required";
126
+
127
+ interface ConfigApplyResult {
128
+ mode: ConfigApplyMode;
129
+ restartRequired: boolean;
130
+ restartReasons: string[];
131
+ }
132
+
133
+ function stringValue(value: unknown): string {
134
+ return typeof value === "string" ? value.trim() : "";
135
+ }
136
+
137
+ function portValue(cfg: AppConfig): number {
138
+ return Number.isInteger(cfg.port) && cfg.port! >= 1 && cfg.port! <= 65535 ? cfg.port! : 18080;
139
+ }
140
+
141
+ function platformEnabled(cfg: AppConfig, platform: "feishu" | "ilink"): boolean {
142
+ return cfg.platforms?.[platform]?.enabled !== false;
143
+ }
144
+
145
+ function feishuPlatformType(cfg: AppConfig): string {
146
+ return cfg.platforms?.feishu?.platformType === "lark" ? "lark" : "feishu";
147
+ }
148
+
149
+ function ilinkReuseTokenOnStart(cfg: AppConfig): boolean {
150
+ return cfg.platforms?.ilink?.reuseTokenOnStart !== false;
151
+ }
152
+
153
+ export function getRestartRequiredReasons(before: AppConfig, after: AppConfig): string[] {
154
+ const reasons: string[] = [];
155
+
156
+ if (portValue(before) !== portValue(after)) reasons.push("port");
157
+ if (stringValue(before.feishu?.appId) !== stringValue(after.feishu?.appId)) reasons.push("feishu.appId");
158
+ if (stringValue(before.feishu?.appSecret) !== stringValue(after.feishu?.appSecret)) reasons.push("feishu.appSecret");
159
+ if (feishuPlatformType(before) !== feishuPlatformType(after)) reasons.push("platforms.feishu.platformType");
160
+ if (platformEnabled(before, "feishu") !== platformEnabled(after, "feishu")) reasons.push("platforms.feishu.enabled");
161
+ if (platformEnabled(before, "ilink") !== platformEnabled(after, "ilink")) reasons.push("platforms.ilink.enabled");
162
+ if (ilinkReuseTokenOnStart(before) !== ilinkReuseTokenOnStart(after)) {
163
+ reasons.push("platforms.ilink.reuseTokenOnStart");
164
+ }
165
+
166
+ return reasons;
167
+ }
168
+
169
+ async function applySavedConfigIfPossible(before: AppConfig, after: AppConfig): Promise<ConfigApplyResult> {
170
+ const setupMode = Boolean(setupActivateHook && setupHttpServer);
171
+ if (setupMode) {
172
+ return { mode: "saved", restartRequired: false, restartReasons: [] };
173
+ }
174
+
175
+ const running = isServiceRunning();
176
+ if (!running) {
177
+ return { mode: "saved", restartRequired: false, restartReasons: [] };
178
+ }
179
+
180
+ const restartReasons = getRestartRequiredReasons(before, after);
181
+ if (restartReasons.length > 0) {
182
+ return { mode: "restart-required", restartRequired: true, restartReasons };
183
+ }
184
+ if (!reloadConfigHook) {
185
+ return { mode: "saved", restartRequired: false, restartReasons: [] };
186
+ }
187
+
188
+ await reloadConfigHook();
189
+ return { mode: "reload", restartRequired: false, restartReasons: [] };
190
+ }
191
191
 
192
192
  function maskSecret(value: string | undefined): string {
193
193
  if (!value) return "(未设置)";
@@ -239,12 +239,12 @@ function spawnService(): { ok: boolean; pid?: number; error?: string } {
239
239
  return { ok: false, error: `Entry not found: ${indexPath}` };
240
240
  }
241
241
  try {
242
- const child = spawn("npx", ["tsx", "src/index.ts"], {
243
- cwd: PROJECT_ROOT,
244
- detached: true,
245
- stdio: "ignore",
246
- shell: true,
247
- env: createInternalRestartEnv(),
242
+ const child = spawn("npx", ["tsx", "src/index.ts"], {
243
+ cwd: PROJECT_ROOT,
244
+ detached: true,
245
+ stdio: "ignore",
246
+ shell: true,
247
+ env: createInternalRestartEnv(),
248
248
  });
249
249
  child.unref();
250
250
  return { ok: true, pid: child.pid ?? undefined };
@@ -262,19 +262,19 @@ function scheduleRestart(): void {
262
262
  if (!existsSync(indexPath)) return;
263
263
  if (process.platform === "win32") {
264
264
  // Windows: ping 作为 sleep(ping 自己 3 次 ≈ 2 秒延迟)
265
- spawn("cmd.exe", ["/c", "ping -n 3 127.0.0.1 > nul && npx tsx src/index.ts"], {
266
- cwd: PROJECT_ROOT,
267
- detached: true,
268
- stdio: "ignore",
269
- windowsHide: true,
270
- env: createInternalRestartEnv(),
265
+ spawn("cmd.exe", ["/c", "ping -n 3 127.0.0.1 > nul && npx tsx src/index.ts"], {
266
+ cwd: PROJECT_ROOT,
267
+ detached: true,
268
+ stdio: "ignore",
269
+ windowsHide: true,
270
+ env: createInternalRestartEnv(),
271
271
  }).unref();
272
272
  } else {
273
- spawn("bash", ["-c", "sleep 2 && npx tsx src/index.ts"], {
274
- cwd: PROJECT_ROOT,
275
- detached: true,
276
- stdio: "ignore",
277
- env: createInternalRestartEnv(),
273
+ spawn("bash", ["-c", "sleep 2 && npx tsx src/index.ts"], {
274
+ cwd: PROJECT_ROOT,
275
+ detached: true,
276
+ stdio: "ignore",
277
+ env: createInternalRestartEnv(),
278
278
  }).unref();
279
279
  }
280
280
  }
@@ -383,14 +383,14 @@ async function handlePostConfig(req: IncomingMessage, res: ServerResponse): Prom
383
383
  existing as unknown as Record<string, unknown>,
384
384
  unflattenConfig(updates),
385
385
  ) as AppConfig;
386
- try {
387
- saveConfig(merged);
388
- const applyResult = await applySavedConfigIfPossible(existing, merged);
389
- jsonReply(res, 200, { ok: true, ...applyResult });
390
- } catch (err) {
391
- jsonReply(res, 500, { ok: false, saved: true, error: (err as Error).message });
392
- }
393
- }
386
+ try {
387
+ saveConfig(merged);
388
+ const applyResult = await applySavedConfigIfPossible(existing, merged);
389
+ jsonReply(res, 200, { ok: true, ...applyResult });
390
+ } catch (err) {
391
+ jsonReply(res, 500, { ok: false, saved: true, error: (err as Error).message });
392
+ }
393
+ }
394
394
 
395
395
  function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
396
396
  const result = { ...target };
@@ -422,14 +422,14 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
422
422
  result.platforms = result.platforms || {};
423
423
  (result.platforms as Record<string, unknown>).feishu = (result.platforms as Record<string, unknown>).feishu || {};
424
424
  ((result.platforms as Record<string, unknown>).feishu as Record<string, unknown>).platformType = val;
425
- } else if (key === "CHATCCC_ILINK_ENABLED") {
426
- result.platforms = result.platforms || {};
427
- (result.platforms as Record<string, unknown>).ilink = (result.platforms as Record<string, unknown>).ilink || {};
428
- ((result.platforms as Record<string, unknown>).ilink as Record<string, unknown>).enabled = val === true || val === "true";
429
- } else if (key === "CHATCCC_WEB_UI_OPEN_ON_START") {
430
- result.webUi = result.webUi || {};
431
- (result.webUi as Record<string, unknown>).openOnStart = val === true || val === "true";
432
- } else if (key === "CHATCCC_CHROME_DEVTOOLS_ENABLED") {
425
+ } else if (key === "CHATCCC_ILINK_ENABLED") {
426
+ result.platforms = result.platforms || {};
427
+ (result.platforms as Record<string, unknown>).ilink = (result.platforms as Record<string, unknown>).ilink || {};
428
+ ((result.platforms as Record<string, unknown>).ilink as Record<string, unknown>).enabled = val === true || val === "true";
429
+ } else if (key === "CHATCCC_WEB_UI_OPEN_ON_START") {
430
+ result.webUi = result.webUi || {};
431
+ (result.webUi as Record<string, unknown>).openOnStart = val === true || val === "true";
432
+ } else if (key === "CHATCCC_CHROME_DEVTOOLS_ENABLED") {
433
433
  result.chromeDevtools = result.chromeDevtools || {};
434
434
  (result.chromeDevtools as Record<string, unknown>).enabled = val === true || val === "true";
435
435
  } else if (key === "CHATCCC_CHROME_DEVTOOLS_PORT") {
@@ -496,37 +496,40 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
496
496
  } else if (key === "CHATCCC_CODEX_ALTERNATIVE_MODEL") {
497
497
  result.codex = result.codex || {};
498
498
  (result.codex as Record<string, unknown>).alternativeModel = val;
499
- } else if (key === "CHATCCC_CODEX_EFFORT") {
500
- result.codex = result.codex || {};
501
- (result.codex as Record<string, unknown>).effort = val;
502
- } else if (key === "CHATCCC_CODEX_FAST_MODE") {
503
- result.codex = result.codex || {};
504
- (result.codex as Record<string, unknown>).fastMode = val === true || val === "true";
505
- } else if (key === "CHATCCC_CODEX_ENABLED") {
499
+ } else if (key === "CHATCCC_CODEX_EFFORT") {
500
+ result.codex = result.codex || {};
501
+ (result.codex as Record<string, unknown>).effort = val;
502
+ } else if (key === "CHATCCC_CODEX_FAST_MODE") {
503
+ result.codex = result.codex || {};
504
+ (result.codex as Record<string, unknown>).fastMode = val === true || val === "true";
505
+ } else if (key === "CHATCCC_CODEX_ENABLED") {
506
506
  result.codex = result.codex || {};
507
507
  (result.codex as Record<string, unknown>).enabled = val === true || val === "true";
508
- } else if (key === "CHATCCC_CODEX_DEFAULT_AGENT") {
509
- result.codex = result.codex || {};
510
- (result.codex as Record<string, unknown>).defaultAgent = val === true || val === "true";
511
- } else if (key === "CHATCCC_CCC_API_KEY") {
512
- result.ccc = result.ccc || {};
513
- (result.ccc as Record<string, unknown>).DEEPSEEK_API_KEY = val;
514
- } else if (key === "CHATCCC_CCC_BASE_URL") {
515
- result.ccc = result.ccc || {};
516
- (result.ccc as Record<string, unknown>).DEEPSEEK_BASE_URL = val;
517
- } else if (key === "CHATCCC_CCC_MODEL") {
518
- result.ccc = result.ccc || {};
519
- (result.ccc as Record<string, unknown>).model = val;
520
- } else if (key === "CHATCCC_CCC_ALTERNATIVE_MODEL") {
521
- result.ccc = result.ccc || {};
522
- (result.ccc as Record<string, unknown>).alternativeModel = val;
523
- } else if (key === "CHATCCC_CCC_ENABLED") {
524
- result.ccc = result.ccc || {};
525
- (result.ccc as Record<string, unknown>).enabled = val === true || val === "true";
526
- } else if (key === "CHATCCC_CCC_DEFAULT_AGENT") {
527
- result.ccc = result.ccc || {};
528
- (result.ccc as Record<string, unknown>).defaultAgent = val === true || val === "true";
529
- }
508
+ } else if (key === "CHATCCC_CODEX_DEFAULT_AGENT") {
509
+ result.codex = result.codex || {};
510
+ (result.codex as Record<string, unknown>).defaultAgent = val === true || val === "true";
511
+ } else if (key === "CHATCCC_CCC_API_KEY") {
512
+ result.ccc = result.ccc || {};
513
+ (result.ccc as Record<string, unknown>).DEEPSEEK_API_KEY = val;
514
+ } else if (key === "CHATCCC_CCC_BASE_URL") {
515
+ result.ccc = result.ccc || {};
516
+ (result.ccc as Record<string, unknown>).DEEPSEEK_BASE_URL = val;
517
+ } else if (key === "CHATCCC_CCC_MODEL") {
518
+ result.ccc = result.ccc || {};
519
+ (result.ccc as Record<string, unknown>).model = val;
520
+ } else if (key === "CHATCCC_CCC_ALTERNATIVE_MODEL") {
521
+ result.ccc = result.ccc || {};
522
+ (result.ccc as Record<string, unknown>).alternativeModel = val;
523
+ } else if (key === "CHATCCC_CCC_EFFORT") {
524
+ result.ccc = result.ccc || {};
525
+ (result.ccc as Record<string, unknown>).effort = val;
526
+ } else if (key === "CHATCCC_CCC_ENABLED") {
527
+ result.ccc = result.ccc || {};
528
+ (result.ccc as Record<string, unknown>).enabled = val === true || val === "true";
529
+ } else if (key === "CHATCCC_CCC_DEFAULT_AGENT") {
530
+ result.ccc = result.ccc || {};
531
+ (result.ccc as Record<string, unknown>).defaultAgent = val === true || val === "true";
532
+ }
530
533
  }
531
534
  return result;
532
535
  }
@@ -562,18 +565,18 @@ async function handleStartService(_req: IncomingMessage, res: ServerResponse): P
562
565
  return;
563
566
  }
564
567
 
565
- if (path === "reload") {
566
- // service 已经在跑:只刷新进程内配置,让后续消息/新会话使用新值。
567
- if (!reloadConfigHook) {
568
- jsonReply(res, 200, { ok: true, pid: process.pid, mode: "saved" });
569
- return;
570
- }
571
- try {
572
- await reloadConfigHook();
573
- jsonReply(res, 200, { ok: true, pid: process.pid, mode: "reload" });
574
- } catch (err) {
575
- jsonReply(res, 500, { ok: false, error: (err as Error).message });
576
- }
568
+ if (path === "reload") {
569
+ // service 已经在跑:只刷新进程内配置,让后续消息/新会话使用新值。
570
+ if (!reloadConfigHook) {
571
+ jsonReply(res, 200, { ok: true, pid: process.pid, mode: "saved" });
572
+ return;
573
+ }
574
+ try {
575
+ await reloadConfigHook();
576
+ jsonReply(res, 200, { ok: true, pid: process.pid, mode: "reload" });
577
+ } catch (err) {
578
+ jsonReply(res, 500, { ok: false, error: (err as Error).message });
579
+ }
577
580
  return;
578
581
  }
579
582
 
@@ -626,29 +629,29 @@ async function handleForgetIlink(_req: IncomingMessage, res: ServerResponse): Pr
626
629
  }
627
630
  }
628
631
 
629
- // ---------------------------------------------------------------------------
630
- // HTML page (embedded template)
631
- // ---------------------------------------------------------------------------
632
-
633
- /** Agent Team 的首个占位页面:按产品约定只展示标题,不提前放入功能内容。 */
634
- export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
635
- <html lang="zh-CN">
636
- <head>
637
- <meta charset="UTF-8">
638
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
639
- <title>Agent Team</title>
640
- <style>
641
- *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
642
- 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}
643
- h1{font-size:clamp(40px,8vw,88px);font-weight:750;letter-spacing:-.04em;text-shadow:0 12px 42px rgba(129,140,248,.45)}
644
- </style>
645
- </head>
646
- <body>
647
- <h1>Agent Team</h1>
648
- </body>
649
- </html>`;
650
-
651
- export const PAGE_HTML = `<!DOCTYPE html>
632
+ // ---------------------------------------------------------------------------
633
+ // HTML page (embedded template)
634
+ // ---------------------------------------------------------------------------
635
+
636
+ /** Agent Team 的首个占位页面:按产品约定只展示标题,不提前放入功能内容。 */
637
+ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
638
+ <html lang="zh-CN">
639
+ <head>
640
+ <meta charset="UTF-8">
641
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
642
+ <title>Agent Team</title>
643
+ <style>
644
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
645
+ 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}
646
+ h1{font-size:clamp(40px,8vw,88px);font-weight:750;letter-spacing:-.04em;text-shadow:0 12px 42px rgba(129,140,248,.45)}
647
+ </style>
648
+ </head>
649
+ <body>
650
+ <h1>Agent Team</h1>
651
+ </body>
652
+ </html>`;
653
+
654
+ export const PAGE_HTML = `<!DOCTYPE html>
652
655
  <html lang="zh-CN">
653
656
  <head>
654
657
  <meta charset="UTF-8">
@@ -657,14 +660,14 @@ export const PAGE_HTML = `<!DOCTYPE html>
657
660
  <style>
658
661
  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
659
662
  body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f1f5f9;color:#1e293b;line-height:1.6}
660
- header{background:#0f172a;color:#fff;padding:16px 24px;display:flex;align-items:center;justify-content:space-between}
661
- header h1{font-size:20px;font-weight:600}
662
- header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500}
663
- .header-actions{display:flex;align-items:center;gap:12px}
664
- .agent-team-entry{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:linear-gradient(135deg,#6366f1,#8b5cf6 55%,#d946ef);color:#fff;text-decoration:none;font-size:14px;font-weight:700;letter-spacing:.01em;box-shadow:0 8px 24px rgba(99,102,241,.38);transition:transform .18s ease,box-shadow .18s ease,filter .18s ease}
665
- .agent-team-entry:hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(139,92,246,.52);filter:saturate(1.16)}
666
- .agent-team-entry:focus-visible{outline:3px solid rgba(196,181,253,.65);outline-offset:3px}
667
- .agent-team-entry .agent-team-icon{font-size:16px;line-height:1}
663
+ header{background:#0f172a;color:#fff;padding:16px 24px;display:flex;align-items:center;justify-content:space-between}
664
+ header h1{font-size:20px;font-weight:600}
665
+ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500}
666
+ .header-actions{display:flex;align-items:center;gap:12px}
667
+ .agent-team-entry{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:linear-gradient(135deg,#6366f1,#8b5cf6 55%,#d946ef);color:#fff;text-decoration:none;font-size:14px;font-weight:700;letter-spacing:.01em;box-shadow:0 8px 24px rgba(99,102,241,.38);transition:transform .18s ease,box-shadow .18s ease,filter .18s ease}
668
+ .agent-team-entry:hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(139,92,246,.52);filter:saturate(1.16)}
669
+ .agent-team-entry:focus-visible{outline:3px solid rgba(196,181,253,.65);outline-offset:3px}
670
+ .agent-team-entry .agent-team-icon{font-size:16px;line-height:1}
668
671
  .badge-running{background:#16a34a;color:#fff}
669
672
  .badge-stopped{background:#94a3b8;color:#fff}
670
673
  /* container 完全不限宽、不留左右内边距 —— step-2 是三列卡片需要尽量利用屏幕;
@@ -732,18 +735,18 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
732
735
  .toast-error{background:#ef4444}
733
736
  @keyframes slideIn{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}
734
737
  .spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite}
735
- @keyframes spin{to{transform:rotate(360deg)}}
736
- @media(max-width:520px){header{padding:12px 14px}.header-actions{gap:8px}.agent-team-entry{padding:8px 11px;font-size:13px}header .badge{padding:4px 8px}}
737
- </style>
738
+ @keyframes spin{to{transform:rotate(360deg)}}
739
+ @media(max-width:520px){header{padding:12px 14px}.header-actions{gap:8px}.agent-team-entry{padding:8px 11px;font-size:13px}header .badge{padding:4px 8px}}
740
+ </style>
738
741
  </head>
739
742
  <body>
740
- <header>
741
- <h1>ChatCCC</h1>
742
- <div class="header-actions">
743
- <a href="/agent-team" class="agent-team-entry"><span class="agent-team-icon" aria-hidden="true">✦</span>Agent Team <span aria-hidden="true">→</span></a>
744
- <span id="header-badge" class="badge badge-stopped">未启动</span>
745
- </div>
746
- </header>
743
+ <header>
744
+ <h1>ChatCCC</h1>
745
+ <div class="header-actions">
746
+ <a href="/agent-team" class="agent-team-entry"><span class="agent-team-icon" aria-hidden="true">✦</span>Agent Team <span aria-hidden="true">→</span></a>
747
+ <span id="header-badge" class="badge badge-stopped">未启动</span>
748
+ </div>
749
+ </header>
747
750
  <div class="container">
748
751
 
749
752
  <!-- ===== Setup Wizard ===== -->
@@ -791,7 +794,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
791
794
  </div>
792
795
  </div>
793
796
 
794
- <!-- 微信 iLink -->
797
+ <!-- 微信 iLink -->
795
798
  <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="platform-block-ilink">
796
799
  <div style="display:flex;align-items:center;justify-content:space-between">
797
800
  <div>
@@ -800,20 +803,20 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
800
803
  </div>
801
804
  <input type="checkbox" class="agent-toggle" id="platform-enable-ilink" checked onchange="onWizardPlatformToggle('ilink', this.checked)">
802
805
  </div>
803
- </div>
804
-
805
- <!-- Web UI 启动行为 -->
806
- <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="web-ui-startup-block">
807
- <div style="display:flex;align-items:center;justify-content:space-between;gap:16px">
808
- <div>
809
- <div style="font-weight:600;font-size:14px">启动时打开 Web UI</div>
810
- <div style="font-size:12px;color:#64748b">直接运行 ChatCCC 时用系统默认浏览器打开管理页面;内部重启始终不打开</div>
811
- </div>
812
- <input type="checkbox" class="agent-toggle" id="field-CHATCCC_WEB_UI_OPEN_ON_START" checked>
813
- </div>
814
- </div>
815
-
816
- <!-- Chrome CDP -->
806
+ </div>
807
+
808
+ <!-- Web UI 启动行为 -->
809
+ <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="web-ui-startup-block">
810
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:16px">
811
+ <div>
812
+ <div style="font-weight:600;font-size:14px">启动时打开 Web UI</div>
813
+ <div style="font-size:12px;color:#64748b">直接运行 ChatCCC 时用系统默认浏览器打开管理页面;内部重启始终不打开</div>
814
+ </div>
815
+ <input type="checkbox" class="agent-toggle" id="field-CHATCCC_WEB_UI_OPEN_ON_START" checked>
816
+ </div>
817
+ </div>
818
+
819
+ <!-- Chrome CDP -->
817
820
  <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="chrome-devtools-block">
818
821
  <div style="display:flex;align-items:center;justify-content:space-between">
819
822
  <div>
@@ -931,7 +934,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
931
934
  </fieldset>
932
935
  </div>
933
936
 
934
- <!-- Codex 卡片 -->
937
+ <!-- Codex 卡片 -->
935
938
  <div class="agent-card" id="agent-card-codex">
936
939
  <div class="agent-card-header">
937
940
  <input type="checkbox" class="agent-toggle" id="agent-enable-codex" onchange="onAgentToggle('codex', this.checked)">
@@ -957,54 +960,67 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
957
960
  <label>备选模型(选填)</label>
958
961
  <input type="text" id="field-CHATCCC_CODEX_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
959
962
  </div>
960
- <div class="form-group">
961
- <label>努力程度 (Effort)</label>
962
- <input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
963
- </div>
964
- <div class="form-group">
965
- <label style="display:flex;align-items:center;gap:8px">
966
- <input type="checkbox" id="field-CHATCCC_CODEX_FAST_MODE"> Fast 模式
967
- </label>
968
- </div>
969
- <button class="btn btn-outline" onclick="validateCli('codex')" style="margin-bottom:12px">检测 Codex CLI</button>
963
+ <div class="form-group">
964
+ <label>努力程度 (Effort)</label>
965
+ <input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
966
+ </div>
967
+ <div class="form-group">
968
+ <label style="display:flex;align-items:center;gap:8px">
969
+ <input type="checkbox" id="field-CHATCCC_CODEX_FAST_MODE"> Fast 模式
970
+ </label>
971
+ </div>
972
+ <button class="btn btn-outline" onclick="validateCli('codex')" style="margin-bottom:12px">检测 Codex CLI</button>
970
973
  <div id="codex-validate-result"></div>
971
974
  </fieldset>
972
- </div>
973
-
974
- <!-- CCC Agent 卡片 -->
975
- <div class="agent-card" id="agent-card-ccc">
976
- <div class="agent-card-header">
977
- <input type="checkbox" class="agent-toggle" id="agent-enable-ccc" onchange="onAgentToggle('ccc', this.checked)">
978
- <div class="meta">
979
- <div class="name">CCC Agent</div>
980
- <div class="desc">ChatCCC 内置 Agent<br>使用 DeepSeek 兼容 API</div>
981
- </div>
982
- </div>
983
- <label class="agent-default-row">
984
- <input type="checkbox" id="agent-default-ccc" onchange="onDefaultAgentToggle('ccc', this.checked)">
985
- 设为默认 Agent
986
- </label>
987
- <fieldset class="agent-body" id="agent-body-ccc" disabled>
988
- <div class="form-group">
989
- <label>API Key</label>
990
- <input type="password" id="field-CHATCCC_CCC_API_KEY" placeholder="DeepSeek 兼容 API Key">
991
- </div>
992
- <div class="form-group">
993
- <label>Base URL</label>
994
- <input type="text" id="field-CHATCCC_CCC_BASE_URL" placeholder="https://api.deepseek.com/v1">
995
- </div>
996
- <div class="form-group">
997
- <label>模型</label>
998
- <input type="text" id="field-CHATCCC_CCC_MODEL" placeholder="deepseek-v4-pro">
999
- </div>
1000
- <div class="form-group">
1001
- <label>备选模型(选填)</label>
1002
- <input type="text" id="field-CHATCCC_CCC_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
1003
- </div>
1004
- </fieldset>
1005
- </div>
1006
-
1007
- </div>
975
+ </div>
976
+
977
+ <!-- CCC Agent 卡片 -->
978
+ <div class="agent-card" id="agent-card-ccc">
979
+ <div class="agent-card-header">
980
+ <input type="checkbox" class="agent-toggle" id="agent-enable-ccc" onchange="onAgentToggle('ccc', this.checked)">
981
+ <div class="meta">
982
+ <div class="name">CCC Agent</div>
983
+ <div class="desc">ChatCCC 内置 Agent<br>使用 DeepSeek 兼容 API</div>
984
+ </div>
985
+ </div>
986
+ <label class="agent-default-row">
987
+ <input type="checkbox" id="agent-default-ccc" onchange="onDefaultAgentToggle('ccc', this.checked)">
988
+ 设为默认 Agent
989
+ </label>
990
+ <fieldset class="agent-body" id="agent-body-ccc" disabled>
991
+ <div class="form-group">
992
+ <label>API Key</label>
993
+ <input type="password" id="field-CHATCCC_CCC_API_KEY" placeholder="DeepSeek 兼容 API Key">
994
+ </div>
995
+ <div class="form-group">
996
+ <label>Base URL</label>
997
+ <input type="text" id="field-CHATCCC_CCC_BASE_URL" placeholder="https://api.deepseek.com/v1">
998
+ </div>
999
+ <div class="form-group">
1000
+ <label>模型</label>
1001
+ <input type="text" id="field-CHATCCC_CCC_MODEL" placeholder="deepseek-v4-pro">
1002
+ </div>
1003
+ <div class="form-group">
1004
+ <label>备选模型(选填)</label>
1005
+ <input type="text" id="field-CHATCCC_CCC_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
1006
+ </div>
1007
+ <div class="form-group">
1008
+ <label>Effort(推理强度,选填)</label>
1009
+ <select id="field-CHATCCC_CCC_EFFORT">
1010
+ <option value="">(留空/默认,服务端 medium)</option>
1011
+ <option value="none">none - 直接作答,最省 token</option>
1012
+ <option value="minimal">minimal</option>
1013
+ <option value="low">low</option>
1014
+ <option value="medium">medium</option>
1015
+ <option value="high">high</option>
1016
+ <option value="xhigh">xhigh</option>
1017
+ <option value="max">max - 最强推理</option>
1018
+ </select>
1019
+ </div>
1020
+ </fieldset>
1021
+ </div>
1022
+
1023
+ </div>
1008
1024
 
1009
1025
  <div class="btn-group" style="justify-content:space-between">
1010
1026
  <button class="btn btn-outline" onclick="goStep(1)">返回</button>
@@ -1047,16 +1063,16 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1047
1063
  <span id="dash-platform-feishu-label">已启用</span>
1048
1064
  </label>
1049
1065
  </div>
1050
- <div class="config-row"><span class="key">App ID</span><span class="val" id="cfg-APP_ID">-</span></div>
1051
- <div class="config-row"><span class="key">App Secret</span><span class="val" id="cfg-APP_SECRET">-</span></div>
1052
- <div class="config-row"><span class="key">平台类型</span><span class="val" id="cfg-FEISHU_PLATFORM_TYPE">-</span></div>
1053
- <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:飞书开关、App ID、App Secret 或平台类型变更需要重启 ChatCCC。</div>
1054
- <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('feishu')">编辑</button>
1066
+ <div class="config-row"><span class="key">App ID</span><span class="val" id="cfg-APP_ID">-</span></div>
1067
+ <div class="config-row"><span class="key">App Secret</span><span class="val" id="cfg-APP_SECRET">-</span></div>
1068
+ <div class="config-row"><span class="key">平台类型</span><span class="val" id="cfg-FEISHU_PLATFORM_TYPE">-</span></div>
1069
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:飞书开关、App ID、App Secret 或平台类型变更需要重启 ChatCCC。</div>
1070
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('feishu')">编辑</button>
1055
1071
  </div>
1056
1072
  </details>
1057
1073
 
1058
- <details class="card config-section">
1059
- <summary>微信 iLink</summary>
1074
+ <details class="card config-section">
1075
+ <summary>微信 iLink</summary>
1060
1076
  <div class="section-detail">
1061
1077
  <div class="config-row">
1062
1078
  <span class="key">状态</span>
@@ -1065,30 +1081,30 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1065
1081
  <span id="dash-platform-ilink-label">未启用</span>
1066
1082
  </label>
1067
1083
  </div>
1068
- <div id="ilink-forget-row" style="margin-top:6px;display:none">
1069
- <button class="btn btn-outline" style="font-size:12px" onclick="forgetIlink()">忘记扫码</button>
1070
- <span style="font-size:11px;color:#94a3b8;margin-left:8px">清除登录状态,重启后重新扫码</span>
1071
- </div>
1072
- <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:微信 iLink 开关变更需要重启 ChatCCC。</div>
1073
- </div>
1074
- </details>
1075
-
1076
- <details class="card config-section">
1077
- <summary>Web UI</summary>
1078
- <div class="section-detail">
1079
- <div class="config-row"><span class="key">直接启动时自动打开</span><span class="val" id="cfg-WEB_UI_OPEN_ON_START">-</span></div>
1080
- <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:下次直接启动生效;内部重启始终不打开。Linux 无图形桌面时会跳过打开并输出 SSH 隧道提示。</div>
1081
- <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('webUi')">编辑</button>
1082
- </div>
1083
- </details>
1084
-
1085
- <details class="card config-section">
1084
+ <div id="ilink-forget-row" style="margin-top:6px;display:none">
1085
+ <button class="btn btn-outline" style="font-size:12px" onclick="forgetIlink()">忘记扫码</button>
1086
+ <span style="font-size:11px;color:#94a3b8;margin-left:8px">清除登录状态,重启后重新扫码</span>
1087
+ </div>
1088
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:微信 iLink 开关变更需要重启 ChatCCC。</div>
1089
+ </div>
1090
+ </details>
1091
+
1092
+ <details class="card config-section">
1093
+ <summary>Web UI</summary>
1094
+ <div class="section-detail">
1095
+ <div class="config-row"><span class="key">直接启动时自动打开</span><span class="val" id="cfg-WEB_UI_OPEN_ON_START">-</span></div>
1096
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:下次直接启动生效;内部重启始终不打开。Linux 无图形桌面时会跳过打开并输出 SSH 隧道提示。</div>
1097
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('webUi')">编辑</button>
1098
+ </div>
1099
+ </details>
1100
+
1101
+ <details class="card config-section">
1086
1102
  <summary>Chrome CDP(选填)</summary>
1087
1103
  <div class="section-detail">
1088
1104
  <div class="config-row"><span class="key">状态</span><span class="val" id="cfg-CHROME_DEVTOOLS_ENABLED">-</span></div>
1089
1105
  <div class="config-row" id="cfg-CHROME_DEVTOOLS_PORT_ROW"><span class="key">CDP 端口</span><span class="val" id="cfg-CHROME_DEVTOOLS_PORT">-</span></div>
1090
1106
  <div class="config-row" id="cfg-CHROME_DEVTOOLS_PATH_ROW"><span class="key">Chrome 路径</span><span class="val" id="cfg-CHROME_DEVTOOLS_PATH">-</span></div>
1091
- <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后立即应用到 Chrome CDP 守护进程。依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。</div>
1107
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后立即应用到 Chrome CDP 守护进程。依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。</div>
1092
1108
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('chromeDevtools')">编辑</button>
1093
1109
  </div>
1094
1110
  </details>
@@ -1101,10 +1117,10 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1101
1117
  <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-ANTHROPIC_EFFORT">-</span></div>
1102
1118
  <div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-ANTHROPIC_API_KEY">-</span></div>
1103
1119
  <div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-ANTHROPIC_BASE_URL">-</span></div>
1104
- <div class="config-row"><span class="key">Max Turns</span><span class="val" id="cfg-ANTHROPIC_MAX_TURN">-</span><span class="hint">(0=无限制)</span></div>
1105
- <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
1106
- <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1107
- <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
1120
+ <div class="config-row"><span class="key">Max Turns</span><span class="val" id="cfg-ANTHROPIC_MAX_TURN">-</span><span class="hint">(0=无限制)</span></div>
1121
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
1122
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1123
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
1108
1124
  </div>
1109
1125
  </details>
1110
1126
 
@@ -1115,39 +1131,39 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1115
1131
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CURSOR_MODEL">-</span></div>
1116
1132
  <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CURSOR_ALTERNATIVE_MODEL">-</span></div>
1117
1133
  <div class="config-row"><span class="key">头像电池电量</span><span class="val" id="cfg-CURSOR_AVATAR_BATTERY_MODE">-</span></div>
1118
- <div class="config-row" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW"><span class="key">每月On demand use预算</span><span class="val" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET">-</span></div>
1119
- <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
1120
- <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1121
- <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('cursor')">编辑</button>
1134
+ <div class="config-row" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW"><span class="key">每月On demand use预算</span><span class="val" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET">-</span></div>
1135
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
1136
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1137
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('cursor')">编辑</button>
1122
1138
  </div>
1123
1139
  </details>
1124
1140
 
1125
- <details class="card config-section" id="dash-codex">
1141
+ <details class="card config-section" id="dash-codex">
1126
1142
  <summary>Codex Agent</summary>
1127
1143
  <div class="section-detail">
1128
1144
  <div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CODEX_PATH">-</span></div>
1129
1145
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CODEX_MODEL">-</span></div>
1130
- <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
1131
- <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
1132
- <div class="config-row"><span class="key">Fast 模式</span><span class="val" id="cfg-CODEX_FAST_MODE">-</span></div>
1133
- <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
1134
- <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1135
- <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
1146
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
1147
+ <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
1148
+ <div class="config-row"><span class="key">Fast 模式</span><span class="val" id="cfg-CODEX_FAST_MODE">-</span></div>
1149
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
1150
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1151
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
1136
1152
  </div>
1137
- </details>
1138
-
1139
- <details class="card config-section" id="dash-ccc">
1140
- <summary>CCC Agent</summary>
1141
- <div class="section-detail">
1142
- <div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-CCC_API_KEY">-</span></div>
1143
- <div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-CCC_BASE_URL">-</span></div>
1144
- <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
1145
- <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
1146
- <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-ccc" onchange="setDashboardDefaultAgent('ccc', this.checked)"> 设为默认 Agent</label>
1147
- <div class="hint" style="margin-top:6px;line-height:1.6">备选模型仅加入 /model 人工切换列表;保存后下一条消息或下个新会话生效。</div>
1148
- <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('ccc')">编辑</button>
1149
- </div>
1150
- </details>
1153
+ </details>
1154
+
1155
+ <details class="card config-section" id="dash-ccc">
1156
+ <summary>CCC Agent</summary>
1157
+ <div class="section-detail">
1158
+ <div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-CCC_API_KEY">-</span></div>
1159
+ <div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-CCC_BASE_URL">-</span></div>
1160
+ <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
1161
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
1162
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-ccc" onchange="setDashboardDefaultAgent('ccc', this.checked)"> 设为默认 Agent</label>
1163
+ <div class="hint" style="margin-top:6px;line-height:1.6">备选模型仅加入 /model 人工切换列表;保存后下一条消息或下个新会话生效。</div>
1164
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('ccc')">编辑</button>
1165
+ </div>
1166
+ </details>
1151
1167
 
1152
1168
  <div class="card" id="dash-no-agent-hint" style="text-align:center;color:#94a3b8;display:none">
1153
1169
  未启用任何 AI Agent。点击下方按钮重新运行配置向导启用。
@@ -1161,8 +1177,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1161
1177
  <!-- Edit Modal -->
1162
1178
  <div id="edit-modal" class="card hidden" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90%;max-width:480px;z-index:200;max-height:80vh;overflow-y:auto">
1163
1179
  <h2 id="edit-modal-title">编辑配置</h2>
1164
- <div id="edit-modal-effect" class="hint" style="margin-bottom:12px;line-height:1.6"></div>
1165
- <div id="edit-modal-fields"></div>
1180
+ <div id="edit-modal-effect" class="hint" style="margin-bottom:12px;line-height:1.6"></div>
1181
+ <div id="edit-modal-fields"></div>
1166
1182
  <div class="btn-group" style="justify-content:flex-end;margin-top:16px">
1167
1183
  <button class="btn btn-outline" onclick="closeEditModal()">取消</button>
1168
1184
  <button class="btn btn-primary" onclick="saveEdit()">保存</button>
@@ -1175,8 +1191,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1175
1191
  <script>
1176
1192
  let state = {
1177
1193
  view: 'loading',
1178
- // 四个 Agent 各自的启用开关;初始全 false,renderStep2() 会按已存在 config 自动打开
1179
- agentsEnabled: { claude: false, cursor: false, codex: false, ccc: false },
1194
+ // 四个 Agent 各自的启用开关;初始全 false,renderStep2() 会按已存在 config 自动打开
1195
+ agentsEnabled: { claude: false, cursor: false, codex: false, ccc: false },
1180
1196
  defaultAgent: 'claude',
1181
1197
  wizardStep: 1,
1182
1198
  config: {},
@@ -1192,39 +1208,39 @@ var step2InputBound = false;
1192
1208
  const AGENT_FIELDS = {
1193
1209
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
1194
1210
  cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
1195
- codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
1196
- ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL']
1211
+ codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
1212
+ ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL','CHATCCC_CCC_EFFORT']
1197
1213
  };
1198
- const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
1199
- const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
1200
- const CHROME_DEVTOOLS_FIELDS = ['CHATCCC_CHROME_DEVTOOLS_ENABLED','CHATCCC_CHROME_DEVTOOLS_PORT','CHATCCC_CHROME_DEVTOOLS_PATH'];
1214
+ const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
1215
+ const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
1216
+ const CHROME_DEVTOOLS_FIELDS = ['CHATCCC_CHROME_DEVTOOLS_ENABLED','CHATCCC_CHROME_DEVTOOLS_PORT','CHATCCC_CHROME_DEVTOOLS_PATH'];
1201
1217
 
1202
1218
  function cursorBatteryModeLabel(value) {
1203
1219
  return value === 'onDemandUse' ? 'On demand use 金额' : 'API 使用比例';
1204
1220
  }
1205
1221
 
1206
- function configEffectHint(section) {
1207
- if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
1208
- if (section === 'webUi') return '生效范围:下次直接启动生效;内部重启始终不打开。';
1209
- if (section === 'chromeDevtools') return '生效范围:保存后立即应用到 Chrome CDP 守护进程。';
1210
- if (section === 'claude') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1211
- if (section === 'cursor') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1212
- if (section === 'codex') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1213
- return '生效范围:保存后按配置类型应用。';
1214
- }
1215
-
1216
- function toastConfigApplyResult(result, savedText) {
1217
- var text = savedText || '配置已保存';
1218
- if (result && result.mode === 'reload') {
1219
- toast(text + ',后续消息/新会话已生效。');
1220
- } else if (result && result.mode === 'restart-required') {
1221
- toast(text + ',需重启服务生效。');
1222
- } else {
1223
- toast(text);
1224
- }
1225
- }
1226
-
1227
- function onCursorBatteryModeChange(prefix, value) {
1222
+ function configEffectHint(section) {
1223
+ if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
1224
+ if (section === 'webUi') return '生效范围:下次直接启动生效;内部重启始终不打开。';
1225
+ if (section === 'chromeDevtools') return '生效范围:保存后立即应用到 Chrome CDP 守护进程。';
1226
+ if (section === 'claude') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1227
+ if (section === 'cursor') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1228
+ if (section === 'codex') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1229
+ return '生效范围:保存后按配置类型应用。';
1230
+ }
1231
+
1232
+ function toastConfigApplyResult(result, savedText) {
1233
+ var text = savedText || '配置已保存';
1234
+ if (result && result.mode === 'reload') {
1235
+ toast(text + ',后续消息/新会话已生效。');
1236
+ } else if (result && result.mode === 'restart-required') {
1237
+ toast(text + ',需重启服务生效。');
1238
+ } else {
1239
+ toast(text);
1240
+ }
1241
+ }
1242
+
1243
+ function onCursorBatteryModeChange(prefix, value) {
1228
1244
  var rowId = prefix === 'edit-' ? 'edit-cursor-on-demand-budget-row' : 'field-cursor-on-demand-budget-row';
1229
1245
  var row = document.getElementById(rowId);
1230
1246
  if (row) row.style.display = value === 'onDemandUse' ? '' : 'none';
@@ -1258,10 +1274,10 @@ async function onPlatformToggle(platform, enabled) {
1258
1274
  if (result.ok) {
1259
1275
  state.config.platforms = state.config.platforms || {};
1260
1276
  state.config.platforms[platform] = state.config.platforms[platform] || {};
1261
- state.config.platforms[platform].enabled = enabled;
1262
- var label = document.getElementById('dash-platform-' + platform + '-label');
1263
- if (label) label.textContent = enabled ? '已启用' : '未启用';
1264
- toastConfigApplyResult(result, (platform === 'feishu' ? '飞书' : '微信 iLink') + (enabled ? ' 已启用' : ' 已禁用'));
1277
+ state.config.platforms[platform].enabled = enabled;
1278
+ var label = document.getElementById('dash-platform-' + platform + '-label');
1279
+ if (label) label.textContent = enabled ? '已启用' : '未启用';
1280
+ toastConfigApplyResult(result, (platform === 'feishu' ? '飞书' : '微信 iLink') + (enabled ? ' 已启用' : ' 已禁用'));
1265
1281
  } else {
1266
1282
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1267
1283
  // 还原 toggle
@@ -1298,25 +1314,25 @@ function onAgentToggle(agent, enabled) {
1298
1314
  function firstEnabledAgent() {
1299
1315
  if (state.agentsEnabled.claude) return 'claude';
1300
1316
  if (state.agentsEnabled.cursor) return 'cursor';
1301
- if (state.agentsEnabled.codex) return 'codex';
1302
- if (state.agentsEnabled.ccc) return 'ccc';
1317
+ if (state.agentsEnabled.codex) return 'codex';
1318
+ if (state.agentsEnabled.ccc) return 'ccc';
1303
1319
  return null;
1304
1320
  }
1305
1321
 
1306
- function resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn) {
1322
+ function resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn) {
1307
1323
  if (claudeOn && c.claude && c.claude.defaultAgent === true) return 'claude';
1308
1324
  if (cursorOn && c.cursor && c.cursor.defaultAgent === true) return 'cursor';
1309
- if (codexOn && c.codex && c.codex.defaultAgent === true) return 'codex';
1310
- if (cccOn && c.ccc && c.ccc.defaultAgent === true) return 'ccc';
1325
+ if (codexOn && c.codex && c.codex.defaultAgent === true) return 'codex';
1326
+ if (cccOn && c.ccc && c.ccc.defaultAgent === true) return 'ccc';
1311
1327
  if (claudeOn) return 'claude';
1312
1328
  if (cursorOn) return 'cursor';
1313
- if (codexOn) return 'codex';
1314
- if (cccOn) return 'ccc';
1329
+ if (codexOn) return 'codex';
1330
+ if (cccOn) return 'ccc';
1315
1331
  return 'claude';
1316
1332
  }
1317
1333
 
1318
1334
  function updateDefaultAgentToggles() {
1319
- ['claude','cursor','codex','ccc'].forEach(function(agent){
1335
+ ['claude','cursor','codex','ccc'].forEach(function(agent){
1320
1336
  var el = document.getElementById('agent-default-' + agent);
1321
1337
  if (el) {
1322
1338
  el.checked = state.defaultAgent === agent;
@@ -1347,8 +1363,8 @@ function updateStep2NextBtn() {
1347
1363
  var validCount = 0;
1348
1364
  if (state.agentsEnabled.claude) validCount++;
1349
1365
  if (state.agentsEnabled.cursor) validCount++;
1350
- if (state.agentsEnabled.codex) validCount++;
1351
- if (state.agentsEnabled.ccc) validCount++;
1366
+ if (state.agentsEnabled.codex) validCount++;
1367
+ if (state.agentsEnabled.ccc) validCount++;
1352
1368
  if (validCount > 0 && !state.defaultAgent) {
1353
1369
  state.defaultAgent = firstEnabledAgent();
1354
1370
  updateDefaultAgentToggles();
@@ -1459,11 +1475,11 @@ function renderStep1() {
1459
1475
  // 飞书凭证字段初始显隐
1460
1476
  var credFields = document.getElementById('feishu-cred-fields');
1461
1477
  if (credFields) credFields.style.display = feishuEnabled ? '' : 'none';
1462
- var ilToggle = document.getElementById('platform-enable-ilink');
1463
- if (ilToggle) ilToggle.checked = ilinkEnabled;
1464
- var webUiOpenOnStart = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
1465
- if (webUiOpenOnStart) webUiOpenOnStart.checked = !c.webUi || c.webUi.openOnStart !== false;
1466
- var cd = c.chromeDevtools || {};
1478
+ var ilToggle = document.getElementById('platform-enable-ilink');
1479
+ if (ilToggle) ilToggle.checked = ilinkEnabled;
1480
+ var webUiOpenOnStart = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
1481
+ if (webUiOpenOnStart) webUiOpenOnStart.checked = !c.webUi || c.webUi.openOnStart !== false;
1482
+ var cd = c.chromeDevtools || {};
1467
1483
  var cdpEnabled = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
1468
1484
  if (cdpEnabled) cdpEnabled.checked = cd.enabled === true;
1469
1485
  prefillNested('field-CHATCCC_CHROME_DEVTOOLS_PORT', cd.port || 15166);
@@ -1488,9 +1504,9 @@ function isAgentEnabled(node, keys) {
1488
1504
 
1489
1505
  var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort','maxTurn'];
1490
1506
  var CURSOR_FALLBACK_KEYS = ['path','command','model','alternativeModel'];
1491
- var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort','fastMode'];
1492
- // 旧版 CCC 配置没有 enabled;只有 API Key 才表示用户实际完成了配置。
1493
- var CCC_FALLBACK_KEYS = ['DEEPSEEK_API_KEY'];
1507
+ var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort','fastMode'];
1508
+ // 旧版 CCC 配置没有 enabled;只有 API Key 才表示用户实际完成了配置。
1509
+ var CCC_FALLBACK_KEYS = ['DEEPSEEK_API_KEY'];
1494
1510
 
1495
1511
  function renderStep2() {
1496
1512
  var c = state.config || {};
@@ -1515,35 +1531,36 @@ function renderStep2() {
1515
1531
  var cursorBatteryModeEl = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1516
1532
  if (cursorBatteryModeEl && !cursorBatteryModeEl.value) cursorBatteryModeEl.value = 'apiPercent';
1517
1533
  onCursorBatteryModeChange('field-', cursorBatteryModeEl ? cursorBatteryModeEl.value : 'apiPercent');
1518
- if (c.codex) {
1519
- prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
1520
- prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
1521
- prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
1522
- prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
1523
- }
1524
- var codexFastModeEl = document.getElementById('field-CHATCCC_CODEX_FAST_MODE');
1525
- if (codexFastModeEl) codexFastModeEl.checked = !!(c.codex && c.codex.fastMode === true);
1526
- if (c.ccc) {
1527
- prefillNested('field-CHATCCC_CCC_API_KEY', c.ccc.DEEPSEEK_API_KEY);
1528
- prefillNested('field-CHATCCC_CCC_BASE_URL', c.ccc.DEEPSEEK_BASE_URL);
1529
- prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
1530
- prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
1531
- }
1534
+ if (c.codex) {
1535
+ prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
1536
+ prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
1537
+ prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
1538
+ prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
1539
+ }
1540
+ var codexFastModeEl = document.getElementById('field-CHATCCC_CODEX_FAST_MODE');
1541
+ if (codexFastModeEl) codexFastModeEl.checked = !!(c.codex && c.codex.fastMode === true);
1542
+ if (c.ccc) {
1543
+ prefillNested('field-CHATCCC_CCC_API_KEY', c.ccc.DEEPSEEK_API_KEY);
1544
+ prefillNested('field-CHATCCC_CCC_BASE_URL', c.ccc.DEEPSEEK_BASE_URL);
1545
+ prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
1546
+ prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
1547
+ prefillNested('field-CHATCCC_CCC_EFFORT', c.ccc.effort);
1548
+ }
1532
1549
 
1533
1550
  // 按已有 config 决定每个 Agent 默认是否开启:优先 enabled 字段,缺省时按"任一字段非空"
1534
1551
  var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
1535
1552
  var cursorOn = isAgentEnabled(c.cursor, CURSOR_FALLBACK_KEYS);
1536
- var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
1537
- var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
1538
- state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
1553
+ var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
1554
+ var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
1555
+ state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
1539
1556
  document.getElementById('agent-enable-claude').checked = claudeOn;
1540
1557
  document.getElementById('agent-enable-cursor').checked = cursorOn;
1541
- document.getElementById('agent-enable-codex').checked = codexOn;
1542
- document.getElementById('agent-enable-ccc').checked = cccOn;
1558
+ document.getElementById('agent-enable-codex').checked = codexOn;
1559
+ document.getElementById('agent-enable-ccc').checked = cccOn;
1543
1560
  onAgentToggle('claude', claudeOn);
1544
1561
  onAgentToggle('cursor', cursorOn);
1545
- onAgentToggle('codex', codexOn);
1546
- onAgentToggle('ccc', cccOn);
1562
+ onAgentToggle('codex', codexOn);
1563
+ onAgentToggle('ccc', cccOn);
1547
1564
  updateDefaultAgentToggles();
1548
1565
 
1549
1566
  // Cursor path placeholder/hint:把已探测到的路径显示为占位
@@ -1567,7 +1584,7 @@ function renderStep2() {
1567
1584
  * 收集"待落地到 config.json 的扁平 vars"。
1568
1585
  *
1569
1586
  * - 飞书字段始终收集
1570
- * - 四个 Agent 的 enabled 状态都显式下发,让 config.json 持久化用户的最新开关偏好
1587
+ * - 四个 Agent 的 enabled 状态都显式下发,让 config.json 持久化用户的最新开关偏好
1571
1588
  * - Agent 字段仅在该 Agent 开关启用时收集;未启用的 Agent 不下发其它字段,
1572
1589
  * 服务端 deepMerge 会保留 config.json 中已有值(避免关闭开关时误清空旧配置)
1573
1590
  */
@@ -1580,11 +1597,11 @@ function collectAllFields() {
1580
1597
  // 平台类型(下拉选择框,始终发送以保持与服务端同步)
1581
1598
  var ptEl = document.getElementById('field-CHATCCC_FEISHU_PLATFORM_TYPE');
1582
1599
  if (ptEl && ptEl.value.trim()) vars['CHATCCC_FEISHU_PLATFORM_TYPE'] = ptEl.value.trim();
1583
- vars.CHATCCC_FEISHU_ENABLED = !!state.platformsEnabled.feishu;
1584
- vars.CHATCCC_ILINK_ENABLED = !!state.platformsEnabled.ilink;
1585
- var webUiOpenOnStartEl = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
1586
- vars.CHATCCC_WEB_UI_OPEN_ON_START = !webUiOpenOnStartEl || !!webUiOpenOnStartEl.checked;
1587
- var cdpEnabledEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
1600
+ vars.CHATCCC_FEISHU_ENABLED = !!state.platformsEnabled.feishu;
1601
+ vars.CHATCCC_ILINK_ENABLED = !!state.platformsEnabled.ilink;
1602
+ var webUiOpenOnStartEl = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
1603
+ vars.CHATCCC_WEB_UI_OPEN_ON_START = !webUiOpenOnStartEl || !!webUiOpenOnStartEl.checked;
1604
+ var cdpEnabledEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
1588
1605
  vars.CHATCCC_CHROME_DEVTOOLS_ENABLED = !!(cdpEnabledEl && cdpEnabledEl.checked);
1589
1606
  var cdpPortEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_PORT');
1590
1607
  vars.CHATCCC_CHROME_DEVTOOLS_PORT = (cdpPortEl && cdpPortEl.value.trim()) ? cdpPortEl.value.trim() : '15166';
@@ -1592,15 +1609,15 @@ function collectAllFields() {
1592
1609
  if (cdpPathEl && cdpPathEl.value.trim()) vars.CHATCCC_CHROME_DEVTOOLS_PATH = cdpPathEl.value.trim();
1593
1610
  vars.CHATCCC_CLAUDE_ENABLED = !!state.agentsEnabled.claude;
1594
1611
  vars.CHATCCC_CURSOR_ENABLED = !!state.agentsEnabled.cursor;
1595
- vars.CHATCCC_CODEX_ENABLED = !!state.agentsEnabled.codex;
1596
- vars.CHATCCC_CCC_ENABLED = !!state.agentsEnabled.ccc;
1612
+ vars.CHATCCC_CODEX_ENABLED = !!state.agentsEnabled.codex;
1613
+ vars.CHATCCC_CCC_ENABLED = !!state.agentsEnabled.ccc;
1597
1614
  if (!state.defaultAgent || !state.agentsEnabled[state.defaultAgent]) {
1598
1615
  state.defaultAgent = firstEnabledAgent();
1599
1616
  }
1600
1617
  vars.CHATCCC_CLAUDE_DEFAULT_AGENT = state.defaultAgent === 'claude';
1601
1618
  vars.CHATCCC_CURSOR_DEFAULT_AGENT = state.defaultAgent === 'cursor';
1602
- vars.CHATCCC_CODEX_DEFAULT_AGENT = state.defaultAgent === 'codex';
1603
- vars.CHATCCC_CCC_DEFAULT_AGENT = state.defaultAgent === 'ccc';
1619
+ vars.CHATCCC_CODEX_DEFAULT_AGENT = state.defaultAgent === 'codex';
1620
+ vars.CHATCCC_CCC_DEFAULT_AGENT = state.defaultAgent === 'ccc';
1604
1621
  if (state.agentsEnabled.claude) {
1605
1622
  AGENT_FIELDS.claude.forEach(function(key){
1606
1623
  var el = document.getElementById('field-' + key);
@@ -1613,21 +1630,21 @@ function collectAllFields() {
1613
1630
  if (el && el.value.trim()) vars[key] = el.value.trim();
1614
1631
  });
1615
1632
  }
1616
- if (state.agentsEnabled.codex) {
1617
- AGENT_FIELDS.codex.forEach(function(key){
1618
- var el = document.getElementById('field-' + key);
1619
- if (!el) return;
1620
- if (key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
1621
- else if (el.value.trim()) vars[key] = el.value.trim();
1622
- });
1623
- }
1624
- if (state.agentsEnabled.ccc) {
1625
- AGENT_FIELDS.ccc.forEach(function(key){
1626
- var el = document.getElementById('field-' + key);
1627
- if (el && el.value.trim()) vars[key] = el.value.trim();
1628
- });
1629
- }
1630
- return vars;
1633
+ if (state.agentsEnabled.codex) {
1634
+ AGENT_FIELDS.codex.forEach(function(key){
1635
+ var el = document.getElementById('field-' + key);
1636
+ if (!el) return;
1637
+ if (key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
1638
+ else if (el.value.trim()) vars[key] = el.value.trim();
1639
+ });
1640
+ }
1641
+ if (state.agentsEnabled.ccc) {
1642
+ AGENT_FIELDS.ccc.forEach(function(key){
1643
+ var el = document.getElementById('field-' + key);
1644
+ if (el && el.value.trim()) vars[key] = el.value.trim();
1645
+ });
1646
+ }
1647
+ return vars;
1631
1648
  }
1632
1649
 
1633
1650
  function renderStep3() {
@@ -1643,16 +1660,16 @@ function renderStep3() {
1643
1660
  lines.push('<div class="config-row"><span class="key">平台类型</span><span class="val">' + ptLabel + '</span></div>');
1644
1661
  }
1645
1662
 
1646
- lines.push('<h3 style="margin:16px 0 8px">微信 iLink</h3>');
1647
- lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (state.platformsEnabled.ilink ? '已启用' : '已禁用') + '</span></div>');
1663
+ lines.push('<h3 style="margin:16px 0 8px">微信 iLink</h3>');
1664
+ lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (state.platformsEnabled.ilink ? '已启用' : '已禁用') + '</span></div>');
1648
1665
 
1649
1666
  if (!state.platformsEnabled.feishu && !state.platformsEnabled.ilink) {
1650
- lines.push('<div style="color:#ef4444;margin-top:8px">未启用任何平台</div>');
1651
- }
1652
- lines.push('<h3 style="margin:16px 0 8px">Web UI</h3>');
1653
- lines.push('<div class="config-row"><span class="key">直接启动时自动打开</span><span class="val">' + (vars.CHATCCC_WEB_UI_OPEN_ON_START ? '已启用' : '已禁用') + '</span></div>');
1654
-
1655
- lines.push('<h3 style="margin:16px 0 8px">Chrome CDP</h3>');
1667
+ lines.push('<div style="color:#ef4444;margin-top:8px">未启用任何平台</div>');
1668
+ }
1669
+ lines.push('<h3 style="margin:16px 0 8px">Web UI</h3>');
1670
+ lines.push('<div class="config-row"><span class="key">直接启动时自动打开</span><span class="val">' + (vars.CHATCCC_WEB_UI_OPEN_ON_START ? '已启用' : '已禁用') + '</span></div>');
1671
+
1672
+ lines.push('<h3 style="margin:16px 0 8px">Chrome CDP</h3>');
1656
1673
  lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (vars.CHATCCC_CHROME_DEVTOOLS_ENABLED ? '已启用' : '已禁用') + '</span></div>');
1657
1674
  if (vars.CHATCCC_CHROME_DEVTOOLS_ENABLED) {
1658
1675
  lines.push('<div class="config-row"><span class="key">CDP 端口</span><span class="val">' + (vars.CHATCCC_CHROME_DEVTOOLS_PORT || '15166') + '</span></div>');
@@ -1665,12 +1682,12 @@ function renderStep3() {
1665
1682
  var enabledList = [];
1666
1683
  if (state.agentsEnabled.claude) enabledList.push('claude');
1667
1684
  if (state.agentsEnabled.cursor) enabledList.push('cursor');
1668
- if (state.agentsEnabled.codex) enabledList.push('codex');
1669
- if (state.agentsEnabled.ccc) enabledList.push('ccc');
1685
+ if (state.agentsEnabled.codex) enabledList.push('codex');
1686
+ if (state.agentsEnabled.ccc) enabledList.push('ccc');
1670
1687
  if (enabledList.length === 0) {
1671
1688
  lines.push('<div style="color:#ef4444">未启用任何 AI Agent</div>');
1672
1689
  } else {
1673
- var defaultLabel = state.defaultAgent === 'cursor' ? 'Cursor' : state.defaultAgent === 'codex' ? 'Codex' : state.defaultAgent === 'ccc' ? 'CCC Agent' : 'Claude Code';
1690
+ var defaultLabel = state.defaultAgent === 'cursor' ? 'Cursor' : state.defaultAgent === 'codex' ? 'Codex' : state.defaultAgent === 'ccc' ? 'CCC Agent' : 'Claude Code';
1674
1691
  lines.push('<div class="config-row"><span class="key">/new 默认 Agent</span><span class="val">' + defaultLabel + '</span></div>');
1675
1692
  }
1676
1693
  enabledList.forEach(function(t){
@@ -1690,35 +1707,36 @@ function renderStep3() {
1690
1707
  if (vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE === 'onDemandUse') {
1691
1708
  lines.push('<div class="config-row"><span class="key">每月On demand use预算</span><span class="val">' + (vars.CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET || '1000') + '</span></div>');
1692
1709
  }
1693
- } else if (t === 'codex') {
1710
+ } else if (t === 'codex') {
1694
1711
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">Codex</h4>');
1695
1712
  if (vars.CHATCCC_CODEX_PATH) lines.push('<div class="config-row"><span class="key">CLI 路径</span><span class="val">' + vars.CHATCCC_CODEX_PATH + '</span></div>');
1696
1713
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CODEX_MODEL || '(留空)') + '</span></div>');
1697
- lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1698
- lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
1699
- lines.push('<div class="config-row"><span class="key">Fast 模式</span><span class="val">' + (vars.CHATCCC_CODEX_FAST_MODE ? '已启用' : '已禁用') + '</span></div>');
1700
- } else if (t === 'ccc') {
1701
- lines.push('<h4 style="margin:10px 0 4px;color:#334155">CCC Agent</h4>');
1702
- lines.push('<div class="config-row"><span class="key">API Key</span><span class="val">' + (vars.CHATCCC_CCC_API_KEY ? '***已设置***' : '(留空)') + '</span></div>');
1703
- lines.push('<div class="config-row"><span class="key">Base URL</span><span class="val">' + (vars.CHATCCC_CCC_BASE_URL || '(留空)') + '</span></div>');
1704
- lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
1705
- lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1714
+ lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1715
+ lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
1716
+ lines.push('<div class="config-row"><span class="key">Fast 模式</span><span class="val">' + (vars.CHATCCC_CODEX_FAST_MODE ? '已启用' : '已禁用') + '</span></div>');
1717
+ } else if (t === 'ccc') {
1718
+ lines.push('<h4 style="margin:10px 0 4px;color:#334155">CCC Agent</h4>');
1719
+ lines.push('<div class="config-row"><span class="key">API Key</span><span class="val">' + (vars.CHATCCC_CCC_API_KEY ? '***已设置***' : '(留空)') + '</span></div>');
1720
+ lines.push('<div class="config-row"><span class="key">Base URL</span><span class="val">' + (vars.CHATCCC_CCC_BASE_URL || '(留空)') + '</span></div>');
1721
+ lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
1722
+ lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1723
+ lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CCC_EFFORT || '(留空)') + '</span></div>');
1706
1724
  }
1707
1725
  });
1708
1726
  document.getElementById('review-content').innerHTML = lines.join('');
1709
1727
  }
1710
1728
 
1711
- async function saveConfig(vars, options) {
1712
- options = options || {};
1729
+ async function saveConfig(vars, options) {
1730
+ options = options || {};
1713
1731
  var result = await api('/api/config', 'POST', { vars: vars });
1714
- if (result.ok) {
1715
- state.config = Object.assign({}, state.config, vars);
1716
- if (!options.quiet) toastConfigApplyResult(result, '配置已保存');
1732
+ if (result.ok) {
1733
+ state.config = Object.assign({}, state.config, vars);
1734
+ if (!options.quiet) toastConfigApplyResult(result, '配置已保存');
1717
1735
  } else {
1718
1736
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1719
1737
  }
1720
- return result;
1721
- }
1738
+ return result;
1739
+ }
1722
1740
 
1723
1741
  async function setDashboardDefaultAgent(agent, enabled) {
1724
1742
  if (!enabled) {
@@ -1728,22 +1746,22 @@ async function setDashboardDefaultAgent(agent, enabled) {
1728
1746
  state.defaultAgent = agent;
1729
1747
  var vars = {
1730
1748
  CHATCCC_CLAUDE_DEFAULT_AGENT: agent === 'claude',
1731
- CHATCCC_CURSOR_DEFAULT_AGENT: agent === 'cursor',
1732
- CHATCCC_CODEX_DEFAULT_AGENT: agent === 'codex',
1733
- CHATCCC_CCC_DEFAULT_AGENT: agent === 'ccc'
1749
+ CHATCCC_CURSOR_DEFAULT_AGENT: agent === 'cursor',
1750
+ CHATCCC_CODEX_DEFAULT_AGENT: agent === 'codex',
1751
+ CHATCCC_CCC_DEFAULT_AGENT: agent === 'ccc'
1734
1752
  };
1735
1753
  var result = await api('/api/config', 'POST', { vars: vars });
1736
1754
  if (result.ok) {
1737
1755
  state.config.claude = state.config.claude || {};
1738
1756
  state.config.cursor = state.config.cursor || {};
1739
- state.config.codex = state.config.codex || {};
1740
- state.config.ccc = state.config.ccc || {};
1757
+ state.config.codex = state.config.codex || {};
1758
+ state.config.ccc = state.config.ccc || {};
1741
1759
  state.config.claude.defaultAgent = agent === 'claude';
1742
- state.config.cursor.defaultAgent = agent === 'cursor';
1743
- state.config.codex.defaultAgent = agent === 'codex';
1744
- state.config.ccc.defaultAgent = agent === 'ccc';
1745
- updateDefaultAgentToggles();
1746
- toastConfigApplyResult(result, '默认 Agent 已更新');
1760
+ state.config.cursor.defaultAgent = agent === 'cursor';
1761
+ state.config.codex.defaultAgent = agent === 'codex';
1762
+ state.config.ccc.defaultAgent = agent === 'ccc';
1763
+ updateDefaultAgentToggles();
1764
+ toastConfigApplyResult(result, '默认 Agent 已更新');
1747
1765
  } else {
1748
1766
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1749
1767
  updateDefaultAgentToggles();
@@ -1756,36 +1774,36 @@ async function saveAndStart() {
1756
1774
  toast('飞书已启用,请先填写 App ID 和 App Secret', 'error');
1757
1775
  return;
1758
1776
  }
1759
- var saved = await saveConfig(vars, { quiet: true });
1760
- if (saved.ok !== true) return;
1761
- document.getElementById('btn-save-start').disabled = true;
1762
- document.getElementById('btn-save-start').innerHTML = '<span class="spinner"></span> 应用中...';
1763
-
1764
- if (state.running && saved.restartRequired === true) {
1765
- await api('/api/restart', 'POST');
1766
- toast('配置已保存,服务正在重启…');
1767
- pollUntilRunning();
1768
- return;
1769
- }
1770
-
1771
- if (state.running && saved.mode === 'reload') {
1772
- toastConfigApplyResult(saved, '配置已保存');
1773
- setTimeout(function(){ location.reload(); }, 1000);
1774
- return;
1775
- }
1776
-
1777
- var result = await api('/api/start', 'POST');
1778
- if (result.ok) {
1779
- var msg;
1780
- if (result.mode === 'reload') {
1781
- msg = '配置已保存,后续消息/新会话已生效。';
1782
- } else if (result.mode === 'inplace') {
1783
- msg = '服务已启动! PID: ' + result.pid;
1784
- } else {
1785
- msg = '服务已启动! PID: ' + (result.pid || '?');
1786
- }
1787
- toast(msg);
1788
- setTimeout(function(){ location.reload(); }, 1500);
1777
+ var saved = await saveConfig(vars, { quiet: true });
1778
+ if (saved.ok !== true) return;
1779
+ document.getElementById('btn-save-start').disabled = true;
1780
+ document.getElementById('btn-save-start').innerHTML = '<span class="spinner"></span> 应用中...';
1781
+
1782
+ if (state.running && saved.restartRequired === true) {
1783
+ await api('/api/restart', 'POST');
1784
+ toast('配置已保存,服务正在重启…');
1785
+ pollUntilRunning();
1786
+ return;
1787
+ }
1788
+
1789
+ if (state.running && saved.mode === 'reload') {
1790
+ toastConfigApplyResult(saved, '配置已保存');
1791
+ setTimeout(function(){ location.reload(); }, 1000);
1792
+ return;
1793
+ }
1794
+
1795
+ var result = await api('/api/start', 'POST');
1796
+ if (result.ok) {
1797
+ var msg;
1798
+ if (result.mode === 'reload') {
1799
+ msg = '配置已保存,后续消息/新会话已生效。';
1800
+ } else if (result.mode === 'inplace') {
1801
+ msg = '服务已启动! PID: ' + result.pid;
1802
+ } else {
1803
+ msg = '服务已启动! PID: ' + (result.pid || '?');
1804
+ }
1805
+ toast(msg);
1806
+ setTimeout(function(){ location.reload(); }, 1500);
1789
1807
  } else {
1790
1808
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1791
1809
  document.getElementById('btn-save-start').disabled = false;
@@ -1857,10 +1875,10 @@ function updateDashboardUI() {
1857
1875
  // 只显示已启用的 Agent 卡片(按 enabled 字段;缺省时退回到"任一字段非空"兼容旧 config)
1858
1876
  var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
1859
1877
  var cursorOn = isAgentEnabled(c.cursor, CURSOR_FALLBACK_KEYS);
1860
- var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
1861
- var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
1862
- state.agentsEnabled = { claude: claudeOn, cursor: cursorOn, codex: codexOn, ccc: cccOn };
1863
- state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
1878
+ var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
1879
+ var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
1880
+ state.agentsEnabled = { claude: claudeOn, cursor: cursorOn, codex: codexOn, ccc: cccOn };
1881
+ state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
1864
1882
 
1865
1883
  // 微信 iLink 平台开关:同步复选框和标签
1866
1884
  var ilinkEnabled = c.platforms && c.platforms.ilink ? c.platforms.ilink.enabled !== false : true;
@@ -1872,14 +1890,14 @@ function updateDashboardUI() {
1872
1890
 
1873
1891
  // 微信 iLink "忘记扫码" 按钮:仅在 ilink 已启用且有已保存 token 时显示
1874
1892
  var ilinkForgetRow = document.getElementById('ilink-forget-row');
1875
- if (ilinkForgetRow) {
1876
- ilinkForgetRow.style.display = (ilinkEnabled && state.ilinkAuthExists) ? '' : 'none';
1877
- }
1878
-
1879
- var webUi = c.webUi || {};
1880
- document.getElementById('cfg-WEB_UI_OPEN_ON_START').textContent = webUi.openOnStart !== false ? '已启用' : '已禁用';
1881
-
1882
- var chromeDevtools = c.chromeDevtools || {};
1893
+ if (ilinkForgetRow) {
1894
+ ilinkForgetRow.style.display = (ilinkEnabled && state.ilinkAuthExists) ? '' : 'none';
1895
+ }
1896
+
1897
+ var webUi = c.webUi || {};
1898
+ document.getElementById('cfg-WEB_UI_OPEN_ON_START').textContent = webUi.openOnStart !== false ? '已启用' : '已禁用';
1899
+
1900
+ var chromeDevtools = c.chromeDevtools || {};
1883
1901
  var cdpPort = chromeDevtools.port || 15166;
1884
1902
  document.getElementById('cfg-CHROME_DEVTOOLS_ENABLED').textContent = chromeDevtools.enabled ? '已启用' : '已禁用';
1885
1903
  document.getElementById('cfg-CHROME_DEVTOOLS_PORT').textContent = String(cdpPort);
@@ -1891,12 +1909,12 @@ function updateDashboardUI() {
1891
1909
 
1892
1910
  document.getElementById('dash-claude').style.display = claudeOn ? '' : 'none';
1893
1911
  document.getElementById('dash-cursor').style.display = cursorOn ? '' : 'none';
1894
- document.getElementById('dash-codex').style.display = codexOn ? '' : 'none';
1895
- document.getElementById('dash-ccc').style.display = cccOn ? '' : 'none';
1912
+ document.getElementById('dash-codex').style.display = codexOn ? '' : 'none';
1913
+ document.getElementById('dash-ccc').style.display = cccOn ? '' : 'none';
1896
1914
  updateDefaultAgentToggles();
1897
- // 四个都未启用时给一个空态提示,引导用户去配置向导启用
1915
+ // 四个都未启用时给一个空态提示,引导用户去配置向导启用
1898
1916
  var emptyHint = document.getElementById('dash-no-agent-hint');
1899
- if (emptyHint) emptyHint.style.display = (!claudeOn && !cursorOn && !codexOn && !cccOn) ? '' : 'none';
1917
+ if (emptyHint) emptyHint.style.display = (!claudeOn && !cursorOn && !codexOn && !cccOn) ? '' : 'none';
1900
1918
 
1901
1919
  document.getElementById('cfg-ANTHROPIC_MODEL').textContent = (c.claude && c.claude.model) || '(留空)';
1902
1920
  document.getElementById('cfg-ANTHROPIC_SUBAGENT_MODEL').textContent = (c.claude && c.claude.subagentModel) || '(留空)';
@@ -1914,13 +1932,13 @@ function updateDashboardUI() {
1914
1932
  document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET').textContent = String((c.cursor && c.cursor.onDemandMonthlyBudget) || 1000);
1915
1933
  document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
1916
1934
  document.getElementById('cfg-CODEX_MODEL').textContent = (c.codex && c.codex.model) || '(留空)';
1917
- document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
1918
- document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
1919
- document.getElementById('cfg-CODEX_FAST_MODE').textContent = c.codex && c.codex.fastMode === true ? '已启用' : '已禁用';
1920
- document.getElementById('cfg-CCC_API_KEY').textContent = (c.ccc && c.ccc.DEEPSEEK_API_KEY) ? '***已设置***' : '(留空)';
1921
- document.getElementById('cfg-CCC_BASE_URL').textContent = (c.ccc && c.ccc.DEEPSEEK_BASE_URL) || '(留空)';
1922
- document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
1923
- document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
1935
+ document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
1936
+ document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
1937
+ document.getElementById('cfg-CODEX_FAST_MODE').textContent = c.codex && c.codex.fastMode === true ? '已启用' : '已禁用';
1938
+ document.getElementById('cfg-CCC_API_KEY').textContent = (c.ccc && c.ccc.DEEPSEEK_API_KEY) ? '***已设置***' : '(留空)';
1939
+ document.getElementById('cfg-CCC_BASE_URL').textContent = (c.ccc && c.ccc.DEEPSEEK_BASE_URL) || '(留空)';
1940
+ document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
1941
+ document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
1924
1942
  }
1925
1943
 
1926
1944
  function pollStatus() {
@@ -1968,24 +1986,24 @@ async function pollUntilRunning() {
1968
1986
  // ---- Edit Modal ----
1969
1987
  var editSectionType = null;
1970
1988
 
1971
- function editSection(section) {
1989
+ function editSection(section) {
1972
1990
  editSectionType = section;
1973
- var fields;
1974
- if (section === 'feishu') fields = FEISHU_FIELDS;
1975
- else if (section === 'webUi') fields = WEB_UI_FIELDS;
1976
- else if (section === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
1991
+ var fields;
1992
+ if (section === 'feishu') fields = FEISHU_FIELDS;
1993
+ else if (section === 'webUi') fields = WEB_UI_FIELDS;
1994
+ else if (section === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
1977
1995
  else fields = AGENT_FIELDS[section] || [];
1978
1996
 
1979
- var titleMap = { feishu: '飞书', webUi: 'Web UI', chromeDevtools: 'Chrome CDP', claude: 'Claude Agent', cursor: 'Cursor Agent', codex: 'Codex Agent', ccc: 'CCC Agent' };
1997
+ var titleMap = { feishu: '飞书', webUi: 'Web UI', chromeDevtools: 'Chrome CDP', claude: 'Claude Agent', cursor: 'Cursor Agent', codex: 'Codex Agent', ccc: 'CCC Agent' };
1980
1998
  document.getElementById('edit-modal-title').textContent = '编辑 ' + (titleMap[section] || section);
1981
1999
 
1982
- document.getElementById('edit-modal-effect').textContent = configEffectHint(section);
1983
-
1984
- var html = '';
1985
- var labelMap = {
1986
- 'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
1987
- 'CHATCCC_WEB_UI_OPEN_ON_START': '直接启动 ChatCCC 时自动打开 Web UI',
1988
- 'CHATCCC_CHROME_DEVTOOLS_ENABLED': '启用常驻 Chrome CDP(选填)',
2000
+ document.getElementById('edit-modal-effect').textContent = configEffectHint(section);
2001
+
2002
+ var html = '';
2003
+ var labelMap = {
2004
+ 'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
2005
+ 'CHATCCC_WEB_UI_OPEN_ON_START': '直接启动 ChatCCC 时自动打开 Web UI',
2006
+ 'CHATCCC_CHROME_DEVTOOLS_ENABLED': '启用常驻 Chrome CDP(选填)',
1989
2007
  'CHATCCC_CHROME_DEVTOOLS_PORT': 'CDP 端口',
1990
2008
  'CHATCCC_CHROME_DEVTOOLS_PATH': 'Chrome 路径(选填)',
1991
2009
  'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL': 'Subagent 模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
@@ -1993,14 +2011,14 @@ function editSection(section) {
1993
2011
  'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型', 'CHATCCC_CURSOR_ALTERNATIVE_MODEL': '备选模型',
1994
2012
  'CHATCCC_CURSOR_AVATAR_BATTERY_MODE': '头像电池电量',
1995
2013
  'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET': '每月On demand use预算',
1996
- 'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort',
1997
- 'CHATCCC_CODEX_FAST_MODE': 'Fast 模式',
1998
- 'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
1999
- 'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型'
2014
+ 'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort',
2015
+ 'CHATCCC_CODEX_FAST_MODE': 'Fast 模式',
2016
+ 'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
2017
+ 'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CCC_EFFORT': 'Effort'
2000
2018
  };
2001
- var hintMap = {
2002
- 'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
2003
- 'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
2019
+ var hintMap = {
2020
+ 'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
2021
+ 'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
2004
2022
  'CHATCCC_CHROME_DEVTOOLS_PORT': '默认 15166,健康检查端点为 http://127.0.0.1:15166/json/version。',
2005
2023
  'CHATCCC_CHROME_DEVTOOLS_PATH': '选填。留空时自动探测 Google Chrome。'
2006
2024
  };
@@ -2013,12 +2031,12 @@ function editSection(section) {
2013
2031
  var val = state.config[key] || '';
2014
2032
  // Also check nested config
2015
2033
  if (!val) {
2016
- if (section === 'feishu') {
2034
+ if (section === 'feishu') {
2017
2035
  if (key === 'CHATCCC_APP_ID' && state.config.feishu) val = state.config.feishu.appId || '';
2018
2036
  else if (key === 'CHATCCC_APP_SECRET' && state.config.feishu) val = state.config.feishu.appSecret || '';
2019
- } else if (section === 'webUi') {
2020
- val = !state.config.webUi || state.config.webUi.openOnStart !== false ? 'true' : 'false';
2021
- } else if (section === 'chromeDevtools' && state.config.chromeDevtools) {
2037
+ } else if (section === 'webUi') {
2038
+ val = !state.config.webUi || state.config.webUi.openOnStart !== false ? 'true' : 'false';
2039
+ } else if (section === 'chromeDevtools' && state.config.chromeDevtools) {
2022
2040
  if (key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED') val = state.config.chromeDevtools.enabled === true ? 'true' : 'false';
2023
2041
  else if (key === 'CHATCCC_CHROME_DEVTOOLS_PORT') val = state.config.chromeDevtools.port != null ? String(state.config.chromeDevtools.port) : '15166';
2024
2042
  else if (key === 'CHATCCC_CHROME_DEVTOOLS_PATH') val = state.config.chromeDevtools.chromePath || '';
@@ -2035,24 +2053,25 @@ function editSection(section) {
2035
2053
  else if (key === 'CHATCCC_CURSOR_ALTERNATIVE_MODEL') val = state.config.cursor.alternativeModel || '';
2036
2054
  else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') val = state.config.cursor.avatarBatteryMode || 'apiPercent';
2037
2055
  else if (key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET') val = (state.config.cursor.onDemandMonthlyBudget != null) ? String(state.config.cursor.onDemandMonthlyBudget) : '1000';
2038
- } else if (section === 'codex' && state.config.codex) {
2056
+ } else if (section === 'codex' && state.config.codex) {
2039
2057
  if (key === 'CHATCCC_CODEX_PATH') val = state.config.codex.path || state.config.codex.command || '';
2040
- else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
2041
- else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
2042
- else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
2043
- else if (key === 'CHATCCC_CODEX_FAST_MODE') val = state.config.codex.fastMode === true ? 'true' : 'false';
2044
- } else if (section === 'ccc' && state.config.ccc) {
2045
- if (key === 'CHATCCC_CCC_API_KEY') val = state.config.ccc.DEEPSEEK_API_KEY || '';
2046
- else if (key === 'CHATCCC_CCC_BASE_URL') val = state.config.ccc.DEEPSEEK_BASE_URL || '';
2047
- else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
2048
- else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
2049
- }
2050
- }
2051
- var isSecret = key.includes('SECRET') || key.includes('API_KEY');
2052
- if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') {
2053
- var checked = val === true || val === 'true';
2054
- var changeHandler = key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' onchange="toggleEditChromeDevtoolsFields(this.checked)"' : '';
2055
- html += '<div class="form-group"><label style="display:flex;align-items:center;gap:8px"><input type="checkbox" id="edit-' + key + '"' + (checked ? ' checked' : '') + changeHandler + '> ' + (labelMap[key] || key) + '</label>';
2058
+ else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
2059
+ else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
2060
+ else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
2061
+ else if (key === 'CHATCCC_CODEX_FAST_MODE') val = state.config.codex.fastMode === true ? 'true' : 'false';
2062
+ } else if (section === 'ccc' && state.config.ccc) {
2063
+ if (key === 'CHATCCC_CCC_API_KEY') val = state.config.ccc.DEEPSEEK_API_KEY || '';
2064
+ else if (key === 'CHATCCC_CCC_BASE_URL') val = state.config.ccc.DEEPSEEK_BASE_URL || '';
2065
+ else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
2066
+ else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
2067
+ else if (key === 'CHATCCC_CCC_EFFORT') val = state.config.ccc.effort || '';
2068
+ }
2069
+ }
2070
+ var isSecret = key.includes('SECRET') || key.includes('API_KEY');
2071
+ if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') {
2072
+ var checked = val === true || val === 'true';
2073
+ var changeHandler = key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' onchange="toggleEditChromeDevtoolsFields(this.checked)"' : '';
2074
+ html += '<div class="form-group"><label style="display:flex;align-items:center;gap:8px"><input type="checkbox" id="edit-' + key + '"' + (checked ? ' checked' : '') + changeHandler + '> ' + (labelMap[key] || key) + '</label>';
2056
2075
  if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
2057
2076
  html += '</div>';
2058
2077
  } else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') {
@@ -2106,18 +2125,18 @@ function closeEditModal() {
2106
2125
  editSectionType = null;
2107
2126
  }
2108
2127
 
2109
- async function saveEdit() {
2110
- var fields;
2111
- if (editSectionType === 'feishu') fields = FEISHU_FIELDS;
2112
- else if (editSectionType === 'webUi') fields = WEB_UI_FIELDS;
2113
- else if (editSectionType === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
2128
+ async function saveEdit() {
2129
+ var fields;
2130
+ if (editSectionType === 'feishu') fields = FEISHU_FIELDS;
2131
+ else if (editSectionType === 'webUi') fields = WEB_UI_FIELDS;
2132
+ else if (editSectionType === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
2114
2133
  else fields = AGENT_FIELDS[editSectionType] || [];
2115
2134
 
2116
2135
  var vars = {};
2117
2136
  fields.forEach(function(key){
2118
2137
  var el = document.getElementById('edit-' + key);
2119
2138
  if (!el) return;
2120
- if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
2139
+ if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
2121
2140
  else vars[key] = el.value.trim();
2122
2141
  });
2123
2142
  if (editSectionType === 'chromeDevtools' && !vars.CHATCCC_CHROME_DEVTOOLS_PORT) {
@@ -2131,16 +2150,16 @@ async function saveEdit() {
2131
2150
  var ptEl = document.getElementById('edit-CHATCCC_FEISHU_PLATFORM_TYPE');
2132
2151
  if (ptEl && ptEl.value.trim()) vars['CHATCCC_FEISHU_PLATFORM_TYPE'] = ptEl.value.trim();
2133
2152
  }
2134
- var result = await saveConfig(vars, { quiet: true });
2135
- if (result.ok !== true) return;
2136
- try {
2137
- var fresh = await api('/api/config');
2138
- state.config = fresh.vars || state.config;
2139
- } catch(e) {}
2140
- closeEditModal();
2141
- updateDashboardUI();
2142
- toastConfigApplyResult(result, '修改已保存');
2143
- }
2153
+ var result = await saveConfig(vars, { quiet: true });
2154
+ if (result.ok !== true) return;
2155
+ try {
2156
+ var fresh = await api('/api/config');
2157
+ state.config = fresh.vars || state.config;
2158
+ } catch(e) {}
2159
+ closeEditModal();
2160
+ updateDashboardUI();
2161
+ toastConfigApplyResult(result, '修改已保存');
2162
+ }
2144
2163
 
2145
2164
  // ---- Other actions ----
2146
2165
  function reconfigure() {
@@ -2173,10 +2192,10 @@ init();
2173
2192
  // Router
2174
2193
  // ---------------------------------------------------------------------------
2175
2194
 
2176
- async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
2177
- const url = req.url ?? "/";
2178
- const method = req.method ?? "GET";
2179
- const pathname = url.split("?", 1)[0];
2195
+ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
2196
+ const url = req.url ?? "/";
2197
+ const method = req.method ?? "GET";
2198
+ const pathname = url.split("?", 1)[0];
2180
2199
 
2181
2200
  if (extraApiHandler && await extraApiHandler(req, res)) return;
2182
2201
 
@@ -2189,13 +2208,13 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
2189
2208
  if (url === "/api/stop" && method === "POST") return handleStopService(req, res);
2190
2209
  if (url === "/api/restart" && method === "POST") return handleRestartService(req, res);
2191
2210
  if (url === "/api/validate" && method === "POST") return handleValidate(req, res);
2192
- if (url === "/api/ilink/forget" && method === "POST") return handleForgetIlink(req, res);
2193
-
2194
- if (method === "GET" && (pathname === "/agent-team" || pathname === "/agent-team/")) {
2195
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
2196
- res.end(AGENT_TEAM_PAGE_HTML);
2197
- return;
2198
- }
2211
+ if (url === "/api/ilink/forget" && method === "POST") return handleForgetIlink(req, res);
2212
+
2213
+ if (method === "GET" && (pathname === "/agent-team" || pathname === "/agent-team/")) {
2214
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
2215
+ res.end(AGENT_TEAM_PAGE_HTML);
2216
+ return;
2217
+ }
2199
2218
 
2200
2219
  // Serve HTML page for all other GET requests
2201
2220
  if (method === "GET") {
@@ -2220,7 +2239,7 @@ export function createUiRouter(): (req: IncomingMessage, res: ServerResponse) =>
2220
2239
  // Setup mode entry point — called from index.ts when no credentials
2221
2240
  // ---------------------------------------------------------------------------
2222
2241
 
2223
- /**
2242
+ /**
2224
2243
  * setup → service「在线切换」回调签名:
2225
2244
  * - 入参 httpServer:setup 模式当前监听的 HTTP server,会被复用为 service 的
2226
2245
  * relay server(避免 close + recreate 的端口竞态)。
@@ -2232,10 +2251,10 @@ export type SetupActivateHook = (
2232
2251
  httpServer: ReturnType<typeof createServer>,
2233
2252
  ) => Promise<{ ok: true } | { ok: false; error: string }>;
2234
2253
 
2235
- export interface StartSetupModeOptions {
2236
- onActivate?: SetupActivateHook;
2237
- /** 内部重启进入 setup 模式时设为 false,避免重复弹出系统浏览器。 */
2238
- openBrowser?: boolean;
2254
+ export interface StartSetupModeOptions {
2255
+ onActivate?: SetupActivateHook;
2256
+ /** 内部重启进入 setup 模式时设为 false,避免重复弹出系统浏览器。 */
2257
+ openBrowser?: boolean;
2239
2258
  }
2240
2259
 
2241
2260
  // setup HTTP server + onActivate 回调通过模块级变量暴露给 handleStartService。
@@ -2266,10 +2285,10 @@ export function setExtraApiHandler(handler: ExtraApiHandler): void {
2266
2285
  extraApiHandler = handler;
2267
2286
  }
2268
2287
 
2269
- export function startSetupMode(
2270
- port: number,
2271
- options: StartSetupModeOptions = {},
2272
- ): ReturnType<typeof createServer> {
2288
+ export function startSetupMode(
2289
+ port: number,
2290
+ options: StartSetupModeOptions = {},
2291
+ ): ReturnType<typeof createServer> {
2273
2292
  const router = createUiRouter();
2274
2293
  const server = createServer(router);
2275
2294
  setupHttpServer = server;
@@ -2285,25 +2304,25 @@ export function startSetupMode(
2285
2304
  process.exit(1);
2286
2305
  });
2287
2306
 
2288
- server.listen(port, "127.0.0.1", () => {
2289
- const url = buildWebUiUrl(port);
2307
+ server.listen(port, "127.0.0.1", () => {
2308
+ const url = buildWebUiUrl(port);
2290
2309
  console.log("");
2291
2310
  console.log("=".repeat(60));
2292
2311
  console.log(" ChatCCC — 首次配置向导");
2293
2312
  console.log("=".repeat(60));
2294
2313
  console.log(" 未检测到已配置的飞书凭证,已启动配置界面。");
2295
- if (options.openBrowser !== false) {
2296
- console.log(` 已启用启动时自动打开浏览器: ${url}`);
2297
- } else {
2298
- console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
2299
- }
2314
+ if (options.openBrowser !== false) {
2315
+ console.log(` 已启用启动时自动打开浏览器: ${url}`);
2316
+ } else {
2317
+ console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
2318
+ }
2300
2319
  console.log(" 若浏览器未自动弹出,请手动访问上面的地址。");
2301
2320
  console.log("");
2302
2321
  console.log(" 在向导里填好 App ID / App Secret 后点「保存并启动」,");
2303
2322
  console.log(" 服务会在当前进程内直接激活,不需要重新运行 chatccc。");
2304
2323
  console.log("=".repeat(60));
2305
2324
  console.log("");
2306
- if (options.openBrowser !== false) openWebUiInDefaultBrowser(port);
2307
- });
2308
- return server;
2309
- }
2325
+ if (options.openBrowser !== false) openWebUiInDefaultBrowser(port);
2326
+ });
2327
+ return server;
2328
+ }