chatccc 0.2.43 → 0.2.44
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 +30 -30
- package/demo/ilink_echo_probe.ts +222 -222
- package/package.json +59 -59
- package/src/__tests__/crash-logging.test.ts +62 -62
- package/src/config.ts +742 -742
- package/src/index.ts +17 -11
- package/src/shared.ts +63 -63
package/src/index.ts
CHANGED
|
@@ -608,7 +608,8 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
608
608
|
logTrace(tid, "BRANCH", { cmd: "/sessions" });
|
|
609
609
|
const allSessions = await getAllSessionsStatus();
|
|
610
610
|
const now = Date.now();
|
|
611
|
-
const
|
|
611
|
+
const others = allSessions.filter(s => s.chatId !== chatId);
|
|
612
|
+
const cardData = others.map(s => ({
|
|
612
613
|
sessionId: s.sessionId,
|
|
613
614
|
chatName: s.chatName,
|
|
614
615
|
active: s.active,
|
|
@@ -619,8 +620,8 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
619
620
|
}));
|
|
620
621
|
const card = buildSessionsCard(cardData);
|
|
621
622
|
const ok = await sendRawCard(freshToken, chatId, card);
|
|
622
|
-
console.log(`[${ts()}] [SESSIONS] card sent, ok=${ok}, count=${
|
|
623
|
-
logTrace(tid, "DONE", { outcome: "sessions", ok, count:
|
|
623
|
+
console.log(`[${ts()}] [SESSIONS] card sent, ok=${ok}, count=${others.length}`);
|
|
624
|
+
logTrace(tid, "DONE", { outcome: "sessions", ok, count: others.length });
|
|
624
625
|
return;
|
|
625
626
|
}
|
|
626
627
|
|
|
@@ -702,17 +703,22 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
702
703
|
const index = parseInt(sessionMatch[1], 10) - 1;
|
|
703
704
|
logTrace(tid, "BRANCH", { cmd: "/session", index: index + 1 });
|
|
704
705
|
const allSessions = await getAllSessionsStatus();
|
|
705
|
-
|
|
706
|
+
// 与 buildSessionsCard 保持一致的排序:Claude Code → Cursor → Codex,组内保持 updatedAt 降序
|
|
707
|
+
const claudeOrdered = allSessions.filter(s => s.tool !== "cursor" && s.tool !== "codex");
|
|
708
|
+
const cursorOrdered = allSessions.filter(s => s.tool === "cursor");
|
|
709
|
+
const codexOrdered = allSessions.filter(s => s.tool === "codex");
|
|
710
|
+
const ordered = [...claudeOrdered, ...cursorOrdered, ...codexOrdered].filter(s => s.chatId !== chatId);
|
|
711
|
+
if (ordered.length === 0) {
|
|
706
712
|
await sendCardReply(freshToken, chatId, "/session", "暂无历史会话。", "yellow");
|
|
707
713
|
logTrace(tid, "DONE", { outcome: "session_no_sessions" });
|
|
708
714
|
return;
|
|
709
715
|
}
|
|
710
|
-
if (index < 0 || index >=
|
|
711
|
-
await sendCardReply(freshToken, chatId, "/session", `序号超出范围,当前共 ${
|
|
712
|
-
logTrace(tid, "DONE", { outcome: "session_out_of_range", index: index + 1, total:
|
|
716
|
+
if (index < 0 || index >= ordered.length) {
|
|
717
|
+
await sendCardReply(freshToken, chatId, "/session", `序号超出范围,当前共 ${ordered.length} 个会话。`, "yellow");
|
|
718
|
+
logTrace(tid, "DONE", { outcome: "session_out_of_range", index: index + 1, total: ordered.length });
|
|
713
719
|
return;
|
|
714
720
|
}
|
|
715
|
-
const target =
|
|
721
|
+
const target = ordered[index];
|
|
716
722
|
|
|
717
723
|
const existing2 = chatSessionMap.get(chatId);
|
|
718
724
|
if (existing2) {
|
|
@@ -736,8 +742,9 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
736
742
|
}
|
|
737
743
|
|
|
738
744
|
const descPrefix2 = sessionPrefixForTool(target.tool);
|
|
739
|
-
const newName2 = sessionChatName("新会话", cwd2);
|
|
745
|
+
const newName2 = target.chatName || sessionChatName("新会话", cwd2);
|
|
740
746
|
await updateChatInfo(freshToken, chatId, newName2, `${descPrefix2} ${target.sessionId}`);
|
|
747
|
+
console.log(`[${ts()}] [SESSION] Switched to session ${target.sessionId} (#${index + 1}), name="${newName2}"`);
|
|
741
748
|
|
|
742
749
|
sessionInfoMap.set(chatId, {
|
|
743
750
|
sessionId: target.sessionId,
|
|
@@ -767,7 +774,6 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
767
774
|
"green"
|
|
768
775
|
);
|
|
769
776
|
|
|
770
|
-
console.log(`[${ts()}] [SESSION] Switched to session ${target.sessionId} (#${index + 1})`);
|
|
771
777
|
logTrace(tid, "DONE", { outcome: "session_switch", sessionId: target.sessionId, index: index + 1, cwd: cwd2 });
|
|
772
778
|
return;
|
|
773
779
|
}
|
|
@@ -839,7 +845,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
839
845
|
console.log(`[${ts()}] [BLOCKED] allowInterrupt=false, ignoring message during generation. Hint sent to user.`);
|
|
840
846
|
await sendCardReply(
|
|
841
847
|
freshToken, chatId, "生成中",
|
|
842
|
-
"AI
|
|
848
|
+
"AI 正在生成回复中,Agent 不会遗忘当前进度,停止后仍可从断点继续。\n请先点击卡片上的「停止」按钮,再发送新消息。",
|
|
843
849
|
"yellow"
|
|
844
850
|
);
|
|
845
851
|
return;
|
package/src/shared.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
appendFileSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
mkdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
unlinkSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
} from "node:fs";
|
|
10
|
-
import { createServer } from "node:http";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { inspect } from "node:util";
|
|
14
|
-
import { WebSocketServer, WebSocket } from "ws";
|
|
2
|
+
import {
|
|
3
|
+
appendFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { createServer } from "node:http";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { inspect } from "node:util";
|
|
14
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
15
15
|
|
|
16
16
|
import { printServiceDidNotStart } from "./exit-banner.ts";
|
|
17
17
|
|
|
@@ -355,56 +355,56 @@ export function installCrashLogging(opts: CrashHandlersOptions = {}): InstallCra
|
|
|
355
355
|
// 文件日志:同时输出到控制台和日志文件
|
|
356
356
|
// ---------------------------------------------------------------------------
|
|
357
357
|
|
|
358
|
-
export function setupFileLogging(logDir: string, prefix: string): { logPath: string; flush: () => void } {
|
|
359
|
-
mkdirSync(logDir, { recursive: true });
|
|
360
|
-
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
361
|
-
const logPath = join(logDir, `${prefix}-${ts}.log`);
|
|
362
|
-
writeFileSync(logPath, "", { flag: "a", encoding: "utf8" });
|
|
363
|
-
const origConsoleLog = console.log.bind(console);
|
|
364
|
-
const origConsoleError = console.error.bind(console);
|
|
365
|
-
const formatArg = (arg: unknown): string => {
|
|
366
|
-
if (typeof arg === "string") return arg;
|
|
367
|
-
if (arg instanceof Error) return arg.stack ?? arg.message;
|
|
368
|
-
return inspect(arg, { depth: 6, breakLength: Infinity, maxArrayLength: 200 });
|
|
369
|
-
};
|
|
370
|
-
const writeLine = (level: string, args: unknown[]) => {
|
|
371
|
-
try {
|
|
372
|
-
const line = args.map(formatArg).join(" ");
|
|
373
|
-
appendFileSync(logPath, `[${new Date().toISOString()}] [${level}] ${line}\n`, "utf8");
|
|
374
|
-
} catch (err) {
|
|
375
|
-
try {
|
|
376
|
-
appendStartupTrace("fileLog write failed", { level, error: err instanceof Error ? err.message : String(err) });
|
|
377
|
-
} catch {
|
|
378
|
-
// 日志系统自身不能影响主流程
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
console.log = (...args: unknown[]) => {
|
|
383
|
-
writeLine("LOG", args);
|
|
384
|
-
try {
|
|
385
|
-
origConsoleLog(...args);
|
|
386
|
-
} catch {
|
|
387
|
-
// 控制台输出失败也不能拖垮服务
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
console.error = (...args: unknown[]) => {
|
|
391
|
-
writeLine("ERR", args);
|
|
392
|
-
try {
|
|
393
|
-
origConsoleError(...args);
|
|
394
|
-
} catch {
|
|
395
|
-
// 控制台输出失败也不能拖垮服务
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
const flush = () => {
|
|
399
|
-
try {
|
|
400
|
-
appendFileSync(logPath, "", "utf8");
|
|
401
|
-
} catch (err) {
|
|
402
|
-
appendStartupTrace("fileLog flush failed", { error: err instanceof Error ? err.message : String(err) });
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
origConsoleLog(`Log file: ${logPath}`);
|
|
406
|
-
return { logPath, flush };
|
|
407
|
-
}
|
|
358
|
+
export function setupFileLogging(logDir: string, prefix: string): { logPath: string; flush: () => void } {
|
|
359
|
+
mkdirSync(logDir, { recursive: true });
|
|
360
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
361
|
+
const logPath = join(logDir, `${prefix}-${ts}.log`);
|
|
362
|
+
writeFileSync(logPath, "", { flag: "a", encoding: "utf8" });
|
|
363
|
+
const origConsoleLog = console.log.bind(console);
|
|
364
|
+
const origConsoleError = console.error.bind(console);
|
|
365
|
+
const formatArg = (arg: unknown): string => {
|
|
366
|
+
if (typeof arg === "string") return arg;
|
|
367
|
+
if (arg instanceof Error) return arg.stack ?? arg.message;
|
|
368
|
+
return inspect(arg, { depth: 6, breakLength: Infinity, maxArrayLength: 200 });
|
|
369
|
+
};
|
|
370
|
+
const writeLine = (level: string, args: unknown[]) => {
|
|
371
|
+
try {
|
|
372
|
+
const line = args.map(formatArg).join(" ");
|
|
373
|
+
appendFileSync(logPath, `[${new Date().toISOString()}] [${level}] ${line}\n`, "utf8");
|
|
374
|
+
} catch (err) {
|
|
375
|
+
try {
|
|
376
|
+
appendStartupTrace("fileLog write failed", { level, error: err instanceof Error ? err.message : String(err) });
|
|
377
|
+
} catch {
|
|
378
|
+
// 日志系统自身不能影响主流程
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
console.log = (...args: unknown[]) => {
|
|
383
|
+
writeLine("LOG", args);
|
|
384
|
+
try {
|
|
385
|
+
origConsoleLog(...args);
|
|
386
|
+
} catch {
|
|
387
|
+
// 控制台输出失败也不能拖垮服务
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
console.error = (...args: unknown[]) => {
|
|
391
|
+
writeLine("ERR", args);
|
|
392
|
+
try {
|
|
393
|
+
origConsoleError(...args);
|
|
394
|
+
} catch {
|
|
395
|
+
// 控制台输出失败也不能拖垮服务
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
const flush = () => {
|
|
399
|
+
try {
|
|
400
|
+
appendFileSync(logPath, "", "utf8");
|
|
401
|
+
} catch (err) {
|
|
402
|
+
appendStartupTrace("fileLog flush failed", { error: err instanceof Error ? err.message : String(err) });
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
origConsoleLog(`Log file: ${logPath}`);
|
|
406
|
+
return { logPath, flush };
|
|
407
|
+
}
|
|
408
408
|
|
|
409
409
|
// ---------------------------------------------------------------------------
|
|
410
410
|
// 本地 WebSocket 中继服务器(同一端口、多客户端广播)
|