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.
- package/README.md +103 -217
- package/bin/chatccc.mjs +23 -23
- package/config.sample.json +34 -30
- package/demo/ilink_echo_probe.ts +222 -222
- package/im-skills/feishu-skill/download-video.mjs +162 -162
- package/im-skills/feishu-skill/receive-send-file.md +66 -66
- package/im-skills/feishu-skill/receive-send-image.md +27 -27
- package/im-skills/feishu-skill/send-file.mjs +50 -50
- package/im-skills/feishu-skill/send-image.mjs +50 -50
- package/im-skills/feishu-skill/skill.md +10 -10
- package/package.json +59 -59
- package/src/__tests__/agent-image-rpc.test.ts +33 -33
- package/src/__tests__/agent-rpc-body.test.ts +41 -41
- package/src/__tests__/card-plain-text.test.ts +42 -0
- package/src/__tests__/claude-adapter.test.ts +54 -1
- package/src/__tests__/codex-adapter.test.ts +304 -304
- package/src/__tests__/config-reload.test.ts +58 -41
- package/src/__tests__/config-sample.test.ts +19 -0
- package/src/__tests__/crash-logging.test.ts +62 -62
- package/src/__tests__/fixtures/codex_simple_text.jsonl +4 -4
- package/src/__tests__/fixtures/codex_with_tool.jsonl +6 -6
- package/src/__tests__/im-skills.test.ts +46 -46
- package/src/__tests__/session.test.ts +95 -2
- package/src/__tests__/sim-agent.test.ts +173 -173
- package/src/__tests__/sim-platform.test.ts +76 -76
- package/src/__tests__/sim-store.test.ts +213 -213
- package/src/__tests__/stream-state.test.ts +134 -134
- package/src/__tests__/wechat-platform.test.ts +57 -0
- package/src/adapters/claude-adapter.ts +23 -2
- package/src/adapters/codex-adapter.ts +294 -294
- package/src/adapters/codex-session-meta-store.ts +130 -130
- package/src/agent-file-rpc.ts +156 -156
- package/src/agent-image-rpc.ts +152 -152
- package/src/agent-rpc-body.ts +48 -48
- package/src/card-plain-text.ts +108 -0
- package/src/cards.ts +4 -4
- package/src/config.ts +775 -742
- package/src/feishu-api.ts +6 -0
- package/src/im-skills.ts +109 -109
- package/src/index.ts +155 -788
- package/src/orchestrator.ts +1032 -0
- package/src/platform-adapter.ts +61 -0
- package/src/session-chat-binding.ts +7 -0
- package/src/session.ts +278 -113
- package/src/sim-agent.ts +167 -156
- package/src/trace.ts +50 -50
- package/src/web-ui.ts +1891 -1718
- package/src/wechat-platform.ts +517 -0
package/src/feishu-api.ts
CHANGED
|
@@ -860,6 +860,12 @@ export async function sendRestartCard(token: string): Promise<void> {
|
|
|
860
860
|
return;
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
+
// 微信 chat ID 无法通过飞书 API 发送,跳过
|
|
864
|
+
if (latestChatId.includes("@im.wechat")) {
|
|
865
|
+
console.log(`[${ts()}] [RESTART] Latest chat is WeChat (${latestChatId}), skipping Feishu notification`);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
863
869
|
console.log(`[${ts()}] [RESTART] Latest active chat: ${latestChatId} (mtime=${new Date(latestTime).toISOString()})`);
|
|
864
870
|
|
|
865
871
|
const restartCard = buildHelpCard("", { greeting: "Bot 已启动完成,可以继续使用。" });
|
package/src/im-skills.ts
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join, dirname } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { PROJECT_ROOT } from "./config.ts";
|
|
5
|
-
|
|
6
|
-
export const DEFAULT_IM_SKILLS_DIR = join(PROJECT_ROOT, "im-skills");
|
|
7
|
-
|
|
8
|
-
export interface ImSkillVariableMap {
|
|
9
|
-
[key: string]: string | undefined;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface BuildImSkillsPromptInput {
|
|
13
|
-
skillsDir?: string;
|
|
14
|
-
variables: ImSkillVariableMap;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface ImSkillDoc {
|
|
18
|
-
name: string;
|
|
19
|
-
content: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function renderTemplate(template: string, variables: ImSkillVariableMap): string {
|
|
23
|
-
return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_match, key: string) => {
|
|
24
|
-
return variables[key] ?? "";
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function loadSkillDocs(skillsDir: string): Promise<ImSkillDoc[]> {
|
|
29
|
-
let entries;
|
|
30
|
-
try {
|
|
31
|
-
entries = await readdir(skillsDir, { withFileTypes: true });
|
|
32
|
-
} catch {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const docs = await Promise.all(
|
|
37
|
-
entries
|
|
38
|
-
.filter((entry) => entry.isDirectory())
|
|
39
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
40
|
-
.map(async (entry): Promise<ImSkillDoc | null> => {
|
|
41
|
-
const skillPath = join(skillsDir, entry.name, "skill.md");
|
|
42
|
-
try {
|
|
43
|
-
const content = await readFile(skillPath, "utf-8");
|
|
44
|
-
return { name: entry.name, content };
|
|
45
|
-
} catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}),
|
|
49
|
-
);
|
|
50
|
-
return docs.filter((doc): doc is ImSkillDoc => doc !== null);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function buildImSkillsPrompt(input: BuildImSkillsPromptInput): Promise<string> {
|
|
54
|
-
const skillsDir = input.skillsDir ?? DEFAULT_IM_SKILLS_DIR;
|
|
55
|
-
const docs = await loadSkillDocs(skillsDir);
|
|
56
|
-
return docs
|
|
57
|
-
.map((doc) => {
|
|
58
|
-
const rendered = renderTemplate(doc.content, input.variables).trim();
|
|
59
|
-
return [
|
|
60
|
-
`[ChatCCC IM skill: ${doc.name}]`,
|
|
61
|
-
rendered,
|
|
62
|
-
`[/ChatCCC IM skill: ${doc.name}]`,
|
|
63
|
-
].join("\n");
|
|
64
|
-
})
|
|
65
|
-
.join("\n\n");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 渲染技能目录下的子文档(skill.md 除外)并写入 outputDir。
|
|
70
|
-
* 返回写入的文件路径列表。通常在会话初始化时调用,写入的文档供 Agent 按需读取。
|
|
71
|
-
*/
|
|
72
|
-
export async function exportSkillSubDocs(
|
|
73
|
-
input: BuildImSkillsPromptInput,
|
|
74
|
-
outputDir: string,
|
|
75
|
-
): Promise<string[]> {
|
|
76
|
-
const skillsDir = input.skillsDir ?? DEFAULT_IM_SKILLS_DIR;
|
|
77
|
-
const files: string[] = [];
|
|
78
|
-
|
|
79
|
-
let entries;
|
|
80
|
-
try {
|
|
81
|
-
entries = await readdir(skillsDir, { withFileTypes: true });
|
|
82
|
-
} catch {
|
|
83
|
-
return files;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
for (const entry of entries) {
|
|
87
|
-
if (!entry.isDirectory()) continue;
|
|
88
|
-
const skillPath = join(skillsDir, entry.name);
|
|
89
|
-
|
|
90
|
-
let subFiles;
|
|
91
|
-
try {
|
|
92
|
-
subFiles = await readdir(skillPath);
|
|
93
|
-
} catch {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
for (const sf of subFiles) {
|
|
98
|
-
if (sf === "skill.md" || !sf.endsWith(".md")) continue;
|
|
99
|
-
const content = await readFile(join(skillPath, sf), "utf-8");
|
|
100
|
-
const rendered = renderTemplate(content, input.variables);
|
|
101
|
-
const outPath = join(outputDir, entry.name, sf);
|
|
102
|
-
await mkdir(dirname(outPath), { recursive: true });
|
|
103
|
-
await writeFile(outPath, rendered, "utf-8");
|
|
104
|
-
files.push(outPath);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return files;
|
|
109
|
-
}
|
|
1
|
+
import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { PROJECT_ROOT } from "./config.ts";
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_IM_SKILLS_DIR = join(PROJECT_ROOT, "im-skills");
|
|
7
|
+
|
|
8
|
+
export interface ImSkillVariableMap {
|
|
9
|
+
[key: string]: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BuildImSkillsPromptInput {
|
|
13
|
+
skillsDir?: string;
|
|
14
|
+
variables: ImSkillVariableMap;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ImSkillDoc {
|
|
18
|
+
name: string;
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderTemplate(template: string, variables: ImSkillVariableMap): string {
|
|
23
|
+
return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_match, key: string) => {
|
|
24
|
+
return variables[key] ?? "";
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function loadSkillDocs(skillsDir: string): Promise<ImSkillDoc[]> {
|
|
29
|
+
let entries;
|
|
30
|
+
try {
|
|
31
|
+
entries = await readdir(skillsDir, { withFileTypes: true });
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const docs = await Promise.all(
|
|
37
|
+
entries
|
|
38
|
+
.filter((entry) => entry.isDirectory())
|
|
39
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
40
|
+
.map(async (entry): Promise<ImSkillDoc | null> => {
|
|
41
|
+
const skillPath = join(skillsDir, entry.name, "skill.md");
|
|
42
|
+
try {
|
|
43
|
+
const content = await readFile(skillPath, "utf-8");
|
|
44
|
+
return { name: entry.name, content };
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
return docs.filter((doc): doc is ImSkillDoc => doc !== null);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function buildImSkillsPrompt(input: BuildImSkillsPromptInput): Promise<string> {
|
|
54
|
+
const skillsDir = input.skillsDir ?? DEFAULT_IM_SKILLS_DIR;
|
|
55
|
+
const docs = await loadSkillDocs(skillsDir);
|
|
56
|
+
return docs
|
|
57
|
+
.map((doc) => {
|
|
58
|
+
const rendered = renderTemplate(doc.content, input.variables).trim();
|
|
59
|
+
return [
|
|
60
|
+
`[ChatCCC IM skill: ${doc.name}]`,
|
|
61
|
+
rendered,
|
|
62
|
+
`[/ChatCCC IM skill: ${doc.name}]`,
|
|
63
|
+
].join("\n");
|
|
64
|
+
})
|
|
65
|
+
.join("\n\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 渲染技能目录下的子文档(skill.md 除外)并写入 outputDir。
|
|
70
|
+
* 返回写入的文件路径列表。通常在会话初始化时调用,写入的文档供 Agent 按需读取。
|
|
71
|
+
*/
|
|
72
|
+
export async function exportSkillSubDocs(
|
|
73
|
+
input: BuildImSkillsPromptInput,
|
|
74
|
+
outputDir: string,
|
|
75
|
+
): Promise<string[]> {
|
|
76
|
+
const skillsDir = input.skillsDir ?? DEFAULT_IM_SKILLS_DIR;
|
|
77
|
+
const files: string[] = [];
|
|
78
|
+
|
|
79
|
+
let entries;
|
|
80
|
+
try {
|
|
81
|
+
entries = await readdir(skillsDir, { withFileTypes: true });
|
|
82
|
+
} catch {
|
|
83
|
+
return files;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (!entry.isDirectory()) continue;
|
|
88
|
+
const skillPath = join(skillsDir, entry.name);
|
|
89
|
+
|
|
90
|
+
let subFiles;
|
|
91
|
+
try {
|
|
92
|
+
subFiles = await readdir(skillPath);
|
|
93
|
+
} catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const sf of subFiles) {
|
|
98
|
+
if (sf === "skill.md" || !sf.endsWith(".md")) continue;
|
|
99
|
+
const content = await readFile(join(skillPath, sf), "utf-8");
|
|
100
|
+
const rendered = renderTemplate(content, input.variables);
|
|
101
|
+
const outPath = join(outputDir, entry.name, sf);
|
|
102
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
103
|
+
await writeFile(outPath, rendered, "utf-8");
|
|
104
|
+
files.push(outPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return files;
|
|
109
|
+
}
|