chatccc 0.2.219 → 0.2.221

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/cards.ts CHANGED
@@ -70,10 +70,29 @@ const TOOL_EMOJI_MAP: Record<string, string> = {
70
70
  Agent: "\u{1F916}", // 🤖
71
71
  NotebookEdit: "\u{1F4D3}", // 📓
72
72
  AskUserQuestion: "\u{2753}",// ❓
73
+ // CCC 内置 Agent 下划线命名(getToolEmoji 会先把下划线转驼峰再查表,这里保留蛇形条目以便直接命中)
74
+ read_file: "\u{1F4D6}", // 📖
75
+ list_dir: "\u{1F4C2}", // 📂
76
+ search_code: "\u{1F50E}", // 🔎
77
+ run_command: "\u{1F5A5}\u{FE0F}", // 🖥️
78
+ edit_file: "\u{270F}\u{FE0F}", // ✏️
79
+ create_file: "\u{270D}\u{FE0F}", // ✍️
80
+ delete_file: "\u{1F5D1}\u{FE0F}", // 🗑️
81
+ move_file: "\u{1F4E6}", // 📦
82
+ apply_patch: "\u{1F4CB}", // 📋
73
83
  };
74
84
 
75
85
  export function getToolEmoji(name: string): string {
76
- return TOOL_EMOJI_MAP[name] ?? "\u{1F527}"; // 🔧
86
+ return TOOL_EMOJI_MAP[name] ?? TOOL_EMOJI_MAP[normalizeToolName(name)] ?? "\u{1F527}"; // 🔧
87
+ }
88
+
89
+ /** 把下划线命名转成驼峰(read_file → ReadFile),用于兼容两种命名风格 */
90
+ export function normalizeToolName(name: string): string {
91
+ return name
92
+ .split("_")
93
+ .filter((part) => part.length > 0)
94
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
95
+ .join("");
77
96
  }
78
97
 
79
98
  // ---------------------------------------------------------------------------
@@ -133,11 +152,12 @@ export function buildHelpCard(
133
152
  `发送 **/new** 创建新会话(使用 /cd 设置的目录,默认 ${defaultToolLabel})`,
134
153
  "发送 **/new claude** 创建新 Claude 会话",
135
154
  "发送 **/new cursor** 创建新 Cursor 会话",
136
- "发送 **/new codex** 创建新 Codex 会话",
155
+ "发送 **/new codex** 创建新 Codex 会话",
156
+ "发送 **/new ccc** 创建新 CCC Agent 会话",
137
157
  "发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
138
158
  "发送 **/plan** 以规划模式提问(只读,不执行写操作)",
139
159
  "发送 **/ask** 以问答模式提问(只读,不执行写操作)",
140
- "发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡",
160
+ "发送 **/usage** 查看当前 Agent 的用量或余额",
141
161
  "发送 **/restart** 重启 ChatCCC 进程",
142
162
  "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
143
163
  ABD_HELP_LINE,
@@ -152,7 +172,8 @@ export function buildHelpCard(
152
172
  { text: `新建默认会话(/new,${defaultToolLabel})`, value: JSON.stringify({ cmd: "new" }), type: "primary" },
153
173
  { text: "新建 Claude 会话(/new claude)", value: JSON.stringify({ cmd: "new claude" }), type: "primary" },
154
174
  { text: "新建 Cursor 会话(/new cursor)", value: JSON.stringify({ cmd: "new cursor" }), type: "primary" },
155
- { text: "新建 Codex 会话(/new codex)", value: JSON.stringify({ cmd: "new codex" }), type: "primary" },
175
+ { text: "新建 Codex 会话(/new codex)", value: JSON.stringify({ cmd: "new codex" }), type: "primary" },
176
+ { text: "新建 CCC Agent 会话(/new ccc)", value: JSON.stringify({ cmd: "new ccc" }), type: "primary" },
156
177
  { text: "重启 ChatCCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
157
178
  { text: "更新并重启(/update)", value: JSON.stringify({ cmd: "update" }), type: "danger" },
158
179
  { text: "切换工作路径(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
@@ -344,8 +365,8 @@ export function buildSessionsCard(sessions: Array<{
344
365
  header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
345
366
  elements: [
346
367
  { tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
347
- ? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;默认 Agent 变化后,下一条普通消息会创建对应 Agent 的新空会话。发送 **/new**、**/new claude**、**/new cursor** 或 **/new codex** 会另外创建会话群。`
348
- : `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor** 或 **/new codex** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
368
+ ? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;默认 Agent 变化后,下一条普通消息会创建对应 Agent 的新空会话。发送 **/new**、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 会另外创建会话群。`
369
+ : `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
349
370
  { tag: "hr" },
350
371
  { tag: "action", actions: [{ tag: "button", text: { tag: "plain_text", content: "收起" }, type: "default", value: { action: "close" } }] },
351
372
  ],
package/src/config.ts CHANGED
@@ -104,12 +104,18 @@ export interface CodexConfig {
104
104
  }
105
105
 
106
106
  export interface CccConfig {
107
+ /** Whether the built-in CCC Agent is available for new sessions. */
108
+ enabled: boolean;
109
+ /** Whether /new without an explicit tool should use CCC Agent. */
110
+ defaultAgent: boolean;
107
111
  /** DeepSeek API Key for the ChatCCC self-developed agent. */
108
112
  DEEPSEEK_API_KEY: string;
109
113
  /** DeepSeek-compatible API Base URL for the ChatCCC self-developed agent. */
110
114
  DEEPSEEK_BASE_URL: string;
111
115
  /** Model used by the ChatCCC self-developed agent. */
112
116
  model: string;
117
+ /** Optional model exposed through /model for manual per-session switching. */
118
+ alternativeModel: string;
113
119
  }
114
120
 
115
121
  export interface FeishuConfig {
@@ -168,8 +174,8 @@ export interface AppConfig {
168
174
  ccc: CccConfig;
169
175
  }
170
176
 
171
- export type AgentTool = "claude" | "cursor" | "codex";
172
- export const AGENT_TOOLS: AgentTool[] = ["claude", "cursor", "codex"];
177
+ export type AgentTool = "claude" | "cursor" | "codex" | "ccc";
178
+ export const AGENT_TOOLS: AgentTool[] = ["claude", "cursor", "codex", "ccc"];
173
179
  export type CursorAvatarBatteryMode = "apiPercent" | "onDemandUse";
174
180
 
175
181
  /** 获取指定 agent 配置中所有模型相关的值(最多 100 个,去重) */
@@ -190,6 +196,7 @@ export function getAllModelsForTool(tool: string, cfg: AppConfig = config): stri
190
196
  collect(cfg.codex.alternativeModel);
191
197
  } else if (tool === "ccc") {
192
198
  collect(cfg.ccc.model);
199
+ collect(cfg.ccc.alternativeModel);
193
200
  }
194
201
 
195
202
  return Array.from(seen).slice(0, 100);
@@ -445,7 +452,14 @@ function loadConfig(): AppConfig {
445
452
  onDemandMonthlyBudget: 1000,
446
453
  },
447
454
  codex: { enabled: false, defaultAgent: false, path: "", model: "", alternativeModel: "", effort: "", fastMode: false },
448
- ccc: { DEEPSEEK_API_KEY: "", DEEPSEEK_BASE_URL: DEFAULT_CCC_DEEPSEEK_BASE_URL, model: DEFAULT_CCC_MODEL },
455
+ ccc: {
456
+ enabled: false,
457
+ defaultAgent: false,
458
+ DEEPSEEK_API_KEY: "",
459
+ DEEPSEEK_BASE_URL: DEFAULT_CCC_DEEPSEEK_BASE_URL,
460
+ model: DEFAULT_CCC_MODEL,
461
+ alternativeModel: "",
462
+ },
449
463
  };
450
464
 
451
465
  if (!IS_TEST_ENV) {
@@ -498,7 +512,14 @@ function loadConfig(): AppConfig {
498
512
  onDemandMonthlyBudget?: unknown;
499
513
  };
500
514
  codex?: { enabled?: unknown; defaultAgent?: unknown; path?: unknown; command?: unknown; model?: unknown; alternativeModel?: unknown; effort?: unknown; fastMode?: unknown };
501
- ccc?: { DEEPSEEK_API_KEY?: unknown; DEEPSEEK_BASE_URL?: unknown; model?: unknown };
515
+ ccc?: {
516
+ enabled?: unknown;
517
+ defaultAgent?: unknown;
518
+ DEEPSEEK_API_KEY?: unknown;
519
+ DEEPSEEK_BASE_URL?: unknown;
520
+ model?: unknown;
521
+ alternativeModel?: unknown;
522
+ };
502
523
  webUi?: { openOnStart?: unknown };
503
524
  chromeDevtools?: { enabled?: unknown; port?: unknown; chromePath?: unknown };
504
525
  rawStreamLogs?: unknown;
@@ -553,7 +574,7 @@ function loadConfig(): AppConfig {
553
574
  (typeof cursorRaw.model === "string" && (cursorRaw.model as string).trim()) ||
554
575
  (typeof cursorRaw.alternativeModel === "string" && (cursorRaw.alternativeModel as string).trim()),
555
576
  );
556
- const codexNonEmpty = (): boolean =>
577
+ const codexNonEmpty = (): boolean =>
557
578
  Boolean(
558
579
  (typeof codexRaw.path === "string" && codexRaw.path.trim()) ||
559
580
  (typeof codexRaw.command === "string" && (codexRaw.command as string).trim()) ||
@@ -561,21 +582,28 @@ function loadConfig(): AppConfig {
561
582
  (typeof codexRaw.alternativeModel === "string" && (codexRaw.alternativeModel as string).trim()) ||
562
583
  (typeof codexRaw.effort === "string" && (codexRaw.effort as string).trim()) ||
563
584
  codexRaw.fastMode === true,
564
- );
585
+ );
586
+ // 旧版 ccc 配置没有 enabled。只用 API Key 推断启用,避免 sample 中自带的
587
+ // 默认 Base URL / model 让升级用户在未配置凭证时意外启用 CCC Agent。
588
+ const cccNonEmpty = (): boolean =>
589
+ Boolean(typeof cccRaw.DEEPSEEK_API_KEY === "string" && cccRaw.DEEPSEEK_API_KEY.trim());
565
590
 
566
591
  const claudeEnabled = resolveEnabled(claude.enabled, claudeNonEmpty);
567
592
  const cursorEnabled = resolveEnabled(cursorRaw.enabled, cursorNonEmpty);
568
- const codexEnabled = resolveEnabled(codexRaw.enabled, codexNonEmpty);
593
+ const codexEnabled = resolveEnabled(codexRaw.enabled, codexNonEmpty);
594
+ const cccEnabled = resolveEnabled(cccRaw.enabled, cccNonEmpty);
569
595
  const chromeDevtoolsPort = Number(chromeDevtoolsRaw.port);
570
596
  const explicitDefaultTool: AgentTool | null =
571
597
  typeof claude.defaultAgent === "boolean" && claude.defaultAgent && claudeEnabled ? "claude" :
572
598
  typeof cursorRaw.defaultAgent === "boolean" && cursorRaw.defaultAgent && cursorEnabled ? "cursor" :
573
- typeof codexRaw.defaultAgent === "boolean" && codexRaw.defaultAgent && codexEnabled ? "codex" :
599
+ typeof codexRaw.defaultAgent === "boolean" && codexRaw.defaultAgent && codexEnabled ? "codex" :
600
+ typeof cccRaw.defaultAgent === "boolean" && cccRaw.defaultAgent && cccEnabled ? "ccc" :
574
601
  null;
575
602
  const fallbackDefaultTool: AgentTool =
576
603
  claudeEnabled ? "claude" :
577
604
  cursorEnabled ? "cursor" :
578
- codexEnabled ? "codex" :
605
+ codexEnabled ? "codex" :
606
+ cccEnabled ? "ccc" :
579
607
  "claude";
580
608
  const defaultTool = explicitDefaultTool ?? fallbackDefaultTool;
581
609
 
@@ -655,12 +683,15 @@ function loadConfig(): AppConfig {
655
683
  fastMode: codexRaw.fastMode === true,
656
684
  },
657
685
  ccc: {
686
+ enabled: cccEnabled,
687
+ defaultAgent: defaultTool === "ccc",
658
688
  DEEPSEEK_API_KEY: normalizeOptionalConfigField(cccRaw.DEEPSEEK_API_KEY, { label: "ccc.DEEPSEEK_API_KEY" }),
659
689
  DEEPSEEK_BASE_URL: normalizeOptionalConfigField(cccRaw.DEEPSEEK_BASE_URL, {
660
690
  label: "ccc.DEEPSEEK_BASE_URL",
661
691
  fallback: DEFAULT_CCC_DEEPSEEK_BASE_URL,
662
692
  }),
663
693
  model: normalizeOptionalConfigField(cccRaw.model, { label: "ccc.model", fallback: DEFAULT_CCC_MODEL }),
694
+ alternativeModel: normalizeOptionalConfigField(cccRaw.alternativeModel, { label: "ccc.alternativeModel" }),
664
695
  },
665
696
  };
666
697
  }
@@ -981,7 +1012,7 @@ export const CLAUDE_SESSION_PREFIX = "Claude Code Session:";
981
1012
  export const CURSOR_SESSION_PREFIX = "Cursor Session:";
982
1013
  /** 群描述中用于识别 Codex 会话的前缀 */
983
1014
  export const CODEX_SESSION_PREFIX = "Codex Session:";
984
- /** 群描述中用于识别 hidden ccc agent 会话的前缀 */
1015
+ /** 群描述中用于识别 CCC Agent 会话的前缀 */
985
1016
  export const CCC_SESSION_PREFIX = "CCC Session:";
986
1017
 
987
1018
  /** 根据 tool 名称返回对应的群描述前缀 */
package/src/index.ts CHANGED
@@ -333,6 +333,19 @@ async function processFeishuMessageEvent(data: Evt): Promise<void> {
333
333
  if (delayNotice) {
334
334
  const delayToken = await getTenantAccessToken();
335
335
  await sendCardReply(delayToken, chatId, "延迟送达", delayNotice, "yellow").catch(() => {});
336
+ // 延迟送达的旧消息只发提醒、不转发给 agent 执行:
337
+ // 断线重连或补推时,早已过期的消息不应未经确认就触发新任务。
338
+ // 用户如需执行可自行重发。
339
+ logTrace(traceId, "DONE", {
340
+ outcome: "skip_delayed_feishu_message",
341
+ chatId,
342
+ msgTimestamp,
343
+ delayMinutes: Math.floor((Date.now() - msgTimestamp) / 60000),
344
+ });
345
+ console.log(
346
+ `[${ts()}] [SKIP] Delayed Feishu message not forwarded to agent: messageId=${messageId ?? "(missing id)"} chatId=${chatId} createTime=${msgTimestamp}`,
347
+ );
348
+ return;
336
349
  }
337
350
 
338
351
  await handleCommand(