chatccc 0.2.223 → 0.2.224

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,54 @@ 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
+ </fieldset>
1008
+ </div>
1009
+
1010
+ </div>
1008
1011
 
1009
1012
  <div class="btn-group" style="justify-content:space-between">
1010
1013
  <button class="btn btn-outline" onclick="goStep(1)">返回</button>
@@ -1047,16 +1050,16 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1047
1050
  <span id="dash-platform-feishu-label">已启用</span>
1048
1051
  </label>
1049
1052
  </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>
1053
+ <div class="config-row"><span class="key">App ID</span><span class="val" id="cfg-APP_ID">-</span></div>
1054
+ <div class="config-row"><span class="key">App Secret</span><span class="val" id="cfg-APP_SECRET">-</span></div>
1055
+ <div class="config-row"><span class="key">平台类型</span><span class="val" id="cfg-FEISHU_PLATFORM_TYPE">-</span></div>
1056
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:飞书开关、App ID、App Secret 或平台类型变更需要重启 ChatCCC。</div>
1057
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('feishu')">编辑</button>
1055
1058
  </div>
1056
1059
  </details>
1057
1060
 
1058
- <details class="card config-section">
1059
- <summary>微信 iLink</summary>
1061
+ <details class="card config-section">
1062
+ <summary>微信 iLink</summary>
1060
1063
  <div class="section-detail">
1061
1064
  <div class="config-row">
1062
1065
  <span class="key">状态</span>
@@ -1065,30 +1068,30 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1065
1068
  <span id="dash-platform-ilink-label">未启用</span>
1066
1069
  </label>
1067
1070
  </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">
1071
+ <div id="ilink-forget-row" style="margin-top:6px;display:none">
1072
+ <button class="btn btn-outline" style="font-size:12px" onclick="forgetIlink()">忘记扫码</button>
1073
+ <span style="font-size:11px;color:#94a3b8;margin-left:8px">清除登录状态,重启后重新扫码</span>
1074
+ </div>
1075
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:微信 iLink 开关变更需要重启 ChatCCC。</div>
1076
+ </div>
1077
+ </details>
1078
+
1079
+ <details class="card config-section">
1080
+ <summary>Web UI</summary>
1081
+ <div class="section-detail">
1082
+ <div class="config-row"><span class="key">直接启动时自动打开</span><span class="val" id="cfg-WEB_UI_OPEN_ON_START">-</span></div>
1083
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:下次直接启动生效;内部重启始终不打开。Linux 无图形桌面时会跳过打开并输出 SSH 隧道提示。</div>
1084
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('webUi')">编辑</button>
1085
+ </div>
1086
+ </details>
1087
+
1088
+ <details class="card config-section">
1086
1089
  <summary>Chrome CDP(选填)</summary>
1087
1090
  <div class="section-detail">
1088
1091
  <div class="config-row"><span class="key">状态</span><span class="val" id="cfg-CHROME_DEVTOOLS_ENABLED">-</span></div>
1089
1092
  <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
1093
  <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>
1094
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后立即应用到 Chrome CDP 守护进程。依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。</div>
1092
1095
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('chromeDevtools')">编辑</button>
1093
1096
  </div>
1094
1097
  </details>
@@ -1101,10 +1104,10 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1101
1104
  <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-ANTHROPIC_EFFORT">-</span></div>
1102
1105
  <div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-ANTHROPIC_API_KEY">-</span></div>
1103
1106
  <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>
1107
+ <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>
1108
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
1109
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1110
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
1108
1111
  </div>
1109
1112
  </details>
1110
1113
 
@@ -1115,39 +1118,39 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1115
1118
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CURSOR_MODEL">-</span></div>
1116
1119
  <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CURSOR_ALTERNATIVE_MODEL">-</span></div>
1117
1120
  <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>
1121
+ <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>
1122
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
1123
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1124
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('cursor')">编辑</button>
1122
1125
  </div>
1123
1126
  </details>
1124
1127
 
1125
- <details class="card config-section" id="dash-codex">
1128
+ <details class="card config-section" id="dash-codex">
1126
1129
  <summary>Codex Agent</summary>
1127
1130
  <div class="section-detail">
1128
1131
  <div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CODEX_PATH">-</span></div>
1129
1132
  <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>
1133
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
1134
+ <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
1135
+ <div class="config-row"><span class="key">Fast 模式</span><span class="val" id="cfg-CODEX_FAST_MODE">-</span></div>
1136
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
1137
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1138
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
1139
+ </div>
1140
+ </details>
1141
+
1142
+ <details class="card config-section" id="dash-ccc">
1143
+ <summary>CCC Agent</summary>
1144
+ <div class="section-detail">
1145
+ <div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-CCC_API_KEY">-</span></div>
1146
+ <div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-CCC_BASE_URL">-</span></div>
1147
+ <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
1148
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
1149
+ <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-ccc" onchange="setDashboardDefaultAgent('ccc', this.checked)"> 设为默认 Agent</label>
1150
+ <div class="hint" style="margin-top:6px;line-height:1.6">备选模型仅加入 /model 人工切换列表;保存后下一条消息或下个新会话生效。</div>
1151
+ <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('ccc')">编辑</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>
1151
1154
 
1152
1155
  <div class="card" id="dash-no-agent-hint" style="text-align:center;color:#94a3b8;display:none">
1153
1156
  未启用任何 AI Agent。点击下方按钮重新运行配置向导启用。
@@ -1161,8 +1164,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1161
1164
  <!-- Edit Modal -->
1162
1165
  <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
1166
  <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>
1167
+ <div id="edit-modal-effect" class="hint" style="margin-bottom:12px;line-height:1.6"></div>
1168
+ <div id="edit-modal-fields"></div>
1166
1169
  <div class="btn-group" style="justify-content:flex-end;margin-top:16px">
1167
1170
  <button class="btn btn-outline" onclick="closeEditModal()">取消</button>
1168
1171
  <button class="btn btn-primary" onclick="saveEdit()">保存</button>
@@ -1175,8 +1178,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1175
1178
  <script>
1176
1179
  let state = {
1177
1180
  view: 'loading',
1178
- // 四个 Agent 各自的启用开关;初始全 false,renderStep2() 会按已存在 config 自动打开
1179
- agentsEnabled: { claude: false, cursor: false, codex: false, ccc: false },
1181
+ // 四个 Agent 各自的启用开关;初始全 false,renderStep2() 会按已存在 config 自动打开
1182
+ agentsEnabled: { claude: false, cursor: false, codex: false, ccc: false },
1180
1183
  defaultAgent: 'claude',
1181
1184
  wizardStep: 1,
1182
1185
  config: {},
@@ -1192,39 +1195,39 @@ var step2InputBound = false;
1192
1195
  const AGENT_FIELDS = {
1193
1196
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
1194
1197
  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']
1198
+ codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
1199
+ ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL']
1197
1200
  };
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'];
1201
+ const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
1202
+ const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
1203
+ const CHROME_DEVTOOLS_FIELDS = ['CHATCCC_CHROME_DEVTOOLS_ENABLED','CHATCCC_CHROME_DEVTOOLS_PORT','CHATCCC_CHROME_DEVTOOLS_PATH'];
1201
1204
 
1202
1205
  function cursorBatteryModeLabel(value) {
1203
1206
  return value === 'onDemandUse' ? 'On demand use 金额' : 'API 使用比例';
1204
1207
  }
1205
1208
 
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) {
1209
+ function configEffectHint(section) {
1210
+ if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
1211
+ if (section === 'webUi') return '生效范围:下次直接启动生效;内部重启始终不打开。';
1212
+ if (section === 'chromeDevtools') return '生效范围:保存后立即应用到 Chrome CDP 守护进程。';
1213
+ if (section === 'claude') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1214
+ if (section === 'cursor') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1215
+ if (section === 'codex') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1216
+ return '生效范围:保存后按配置类型应用。';
1217
+ }
1218
+
1219
+ function toastConfigApplyResult(result, savedText) {
1220
+ var text = savedText || '配置已保存';
1221
+ if (result && result.mode === 'reload') {
1222
+ toast(text + ',后续消息/新会话已生效。');
1223
+ } else if (result && result.mode === 'restart-required') {
1224
+ toast(text + ',需重启服务生效。');
1225
+ } else {
1226
+ toast(text);
1227
+ }
1228
+ }
1229
+
1230
+ function onCursorBatteryModeChange(prefix, value) {
1228
1231
  var rowId = prefix === 'edit-' ? 'edit-cursor-on-demand-budget-row' : 'field-cursor-on-demand-budget-row';
1229
1232
  var row = document.getElementById(rowId);
1230
1233
  if (row) row.style.display = value === 'onDemandUse' ? '' : 'none';
@@ -1258,10 +1261,10 @@ async function onPlatformToggle(platform, enabled) {
1258
1261
  if (result.ok) {
1259
1262
  state.config.platforms = state.config.platforms || {};
1260
1263
  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 ? ' 已启用' : ' 已禁用'));
1264
+ state.config.platforms[platform].enabled = enabled;
1265
+ var label = document.getElementById('dash-platform-' + platform + '-label');
1266
+ if (label) label.textContent = enabled ? '已启用' : '未启用';
1267
+ toastConfigApplyResult(result, (platform === 'feishu' ? '飞书' : '微信 iLink') + (enabled ? ' 已启用' : ' 已禁用'));
1265
1268
  } else {
1266
1269
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1267
1270
  // 还原 toggle
@@ -1298,25 +1301,25 @@ function onAgentToggle(agent, enabled) {
1298
1301
  function firstEnabledAgent() {
1299
1302
  if (state.agentsEnabled.claude) return 'claude';
1300
1303
  if (state.agentsEnabled.cursor) return 'cursor';
1301
- if (state.agentsEnabled.codex) return 'codex';
1302
- if (state.agentsEnabled.ccc) return 'ccc';
1304
+ if (state.agentsEnabled.codex) return 'codex';
1305
+ if (state.agentsEnabled.ccc) return 'ccc';
1303
1306
  return null;
1304
1307
  }
1305
1308
 
1306
- function resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn) {
1309
+ function resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn) {
1307
1310
  if (claudeOn && c.claude && c.claude.defaultAgent === true) return 'claude';
1308
1311
  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';
1312
+ if (codexOn && c.codex && c.codex.defaultAgent === true) return 'codex';
1313
+ if (cccOn && c.ccc && c.ccc.defaultAgent === true) return 'ccc';
1311
1314
  if (claudeOn) return 'claude';
1312
1315
  if (cursorOn) return 'cursor';
1313
- if (codexOn) return 'codex';
1314
- if (cccOn) return 'ccc';
1316
+ if (codexOn) return 'codex';
1317
+ if (cccOn) return 'ccc';
1315
1318
  return 'claude';
1316
1319
  }
1317
1320
 
1318
1321
  function updateDefaultAgentToggles() {
1319
- ['claude','cursor','codex','ccc'].forEach(function(agent){
1322
+ ['claude','cursor','codex','ccc'].forEach(function(agent){
1320
1323
  var el = document.getElementById('agent-default-' + agent);
1321
1324
  if (el) {
1322
1325
  el.checked = state.defaultAgent === agent;
@@ -1347,8 +1350,8 @@ function updateStep2NextBtn() {
1347
1350
  var validCount = 0;
1348
1351
  if (state.agentsEnabled.claude) validCount++;
1349
1352
  if (state.agentsEnabled.cursor) validCount++;
1350
- if (state.agentsEnabled.codex) validCount++;
1351
- if (state.agentsEnabled.ccc) validCount++;
1353
+ if (state.agentsEnabled.codex) validCount++;
1354
+ if (state.agentsEnabled.ccc) validCount++;
1352
1355
  if (validCount > 0 && !state.defaultAgent) {
1353
1356
  state.defaultAgent = firstEnabledAgent();
1354
1357
  updateDefaultAgentToggles();
@@ -1459,11 +1462,11 @@ function renderStep1() {
1459
1462
  // 飞书凭证字段初始显隐
1460
1463
  var credFields = document.getElementById('feishu-cred-fields');
1461
1464
  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 || {};
1465
+ var ilToggle = document.getElementById('platform-enable-ilink');
1466
+ if (ilToggle) ilToggle.checked = ilinkEnabled;
1467
+ var webUiOpenOnStart = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
1468
+ if (webUiOpenOnStart) webUiOpenOnStart.checked = !c.webUi || c.webUi.openOnStart !== false;
1469
+ var cd = c.chromeDevtools || {};
1467
1470
  var cdpEnabled = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
1468
1471
  if (cdpEnabled) cdpEnabled.checked = cd.enabled === true;
1469
1472
  prefillNested('field-CHATCCC_CHROME_DEVTOOLS_PORT', cd.port || 15166);
@@ -1488,9 +1491,9 @@ function isAgentEnabled(node, keys) {
1488
1491
 
1489
1492
  var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort','maxTurn'];
1490
1493
  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'];
1494
+ var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort','fastMode'];
1495
+ // 旧版 CCC 配置没有 enabled;只有 API Key 才表示用户实际完成了配置。
1496
+ var CCC_FALLBACK_KEYS = ['DEEPSEEK_API_KEY'];
1494
1497
 
1495
1498
  function renderStep2() {
1496
1499
  var c = state.config || {};
@@ -1515,35 +1518,35 @@ function renderStep2() {
1515
1518
  var cursorBatteryModeEl = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1516
1519
  if (cursorBatteryModeEl && !cursorBatteryModeEl.value) cursorBatteryModeEl.value = 'apiPercent';
1517
1520
  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
- }
1521
+ if (c.codex) {
1522
+ prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
1523
+ prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
1524
+ prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
1525
+ prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
1526
+ }
1527
+ var codexFastModeEl = document.getElementById('field-CHATCCC_CODEX_FAST_MODE');
1528
+ if (codexFastModeEl) codexFastModeEl.checked = !!(c.codex && c.codex.fastMode === true);
1529
+ if (c.ccc) {
1530
+ prefillNested('field-CHATCCC_CCC_API_KEY', c.ccc.DEEPSEEK_API_KEY);
1531
+ prefillNested('field-CHATCCC_CCC_BASE_URL', c.ccc.DEEPSEEK_BASE_URL);
1532
+ prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
1533
+ prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
1534
+ }
1532
1535
 
1533
1536
  // 按已有 config 决定每个 Agent 默认是否开启:优先 enabled 字段,缺省时按"任一字段非空"
1534
1537
  var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
1535
1538
  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);
1539
+ var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
1540
+ var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
1541
+ state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
1539
1542
  document.getElementById('agent-enable-claude').checked = claudeOn;
1540
1543
  document.getElementById('agent-enable-cursor').checked = cursorOn;
1541
- document.getElementById('agent-enable-codex').checked = codexOn;
1542
- document.getElementById('agent-enable-ccc').checked = cccOn;
1544
+ document.getElementById('agent-enable-codex').checked = codexOn;
1545
+ document.getElementById('agent-enable-ccc').checked = cccOn;
1543
1546
  onAgentToggle('claude', claudeOn);
1544
1547
  onAgentToggle('cursor', cursorOn);
1545
- onAgentToggle('codex', codexOn);
1546
- onAgentToggle('ccc', cccOn);
1548
+ onAgentToggle('codex', codexOn);
1549
+ onAgentToggle('ccc', cccOn);
1547
1550
  updateDefaultAgentToggles();
1548
1551
 
1549
1552
  // Cursor path placeholder/hint:把已探测到的路径显示为占位
@@ -1567,7 +1570,7 @@ function renderStep2() {
1567
1570
  * 收集"待落地到 config.json 的扁平 vars"。
1568
1571
  *
1569
1572
  * - 飞书字段始终收集
1570
- * - 四个 Agent 的 enabled 状态都显式下发,让 config.json 持久化用户的最新开关偏好
1573
+ * - 四个 Agent 的 enabled 状态都显式下发,让 config.json 持久化用户的最新开关偏好
1571
1574
  * - Agent 字段仅在该 Agent 开关启用时收集;未启用的 Agent 不下发其它字段,
1572
1575
  * 服务端 deepMerge 会保留 config.json 中已有值(避免关闭开关时误清空旧配置)
1573
1576
  */
@@ -1580,11 +1583,11 @@ function collectAllFields() {
1580
1583
  // 平台类型(下拉选择框,始终发送以保持与服务端同步)
1581
1584
  var ptEl = document.getElementById('field-CHATCCC_FEISHU_PLATFORM_TYPE');
1582
1585
  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');
1586
+ vars.CHATCCC_FEISHU_ENABLED = !!state.platformsEnabled.feishu;
1587
+ vars.CHATCCC_ILINK_ENABLED = !!state.platformsEnabled.ilink;
1588
+ var webUiOpenOnStartEl = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
1589
+ vars.CHATCCC_WEB_UI_OPEN_ON_START = !webUiOpenOnStartEl || !!webUiOpenOnStartEl.checked;
1590
+ var cdpEnabledEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
1588
1591
  vars.CHATCCC_CHROME_DEVTOOLS_ENABLED = !!(cdpEnabledEl && cdpEnabledEl.checked);
1589
1592
  var cdpPortEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_PORT');
1590
1593
  vars.CHATCCC_CHROME_DEVTOOLS_PORT = (cdpPortEl && cdpPortEl.value.trim()) ? cdpPortEl.value.trim() : '15166';
@@ -1592,15 +1595,15 @@ function collectAllFields() {
1592
1595
  if (cdpPathEl && cdpPathEl.value.trim()) vars.CHATCCC_CHROME_DEVTOOLS_PATH = cdpPathEl.value.trim();
1593
1596
  vars.CHATCCC_CLAUDE_ENABLED = !!state.agentsEnabled.claude;
1594
1597
  vars.CHATCCC_CURSOR_ENABLED = !!state.agentsEnabled.cursor;
1595
- vars.CHATCCC_CODEX_ENABLED = !!state.agentsEnabled.codex;
1596
- vars.CHATCCC_CCC_ENABLED = !!state.agentsEnabled.ccc;
1598
+ vars.CHATCCC_CODEX_ENABLED = !!state.agentsEnabled.codex;
1599
+ vars.CHATCCC_CCC_ENABLED = !!state.agentsEnabled.ccc;
1597
1600
  if (!state.defaultAgent || !state.agentsEnabled[state.defaultAgent]) {
1598
1601
  state.defaultAgent = firstEnabledAgent();
1599
1602
  }
1600
1603
  vars.CHATCCC_CLAUDE_DEFAULT_AGENT = state.defaultAgent === 'claude';
1601
1604
  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';
1605
+ vars.CHATCCC_CODEX_DEFAULT_AGENT = state.defaultAgent === 'codex';
1606
+ vars.CHATCCC_CCC_DEFAULT_AGENT = state.defaultAgent === 'ccc';
1604
1607
  if (state.agentsEnabled.claude) {
1605
1608
  AGENT_FIELDS.claude.forEach(function(key){
1606
1609
  var el = document.getElementById('field-' + key);
@@ -1613,21 +1616,21 @@ function collectAllFields() {
1613
1616
  if (el && el.value.trim()) vars[key] = el.value.trim();
1614
1617
  });
1615
1618
  }
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;
1619
+ if (state.agentsEnabled.codex) {
1620
+ AGENT_FIELDS.codex.forEach(function(key){
1621
+ var el = document.getElementById('field-' + key);
1622
+ if (!el) return;
1623
+ if (key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
1624
+ else if (el.value.trim()) vars[key] = el.value.trim();
1625
+ });
1626
+ }
1627
+ if (state.agentsEnabled.ccc) {
1628
+ AGENT_FIELDS.ccc.forEach(function(key){
1629
+ var el = document.getElementById('field-' + key);
1630
+ if (el && el.value.trim()) vars[key] = el.value.trim();
1631
+ });
1632
+ }
1633
+ return vars;
1631
1634
  }
1632
1635
 
1633
1636
  function renderStep3() {
@@ -1643,16 +1646,16 @@ function renderStep3() {
1643
1646
  lines.push('<div class="config-row"><span class="key">平台类型</span><span class="val">' + ptLabel + '</span></div>');
1644
1647
  }
1645
1648
 
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>');
1649
+ lines.push('<h3 style="margin:16px 0 8px">微信 iLink</h3>');
1650
+ lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (state.platformsEnabled.ilink ? '已启用' : '已禁用') + '</span></div>');
1648
1651
 
1649
1652
  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>');
1653
+ lines.push('<div style="color:#ef4444;margin-top:8px">未启用任何平台</div>');
1654
+ }
1655
+ lines.push('<h3 style="margin:16px 0 8px">Web UI</h3>');
1656
+ lines.push('<div class="config-row"><span class="key">直接启动时自动打开</span><span class="val">' + (vars.CHATCCC_WEB_UI_OPEN_ON_START ? '已启用' : '已禁用') + '</span></div>');
1657
+
1658
+ lines.push('<h3 style="margin:16px 0 8px">Chrome CDP</h3>');
1656
1659
  lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (vars.CHATCCC_CHROME_DEVTOOLS_ENABLED ? '已启用' : '已禁用') + '</span></div>');
1657
1660
  if (vars.CHATCCC_CHROME_DEVTOOLS_ENABLED) {
1658
1661
  lines.push('<div class="config-row"><span class="key">CDP 端口</span><span class="val">' + (vars.CHATCCC_CHROME_DEVTOOLS_PORT || '15166') + '</span></div>');
@@ -1665,12 +1668,12 @@ function renderStep3() {
1665
1668
  var enabledList = [];
1666
1669
  if (state.agentsEnabled.claude) enabledList.push('claude');
1667
1670
  if (state.agentsEnabled.cursor) enabledList.push('cursor');
1668
- if (state.agentsEnabled.codex) enabledList.push('codex');
1669
- if (state.agentsEnabled.ccc) enabledList.push('ccc');
1671
+ if (state.agentsEnabled.codex) enabledList.push('codex');
1672
+ if (state.agentsEnabled.ccc) enabledList.push('ccc');
1670
1673
  if (enabledList.length === 0) {
1671
1674
  lines.push('<div style="color:#ef4444">未启用任何 AI Agent</div>');
1672
1675
  } else {
1673
- var defaultLabel = state.defaultAgent === 'cursor' ? 'Cursor' : state.defaultAgent === 'codex' ? 'Codex' : state.defaultAgent === 'ccc' ? 'CCC Agent' : 'Claude Code';
1676
+ var defaultLabel = state.defaultAgent === 'cursor' ? 'Cursor' : state.defaultAgent === 'codex' ? 'Codex' : state.defaultAgent === 'ccc' ? 'CCC Agent' : 'Claude Code';
1674
1677
  lines.push('<div class="config-row"><span class="key">/new 默认 Agent</span><span class="val">' + defaultLabel + '</span></div>');
1675
1678
  }
1676
1679
  enabledList.forEach(function(t){
@@ -1690,35 +1693,35 @@ function renderStep3() {
1690
1693
  if (vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE === 'onDemandUse') {
1691
1694
  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
1695
  }
1693
- } else if (t === 'codex') {
1696
+ } else if (t === 'codex') {
1694
1697
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">Codex</h4>');
1695
1698
  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
1699
  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>');
1700
+ lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1701
+ lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
1702
+ lines.push('<div class="config-row"><span class="key">Fast 模式</span><span class="val">' + (vars.CHATCCC_CODEX_FAST_MODE ? '已启用' : '已禁用') + '</span></div>');
1703
+ } else if (t === 'ccc') {
1704
+ lines.push('<h4 style="margin:10px 0 4px;color:#334155">CCC Agent</h4>');
1705
+ lines.push('<div class="config-row"><span class="key">API Key</span><span class="val">' + (vars.CHATCCC_CCC_API_KEY ? '***已设置***' : '(留空)') + '</span></div>');
1706
+ lines.push('<div class="config-row"><span class="key">Base URL</span><span class="val">' + (vars.CHATCCC_CCC_BASE_URL || '(留空)') + '</span></div>');
1707
+ lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
1708
+ lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1706
1709
  }
1707
1710
  });
1708
1711
  document.getElementById('review-content').innerHTML = lines.join('');
1709
1712
  }
1710
1713
 
1711
- async function saveConfig(vars, options) {
1712
- options = options || {};
1714
+ async function saveConfig(vars, options) {
1715
+ options = options || {};
1713
1716
  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, '配置已保存');
1717
+ if (result.ok) {
1718
+ state.config = Object.assign({}, state.config, vars);
1719
+ if (!options.quiet) toastConfigApplyResult(result, '配置已保存');
1717
1720
  } else {
1718
1721
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1719
1722
  }
1720
- return result;
1721
- }
1723
+ return result;
1724
+ }
1722
1725
 
1723
1726
  async function setDashboardDefaultAgent(agent, enabled) {
1724
1727
  if (!enabled) {
@@ -1728,22 +1731,22 @@ async function setDashboardDefaultAgent(agent, enabled) {
1728
1731
  state.defaultAgent = agent;
1729
1732
  var vars = {
1730
1733
  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'
1734
+ CHATCCC_CURSOR_DEFAULT_AGENT: agent === 'cursor',
1735
+ CHATCCC_CODEX_DEFAULT_AGENT: agent === 'codex',
1736
+ CHATCCC_CCC_DEFAULT_AGENT: agent === 'ccc'
1734
1737
  };
1735
1738
  var result = await api('/api/config', 'POST', { vars: vars });
1736
1739
  if (result.ok) {
1737
1740
  state.config.claude = state.config.claude || {};
1738
1741
  state.config.cursor = state.config.cursor || {};
1739
- state.config.codex = state.config.codex || {};
1740
- state.config.ccc = state.config.ccc || {};
1742
+ state.config.codex = state.config.codex || {};
1743
+ state.config.ccc = state.config.ccc || {};
1741
1744
  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 已更新');
1745
+ state.config.cursor.defaultAgent = agent === 'cursor';
1746
+ state.config.codex.defaultAgent = agent === 'codex';
1747
+ state.config.ccc.defaultAgent = agent === 'ccc';
1748
+ updateDefaultAgentToggles();
1749
+ toastConfigApplyResult(result, '默认 Agent 已更新');
1747
1750
  } else {
1748
1751
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1749
1752
  updateDefaultAgentToggles();
@@ -1756,36 +1759,36 @@ async function saveAndStart() {
1756
1759
  toast('飞书已启用,请先填写 App ID 和 App Secret', 'error');
1757
1760
  return;
1758
1761
  }
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);
1762
+ var saved = await saveConfig(vars, { quiet: true });
1763
+ if (saved.ok !== true) return;
1764
+ document.getElementById('btn-save-start').disabled = true;
1765
+ document.getElementById('btn-save-start').innerHTML = '<span class="spinner"></span> 应用中...';
1766
+
1767
+ if (state.running && saved.restartRequired === true) {
1768
+ await api('/api/restart', 'POST');
1769
+ toast('配置已保存,服务正在重启…');
1770
+ pollUntilRunning();
1771
+ return;
1772
+ }
1773
+
1774
+ if (state.running && saved.mode === 'reload') {
1775
+ toastConfigApplyResult(saved, '配置已保存');
1776
+ setTimeout(function(){ location.reload(); }, 1000);
1777
+ return;
1778
+ }
1779
+
1780
+ var result = await api('/api/start', 'POST');
1781
+ if (result.ok) {
1782
+ var msg;
1783
+ if (result.mode === 'reload') {
1784
+ msg = '配置已保存,后续消息/新会话已生效。';
1785
+ } else if (result.mode === 'inplace') {
1786
+ msg = '服务已启动! PID: ' + result.pid;
1787
+ } else {
1788
+ msg = '服务已启动! PID: ' + (result.pid || '?');
1789
+ }
1790
+ toast(msg);
1791
+ setTimeout(function(){ location.reload(); }, 1500);
1789
1792
  } else {
1790
1793
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1791
1794
  document.getElementById('btn-save-start').disabled = false;
@@ -1857,10 +1860,10 @@ function updateDashboardUI() {
1857
1860
  // 只显示已启用的 Agent 卡片(按 enabled 字段;缺省时退回到"任一字段非空"兼容旧 config)
1858
1861
  var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
1859
1862
  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);
1863
+ var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
1864
+ var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
1865
+ state.agentsEnabled = { claude: claudeOn, cursor: cursorOn, codex: codexOn, ccc: cccOn };
1866
+ state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
1864
1867
 
1865
1868
  // 微信 iLink 平台开关:同步复选框和标签
1866
1869
  var ilinkEnabled = c.platforms && c.platforms.ilink ? c.platforms.ilink.enabled !== false : true;
@@ -1872,14 +1875,14 @@ function updateDashboardUI() {
1872
1875
 
1873
1876
  // 微信 iLink "忘记扫码" 按钮:仅在 ilink 已启用且有已保存 token 时显示
1874
1877
  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 || {};
1878
+ if (ilinkForgetRow) {
1879
+ ilinkForgetRow.style.display = (ilinkEnabled && state.ilinkAuthExists) ? '' : 'none';
1880
+ }
1881
+
1882
+ var webUi = c.webUi || {};
1883
+ document.getElementById('cfg-WEB_UI_OPEN_ON_START').textContent = webUi.openOnStart !== false ? '已启用' : '已禁用';
1884
+
1885
+ var chromeDevtools = c.chromeDevtools || {};
1883
1886
  var cdpPort = chromeDevtools.port || 15166;
1884
1887
  document.getElementById('cfg-CHROME_DEVTOOLS_ENABLED').textContent = chromeDevtools.enabled ? '已启用' : '已禁用';
1885
1888
  document.getElementById('cfg-CHROME_DEVTOOLS_PORT').textContent = String(cdpPort);
@@ -1891,12 +1894,12 @@ function updateDashboardUI() {
1891
1894
 
1892
1895
  document.getElementById('dash-claude').style.display = claudeOn ? '' : 'none';
1893
1896
  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';
1897
+ document.getElementById('dash-codex').style.display = codexOn ? '' : 'none';
1898
+ document.getElementById('dash-ccc').style.display = cccOn ? '' : 'none';
1896
1899
  updateDefaultAgentToggles();
1897
- // 四个都未启用时给一个空态提示,引导用户去配置向导启用
1900
+ // 四个都未启用时给一个空态提示,引导用户去配置向导启用
1898
1901
  var emptyHint = document.getElementById('dash-no-agent-hint');
1899
- if (emptyHint) emptyHint.style.display = (!claudeOn && !cursorOn && !codexOn && !cccOn) ? '' : 'none';
1902
+ if (emptyHint) emptyHint.style.display = (!claudeOn && !cursorOn && !codexOn && !cccOn) ? '' : 'none';
1900
1903
 
1901
1904
  document.getElementById('cfg-ANTHROPIC_MODEL').textContent = (c.claude && c.claude.model) || '(留空)';
1902
1905
  document.getElementById('cfg-ANTHROPIC_SUBAGENT_MODEL').textContent = (c.claude && c.claude.subagentModel) || '(留空)';
@@ -1914,13 +1917,13 @@ function updateDashboardUI() {
1914
1917
  document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET').textContent = String((c.cursor && c.cursor.onDemandMonthlyBudget) || 1000);
1915
1918
  document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
1916
1919
  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) || '(留空)';
1920
+ document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
1921
+ document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
1922
+ document.getElementById('cfg-CODEX_FAST_MODE').textContent = c.codex && c.codex.fastMode === true ? '已启用' : '已禁用';
1923
+ document.getElementById('cfg-CCC_API_KEY').textContent = (c.ccc && c.ccc.DEEPSEEK_API_KEY) ? '***已设置***' : '(留空)';
1924
+ document.getElementById('cfg-CCC_BASE_URL').textContent = (c.ccc && c.ccc.DEEPSEEK_BASE_URL) || '(留空)';
1925
+ document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
1926
+ document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
1924
1927
  }
1925
1928
 
1926
1929
  function pollStatus() {
@@ -1968,24 +1971,24 @@ async function pollUntilRunning() {
1968
1971
  // ---- Edit Modal ----
1969
1972
  var editSectionType = null;
1970
1973
 
1971
- function editSection(section) {
1974
+ function editSection(section) {
1972
1975
  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;
1976
+ var fields;
1977
+ if (section === 'feishu') fields = FEISHU_FIELDS;
1978
+ else if (section === 'webUi') fields = WEB_UI_FIELDS;
1979
+ else if (section === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
1977
1980
  else fields = AGENT_FIELDS[section] || [];
1978
1981
 
1979
- var titleMap = { feishu: '飞书', webUi: 'Web UI', chromeDevtools: 'Chrome CDP', claude: 'Claude Agent', cursor: 'Cursor Agent', codex: 'Codex Agent', ccc: 'CCC Agent' };
1982
+ var titleMap = { feishu: '飞书', webUi: 'Web UI', chromeDevtools: 'Chrome CDP', claude: 'Claude Agent', cursor: 'Cursor Agent', codex: 'Codex Agent', ccc: 'CCC Agent' };
1980
1983
  document.getElementById('edit-modal-title').textContent = '编辑 ' + (titleMap[section] || section);
1981
1984
 
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(选填)',
1985
+ document.getElementById('edit-modal-effect').textContent = configEffectHint(section);
1986
+
1987
+ var html = '';
1988
+ var labelMap = {
1989
+ 'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
1990
+ 'CHATCCC_WEB_UI_OPEN_ON_START': '直接启动 ChatCCC 时自动打开 Web UI',
1991
+ 'CHATCCC_CHROME_DEVTOOLS_ENABLED': '启用常驻 Chrome CDP(选填)',
1989
1992
  'CHATCCC_CHROME_DEVTOOLS_PORT': 'CDP 端口',
1990
1993
  'CHATCCC_CHROME_DEVTOOLS_PATH': 'Chrome 路径(选填)',
1991
1994
  'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL': 'Subagent 模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
@@ -1993,14 +1996,14 @@ function editSection(section) {
1993
1996
  'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型', 'CHATCCC_CURSOR_ALTERNATIVE_MODEL': '备选模型',
1994
1997
  'CHATCCC_CURSOR_AVATAR_BATTERY_MODE': '头像电池电量',
1995
1998
  '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': '备选模型'
1999
+ 'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort',
2000
+ 'CHATCCC_CODEX_FAST_MODE': 'Fast 模式',
2001
+ 'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
2002
+ 'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型'
2000
2003
  };
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。',
2004
+ var hintMap = {
2005
+ 'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
2006
+ 'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
2004
2007
  'CHATCCC_CHROME_DEVTOOLS_PORT': '默认 15166,健康检查端点为 http://127.0.0.1:15166/json/version。',
2005
2008
  'CHATCCC_CHROME_DEVTOOLS_PATH': '选填。留空时自动探测 Google Chrome。'
2006
2009
  };
@@ -2013,12 +2016,12 @@ function editSection(section) {
2013
2016
  var val = state.config[key] || '';
2014
2017
  // Also check nested config
2015
2018
  if (!val) {
2016
- if (section === 'feishu') {
2019
+ if (section === 'feishu') {
2017
2020
  if (key === 'CHATCCC_APP_ID' && state.config.feishu) val = state.config.feishu.appId || '';
2018
2021
  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) {
2022
+ } else if (section === 'webUi') {
2023
+ val = !state.config.webUi || state.config.webUi.openOnStart !== false ? 'true' : 'false';
2024
+ } else if (section === 'chromeDevtools' && state.config.chromeDevtools) {
2022
2025
  if (key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED') val = state.config.chromeDevtools.enabled === true ? 'true' : 'false';
2023
2026
  else if (key === 'CHATCCC_CHROME_DEVTOOLS_PORT') val = state.config.chromeDevtools.port != null ? String(state.config.chromeDevtools.port) : '15166';
2024
2027
  else if (key === 'CHATCCC_CHROME_DEVTOOLS_PATH') val = state.config.chromeDevtools.chromePath || '';
@@ -2035,24 +2038,24 @@ function editSection(section) {
2035
2038
  else if (key === 'CHATCCC_CURSOR_ALTERNATIVE_MODEL') val = state.config.cursor.alternativeModel || '';
2036
2039
  else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') val = state.config.cursor.avatarBatteryMode || 'apiPercent';
2037
2040
  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) {
2041
+ } else if (section === 'codex' && state.config.codex) {
2039
2042
  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>';
2043
+ else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
2044
+ else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
2045
+ else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
2046
+ else if (key === 'CHATCCC_CODEX_FAST_MODE') val = state.config.codex.fastMode === true ? 'true' : 'false';
2047
+ } else if (section === 'ccc' && state.config.ccc) {
2048
+ if (key === 'CHATCCC_CCC_API_KEY') val = state.config.ccc.DEEPSEEK_API_KEY || '';
2049
+ else if (key === 'CHATCCC_CCC_BASE_URL') val = state.config.ccc.DEEPSEEK_BASE_URL || '';
2050
+ else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
2051
+ else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
2052
+ }
2053
+ }
2054
+ var isSecret = key.includes('SECRET') || key.includes('API_KEY');
2055
+ if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') {
2056
+ var checked = val === true || val === 'true';
2057
+ var changeHandler = key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' onchange="toggleEditChromeDevtoolsFields(this.checked)"' : '';
2058
+ 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
2059
  if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
2057
2060
  html += '</div>';
2058
2061
  } else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') {
@@ -2106,18 +2109,18 @@ function closeEditModal() {
2106
2109
  editSectionType = null;
2107
2110
  }
2108
2111
 
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;
2112
+ async function saveEdit() {
2113
+ var fields;
2114
+ if (editSectionType === 'feishu') fields = FEISHU_FIELDS;
2115
+ else if (editSectionType === 'webUi') fields = WEB_UI_FIELDS;
2116
+ else if (editSectionType === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
2114
2117
  else fields = AGENT_FIELDS[editSectionType] || [];
2115
2118
 
2116
2119
  var vars = {};
2117
2120
  fields.forEach(function(key){
2118
2121
  var el = document.getElementById('edit-' + key);
2119
2122
  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;
2123
+ if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
2121
2124
  else vars[key] = el.value.trim();
2122
2125
  });
2123
2126
  if (editSectionType === 'chromeDevtools' && !vars.CHATCCC_CHROME_DEVTOOLS_PORT) {
@@ -2131,16 +2134,16 @@ async function saveEdit() {
2131
2134
  var ptEl = document.getElementById('edit-CHATCCC_FEISHU_PLATFORM_TYPE');
2132
2135
  if (ptEl && ptEl.value.trim()) vars['CHATCCC_FEISHU_PLATFORM_TYPE'] = ptEl.value.trim();
2133
2136
  }
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
- }
2137
+ var result = await saveConfig(vars, { quiet: true });
2138
+ if (result.ok !== true) return;
2139
+ try {
2140
+ var fresh = await api('/api/config');
2141
+ state.config = fresh.vars || state.config;
2142
+ } catch(e) {}
2143
+ closeEditModal();
2144
+ updateDashboardUI();
2145
+ toastConfigApplyResult(result, '修改已保存');
2146
+ }
2144
2147
 
2145
2148
  // ---- Other actions ----
2146
2149
  function reconfigure() {
@@ -2173,10 +2176,10 @@ init();
2173
2176
  // Router
2174
2177
  // ---------------------------------------------------------------------------
2175
2178
 
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];
2179
+ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
2180
+ const url = req.url ?? "/";
2181
+ const method = req.method ?? "GET";
2182
+ const pathname = url.split("?", 1)[0];
2180
2183
 
2181
2184
  if (extraApiHandler && await extraApiHandler(req, res)) return;
2182
2185
 
@@ -2189,13 +2192,13 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
2189
2192
  if (url === "/api/stop" && method === "POST") return handleStopService(req, res);
2190
2193
  if (url === "/api/restart" && method === "POST") return handleRestartService(req, res);
2191
2194
  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
- }
2195
+ if (url === "/api/ilink/forget" && method === "POST") return handleForgetIlink(req, res);
2196
+
2197
+ if (method === "GET" && (pathname === "/agent-team" || pathname === "/agent-team/")) {
2198
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
2199
+ res.end(AGENT_TEAM_PAGE_HTML);
2200
+ return;
2201
+ }
2199
2202
 
2200
2203
  // Serve HTML page for all other GET requests
2201
2204
  if (method === "GET") {
@@ -2220,7 +2223,7 @@ export function createUiRouter(): (req: IncomingMessage, res: ServerResponse) =>
2220
2223
  // Setup mode entry point — called from index.ts when no credentials
2221
2224
  // ---------------------------------------------------------------------------
2222
2225
 
2223
- /**
2226
+ /**
2224
2227
  * setup → service「在线切换」回调签名:
2225
2228
  * - 入参 httpServer:setup 模式当前监听的 HTTP server,会被复用为 service 的
2226
2229
  * relay server(避免 close + recreate 的端口竞态)。
@@ -2232,10 +2235,10 @@ export type SetupActivateHook = (
2232
2235
  httpServer: ReturnType<typeof createServer>,
2233
2236
  ) => Promise<{ ok: true } | { ok: false; error: string }>;
2234
2237
 
2235
- export interface StartSetupModeOptions {
2236
- onActivate?: SetupActivateHook;
2237
- /** 内部重启进入 setup 模式时设为 false,避免重复弹出系统浏览器。 */
2238
- openBrowser?: boolean;
2238
+ export interface StartSetupModeOptions {
2239
+ onActivate?: SetupActivateHook;
2240
+ /** 内部重启进入 setup 模式时设为 false,避免重复弹出系统浏览器。 */
2241
+ openBrowser?: boolean;
2239
2242
  }
2240
2243
 
2241
2244
  // setup HTTP server + onActivate 回调通过模块级变量暴露给 handleStartService。
@@ -2266,10 +2269,10 @@ export function setExtraApiHandler(handler: ExtraApiHandler): void {
2266
2269
  extraApiHandler = handler;
2267
2270
  }
2268
2271
 
2269
- export function startSetupMode(
2270
- port: number,
2271
- options: StartSetupModeOptions = {},
2272
- ): ReturnType<typeof createServer> {
2272
+ export function startSetupMode(
2273
+ port: number,
2274
+ options: StartSetupModeOptions = {},
2275
+ ): ReturnType<typeof createServer> {
2273
2276
  const router = createUiRouter();
2274
2277
  const server = createServer(router);
2275
2278
  setupHttpServer = server;
@@ -2285,25 +2288,25 @@ export function startSetupMode(
2285
2288
  process.exit(1);
2286
2289
  });
2287
2290
 
2288
- server.listen(port, "127.0.0.1", () => {
2289
- const url = buildWebUiUrl(port);
2291
+ server.listen(port, "127.0.0.1", () => {
2292
+ const url = buildWebUiUrl(port);
2290
2293
  console.log("");
2291
2294
  console.log("=".repeat(60));
2292
2295
  console.log(" ChatCCC — 首次配置向导");
2293
2296
  console.log("=".repeat(60));
2294
2297
  console.log(" 未检测到已配置的飞书凭证,已启动配置界面。");
2295
- if (options.openBrowser !== false) {
2296
- console.log(` 已启用启动时自动打开浏览器: ${url}`);
2297
- } else {
2298
- console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
2299
- }
2298
+ if (options.openBrowser !== false) {
2299
+ console.log(` 已启用启动时自动打开浏览器: ${url}`);
2300
+ } else {
2301
+ console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
2302
+ }
2300
2303
  console.log(" 若浏览器未自动弹出,请手动访问上面的地址。");
2301
2304
  console.log("");
2302
2305
  console.log(" 在向导里填好 App ID / App Secret 后点「保存并启动」,");
2303
2306
  console.log(" 服务会在当前进程内直接激活,不需要重新运行 chatccc。");
2304
2307
  console.log("=".repeat(60));
2305
2308
  console.log("");
2306
- if (options.openBrowser !== false) openWebUiInDefaultBrowser(port);
2307
- });
2308
- return server;
2309
- }
2309
+ if (options.openBrowser !== false) openWebUiInDefaultBrowser(port);
2310
+ });
2311
+ return server;
2312
+ }