chatccc 0.2.186 → 0.2.187

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.
@@ -12,30 +12,30 @@
12
12
  "port": 15166,
13
13
  "chromePath": ""
14
14
  },
15
- "port": 18080,
16
- "gitTimeoutSeconds": 180,
17
- "allowInterrupt": false,
18
- "rawStreamLogs": {
19
- "claude": {
20
- "enabled": false,
21
- "maxBytesPerTurn": 52428800,
22
- "retentionDays": 7,
23
- "keepCompleted": false
24
- },
25
- "cursor": {
26
- "enabled": false,
27
- "maxBytesPerTurn": 52428800,
28
- "retentionDays": 7,
29
- "keepCompleted": false
30
- },
31
- "codex": {
32
- "enabled": false,
33
- "maxBytesPerTurn": 52428800,
34
- "retentionDays": 7,
35
- "keepCompleted": false
36
- }
37
- },
38
- "claude": {
15
+ "port": 18080,
16
+ "gitTimeoutSeconds": 180,
17
+ "allowInterrupt": false,
18
+ "rawStreamLogs": {
19
+ "claude": {
20
+ "enabled": false,
21
+ "maxBytesPerTurn": 52428800,
22
+ "retentionDays": 7,
23
+ "keepCompleted": false
24
+ },
25
+ "cursor": {
26
+ "enabled": false,
27
+ "maxBytesPerTurn": 52428800,
28
+ "retentionDays": 7,
29
+ "keepCompleted": false
30
+ },
31
+ "codex": {
32
+ "enabled": false,
33
+ "maxBytesPerTurn": 52428800,
34
+ "retentionDays": 7,
35
+ "keepCompleted": false
36
+ }
37
+ },
38
+ "claude": {
39
39
  "enabled": false,
40
40
  "defaultAgent": true,
41
41
  "model": "",
@@ -50,6 +50,7 @@
50
50
  "defaultAgent": false,
51
51
  "path": "",
52
52
  "model": "",
53
+ "alternativeModel": "",
53
54
  "avatarBatteryMode": "apiPercent",
54
55
  "onDemandMonthlyBudget": 1000
55
56
  },
@@ -58,6 +59,7 @@
58
59
  "defaultAgent": false,
59
60
  "path": "",
60
61
  "model": "",
62
+ "alternativeModel": "",
61
63
  "effort": ""
62
64
  }
63
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.186",
3
+ "version": "0.2.187",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -22,6 +22,7 @@ import {
22
22
  ILINK_ENABLED,
23
23
  applyLoadedConfig,
24
24
  config,
25
+ getAllModelsForTool,
25
26
  resolveDefaultAgentTool,
26
27
  type AppConfig,
27
28
  } from "../config.ts";
@@ -41,15 +42,15 @@ const baseAppConfig: AppConfig = {
41
42
  feishu: { appId: "INITIAL_APP", appSecret: "INITIAL_SECRET" },
42
43
  platforms: { feishu: { enabled: true }, ilink: { enabled: true } },
43
44
  chromeDevtools: { enabled: false, port: 15166, chromePath: "" },
44
- port: 18080,
45
- gitTimeoutSeconds: 180,
46
- allowInterrupt: false,
47
- rawStreamLogs: {
48
- claude: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
49
- cursor: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
50
- codex: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
51
- },
52
- claude: {
45
+ port: 18080,
46
+ gitTimeoutSeconds: 180,
47
+ allowInterrupt: false,
48
+ rawStreamLogs: {
49
+ claude: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
50
+ cursor: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
51
+ codex: { enabled: false, maxBytesPerTurn: 52_428_800, retentionDays: 7, keepCompleted: false },
52
+ },
53
+ claude: {
53
54
  enabled: true,
54
55
  defaultAgent: true,
55
56
  model: "initial-model",
@@ -64,10 +65,11 @@ const baseAppConfig: AppConfig = {
64
65
  defaultAgent: false,
65
66
  path: "/initial/cursor",
66
67
  model: "initial-cursor-model",
68
+ alternativeModel: "initial-cursor-alt-model",
67
69
  avatarBatteryMode: "apiPercent",
68
70
  onDemandMonthlyBudget: 1000,
69
71
  },
70
- codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", effort: "initial-codex-effort" },
72
+ codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", alternativeModel: "initial-codex-alt-model", effort: "initial-codex-effort" },
71
73
  };
72
74
 
73
75
  // 把 module 状态抢救快照:每个 it 跑前重置回这个状态,避免污染相邻测试。
@@ -145,7 +147,7 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
145
147
  it("CURSOR_AGENT_ARGS 跟随 cursor.model 重新解析", () => {
146
148
  applyLoadedConfig({
147
149
  ...structuredClone(baseAppConfig),
148
- cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "claude-3.7-sonnet", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
150
+ cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "claude-3.7-sonnet", alternativeModel: "", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
149
151
  });
150
152
 
151
153
  // CURSOR_AGENT_ARGS 是 ['-p', '--force', '--approve-mcps', ..., '--model', 'claude-3.7-sonnet']
@@ -157,7 +159,7 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
157
159
  it("cursor.model 留空时 CURSOR_AGENT_ARGS 不含 --model", () => {
158
160
  applyLoadedConfig({
159
161
  ...structuredClone(baseAppConfig),
160
- cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
162
+ cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "", alternativeModel: "", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
161
163
  });
162
164
 
163
165
  expect(CURSOR_AGENT_ARGS).not.toContain("--model");
@@ -166,12 +168,23 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
166
168
  it("CURSOR_AGENT_COMMAND 优先取 config.cursor.path", () => {
167
169
  applyLoadedConfig({
168
170
  ...structuredClone(baseAppConfig),
169
- cursor: { enabled: true, defaultAgent: false, path: "C:/custom/cursor.exe", model: "", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
171
+ cursor: { enabled: true, defaultAgent: false, path: "C:/custom/cursor.exe", model: "", alternativeModel: "", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
170
172
  });
171
173
 
172
174
  expect(CURSOR_AGENT_COMMAND).toBe("C:/custom/cursor.exe");
173
175
  });
174
176
 
177
+ it("/model 候选列表包含 Cursor/Codex 的单个备选模型并保持主模型优先", () => {
178
+ const cfg = structuredClone(baseAppConfig);
179
+ cfg.cursor.model = "cursor-main";
180
+ cfg.cursor.alternativeModel = "cursor-alt";
181
+ cfg.codex.model = "codex-main";
182
+ cfg.codex.alternativeModel = "codex-main";
183
+
184
+ expect(getAllModelsForTool("cursor", cfg)).toEqual(["cursor-main", "cursor-alt"]);
185
+ expect(getAllModelsForTool("codex", cfg)).toEqual(["codex-main"]);
186
+ });
187
+
175
188
  it("不修改 CHATCCC_PORT(端口在 setup 切换时必须保持不变)", () => {
176
189
  const portBefore = CHATCCC_PORT;
177
190
  applyLoadedConfig({
@@ -191,7 +204,7 @@ describe("applyLoadedConfig — config 对象引用契约", () => {
191
204
  applyLoadedConfig({
192
205
  ...structuredClone(baseAppConfig),
193
206
  feishu: { appId: "REF_TEST_APP", appSecret: "REF_TEST_SECRET" },
194
- codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", effort: "low" },
207
+ codex: { enabled: true, defaultAgent: false, path: "/refresh/codex", model: "fresh-model", alternativeModel: "", effort: "low" },
195
208
  });
196
209
 
197
210
  // 必须是同一个引用:codex-adapter 等下游模块"直接 import config",
@@ -28,7 +28,18 @@ describe("config.sample.json", () => {
28
28
  expect(sample.claude?.subagentModel).toBe("");
29
29
  });
30
30
 
31
- it("keeps Chrome CDP guard disabled by default with port 15166", () => {
31
+ it("leaves Cursor and Codex alternative models empty by default", () => {
32
+ const configSamplePath = join(process.cwd(), "config.sample.json");
33
+ const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
34
+ cursor?: { alternativeModel?: unknown };
35
+ codex?: { alternativeModel?: unknown };
36
+ };
37
+
38
+ expect(sample.cursor?.alternativeModel).toBe("");
39
+ expect(sample.codex?.alternativeModel).toBe("");
40
+ });
41
+
42
+ it("keeps Chrome CDP guard disabled by default with port 15166", () => {
32
43
  const configSamplePath = join(process.cwd(), "config.sample.json");
33
44
  const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
34
45
  chromeDevtools?: { enabled?: unknown; port?: unknown; chromePath?: unknown };
@@ -36,25 +47,25 @@ describe("config.sample.json", () => {
36
47
 
37
48
  expect(sample.chromeDevtools?.enabled).toBe(false);
38
49
  expect(sample.chromeDevtools?.port).toBe(15166);
39
- expect(sample.chromeDevtools?.chromePath).toBe("");
40
- });
41
-
42
- it("keeps raw stream logs disabled by default for every agent", () => {
43
- const configSamplePath = join(process.cwd(), "config.sample.json");
44
- const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
45
- rawStreamLogs?: Record<string, {
46
- enabled?: unknown;
47
- maxBytesPerTurn?: unknown;
48
- retentionDays?: unknown;
49
- keepCompleted?: unknown;
50
- }>;
51
- };
52
-
53
- for (const tool of ["claude", "cursor", "codex"]) {
54
- expect(sample.rawStreamLogs?.[tool]?.enabled).toBe(false);
55
- expect(sample.rawStreamLogs?.[tool]?.maxBytesPerTurn).toBe(52_428_800);
56
- expect(sample.rawStreamLogs?.[tool]?.retentionDays).toBe(7);
57
- expect(sample.rawStreamLogs?.[tool]?.keepCompleted).toBe(false);
58
- }
59
- });
60
- });
50
+ expect(sample.chromeDevtools?.chromePath).toBe("");
51
+ });
52
+
53
+ it("keeps raw stream logs disabled by default for every agent", () => {
54
+ const configSamplePath = join(process.cwd(), "config.sample.json");
55
+ const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
56
+ rawStreamLogs?: Record<string, {
57
+ enabled?: unknown;
58
+ maxBytesPerTurn?: unknown;
59
+ retentionDays?: unknown;
60
+ keepCompleted?: unknown;
61
+ }>;
62
+ };
63
+
64
+ for (const tool of ["claude", "cursor", "codex"]) {
65
+ expect(sample.rawStreamLogs?.[tool]?.enabled).toBe(false);
66
+ expect(sample.rawStreamLogs?.[tool]?.maxBytesPerTurn).toBe(52_428_800);
67
+ expect(sample.rawStreamLogs?.[tool]?.retentionDays).toBe(7);
68
+ expect(sample.rawStreamLogs?.[tool]?.keepCompleted).toBe(false);
69
+ }
70
+ });
71
+ });
@@ -34,6 +34,22 @@ describe("unflattenConfig", () => {
34
34
  });
35
35
  });
36
36
 
37
+ it("maps Cursor and Codex alternative models into agent config", () => {
38
+ expect(
39
+ unflattenConfig({
40
+ CHATCCC_CURSOR_ALTERNATIVE_MODEL: "gpt-5.5-high",
41
+ CHATCCC_CODEX_ALTERNATIVE_MODEL: "gpt-5.3-codex",
42
+ }),
43
+ ).toEqual({
44
+ cursor: {
45
+ alternativeModel: "gpt-5.5-high",
46
+ },
47
+ codex: {
48
+ alternativeModel: "gpt-5.3-codex",
49
+ },
50
+ });
51
+ });
52
+
37
53
  it("maps Chrome CDP guard fields into chromeDevtools config", () => {
38
54
  expect(
39
55
  unflattenConfig({
@@ -57,6 +73,12 @@ describe("dashboard edit modal", () => {
57
73
  expect(PAGE_HTML).toContain("document.getElementById('edit-modal').classList.remove('hidden');");
58
74
  expect(PAGE_HTML).toContain("document.getElementById('edit-overlay').classList.remove('hidden');");
59
75
  });
76
+
77
+ it("uses plain alternative model labels for Cursor and Codex", () => {
78
+ expect(PAGE_HTML).toContain("field-CHATCCC_CURSOR_ALTERNATIVE_MODEL");
79
+ expect(PAGE_HTML).toContain("field-CHATCCC_CODEX_ALTERNATIVE_MODEL");
80
+ expect(PAGE_HTML).toContain("备选模型");
81
+ });
60
82
  });
61
83
 
62
84
  // ---------------------------------------------------------------------------
package/src/config.ts CHANGED
@@ -39,10 +39,10 @@ export const USER_DATA_DIR = join(homedir(), ".chatccc");
39
39
  export const PID_FILE = join(USER_DATA_DIR, "state", "runtime.pid");
40
40
 
41
41
  export const LOG_DIR = join(USER_DATA_DIR, "logs");
42
- export const fileLog = setupFileLogging(LOG_DIR, "index");
43
-
44
- export const CHAT_LOGS_DIR = join(USER_DATA_DIR, "state", "chat_logs");
45
- export const RAW_STREAM_LOGS_DIR = join(LOG_DIR, "raw-streams");
42
+ export const fileLog = setupFileLogging(LOG_DIR, "index");
43
+
44
+ export const CHAT_LOGS_DIR = join(USER_DATA_DIR, "state", "chat_logs");
45
+ export const RAW_STREAM_LOGS_DIR = join(LOG_DIR, "raw-streams");
46
46
 
47
47
  export async function appendChatLog(chatId: string, sender: string, text: string): Promise<void> {
48
48
  try {
@@ -82,6 +82,8 @@ export interface CursorConfig {
82
82
  /** Cursor Agent CLI 可执行文件绝对路径;留空时由运行时按 LocalAppData / PATH 兜底 */
83
83
  path: string;
84
84
  model: string;
85
+ /** /model 可切换的单个备选模型;留空则不加入候选列表 */
86
+ alternativeModel: string;
85
87
  avatarBatteryMode: CursorAvatarBatteryMode;
86
88
  onDemandMonthlyBudget: number;
87
89
  }
@@ -94,6 +96,8 @@ export interface CodexConfig {
94
96
  /** Codex CLI 可执行文件绝对路径;留空时退回到 PATH 中的 `codex` */
95
97
  path: string;
96
98
  model: string;
99
+ /** /model 可切换的单个备选模型;留空则不加入候选列表 */
100
+ alternativeModel: string;
97
101
  effort: string;
98
102
  }
99
103
 
@@ -114,41 +118,41 @@ export interface PlatformsConfig {
114
118
  ilink: PlatformConfig;
115
119
  }
116
120
 
117
- export interface ChromeDevtoolsConfig {
121
+ export interface ChromeDevtoolsConfig {
118
122
  /** 是否由 ChatCCC 守护一个常驻 Chrome CDP 实例 */
119
123
  enabled: boolean;
120
124
  /** Chrome remote debugging 端口,默认 15166 */
121
125
  port: number;
122
126
  /** Chrome 可执行文件路径;留空时按常见安装位置自动探测 */
123
- chromePath: string;
124
- }
125
-
126
- export interface RawStreamAgentLogConfig {
127
- enabled: boolean;
128
- maxBytesPerTurn: number;
129
- retentionDays: number;
130
- keepCompleted: boolean;
131
- }
132
-
133
- export interface RawStreamLogsConfig {
134
- claude: RawStreamAgentLogConfig;
135
- cursor: RawStreamAgentLogConfig;
136
- codex: RawStreamAgentLogConfig;
137
- }
138
-
139
- export interface AppConfig {
127
+ chromePath: string;
128
+ }
129
+
130
+ export interface RawStreamAgentLogConfig {
131
+ enabled: boolean;
132
+ maxBytesPerTurn: number;
133
+ retentionDays: number;
134
+ keepCompleted: boolean;
135
+ }
136
+
137
+ export interface RawStreamLogsConfig {
138
+ claude: RawStreamAgentLogConfig;
139
+ cursor: RawStreamAgentLogConfig;
140
+ codex: RawStreamAgentLogConfig;
141
+ }
142
+
143
+ export interface AppConfig {
140
144
  feishu: FeishuConfig;
141
145
  platforms: PlatformsConfig;
142
146
  chromeDevtools: ChromeDevtoolsConfig;
143
147
  port: number;
144
- gitTimeoutSeconds: number;
145
- /** 若为 false,AI 生成过程中用户发送消息不会打断,须先点「停止」再发送新消息 */
146
- allowInterrupt: boolean;
147
- rawStreamLogs: RawStreamLogsConfig;
148
- claude: ClaudeConfig;
149
- cursor: CursorConfig;
150
- codex: CodexConfig;
151
- }
148
+ gitTimeoutSeconds: number;
149
+ /** 若为 false,AI 生成过程中用户发送消息不会打断,须先点「停止」再发送新消息 */
150
+ allowInterrupt: boolean;
151
+ rawStreamLogs: RawStreamLogsConfig;
152
+ claude: ClaudeConfig;
153
+ cursor: CursorConfig;
154
+ codex: CodexConfig;
155
+ }
152
156
 
153
157
  export type AgentTool = "claude" | "cursor" | "codex";
154
158
  export const AGENT_TOOLS: AgentTool[] = ["claude", "cursor", "codex"];
@@ -166,8 +170,10 @@ export function getAllModelsForTool(tool: AgentTool, cfg: AppConfig = config): s
166
170
  collect(cfg.claude.subagentModel);
167
171
  } else if (tool === "cursor") {
168
172
  collect(cfg.cursor.model);
173
+ collect(cfg.cursor.alternativeModel);
169
174
  } else if (tool === "codex") {
170
175
  collect(cfg.codex.model);
176
+ collect(cfg.codex.alternativeModel);
171
177
  }
172
178
 
173
179
  return Array.from(seen).slice(0, 100);
@@ -358,51 +364,52 @@ function normalizeCursorAvatarBatteryMode(raw: unknown): CursorAvatarBatteryMode
358
364
  return raw === "onDemandUse" ? "onDemandUse" : "apiPercent";
359
365
  }
360
366
 
361
- function normalizeCursorOnDemandMonthlyBudget(raw: unknown): number {
362
- const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
363
- return Number.isFinite(value) && value > 0 ? value : 1000;
364
- }
365
-
366
- function normalizePositiveInteger(raw: unknown, fallback: number): number {
367
- const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
368
- return Number.isInteger(value) && value > 0 ? value : fallback;
369
- }
370
-
371
- function normalizeRawStreamAgentLogConfig(raw: unknown): RawStreamAgentLogConfig {
372
- const obj = typeof raw === "object" && raw !== null
373
- ? raw as Record<string, unknown>
374
- : {};
375
- return {
376
- enabled: typeof obj.enabled === "boolean" ? obj.enabled : false,
377
- maxBytesPerTurn: normalizePositiveInteger(obj.maxBytesPerTurn, 50 * 1024 * 1024),
378
- retentionDays: normalizePositiveInteger(obj.retentionDays, 7),
379
- keepCompleted: typeof obj.keepCompleted === "boolean" ? obj.keepCompleted : false,
380
- };
381
- }
382
-
383
- function loadConfig(): AppConfig {
384
- const defaults: AppConfig = {
385
- feishu: { appId: "", appSecret: "" },
386
- platforms: { feishu: { enabled: true }, ilink: { enabled: true } },
387
- chromeDevtools: { enabled: false, port: 15166, chromePath: "" },
388
- port: 18080,
389
- gitTimeoutSeconds: 180,
390
- allowInterrupt: false,
391
- rawStreamLogs: {
392
- claude: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
393
- cursor: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
394
- codex: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
395
- },
396
- claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "", maxTurn: 0 },
367
+ function normalizeCursorOnDemandMonthlyBudget(raw: unknown): number {
368
+ const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
369
+ return Number.isFinite(value) && value > 0 ? value : 1000;
370
+ }
371
+
372
+ function normalizePositiveInteger(raw: unknown, fallback: number): number {
373
+ const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
374
+ return Number.isInteger(value) && value > 0 ? value : fallback;
375
+ }
376
+
377
+ function normalizeRawStreamAgentLogConfig(raw: unknown): RawStreamAgentLogConfig {
378
+ const obj = typeof raw === "object" && raw !== null
379
+ ? raw as Record<string, unknown>
380
+ : {};
381
+ return {
382
+ enabled: typeof obj.enabled === "boolean" ? obj.enabled : false,
383
+ maxBytesPerTurn: normalizePositiveInteger(obj.maxBytesPerTurn, 50 * 1024 * 1024),
384
+ retentionDays: normalizePositiveInteger(obj.retentionDays, 7),
385
+ keepCompleted: typeof obj.keepCompleted === "boolean" ? obj.keepCompleted : false,
386
+ };
387
+ }
388
+
389
+ function loadConfig(): AppConfig {
390
+ const defaults: AppConfig = {
391
+ feishu: { appId: "", appSecret: "" },
392
+ platforms: { feishu: { enabled: true }, ilink: { enabled: true } },
393
+ chromeDevtools: { enabled: false, port: 15166, chromePath: "" },
394
+ port: 18080,
395
+ gitTimeoutSeconds: 180,
396
+ allowInterrupt: false,
397
+ rawStreamLogs: {
398
+ claude: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
399
+ cursor: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
400
+ codex: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
401
+ },
402
+ claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "", maxTurn: 0 },
397
403
  cursor: {
398
404
  enabled: false,
399
405
  defaultAgent: false,
400
406
  path: "",
401
407
  model: "claude-opus-4-7-max",
408
+ alternativeModel: "",
402
409
  avatarBatteryMode: "apiPercent",
403
410
  onDemandMonthlyBudget: 1000,
404
411
  },
405
- codex: { enabled: false, defaultAgent: false, path: "", model: "", effort: "" },
412
+ codex: { enabled: false, defaultAgent: false, path: "", model: "", alternativeModel: "", effort: "" },
406
413
  };
407
414
 
408
415
  if (!IS_TEST_ENV) {
@@ -450,13 +457,14 @@ function loadConfig(): AppConfig {
450
457
  path?: unknown;
451
458
  command?: unknown;
452
459
  model?: unknown;
460
+ alternativeModel?: unknown;
453
461
  avatarBatteryMode?: unknown;
454
462
  onDemandMonthlyBudget?: unknown;
455
463
  };
456
- codex?: { enabled?: unknown; defaultAgent?: unknown; path?: unknown; command?: unknown; model?: unknown; effort?: unknown };
457
- chromeDevtools?: { enabled?: unknown; port?: unknown; chromePath?: unknown };
458
- rawStreamLogs?: unknown;
459
- };
464
+ codex?: { enabled?: unknown; defaultAgent?: unknown; path?: unknown; command?: unknown; model?: unknown; alternativeModel?: unknown; effort?: unknown };
465
+ chromeDevtools?: { enabled?: unknown; port?: unknown; chromePath?: unknown };
466
+ rawStreamLogs?: unknown;
467
+ };
460
468
  try {
461
469
  parsed = JSON.parse(raw);
462
470
  } catch (err) {
@@ -466,12 +474,12 @@ function loadConfig(): AppConfig {
466
474
 
467
475
  const feishu = parsed.feishu ?? { appId: "", appSecret: "" };
468
476
  const claude = parsed.claude ?? {} as Partial<ClaudeConfig>;
469
- const cursorRaw = (parsed.cursor ?? {}) as NonNullable<typeof parsed.cursor>;
470
- const codexRaw = (parsed.codex ?? {}) as NonNullable<typeof parsed.codex>;
471
- const chromeDevtoolsRaw = (parsed.chromeDevtools ?? {}) as NonNullable<typeof parsed.chromeDevtools>;
472
- const rawStreamLogsRaw = typeof parsed.rawStreamLogs === "object" && parsed.rawStreamLogs !== null
473
- ? parsed.rawStreamLogs as unknown as Record<string, unknown>
474
- : {};
477
+ const cursorRaw = (parsed.cursor ?? {}) as NonNullable<typeof parsed.cursor>;
478
+ const codexRaw = (parsed.codex ?? {}) as NonNullable<typeof parsed.codex>;
479
+ const chromeDevtoolsRaw = (parsed.chromeDevtools ?? {}) as NonNullable<typeof parsed.chromeDevtools>;
480
+ const rawStreamLogsRaw = typeof parsed.rawStreamLogs === "object" && parsed.rawStreamLogs !== null
481
+ ? parsed.rawStreamLogs as unknown as Record<string, unknown>
482
+ : {};
475
483
 
476
484
  // 兼容旧字段 `command`:命中时打印一次性 warning 提示用户改名
477
485
  const onLegacyField = (label: string, value: string): void => {
@@ -502,13 +510,15 @@ function loadConfig(): AppConfig {
502
510
  Boolean(
503
511
  (typeof cursorRaw.path === "string" && cursorRaw.path.trim()) ||
504
512
  (typeof cursorRaw.command === "string" && (cursorRaw.command as string).trim()) ||
505
- (typeof cursorRaw.model === "string" && (cursorRaw.model as string).trim()),
513
+ (typeof cursorRaw.model === "string" && (cursorRaw.model as string).trim()) ||
514
+ (typeof cursorRaw.alternativeModel === "string" && (cursorRaw.alternativeModel as string).trim()),
506
515
  );
507
516
  const codexNonEmpty = (): boolean =>
508
517
  Boolean(
509
518
  (typeof codexRaw.path === "string" && codexRaw.path.trim()) ||
510
519
  (typeof codexRaw.command === "string" && (codexRaw.command as string).trim()) ||
511
520
  (typeof codexRaw.model === "string" && (codexRaw.model as string).trim()) ||
521
+ (typeof codexRaw.alternativeModel === "string" && (codexRaw.alternativeModel as string).trim()) ||
512
522
  (typeof codexRaw.effort === "string" && (codexRaw.effort as string).trim()),
513
523
  );
514
524
 
@@ -560,15 +570,15 @@ function loadConfig(): AppConfig {
560
570
  : 15166,
561
571
  chromePath: normalizeOptionalConfigField(chromeDevtoolsRaw.chromePath, { label: "chromeDevtools.chromePath" }),
562
572
  },
563
- port: typeof parsed.port === "number" ? parsed.port : 18080,
564
- gitTimeoutSeconds: typeof parsed.gitTimeoutSeconds === "number" ? parsed.gitTimeoutSeconds : 180,
565
- allowInterrupt: typeof parsed.allowInterrupt === "boolean" ? parsed.allowInterrupt : false,
566
- rawStreamLogs: {
567
- claude: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.claude),
568
- cursor: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.cursor),
569
- codex: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.codex),
570
- },
571
- claude: {
573
+ port: typeof parsed.port === "number" ? parsed.port : 18080,
574
+ gitTimeoutSeconds: typeof parsed.gitTimeoutSeconds === "number" ? parsed.gitTimeoutSeconds : 180,
575
+ allowInterrupt: typeof parsed.allowInterrupt === "boolean" ? parsed.allowInterrupt : false,
576
+ rawStreamLogs: {
577
+ claude: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.claude),
578
+ cursor: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.cursor),
579
+ codex: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.codex),
580
+ },
581
+ claude: {
572
582
  enabled: claudeEnabled,
573
583
  defaultAgent: defaultTool === "claude",
574
584
  model: normalizeOptionalConfigField(claude.model, { label: "claude.model" }),
@@ -585,6 +595,7 @@ function loadConfig(): AppConfig {
585
595
  defaultAgent: defaultTool === "cursor",
586
596
  path: readToolCliPath(cursorRaw, { label: "cursor", onLegacyField }),
587
597
  model: normalizeOptionalConfigField(cursorRaw.model, { label: "cursor.model", fallback: "claude-opus-4-7-max" }),
598
+ alternativeModel: normalizeOptionalConfigField(cursorRaw.alternativeModel, { label: "cursor.alternativeModel" }),
588
599
  avatarBatteryMode: normalizeCursorAvatarBatteryMode(cursorRaw.avatarBatteryMode),
589
600
  onDemandMonthlyBudget: normalizeCursorOnDemandMonthlyBudget(cursorRaw.onDemandMonthlyBudget),
590
601
  },
@@ -593,6 +604,7 @@ function loadConfig(): AppConfig {
593
604
  defaultAgent: defaultTool === "codex",
594
605
  path: readToolCliPath(codexRaw, { label: "codex", onLegacyField }),
595
606
  model: normalizeOptionalConfigField(codexRaw.model, { label: "codex.model" }),
607
+ alternativeModel: normalizeOptionalConfigField(codexRaw.alternativeModel, { label: "codex.alternativeModel" }),
596
608
  effort: normalizeOptionalConfigField(codexRaw.effort, { label: "codex.effort" }),
597
609
  },
598
610
  };
package/src/web-ui.ts CHANGED
@@ -40,10 +40,11 @@ interface AppConfig {
40
40
  path?: string;
41
41
  command?: string;
42
42
  model?: string;
43
+ alternativeModel?: string;
43
44
  avatarBatteryMode?: string;
44
45
  onDemandMonthlyBudget?: number;
45
46
  };
46
- codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; effort?: string };
47
+ codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string };
47
48
  }
48
49
 
49
50
  // ---------------------------------------------------------------------------
@@ -380,6 +381,9 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
380
381
  } else if (key === "CHATCCC_CURSOR_MODEL") {
381
382
  result.cursor = result.cursor || {};
382
383
  (result.cursor as Record<string, unknown>).model = val;
384
+ } else if (key === "CHATCCC_CURSOR_ALTERNATIVE_MODEL") {
385
+ result.cursor = result.cursor || {};
386
+ (result.cursor as Record<string, unknown>).alternativeModel = val;
383
387
  } else if (key === "CHATCCC_CURSOR_AVATAR_BATTERY_MODE") {
384
388
  result.cursor = result.cursor || {};
385
389
  (result.cursor as Record<string, unknown>).avatarBatteryMode = val;
@@ -398,6 +402,9 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
398
402
  } else if (key === "CHATCCC_CODEX_MODEL") {
399
403
  result.codex = result.codex || {};
400
404
  (result.codex as Record<string, unknown>).model = val;
405
+ } else if (key === "CHATCCC_CODEX_ALTERNATIVE_MODEL") {
406
+ result.codex = result.codex || {};
407
+ (result.codex as Record<string, unknown>).alternativeModel = val;
401
408
  } else if (key === "CHATCCC_CODEX_EFFORT") {
402
409
  result.codex = result.codex || {};
403
410
  (result.codex as Record<string, unknown>).effort = val;
@@ -747,6 +754,10 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
747
754
  <label>模型</label>
748
755
  <input type="text" id="field-CHATCCC_CURSOR_MODEL" placeholder="留空表示不传 --model">
749
756
  </div>
757
+ <div class="form-group">
758
+ <label>备选模型(选填)</label>
759
+ <input type="text" id="field-CHATCCC_CURSOR_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
760
+ </div>
750
761
  <div class="form-group">
751
762
  <label>头像电池电量</label>
752
763
  <select id="field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE" onchange="onCursorBatteryModeChange('field-', this.value)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none">
@@ -785,6 +796,10 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
785
796
  <label>模型</label>
786
797
  <input type="text" id="field-CHATCCC_CODEX_MODEL" placeholder="留空由 codex config.toml 决定">
787
798
  </div>
799
+ <div class="form-group">
800
+ <label>备选模型(选填)</label>
801
+ <input type="text" id="field-CHATCCC_CODEX_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
802
+ </div>
788
803
  <div class="form-group">
789
804
  <label>努力程度 (Effort)</label>
790
805
  <input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
@@ -891,6 +906,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
891
906
  <div class="section-detail">
892
907
  <div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CURSOR_PATH">-</span></div>
893
908
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CURSOR_MODEL">-</span></div>
909
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CURSOR_ALTERNATIVE_MODEL">-</span></div>
894
910
  <div class="config-row"><span class="key">头像电池电量</span><span class="val" id="cfg-CURSOR_AVATAR_BATTERY_MODE">-</span></div>
895
911
  <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>
896
912
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
@@ -903,6 +919,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
903
919
  <div class="section-detail">
904
920
  <div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CODEX_PATH">-</span></div>
905
921
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CODEX_MODEL">-</span></div>
922
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
906
923
  <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
907
924
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
908
925
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
@@ -950,8 +967,8 @@ var step2InputBound = false;
950
967
 
951
968
  const AGENT_FIELDS = {
952
969
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
953
- cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
954
- codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_EFFORT']
970
+ cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
971
+ codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT']
955
972
  };
956
973
  const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
957
974
  const CHROME_DEVTOOLS_FIELDS = ['CHATCCC_CHROME_DEVTOOLS_ENABLED','CHATCCC_CHROME_DEVTOOLS_PORT','CHATCCC_CHROME_DEVTOOLS_PATH'];
@@ -1217,8 +1234,8 @@ function isAgentEnabled(node, keys) {
1217
1234
  }
1218
1235
 
1219
1236
  var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort','maxTurn'];
1220
- var CURSOR_FALLBACK_KEYS = ['path','command','model'];
1221
- var CODEX_FALLBACK_KEYS = ['path','command','model','effort'];
1237
+ var CURSOR_FALLBACK_KEYS = ['path','command','model','alternativeModel'];
1238
+ var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort'];
1222
1239
 
1223
1240
  function renderStep2() {
1224
1241
  var c = state.config || {};
@@ -1230,6 +1247,7 @@ function renderStep2() {
1230
1247
  if (c.cursor) {
1231
1248
  prefillNested('field-CHATCCC_CURSOR_PATH', c.cursor.path || c.cursor.command);
1232
1249
  prefillNested('field-CHATCCC_CURSOR_MODEL', c.cursor.model);
1250
+ prefillNested('field-CHATCCC_CURSOR_ALTERNATIVE_MODEL', c.cursor.alternativeModel);
1233
1251
  var cursorMode = c.cursor.avatarBatteryMode || 'apiPercent';
1234
1252
  var cursorModeInput = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
1235
1253
  if (cursorModeInput) cursorModeInput.value = cursorMode;
@@ -1245,6 +1263,7 @@ function renderStep2() {
1245
1263
  if (c.codex) {
1246
1264
  prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
1247
1265
  prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
1266
+ prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
1248
1267
  prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
1249
1268
  }
1250
1269
 
@@ -1385,6 +1404,7 @@ function renderStep3() {
1385
1404
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">Cursor</h4>');
1386
1405
  if (vars.CHATCCC_CURSOR_PATH) lines.push('<div class="config-row"><span class="key">CLI 路径</span><span class="val">' + vars.CHATCCC_CURSOR_PATH + '</span></div>');
1387
1406
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CURSOR_MODEL || '(留空)') + '</span></div>');
1407
+ lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CURSOR_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1388
1408
  lines.push('<div class="config-row"><span class="key">头像电池电量</span><span class="val">' + cursorBatteryModeLabel(vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE) + '</span></div>');
1389
1409
  if (vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE === 'onDemandUse') {
1390
1410
  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>');
@@ -1393,6 +1413,7 @@ function renderStep3() {
1393
1413
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">Codex</h4>');
1394
1414
  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>');
1395
1415
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CODEX_MODEL || '(留空)') + '</span></div>');
1416
+ lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1396
1417
  lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
1397
1418
  }
1398
1419
  });
@@ -1578,6 +1599,7 @@ function updateDashboardUI() {
1578
1599
  document.getElementById('cfg-ANTHROPIC_MAX_TURN').textContent = (c.claude && c.claude.maxTurn != null) ? String(c.claude.maxTurn) : '0';
1579
1600
  document.getElementById('cfg-CURSOR_PATH').textContent = (c.cursor && (c.cursor.path || c.cursor.command)) || '-';
1580
1601
  document.getElementById('cfg-CURSOR_MODEL').textContent = (c.cursor && c.cursor.model) || '(留空)';
1602
+ document.getElementById('cfg-CURSOR_ALTERNATIVE_MODEL').textContent = (c.cursor && c.cursor.alternativeModel) || '(留空)';
1581
1603
  var cursorBatteryMode = (c.cursor && c.cursor.avatarBatteryMode) || 'apiPercent';
1582
1604
  document.getElementById('cfg-CURSOR_AVATAR_BATTERY_MODE').textContent = cursorBatteryModeLabel(cursorBatteryMode);
1583
1605
  var cursorBudgetRow = document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW');
@@ -1585,6 +1607,7 @@ function updateDashboardUI() {
1585
1607
  document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET').textContent = String((c.cursor && c.cursor.onDemandMonthlyBudget) || 1000);
1586
1608
  document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
1587
1609
  document.getElementById('cfg-CODEX_MODEL').textContent = (c.codex && c.codex.model) || '(留空)';
1610
+ document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
1588
1611
  document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
1589
1612
  }
1590
1613
 
@@ -1651,10 +1674,10 @@ function editSection(section) {
1651
1674
  'CHATCCC_CHROME_DEVTOOLS_PATH': 'Chrome 路径(选填)',
1652
1675
  'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL': 'Subagent 模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
1653
1676
  'CHATCCC_ANTHROPIC_API_KEY': 'API Key', 'CHATCCC_ANTHROPIC_BASE_URL': 'Base URL', 'CHATCCC_ANTHROPIC_MAX_TURN': 'Max Turns (0=无限制)',
1654
- 'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型',
1677
+ 'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型', 'CHATCCC_CURSOR_ALTERNATIVE_MODEL': '备选模型',
1655
1678
  'CHATCCC_CURSOR_AVATAR_BATTERY_MODE': '头像电池电量',
1656
1679
  'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET': '每月On demand use预算',
1657
- 'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_EFFORT': 'Effort'
1680
+ 'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort'
1658
1681
  };
1659
1682
  var hintMap = {
1660
1683
  'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
@@ -1687,11 +1710,13 @@ function editSection(section) {
1687
1710
  } else if (section === 'cursor' && state.config.cursor) {
1688
1711
  if (key === 'CHATCCC_CURSOR_PATH') val = state.config.cursor.path || state.config.cursor.command || '';
1689
1712
  else if (key === 'CHATCCC_CURSOR_MODEL') val = state.config.cursor.model || '';
1713
+ else if (key === 'CHATCCC_CURSOR_ALTERNATIVE_MODEL') val = state.config.cursor.alternativeModel || '';
1690
1714
  else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') val = state.config.cursor.avatarBatteryMode || 'apiPercent';
1691
1715
  else if (key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET') val = (state.config.cursor.onDemandMonthlyBudget != null) ? String(state.config.cursor.onDemandMonthlyBudget) : '1000';
1692
1716
  } else if (section === 'codex' && state.config.codex) {
1693
1717
  if (key === 'CHATCCC_CODEX_PATH') val = state.config.codex.path || state.config.codex.command || '';
1694
1718
  else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
1719
+ else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
1695
1720
  else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
1696
1721
  }
1697
1722
  }