chatccc 0.2.186 → 0.2.188

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/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,25 +82,38 @@ 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
  }
88
90
 
89
- export interface CodexConfig {
90
- /** 是否启用 Codex Agent;缺省时按"有任意字段非空"自动判定(向后兼容) */
91
- enabled: boolean;
91
+ export interface CodexConfig {
92
+ /** 是否启用 Codex Agent;缺省时按"有任意字段非空"自动判定(向后兼容) */
93
+ enabled: boolean;
92
94
  /** 是否作为 /new 未指定工具时使用的默认 Agent */
93
95
  defaultAgent: boolean;
94
96
  /** Codex CLI 可执行文件绝对路径;留空时退回到 PATH 中的 `codex` */
95
97
  path: string;
96
98
  model: string;
97
- effort: string;
98
- }
99
-
100
- export interface FeishuConfig {
101
- appId: string;
102
- appSecret: string;
103
- }
99
+ /** /model 可切换的单个备选模型;留空则不加入候选列表 */
100
+ alternativeModel: string;
101
+ effort: string;
102
+ }
103
+
104
+ export interface CccConfig {
105
+ /** DeepSeek API Key for the ChatCCC self-developed agent. */
106
+ DEEPSEEK_API_KEY: string;
107
+ /** DeepSeek-compatible API Base URL for the ChatCCC self-developed agent. */
108
+ DEEPSEEK_BASE_URL: string;
109
+ /** Model used by the ChatCCC self-developed agent. */
110
+ model: string;
111
+ }
112
+
113
+ export interface FeishuConfig {
114
+ appId: string;
115
+ appSecret: string;
116
+ }
104
117
 
105
118
  export interface PlatformConfig {
106
119
  enabled: boolean;
@@ -114,40 +127,41 @@ export interface PlatformsConfig {
114
127
  ilink: PlatformConfig;
115
128
  }
116
129
 
117
- export interface ChromeDevtoolsConfig {
130
+ export interface ChromeDevtoolsConfig {
118
131
  /** 是否由 ChatCCC 守护一个常驻 Chrome CDP 实例 */
119
132
  enabled: boolean;
120
133
  /** Chrome remote debugging 端口,默认 15166 */
121
134
  port: number;
122
135
  /** 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 {
136
+ chromePath: string;
137
+ }
138
+
139
+ export interface RawStreamAgentLogConfig {
140
+ enabled: boolean;
141
+ maxBytesPerTurn: number;
142
+ retentionDays: number;
143
+ keepCompleted: boolean;
144
+ }
145
+
146
+ export interface RawStreamLogsConfig {
147
+ claude: RawStreamAgentLogConfig;
148
+ cursor: RawStreamAgentLogConfig;
149
+ codex: RawStreamAgentLogConfig;
150
+ }
151
+
152
+ export interface AppConfig {
140
153
  feishu: FeishuConfig;
141
154
  platforms: PlatformsConfig;
142
155
  chromeDevtools: ChromeDevtoolsConfig;
143
156
  port: number;
144
- gitTimeoutSeconds: number;
145
- /** 若为 false,AI 生成过程中用户发送消息不会打断,须先点「停止」再发送新消息 */
146
- allowInterrupt: boolean;
147
- rawStreamLogs: RawStreamLogsConfig;
157
+ gitTimeoutSeconds: number;
158
+ /** 若为 false,AI 生成过程中用户发送消息不会打断,须先点「停止」再发送新消息 */
159
+ allowInterrupt: boolean;
160
+ rawStreamLogs: RawStreamLogsConfig;
148
161
  claude: ClaudeConfig;
149
162
  cursor: CursorConfig;
150
163
  codex: CodexConfig;
164
+ ccc: CccConfig;
151
165
  }
152
166
 
153
167
  export type AgentTool = "claude" | "cursor" | "codex";
@@ -155,26 +169,45 @@ export const AGENT_TOOLS: AgentTool[] = ["claude", "cursor", "codex"];
155
169
  export type CursorAvatarBatteryMode = "apiPercent" | "onDemandUse";
156
170
 
157
171
  /** 获取指定 agent 配置中所有模型相关的值(最多 100 个,去重) */
158
- export function getAllModelsForTool(tool: AgentTool, cfg: AppConfig = config): string[] {
159
- const seen = new Set<string>();
160
- const collect = (v: unknown) => {
161
- if (typeof v === "string" && v.trim()) seen.add(v.trim());
162
- };
172
+ export function getAllModelsForTool(tool: AgentTool, cfg: AppConfig = config): string[] {
173
+ const seen = new Set<string>();
174
+ const collect = (v: unknown) => {
175
+ if (typeof v === "string" && v.trim()) seen.add(v.trim());
176
+ };
163
177
 
164
178
  if (tool === "claude") {
165
179
  collect(cfg.claude.model);
166
180
  collect(cfg.claude.subagentModel);
167
181
  } else if (tool === "cursor") {
168
182
  collect(cfg.cursor.model);
183
+ collect(cfg.cursor.alternativeModel);
169
184
  } else if (tool === "codex") {
170
185
  collect(cfg.codex.model);
186
+ collect(cfg.codex.alternativeModel);
171
187
  }
172
-
173
- return Array.from(seen).slice(0, 100);
174
- }
175
-
176
- const CONFIG_FILE = join(USER_DATA_DIR, "config.json");
177
- const CONFIG_SAMPLE_FILE = join(PROJECT_ROOT, "config.sample.json");
188
+
189
+ return Array.from(seen).slice(0, 100);
190
+ }
191
+
192
+ export const CLAUDE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"] as const;
193
+ export const CODEX_EFFORT_LEVELS = ["minimal", "low", "medium", "high", "xhigh"] as const;
194
+
195
+ export function getAllEffortsForTool(tool: AgentTool): string[] {
196
+ if (tool === "claude") return [...CLAUDE_EFFORT_LEVELS];
197
+ if (tool === "codex") return [...CODEX_EFFORT_LEVELS];
198
+ return [];
199
+ }
200
+
201
+ export function getDefaultEffortForTool(tool: AgentTool, cfg: AppConfig = config): string {
202
+ if (tool === "claude") return cfg.claude.effort;
203
+ if (tool === "codex") return cfg.codex.effort;
204
+ return "";
205
+ }
206
+
207
+ const CONFIG_FILE = join(USER_DATA_DIR, "config.json");
208
+ const CONFIG_SAMPLE_FILE = join(PROJECT_ROOT, "config.sample.json");
209
+ export const DEFAULT_CCC_DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1";
210
+ export const DEFAULT_CCC_MODEL = "deepseek-v4-pro[1m]";
178
211
 
179
212
  /**
180
213
  * 将旧位置(PROJECT_ROOT)的持久化数据一次性迁移到 USER_DATA_DIR。
@@ -358,40 +391,40 @@ function normalizeCursorAvatarBatteryMode(raw: unknown): CursorAvatarBatteryMode
358
391
  return raw === "onDemandUse" ? "onDemandUse" : "apiPercent";
359
392
  }
360
393
 
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 },
394
+ function normalizeCursorOnDemandMonthlyBudget(raw: unknown): number {
395
+ const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
396
+ return Number.isFinite(value) && value > 0 ? value : 1000;
397
+ }
398
+
399
+ function normalizePositiveInteger(raw: unknown, fallback: number): number {
400
+ const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
401
+ return Number.isInteger(value) && value > 0 ? value : fallback;
402
+ }
403
+
404
+ function normalizeRawStreamAgentLogConfig(raw: unknown): RawStreamAgentLogConfig {
405
+ const obj = typeof raw === "object" && raw !== null
406
+ ? raw as Record<string, unknown>
407
+ : {};
408
+ return {
409
+ enabled: typeof obj.enabled === "boolean" ? obj.enabled : false,
410
+ maxBytesPerTurn: normalizePositiveInteger(obj.maxBytesPerTurn, 50 * 1024 * 1024),
411
+ retentionDays: normalizePositiveInteger(obj.retentionDays, 7),
412
+ keepCompleted: typeof obj.keepCompleted === "boolean" ? obj.keepCompleted : false,
413
+ };
414
+ }
415
+
416
+ function loadConfig(): AppConfig {
417
+ const defaults: AppConfig = {
418
+ feishu: { appId: "", appSecret: "" },
419
+ platforms: { feishu: { enabled: true }, ilink: { enabled: true } },
420
+ chromeDevtools: { enabled: false, port: 15166, chromePath: "" },
421
+ port: 18080,
422
+ gitTimeoutSeconds: 180,
423
+ allowInterrupt: false,
424
+ rawStreamLogs: {
425
+ claude: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
426
+ cursor: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
427
+ codex: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
395
428
  },
396
429
  claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "", maxTurn: 0 },
397
430
  cursor: {
@@ -399,11 +432,13 @@ function loadConfig(): AppConfig {
399
432
  defaultAgent: false,
400
433
  path: "",
401
434
  model: "claude-opus-4-7-max",
435
+ alternativeModel: "",
402
436
  avatarBatteryMode: "apiPercent",
403
437
  onDemandMonthlyBudget: 1000,
404
- },
405
- codex: { enabled: false, defaultAgent: false, path: "", model: "", effort: "" },
406
- };
438
+ },
439
+ codex: { enabled: false, defaultAgent: false, path: "", model: "", alternativeModel: "", effort: "" },
440
+ ccc: { DEEPSEEK_API_KEY: "", DEEPSEEK_BASE_URL: DEFAULT_CCC_DEEPSEEK_BASE_URL, model: DEFAULT_CCC_MODEL },
441
+ };
407
442
 
408
443
  if (!IS_TEST_ENV) {
409
444
  migrateLegacyData();
@@ -450,10 +485,12 @@ function loadConfig(): AppConfig {
450
485
  path?: unknown;
451
486
  command?: unknown;
452
487
  model?: unknown;
488
+ alternativeModel?: unknown;
453
489
  avatarBatteryMode?: unknown;
454
490
  onDemandMonthlyBudget?: unknown;
455
- };
456
- codex?: { enabled?: unknown; defaultAgent?: unknown; path?: unknown; command?: unknown; model?: unknown; effort?: unknown };
491
+ };
492
+ codex?: { enabled?: unknown; defaultAgent?: unknown; path?: unknown; command?: unknown; model?: unknown; alternativeModel?: unknown; effort?: unknown };
493
+ ccc?: { DEEPSEEK_API_KEY?: unknown; DEEPSEEK_BASE_URL?: unknown; model?: unknown };
457
494
  chromeDevtools?: { enabled?: unknown; port?: unknown; chromePath?: unknown };
458
495
  rawStreamLogs?: unknown;
459
496
  };
@@ -468,10 +505,11 @@ function loadConfig(): AppConfig {
468
505
  const claude = parsed.claude ?? {} as Partial<ClaudeConfig>;
469
506
  const cursorRaw = (parsed.cursor ?? {}) as NonNullable<typeof parsed.cursor>;
470
507
  const codexRaw = (parsed.codex ?? {}) as NonNullable<typeof parsed.codex>;
508
+ const cccRaw = (parsed.ccc ?? {}) as NonNullable<typeof parsed.ccc>;
471
509
  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
- : {};
510
+ const rawStreamLogsRaw = typeof parsed.rawStreamLogs === "object" && parsed.rawStreamLogs !== null
511
+ ? parsed.rawStreamLogs as unknown as Record<string, unknown>
512
+ : {};
475
513
 
476
514
  // 兼容旧字段 `command`:命中时打印一次性 warning 提示用户改名
477
515
  const onLegacyField = (label: string, value: string): void => {
@@ -502,13 +540,15 @@ function loadConfig(): AppConfig {
502
540
  Boolean(
503
541
  (typeof cursorRaw.path === "string" && cursorRaw.path.trim()) ||
504
542
  (typeof cursorRaw.command === "string" && (cursorRaw.command as string).trim()) ||
505
- (typeof cursorRaw.model === "string" && (cursorRaw.model as string).trim()),
543
+ (typeof cursorRaw.model === "string" && (cursorRaw.model as string).trim()) ||
544
+ (typeof cursorRaw.alternativeModel === "string" && (cursorRaw.alternativeModel as string).trim()),
506
545
  );
507
546
  const codexNonEmpty = (): boolean =>
508
547
  Boolean(
509
548
  (typeof codexRaw.path === "string" && codexRaw.path.trim()) ||
510
549
  (typeof codexRaw.command === "string" && (codexRaw.command as string).trim()) ||
511
550
  (typeof codexRaw.model === "string" && (codexRaw.model as string).trim()) ||
551
+ (typeof codexRaw.alternativeModel === "string" && (codexRaw.alternativeModel as string).trim()) ||
512
552
  (typeof codexRaw.effort === "string" && (codexRaw.effort as string).trim()),
513
553
  );
514
554
 
@@ -560,15 +600,15 @@ function loadConfig(): AppConfig {
560
600
  : 15166,
561
601
  chromePath: normalizeOptionalConfigField(chromeDevtoolsRaw.chromePath, { label: "chromeDevtools.chromePath" }),
562
602
  },
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: {
603
+ port: typeof parsed.port === "number" ? parsed.port : 18080,
604
+ gitTimeoutSeconds: typeof parsed.gitTimeoutSeconds === "number" ? parsed.gitTimeoutSeconds : 180,
605
+ allowInterrupt: typeof parsed.allowInterrupt === "boolean" ? parsed.allowInterrupt : false,
606
+ rawStreamLogs: {
607
+ claude: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.claude),
608
+ cursor: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.cursor),
609
+ codex: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.codex),
610
+ },
611
+ claude: {
572
612
  enabled: claudeEnabled,
573
613
  defaultAgent: defaultTool === "claude",
574
614
  model: normalizeOptionalConfigField(claude.model, { label: "claude.model" }),
@@ -585,18 +625,28 @@ function loadConfig(): AppConfig {
585
625
  defaultAgent: defaultTool === "cursor",
586
626
  path: readToolCliPath(cursorRaw, { label: "cursor", onLegacyField }),
587
627
  model: normalizeOptionalConfigField(cursorRaw.model, { label: "cursor.model", fallback: "claude-opus-4-7-max" }),
628
+ alternativeModel: normalizeOptionalConfigField(cursorRaw.alternativeModel, { label: "cursor.alternativeModel" }),
588
629
  avatarBatteryMode: normalizeCursorAvatarBatteryMode(cursorRaw.avatarBatteryMode),
589
630
  onDemandMonthlyBudget: normalizeCursorOnDemandMonthlyBudget(cursorRaw.onDemandMonthlyBudget),
590
631
  },
591
- codex: {
592
- enabled: codexEnabled,
593
- defaultAgent: defaultTool === "codex",
594
- path: readToolCliPath(codexRaw, { label: "codex", onLegacyField }),
595
- model: normalizeOptionalConfigField(codexRaw.model, { label: "codex.model" }),
596
- effort: normalizeOptionalConfigField(codexRaw.effort, { label: "codex.effort" }),
597
- },
598
- };
599
- }
632
+ codex: {
633
+ enabled: codexEnabled,
634
+ defaultAgent: defaultTool === "codex",
635
+ path: readToolCliPath(codexRaw, { label: "codex", onLegacyField }),
636
+ model: normalizeOptionalConfigField(codexRaw.model, { label: "codex.model" }),
637
+ alternativeModel: normalizeOptionalConfigField(codexRaw.alternativeModel, { label: "codex.alternativeModel" }),
638
+ effort: normalizeOptionalConfigField(codexRaw.effort, { label: "codex.effort" }),
639
+ },
640
+ ccc: {
641
+ DEEPSEEK_API_KEY: normalizeOptionalConfigField(cccRaw.DEEPSEEK_API_KEY, { label: "ccc.DEEPSEEK_API_KEY" }),
642
+ DEEPSEEK_BASE_URL: normalizeOptionalConfigField(cccRaw.DEEPSEEK_BASE_URL, {
643
+ label: "ccc.DEEPSEEK_BASE_URL",
644
+ fallback: DEFAULT_CCC_DEEPSEEK_BASE_URL,
645
+ }),
646
+ model: normalizeOptionalConfigField(cccRaw.model, { label: "ccc.model", fallback: DEFAULT_CCC_MODEL }),
647
+ },
648
+ };
649
+ }
600
650
 
601
651
  /**
602
652
  * 全局可变 config 对象。