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 +3 -1
- package/config.sample.json +2 -1
- package/package.json +59 -59
- package/src/__tests__/claude-adapter.test.ts +39 -0
- package/src/__tests__/config-reload.test.ts +7 -3
- package/src/__tests__/config-sample.test.ts +11 -0
- package/src/__tests__/web-ui.test.ts +17 -0
- package/src/__tests__/wechat-platform.test.ts +25 -0
- package/src/adapters/claude-adapter.ts +27 -6
- package/src/adapters/codex-adapter.ts +4 -0
- package/src/adapters/cursor-adapter.ts +4 -0
- package/src/config.ts +14 -1
- package/src/index.ts +3 -0
- package/src/session.ts +8 -2
- package/src/web-ui.ts +53 -9
- package/src/wechat-platform.ts +1 -1
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 操作确认。请只在可信环境中使用。
|
package/config.sample.json
CHANGED
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "chatccc",
|
|
3
|
-
"version": "0.2.
|
|
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: "
|
|
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("
|
|
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
|
-
|
|
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.
|
|
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)
|
|
105
|
-
|
|
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
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
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,'"') + '">';
|
|
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
|
});
|
package/src/wechat-platform.ts
CHANGED
|
@@ -157,7 +157,7 @@ export function createWechatAdapter(
|
|
|
157
157
|
|
|
158
158
|
// 第10条且非最终消息:附加 claw 限制提示
|
|
159
159
|
if (count === 10 && !isFinal) {
|
|
160
|
-
text = text + "\n
|
|
160
|
+
text = text + "\n━━ 后台工作中,由于微信claw机制限制,请唤醒我才能继续发送消息";
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// 超过10条后非最终消息不再发送(claw 限制)
|