chatccc 0.2.63 → 0.2.65
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/config.sample.json +2 -3
- package/im-skills/feishu-skill/download-video.mjs +10 -17
- package/im-skills/wechat-file-skill/receive-send-file.md +39 -42
- package/im-skills/wechat-file-skill/send-file.mjs +84 -101
- package/im-skills/wechat-file-skill/skill.md +11 -11
- package/im-skills/wechat-image-skill/receive-send-image.md +5 -5
- package/im-skills/wechat-image-skill/send-image.mjs +2 -6
- package/im-skills/wechat-image-skill/skill.md +4 -4
- package/im-skills/wechat-video-skill/receive-send-video.md +39 -41
- package/im-skills/wechat-video-skill/send-video.mjs +80 -84
- package/im-skills/wechat-video-skill/skill.md +11 -11
- package/package.json +59 -59
- package/src/__tests__/agent-platform-routing.test.ts +26 -0
- package/src/__tests__/claude-adapter.test.ts +48 -48
- package/src/__tests__/config-reload.test.ts +14 -14
- package/src/__tests__/config-sample.test.ts +22 -22
- package/src/__tests__/im-skills.test.ts +49 -50
- package/src/__tests__/orchestrator.test.ts +1 -1
- package/src/__tests__/privacy.test.ts +142 -142
- package/src/__tests__/simplify.test.ts +282 -282
- package/src/__tests__/web-ui.test.ts +23 -23
- package/src/__tests__/wechat-platform.test.ts +0 -65
- package/src/adapters/claude-adapter.ts +40 -40
- package/src/agent-file-rpc.ts +19 -4
- package/src/agent-image-rpc.ts +19 -4
- package/src/agent-platform-routing.ts +28 -0
- package/src/config.ts +2 -24
- package/src/im-skills.ts +9 -0
- package/src/index.ts +11 -6
- package/src/orchestrator.ts +3 -4
- package/src/privacy.ts +67 -67
- package/src/session.ts +15 -3
- package/src/simplify.ts +119 -119
- package/src/web-ui.ts +3 -26
- package/src/wechat-platform.ts +68 -151
|
@@ -1,84 +1,80 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { basename, extname,
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
|
|
6
|
-
import { Client as OpenIlinkWire } from "@openilink/openilink-sdk-node";
|
|
7
|
-
|
|
8
|
-
const ILINK_AUTH_PATH = join(homedir(), ".chatccc", "state", "ilink-auth.json");
|
|
9
|
-
const MAX_VIDEO_BYTES =
|
|
10
|
-
const ALLOWED_EXTS = new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv"]);
|
|
11
|
-
|
|
12
|
-
function parseArgs(argv) {
|
|
13
|
-
const result = {};
|
|
14
|
-
for (let i = 0; i < argv.length; i++) {
|
|
15
|
-
const key = argv[i];
|
|
16
|
-
if (!key.startsWith("--")) continue;
|
|
17
|
-
const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
|
|
18
|
-
result[key.slice(2)] = value;
|
|
19
|
-
}
|
|
20
|
-
return result;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function usage() {
|
|
24
|
-
console.error(`Usage:
|
|
25
|
-
node ${basename(process.argv[1])} --path <absolute video path> [--caption <text>]`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function main() {
|
|
29
|
-
const args = parseArgs(process.argv.slice(2));
|
|
30
|
-
const videoPath = args.path;
|
|
31
|
-
const caption = args.caption || "";
|
|
32
|
-
|
|
33
|
-
if (!videoPath) {
|
|
34
|
-
usage();
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
console.error("Video
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
main().catch((err) => {
|
|
82
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
83
|
-
process.exit(1);
|
|
84
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { basename, extname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
import { Client as OpenIlinkWire } from "@openilink/openilink-sdk-node";
|
|
7
|
+
|
|
8
|
+
const ILINK_AUTH_PATH = join(homedir(), ".chatccc", "state", "ilink-auth.json");
|
|
9
|
+
const MAX_VIDEO_BYTES = 100 * 1024 * 1024;
|
|
10
|
+
const ALLOWED_EXTS = new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv"]);
|
|
11
|
+
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
const result = {};
|
|
14
|
+
for (let i = 0; i < argv.length; i++) {
|
|
15
|
+
const key = argv[i];
|
|
16
|
+
if (!key.startsWith("--")) continue;
|
|
17
|
+
const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
|
|
18
|
+
result[key.slice(2)] = value;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function usage() {
|
|
24
|
+
console.error(`Usage:
|
|
25
|
+
node ${basename(process.argv[1])} --path <absolute video path> [--caption <text>]`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function main() {
|
|
29
|
+
const args = parseArgs(process.argv.slice(2));
|
|
30
|
+
const videoPath = args.path;
|
|
31
|
+
const caption = args.caption || "";
|
|
32
|
+
|
|
33
|
+
if (!videoPath) {
|
|
34
|
+
usage();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ext = extname(videoPath).toLowerCase();
|
|
39
|
+
if (!ALLOWED_EXTS.has(ext)) {
|
|
40
|
+
console.error(`Unsupported video extension: ${ext || "(none)"}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const st = statSync(videoPath);
|
|
45
|
+
if (!st.isFile()) {
|
|
46
|
+
console.error("Video path is not a file");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (st.size > MAX_VIDEO_BYTES) {
|
|
50
|
+
console.error("Video file exceeds 100MB limit");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!existsSync(ILINK_AUTH_PATH)) {
|
|
55
|
+
console.error(`Auth snapshot not found: ${ILINK_AUTH_PATH}`);
|
|
56
|
+
console.error("Make sure the WeChat iLink platform is logged in.");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const snap = JSON.parse(readFileSync(ILINK_AUTH_PATH, "utf8"));
|
|
61
|
+
if (!snap.token || !snap.lastChatId || !snap.contextToken) {
|
|
62
|
+
console.error("Auth snapshot missing token, lastChatId, or contextToken.");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const videoData = readFileSync(videoPath);
|
|
67
|
+
const fileName = basename(videoPath);
|
|
68
|
+
|
|
69
|
+
const wire = new OpenIlinkWire(snap.token, {
|
|
70
|
+
base_url: snap.baseUrl,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await wire.sendMediaFile(snap.lastChatId, snap.contextToken, videoData, fileName, caption);
|
|
74
|
+
console.log(JSON.stringify({ ok: true, sentTo: 1 }));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch((err) => {
|
|
78
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: wechat-video-skill
|
|
3
|
-
description: WeChat iLink local
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Current working directory: {{cwd}}
|
|
7
|
-
|
|
8
|
-
WeChat videos are handled through the iLink SDK. The local server does NOT have HTTP RPC endpoints for WeChat; use the helper
|
|
9
|
-
|
|
10
|
-
- **Receive videos**: Videos sent to the bot are automatically downloaded to `~/.chatccc/
|
|
11
|
-
- **Send videos**: Use the send-video helper script — read `{{im_skills_cache_dir}}/wechat-video-skill/receive-send-video.md`
|
|
1
|
+
---
|
|
2
|
+
name: wechat-video-skill
|
|
3
|
+
description: WeChat iLink local skills for sending and receiving videos.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Current working directory: {{cwd}}
|
|
7
|
+
|
|
8
|
+
WeChat videos are handled through the iLink SDK. The local server does NOT have HTTP RPC endpoints for WeChat; use the helper scripts below instead.
|
|
9
|
+
|
|
10
|
+
- **Receive videos**: Videos sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/` with the filename prefix `wx_`. The message text will include the local path as `[视频] <absolute path>` — read `{{im_skills_cache_dir}}/wechat-video-skill/receive-send-video.md`
|
|
11
|
+
- **Send videos**: Use the send-video helper script — read `{{im_skills_cache_dir}}/wechat-video-skill/receive-send-video.md`
|
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.65",
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { splitFeishuTargetChats } from "../agent-platform-routing.ts";
|
|
4
|
+
|
|
5
|
+
describe("agent platform routing", () => {
|
|
6
|
+
it("keeps Feishu-compatible chats and skips WeChat chats for Feishu RPC", () => {
|
|
7
|
+
const result = splitFeishuTargetChats(["fs-1", "wx-1", "unknown-1"], (chatId) => {
|
|
8
|
+
if (chatId.startsWith("wx")) return "wechat";
|
|
9
|
+
if (chatId.startsWith("fs")) return "feishu";
|
|
10
|
+
return undefined;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(result.targetChatIds).toEqual(["fs-1", "unknown-1"]);
|
|
14
|
+
expect(result.skippedUnsupported).toEqual([{ chatId: "wx-1", platformKind: "wechat" }]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("reports no Feishu targets when every bound chat is WeChat", () => {
|
|
18
|
+
const result = splitFeishuTargetChats(["wx-1", "wx-2"], () => "wechat");
|
|
19
|
+
|
|
20
|
+
expect(result.targetChatIds).toEqual([]);
|
|
21
|
+
expect(result.skippedUnsupported).toEqual([
|
|
22
|
+
{ chatId: "wx-1", platformKind: "wechat" },
|
|
23
|
+
{ chatId: "wx-2", platformKind: "wechat" },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -771,10 +771,10 @@ describe("createClaudeAdapter — env 注入", () => {
|
|
|
771
771
|
expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://api.deepseek.com/anthropic");
|
|
772
772
|
});
|
|
773
773
|
|
|
774
|
-
it("apiKey + baseUrl 都设置 → 同时覆盖", async () => {
|
|
775
|
-
setupMockCreateSession();
|
|
776
|
-
const adapter = createClaudeAdapter({
|
|
777
|
-
model: "",
|
|
774
|
+
it("apiKey + baseUrl 都设置 → 同时覆盖", async () => {
|
|
775
|
+
setupMockCreateSession();
|
|
776
|
+
const adapter = createClaudeAdapter({
|
|
777
|
+
model: "",
|
|
778
778
|
effort: "",
|
|
779
779
|
isEmpty: (v) => v.trim() === "",
|
|
780
780
|
apiKey: "sk-x",
|
|
@@ -784,50 +784,50 @@ describe("createClaudeAdapter — env 注入", () => {
|
|
|
784
784
|
await adapter.createSession("/cwd");
|
|
785
785
|
|
|
786
786
|
const opts = sdk.unstable_v2_createSession.mock.calls[0][0];
|
|
787
|
-
expect(opts.env.ANTHROPIC_API_KEY).toBe("sk-x");
|
|
788
|
-
expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://gateway.example/anthropic");
|
|
789
|
-
});
|
|
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
|
-
|
|
830
|
-
it("ChatCCC API config is isolated from Claude settings auth/model env", async () => {
|
|
787
|
+
expect(opts.env.ANTHROPIC_API_KEY).toBe("sk-x");
|
|
788
|
+
expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://gateway.example/anthropic");
|
|
789
|
+
});
|
|
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
|
+
|
|
830
|
+
it("ChatCCC API config is isolated from Claude settings auth/model env", async () => {
|
|
831
831
|
setupMockCreateSession();
|
|
832
832
|
process.env.ANTHROPIC_AUTH_TOKEN = "token-from-claude-settings";
|
|
833
833
|
process.env.CLAUDE_CODE_OAUTH_TOKEN = "oauth-from-claude-settings";
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
CHATCCC_PORT,
|
|
11
11
|
CLAUDE_API_KEY,
|
|
12
12
|
CLAUDE_BASE_URL,
|
|
13
|
-
CLAUDE_EFFORT,
|
|
14
|
-
CLAUDE_MODEL,
|
|
15
|
-
CLAUDE_SUBAGENT_MODEL,
|
|
13
|
+
CLAUDE_EFFORT,
|
|
14
|
+
CLAUDE_MODEL,
|
|
15
|
+
CLAUDE_SUBAGENT_MODEL,
|
|
16
16
|
CURSOR_AGENT_ARGS,
|
|
17
17
|
CURSOR_AGENT_COMMAND,
|
|
18
18
|
FEISHU_ENABLED,
|
|
@@ -44,10 +44,10 @@ const baseAppConfig: AppConfig = {
|
|
|
44
44
|
allowInterrupt: false,
|
|
45
45
|
claude: {
|
|
46
46
|
enabled: true,
|
|
47
|
-
defaultAgent: true,
|
|
48
|
-
model: "initial-model",
|
|
49
|
-
subagentModel: "initial-subagent-model",
|
|
50
|
-
effort: "initial-effort",
|
|
47
|
+
defaultAgent: true,
|
|
48
|
+
model: "initial-model",
|
|
49
|
+
subagentModel: "initial-subagent-model",
|
|
50
|
+
effort: "initial-effort",
|
|
51
51
|
apiKey: "sk-initial",
|
|
52
52
|
baseUrl: "https://initial.gw/anthropic",
|
|
53
53
|
},
|
|
@@ -80,22 +80,22 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
|
|
|
80
80
|
expect(APP_SECRET).toBe("NEW_APP_SECRET");
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
it("更新 Claude 配置(model / subagentModel / effort / apiKey / baseUrl)", () => {
|
|
83
|
+
it("更新 Claude 配置(model / subagentModel / effort / apiKey / baseUrl)", () => {
|
|
84
84
|
applyLoadedConfig({
|
|
85
85
|
...structuredClone(baseAppConfig),
|
|
86
86
|
claude: {
|
|
87
87
|
enabled: true,
|
|
88
|
-
defaultAgent: true,
|
|
89
|
-
model: "claude-sonnet-4-6",
|
|
90
|
-
subagentModel: "claude-haiku-4-5-20251001",
|
|
91
|
-
effort: "high",
|
|
88
|
+
defaultAgent: true,
|
|
89
|
+
model: "claude-sonnet-4-6",
|
|
90
|
+
subagentModel: "claude-haiku-4-5-20251001",
|
|
91
|
+
effort: "high",
|
|
92
92
|
apiKey: "sk-newkey",
|
|
93
93
|
baseUrl: "https://gw2.example/anthropic",
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
|
|
98
|
-
expect(CLAUDE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
|
|
97
|
+
expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
|
|
98
|
+
expect(CLAUDE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
|
|
99
99
|
expect(CLAUDE_EFFORT).toBe("high");
|
|
100
100
|
expect(CLAUDE_API_KEY).toBe("sk-newkey");
|
|
101
101
|
expect(CLAUDE_BASE_URL).toBe("https://gw2.example/anthropic");
|
|
@@ -6,25 +6,25 @@ import { describe, expect, it } from "vitest";
|
|
|
6
6
|
describe("config.sample.json", () => {
|
|
7
7
|
it("enables Feishu and WeChat iLink by default", () => {
|
|
8
8
|
const configSamplePath = join(process.cwd(), "config.sample.json");
|
|
9
|
-
const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
|
|
10
|
-
platforms?: {
|
|
11
|
-
feishu?: { enabled?: unknown };
|
|
12
|
-
ilink?: { enabled?: unknown };
|
|
13
|
-
};
|
|
14
|
-
claude?: { model?: unknown; subagentModel?: unknown };
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
expect(sample.platforms?.feishu?.enabled).toBe(true);
|
|
18
|
-
expect(sample.platforms?.ilink?.enabled).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("
|
|
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("
|
|
28
|
-
expect(sample.claude?.subagentModel).toBe("");
|
|
29
|
-
});
|
|
30
|
-
});
|
|
9
|
+
const sample = JSON.parse(readFileSync(configSamplePath, "utf8")) as {
|
|
10
|
+
platforms?: {
|
|
11
|
+
feishu?: { enabled?: unknown };
|
|
12
|
+
ilink?: { enabled?: unknown };
|
|
13
|
+
};
|
|
14
|
+
claude?: { model?: unknown; subagentModel?: unknown };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
expect(sample.platforms?.feishu?.enabled).toBe(true);
|
|
18
|
+
expect(sample.platforms?.ilink?.enabled).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("leaves Claude model and subagent model empty by default so the SDK uses its own defaults", () => {
|
|
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("");
|
|
28
|
+
expect(sample.claude?.subagentModel).toBe("");
|
|
29
|
+
});
|
|
30
|
+
});
|