chatccc 0.2.63 → 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.
- package/config.sample.json +1 -2
- package/im-skills/feishu-skill/download-video.mjs +10 -17
- package/im-skills/wechat-file-skill/receive-send-file.md +8 -11
- package/im-skills/wechat-file-skill/send-file.mjs +7 -24
- package/im-skills/wechat-file-skill/skill.md +4 -4
- package/im-skills/wechat-video-skill/receive-send-video.md +7 -9
- package/im-skills/wechat-video-skill/send-video.mjs +4 -8
- package/im-skills/wechat-video-skill/skill.md +4 -4
- 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 +56 -2
- 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/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/privacy.ts +67 -67
- package/src/session.ts +14 -2
- package/src/simplify.ts +119 -119
- package/src/web-ui.ts +3 -26
|
@@ -86,33 +86,33 @@ function buildSdkEnv(
|
|
|
86
86
|
baseUrl: string | undefined,
|
|
87
87
|
subagentModel: string | undefined,
|
|
88
88
|
): Record<string, string | undefined> | undefined {
|
|
89
|
-
const apiKeyTrim = (apiKey ?? "").trim();
|
|
90
|
-
const baseUrlTrim = (baseUrl ?? "").trim();
|
|
91
|
-
const subagentModelTrim = (subagentModel ?? "").trim();
|
|
92
|
-
const hasApiOverride = Boolean(apiKeyTrim || baseUrlTrim);
|
|
93
|
-
if (!hasApiOverride) return undefined;
|
|
94
|
-
|
|
95
|
-
const env: Record<string, string | undefined> = { ...process.env };
|
|
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.
|
|
98
|
-
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
99
|
-
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
100
|
-
delete env.ANTHROPIC_MODEL;
|
|
101
|
-
delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
102
|
-
delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
103
|
-
delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
104
|
-
delete env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
105
|
-
delete env.CLAUDE_CODE_EFFORT_LEVEL;
|
|
106
|
-
|
|
107
|
-
if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
|
|
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;
|
|
114
|
-
return env;
|
|
115
|
-
}
|
|
89
|
+
const apiKeyTrim = (apiKey ?? "").trim();
|
|
90
|
+
const baseUrlTrim = (baseUrl ?? "").trim();
|
|
91
|
+
const subagentModelTrim = (subagentModel ?? "").trim();
|
|
92
|
+
const hasApiOverride = Boolean(apiKeyTrim || baseUrlTrim);
|
|
93
|
+
if (!hasApiOverride) return undefined;
|
|
94
|
+
|
|
95
|
+
const env: Record<string, string | undefined> = { ...process.env };
|
|
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.
|
|
98
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
99
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
100
|
+
delete env.ANTHROPIC_MODEL;
|
|
101
|
+
delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
102
|
+
delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
103
|
+
delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
104
|
+
delete env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
105
|
+
delete env.CLAUDE_CODE_EFFORT_LEVEL;
|
|
106
|
+
|
|
107
|
+
if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
|
|
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;
|
|
114
|
+
return env;
|
|
115
|
+
}
|
|
116
116
|
|
|
117
117
|
function resolveSettingSources(
|
|
118
118
|
_apiKey: string | undefined,
|
|
@@ -291,19 +291,19 @@ class ClaudeAdapter implements ToolAdapter {
|
|
|
291
291
|
this.baseUrl,
|
|
292
292
|
this.subagentModel,
|
|
293
293
|
);
|
|
294
|
-
const session = unstable_v2_resumeSession(
|
|
295
|
-
sessionId,
|
|
296
|
-
sessionOpts as any,
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
if (signal?.aborted) {
|
|
300
|
-
session.close();
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const onAbort = () => { session.close(); };
|
|
304
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
305
|
-
|
|
306
|
-
await session.send(userText);
|
|
294
|
+
const session = unstable_v2_resumeSession(
|
|
295
|
+
sessionId,
|
|
296
|
+
sessionOpts as any,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
if (signal?.aborted) {
|
|
300
|
+
session.close();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const onAbort = () => { session.close(); };
|
|
304
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
305
|
+
|
|
306
|
+
await session.send(userText);
|
|
307
307
|
|
|
308
308
|
const stream = session.stream();
|
|
309
309
|
|
package/src/agent-file-rpc.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { ts } from "./config.ts";
|
|
|
7
7
|
import { readUtf8JsonBody } from "./agent-rpc-body.ts";
|
|
8
8
|
import { getAdapterForTool } from "./session.ts";
|
|
9
9
|
import { getChatsForSession } from "./session-chat-binding.ts";
|
|
10
|
+
import { splitFeishuTargetChats } from "./agent-platform-routing.ts";
|
|
10
11
|
|
|
11
12
|
export const AGENT_SEND_FILE_PATH = "/api/agent/send-file";
|
|
12
13
|
|
|
@@ -102,11 +103,25 @@ export async function handleAgentFileRequest(
|
|
|
102
103
|
return true;
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
const { getPlatformForChat } = await import("./session.ts");
|
|
107
|
+
const { targetChatIds, skippedUnsupported } = splitFeishuTargetChats(
|
|
108
|
+
chatIds,
|
|
109
|
+
(cid) => getPlatformForChat(cid)?.kind,
|
|
110
|
+
);
|
|
111
|
+
if (targetChatIds.length === 0) {
|
|
112
|
+
jsonReply(res, 409, {
|
|
113
|
+
ok: false,
|
|
114
|
+
error: "This endpoint only sends to Feishu chats. The bound chats are WeChat chats; use the WeChat file or video helper script instead.",
|
|
115
|
+
skippedUnsupported,
|
|
116
|
+
});
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
try {
|
|
106
121
|
const token = await getTenantAccessToken();
|
|
107
122
|
const caption = typeof payload.caption === "string" ? payload.caption.trim() : "";
|
|
108
123
|
let sentCount = 0;
|
|
109
|
-
for (const cid of
|
|
124
|
+
for (const cid of targetChatIds) {
|
|
110
125
|
try {
|
|
111
126
|
await sendFileReply(token, cid, filePath);
|
|
112
127
|
if (caption) await sendTextReply(token, cid, caption);
|
|
@@ -115,8 +130,8 @@ export async function handleAgentFileRequest(
|
|
|
115
130
|
console.error(`[${ts()}] [AGENT-FILE] send to ${cid} failed: ${(err as Error).message}`);
|
|
116
131
|
}
|
|
117
132
|
}
|
|
118
|
-
console.log(`[${ts()}] [AGENT-FILE] sent file to ${sentCount}/${
|
|
119
|
-
jsonReply(res, 200, { ok: true, sentTo: sentCount, total:
|
|
133
|
+
console.log(`[${ts()}] [AGENT-FILE] sent file to ${sentCount}/${targetChatIds.length} Feishu chats, session=${sessionId} path=${filePath} skippedUnsupported=${skippedUnsupported.length}`);
|
|
134
|
+
jsonReply(res, 200, { ok: true, sentTo: sentCount, total: targetChatIds.length, skippedUnsupported });
|
|
120
135
|
} catch (err) {
|
|
121
136
|
console.error(`[${ts()}] [AGENT-FILE] send failed: ${(err as Error).message}`);
|
|
122
137
|
jsonReply(res, 500, { ok: false, error: (err as Error).message });
|
|
@@ -154,4 +169,4 @@ export function buildAgentFileCapabilityPrompt(input: {
|
|
|
154
169
|
lines.splice(2, 0, `Current working directory: ${input.cwd}`);
|
|
155
170
|
}
|
|
156
171
|
return lines.join("\n");
|
|
157
|
-
}
|
|
172
|
+
}
|
package/src/agent-image-rpc.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { ts } from "./config.ts";
|
|
|
7
7
|
import { readUtf8JsonBody } from "./agent-rpc-body.ts";
|
|
8
8
|
import { getAdapterForTool } from "./session.ts";
|
|
9
9
|
import { getChatsForSession } from "./session-chat-binding.ts";
|
|
10
|
+
import { splitFeishuTargetChats } from "./agent-platform-routing.ts";
|
|
10
11
|
|
|
11
12
|
export const AGENT_SEND_IMAGE_PATH = "/api/agent/send-image";
|
|
12
13
|
|
|
@@ -99,11 +100,25 @@ export async function handleAgentImageRequest(
|
|
|
99
100
|
return true;
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
const { getPlatformForChat } = await import("./session.ts");
|
|
104
|
+
const { targetChatIds, skippedUnsupported } = splitFeishuTargetChats(
|
|
105
|
+
chatIds,
|
|
106
|
+
(cid) => getPlatformForChat(cid)?.kind,
|
|
107
|
+
);
|
|
108
|
+
if (targetChatIds.length === 0) {
|
|
109
|
+
jsonReply(res, 409, {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: "This endpoint only sends to Feishu chats. The bound chats are WeChat chats; use the WeChat image helper script instead.",
|
|
112
|
+
skippedUnsupported,
|
|
113
|
+
});
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
try {
|
|
103
118
|
const token = await getTenantAccessToken();
|
|
104
119
|
const caption = typeof payload.caption === "string" ? payload.caption.trim() : "";
|
|
105
120
|
let sentCount = 0;
|
|
106
|
-
for (const cid of
|
|
121
|
+
for (const cid of targetChatIds) {
|
|
107
122
|
try {
|
|
108
123
|
await sendImageReply(token, cid, imagePath);
|
|
109
124
|
if (caption) await sendTextReply(token, cid, caption);
|
|
@@ -112,8 +127,8 @@ export async function handleAgentImageRequest(
|
|
|
112
127
|
console.error(`[${ts()}] [AGENT-IMAGE] send to ${cid} failed: ${(err as Error).message}`);
|
|
113
128
|
}
|
|
114
129
|
}
|
|
115
|
-
console.log(`[${ts()}] [AGENT-IMAGE] sent image to ${sentCount}/${
|
|
116
|
-
jsonReply(res, 200, { ok: true, sentTo: sentCount, total:
|
|
130
|
+
console.log(`[${ts()}] [AGENT-IMAGE] sent image to ${sentCount}/${targetChatIds.length} Feishu chats, session=${sessionId} path=${imagePath} skippedUnsupported=${skippedUnsupported.length}`);
|
|
131
|
+
jsonReply(res, 200, { ok: true, sentTo: sentCount, total: targetChatIds.length, skippedUnsupported });
|
|
117
132
|
} catch (err) {
|
|
118
133
|
console.error(`[${ts()}] [AGENT-IMAGE] send failed: ${(err as Error).message}`);
|
|
119
134
|
jsonReply(res, 500, { ok: false, error: (err as Error).message });
|
|
@@ -150,4 +165,4 @@ export function buildAgentImageCapabilityPrompt(input: {
|
|
|
150
165
|
lines.splice(2, 0, `Current working directory: ${input.cwd}`);
|
|
151
166
|
}
|
|
152
167
|
return lines.join("\n");
|
|
153
|
-
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SkippedUnsupportedChat {
|
|
2
|
+
chatId: string;
|
|
3
|
+
platformKind: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface FeishuTargetSplit {
|
|
7
|
+
targetChatIds: string[];
|
|
8
|
+
skippedUnsupported: SkippedUnsupportedChat[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function splitFeishuTargetChats(
|
|
12
|
+
chatIds: readonly string[],
|
|
13
|
+
getPlatformKind: (chatId: string) => string | undefined,
|
|
14
|
+
): FeishuTargetSplit {
|
|
15
|
+
const targetChatIds: string[] = [];
|
|
16
|
+
const skippedUnsupported: SkippedUnsupportedChat[] = [];
|
|
17
|
+
|
|
18
|
+
for (const chatId of chatIds) {
|
|
19
|
+
const platformKind = getPlatformKind(chatId);
|
|
20
|
+
if (platformKind === "wechat") {
|
|
21
|
+
skippedUnsupported.push({ chatId, platformKind });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
targetChatIds.push(chatId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { targetChatIds, skippedUnsupported };
|
|
28
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -5,8 +5,6 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { appendFile, cp, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
|
|
8
|
-
import { Domain } from "@larksuiteoapi/node-sdk";
|
|
9
|
-
|
|
10
8
|
import { printServiceDidNotStart } from "./exit-banner.ts";
|
|
11
9
|
import { appendStartupTrace, setupFileLogging } from "./shared.ts";
|
|
12
10
|
import {
|
|
@@ -92,25 +90,9 @@ export interface CodexConfig {
|
|
|
92
90
|
effort: string;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
export type FeishuDomain = "feishu" | "lark";
|
|
96
|
-
|
|
97
93
|
export interface FeishuConfig {
|
|
98
94
|
appId: string;
|
|
99
95
|
appSecret: string;
|
|
100
|
-
/** "feishu" (默认) → https://open.feishu.cn, "lark" → https://open.larksuite.com */
|
|
101
|
-
domain?: FeishuDomain;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** 用户配置的 domain 字符串 → SDK Domain 枚举 */
|
|
105
|
-
export function feishuDomainToSdkDomain(domain?: FeishuDomain): Domain {
|
|
106
|
-
return domain === "lark" ? Domain.Lark : Domain.Feishu;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** 用户配置的 domain 字符串 → REST API base URL */
|
|
110
|
-
export function feishuDomainToBaseUrl(domain?: FeishuDomain): string {
|
|
111
|
-
return domain === "lark"
|
|
112
|
-
? "https://open.larksuite.com/open-apis"
|
|
113
|
-
: "https://open.feishu.cn/open-apis";
|
|
114
96
|
}
|
|
115
97
|
|
|
116
98
|
export interface PlatformConfig {
|
|
@@ -316,7 +298,7 @@ function autofillToolPathsAfterSampleCopy(configFile: string): void {
|
|
|
316
298
|
|
|
317
299
|
function loadConfig(): AppConfig {
|
|
318
300
|
const defaults: AppConfig = {
|
|
319
|
-
feishu: { appId: "", appSecret: ""
|
|
301
|
+
feishu: { appId: "", appSecret: "" },
|
|
320
302
|
platforms: { feishu: { enabled: true }, ilink: { enabled: true } },
|
|
321
303
|
port: 18080,
|
|
322
304
|
gitTimeoutSeconds: 180,
|
|
@@ -376,8 +358,6 @@ function loadConfig(): AppConfig {
|
|
|
376
358
|
}
|
|
377
359
|
|
|
378
360
|
const feishu = parsed.feishu ?? { appId: "", appSecret: "" };
|
|
379
|
-
const feishuDomain: FeishuDomain =
|
|
380
|
-
(feishu as { domain?: unknown }).domain === "lark" ? "lark" : "feishu";
|
|
381
361
|
const claude = parsed.claude ?? {} as Partial<ClaudeConfig>;
|
|
382
362
|
const cursorRaw = (parsed.cursor ?? {}) as NonNullable<typeof parsed.cursor>;
|
|
383
363
|
const codexRaw = (parsed.codex ?? {}) as NonNullable<typeof parsed.codex>;
|
|
@@ -440,7 +420,6 @@ function loadConfig(): AppConfig {
|
|
|
440
420
|
feishu: {
|
|
441
421
|
appId: feishu.appId ?? "",
|
|
442
422
|
appSecret: feishu.appSecret ?? "",
|
|
443
|
-
domain: feishuDomain,
|
|
444
423
|
},
|
|
445
424
|
platforms: {
|
|
446
425
|
feishu: {
|
|
@@ -518,7 +497,7 @@ export let APP_SECRET = config.feishu.appSecret;
|
|
|
518
497
|
export let FEISHU_ENABLED = config.platforms.feishu.enabled;
|
|
519
498
|
export let ILINK_ENABLED = config.platforms.ilink.enabled;
|
|
520
499
|
export let ILINK_REUSE_TOKEN_ON_START = config.platforms.ilink.reuseTokenOnStart ?? true;
|
|
521
|
-
export
|
|
500
|
+
export const BASE_URL = "https://open.feishu.cn/open-apis";
|
|
522
501
|
export const CHATCCC_PORT = config.port;
|
|
523
502
|
|
|
524
503
|
/** 与 CHATCCC_PORT 一致,供 --local 连接本机中继 */
|
|
@@ -597,7 +576,6 @@ export function applyLoadedConfig(next: AppConfig): void {
|
|
|
597
576
|
|
|
598
577
|
APP_ID = next.feishu.appId;
|
|
599
578
|
APP_SECRET = next.feishu.appSecret;
|
|
600
|
-
BASE_URL = feishuDomainToBaseUrl(next.feishu.domain);
|
|
601
579
|
FEISHU_ENABLED = next.platforms.feishu.enabled;
|
|
602
580
|
ILINK_ENABLED = next.platforms.ilink.enabled;
|
|
603
581
|
ILINK_REUSE_TOKEN_ON_START = next.platforms.ilink.reuseTokenOnStart ?? true;
|
package/src/im-skills.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface ImSkillVariableMap {
|
|
|
11
11
|
|
|
12
12
|
export interface BuildImSkillsPromptInput {
|
|
13
13
|
skillsDir?: string;
|
|
14
|
+
enabledSkillNames?: readonly string[];
|
|
14
15
|
variables: ImSkillVariableMap;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -52,8 +53,12 @@ async function loadSkillDocs(skillsDir: string): Promise<ImSkillDoc[]> {
|
|
|
52
53
|
|
|
53
54
|
export async function buildImSkillsPrompt(input: BuildImSkillsPromptInput): Promise<string> {
|
|
54
55
|
const skillsDir = input.skillsDir ?? DEFAULT_IM_SKILLS_DIR;
|
|
56
|
+
const enabledSkillNames = input.enabledSkillNames
|
|
57
|
+
? new Set(input.enabledSkillNames)
|
|
58
|
+
: null;
|
|
55
59
|
const docs = await loadSkillDocs(skillsDir);
|
|
56
60
|
return docs
|
|
61
|
+
.filter((doc) => !enabledSkillNames || enabledSkillNames.has(doc.name))
|
|
57
62
|
.map((doc) => {
|
|
58
63
|
const rendered = renderTemplate(doc.content, input.variables).trim();
|
|
59
64
|
return [
|
|
@@ -74,6 +79,9 @@ export async function exportSkillSubDocs(
|
|
|
74
79
|
outputDir: string,
|
|
75
80
|
): Promise<string[]> {
|
|
76
81
|
const skillsDir = input.skillsDir ?? DEFAULT_IM_SKILLS_DIR;
|
|
82
|
+
const enabledSkillNames = input.enabledSkillNames
|
|
83
|
+
? new Set(input.enabledSkillNames)
|
|
84
|
+
: null;
|
|
77
85
|
const files: string[] = [];
|
|
78
86
|
|
|
79
87
|
let entries;
|
|
@@ -85,6 +93,7 @@ export async function exportSkillSubDocs(
|
|
|
85
93
|
|
|
86
94
|
for (const entry of entries) {
|
|
87
95
|
if (!entry.isDirectory()) continue;
|
|
96
|
+
if (enabledSkillNames && !enabledSkillNames.has(entry.name)) continue;
|
|
88
97
|
const skillPath = join(skillsDir, entry.name);
|
|
89
98
|
|
|
90
99
|
let subFiles;
|
package/src/index.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
26
26
|
|
|
27
|
-
import { WSClient, EventDispatcher
|
|
27
|
+
import { WSClient, EventDispatcher } from "@larksuiteoapi/node-sdk";
|
|
28
28
|
import WebSocket from "ws";
|
|
29
29
|
|
|
30
30
|
import { appendStartupTrace, attachRelayWebSocket, ensureSingleInstance, freeRelayListenPort, installCrashLogging, waitForPortFree } from "./shared.ts";
|
|
@@ -50,8 +50,6 @@ import {
|
|
|
50
50
|
maskAppId,
|
|
51
51
|
resolveDefaultAgentTool,
|
|
52
52
|
toolDisplayName,
|
|
53
|
-
config,
|
|
54
|
-
feishuDomainToSdkDomain,
|
|
55
53
|
ts,
|
|
56
54
|
} from "./config.ts";
|
|
57
55
|
import { printServiceDidNotStart, printServiceRunningHint } from "./exit-banner.ts";
|
|
@@ -215,7 +213,15 @@ async function formatMessageContent(message: { message_id?: string; message_type
|
|
|
215
213
|
return `[视频] message_id=${messageId} file_key=${fileKey} file_name=${fileName}`;
|
|
216
214
|
}
|
|
217
215
|
|
|
218
|
-
|
|
216
|
+
if (message.message_type === "file") {
|
|
217
|
+
const fileKey = content.file_key as string | undefined;
|
|
218
|
+
const fileName = (content.file_name as string) || "download.bin";
|
|
219
|
+
const messageId = message.message_id;
|
|
220
|
+
if (!fileKey || !messageId) return contentStr;
|
|
221
|
+
return `[文件] message_id=${messageId} file_key=${fileKey} file_name=${fileName}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 其他类型(audio, sticker 等)直接给原始 JSON
|
|
219
225
|
return contentStr;
|
|
220
226
|
}
|
|
221
227
|
|
|
@@ -405,7 +411,7 @@ async function startBotServiceCore(): Promise<void> {
|
|
|
405
411
|
console.error(` 接口: POST ${BASE_URL}/auth/v3/tenant_access_token/internal`);
|
|
406
412
|
console.error(" 常见原因:");
|
|
407
413
|
console.error(
|
|
408
|
-
|
|
414
|
+
" - 本机网络无法访问 open.feishu.cn(可尝试:关闭系统/终端代理、检查防火墙;Windows 可管理员运行 netsh winsock reset 后重启)",
|
|
409
415
|
);
|
|
410
416
|
console.error(" - App ID / App Secret 与开放平台「凭证与基础信息」不一致");
|
|
411
417
|
console.error(" - 自建应用尚未创建/发布可用版本");
|
|
@@ -596,7 +602,6 @@ async function startBotServiceCore(): Promise<void> {
|
|
|
596
602
|
const wsClient = new WSClient({
|
|
597
603
|
appId: APP_ID,
|
|
598
604
|
appSecret: APP_SECRET,
|
|
599
|
-
domain: feishuDomainToSdkDomain(config.feishu.domain),
|
|
600
605
|
onReady: async () => {
|
|
601
606
|
await rebuildBindingsFromRegistry().catch((err) =>
|
|
602
607
|
console.error(`[${ts()}] [SDK READY] rebuild bindings failed: ${(err as Error).message}`)
|
package/src/privacy.ts
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { USER_DATA_DIR } from "./config.ts";
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// 隐私替换规则
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
interface PrivacyRules {
|
|
10
|
-
[key: string]: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let rules: PrivacyRules | null = null;
|
|
14
|
-
let loaded = false;
|
|
15
|
-
|
|
16
|
-
function loadRules(): PrivacyRules {
|
|
17
|
-
const filePath = join(USER_DATA_DIR, "privacy.json");
|
|
18
|
-
if (!existsSync(filePath)) {
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
23
|
-
const parsed = JSON.parse(raw);
|
|
24
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
25
|
-
console.error(`[PRIVACY] privacy.json 格式错误:应为对象`);
|
|
26
|
-
return {};
|
|
27
|
-
}
|
|
28
|
-
for (const [k, v] of Object.entries(parsed)) {
|
|
29
|
-
if (typeof v !== "string") {
|
|
30
|
-
console.error(`[PRIVACY] privacy.json 值必须为字符串,跳过 "${k}"`);
|
|
31
|
-
delete (parsed as Record<string, unknown>)[k];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return parsed as PrivacyRules;
|
|
35
|
-
} catch (err) {
|
|
36
|
-
console.error(`[PRIVACY] 读取 privacy.json 失败: ${(err as Error).message}`);
|
|
37
|
-
return {};
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function getPrivacyRules(): PrivacyRules {
|
|
42
|
-
if (!loaded) {
|
|
43
|
-
rules = loadRules();
|
|
44
|
-
loaded = true;
|
|
45
|
-
}
|
|
46
|
-
return rules!;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** 重新加载规则(热更新用) */
|
|
50
|
-
export function reloadPrivacyRules(): void {
|
|
51
|
-
loaded = false;
|
|
52
|
-
rules = null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 对文本应用隐私替换规则。
|
|
57
|
-
* 若无规则或文本为空,直接返回原文。
|
|
58
|
-
*/
|
|
59
|
-
export function applyPrivacy(text: string): string {
|
|
60
|
-
const r = getPrivacyRules();
|
|
61
|
-
if (Object.keys(r).length === 0 || !text) return text;
|
|
62
|
-
let result = text;
|
|
63
|
-
for (const [from, to] of Object.entries(r)) {
|
|
64
|
-
// 用 split+join 替代 replaceAll,避免正则特殊字符问题
|
|
65
|
-
result = result.split(from).join(to);
|
|
66
|
-
}
|
|
67
|
-
return result;
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { USER_DATA_DIR } from "./config.ts";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// 隐私替换规则
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
interface PrivacyRules {
|
|
10
|
+
[key: string]: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let rules: PrivacyRules | null = null;
|
|
14
|
+
let loaded = false;
|
|
15
|
+
|
|
16
|
+
function loadRules(): PrivacyRules {
|
|
17
|
+
const filePath = join(USER_DATA_DIR, "privacy.json");
|
|
18
|
+
if (!existsSync(filePath)) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
23
|
+
const parsed = JSON.parse(raw);
|
|
24
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
25
|
+
console.error(`[PRIVACY] privacy.json 格式错误:应为对象`);
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
29
|
+
if (typeof v !== "string") {
|
|
30
|
+
console.error(`[PRIVACY] privacy.json 值必须为字符串,跳过 "${k}"`);
|
|
31
|
+
delete (parsed as Record<string, unknown>)[k];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return parsed as PrivacyRules;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(`[PRIVACY] 读取 privacy.json 失败: ${(err as Error).message}`);
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getPrivacyRules(): PrivacyRules {
|
|
42
|
+
if (!loaded) {
|
|
43
|
+
rules = loadRules();
|
|
44
|
+
loaded = true;
|
|
45
|
+
}
|
|
46
|
+
return rules!;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 重新加载规则(热更新用) */
|
|
50
|
+
export function reloadPrivacyRules(): void {
|
|
51
|
+
loaded = false;
|
|
52
|
+
rules = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 对文本应用隐私替换规则。
|
|
57
|
+
* 若无规则或文本为空,直接返回原文。
|
|
58
|
+
*/
|
|
59
|
+
export function applyPrivacy(text: string): string {
|
|
60
|
+
const r = getPrivacyRules();
|
|
61
|
+
if (Object.keys(r).length === 0 || !text) return text;
|
|
62
|
+
let result = text;
|
|
63
|
+
for (const [from, to] of Object.entries(r)) {
|
|
64
|
+
// 用 split+join 替代 replaceAll,避免正则特殊字符问题
|
|
65
|
+
result = result.split(from).join(to);
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
68
|
}
|
package/src/session.ts
CHANGED
|
@@ -92,6 +92,17 @@ export function _getPlatformForChatForTest(chatId: string): PlatformAdapter | nu
|
|
|
92
92
|
return platformForChat(chatId);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
export function getPlatformForChat(chatId: string): PlatformAdapter | null {
|
|
96
|
+
return platformForChat(chatId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function imSkillNamesForPlatform(platform: PlatformAdapter): string[] {
|
|
100
|
+
if (platform.kind === "wechat") {
|
|
101
|
+
return ["wechat-image-skill", "wechat-file-skill", "wechat-video-skill"];
|
|
102
|
+
}
|
|
103
|
+
return ["feishu-skill"];
|
|
104
|
+
}
|
|
105
|
+
|
|
95
106
|
export let sessionGen = 0;
|
|
96
107
|
/** @deprecated 使用 activePrompts (session-chat-binding.ts) + displayCards 替代 */
|
|
97
108
|
export const chatSessionMap = new Map<string, {
|
|
@@ -722,8 +733,9 @@ export async function runAgentSession(
|
|
|
722
733
|
wechat_send_file_script: join(wechatFileSkillDir, "send-file.mjs"),
|
|
723
734
|
wechat_send_video_script: join(wechatVideoSkillDir, "send-video.mjs"),
|
|
724
735
|
};
|
|
725
|
-
|
|
726
|
-
await
|
|
736
|
+
const enabledSkillNames = imSkillNamesForPlatform(platform);
|
|
737
|
+
var imSkillsPrompt = await buildImSkillsPrompt({ variables: skillVariables, enabledSkillNames });
|
|
738
|
+
await exportSkillSubDocs({ variables: skillVariables, enabledSkillNames }, imSkillsCacheDir);
|
|
727
739
|
var userTextWithCapabilities = [
|
|
728
740
|
...(imSkillsPrompt ? [imSkillsPrompt, ""] : []),
|
|
729
741
|
"[User message]",
|