chatccc 0.2.62 → 0.2.64

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 (35) hide show
  1. package/config.sample.json +2 -3
  2. package/im-skills/feishu-skill/download-video.mjs +10 -17
  3. package/im-skills/wechat-file-skill/receive-send-file.md +39 -0
  4. package/im-skills/wechat-file-skill/send-file.mjs +84 -0
  5. package/im-skills/wechat-file-skill/skill.md +11 -0
  6. package/im-skills/{wechat-skill → wechat-image-skill}/receive-send-image.md +39 -39
  7. package/im-skills/{wechat-skill → wechat-image-skill}/send-image.mjs +84 -80
  8. package/im-skills/{wechat-skill → wechat-image-skill}/skill.md +11 -11
  9. package/im-skills/wechat-video-skill/receive-send-video.md +39 -0
  10. package/im-skills/wechat-video-skill/send-video.mjs +80 -0
  11. package/im-skills/wechat-video-skill/skill.md +11 -0
  12. package/package.json +59 -59
  13. package/src/__tests__/agent-platform-routing.test.ts +26 -0
  14. package/src/__tests__/claude-adapter.test.ts +48 -48
  15. package/src/__tests__/config-reload.test.ts +14 -14
  16. package/src/__tests__/config-sample.test.ts +22 -22
  17. package/src/__tests__/im-skills.test.ts +112 -3
  18. package/src/__tests__/orchestrator.test.ts +1 -1
  19. package/src/__tests__/privacy.test.ts +142 -142
  20. package/src/__tests__/simplify.test.ts +282 -282
  21. package/src/__tests__/web-ui.test.ts +23 -23
  22. package/src/__tests__/wechat-platform.test.ts +65 -0
  23. package/src/adapters/claude-adapter.ts +40 -40
  24. package/src/agent-file-rpc.ts +19 -4
  25. package/src/agent-image-rpc.ts +19 -4
  26. package/src/agent-platform-routing.ts +28 -0
  27. package/src/config.ts +2 -24
  28. package/src/im-skills.ts +9 -0
  29. package/src/index.ts +11 -6
  30. package/src/orchestrator.ts +2 -2
  31. package/src/privacy.ts +67 -67
  32. package/src/session.ts +21 -5
  33. package/src/simplify.ts +119 -119
  34. package/src/web-ui.ts +3 -26
  35. package/src/wechat-platform.ts +170 -30
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "chatccc",
3
- "version": "0.2.62",
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.64",
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("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
- });
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
+ });
@@ -1,9 +1,9 @@
1
- import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
1
+ import { mkdtemp, mkdir, rm, writeFile, readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { tmpdir } from "node:os";
4
4
  import { afterEach, describe, expect, it } from "vitest";
5
5
 
6
- import { buildImSkillsPrompt } from "../im-skills.ts";
6
+ import { buildImSkillsPrompt, exportSkillSubDocs } from "../im-skills.ts";
7
7
 
8
8
  let tempRoot: string | null = null;
9
9
 
@@ -43,4 +43,113 @@ describe("IM skills prompt rendering", () => {
43
43
  expect(prompt).toContain("cwd=C:/work");
44
44
  expect(prompt).not.toContain("{{");
45
45
  });
46
- });
46
+
47
+ it("filters skills by enabledSkillNames", async () => {
48
+ tempRoot = await mkdtemp(join(tmpdir(), "chatccc-im-skills-filter-"));
49
+ const feishuDir = join(tempRoot, "feishu-skill");
50
+ const wechatDir = join(tempRoot, "wechat-skill");
51
+ const outDir = join(tempRoot, "out");
52
+ await mkdir(feishuDir);
53
+ await mkdir(wechatDir);
54
+ await writeFile(join(feishuDir, "skill.md"), "Feishu {{cwd}}", "utf-8");
55
+ await writeFile(join(feishuDir, "receive-send-image.md"), "Feishu doc", "utf-8");
56
+ await writeFile(join(wechatDir, "skill.md"), "WeChat {{cwd}}", "utf-8");
57
+ await writeFile(join(wechatDir, "receive-send-image.md"), "WeChat doc", "utf-8");
58
+
59
+ const input = {
60
+ skillsDir: tempRoot,
61
+ enabledSkillNames: ["feishu-skill"],
62
+ variables: { cwd: "C:/work" },
63
+ };
64
+ const prompt = await buildImSkillsPrompt(input);
65
+ const exported = await exportSkillSubDocs(input, outDir);
66
+
67
+ expect(prompt).toContain("[ChatCCC IM skill: feishu-skill]");
68
+ expect(prompt).toContain("Feishu C:/work");
69
+ expect(prompt).not.toContain("[ChatCCC IM skill: wechat-skill]");
70
+ expect(prompt).not.toContain("WeChat C:/work");
71
+ expect(exported).toHaveLength(1);
72
+ expect(exported[0]).toContain(join("feishu-skill", "receive-send-image.md"));
73
+ await expect(readFile(join(outDir, "feishu-skill", "receive-send-image.md"), "utf8")).resolves.toContain(
74
+ "Feishu doc",
75
+ );
76
+ });
77
+
78
+ it("skips filtering when enabledSkillNames is omitted", async () => {
79
+ tempRoot = await mkdtemp(join(tmpdir(), "chatccc-im-skills-all-"));
80
+ const feishuDir = join(tempRoot, "feishu-skill");
81
+ const wechatDir = join(tempRoot, "wechat-skill");
82
+ const outDir = join(tempRoot, "out");
83
+ await mkdir(feishuDir);
84
+ await mkdir(wechatDir);
85
+ await writeFile(join(feishuDir, "skill.md"), "Feishu {{cwd}}", "utf-8");
86
+ await writeFile(join(feishuDir, "receive-send-image.md"), "Feishu doc", "utf-8");
87
+ await writeFile(join(wechatDir, "skill.md"), "WeChat {{cwd}}", "utf-8");
88
+ await writeFile(join(wechatDir, "receive-send-image.md"), "WeChat doc", "utf-8");
89
+
90
+ const prompt = await buildImSkillsPrompt({
91
+ skillsDir: tempRoot,
92
+ variables: { cwd: "C:/work" },
93
+ });
94
+ const exported = await exportSkillSubDocs({ skillsDir: tempRoot, variables: { cwd: "C:/work" } }, outDir);
95
+
96
+ expect(prompt).toContain("[ChatCCC IM skill: feishu-skill]");
97
+ expect(prompt).toContain("[ChatCCC IM skill: wechat-skill]");
98
+ expect(exported.length).toBeGreaterThanOrEqual(2);
99
+ });
100
+
101
+ it("renders separate WeChat image, file, and video capability docs", async () => {
102
+ tempRoot = await mkdtemp(join(tmpdir(), "chatccc-im-skills-cache-"));
103
+ const prompt = await buildImSkillsPrompt({
104
+ variables: {
105
+ cwd: "C:/work",
106
+ im_skills_cache_dir: tempRoot,
107
+ wechat_send_image_script: "C:/work/im-skills/wechat-image-skill/send-image.mjs",
108
+ wechat_send_file_script: "C:/work/im-skills/wechat-file-skill/send-file.mjs",
109
+ wechat_send_video_script: "C:/work/im-skills/wechat-video-skill/send-video.mjs",
110
+ },
111
+ });
112
+
113
+ const exported = await exportSkillSubDocs(
114
+ {
115
+ variables: {
116
+ cwd: "C:/work",
117
+ im_skills_cache_dir: tempRoot,
118
+ wechat_send_image_script: "C:/work/im-skills/wechat-image-skill/send-image.mjs",
119
+ wechat_send_file_script: "C:/work/im-skills/wechat-file-skill/send-file.mjs",
120
+ wechat_send_video_script: "C:/work/im-skills/wechat-video-skill/send-video.mjs",
121
+ },
122
+ },
123
+ tempRoot,
124
+ );
125
+
126
+ expect(prompt).toContain("[ChatCCC IM skill: wechat-image-skill]");
127
+ expect(prompt).toContain("[ChatCCC IM skill: wechat-file-skill]");
128
+ expect(prompt).toContain("[ChatCCC IM skill: wechat-video-skill]");
129
+ expect(prompt).not.toContain("[ChatCCC IM skill: wechat-skill]");
130
+ expect(prompt).toContain("Receive images");
131
+ expect(prompt).toContain("Send images");
132
+ expect(prompt).toContain("Receive files");
133
+ expect(prompt).toContain("Send files");
134
+ expect(prompt).toContain("Receive videos");
135
+ expect(prompt).toContain("Send videos");
136
+ expect(prompt).toContain("receive-send-image.md");
137
+ expect(prompt).toContain("receive-send-file.md");
138
+ expect(prompt).toContain("receive-send-video.md");
139
+ expect(prompt).not.toContain("{{wechat_send_image_script}}");
140
+ expect(prompt).not.toContain("{{wechat_send_file_script}}");
141
+ expect(prompt).not.toContain("{{wechat_send_video_script}}");
142
+ expect(exported.some((file) => file.endsWith(join("wechat-image-skill", "receive-send-image.md")))).toBe(true);
143
+ expect(exported.some((file) => file.endsWith(join("wechat-file-skill", "receive-send-file.md")))).toBe(true);
144
+ expect(exported.some((file) => file.endsWith(join("wechat-video-skill", "receive-send-video.md")))).toBe(true);
145
+ await expect(readFile(join(tempRoot, "wechat-image-skill", "receive-send-image.md"), "utf8")).resolves.toContain(
146
+ "send-image.mjs",
147
+ );
148
+ await expect(readFile(join(tempRoot, "wechat-file-skill", "receive-send-file.md"), "utf8")).resolves.toContain(
149
+ "send-file.mjs",
150
+ );
151
+ await expect(readFile(join(tempRoot, "wechat-video-skill", "receive-send-video.md"), "utf8")).resolves.toContain(
152
+ "send-video.mjs",
153
+ );
154
+ });
155
+ });
@@ -146,7 +146,7 @@ describe("handleCommand WeChat processing ack", () => {
146
146
  expect(platform.sendCard).toHaveBeenCalledWith(
147
147
  "wx-chat",
148
148
  "生成中",
149
- "该会话正在生成回复中,请等待完成后再发送新消息。",
149
+ "该会话正在生成回复中,请等待完成后再发送新消息。如需中断生成,请发送 /stop 指令。",
150
150
  "yellow",
151
151
  );
152
152
  });