chatccc 0.2.51 → 0.2.53

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.
Files changed (48) hide show
  1. package/README.md +103 -217
  2. package/bin/chatccc.mjs +23 -23
  3. package/config.sample.json +34 -30
  4. package/demo/ilink_echo_probe.ts +222 -222
  5. package/im-skills/feishu-skill/download-video.mjs +162 -162
  6. package/im-skills/feishu-skill/receive-send-file.md +66 -66
  7. package/im-skills/feishu-skill/receive-send-image.md +27 -27
  8. package/im-skills/feishu-skill/send-file.mjs +50 -50
  9. package/im-skills/feishu-skill/send-image.mjs +50 -50
  10. package/im-skills/feishu-skill/skill.md +10 -10
  11. package/package.json +59 -59
  12. package/src/__tests__/agent-image-rpc.test.ts +33 -33
  13. package/src/__tests__/agent-rpc-body.test.ts +41 -41
  14. package/src/__tests__/card-plain-text.test.ts +42 -0
  15. package/src/__tests__/claude-adapter.test.ts +54 -1
  16. package/src/__tests__/codex-adapter.test.ts +304 -304
  17. package/src/__tests__/config-reload.test.ts +58 -41
  18. package/src/__tests__/config-sample.test.ts +19 -0
  19. package/src/__tests__/crash-logging.test.ts +62 -62
  20. package/src/__tests__/fixtures/codex_simple_text.jsonl +4 -4
  21. package/src/__tests__/fixtures/codex_with_tool.jsonl +6 -6
  22. package/src/__tests__/im-skills.test.ts +46 -46
  23. package/src/__tests__/session.test.ts +95 -2
  24. package/src/__tests__/sim-agent.test.ts +173 -173
  25. package/src/__tests__/sim-platform.test.ts +76 -76
  26. package/src/__tests__/sim-store.test.ts +213 -213
  27. package/src/__tests__/stream-state.test.ts +134 -134
  28. package/src/__tests__/wechat-platform.test.ts +57 -0
  29. package/src/adapters/claude-adapter.ts +23 -2
  30. package/src/adapters/codex-adapter.ts +294 -294
  31. package/src/adapters/codex-session-meta-store.ts +130 -130
  32. package/src/agent-file-rpc.ts +156 -156
  33. package/src/agent-image-rpc.ts +152 -152
  34. package/src/agent-rpc-body.ts +48 -48
  35. package/src/card-plain-text.ts +108 -0
  36. package/src/cards.ts +4 -4
  37. package/src/config.ts +775 -742
  38. package/src/feishu-api.ts +6 -0
  39. package/src/im-skills.ts +109 -109
  40. package/src/index.ts +155 -788
  41. package/src/orchestrator.ts +1032 -0
  42. package/src/platform-adapter.ts +61 -0
  43. package/src/session-chat-binding.ts +7 -0
  44. package/src/session.ts +278 -113
  45. package/src/sim-agent.ts +167 -156
  46. package/src/trace.ts +50 -50
  47. package/src/web-ui.ts +1891 -1718
  48. package/src/wechat-platform.ts +517 -0
@@ -1,51 +1,51 @@
1
- #!/usr/bin/env node
2
- import { basename } from "node:path";
3
-
4
- function parseArgs(argv) {
5
- const result = {};
6
- for (let i = 0; i < argv.length; i++) {
7
- const key = argv[i];
8
- if (!key.startsWith("--")) continue;
9
- const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
10
- result[key.slice(2)] = value;
11
- }
12
- return result;
13
- }
14
-
15
- function usage() {
16
- console.error(`Usage:
17
- node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute image path> [--caption <text>]`);
18
- }
19
-
20
- async function main() {
21
- const args = parseArgs(process.argv.slice(2));
22
- const url = args.url || process.env.CHATCCC_SEND_IMAGE_URL;
23
- const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
24
- const path = args.path;
25
- const caption = args.caption || "";
26
-
27
- if (!url || !sessionId || !path) {
28
- usage();
29
- process.exit(1);
30
- }
31
-
32
- const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
33
- const response = await fetch(url, {
34
- method: "POST",
35
- headers: {
36
- "Content-Type": "application/json; charset=utf-8",
37
- },
38
- body,
39
- });
40
-
41
- const text = await response.text();
42
- if (!response.ok) {
43
- throw new Error(`HTTP ${response.status}: ${text}`);
44
- }
45
- console.log(text);
46
- }
47
-
48
- main().catch((err) => {
49
- console.error(err instanceof Error ? err.message : String(err));
50
- process.exit(1);
1
+ #!/usr/bin/env node
2
+ import { basename } from "node:path";
3
+
4
+ function parseArgs(argv) {
5
+ const result = {};
6
+ for (let i = 0; i < argv.length; i++) {
7
+ const key = argv[i];
8
+ if (!key.startsWith("--")) continue;
9
+ const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
10
+ result[key.slice(2)] = value;
11
+ }
12
+ return result;
13
+ }
14
+
15
+ function usage() {
16
+ console.error(`Usage:
17
+ node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute image path> [--caption <text>]`);
18
+ }
19
+
20
+ async function main() {
21
+ const args = parseArgs(process.argv.slice(2));
22
+ const url = args.url || process.env.CHATCCC_SEND_IMAGE_URL;
23
+ const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
24
+ const path = args.path;
25
+ const caption = args.caption || "";
26
+
27
+ if (!url || !sessionId || !path) {
28
+ usage();
29
+ process.exit(1);
30
+ }
31
+
32
+ const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
33
+ const response = await fetch(url, {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json; charset=utf-8",
37
+ },
38
+ body,
39
+ });
40
+
41
+ const text = await response.text();
42
+ if (!response.ok) {
43
+ throw new Error(`HTTP ${response.status}: ${text}`);
44
+ }
45
+ console.log(text);
46
+ }
47
+
48
+ main().catch((err) => {
49
+ console.error(err instanceof Error ? err.message : String(err));
50
+ process.exit(1);
51
51
  });
@@ -1,11 +1,11 @@
1
- ---
2
- name: feishu-skill
3
- description: Feishu IM local skills for sending and receiving images, files, and videos.
4
- ---
5
-
6
- Current working directory: {{cwd}}
7
-
8
- Use local endpoints instead of calling Feishu Open Platform directly.
9
-
10
- - **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
1
+ ---
2
+ name: feishu-skill
3
+ description: Feishu IM local skills for sending and receiving images, files, and videos.
4
+ ---
5
+
6
+ Current working directory: {{cwd}}
7
+
8
+ Use local endpoints instead of calling Feishu Open Platform directly.
9
+
10
+ - **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
11
11
  - **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "chatccc",
3
- "version": "0.2.51",
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.53",
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,34 +1,34 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- AGENT_SEND_IMAGE_PATH,
5
- buildAgentImageCapabilityPrompt,
6
- } from "../agent-image-rpc.ts";
7
-
8
- describe("agent image RPC", () => {
9
- it("builds prompt instructions with session_id", () => {
10
- const prompt = buildAgentImageCapabilityPrompt({
11
- url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
12
- sessionId: "sid-test",
13
- });
14
-
15
- expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/send-image");
16
- expect(prompt).toContain('"session_id":"sid-test"');
17
- expect(prompt).toContain("Content-Type: application/json; charset=utf-8");
18
- expect(prompt).toContain("UTF-8 encoded JSON bytes");
19
- expect(prompt).toContain('"path"');
20
- expect(prompt).not.toContain("Authorization: Bearer");
21
- expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_URL");
22
- expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_TOKEN");
23
- });
24
-
25
- it("builds prompt with cwd hint", () => {
26
- const prompt = buildAgentImageCapabilityPrompt({
27
- url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
28
- sessionId: "sid-1",
29
- cwd: "F:/repo",
30
- });
31
-
32
- expect(prompt).toContain("Current working directory: F:/repo");
33
- });
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ AGENT_SEND_IMAGE_PATH,
5
+ buildAgentImageCapabilityPrompt,
6
+ } from "../agent-image-rpc.ts";
7
+
8
+ describe("agent image RPC", () => {
9
+ it("builds prompt instructions with session_id", () => {
10
+ const prompt = buildAgentImageCapabilityPrompt({
11
+ url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
12
+ sessionId: "sid-test",
13
+ });
14
+
15
+ expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/send-image");
16
+ expect(prompt).toContain('"session_id":"sid-test"');
17
+ expect(prompt).toContain("Content-Type: application/json; charset=utf-8");
18
+ expect(prompt).toContain("UTF-8 encoded JSON bytes");
19
+ expect(prompt).toContain('"path"');
20
+ expect(prompt).not.toContain("Authorization: Bearer");
21
+ expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_URL");
22
+ expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_TOKEN");
23
+ });
24
+
25
+ it("builds prompt with cwd hint", () => {
26
+ const prompt = buildAgentImageCapabilityPrompt({
27
+ url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
28
+ sessionId: "sid-1",
29
+ cwd: "F:/repo",
30
+ });
31
+
32
+ expect(prompt).toContain("Current working directory: F:/repo");
33
+ });
34
34
  });
@@ -1,41 +1,41 @@
1
- import type { IncomingMessage } from "node:http";
2
- import { Readable } from "node:stream";
3
-
4
- import { describe, expect, it } from "vitest";
5
-
6
- import { getRequestCharset, readUtf8JsonBody } from "../agent-rpc-body.ts";
7
-
8
- function requestFrom(body: Buffer, contentType?: string): IncomingMessage {
9
- const req = Readable.from([body]) as IncomingMessage;
10
- req.headers = {};
11
- if (contentType) req.headers["content-type"] = contentType;
12
- return req;
13
- }
14
-
15
- describe("agent RPC body parsing", () => {
16
- it("defaults JSON requests to UTF-8 and preserves Unicode captions", async () => {
17
- const caption = "\u4e2d\u6587 caption";
18
- const body = Buffer.from(JSON.stringify({ caption }), "utf8");
19
- const req = requestFrom(body, "application/json");
20
-
21
- await expect(readUtf8JsonBody(req, 1024)).resolves.toEqual({ caption });
22
- });
23
-
24
- it("reads charset from content-type", () => {
25
- const req = requestFrom(Buffer.from("{}"), 'application/json; charset="utf-8"');
26
-
27
- expect(getRequestCharset(req)).toBe("utf-8");
28
- });
29
-
30
- it("rejects non UTF-8 charsets", async () => {
31
- const req = requestFrom(Buffer.from("{}"), "application/json; charset=gbk");
32
-
33
- await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("Unsupported charset");
34
- });
35
-
36
- it("rejects invalid UTF-8 bytes", async () => {
37
- const req = requestFrom(Buffer.from([0xff, 0xfe]), "application/json; charset=utf-8");
38
-
39
- await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("valid UTF-8");
40
- });
41
- });
1
+ import type { IncomingMessage } from "node:http";
2
+ import { Readable } from "node:stream";
3
+
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ import { getRequestCharset, readUtf8JsonBody } from "../agent-rpc-body.ts";
7
+
8
+ function requestFrom(body: Buffer, contentType?: string): IncomingMessage {
9
+ const req = Readable.from([body]) as IncomingMessage;
10
+ req.headers = {};
11
+ if (contentType) req.headers["content-type"] = contentType;
12
+ return req;
13
+ }
14
+
15
+ describe("agent RPC body parsing", () => {
16
+ it("defaults JSON requests to UTF-8 and preserves Unicode captions", async () => {
17
+ const caption = "\u4e2d\u6587 caption";
18
+ const body = Buffer.from(JSON.stringify({ caption }), "utf8");
19
+ const req = requestFrom(body, "application/json");
20
+
21
+ await expect(readUtf8JsonBody(req, 1024)).resolves.toEqual({ caption });
22
+ });
23
+
24
+ it("reads charset from content-type", () => {
25
+ const req = requestFrom(Buffer.from("{}"), 'application/json; charset="utf-8"');
26
+
27
+ expect(getRequestCharset(req)).toBe("utf-8");
28
+ });
29
+
30
+ it("rejects non UTF-8 charsets", async () => {
31
+ const req = requestFrom(Buffer.from("{}"), "application/json; charset=gbk");
32
+
33
+ await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("Unsupported charset");
34
+ });
35
+
36
+ it("rejects invalid UTF-8 bytes", async () => {
37
+ const req = requestFrom(Buffer.from([0xff, 0xfe]), "application/json; charset=utf-8");
38
+
39
+ await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("valid UTF-8");
40
+ });
41
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { cardJsonToPlainText } from "../card-plain-text.ts";
4
+ import {
5
+ buildHelpCard,
6
+ buildProgressCard,
7
+ buildStatusCard,
8
+ } from "../cards.ts";
9
+
10
+ describe("cardJsonToPlainText", () => {
11
+ it("converts help cards to readable text with commands", () => {
12
+ const text = cardJsonToPlainText(buildHelpCard("hello"));
13
+
14
+ expect(text).toContain("# ChatCCC");
15
+ expect(text).toContain("hello");
16
+ expect(text).toContain("/new");
17
+ expect(text).toContain("/new cursor");
18
+ expect(text).toContain("/new codex");
19
+ expect(text).toContain("/restart");
20
+ expect(text).toContain("/cd");
21
+ });
22
+
23
+ it("converts status cards from v1 card format", () => {
24
+ const text = cardJsonToPlainText(buildStatusCard("status body", "green"));
25
+
26
+ expect(text).toContain("# 会话状态");
27
+ expect(text).toContain("status body");
28
+ });
29
+
30
+ it("converts schema 2.0 progress cards", () => {
31
+ const text = cardJsonToPlainText(buildProgressCard("stream body"));
32
+
33
+ expect(text).toContain("# 生成中...");
34
+ expect(text).toContain("stream body");
35
+ expect(text).toContain("/status");
36
+ expect(text).toContain("/stop");
37
+ });
38
+
39
+ it("returns null for invalid card json", () => {
40
+ expect(cardJsonToPlainText("{bad json")).toBeNull();
41
+ });
42
+ });
@@ -685,6 +685,14 @@ describe("createClaudeAdapter — env 注入", () => {
685
685
  // 确保每个用例从干净的 process.env 起步
686
686
  delete process.env.ANTHROPIC_API_KEY;
687
687
  delete process.env.ANTHROPIC_BASE_URL;
688
+ delete process.env.ANTHROPIC_AUTH_TOKEN;
689
+ delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
690
+ delete process.env.ANTHROPIC_MODEL;
691
+ delete process.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
692
+ delete process.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
693
+ delete process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
694
+ delete process.env.CLAUDE_CODE_SUBAGENT_MODEL;
695
+ delete process.env.CLAUDE_CODE_EFFORT_LEVEL;
688
696
  });
689
697
 
690
698
  // 用例之间清掉我们写入的 env,避免互相污染
@@ -780,6 +788,51 @@ describe("createClaudeAdapter — env 注入", () => {
780
788
  expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://gateway.example/anthropic");
781
789
  });
782
790
 
791
+ it("ChatCCC API config is isolated from Claude settings auth/model env", async () => {
792
+ setupMockCreateSession();
793
+ process.env.ANTHROPIC_AUTH_TOKEN = "token-from-claude-settings";
794
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "oauth-from-claude-settings";
795
+ process.env.ANTHROPIC_MODEL = "model-from-claude-settings";
796
+ process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = "sonnet-from-claude-settings";
797
+ process.env.CLAUDE_CODE_SUBAGENT_MODEL = "subagent-from-claude-settings";
798
+ process.env.CLAUDE_CODE_EFFORT_LEVEL = "max";
799
+ const adapter = createClaudeAdapter({
800
+ model: "chatccc-model",
801
+ effort: "high",
802
+ isEmpty: (v) => v.trim() === "",
803
+ apiKey: "sk-chatccc",
804
+ baseUrl: "https://chatccc-gateway.example/anthropic",
805
+ });
806
+
807
+ await adapter.createSession("/cwd");
808
+
809
+ const opts = sdk.unstable_v2_createSession.mock.calls[0][0];
810
+ expect(opts.env.ANTHROPIC_API_KEY).toBe("sk-chatccc");
811
+ expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://chatccc-gateway.example/anthropic");
812
+ expect(opts.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
813
+ expect(opts.env.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
814
+ expect(opts.env.ANTHROPIC_MODEL).toBeUndefined();
815
+ expect(opts.env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBeUndefined();
816
+ expect(opts.env.CLAUDE_CODE_SUBAGENT_MODEL).toBeUndefined();
817
+ expect(opts.env.CLAUDE_CODE_EFFORT_LEVEL).toBeUndefined();
818
+ });
819
+
820
+ it("第三方 API 配置时仍然加载 CLAUDE.md(settingSources 始终为 project+local)", async () => {
821
+ setupMockCreateSession();
822
+ const adapter = createClaudeAdapter({
823
+ model: "",
824
+ effort: "",
825
+ isEmpty: (v) => v.trim() === "",
826
+ apiKey: "sk-chatccc",
827
+ baseUrl: "https://chatccc-gateway.example/anthropic",
828
+ });
829
+
830
+ await adapter.createSession("/cwd");
831
+
832
+ const opts = sdk.unstable_v2_createSession.mock.calls[0][0];
833
+ expect(opts.settingSources).toEqual(["project", "local"]);
834
+ });
835
+
783
836
  it("不修改主进程 process.env(永不污染)", async () => {
784
837
  setupMockCreateSession();
785
838
  const before = { ...process.env };
@@ -817,4 +870,4 @@ describe("createClaudeAdapter — env 注入", () => {
817
870
  expect(opts.env.ANTHROPIC_API_KEY).toBe("from-system-env");
818
871
  expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://override.example/anthropic");
819
872
  });
820
- });
873
+ });