chatccc 0.2.43 → 0.2.45
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 +1 -1
- 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/cards.ts +2 -2
- package/src/config.ts +742 -742
- package/src/index.ts +24 -18
- package/src/shared.ts +63 -63
package/src/index.ts
CHANGED
|
@@ -232,7 +232,7 @@ function parseCardAction(data: unknown): CardActionResult | null {
|
|
|
232
232
|
}
|
|
233
233
|
if (!cmd) return null;
|
|
234
234
|
|
|
235
|
-
const CMD_MAP: Record<string, string> = { stop: "/stop", new: "/new", "new claude": "/new claude", "new cursor": "/new cursor", "new codex": "/new codex", restart: "/restart", status: "/status", cd: "/cd", sessions: "/sessions",
|
|
235
|
+
const CMD_MAP: Record<string, string> = { stop: "/stop", new: "/new", "new claude": "/new claude", "new cursor": "/new cursor", "new codex": "/new codex", restart: "/restart", status: "/status", cd: "/cd", sessions: "/sessions", newh: "/newh" };
|
|
236
236
|
let text = CMD_MAP[cmd] ?? "";
|
|
237
237
|
if (cmd === "cd" && typeof action.value === "object" && action.value !== null) {
|
|
238
238
|
const path = (action.value as Record<string, string>).path;
|
|
@@ -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,13 +620,13 @@ 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
|
|
|
627
|
-
if (textLower === "/
|
|
628
|
-
logTrace(tid, "BRANCH", { cmd: "/
|
|
628
|
+
if (textLower === "/newh") {
|
|
629
|
+
logTrace(tid, "BRANCH", { cmd: "/newh" });
|
|
629
630
|
const adapter = getAdapterForTool(descriptionTool);
|
|
630
631
|
let cwd: string;
|
|
631
632
|
try {
|
|
@@ -652,7 +653,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
652
653
|
const init = await initClaudeSession(descriptionTool, cwd);
|
|
653
654
|
newSessionId = init.sessionId;
|
|
654
655
|
} catch (err) {
|
|
655
|
-
logTrace(tid, "DONE", { outcome: "
|
|
656
|
+
logTrace(tid, "DONE", { outcome: "newh_session_fail", error: (err as Error).message });
|
|
656
657
|
await sendCardReply(freshToken, chatId, "Error", `Failed to create new session:\n${(err as Error).message}`, "red");
|
|
657
658
|
return;
|
|
658
659
|
}
|
|
@@ -660,7 +661,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
660
661
|
const descPrefix = sessionPrefixForTool(descriptionTool);
|
|
661
662
|
const newName = sessionChatName("新会话", cwd);
|
|
662
663
|
await updateChatInfo(freshToken, chatId, newName, `${descPrefix} ${newSessionId}`);
|
|
663
|
-
console.log(`[${ts()}] [
|
|
664
|
+
console.log(`[${ts()}] [NEWH] Group updated: name="${newName}" desc="${descPrefix} ${newSessionId}"`);
|
|
664
665
|
|
|
665
666
|
sessionInfoMap.set(chatId, {
|
|
666
667
|
sessionId: newSessionId,
|
|
@@ -691,8 +692,8 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
691
692
|
"green"
|
|
692
693
|
);
|
|
693
694
|
|
|
694
|
-
console.log(`[${ts()}] [
|
|
695
|
-
logTrace(tid, "DONE", { outcome: "
|
|
695
|
+
console.log(`[${ts()}] [NEWH] Session ${sessionId} → ${newSessionId} (same cwd=${cwd})`);
|
|
696
|
+
logTrace(tid, "DONE", { outcome: "newh", newSessionId, cwd });
|
|
696
697
|
return;
|
|
697
698
|
}
|
|
698
699
|
|
|
@@ -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 中继服务器(同一端口、多客户端广播)
|