chatccc 0.2.56 → 0.2.58

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.
@@ -86,33 +86,33 @@ function buildSdkEnv(
86
86
  baseUrl: string | undefined,
87
87
  subagentModel: string | undefined,
88
88
  ): Record<string, string | undefined> | undefined {
89
- const apiKeyTrim = (apiKey ?? "").trim();
90
- const baseUrlTrim = (baseUrl ?? "").trim();
91
- const subagentModelTrim = (subagentModel ?? "").trim();
92
- const hasApiOverride = Boolean(apiKeyTrim || baseUrlTrim);
93
- if (!hasApiOverride) return undefined;
94
-
95
- const env: Record<string, string | undefined> = { ...process.env };
96
- // ChatCCC's third-party Claude API config is authoritative when present.
97
- // Remove Claude Code/user settings env that can silently override gateway/auth/model choice.
98
- delete env.ANTHROPIC_AUTH_TOKEN;
99
- delete env.CLAUDE_CODE_OAUTH_TOKEN;
100
- delete env.ANTHROPIC_MODEL;
101
- delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
102
- delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
103
- delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
104
- delete env.CLAUDE_CODE_SUBAGENT_MODEL;
105
- delete env.CLAUDE_CODE_EFFORT_LEVEL;
106
-
107
- if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
108
- if (baseUrlTrim) {
109
- env.ANTHROPIC_BASE_URL = baseUrlTrim;
110
- } else {
111
- delete env.ANTHROPIC_BASE_URL;
112
- }
113
- if (subagentModelTrim) env.CLAUDE_CODE_SUBAGENT_MODEL = subagentModelTrim;
114
- return env;
115
- }
89
+ const apiKeyTrim = (apiKey ?? "").trim();
90
+ const baseUrlTrim = (baseUrl ?? "").trim();
91
+ const subagentModelTrim = (subagentModel ?? "").trim();
92
+ const hasApiOverride = Boolean(apiKeyTrim || baseUrlTrim);
93
+ if (!hasApiOverride) return undefined;
94
+
95
+ const env: Record<string, string | undefined> = { ...process.env };
96
+ // ChatCCC's third-party Claude API config is authoritative when present.
97
+ // Remove Claude Code/user settings env that can silently override gateway/auth/model choice.
98
+ delete env.ANTHROPIC_AUTH_TOKEN;
99
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
100
+ delete env.ANTHROPIC_MODEL;
101
+ delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
102
+ delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
103
+ delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
104
+ delete env.CLAUDE_CODE_SUBAGENT_MODEL;
105
+ delete env.CLAUDE_CODE_EFFORT_LEVEL;
106
+
107
+ if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
108
+ if (baseUrlTrim) {
109
+ env.ANTHROPIC_BASE_URL = baseUrlTrim;
110
+ } else {
111
+ delete env.ANTHROPIC_BASE_URL;
112
+ }
113
+ if (subagentModelTrim) env.CLAUDE_CODE_SUBAGENT_MODEL = subagentModelTrim;
114
+ return env;
115
+ }
116
116
 
117
117
  function resolveSettingSources(
118
118
  _apiKey: string | undefined,
@@ -291,19 +291,19 @@ class ClaudeAdapter implements ToolAdapter {
291
291
  this.baseUrl,
292
292
  this.subagentModel,
293
293
  );
294
- const session = unstable_v2_resumeSession(
295
- sessionId,
296
- sessionOpts as any,
297
- );
298
-
299
- if (signal?.aborted) {
300
- session.close();
301
- return;
302
- }
303
- const onAbort = () => { session.close(); };
304
- signal?.addEventListener("abort", onAbort, { once: true });
305
-
306
- await session.send(userText);
294
+ const session = unstable_v2_resumeSession(
295
+ sessionId,
296
+ sessionOpts as any,
297
+ );
298
+
299
+ if (signal?.aborted) {
300
+ session.close();
301
+ return;
302
+ }
303
+ const onAbort = () => { session.close(); };
304
+ signal?.addEventListener("abort", onAbort, { once: true });
305
+
306
+ await session.send(userText);
307
307
 
308
308
  const stream = session.stream();
309
309
 
package/src/index.ts CHANGED
@@ -84,9 +84,9 @@ import {
84
84
  updateCardKitCard,
85
85
  } from "./cardkit.ts";
86
86
  import {
87
- MAX_PROCESSED,
88
- clearAdapterCache,
89
- loadSessionRegistryForBinding,
87
+ MAX_PROCESSED,
88
+ clearAdapterCache,
89
+ loadSessionRegistryForBinding,
90
90
  processedMessages,
91
91
  rebuildBindingsFromRegistry,
92
92
  resetState,
@@ -744,12 +744,12 @@ async function main(): Promise<void> {
744
744
  // "保存并启动" 时,web-ui 会调用本回调,把磁盘上刚保存的 config.json
745
745
  // 刷进进程内的 export let 常量(live binding 让 CLAUDE_MODEL 等下次创建
746
746
  // 会话时自动看到新值)。setup 首次激活走 onActivate 路径,不依赖此 hook。
747
- setReloadConfigHook(() => {
748
- reloadConfigFromDisk();
749
- clearAdapterCache();
750
- appendStartupTrace("reload-from-ui: config reloaded", {
751
- appIdMask: maskAppId(APP_ID),
752
- });
747
+ setReloadConfigHook(() => {
748
+ reloadConfigFromDisk();
749
+ clearAdapterCache();
750
+ appendStartupTrace("reload-from-ui: config reloaded", {
751
+ appIdMask: maskAppId(APP_ID),
752
+ });
753
753
  });
754
754
  setExtraApiHandler(async (req, res) => {
755
755
  return (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res));
@@ -769,12 +769,12 @@ async function main(): Promise<void> {
769
769
  // 凭证不全:进 setup 向导。注入 onActivate 回调让用户点"保存并启动"
770
770
  // 时,原地(同进程)调用 startBotService,复用 setup HTTP server。
771
771
  startSetupMode(CHATCCC_PORT, {
772
- onActivate: async (httpServer: Server) => {
773
- reloadConfigFromDisk();
774
- clearAdapterCache();
775
- appendStartupTrace("setup-activate: reloaded config from disk", {
776
- appIdMaskAfterReload: maskAppId(APP_ID),
777
- });
772
+ onActivate: async (httpServer: Server) => {
773
+ reloadConfigFromDisk();
774
+ clearAdapterCache();
775
+ appendStartupTrace("setup-activate: reloaded config from disk", {
776
+ appIdMaskAfterReload: maskAppId(APP_ID),
777
+ });
778
778
  try {
779
779
  await startBotService({ httpServer, port: CHATCCC_PORT });
780
780
  installShutdownHandlers(httpServer);
@@ -72,13 +72,21 @@ export function cwdDisplayName(cwd: string): string {
72
72
  return trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed || "cwd";
73
73
  }
74
74
 
75
- export function sessionChatName(left: string, cwd: string): string {
76
- return `${left}-${cwdDisplayName(cwd)}`;
77
- }
78
-
79
- function isUntitledSessionChatName(name: string): boolean {
80
- return name === "新会话" || name.startsWith("新会话-");
81
- }
75
+ export function sessionChatName(left: string, cwd: string): string {
76
+ return `${left}-${cwdDisplayName(cwd)}`;
77
+ }
78
+
79
+ function isUntitledSessionChatName(name: string): boolean {
80
+ return name === "新会话" || name.startsWith("新会话-");
81
+ }
82
+
83
+ function shouldSendWechatProcessingAck(
84
+ platform: PlatformAdapter,
85
+ textLower: string,
86
+ chatType: string,
87
+ ): boolean {
88
+ return platform.kind === "wechat" && chatType === "p2p" && !textLower.startsWith("/");
89
+ }
82
90
 
83
91
  // ---------------------------------------------------------------------------
84
92
  // handleCommand — 平台无关的命令分发
@@ -971,11 +979,11 @@ export async function handleCommand(
971
979
  }
972
980
 
973
981
  // 并发检查:同一 session 只能有一个活跃 prompt
974
- if (isSessionRunning(sessionId)) {
975
- logTrace(tid, "BLOCKED", {
976
- outcome: "session_busy",
977
- sessionId,
978
- });
982
+ if (isSessionRunning(sessionId)) {
983
+ logTrace(tid, "BLOCKED", {
984
+ outcome: "session_busy",
985
+ sessionId,
986
+ });
979
987
  console.log(
980
988
  `[${ts()}] [BLOCKED] Session ${sessionId} is already generating, rejecting message from chat ${chatId}`,
981
989
  );
@@ -984,13 +992,17 @@ export async function handleCommand(
984
992
  "生成中",
985
993
  "该会话正在生成回复中,请等待完成后再发送新消息。",
986
994
  "yellow",
987
- );
988
- return;
989
- }
990
-
991
- try {
992
- logTrace(tid, "RESUME", { sessionId, tool: descriptionTool });
993
- await resumeAndPrompt(
995
+ );
996
+ return;
997
+ }
998
+
999
+ if (shouldSendWechatProcessingAck(platform, textLower, chatType)) {
1000
+ await platform.sendText(chatId, "生成中...").catch(() => {});
1001
+ }
1002
+
1003
+ try {
1004
+ logTrace(tid, "RESUME", { sessionId, tool: descriptionTool });
1005
+ await resumeAndPrompt(
994
1006
  sessionId,
995
1007
  text,
996
1008
  platform,
package/src/privacy.ts CHANGED
@@ -1,68 +1,68 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { USER_DATA_DIR } from "./config.ts";
4
-
5
- // ---------------------------------------------------------------------------
6
- // 隐私替换规则
7
- // ---------------------------------------------------------------------------
8
-
9
- interface PrivacyRules {
10
- [key: string]: string;
11
- }
12
-
13
- let rules: PrivacyRules | null = null;
14
- let loaded = false;
15
-
16
- function loadRules(): PrivacyRules {
17
- const filePath = join(USER_DATA_DIR, "privacy.json");
18
- if (!existsSync(filePath)) {
19
- return {};
20
- }
21
- try {
22
- const raw = readFileSync(filePath, "utf-8");
23
- const parsed = JSON.parse(raw);
24
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
25
- console.error(`[PRIVACY] privacy.json 格式错误:应为对象`);
26
- return {};
27
- }
28
- for (const [k, v] of Object.entries(parsed)) {
29
- if (typeof v !== "string") {
30
- console.error(`[PRIVACY] privacy.json 值必须为字符串,跳过 "${k}"`);
31
- delete (parsed as Record<string, unknown>)[k];
32
- }
33
- }
34
- return parsed as PrivacyRules;
35
- } catch (err) {
36
- console.error(`[PRIVACY] 读取 privacy.json 失败: ${(err as Error).message}`);
37
- return {};
38
- }
39
- }
40
-
41
- export function getPrivacyRules(): PrivacyRules {
42
- if (!loaded) {
43
- rules = loadRules();
44
- loaded = true;
45
- }
46
- return rules!;
47
- }
48
-
49
- /** 重新加载规则(热更新用) */
50
- export function reloadPrivacyRules(): void {
51
- loaded = false;
52
- rules = null;
53
- }
54
-
55
- /**
56
- * 对文本应用隐私替换规则。
57
- * 若无规则或文本为空,直接返回原文。
58
- */
59
- export function applyPrivacy(text: string): string {
60
- const r = getPrivacyRules();
61
- if (Object.keys(r).length === 0 || !text) return text;
62
- let result = text;
63
- for (const [from, to] of Object.entries(r)) {
64
- // 用 split+join 替代 replaceAll,避免正则特殊字符问题
65
- result = result.split(from).join(to);
66
- }
67
- return result;
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { USER_DATA_DIR } from "./config.ts";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // 隐私替换规则
7
+ // ---------------------------------------------------------------------------
8
+
9
+ interface PrivacyRules {
10
+ [key: string]: string;
11
+ }
12
+
13
+ let rules: PrivacyRules | null = null;
14
+ let loaded = false;
15
+
16
+ function loadRules(): PrivacyRules {
17
+ const filePath = join(USER_DATA_DIR, "privacy.json");
18
+ if (!existsSync(filePath)) {
19
+ return {};
20
+ }
21
+ try {
22
+ const raw = readFileSync(filePath, "utf-8");
23
+ const parsed = JSON.parse(raw);
24
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
25
+ console.error(`[PRIVACY] privacy.json 格式错误:应为对象`);
26
+ return {};
27
+ }
28
+ for (const [k, v] of Object.entries(parsed)) {
29
+ if (typeof v !== "string") {
30
+ console.error(`[PRIVACY] privacy.json 值必须为字符串,跳过 "${k}"`);
31
+ delete (parsed as Record<string, unknown>)[k];
32
+ }
33
+ }
34
+ return parsed as PrivacyRules;
35
+ } catch (err) {
36
+ console.error(`[PRIVACY] 读取 privacy.json 失败: ${(err as Error).message}`);
37
+ return {};
38
+ }
39
+ }
40
+
41
+ export function getPrivacyRules(): PrivacyRules {
42
+ if (!loaded) {
43
+ rules = loadRules();
44
+ loaded = true;
45
+ }
46
+ return rules!;
47
+ }
48
+
49
+ /** 重新加载规则(热更新用) */
50
+ export function reloadPrivacyRules(): void {
51
+ loaded = false;
52
+ rules = null;
53
+ }
54
+
55
+ /**
56
+ * 对文本应用隐私替换规则。
57
+ * 若无规则或文本为空,直接返回原文。
58
+ */
59
+ export function applyPrivacy(text: string): string {
60
+ const r = getPrivacyRules();
61
+ if (Object.keys(r).length === 0 || !text) return text;
62
+ let result = text;
63
+ for (const [from, to] of Object.entries(r)) {
64
+ // 用 split+join 替代 replaceAll,避免正则特殊字符问题
65
+ result = result.split(from).join(to);
66
+ }
67
+ return result;
68
68
  }
package/src/session.ts CHANGED
@@ -393,7 +393,8 @@ export function accumulateBlockContent(
393
393
  switch (block.type) {
394
394
  case "thinking":
395
395
  state.chunkCount++;
396
- state.accumulatedContent += block.thinking;
396
+ // 用引用块标记思考内容(中文无法斜体,引用块有视觉区分)
397
+ state.accumulatedContent += `\n> ${block.thinking.replace(/\n/g, "\n> ")}\n`;
397
398
  break;
398
399
  case "tool_use": {
399
400
  // 记录 tool_use 信息供后续 tool_result 使用
package/src/simplify.ts CHANGED
@@ -1,120 +1,120 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { resolve as resolvePath } from "node:path";
3
- import { PROJECT_ROOT } from "./config.ts";
4
-
5
- // ---------------------------------------------------------------------------
6
- // 消息简化规则 —— 数据驱动,在 simplify.json 中配置
7
- // ---------------------------------------------------------------------------
8
-
9
- interface ToolRule {
10
- template: string;
11
- maxLength: number;
12
- }
13
-
14
- interface SimplifyConfig {
15
- tool_use?: Record<string, ToolRule>;
16
- tool_result?: Record<string, ToolRule>;
17
- }
18
-
19
- let config: SimplifyConfig | null = null;
20
- let loaded = false;
21
-
22
- function loadConfig(): SimplifyConfig {
23
- const filePath = resolvePath(PROJECT_ROOT, "simplify.json");
24
- if (!existsSync(filePath)) return {};
25
- try {
26
- const raw = readFileSync(filePath, "utf-8");
27
- const parsed = JSON.parse(raw);
28
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
29
- console.error("[SIMPLIFY] simplify.json 格式错误:应为对象");
30
- return {};
31
- }
32
- return parsed as SimplifyConfig;
33
- } catch (err) {
34
- console.error(`[SIMPLIFY] 读取 simplify.json 失败: ${(err as Error).message}`);
35
- return {};
36
- }
37
- }
38
-
39
- function getConfig(): SimplifyConfig {
40
- if (!loaded) {
41
- config = loadConfig();
42
- loaded = true;
43
- }
44
- return config!;
45
- }
46
-
47
- /** 热重载 */
48
- export function reloadSimplifyConfig(): void {
49
- loaded = false;
50
- config = null;
51
- }
52
-
53
- /**
54
- * 对模板字符串中的 {field} 占位符做替换。
55
- * fields 为可用字段映射,extra 是额外的上下文(如 tool_use_id 的 id)。
56
- */
57
- function resolveTemplate(template: string, fields: Record<string, unknown>, extra?: Record<string, string>): string {
58
- let result = template;
59
- if (extra) {
60
- for (const [k, v] of Object.entries(extra)) {
61
- result = result.split(`{${k}}`).join(v);
62
- }
63
- }
64
- for (const [k, v] of Object.entries(fields)) {
65
- const strVal = typeof v === "string" ? v : JSON.stringify(v);
66
- result = result.split(`{${k}}`).join(strVal);
67
- }
68
- return result;
69
- }
70
-
71
- /**
72
- * 简化 tool_use 展示。
73
- * 返回 null 表示无规则,调用方应回退到默认格式化。
74
- */
75
- export function simplifyToolUse(name: string, input: unknown): string | null {
76
- const cfg = getConfig();
77
- const rules = cfg.tool_use;
78
- if (!rules) return null;
79
- const rule = rules[name];
80
- if (!rule) return null;
81
-
82
- const fields = typeof input === "object" && input !== null
83
- ? input as Record<string, unknown>
84
- : {};
85
- let result = resolveTemplate(rule.template, fields);
86
- if (result.length > rule.maxLength) {
87
- result = result.slice(0, rule.maxLength) + "...";
88
- }
89
- return result;
90
- }
91
-
92
- /**
93
- * 简化 tool_result 展示。
94
- * 返回 null 表示无规则,调用方应回退到默认格式化。
95
- * toolCallInput 为对应的 tool_use 输入(可选),用于在 result 模板中引用输入字段。
96
- */
97
- export function simplifyToolResult(
98
- name: string,
99
- toolUseId: string,
100
- isError: boolean,
101
- toolCallInput?: unknown,
102
- ): string | null {
103
- const cfg = getConfig();
104
- const rules = cfg.tool_result;
105
- if (!rules) return null;
106
- const rule = rules[name];
107
- if (!rule) return null;
108
-
109
- const id = toolUseId.slice(-6);
110
- const extra = { id };
111
- const fields = toolCallInput && typeof toolCallInput === "object"
112
- ? toolCallInput as Record<string, unknown>
113
- : {};
114
- let result = resolveTemplate(rule.template, fields, extra);
115
- if (isError) result = "❌ " + result;
116
- if (result.length > rule.maxLength) {
117
- result = result.slice(0, rule.maxLength) + "...";
118
- }
119
- return result;
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve as resolvePath } from "node:path";
3
+ import { PROJECT_ROOT } from "./config.ts";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // 消息简化规则 —— 数据驱动,在 simplify.json 中配置
7
+ // ---------------------------------------------------------------------------
8
+
9
+ interface ToolRule {
10
+ template: string;
11
+ maxLength: number;
12
+ }
13
+
14
+ interface SimplifyConfig {
15
+ tool_use?: Record<string, ToolRule>;
16
+ tool_result?: Record<string, ToolRule>;
17
+ }
18
+
19
+ let config: SimplifyConfig | null = null;
20
+ let loaded = false;
21
+
22
+ function loadConfig(): SimplifyConfig {
23
+ const filePath = resolvePath(PROJECT_ROOT, "simplify.json");
24
+ if (!existsSync(filePath)) return {};
25
+ try {
26
+ const raw = readFileSync(filePath, "utf-8");
27
+ const parsed = JSON.parse(raw);
28
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
29
+ console.error("[SIMPLIFY] simplify.json 格式错误:应为对象");
30
+ return {};
31
+ }
32
+ return parsed as SimplifyConfig;
33
+ } catch (err) {
34
+ console.error(`[SIMPLIFY] 读取 simplify.json 失败: ${(err as Error).message}`);
35
+ return {};
36
+ }
37
+ }
38
+
39
+ function getConfig(): SimplifyConfig {
40
+ if (!loaded) {
41
+ config = loadConfig();
42
+ loaded = true;
43
+ }
44
+ return config!;
45
+ }
46
+
47
+ /** 热重载 */
48
+ export function reloadSimplifyConfig(): void {
49
+ loaded = false;
50
+ config = null;
51
+ }
52
+
53
+ /**
54
+ * 对模板字符串中的 {field} 占位符做替换。
55
+ * fields 为可用字段映射,extra 是额外的上下文(如 tool_use_id 的 id)。
56
+ */
57
+ function resolveTemplate(template: string, fields: Record<string, unknown>, extra?: Record<string, string>): string {
58
+ let result = template;
59
+ if (extra) {
60
+ for (const [k, v] of Object.entries(extra)) {
61
+ result = result.split(`{${k}}`).join(v);
62
+ }
63
+ }
64
+ for (const [k, v] of Object.entries(fields)) {
65
+ const strVal = typeof v === "string" ? v : JSON.stringify(v);
66
+ result = result.split(`{${k}}`).join(strVal);
67
+ }
68
+ return result;
69
+ }
70
+
71
+ /**
72
+ * 简化 tool_use 展示。
73
+ * 返回 null 表示无规则,调用方应回退到默认格式化。
74
+ */
75
+ export function simplifyToolUse(name: string, input: unknown): string | null {
76
+ const cfg = getConfig();
77
+ const rules = cfg.tool_use;
78
+ if (!rules) return null;
79
+ const rule = rules[name];
80
+ if (!rule) return null;
81
+
82
+ const fields = typeof input === "object" && input !== null
83
+ ? input as Record<string, unknown>
84
+ : {};
85
+ let result = resolveTemplate(rule.template, fields);
86
+ if (result.length > rule.maxLength) {
87
+ result = result.slice(0, rule.maxLength) + "...";
88
+ }
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * 简化 tool_result 展示。
94
+ * 返回 null 表示无规则,调用方应回退到默认格式化。
95
+ * toolCallInput 为对应的 tool_use 输入(可选),用于在 result 模板中引用输入字段。
96
+ */
97
+ export function simplifyToolResult(
98
+ name: string,
99
+ toolUseId: string,
100
+ isError: boolean,
101
+ toolCallInput?: unknown,
102
+ ): string | null {
103
+ const cfg = getConfig();
104
+ const rules = cfg.tool_result;
105
+ if (!rules) return null;
106
+ const rule = rules[name];
107
+ if (!rule) return null;
108
+
109
+ const id = toolUseId.slice(-6);
110
+ const extra = { id };
111
+ const fields = toolCallInput && typeof toolCallInput === "object"
112
+ ? toolCallInput as Record<string, unknown>
113
+ : {};
114
+ let result = resolveTemplate(rule.template, fields, extra);
115
+ if (isError) result = "❌ " + result;
116
+ if (result.length > rule.maxLength) {
117
+ result = result.slice(0, rule.maxLength) + "...";
118
+ }
119
+ return result;
120
120
  }
@@ -500,21 +500,9 @@ async function handleWechatMessage(
500
500
  // 用户回复,重置 claw 连发计数
501
501
  consecutiveSendCount.set(chatId, 0);
502
502
 
503
- // 非指令消息:立即发送"生成中...",让用户知道消息已收到并正在处理
504
- if (text && !text.startsWith("/") && ilinkWire) {
505
- const ctxToken = contextTokenMap.get(chatId);
506
- if (ctxToken) {
507
- ilinkWire.sendText(chatId, "生成中...", ctxToken).catch(() => {});
508
- } else {
509
- ilinkWire.push(chatId, "生成中...").catch(() => {});
510
- }
511
- // "生成中..." 计为第1条连发消息
512
- consecutiveSendCount.set(chatId, 1);
513
- }
514
-
515
- // WeChat 中所有会话都视为 p2p,/new 复用 p2p 路径(等同飞书 /newh 效果)
516
- // 不 await:避免长 prompt 阻塞后续消息处理(如 /cd、/stop 等命令)
517
- handler(text, chatId, chatId, msgTimestamp, "p2p").catch((err) => {
503
+ // WeChat 中所有会话都视为 p2p,/new 复用 p2p 路径(等同飞书 /newh 效果)
504
+ // await:避免长 prompt 阻塞后续消息处理(如 /cd、/stop 等命令)
505
+ handler(text, chatId, chatId, msgTimestamp, "p2p").catch((err) => {
518
506
  platformLog(`消息处理失败: ${(err as Error).stack ?? (err as Error).message}`);
519
507
  });
520
508
  }