chatccc 0.2.214 → 0.2.216

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.
@@ -39,6 +39,7 @@ import {
39
39
  import {
40
40
  buildHelpCard,
41
41
  buildEffortCard,
42
+ buildFastModeCard,
42
43
  buildModelCard,
43
44
  buildStatusCard,
44
45
  buildCdContent,
@@ -69,6 +70,8 @@ import {
69
70
  getAdapterForTool,
70
71
  getEffectiveModelForTool,
71
72
  getEffectiveEffortForTool,
73
+ getEffectiveFastModeForTool,
74
+ setSessionFastModeOverride,
72
75
  stopSession,
73
76
  loadSessionRegistryForBinding,
74
77
  removeSessionRegistryRecord,
@@ -325,11 +328,33 @@ function formatCursorUsageSummary(usage: CursorUsageSummary): string {
325
328
  ].filter(Boolean).join("\n");
326
329
  }
327
330
 
328
- function usageHelpLine(tool: string): string {
331
+ function usageHelpLine(tool: string): string {
329
332
  if (tool === "codex") return "\n发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。";
330
333
  if (tool === "cursor") return "\n发送 **/usage** 查看 Cursor 用量。";
331
- return "";
332
- }
334
+ return "";
335
+ }
336
+
337
+ function fastHelpAfterModel(tool: string): string {
338
+ return tool === "codex"
339
+ ? "\n发送 **/fast** 查看或切换当前会话的 Fast 模式。"
340
+ : "";
341
+ }
342
+
343
+ async function sendFastModeStatus(
344
+ platform: PlatformAdapter,
345
+ chatId: string,
346
+ enabled: boolean,
347
+ ): Promise<void> {
348
+ if (platform.kind === "wechat") {
349
+ const mode = enabled ? "ON (Fast)" : "OFF (Standard)";
350
+ await platform.sendText(
351
+ chatId,
352
+ `Codex Fast 模式: ${mode}\n输入 /fast on 或 /fast off 切换。切换将在下一条消息生效,当前生成不中断。`,
353
+ );
354
+ return;
355
+ }
356
+ await platform.sendRawCard(chatId, buildFastModeCard(enabled));
357
+ }
333
358
 
334
359
  async function resolveUsageTarget(chatId: string): Promise<{ tool: "codex" | "cursor"; sessionId?: string }> {
335
360
  try {
@@ -1021,7 +1046,7 @@ export async function handleCommand(
1021
1046
  `**工作目录:** \`${cwd}\`\n\n` +
1022
1047
  `直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
1023
1048
  `发送 **/cd** 切换新建会话的默认目录。\n` +
1024
- `发送 **/model** 查看或切换当前会话的模型。\n` +
1049
+ `发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(tool)}\n` +
1025
1050
  `发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
1026
1051
  `发送 **/sessions** 查看所有会话状态。\n` +
1027
1052
  `发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
@@ -1109,7 +1134,7 @@ export async function handleCommand(
1109
1134
  `**工作目录:** \`${cwd}\`\n\n` +
1110
1135
  `直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
1111
1136
  `发送 **/cd** 切换新建会话的默认目录。\n` +
1112
- `发送 **/model** 查看或切换当前会话的模型。\n` +
1137
+ `发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(tool)}\n` +
1113
1138
  `发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
1114
1139
  `发送 **/sessions** 查看所有会话状态。\n` +
1115
1140
  `发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
@@ -1531,7 +1556,7 @@ export async function handleCommand(
1531
1556
  `**工作目录:** \`${cwd}\`${isFeishuP2p(platform, chatType) ? "(飞书私聊固定使用系统用户目录)" : "(沿用当前会话目录)"}\n\n` +
1532
1557
  `直接在这里发消息即可继续对话。\n` +
1533
1558
  `发送 **/cd** 可切换新建会话的默认目录。\n` +
1534
- `发送 **/model** 查看或切换当前会话的模型。`,
1559
+ `发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(descriptionTool)}`,
1535
1560
  "green",
1536
1561
  );
1537
1562
 
@@ -1705,7 +1730,7 @@ export async function handleCommand(
1705
1730
  `**Session ID:** ${target.sessionId}\n` +
1706
1731
  `**工作目录:** \`${cwd2}\`\n\n` +
1707
1732
  `直接在这里发消息即可继续对话。\n` +
1708
- `发送 **/model** 查看或切换当前会话的模型。${busyNote}`,
1733
+ `发送 **/model** 查看或切换当前会话的模型。${fastHelpAfterModel(descriptionTool)}${busyNote}`,
1709
1734
  "green",
1710
1735
  );
1711
1736
 
@@ -1718,7 +1743,44 @@ export async function handleCommand(
1718
1743
  return;
1719
1744
  }
1720
1745
 
1721
- // /model clear 清除当前 session 的模型覆盖
1746
+ if (isCommandText && (textLower === "/fast" || textLower.startsWith("/fast "))) {
1747
+ const fastArg = text.slice(5).trim().toLowerCase();
1748
+ logTrace(tid, "BRANCH", { cmd: "/fast", arg: fastArg, sessionId, tool: descriptionTool });
1749
+
1750
+ if (descriptionTool !== "codex") {
1751
+ const msg = `当前 ${toolLabel} 会话不支持 Fast 模式;/fast 仅适用于 Codex。`;
1752
+ await (platform.kind === "wechat"
1753
+ ? platform.sendText(chatId, msg)
1754
+ : platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
1755
+ ).catch(() => {});
1756
+ logTrace(tid, "DONE", { outcome: "fast_unsupported", tool: descriptionTool });
1757
+ return;
1758
+ }
1759
+
1760
+ if (fastArg && fastArg !== "on" && fastArg !== "off") {
1761
+ const msg = "用法: /fast、/fast on 或 /fast off";
1762
+ await (platform.kind === "wechat"
1763
+ ? platform.sendText(chatId, msg)
1764
+ : platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
1765
+ ).catch(() => {});
1766
+ logTrace(tid, "DONE", { outcome: "fast_invalid", arg: fastArg });
1767
+ return;
1768
+ }
1769
+
1770
+ if (fastArg) {
1771
+ setSessionFastModeOverride(sessionId, fastArg === "on");
1772
+ }
1773
+ const enabled = getEffectiveFastModeForTool("codex", sessionId);
1774
+ await sendFastModeStatus(platform, chatId, enabled).catch(() => {});
1775
+ logTrace(tid, "DONE", {
1776
+ outcome: fastArg ? "fast_switched" : "fast_query",
1777
+ enabled,
1778
+ sessionId,
1779
+ });
1780
+ return;
1781
+ }
1782
+
1783
+ // /model clear — 清除当前 session 的模型覆盖
1722
1784
  if (isCommandText && textLower === "/model clear") {
1723
1785
  logTrace(tid, "BRANCH", { cmd: "/model clear", sessionId });
1724
1786
  clearSessionModelOverride(sessionId);
@@ -1776,12 +1838,15 @@ export async function handleCommand(
1776
1838
  if (platform.kind === "wechat") {
1777
1839
  const lines = [currentModel ? `当前模型 (${toolLabel}): ${currentModel}` : `当前模型 (${toolLabel}): 未指定`];
1778
1840
  if (models.length > 0) {
1779
- lines.push("", "可切换模型:");
1780
- for (const m of models) lines.push(` ${m}`);
1781
- lines.push("", "输入 /model <模型名> 切换模型");
1782
- } else {
1783
- lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
1784
- }
1841
+ lines.push("", "可切换模型:");
1842
+ for (const m of models) lines.push(` ${m}`);
1843
+ lines.push("", "输入 /model <模型名> 切换模型");
1844
+ } else {
1845
+ lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
1846
+ }
1847
+ if (descriptionTool === "codex") {
1848
+ lines.push("输入 /fast 查看或切换当前会话的 Fast 模式");
1849
+ }
1785
1850
  await platform.sendText(chatId, lines.join("\n")).catch(() => {});
1786
1851
  } else {
1787
1852
  const card = buildModelCard(currentModel, models, descriptionTool);
@@ -2022,7 +2087,34 @@ export async function handleCommand(
2022
2087
  return;
2023
2088
  }
2024
2089
 
2025
- // 无会话上下文 检查是否是 /model 查询
2090
+ if (isCommandText && (textLower === "/fast" || textLower.startsWith("/fast "))) {
2091
+ const defaultTool = resolveDefaultAgentTool();
2092
+ const fastArg = text.slice(5).trim().toLowerCase();
2093
+ if (defaultTool !== "codex") {
2094
+ const msg = `当前默认 Agent (${toolDisplayName(defaultTool)}) 不支持 Fast 模式;/fast 仅适用于 Codex。`;
2095
+ await (platform.kind === "wechat"
2096
+ ? platform.sendText(chatId, msg)
2097
+ : platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
2098
+ ).catch(() => {});
2099
+ logTrace(tid, "DONE", { outcome: "fast_unsupported", defaultTool });
2100
+ return;
2101
+ }
2102
+ if (fastArg) {
2103
+ const msg = "当前没有绑定 Codex 会话,无法设置会话覆盖。请先创建或进入 Codex 会话;全局默认值可在 Web UI 中设置。";
2104
+ await (platform.kind === "wechat"
2105
+ ? platform.sendText(chatId, msg)
2106
+ : platform.sendCard(chatId, "Codex Fast 模式", msg, "yellow")
2107
+ ).catch(() => {});
2108
+ logTrace(tid, "DONE", { outcome: "fast_no_session", arg: fastArg });
2109
+ return;
2110
+ }
2111
+ const enabled = getEffectiveFastModeForTool("codex");
2112
+ await sendFastModeStatus(platform, chatId, enabled).catch(() => {});
2113
+ logTrace(tid, "DONE", { outcome: "fast_query", enabled, defaultTool });
2114
+ return;
2115
+ }
2116
+
2117
+ // 无会话上下文 → 检查是否是 /model 查询
2026
2118
  if (isCommandText && textLower === "/model") {
2027
2119
  const defaultTool = resolveDefaultAgentTool();
2028
2120
  const models = getAllModelsForTool(defaultTool);
@@ -2034,12 +2126,15 @@ export async function handleCommand(
2034
2126
  if (platform.kind === "wechat") {
2035
2127
  const lines = [currentModel ? `当前模型 (${defaultTool}): ${currentModel}` : `当前模型 (${defaultTool}): 未指定`];
2036
2128
  if (models.length > 0) {
2037
- lines.push("", "可切换模型:");
2038
- for (const m of models) lines.push(` ${m}`);
2039
- lines.push("", "在会话中输入 /model <模型名> 切换模型");
2040
- } else {
2041
- lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
2042
- }
2129
+ lines.push("", "可切换模型:");
2130
+ for (const m of models) lines.push(` ${m}`);
2131
+ lines.push("", "在会话中输入 /model <模型名> 切换模型");
2132
+ } else {
2133
+ lines.push("", "没有可切换的模型。请在 config.json 中配置模型字段。");
2134
+ }
2135
+ if (defaultTool === "codex") {
2136
+ lines.push("输入 /fast 查看当前 Codex Fast 模式");
2137
+ }
2043
2138
  await platform.sendText(chatId, lines.join("\n")).catch(() => {});
2044
2139
  } else {
2045
2140
  const card = buildModelCard(currentModel, models, defaultTool);
package/src/session.ts CHANGED
@@ -42,6 +42,13 @@ import { resourceMonitor, registerProcess, unregisterProcess } from "./adapters/
42
42
  import { buildImSkillsPromptCached, exportSkillSubDocs, clearImSkillsPromptCache } from "./im-skills.ts";
43
43
  import type { PlatformAdapter } from "./platform-adapter.ts";
44
44
  import { hasResponseStalled, observeResponseProgress } from "./response-stall.ts";
45
+ import {
46
+ MAX_PROCESSED,
47
+ clearFeishuMessageLedgerMemory,
48
+ processedMessages,
49
+ } from "./feishu-message-ingress.ts";
50
+
51
+ export { MAX_PROCESSED, processedMessages };
45
52
 
46
53
  // 微信显示循环压缩:头5 + ... + 尾5,避免在最后一步 sendText 中压缩指令回复
47
54
  function compressWechatDisplayText(text: string): string {
@@ -131,9 +138,6 @@ async function createVisibleProgressCard(
131
138
  // Shared state (imported by index.ts)
132
139
  // ---------------------------------------------------------------------------
133
140
 
134
- export const processedMessages = new Set<string>();
135
- export const MAX_PROCESSED = 5000;
136
-
137
141
  /** 每个 chatId 上一次已处理消息的时间戳,用于拦截延迟送达的旧消息 */
138
142
  export const lastMsgTimestamps = new Map<string, number>();
139
143
 
@@ -463,7 +467,7 @@ export function resetState(): void {
463
467
  }
464
468
  chatSessionMap.clear();
465
469
  sessionInfoMap.clear();
466
- processedMessages.clear();
470
+ clearFeishuMessageLedgerMemory();
467
471
  lastMsgTimestamps.clear();
468
472
  chatPlatformMap.clear();
469
473
  for (const prompt of activePrompts.values()) {
@@ -475,6 +479,7 @@ export function resetState(): void {
475
479
  displayCards.clear();
476
480
  sessionModelOverrides.clear();
477
481
  sessionEffortOverrides.clear();
482
+ sessionFastModeOverrides.clear();
478
483
  adapterCache.clear();
479
484
  stopUnifiedDisplayLoop();
480
485
  console.log(`[${ts()}] [RESET] State cleared (dedup + active sessions + bindings)`);
@@ -492,6 +497,7 @@ const adapterCache = new Map<string, ToolAdapter>();
492
497
  // Per-session 模型覆盖(/model 命令设置,不持久化)
493
498
  const sessionModelOverrides = new Map<string, string>();
494
499
  const sessionEffortOverrides = new Map<string, string>();
500
+ const sessionFastModeOverrides = new Map<string, boolean>();
495
501
 
496
502
  /** 返回 session 的生效模型:优先 per-session 覆盖,其次全局配置(Claude) */
497
503
  function getModelForSession(sessionId?: string): string {
@@ -524,6 +530,14 @@ export function getEffectiveEffortForTool(tool: string, sessionId?: string): str
524
530
  }
525
531
  return "";
526
532
  }
533
+
534
+ export function getEffectiveFastModeForTool(tool: string, sessionId?: string): boolean {
535
+ if (tool !== "codex") return false;
536
+ if (sessionId && sessionFastModeOverrides.has(sessionId)) {
537
+ return sessionFastModeOverrides.get(sessionId) === true;
538
+ }
539
+ return config.codex.fastMode;
540
+ }
527
541
 
528
542
  /** 为指定 session 设置模型覆盖(/model <name>) */
529
543
  export function setSessionModelOverride(sessionId: string, model: string): void {
@@ -547,10 +561,16 @@ export function clearSessionEffortOverride(sessionId: string): void {
547
561
  adapterCache.clear();
548
562
  }
549
563
 
564
+ export function setSessionFastModeOverride(sessionId: string, fastMode: boolean): void {
565
+ sessionFastModeOverrides.set(sessionId, fastMode);
566
+ adapterCache.clear();
567
+ }
568
+
550
569
  export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter {
551
570
  const effectiveModel = getEffectiveModelForTool(tool, sessionId);
552
571
  const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
553
- const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}`;
572
+ const effectiveFastMode = getEffectiveFastModeForTool(tool, sessionId);
573
+ const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}:${effectiveFastMode ? "fast" : "default"}`;
554
574
  const cached = adapterCache.get(cacheKey);
555
575
  if (cached) return cached;
556
576
 
@@ -558,7 +578,11 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
558
578
  if (tool === "cursor") {
559
579
  adapter = createCursorAdapter({ model: effectiveModel || undefined });
560
580
  } else if (tool === "codex") {
561
- adapter = createCodexAdapter({ model: effectiveModel || undefined, effort: effectiveEffort || undefined });
581
+ adapter = createCodexAdapter({
582
+ model: effectiveModel || undefined,
583
+ effort: effectiveEffort || undefined,
584
+ fastMode: effectiveFastMode,
585
+ });
562
586
  } else if (tool === "ccc") {
563
587
  adapter = createCccAdapter({ model: effectiveModel || undefined });
564
588
  } else {
@@ -1009,7 +1033,7 @@ function formatToolConfigForLog(tool: string, sessionModel?: string, sessionId?:
1009
1033
  const effortStr = e.trim() !== ""
1010
1034
  ? `effort=${e}`
1011
1035
  : "effort=(由 codex config.toml 决定)";
1012
- return `model=${modelStr}, ${effortStr}`;
1036
+ return `model=${modelStr}, ${effortStr}, fast=${getEffectiveFastModeForTool(tool, sessionId) ? "on" : "off"}`;
1013
1037
  }
1014
1038
  if (tool === "ccc") {
1015
1039
  const m = getEffectiveModelForTool(tool, sessionId);
@@ -2406,9 +2430,10 @@ export async function getAllSessionsStatus(): Promise<SessionsListEntry[]> {
2406
2430
  export function _setAdapterForToolForTest(tool: string, adapter: ToolAdapter): void {
2407
2431
  adapterCache.set(tool, adapter);
2408
2432
  // 同时设置当前配置模型对应的 key(getAdapterForTool 会优先 lookup 含 model 的 key)
2409
- const effective = getEffectiveModelForTool(tool);
2433
+ const effective = getEffectiveModelForTool(tool);
2410
2434
  const effort = getEffectiveEffortForTool(tool);
2411
- adapterCache.set(`${tool}:${effective || ""}:${effort || ""}`, adapter);
2435
+ const fastMode = getEffectiveFastModeForTool(tool);
2436
+ adapterCache.set(`${tool}:${effective || ""}:${effort || ""}:${fastMode ? "fast" : "default"}`, adapter);
2412
2437
  if (effective) adapterCache.set(`${tool}:${effective}`, adapter);
2413
2438
  }
2414
2439
 
package/src/web-ui.ts CHANGED
@@ -53,7 +53,7 @@ interface AppConfig {
53
53
  avatarBatteryMode?: string;
54
54
  onDemandMonthlyBudget?: number;
55
55
  };
56
- codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string };
56
+ codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string; fastMode?: boolean };
57
57
  }
58
58
 
59
59
  // ---------------------------------------------------------------------------
@@ -488,10 +488,13 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
488
488
  } else if (key === "CHATCCC_CODEX_ALTERNATIVE_MODEL") {
489
489
  result.codex = result.codex || {};
490
490
  (result.codex as Record<string, unknown>).alternativeModel = val;
491
- } else if (key === "CHATCCC_CODEX_EFFORT") {
492
- result.codex = result.codex || {};
493
- (result.codex as Record<string, unknown>).effort = val;
494
- } else if (key === "CHATCCC_CODEX_ENABLED") {
491
+ } else if (key === "CHATCCC_CODEX_EFFORT") {
492
+ result.codex = result.codex || {};
493
+ (result.codex as Record<string, unknown>).effort = val;
494
+ } else if (key === "CHATCCC_CODEX_FAST_MODE") {
495
+ result.codex = result.codex || {};
496
+ (result.codex as Record<string, unknown>).fastMode = val === true || val === "true";
497
+ } else if (key === "CHATCCC_CODEX_ENABLED") {
495
498
  result.codex = result.codex || {};
496
499
  (result.codex as Record<string, unknown>).enabled = val === true || val === "true";
497
500
  } else if (key === "CHATCCC_CODEX_DEFAULT_AGENT") {
@@ -928,11 +931,16 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
928
931
  <label>备选模型(选填)</label>
929
932
  <input type="text" id="field-CHATCCC_CODEX_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
930
933
  </div>
931
- <div class="form-group">
932
- <label>努力程度 (Effort)</label>
933
- <input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
934
- </div>
935
- <button class="btn btn-outline" onclick="validateCli('codex')" style="margin-bottom:12px">检测 Codex CLI</button>
934
+ <div class="form-group">
935
+ <label>努力程度 (Effort)</label>
936
+ <input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
937
+ </div>
938
+ <div class="form-group">
939
+ <label style="display:flex;align-items:center;gap:8px">
940
+ <input type="checkbox" id="field-CHATCCC_CODEX_FAST_MODE"> Fast 模式
941
+ </label>
942
+ </div>
943
+ <button class="btn btn-outline" onclick="validateCli('codex')" style="margin-bottom:12px">检测 Codex CLI</button>
936
944
  <div id="codex-validate-result"></div>
937
945
  </fieldset>
938
946
  </div>
@@ -1060,8 +1068,9 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1060
1068
  <div class="section-detail">
1061
1069
  <div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CODEX_PATH">-</span></div>
1062
1070
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CODEX_MODEL">-</span></div>
1063
- <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
1071
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
1064
1072
  <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
1073
+ <div class="config-row"><span class="key">Fast 模式</span><span class="val" id="cfg-CODEX_FAST_MODE">-</span></div>
1065
1074
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
1066
1075
  <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
1067
1076
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
@@ -1111,7 +1120,7 @@ var step2InputBound = false;
1111
1120
  const AGENT_FIELDS = {
1112
1121
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
1113
1122
  cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
1114
- codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT']
1123
+ codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE']
1115
1124
  };
1116
1125
  const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
1117
1126
  const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
@@ -1402,7 +1411,7 @@ function isAgentEnabled(node, keys) {
1402
1411
 
1403
1412
  var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort','maxTurn'];
1404
1413
  var CURSOR_FALLBACK_KEYS = ['path','command','model','alternativeModel'];
1405
- var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort'];
1414
+ var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort','fastMode'];
1406
1415
 
1407
1416
  function renderStep2() {
1408
1417
  var c = state.config || {};
@@ -1427,12 +1436,14 @@ function renderStep2() {
1427
1436
  var cursorBatteryModeEl = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1428
1437
  if (cursorBatteryModeEl && !cursorBatteryModeEl.value) cursorBatteryModeEl.value = 'apiPercent';
1429
1438
  onCursorBatteryModeChange('field-', cursorBatteryModeEl ? cursorBatteryModeEl.value : 'apiPercent');
1430
- if (c.codex) {
1431
- prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
1432
- prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
1433
- prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
1434
- prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
1435
- }
1439
+ if (c.codex) {
1440
+ prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
1441
+ prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
1442
+ prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
1443
+ prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
1444
+ }
1445
+ var codexFastModeEl = document.getElementById('field-CHATCCC_CODEX_FAST_MODE');
1446
+ if (codexFastModeEl) codexFastModeEl.checked = !!(c.codex && c.codex.fastMode === true);
1436
1447
 
1437
1448
  // 按已有 config 决定每个 Agent 默认是否开启:优先 enabled 字段,缺省时按"任一字段非空"
1438
1449
  var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
@@ -1512,11 +1523,13 @@ function collectAllFields() {
1512
1523
  if (el && el.value.trim()) vars[key] = el.value.trim();
1513
1524
  });
1514
1525
  }
1515
- if (state.agentsEnabled.codex) {
1516
- AGENT_FIELDS.codex.forEach(function(key){
1517
- var el = document.getElementById('field-' + key);
1518
- if (el && el.value.trim()) vars[key] = el.value.trim();
1519
- });
1526
+ if (state.agentsEnabled.codex) {
1527
+ AGENT_FIELDS.codex.forEach(function(key){
1528
+ var el = document.getElementById('field-' + key);
1529
+ if (!el) return;
1530
+ if (key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
1531
+ else if (el.value.trim()) vars[key] = el.value.trim();
1532
+ });
1520
1533
  }
1521
1534
  return vars;
1522
1535
  }
@@ -1585,8 +1598,9 @@ function renderStep3() {
1585
1598
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">Codex</h4>');
1586
1599
  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>');
1587
1600
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CODEX_MODEL || '(留空)') + '</span></div>');
1588
- lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1589
- lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
1601
+ lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1602
+ lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
1603
+ lines.push('<div class="config-row"><span class="key">Fast 模式</span><span class="val">' + (vars.CHATCCC_CODEX_FAST_MODE ? '已启用' : '已禁用') + '</span></div>');
1590
1604
  }
1591
1605
  });
1592
1606
  document.getElementById('review-content').innerHTML = lines.join('');
@@ -1793,8 +1807,9 @@ function updateDashboardUI() {
1793
1807
  document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET').textContent = String((c.cursor && c.cursor.onDemandMonthlyBudget) || 1000);
1794
1808
  document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
1795
1809
  document.getElementById('cfg-CODEX_MODEL').textContent = (c.codex && c.codex.model) || '(留空)';
1796
- document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
1797
- document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
1810
+ document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
1811
+ document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
1812
+ document.getElementById('cfg-CODEX_FAST_MODE').textContent = c.codex && c.codex.fastMode === true ? '已启用' : '已禁用';
1798
1813
  }
1799
1814
 
1800
1815
  function pollStatus() {
@@ -1867,7 +1882,8 @@ function editSection(section) {
1867
1882
  'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型', 'CHATCCC_CURSOR_ALTERNATIVE_MODEL': '备选模型',
1868
1883
  'CHATCCC_CURSOR_AVATAR_BATTERY_MODE': '头像电池电量',
1869
1884
  'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET': '每月On demand use预算',
1870
- 'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort'
1885
+ 'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort',
1886
+ 'CHATCCC_CODEX_FAST_MODE': 'Fast 模式'
1871
1887
  };
1872
1888
  var hintMap = {
1873
1889
  'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
@@ -1908,13 +1924,14 @@ function editSection(section) {
1908
1924
  else if (key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET') val = (state.config.cursor.onDemandMonthlyBudget != null) ? String(state.config.cursor.onDemandMonthlyBudget) : '1000';
1909
1925
  } else if (section === 'codex' && state.config.codex) {
1910
1926
  if (key === 'CHATCCC_CODEX_PATH') val = state.config.codex.path || state.config.codex.command || '';
1911
- else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
1912
- else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
1913
- else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
1914
- }
1915
- }
1916
- var isSecret = key.includes('SECRET') || key.includes('API_KEY');
1917
- if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED') {
1927
+ else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
1928
+ else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
1929
+ else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
1930
+ else if (key === 'CHATCCC_CODEX_FAST_MODE') val = state.config.codex.fastMode === true ? 'true' : 'false';
1931
+ }
1932
+ }
1933
+ var isSecret = key.includes('SECRET') || key.includes('API_KEY');
1934
+ if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') {
1918
1935
  var checked = val === true || val === 'true';
1919
1936
  var changeHandler = key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' onchange="toggleEditChromeDevtoolsFields(this.checked)"' : '';
1920
1937
  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>';
@@ -1982,7 +1999,7 @@ async function saveEdit() {
1982
1999
  fields.forEach(function(key){
1983
2000
  var el = document.getElementById('edit-' + key);
1984
2001
  if (!el) return;
1985
- if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED') vars[key] = !!el.checked;
2002
+ if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
1986
2003
  else vars[key] = el.value.trim();
1987
2004
  });
1988
2005
  if (editSectionType === 'chromeDevtools' && !vars.CHATCCC_CHROME_DEVTOOLS_PORT) {