chatccc 0.2.5 → 0.2.7
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 +84 -20
- package/package.json +1 -1
- package/src/__tests__/adapter-interface.test.ts +151 -151
- package/src/__tests__/cards.test.ts +417 -296
- package/src/__tests__/claude-adapter.test.ts +528 -528
- package/src/__tests__/config.test.ts +123 -0
- package/src/__tests__/cursor-adapter.test.ts +663 -0
- package/src/__tests__/cursor-session-meta-store.test.ts +212 -0
- package/src/__tests__/fixtures/cursor_partial_only.jsonl +5 -0
- package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +13 -0
- package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +12 -0
- package/src/__tests__/git-command.test.ts +288 -0
- package/src/__tests__/session.test.ts +475 -295
- package/src/adapters/adapter-interface.ts +151 -126
- package/src/adapters/claude-adapter.ts +257 -256
- package/src/adapters/cursor-adapter.ts +402 -0
- package/src/adapters/cursor-session-meta-store.ts +154 -0
- package/src/cards.ts +133 -22
- package/src/config.ts +152 -4
- package/src/feishu-api.ts +23 -9
- package/src/git-command.ts +202 -0
- package/src/index.ts +164 -35
- package/src/session.ts +620 -450
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
BASE_URL,
|
|
37
37
|
CLAUDE_EFFORT,
|
|
38
38
|
CLAUDE_MODEL,
|
|
39
|
+
GIT_TIMEOUT_MS,
|
|
39
40
|
anthropicConfigDisplay,
|
|
40
41
|
LOCAL_RELAY_URL,
|
|
41
42
|
PID_FILE,
|
|
@@ -48,13 +49,17 @@ import {
|
|
|
48
49
|
getDefaultCwd,
|
|
49
50
|
maskAppId,
|
|
50
51
|
setDefaultCwd,
|
|
52
|
+
getRecentDirs,
|
|
53
|
+
addRecentDir,
|
|
54
|
+
sessionPrefixForTool,
|
|
55
|
+
toolDisplayName,
|
|
51
56
|
ts,
|
|
52
57
|
} from "./config.ts";
|
|
53
58
|
import { printServiceDidNotStart, printServiceRunningHint } from "./exit-banner.ts";
|
|
54
59
|
import {
|
|
55
60
|
addReaction,
|
|
56
61
|
createGroupChat,
|
|
57
|
-
|
|
62
|
+
extractSessionInfo,
|
|
58
63
|
getChatInfo,
|
|
59
64
|
getTenantAccessToken,
|
|
60
65
|
recallMessage,
|
|
@@ -66,8 +71,9 @@ import {
|
|
|
66
71
|
verifyAllPermissions,
|
|
67
72
|
reportPermissionResults,
|
|
68
73
|
} from "./feishu-api.ts";
|
|
69
|
-
import { buildHelpCard, buildStatusCard, buildProgressCard, buildCdContent, buildSessionsCard } from "./cards.ts";
|
|
74
|
+
import { buildHelpCard, buildStatusCard, buildProgressCard, buildCdContent, buildCdCard, buildSessionsCard } from "./cards.ts";
|
|
70
75
|
import { updateCardKitCard } from "./cardkit.ts";
|
|
76
|
+
import { formatGitResult, gitResultHeaderTemplate, runGitCommand } from "./git-command.ts";
|
|
71
77
|
import {
|
|
72
78
|
MAX_PROCESSED,
|
|
73
79
|
chatSessionMap,
|
|
@@ -78,8 +84,7 @@ import {
|
|
|
78
84
|
resetState,
|
|
79
85
|
resumeAndPrompt,
|
|
80
86
|
sessionInfoMap,
|
|
81
|
-
|
|
82
|
-
getAdapter,
|
|
87
|
+
getAdapterForTool,
|
|
83
88
|
} from "./session.ts";
|
|
84
89
|
|
|
85
90
|
// ---------------------------------------------------------------------------
|
|
@@ -97,7 +102,11 @@ function getInnerEvent(data: Evt): InnerEvent {
|
|
|
97
102
|
return (data.event ?? data) as InnerEvent;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
/**
|
|
106
|
+
* 将飞书消息的原始 content JSON 结构转成可读文本,保留代码块等结构信息。
|
|
107
|
+
* 未知类型直接返回 JSON 原文,让 AI 自行理解。
|
|
108
|
+
*/
|
|
109
|
+
function formatMessageContent(message: { message_type?: string; content?: string }): string {
|
|
101
110
|
const contentStr = message.content ?? "{}";
|
|
102
111
|
let content: Record<string, unknown>;
|
|
103
112
|
try { content = JSON.parse(contentStr); } catch { return ""; }
|
|
@@ -109,7 +118,36 @@ function extractText(message: { message_type?: string; content?: string }): stri
|
|
|
109
118
|
text = text.replace(/ /gi, " ");
|
|
110
119
|
return text.trim();
|
|
111
120
|
}
|
|
112
|
-
|
|
121
|
+
|
|
122
|
+
if (message.message_type === "post") {
|
|
123
|
+
return formatPostContent(content);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 其他类型(image, file, audio, media, sticker)直接给原始 JSON
|
|
127
|
+
return contentStr;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function formatPostContent(content: Record<string, unknown>): string {
|
|
131
|
+
const paragraphs = content.content as unknown[][];
|
|
132
|
+
if (!Array.isArray(paragraphs)) return "";
|
|
133
|
+
|
|
134
|
+
const parts: string[] = [];
|
|
135
|
+
for (const line of paragraphs) {
|
|
136
|
+
if (!Array.isArray(line)) continue;
|
|
137
|
+
for (const elem of line) {
|
|
138
|
+
const el = elem as Record<string, unknown>;
|
|
139
|
+
if (!el || typeof el !== "object") continue;
|
|
140
|
+
const t = typeof el.text === "string" ? el.text : "";
|
|
141
|
+
|
|
142
|
+
if (el.tag === "code_block") {
|
|
143
|
+
const lang = typeof el.language === "string" ? el.language : "";
|
|
144
|
+
parts.push("```" + lang + "\n" + t + "\n```");
|
|
145
|
+
} else if (el.tag === "p" || el.tag === "text") {
|
|
146
|
+
if (t) parts.push(t);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return parts.join("\n").trim();
|
|
113
151
|
}
|
|
114
152
|
|
|
115
153
|
// ---------------------------------------------------------------------------
|
|
@@ -139,8 +177,12 @@ function parseCardAction(data: unknown): CardActionResult | null {
|
|
|
139
177
|
}
|
|
140
178
|
if (!cmd) return null;
|
|
141
179
|
|
|
142
|
-
const CMD_MAP: Record<string, string> = { stop: "/stop", new: "/new", restart: "/restart", status: "/status", cd: "/cd", sessions: "/sessions" };
|
|
143
|
-
|
|
180
|
+
const CMD_MAP: Record<string, string> = { stop: "/stop", new: "/new", "new cursor": "/new cursor", restart: "/restart", status: "/status", cd: "/cd", sessions: "/sessions" };
|
|
181
|
+
let text = CMD_MAP[cmd] ?? "";
|
|
182
|
+
if (cmd === "cd" && typeof action.value === "object" && action.value !== null) {
|
|
183
|
+
const path = (action.value as Record<string, string>).path;
|
|
184
|
+
if (path) text = `/cd ${path}`;
|
|
185
|
+
}
|
|
144
186
|
if (!text) return null;
|
|
145
187
|
|
|
146
188
|
const chatId =
|
|
@@ -189,9 +231,10 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
189
231
|
let sessionCwd: string | undefined;
|
|
190
232
|
try {
|
|
191
233
|
const chatInfo = await getChatInfo(cdToken, chatId);
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
const
|
|
234
|
+
const sessionInfoResult = extractSessionInfo(chatInfo.description);
|
|
235
|
+
if (sessionInfoResult) {
|
|
236
|
+
const adapter = getAdapterForTool(sessionInfoResult.tool);
|
|
237
|
+
const info = await adapter.getSessionInfo(sessionInfoResult.sessionId);
|
|
195
238
|
sessionCwd = info?.cwd;
|
|
196
239
|
}
|
|
197
240
|
} catch { /* 非会话群或获取失败,不显示 */ }
|
|
@@ -224,6 +267,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
224
267
|
const isUpdate = !!arg && targetDir !== currentDir;
|
|
225
268
|
if (isUpdate) {
|
|
226
269
|
await setDefaultCwd(targetDir);
|
|
270
|
+
await addRecentDir(targetDir);
|
|
227
271
|
}
|
|
228
272
|
|
|
229
273
|
// Read directory entries
|
|
@@ -248,12 +292,39 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
248
292
|
return a.name.localeCompare(b.name);
|
|
249
293
|
});
|
|
250
294
|
|
|
251
|
-
|
|
252
|
-
|
|
295
|
+
if (!arg) {
|
|
296
|
+
// /cd 无参数:展示卡片(含最近使用路径按钮)
|
|
297
|
+
const recentDirs = await getRecentDirs();
|
|
298
|
+
const card = buildCdCard(targetDir, withStats, recentDirs, sessionCwd);
|
|
299
|
+
const resp = await fetch(`${BASE_URL}/im/v1/messages?receive_id_type=chat_id`, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: {
|
|
302
|
+
Authorization: `Bearer ${cdToken}`,
|
|
303
|
+
"Content-Type": "application/json",
|
|
304
|
+
},
|
|
305
|
+
body: JSON.stringify({ receive_id: chatId, msg_type: "interactive", content: card }),
|
|
306
|
+
});
|
|
307
|
+
const respData: Record<string, any> = await resp.json().catch(() => ({}));
|
|
308
|
+
console.log(`[${ts()}] [CD] card sent, code=${respData.code}, msgId=${respData.data?.message_id ?? "N/A"}, recentDirs=${recentDirs.length}`);
|
|
309
|
+
} else {
|
|
310
|
+
// /cd <path>:切换目录,发送文本卡片
|
|
311
|
+
const content = buildCdContent(targetDir, withStats, isUpdate, sessionCwd);
|
|
312
|
+
await sendCardReply(cdToken, chatId, "新会话工作路径", content, "blue");
|
|
313
|
+
}
|
|
253
314
|
return;
|
|
254
315
|
}
|
|
255
316
|
|
|
256
|
-
if (text === "/new") {
|
|
317
|
+
if (text === "/new" || text.startsWith("/new ")) {
|
|
318
|
+
const toolArg = text.slice(5).trim();
|
|
319
|
+
const tool = toolArg || "claude";
|
|
320
|
+
const validTools = ["claude", "cursor"];
|
|
321
|
+
if (!validTools.includes(tool)) {
|
|
322
|
+
const warnToken = await getTenantAccessToken();
|
|
323
|
+
await sendCardReply(warnToken, chatId, "Error", `未知的工具类型: "${toolArg}"。支持: claude (Claude Code), cursor (Cursor)。`, "red");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const toolLabel = toolDisplayName(tool);
|
|
327
|
+
|
|
257
328
|
if (!openId) {
|
|
258
329
|
console.log(`[${ts()}] [WARN] Cannot get sender open_id`);
|
|
259
330
|
const warnToken = await getTenantAccessToken();
|
|
@@ -265,13 +336,13 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
265
336
|
|
|
266
337
|
let sessionId: string;
|
|
267
338
|
try {
|
|
268
|
-
sessionId = await initClaudeSession();
|
|
269
|
-
console.log(`[${ts()}] [STEP 1/4]
|
|
339
|
+
sessionId = await initClaudeSession(tool);
|
|
340
|
+
console.log(`[${ts()}] [STEP 1/4] ${toolLabel} session created: ${sessionId} → OK`);
|
|
270
341
|
} catch (err) {
|
|
271
342
|
console.error(`[${ts()}] [STEP 1/4] FAIL: ${(err as Error).message}`);
|
|
272
343
|
await sendCardReply(
|
|
273
344
|
freshToken, chatId, "Error",
|
|
274
|
-
`Failed to initialize
|
|
345
|
+
`Failed to initialize ${toolLabel} session:\n${(err as Error).message}`,
|
|
275
346
|
"red"
|
|
276
347
|
);
|
|
277
348
|
return;
|
|
@@ -289,8 +360,9 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
289
360
|
|
|
290
361
|
try {
|
|
291
362
|
const initialName = `新会话-${sessionId}`;
|
|
292
|
-
|
|
293
|
-
|
|
363
|
+
const descPrefix = sessionPrefixForTool(tool);
|
|
364
|
+
await updateChatInfo(freshToken, newChatId, initialName, `${descPrefix} ${sessionId}`);
|
|
365
|
+
console.log(`[${ts()}] [STEP 3/4] Renamed group → name="${initialName}" (${toolLabel}) → OK`);
|
|
294
366
|
} catch (err) {
|
|
295
367
|
console.error(`[${ts()}] [STEP 3/4] FAIL: ${(err as Error).message}`);
|
|
296
368
|
await sendCardReply(freshToken, chatId, "Error", `Group created but rename failed:\n${(err as Error).message}`, "yellow");
|
|
@@ -298,9 +370,15 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
298
370
|
}
|
|
299
371
|
|
|
300
372
|
const cwd = await getDefaultCwd();
|
|
373
|
+
const adapter = getAdapterForTool(tool);
|
|
301
374
|
await sendCardReply(
|
|
302
|
-
freshToken, newChatId,
|
|
303
|
-
`群聊已创建,这是你的
|
|
375
|
+
freshToken, newChatId, `${toolLabel} Session Ready`,
|
|
376
|
+
`群聊已创建,这是你的 **${toolLabel}** 会话群。\n\n` +
|
|
377
|
+
`**Session ID:** ${sessionId}\n` +
|
|
378
|
+
`**工作目录:** \`${cwd}\`\n\n` +
|
|
379
|
+
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
380
|
+
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
381
|
+
`发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。`,
|
|
304
382
|
"green"
|
|
305
383
|
);
|
|
306
384
|
|
|
@@ -313,10 +391,13 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
313
391
|
const token = await getTenantAccessToken();
|
|
314
392
|
const chatInfo = await getChatInfo(token, chatId);
|
|
315
393
|
const description = chatInfo.description;
|
|
316
|
-
const
|
|
394
|
+
const sessionInfo = extractSessionInfo(description);
|
|
317
395
|
|
|
318
|
-
if (
|
|
319
|
-
|
|
396
|
+
if (sessionInfo) {
|
|
397
|
+
const sessionId = sessionInfo.sessionId;
|
|
398
|
+
const descriptionTool = sessionInfo.tool;
|
|
399
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
400
|
+
console.log(`[${ts()}] [RESUME] ${toolLabel} session group detected, session=${sessionId} tool=${descriptionTool}`);
|
|
320
401
|
|
|
321
402
|
const freshToken = await getTenantAccessToken();
|
|
322
403
|
|
|
@@ -347,16 +428,21 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
347
428
|
}
|
|
348
429
|
|
|
349
430
|
if (text === "/status") {
|
|
350
|
-
const status = getSessionStatus(chatId);
|
|
431
|
+
const status = await getSessionStatus(chatId);
|
|
351
432
|
const running = chatSessionMap.get(chatId);
|
|
352
433
|
const isActive = running && !running.stopped;
|
|
353
434
|
const statusText = [
|
|
354
435
|
`**Session ID:** \`${status?.sessionId ?? sessionId}\``,
|
|
436
|
+
`**工具:** ${toolLabel}`,
|
|
355
437
|
`**状态:** ${isActive ? "🟢 运行中" : "⚪ 空闲"}`,
|
|
356
438
|
`**已对话轮数:** ${status?.turnCount ?? 0}`,
|
|
357
439
|
`**模型:** ${status?.model ?? anthropicConfigDisplay(CLAUDE_MODEL)}`,
|
|
358
|
-
`**Effort:** ${status?.effort ?? anthropicConfigDisplay(CLAUDE_EFFORT)}`,
|
|
359
440
|
];
|
|
441
|
+
// effort 仅在该工具有此概念时显示(status?.effort 为 null 表示
|
|
442
|
+
// 当前工具没有 effort,如 Cursor,应隐藏整行避免误导)
|
|
443
|
+
if (status?.effort != null) {
|
|
444
|
+
statusText.push(`**Effort:** ${status.effort}`);
|
|
445
|
+
}
|
|
360
446
|
if (isActive) {
|
|
361
447
|
const elapsed = Math.floor((Date.now() - (status!.startTime)) / 1000);
|
|
362
448
|
const mins = Math.floor(elapsed / 60);
|
|
@@ -382,7 +468,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
382
468
|
}
|
|
383
469
|
|
|
384
470
|
if (text === "/sessions") {
|
|
385
|
-
const allSessions = getAllSessionsStatus();
|
|
471
|
+
const allSessions = await getAllSessionsStatus();
|
|
386
472
|
const now = Date.now();
|
|
387
473
|
const cardData = allSessions.map(s => ({
|
|
388
474
|
sessionId: s.sessionId,
|
|
@@ -390,6 +476,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
390
476
|
turnCount: s.turnCount,
|
|
391
477
|
elapsedSeconds: s.active ? Math.floor((now - s.startTime) / 1000) : null,
|
|
392
478
|
model: s.model,
|
|
479
|
+
tool: s.tool,
|
|
393
480
|
}));
|
|
394
481
|
const card = buildSessionsCard(cardData);
|
|
395
482
|
const sessionsResp = await fetch(`${BASE_URL}/im/v1/messages?receive_id_type=chat_id`, {
|
|
@@ -405,6 +492,49 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
405
492
|
return;
|
|
406
493
|
}
|
|
407
494
|
|
|
495
|
+
// /git <args>:在「当前会话工作目录」执行 git 命令,把输出回发到群里。
|
|
496
|
+
// 注意 cwd 必须取自 adapter.getSessionInfo(会话真实 cwd),而非
|
|
497
|
+
// getDefaultCwd(下一次 /new 才会使用的默认路径)。
|
|
498
|
+
if (text.startsWith("/git ") || text === "/git") {
|
|
499
|
+
const args = text === "/git" ? "" : text.slice(5).trim();
|
|
500
|
+
if (!args) {
|
|
501
|
+
await sendCardReply(
|
|
502
|
+
freshToken, chatId, "/git",
|
|
503
|
+
"用法:`/git <子命令> [参数]`,例如 `/git status`、`/git log --oneline -n 5`。",
|
|
504
|
+
"yellow"
|
|
505
|
+
);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const adapter = getAdapterForTool(descriptionTool);
|
|
510
|
+
let cwd: string | undefined;
|
|
511
|
+
try {
|
|
512
|
+
const info = await adapter.getSessionInfo(sessionId);
|
|
513
|
+
cwd = info?.cwd;
|
|
514
|
+
} catch (err) {
|
|
515
|
+
console.error(`[${ts()}] [GIT] getSessionInfo FAIL: ${(err as Error).message}`);
|
|
516
|
+
}
|
|
517
|
+
if (!cwd) {
|
|
518
|
+
// Cursor 会话的 cwd 依赖 .claude/cursor-session-cwd.json 持久化映射;
|
|
519
|
+
// 升级前创建的旧会话或映射文件丢失时,向会话发送一次普通消息即可触发
|
|
520
|
+
// adapter 自动学习并补全(resume 流首条 init 事件携带 cwd)。
|
|
521
|
+
const isCursor = descriptionTool === "cursor";
|
|
522
|
+
const hint = isCursor
|
|
523
|
+
? "无法获取当前 Cursor 会话的工作目录(缺少 sessionId→cwd 持久化映射)。请先在本群发送一条普通消息(让 adapter 从 cursor-agent 流中自动补回 cwd),然后再试 /git;若仍失败,可用 /new 重建会话。"
|
|
524
|
+
: `无法获取当前会话的工作目录(${toolLabel} adapter 未返回 cwd)。请先与 AI 对话一次再试,或检查会话是否仍存在。`;
|
|
525
|
+
await sendCardReply(freshToken, chatId, "/git", hint, "red");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
console.log(`[${ts()}] [GIT] chat=${chatId} cwd=${cwd} cmd="git ${args}" timeoutMs=${GIT_TIMEOUT_MS}`);
|
|
530
|
+
const result = await runGitCommand(args, cwd, { timeoutMs: GIT_TIMEOUT_MS });
|
|
531
|
+
console.log(`[${ts()}] [GIT] exitCode=${result.exitCode}, durationMs=${result.durationMs}, truncated=${result.truncated}, timedOut=${result.timedOut}`);
|
|
532
|
+
const content = formatGitResult(args, cwd, result);
|
|
533
|
+
const template = gitResultHeaderTemplate(result);
|
|
534
|
+
await sendCardReply(freshToken, chatId, "/git 输出", content, template);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
408
538
|
const existing = chatSessionMap.get(chatId);
|
|
409
539
|
if (existing && !existing.stopped) {
|
|
410
540
|
if (msgTimestamp <= existing.msgTimestamp) {
|
|
@@ -434,13 +564,14 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
434
564
|
}
|
|
435
565
|
|
|
436
566
|
try {
|
|
437
|
-
await resumeAndPrompt(sessionId, text, freshToken, chatId, msgTimestamp);
|
|
567
|
+
await resumeAndPrompt(sessionId, text, freshToken, chatId, msgTimestamp, descriptionTool);
|
|
438
568
|
console.log(`[${ts()}] [RESUME] Session ${sessionId} done`);
|
|
439
569
|
} catch (err) {
|
|
440
570
|
console.error(`[${ts()}] [RESUME] FAIL: ${(err as Error).message}`);
|
|
571
|
+
fileLog.flush();
|
|
441
572
|
await sendCardReply(
|
|
442
573
|
freshToken, chatId, "Error",
|
|
443
|
-
`Failed to resume
|
|
574
|
+
`Failed to resume ${toolLabel} session:\n${(err as Error).message}`,
|
|
444
575
|
"red"
|
|
445
576
|
);
|
|
446
577
|
}
|
|
@@ -580,7 +711,7 @@ async function main(): Promise<void> {
|
|
|
580
711
|
}
|
|
581
712
|
}
|
|
582
713
|
|
|
583
|
-
const text =
|
|
714
|
+
const text = formatMessageContent(message);
|
|
584
715
|
const sender = event.sender;
|
|
585
716
|
const openId = sender?.sender_id?.open_id ?? "";
|
|
586
717
|
const chatId = message.chat_id ?? "";
|
|
@@ -649,7 +780,6 @@ async function main(): Promise<void> {
|
|
|
649
780
|
});
|
|
650
781
|
|
|
651
782
|
if (USE_LOCAL) {
|
|
652
|
-
initAdapter();
|
|
653
783
|
console.log(`\n[启动 6/7] 本地区 relay 模式:正在连接 ${LOCAL_RELAY_URL} …`);
|
|
654
784
|
console.log(" 若失败:请先在 SDK 模式下启动主进程,或确认本机中继已在该地址监听。");
|
|
655
785
|
let localRelayOpened = false;
|
|
@@ -673,7 +803,7 @@ async function main(): Promise<void> {
|
|
|
673
803
|
const event = getInnerEvent(data);
|
|
674
804
|
const message = event.message;
|
|
675
805
|
if (!message) return;
|
|
676
|
-
const text =
|
|
806
|
+
const text = formatMessageContent(message);
|
|
677
807
|
const openId = event.sender?.sender_id?.open_id ?? "";
|
|
678
808
|
const chatId = message.chat_id ?? "";
|
|
679
809
|
appendChatLog(chatId, openId, text);
|
|
@@ -695,13 +825,12 @@ async function main(): Promise<void> {
|
|
|
695
825
|
});
|
|
696
826
|
} else {
|
|
697
827
|
resetState();
|
|
698
|
-
initAdapter();
|
|
699
828
|
|
|
700
829
|
const wsClient = new WSClient({
|
|
701
830
|
appId: APP_ID,
|
|
702
831
|
appSecret: APP_SECRET,
|
|
703
|
-
onReady: () => { resetState();
|
|
704
|
-
onReconnected: () => { resetState();
|
|
832
|
+
onReady: () => { resetState(); },
|
|
833
|
+
onReconnected: () => { resetState(); },
|
|
705
834
|
});
|
|
706
835
|
|
|
707
836
|
console.log(`\n[启动 6/7] 飞书长连接:正在通过 SDK 建立 WebSocket …`);
|