chatccc 0.2.55 → 0.2.56

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/README.md CHANGED
@@ -158,7 +158,8 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
158
158
  "claude": {
159
159
  "enabled": false,
160
160
  "defaultAgent": true,
161
- "model": "",
161
+ "model": "claude-sonnet-4-6",
162
+ "subagentModel": "",
162
163
  "effort": "",
163
164
  "apiKey": "",
164
165
  "baseUrl": ""
@@ -189,6 +190,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
189
190
  | `*.enabled` | 是否启用对应 AI Agent |
190
191
  | `*.defaultAgent` | `/new` 未指定 Agent 时使用哪个工具 |
191
192
  | `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
193
+ | `claude.model` / `claude.subagentModel` | Claude Code 主模型 / subagent 模型;`subagentModel` 仅在第三方 API 模式下注入 `CLAUDE_CODE_SUBAGENT_MODEL` |
192
194
  | `claude.apiKey` / `claude.baseUrl` | 第三方 Anthropic 兼容网关配置;官方 Claude 用户留空 |
193
195
 
194
196
  > 当前 ChatCCC 以 `bypassPermissions` 模式运行,会跳过 Agent 操作确认。请只在可信环境中使用。
@@ -13,7 +13,8 @@
13
13
  "claude": {
14
14
  "enabled": false,
15
15
  "defaultAgent": true,
16
- "model": "",
16
+ "model": "claude-sonnet-4-6",
17
+ "subagentModel": "",
17
18
  "effort": "",
18
19
  "apiKey": "",
19
20
  "baseUrl": ""
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "chatccc",
3
- "version": "0.2.55",
4
- "description": "Feishu bot bridge for Claude Code",
5
- "license": "Apache-2.0",
6
- "type": "module",
7
- "main": "./src/index.ts",
8
- "bin": {
9
- "chatccc": "bin/chatccc.mjs"
10
- },
11
- "files": [
12
- "src/",
13
- "bin/",
14
- "demo/ilink_echo_probe.ts",
15
- "im-skills/",
16
- "images/img_readme_*.jpg",
17
- "images/img_readme_*.png",
18
- "images/avatars/status_*.png",
19
- "images/avatars/badges/",
20
- "package.json",
21
- "README.md",
22
- "config.sample.json"
23
- ],
24
- "scripts": {
25
- "dev": "tsx src/index.ts",
26
- "chatccc": "tsx src/index.ts",
27
- "start": "tsx src/index.ts",
28
- "demo:bot-test": "tsx demo/bot_test.ts",
29
- "demo:bot-test:local": "tsx demo/bot_test.ts --local",
30
- "demo:create-group": "tsx src/index.ts",
31
- "demo:create-group:local": "tsx src/index.ts --local",
32
- "demo:permission-check": "tsx demo/permission_check.ts",
33
- "demo:claude-hi": "tsx demo/claude_say_hi.ts",
34
- "demo:codex-hi": "tsx demo/codex_say_hi.ts",
35
- "demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
36
- "test": "vitest run",
37
- "test:watch": "vitest"
38
- },
39
- "dependencies": {
40
- "@anthropic-ai/claude-agent-sdk": "0.2.133",
41
- "@larksuiteoapi/node-sdk": "^1.59.0",
42
- "@openilink/openilink-sdk-node": "^0.6.0",
43
- "nodemailer": "^8.0.7",
44
- "qrcode-terminal": "^0.12.0",
45
- "sharp": "^0.34.5",
46
- "tsx": "^4.0.0",
47
- "ws": "^8.18.0"
48
- },
49
- "devDependencies": {
50
- "@types/node": "^20.0.0",
51
- "@types/qrcode-terminal": "^0.12.2",
52
- "@types/ws": "^8.18.1",
53
- "typescript": "^5.0.0",
54
- "vitest": "^4.1.5"
55
- },
56
- "engines": {
57
- "node": ">=20"
58
- }
59
- }
1
+ {
2
+ "name": "chatccc",
3
+ "version": "0.2.56",
4
+ "description": "Feishu bot bridge for Claude Code",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./src/index.ts",
8
+ "bin": {
9
+ "chatccc": "bin/chatccc.mjs"
10
+ },
11
+ "files": [
12
+ "src/",
13
+ "bin/",
14
+ "demo/ilink_echo_probe.ts",
15
+ "im-skills/",
16
+ "images/img_readme_*.jpg",
17
+ "images/img_readme_*.png",
18
+ "images/avatars/status_*.png",
19
+ "images/avatars/badges/",
20
+ "package.json",
21
+ "README.md",
22
+ "config.sample.json"
23
+ ],
24
+ "scripts": {
25
+ "dev": "tsx src/index.ts",
26
+ "chatccc": "tsx src/index.ts",
27
+ "start": "tsx src/index.ts",
28
+ "demo:bot-test": "tsx demo/bot_test.ts",
29
+ "demo:bot-test:local": "tsx demo/bot_test.ts --local",
30
+ "demo:create-group": "tsx src/index.ts",
31
+ "demo:create-group:local": "tsx src/index.ts --local",
32
+ "demo:permission-check": "tsx demo/permission_check.ts",
33
+ "demo:claude-hi": "tsx demo/claude_say_hi.ts",
34
+ "demo:codex-hi": "tsx demo/codex_say_hi.ts",
35
+ "demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest"
38
+ },
39
+ "dependencies": {
40
+ "@anthropic-ai/claude-agent-sdk": "0.2.133",
41
+ "@larksuiteoapi/node-sdk": "^1.59.0",
42
+ "@openilink/openilink-sdk-node": "^0.6.0",
43
+ "nodemailer": "^8.0.7",
44
+ "qrcode-terminal": "^0.12.0",
45
+ "sharp": "^0.34.5",
46
+ "tsx": "^4.0.0",
47
+ "ws": "^8.18.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.0.0",
51
+ "@types/qrcode-terminal": "^0.12.2",
52
+ "@types/ws": "^8.18.1",
53
+ "typescript": "^5.0.0",
54
+ "vitest": "^4.1.5"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ }
59
+ }
@@ -788,6 +788,45 @@ describe("createClaudeAdapter — env 注入", () => {
788
788
  expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://gateway.example/anthropic");
789
789
  });
790
790
 
791
+ it("官方 API 模式下 subagentModel 不注入 env,也不覆盖 Claude 登录环境", async () => {
792
+ setupMockCreateSession();
793
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "oauth-from-login";
794
+ const adapter = createClaudeAdapter({
795
+ model: "claude-sonnet-4-6",
796
+ subagentModel: "claude-haiku-4-5-20251001",
797
+ effort: "",
798
+ isEmpty: (v) => v.trim() === "",
799
+ apiKey: "",
800
+ baseUrl: "",
801
+ });
802
+
803
+ await adapter.createSession("/cwd");
804
+
805
+ const opts = sdk.unstable_v2_createSession.mock.calls[0][0];
806
+ expect(opts).not.toHaveProperty("env");
807
+ });
808
+
809
+ it("第三方 API 模式下 subagentModel 通过 CLAUDE_CODE_SUBAGENT_MODEL 覆盖 Haiku", async () => {
810
+ setupMockCreateSession();
811
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "oauth-from-login";
812
+ const adapter = createClaudeAdapter({
813
+ model: "claude-sonnet-4-6",
814
+ subagentModel: "claude-haiku-4-5-20251001",
815
+ effort: "",
816
+ isEmpty: (v) => v.trim() === "",
817
+ apiKey: "sk-thirdparty",
818
+ baseUrl: "https://gateway.example/anthropic",
819
+ });
820
+
821
+ await adapter.createSession("/cwd");
822
+
823
+ const opts = sdk.unstable_v2_createSession.mock.calls[0][0];
824
+ expect(opts.model).toBe("claude-sonnet-4-6");
825
+ expect(opts.env.ANTHROPIC_API_KEY).toBe("sk-thirdparty");
826
+ expect(opts.env.CLAUDE_CODE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
827
+ expect(opts.env.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
828
+ });
829
+
791
830
  it("ChatCCC API config is isolated from Claude settings auth/model env", async () => {
792
831
  setupMockCreateSession();
793
832
  process.env.ANTHROPIC_AUTH_TOKEN = "token-from-claude-settings";
@@ -12,6 +12,7 @@ import {
12
12
  CLAUDE_BASE_URL,
13
13
  CLAUDE_EFFORT,
14
14
  CLAUDE_MODEL,
15
+ CLAUDE_SUBAGENT_MODEL,
15
16
  CURSOR_AGENT_ARGS,
16
17
  CURSOR_AGENT_COMMAND,
17
18
  FEISHU_ENABLED,
@@ -45,6 +46,7 @@ const baseAppConfig: AppConfig = {
45
46
  enabled: true,
46
47
  defaultAgent: true,
47
48
  model: "initial-model",
49
+ subagentModel: "initial-subagent-model",
48
50
  effort: "initial-effort",
49
51
  apiKey: "sk-initial",
50
52
  baseUrl: "https://initial.gw/anthropic",
@@ -78,20 +80,22 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
78
80
  expect(APP_SECRET).toBe("NEW_APP_SECRET");
79
81
  });
80
82
 
81
- it("更新 Claude 配置(model / effort / apiKey / baseUrl)", () => {
83
+ it("更新 Claude 配置(model / subagentModel / effort / apiKey / baseUrl)", () => {
82
84
  applyLoadedConfig({
83
85
  ...structuredClone(baseAppConfig),
84
86
  claude: {
85
87
  enabled: true,
86
88
  defaultAgent: true,
87
- model: "deepseek-v4-pro",
89
+ model: "claude-sonnet-4-6",
90
+ subagentModel: "claude-haiku-4-5-20251001",
88
91
  effort: "high",
89
92
  apiKey: "sk-newkey",
90
93
  baseUrl: "https://gw2.example/anthropic",
91
94
  },
92
95
  });
93
96
 
94
- expect(CLAUDE_MODEL).toBe("deepseek-v4-pro");
97
+ expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
98
+ expect(CLAUDE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
95
99
  expect(CLAUDE_EFFORT).toBe("high");
96
100
  expect(CLAUDE_API_KEY).toBe("sk-newkey");
97
101
  expect(CLAUDE_BASE_URL).toBe("https://gw2.example/anthropic");
@@ -11,9 +11,20 @@ describe("config.sample.json", () => {
11
11
  feishu?: { enabled?: unknown };
12
12
  ilink?: { enabled?: unknown };
13
13
  };
14
+ claude?: { model?: unknown; subagentModel?: unknown };
14
15
  };
15
16
 
16
17
  expect(sample.platforms?.feishu?.enabled).toBe(true);
17
18
  expect(sample.platforms?.ilink?.enabled).toBe(true);
18
19
  });
20
+
21
+ it("uses an Anthropic official main model and leaves subagent override empty by default", () => {
22
+ const configSamplePath = join(process.cwd(), "config.sample.json");
23
+ const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
24
+ claude?: { model?: unknown; subagentModel?: unknown };
25
+ };
26
+
27
+ expect(sample.claude?.model).toBe("claude-sonnet-4-6");
28
+ expect(sample.claude?.subagentModel).toBe("");
29
+ });
19
30
  });
@@ -3,6 +3,7 @@ import {
3
3
  applyClaudeApiMode,
4
4
  chooseStartPath,
5
5
  detectClaudeApiMode,
6
+ unflattenConfig,
6
7
  } from "../web-ui.ts";
7
8
 
8
9
  // ---------------------------------------------------------------------------
@@ -129,6 +130,22 @@ describe("applyClaudeApiMode", () => {
129
130
  });
130
131
  });
131
132
 
133
+ describe("unflattenConfig", () => {
134
+ it("maps Claude subagent model into claude.subagentModel", () => {
135
+ expect(
136
+ unflattenConfig({
137
+ CHATCCC_ANTHROPIC_MODEL: "claude-sonnet-4-6",
138
+ CHATCCC_ANTHROPIC_SUBAGENT_MODEL: "claude-haiku-4-5-20251001",
139
+ }),
140
+ ).toEqual({
141
+ claude: {
142
+ model: "claude-sonnet-4-6",
143
+ subagentModel: "claude-haiku-4-5-20251001",
144
+ },
145
+ });
146
+ });
147
+ });
148
+
132
149
  // ---------------------------------------------------------------------------
133
150
  // chooseStartPath — /api/start 的路径选择
134
151
  // 关键护栏:
@@ -29,4 +29,29 @@ describe("createWechatAdapter", () => {
29
29
  expect(text).toContain("/new");
30
30
  expect(log).toHaveBeenCalledWith("[WECHAT] sendRawCard degraded to text");
31
31
  });
32
+
33
+ it("reports unsent non-final messages after the claw limit", async () => {
34
+ const wire = {
35
+ push: vi.fn(async (_chatId: string, _text: string) => "msg-id"),
36
+ sendText: vi.fn(
37
+ async (_chatId: string, _text: string, _contextToken?: string) =>
38
+ "msg-id",
39
+ ),
40
+ };
41
+ const log = vi.fn();
42
+ const platform = createWechatAdapter({
43
+ getWire: () => wire,
44
+ log,
45
+ });
46
+
47
+ for (let i = 0; i < 10; i++) {
48
+ await expect(platform.sendText("wx-chat-limit", `chunk ${i}`)).resolves.toBe(true);
49
+ }
50
+
51
+ await expect(platform.sendText("wx-chat-limit", "chunk 10")).resolves.toBe(false);
52
+ expect(wire.push).toHaveBeenCalledTimes(10);
53
+ expect(log).toHaveBeenCalledWith(
54
+ "[WECHAT] sendText skipped (claw limit): chatId=wx-chat-limit count=11",
55
+ );
56
+ });
32
57
  });
@@ -53,6 +53,7 @@ interface SdkMessageLike {
53
53
 
54
54
  export interface ClaudeAdapterOptions {
55
55
  model: string;
56
+ subagentModel?: string;
56
57
  effort: string;
57
58
  /** 判断字段是否为"不传给 SDK"的占位(项目约定:空字符串/全空白) */
58
59
  isEmpty: (value: string) => boolean;
@@ -83,14 +84,17 @@ export interface ClaudeAdapterOptions {
83
84
  function buildSdkEnv(
84
85
  apiKey: string | undefined,
85
86
  baseUrl: string | undefined,
87
+ subagentModel: string | undefined,
86
88
  ): Record<string, string | undefined> | undefined {
87
89
  const apiKeyTrim = (apiKey ?? "").trim();
88
90
  const baseUrlTrim = (baseUrl ?? "").trim();
89
- if (!apiKeyTrim && !baseUrlTrim) return undefined;
91
+ const subagentModelTrim = (subagentModel ?? "").trim();
92
+ const hasApiOverride = Boolean(apiKeyTrim || baseUrlTrim);
93
+ if (!hasApiOverride) return undefined;
90
94
 
91
95
  const env: Record<string, string | undefined> = { ...process.env };
92
- // ChatCCC's Claude API config is authoritative when present. Remove Claude
93
- // Code/user settings env that can silently override gateway/auth/model choice.
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.
94
98
  delete env.ANTHROPIC_AUTH_TOKEN;
95
99
  delete env.CLAUDE_CODE_OAUTH_TOKEN;
96
100
  delete env.ANTHROPIC_MODEL;
@@ -101,8 +105,12 @@ function buildSdkEnv(
101
105
  delete env.CLAUDE_CODE_EFFORT_LEVEL;
102
106
 
103
107
  if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
104
- if (baseUrlTrim) env.ANTHROPIC_BASE_URL = baseUrlTrim;
105
- else delete env.ANTHROPIC_BASE_URL;
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;
106
114
  return env;
107
115
  }
108
116
 
@@ -126,6 +134,7 @@ function buildSessionOptions(
126
134
  isEmpty: (value: string) => boolean,
127
135
  apiKey: string | undefined,
128
136
  baseUrl: string | undefined,
137
+ subagentModel: string | undefined,
129
138
  ): Record<string, unknown> {
130
139
  const o: Record<string, unknown> = {
131
140
  cwd,
@@ -136,7 +145,7 @@ function buildSessionOptions(
136
145
  };
137
146
  if (!isEmpty(model)) o.model = model;
138
147
  if (!isEmpty(effort)) o.effort = effort;
139
- const env = buildSdkEnv(apiKey, baseUrl);
148
+ const env = buildSdkEnv(apiKey, baseUrl, subagentModel);
140
149
  if (env) o.env = env;
141
150
  return o;
142
151
  }
@@ -213,6 +222,7 @@ class ClaudeAdapter implements ToolAdapter {
213
222
  readonly sessionDescPrefix = "Claude Code Session:";
214
223
  private model: string;
215
224
  private effort: string;
225
+ private subagentModel: string | undefined;
216
226
  private isEmpty: (value: string) => boolean;
217
227
  private apiKey: string | undefined;
218
228
  private baseUrl: string | undefined;
@@ -220,6 +230,7 @@ class ClaudeAdapter implements ToolAdapter {
220
230
  constructor(options: ClaudeAdapterOptions) {
221
231
  this.model = options.model;
222
232
  this.effort = options.effort;
233
+ this.subagentModel = options.subagentModel;
223
234
  this.isEmpty = options.isEmpty;
224
235
  this.apiKey = options.apiKey;
225
236
  this.baseUrl = options.baseUrl;
@@ -233,6 +244,7 @@ class ClaudeAdapter implements ToolAdapter {
233
244
  this.isEmpty,
234
245
  this.apiKey,
235
246
  this.baseUrl,
247
+ this.subagentModel,
236
248
  );
237
249
  const session = unstable_v2_createSession(sessionOpts as any);
238
250
 
@@ -277,12 +289,20 @@ class ClaudeAdapter implements ToolAdapter {
277
289
  this.isEmpty,
278
290
  this.apiKey,
279
291
  this.baseUrl,
292
+ this.subagentModel,
280
293
  );
281
294
  const session = unstable_v2_resumeSession(
282
295
  sessionId,
283
296
  sessionOpts as any,
284
297
  );
285
298
 
299
+ if (signal?.aborted) {
300
+ session.close();
301
+ return;
302
+ }
303
+ const onAbort = () => { session.close(); };
304
+ signal?.addEventListener("abort", onAbort, { once: true });
305
+
286
306
  await session.send(userText);
287
307
 
288
308
  const stream = session.stream();
@@ -296,6 +316,7 @@ class ClaudeAdapter implements ToolAdapter {
296
316
  if (normalized) yield normalized;
297
317
  }
298
318
  } finally {
319
+ signal?.removeEventListener("abort", onAbort);
299
320
  session.close();
300
321
  }
301
322
  }
@@ -242,6 +242,9 @@ class CodexAdapter implements ToolAdapter {
242
242
 
243
243
  const proc = spawnCodex(args, cwd, userText);
244
244
 
245
+ const onAbort = () => { proc.kill(); };
246
+ signal?.addEventListener("abort", onAbort, { once: true });
247
+
245
248
  try {
246
249
  for await (const raw of readJsonLines(proc, signal)) {
247
250
  if (signal?.aborted) break;
@@ -261,6 +264,7 @@ class CodexAdapter implements ToolAdapter {
261
264
  if (normalized) yield normalized;
262
265
  }
263
266
  } finally {
267
+ signal?.removeEventListener("abort", onAbort);
264
268
  proc.kill();
265
269
  }
266
270
  }
@@ -346,6 +346,9 @@ class CursorAdapter implements ToolAdapter {
346
346
  const proc = spawnAgent(["--resume", sessionId], cwd, userText);
347
347
  this.activeProcs.add(proc);
348
348
 
349
+ const onAbort = () => { proc.kill(); };
350
+ signal?.addEventListener("abort", onAbort, { once: true });
351
+
349
352
  try {
350
353
  for await (const raw of readJsonLines(proc, signal)) {
351
354
  if (signal?.aborted) break;
@@ -367,6 +370,7 @@ class CursorAdapter implements ToolAdapter {
367
370
  if (normalized) yield normalized;
368
371
  }
369
372
  } finally {
373
+ signal?.removeEventListener("abort", onAbort);
370
374
  proc.kill();
371
375
  this.activeProcs.delete(proc);
372
376
  }
package/src/config.ts CHANGED
@@ -63,6 +63,7 @@ export interface ClaudeConfig {
63
63
  /** 是否作为 /new 未指定工具时使用的默认 Agent */
64
64
  defaultAgent: boolean;
65
65
  model: string;
66
+ subagentModel: string;
66
67
  effort: string;
67
68
  apiKey: string;
68
69
  baseUrl: string;
@@ -302,7 +303,7 @@ function loadConfig(): AppConfig {
302
303
  port: 18080,
303
304
  gitTimeoutSeconds: 180,
304
305
  allowInterrupt: false,
305
- claude: { enabled: false, defaultAgent: true, model: "", effort: "", apiKey: "", baseUrl: "" },
306
+ claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "" },
306
307
  cursor: { enabled: false, defaultAgent: false, path: "", model: "claude-opus-4-7-max" },
307
308
  codex: { enabled: false, defaultAgent: false, path: "", model: "", effort: "" },
308
309
  };
@@ -336,6 +337,9 @@ function loadConfig(): AppConfig {
336
337
  let raw: string;
337
338
  try {
338
339
  raw = readFileSync(CONFIG_FILE, "utf-8");
340
+ // 移除可能意外写入的 UTF-8 BOM(如通过记事本编辑等场景),
341
+ // 避免 JSON.parse 因 BOM 失败导致返回空默认值、丢失所有配置。
342
+ if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
339
343
  } catch (err) {
340
344
  console.error(`[CONFIG] 无法读取 config.json: ${(err as Error).message}`);
341
345
  return defaults;
@@ -378,6 +382,7 @@ function loadConfig(): AppConfig {
378
382
  const claudeNonEmpty = (): boolean =>
379
383
  Boolean(
380
384
  (typeof claude.model === "string" && claude.model.trim()) ||
385
+ (typeof claude.subagentModel === "string" && claude.subagentModel.trim()) ||
381
386
  (typeof claude.effort === "string" && claude.effort.trim()) ||
382
387
  (typeof claude.apiKey === "string" && claude.apiKey.trim()) ||
383
388
  (typeof claude.baseUrl === "string" && claude.baseUrl.trim()),
@@ -438,6 +443,7 @@ function loadConfig(): AppConfig {
438
443
  enabled: claudeEnabled,
439
444
  defaultAgent: defaultTool === "claude",
440
445
  model: normalizeOptionalConfigField(claude.model, { label: "claude.model" }),
446
+ subagentModel: normalizeOptionalConfigField(claude.subagentModel, { label: "claude.subagentModel" }),
441
447
  effort: normalizeOptionalConfigField(claude.effort, { label: "claude.effort" }),
442
448
  apiKey: claude.apiKey ?? "",
443
449
  baseUrl: claude.baseUrl ?? "",
@@ -498,6 +504,7 @@ export const CHATCCC_PORT = config.port;
498
504
  export const LOCAL_RELAY_URL = `ws://127.0.0.1:${CHATCCC_PORT}`;
499
505
 
500
506
  export let CLAUDE_MODEL = config.claude.model;
507
+ export let CLAUDE_SUBAGENT_MODEL = config.claude.subagentModel;
501
508
  export let CLAUDE_EFFORT = config.claude.effort;
502
509
  /** Anthropic 兼容网关的 API key(仅经 SDK 子进程 env 传递,从不写入主进程 process.env) */
503
510
  export let CLAUDE_API_KEY = config.claude.apiKey;
@@ -573,6 +580,7 @@ export function applyLoadedConfig(next: AppConfig): void {
573
580
  ILINK_ENABLED = next.platforms.ilink.enabled;
574
581
  ILINK_REUSE_TOKEN_ON_START = next.platforms.ilink.reuseTokenOnStart ?? true;
575
582
  CLAUDE_MODEL = next.claude.model;
583
+ CLAUDE_SUBAGENT_MODEL = next.claude.subagentModel;
576
584
  CLAUDE_EFFORT = next.claude.effort;
577
585
  CLAUDE_API_KEY = next.claude.apiKey;
578
586
  CLAUDE_BASE_URL = next.claude.baseUrl;
@@ -705,6 +713,11 @@ export function reportEnvironmentVariableReadout(): void {
705
713
  ` Claude 模型: ${anthropicConfigDisplay(CLAUDE_MODEL)}(留空时不向 SDK 传 model)`
706
714
  );
707
715
 
716
+ console.log(` [默认] [可选] claude.subagentModel`);
717
+ console.log(
718
+ ` Claude Subagent 模型: ${anthropicConfigDisplay(CLAUDE_SUBAGENT_MODEL)}(留空时不向 SDK 传 CLAUDE_CODE_SUBAGENT_MODEL)`
719
+ );
720
+
708
721
  console.log(` [默认] [可选] claude.effort`);
709
722
  console.log(
710
723
  ` 思考深度: ${anthropicConfigDisplay(CLAUDE_EFFORT)}(留空时不向 SDK 传 effort)`
package/src/index.ts CHANGED
@@ -85,6 +85,7 @@ import {
85
85
  } from "./cardkit.ts";
86
86
  import {
87
87
  MAX_PROCESSED,
88
+ clearAdapterCache,
88
89
  loadSessionRegistryForBinding,
89
90
  processedMessages,
90
91
  rebuildBindingsFromRegistry,
@@ -745,6 +746,7 @@ async function main(): Promise<void> {
745
746
  // 会话时自动看到新值)。setup 首次激活走 onActivate 路径,不依赖此 hook。
746
747
  setReloadConfigHook(() => {
747
748
  reloadConfigFromDisk();
749
+ clearAdapterCache();
748
750
  appendStartupTrace("reload-from-ui: config reloaded", {
749
751
  appIdMask: maskAppId(APP_ID),
750
752
  });
@@ -769,6 +771,7 @@ async function main(): Promise<void> {
769
771
  startSetupMode(CHATCCC_PORT, {
770
772
  onActivate: async (httpServer: Server) => {
771
773
  reloadConfigFromDisk();
774
+ clearAdapterCache();
772
775
  appendStartupTrace("setup-activate: reloaded config from disk", {
773
776
  appIdMaskAfterReload: maskAppId(APP_ID),
774
777
  });
package/src/session.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  CLAUDE_BASE_URL,
7
7
  CLAUDE_EFFORT,
8
8
  CLAUDE_MODEL,
9
+ CLAUDE_SUBAGENT_MODEL,
9
10
  CHATCCC_PORT,
10
11
  PROJECT_ROOT,
11
12
  SESSIONS_FILE,
@@ -172,6 +173,7 @@ export function getAdapterForTool(tool: string): ToolAdapter {
172
173
  } else {
173
174
  adapter = createClaudeAdapter({
174
175
  model: CLAUDE_MODEL,
176
+ subagentModel: CLAUDE_SUBAGENT_MODEL,
175
177
  effort: CLAUDE_EFFORT,
176
178
  isEmpty: isAnthropicConfigEmpty,
177
179
  apiKey: CLAUDE_API_KEY,
@@ -606,7 +608,7 @@ function formatToolConfigForLog(tool: string, sessionModel?: string): string {
606
608
  : "effort=(由 codex config.toml 决定)";
607
609
  return `model=${modelStr}, ${effortStr}`;
608
610
  }
609
- return `model=${anthropicConfigDisplay(CLAUDE_MODEL)}, effort=${anthropicConfigDisplay(CLAUDE_EFFORT)}`;
611
+ return `model=${anthropicConfigDisplay(CLAUDE_MODEL)}, subagentModel=${anthropicConfigDisplay(CLAUDE_SUBAGENT_MODEL)}, effort=${anthropicConfigDisplay(CLAUDE_EFFORT)}`;
610
612
  }
611
613
 
612
614
  export async function initClaudeSession(tool: string, overrideCwd?: string, chatId?: string): Promise<{ sessionId: string; cwd: string }> {
@@ -1322,6 +1324,10 @@ export function _setAdapterForToolForTest(tool: string, adapter: ToolAdapter): v
1322
1324
  adapterCache.set(tool, adapter);
1323
1325
  }
1324
1326
 
1325
- export function _clearAdapterCacheForTest(): void {
1327
+ export function clearAdapterCache(): void {
1326
1328
  adapterCache.clear();
1327
1329
  }
1330
+
1331
+ export function _clearAdapterCacheForTest(): void {
1332
+ clearAdapterCache();
1333
+ }
package/src/web-ui.ts CHANGED
@@ -31,7 +31,7 @@ interface AppConfig {
31
31
  platforms?: { feishu?: { enabled?: boolean }; ilink?: { enabled?: boolean } };
32
32
  port?: number;
33
33
  gitTimeoutSeconds?: number;
34
- claude?: { enabled?: boolean; defaultAgent?: boolean; model?: string; effort?: string; apiKey?: string; baseUrl?: string };
34
+ claude?: { enabled?: boolean; defaultAgent?: boolean; model?: string; subagentModel?: string; effort?: string; apiKey?: string; baseUrl?: string };
35
35
  // `command` 是已废弃的旧字段名,保留只读以兼容升级前的 config.json
36
36
  cursor?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string };
37
37
  codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; effort?: string };
@@ -126,7 +126,11 @@ function readToolPath(tool?: { path?: string; command?: string }): string {
126
126
  function loadConfig(): AppConfig {
127
127
  if (!existsSync(CONFIG_FILE)) return {};
128
128
  try {
129
- return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
129
+ let raw = readFileSync(CONFIG_FILE, "utf8");
130
+ // 移除可能意外写入的 UTF-8 BOM,避免 JSON.parse 失败导致返回空对象、
131
+ // UI 显示所有配置为空。
132
+ if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
133
+ return JSON.parse(raw);
130
134
  } catch {
131
135
  return {};
132
136
  }
@@ -330,7 +334,7 @@ function deepMerge(target: Record<string, unknown>, source: Record<string, unkno
330
334
  }
331
335
 
332
336
  // Convert flat key-value pairs to nested config structure
333
- function unflattenConfig(flat: Record<string, unknown>): Record<string, unknown> {
337
+ export function unflattenConfig(flat: Record<string, unknown>): Record<string, unknown> {
334
338
  const result: Record<string, unknown> = {};
335
339
  for (const [key, val] of Object.entries(flat)) {
336
340
  if (key === "CHATCCC_APP_ID") {
@@ -360,6 +364,9 @@ function unflattenConfig(flat: Record<string, unknown>): Record<string, unknown>
360
364
  } else if (key === "CHATCCC_ANTHROPIC_MODEL") {
361
365
  result.claude = result.claude || {};
362
366
  (result.claude as Record<string, unknown>).model = val;
367
+ } else if (key === "CHATCCC_ANTHROPIC_SUBAGENT_MODEL") {
368
+ result.claude = result.claude || {};
369
+ (result.claude as Record<string, unknown>).subagentModel = val;
363
370
  } else if (key === "CHATCCC_ANTHROPIC_EFFORT") {
364
371
  result.claude = result.claude || {};
365
372
  (result.claude as Record<string, unknown>).effort = val;
@@ -705,6 +712,10 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
705
712
  <label>模型</label>
706
713
  <input type="text" id="field-CHATCCC_ANTHROPIC_MODEL" placeholder="留空表示不向 SDK 传 model">
707
714
  </div>
715
+ <div class="form-group" id="claude-subagent-model-field">
716
+ <label>Subagent 模型</label>
717
+ <input type="text" id="field-CHATCCC_ANTHROPIC_SUBAGENT_MODEL" placeholder="留空表示不向 SDK 传 CLAUDE_CODE_SUBAGENT_MODEL">
718
+ </div>
708
719
  <div class="form-group">
709
720
  <label>思考深度 (Effort)</label>
710
721
  <input type="text" id="field-CHATCCC_ANTHROPIC_EFFORT" placeholder="留空表示不向 SDK 传 effort">
@@ -844,6 +855,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
844
855
  <div class="config-row" id="cfg-row-CLAUDE_API_KEY"><span class="key">API Key</span><span class="val" id="cfg-CLAUDE_API_KEY">-</span></div>
845
856
  <div class="config-row" id="cfg-row-CLAUDE_BASE_URL"><span class="key">Base URL</span><span class="val" id="cfg-CLAUDE_BASE_URL">-</span></div>
846
857
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-ANTHROPIC_MODEL">-</span></div>
858
+ <div class="config-row" id="cfg-row-ANTHROPIC_SUBAGENT_MODEL"><span class="key">Subagent 模型</span><span class="val" id="cfg-ANTHROPIC_SUBAGENT_MODEL">-</span></div>
847
859
  <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-ANTHROPIC_EFFORT">-</span></div>
848
860
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
849
861
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
@@ -911,7 +923,7 @@ let state = {
911
923
  var step2InputBound = false;
912
924
 
913
925
  const AGENT_FIELDS = {
914
- claude: ['CLAUDE_API_KEY','CLAUDE_BASE_URL','CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_EFFORT'],
926
+ claude: ['CLAUDE_API_KEY','CLAUDE_BASE_URL','CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT'],
915
927
  cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL'],
916
928
  codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_EFFORT']
917
929
  };
@@ -925,7 +937,11 @@ var claudeApiMode = 'official';
925
937
  // 示例只是引导);官方模式不列举任何具体值,避免给出过期/不支持的取值产生误导。
926
938
  var CLAUDE_MODEL_PLACEHOLDER = {
927
939
  official: '留空表示不向 SDK 传 model',
928
- thirdparty: 'deepseek-v4-pro'
940
+ thirdparty: 'claude-sonnet-4-6'
941
+ };
942
+ var CLAUDE_SUBAGENT_MODEL_PLACEHOLDER = {
943
+ official: '留空表示不向 SDK 传 CLAUDE_CODE_SUBAGENT_MODEL',
944
+ thirdparty: 'claude-haiku-4-5-20251001'
929
945
  };
930
946
  var CLAUDE_EFFORT_PLACEHOLDER = {
931
947
  official: '留空表示不向 SDK 传 effort',
@@ -943,13 +959,23 @@ function onClaudeApiModeChange(mode) {
943
959
  if (editThirdPartyEl) {
944
960
  editThirdPartyEl.classList.toggle('hidden', mode !== 'thirdparty');
945
961
  }
962
+ var wizardSubagentField = document.getElementById('claude-subagent-model-field');
963
+ if (wizardSubagentField) wizardSubagentField.classList.toggle('hidden', mode !== 'thirdparty');
964
+ document.querySelectorAll('[data-claude-subagent-field]').forEach(function(el){
965
+ el.classList.toggle('hidden', mode !== 'thirdparty');
966
+ });
946
967
  // 同步 model / effort 输入框的 placeholder(Wizard + Edit Modal 两处都更新)
947
968
  var modelPlaceholder = CLAUDE_MODEL_PLACEHOLDER[mode] || CLAUDE_MODEL_PLACEHOLDER.official;
969
+ var subagentModelPlaceholder = CLAUDE_SUBAGENT_MODEL_PLACEHOLDER[mode] || CLAUDE_SUBAGENT_MODEL_PLACEHOLDER.official;
948
970
  var effortPlaceholder = CLAUDE_EFFORT_PLACEHOLDER[mode] || CLAUDE_EFFORT_PLACEHOLDER.official;
949
971
  var wizardModelEl = document.getElementById('field-CHATCCC_ANTHROPIC_MODEL');
950
972
  if (wizardModelEl) wizardModelEl.placeholder = modelPlaceholder;
951
973
  var editModelEl = document.getElementById('edit-CHATCCC_ANTHROPIC_MODEL');
952
974
  if (editModelEl) editModelEl.placeholder = modelPlaceholder;
975
+ var wizardSubagentModelEl = document.getElementById('field-CHATCCC_ANTHROPIC_SUBAGENT_MODEL');
976
+ if (wizardSubagentModelEl) wizardSubagentModelEl.placeholder = subagentModelPlaceholder;
977
+ var editSubagentModelEl = document.getElementById('edit-CHATCCC_ANTHROPIC_SUBAGENT_MODEL');
978
+ if (editSubagentModelEl) editSubagentModelEl.placeholder = subagentModelPlaceholder;
953
979
  var wizardEffortEl = document.getElementById('field-CHATCCC_ANTHROPIC_EFFORT');
954
980
  if (wizardEffortEl) wizardEffortEl.placeholder = effortPlaceholder;
955
981
  var editEffortEl = document.getElementById('edit-CHATCCC_ANTHROPIC_EFFORT');
@@ -1200,7 +1226,7 @@ function isAgentEnabled(node, keys) {
1200
1226
  return false;
1201
1227
  }
1202
1228
 
1203
- var CLAUDE_FALLBACK_KEYS = ['apiKey','baseUrl','model','effort'];
1229
+ var CLAUDE_FALLBACK_KEYS = ['apiKey','baseUrl','model','subagentModel','effort'];
1204
1230
  var CURSOR_FALLBACK_KEYS = ['path','command','model'];
1205
1231
  var CODEX_FALLBACK_KEYS = ['path','command','model','effort'];
1206
1232
 
@@ -1210,6 +1236,7 @@ function renderStep2() {
1210
1236
  prefillNested('field-CLAUDE_API_KEY', c.claude.apiKey);
1211
1237
  prefillNested('field-CLAUDE_BASE_URL', c.claude.baseUrl);
1212
1238
  prefillNested('field-CHATCCC_ANTHROPIC_MODEL', c.claude.model);
1239
+ prefillNested('field-CHATCCC_ANTHROPIC_SUBAGENT_MODEL', c.claude.subagentModel);
1213
1240
  prefillNested('field-CHATCCC_ANTHROPIC_EFFORT', c.claude.effort);
1214
1241
  }
1215
1242
  // 按已有 apiKey/baseUrl 判定初始 API 模式,并相应显示/隐藏字段
@@ -1285,6 +1312,7 @@ function collectAllFields() {
1285
1312
  vars.CHATCCC_CODEX_DEFAULT_AGENT = state.defaultAgent === 'codex';
1286
1313
  if (state.agentsEnabled.claude) {
1287
1314
  AGENT_FIELDS.claude.forEach(function(key){
1315
+ if (claudeApiMode !== 'thirdparty' && key === 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL') return;
1288
1316
  var el = document.getElementById('field-' + key);
1289
1317
  if (el && el.value.trim()) vars[key] = el.value.trim();
1290
1318
  });
@@ -1345,6 +1373,7 @@ function renderStep3() {
1345
1373
  if (claudeApiMode === 'thirdparty') {
1346
1374
  lines.push('<div class="config-row"><span class="key">API Key</span><span class="val">' + (vars.CLAUDE_API_KEY ? '***已设置***' : '(未设置)') + '</span></div>');
1347
1375
  if (vars.CLAUDE_BASE_URL) lines.push('<div class="config-row"><span class="key">Base URL</span><span class="val">' + vars.CLAUDE_BASE_URL + '</span></div>');
1376
+ lines.push('<div class="config-row"><span class="key">Subagent 模型</span><span class="val">' + (vars.CHATCCC_ANTHROPIC_SUBAGENT_MODEL || '(留空)') + '</span></div>');
1348
1377
  }
1349
1378
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_ANTHROPIC_MODEL || '(留空)') + '</span></div>');
1350
1379
  lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_ANTHROPIC_EFFORT || '(留空)') + '</span></div>');
@@ -1542,6 +1571,8 @@ function updateDashboardUI() {
1542
1571
  document.getElementById('cfg-CLAUDE_API_KEY').textContent = claudeApiKey ? '***已设置***' : '-';
1543
1572
  document.getElementById('cfg-CLAUDE_BASE_URL').textContent = claudeBaseUrl || '-';
1544
1573
  document.getElementById('cfg-ANTHROPIC_MODEL').textContent = (c.claude && c.claude.model) || '(留空)';
1574
+ document.getElementById('cfg-row-ANTHROPIC_SUBAGENT_MODEL').style.display = isThirdPartyClaude ? '' : 'none';
1575
+ document.getElementById('cfg-ANTHROPIC_SUBAGENT_MODEL').textContent = (c.claude && c.claude.subagentModel) || '(留空)';
1545
1576
  document.getElementById('cfg-ANTHROPIC_EFFORT').textContent = (c.claude && c.claude.effort) || '(留空)';
1546
1577
  document.getElementById('cfg-CURSOR_PATH').textContent = (c.cursor && (c.cursor.path || c.cursor.command)) || '-';
1547
1578
  document.getElementById('cfg-CURSOR_MODEL').textContent = (c.cursor && c.cursor.model) || '(留空)';
@@ -1603,7 +1634,7 @@ function editSection(section) {
1603
1634
  var labelMap = {
1604
1635
  'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
1605
1636
  'CLAUDE_API_KEY': 'API Key', 'CLAUDE_BASE_URL': 'Base URL',
1606
- 'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
1637
+ 'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL': 'Subagent 模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
1607
1638
  'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型',
1608
1639
  'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_EFFORT': 'Effort'
1609
1640
  };
@@ -1630,6 +1661,7 @@ function editSection(section) {
1630
1661
  }
1631
1662
 
1632
1663
  var thirdPartyOpened = false;
1664
+ var thirdPartyClosed = false;
1633
1665
  fields.forEach(function(key){
1634
1666
  var val = state.config[key] || '';
1635
1667
  // Also check nested config
@@ -1641,6 +1673,7 @@ function editSection(section) {
1641
1673
  if (key === 'CLAUDE_API_KEY') val = state.config.claude.apiKey || '';
1642
1674
  else if (key === 'CLAUDE_BASE_URL') val = state.config.claude.baseUrl || '';
1643
1675
  else if (key === 'CHATCCC_ANTHROPIC_MODEL') val = state.config.claude.model || '';
1676
+ else if (key === 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL') val = state.config.claude.subagentModel || '';
1644
1677
  else if (key === 'CHATCCC_ANTHROPIC_EFFORT') val = state.config.claude.effort || '';
1645
1678
  } else if (section === 'cursor' && state.config.cursor) {
1646
1679
  if (key === 'CHATCCC_CURSOR_PATH') val = state.config.cursor.path || state.config.cursor.command || '';
@@ -1653,18 +1686,28 @@ function editSection(section) {
1653
1686
  }
1654
1687
  var isSecret = key.includes('SECRET') || key.includes('API_KEY');
1655
1688
  var isClaudeThirdPartyField = section === 'claude' && (key === 'CLAUDE_API_KEY' || key === 'CLAUDE_BASE_URL');
1689
+ var isClaudeSubagentField = section === 'claude' && key === 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL';
1656
1690
 
1657
1691
  if (isClaudeThirdPartyField && !thirdPartyOpened) {
1658
1692
  html += '<div id="edit-claude-thirdparty-fields"' + (claudeApiMode === 'thirdparty' ? '' : ' class="hidden"') + '>';
1659
1693
  thirdPartyOpened = true;
1660
1694
  }
1661
1695
 
1662
- html += '<div class="form-group"><label>' + (labelMap[key] || key) + '</label>';
1696
+ // 在第三方字段之后、第一个通用字段之前闭合 wrapper,
1697
+ // 避免 model / subagentModel / effort 被包裹在 hidden div 中。
1698
+ if (thirdPartyOpened && !thirdPartyClosed && !isClaudeThirdPartyField) {
1699
+ html += '</div>';
1700
+ thirdPartyClosed = true;
1701
+ }
1702
+
1703
+ var groupClass = 'form-group' + (isClaudeSubagentField && claudeApiMode !== 'thirdparty' ? ' hidden' : '');
1704
+ var subagentAttr = isClaudeSubagentField ? ' data-claude-subagent-field="1"' : '';
1705
+ html += '<div class="' + groupClass + '"' + subagentAttr + '><label>' + (labelMap[key] || key) + '</label>';
1663
1706
  html += '<input type="' + (isSecret ? 'password' : 'text') + '" id="edit-' + key + '" value="' + String(val).replace(/"/g,'&quot;') + '">';
1664
1707
  html += '</div>';
1665
1708
  });
1666
1709
 
1667
- if (thirdPartyOpened) html += '</div>';
1710
+ if (thirdPartyOpened && !thirdPartyClosed) html += '</div>';
1668
1711
 
1669
1712
  document.getElementById('edit-modal-fields').innerHTML = html;
1670
1713
  document.getElementById('edit-modal').classList.remove('hidden');
@@ -1688,6 +1731,7 @@ async function saveEdit() {
1688
1731
 
1689
1732
  var vars = {};
1690
1733
  fields.forEach(function(key){
1734
+ if (editSectionType === 'claude' && claudeApiMode !== 'thirdparty' && key === 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL') return;
1691
1735
  var el = document.getElementById('edit-' + key);
1692
1736
  if (el) vars[key] = el.value.trim();
1693
1737
  });
@@ -157,7 +157,7 @@ export function createWechatAdapter(
157
157
 
158
158
  // 第10条且非最终消息:附加 claw 限制提示
159
159
  if (count === 10 && !isFinal) {
160
- text = text + "\n━━━━━━━━━━━━━━━━━━━━\n后台工作中,由于微信claw机制限制,请唤醒我才能继续发送消息";
160
+ text = text + "\n━━ 后台工作中,由于微信claw机制限制,请唤醒我才能继续发送消息";
161
161
  }
162
162
 
163
163
  // 超过10条后非最终消息不再发送(claw 限制)