chatccc 0.2.201 → 0.2.203
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 +23 -21
- package/agent-prompts/claude_specific.md +45 -45
- package/agent-prompts/codex_specific.md +2 -2
- package/agent-prompts/cursor_specific.md +13 -13
- package/bin/cccagent.mjs +17 -17
- package/config.sample.json +27 -27
- package/im-skills/feishu-skill/receive-send-file.md +63 -63
- package/im-skills/feishu-skill/receive-send-image.md +24 -24
- package/im-skills/feishu-skill/skill.md +3 -3
- package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
- package/im-skills/wechat-file-skill/send-file.mjs +83 -83
- package/im-skills/wechat-file-skill/skill.md +10 -10
- package/im-skills/wechat-image-skill/skill.md +10 -10
- package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
- package/im-skills/wechat-video-skill/send-video.mjs +79 -79
- package/im-skills/wechat-video-skill/skill.md +10 -10
- package/package.json +1 -1
- package/scripts/postinstall-sharp-check.mjs +58 -58
- package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
- package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
- package/src/__tests__/builtin-chat-session.test.ts +277 -277
- package/src/__tests__/builtin-cli-json.test.ts +39 -39
- package/src/__tests__/builtin-config.test.ts +33 -33
- package/src/__tests__/builtin-context.test.ts +163 -163
- package/src/__tests__/builtin-file-tools.test.ts +224 -224
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/card-plain-text.test.ts +5 -5
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/cards.test.ts +133 -122
- package/src/__tests__/ccc-adapter.test.ts +114 -114
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
- package/src/__tests__/chatgpt-subscription.test.ts +135 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
- package/src/__tests__/claude-adapter.test.ts +592 -592
- package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
- package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/config-reload.test.ts +10 -10
- package/src/__tests__/config-sample.test.ts +18 -18
- package/src/__tests__/cursor-adapter.test.ts +226 -226
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +90 -90
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/jsonl-stream.test.ts +79 -79
- package/src/__tests__/orchestrator.test.ts +464 -301
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +46 -44
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/sim-platform.test.ts +12 -12
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/update-command-guard.test.ts +144 -0
- package/src/__tests__/web-ui.test.ts +209 -209
- package/src/adapters/ccc-adapter.ts +121 -121
- package/src/adapters/claude-adapter.ts +603 -603
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +380 -380
- package/src/adapters/cursor-adapter.ts +673 -673
- package/src/adapters/jsonl-stream.ts +157 -157
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/agent-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +81 -81
- package/src/agent-reload-config-rpc.ts +34 -34
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +473 -473
- package/src/builtin/context.ts +323 -323
- package/src/builtin/file-tools.ts +1072 -1072
- package/src/builtin/index.ts +404 -404
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- package/src/cards.ts +222 -210
- package/src/chatgpt-subscription-rpc.ts +27 -27
- package/src/chatgpt-subscription.ts +299 -299
- package/src/chrome-devtools-guard.ts +318 -318
- package/src/codex-reset-actions.ts +184 -184
- package/src/config.ts +125 -125
- package/src/feishu-api.ts +118 -118
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -293
- package/src/index.ts +48 -9
- package/src/litellm-proxy.ts +374 -374
- package/src/orchestrator.ts +391 -346
- package/src/privacy.ts +118 -118
- package/src/runtime-reload.ts +34 -34
- package/src/session-chat-binding.ts +6 -6
- package/src/session-name.ts +8 -8
- package/src/session.ts +177 -169
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/turn-cards.ts +117 -117
- package/src/update-command-guard.ts +165 -0
- package/src/web-ui.ts +205 -205
package/src/orchestrator.ts
CHANGED
|
@@ -22,24 +22,24 @@ import {
|
|
|
22
22
|
PROJECT_ROOT,
|
|
23
23
|
anthropicConfigDisplay,
|
|
24
24
|
config,
|
|
25
|
-
fileLog,
|
|
26
|
-
getAllEffortsForTool,
|
|
27
|
-
getAllModelsForTool,
|
|
28
|
-
getDefaultEffortForTool,
|
|
29
|
-
getDefaultCwd,
|
|
25
|
+
fileLog,
|
|
26
|
+
getAllEffortsForTool,
|
|
27
|
+
getAllModelsForTool,
|
|
28
|
+
getDefaultEffortForTool,
|
|
29
|
+
getDefaultCwd,
|
|
30
30
|
setDefaultCwd,
|
|
31
31
|
getRecentDirs,
|
|
32
32
|
addRecentDir,
|
|
33
33
|
resolveDefaultAgentTool,
|
|
34
|
-
sessionPrefixForTool,
|
|
35
|
-
toolDisplayName,
|
|
36
|
-
ts,
|
|
37
|
-
type AgentTool,
|
|
38
|
-
} from "./config.ts";
|
|
34
|
+
sessionPrefixForTool,
|
|
35
|
+
toolDisplayName,
|
|
36
|
+
ts,
|
|
37
|
+
type AgentTool,
|
|
38
|
+
} from "./config.ts";
|
|
39
39
|
import {
|
|
40
|
-
buildHelpCard,
|
|
41
|
-
buildEffortCard,
|
|
42
|
-
buildModelCard,
|
|
40
|
+
buildHelpCard,
|
|
41
|
+
buildEffortCard,
|
|
42
|
+
buildModelCard,
|
|
43
43
|
buildStatusCard,
|
|
44
44
|
buildCdContent,
|
|
45
45
|
buildCdCard,
|
|
@@ -54,21 +54,21 @@ import {
|
|
|
54
54
|
runGitCommand,
|
|
55
55
|
} from "./git-command.ts";
|
|
56
56
|
import {
|
|
57
|
-
clearSessionModelOverride,
|
|
58
|
-
clearSessionEffortOverride,
|
|
57
|
+
clearSessionModelOverride,
|
|
58
|
+
clearSessionEffortOverride,
|
|
59
59
|
getSessionStatus,
|
|
60
60
|
getAllSessionsStatus,
|
|
61
61
|
initClaudeSession,
|
|
62
62
|
lastMsgTimestamps,
|
|
63
63
|
resumeAndPrompt,
|
|
64
64
|
sessionInfoMap,
|
|
65
|
-
setSessionModelOverride,
|
|
66
|
-
setSessionEffortOverride,
|
|
65
|
+
setSessionModelOverride,
|
|
66
|
+
setSessionEffortOverride,
|
|
67
67
|
switchChatBinding,
|
|
68
68
|
recordSessionRegistry,
|
|
69
69
|
getAdapterForTool,
|
|
70
|
-
getEffectiveModelForTool,
|
|
71
|
-
getEffectiveEffortForTool,
|
|
70
|
+
getEffectiveModelForTool,
|
|
71
|
+
getEffectiveEffortForTool,
|
|
72
72
|
stopSession,
|
|
73
73
|
loadSessionRegistryForBinding,
|
|
74
74
|
removeSessionRegistryRecord,
|
|
@@ -87,11 +87,11 @@ import {
|
|
|
87
87
|
import { getCodexUsageSummary, getTenantAccessToken, sendPostMessage } from "./feishu-platform.ts";
|
|
88
88
|
import { getCursorUsageSummary, type CursorUsageSummary } from "./cursor-usage.ts";
|
|
89
89
|
import { getChatGptSubscriptionStatus, type ChatGptSubscriptionResult } from "./chatgpt-subscription.ts";
|
|
90
|
-
import {
|
|
91
|
-
import {
|
|
92
|
-
import {
|
|
93
|
-
import {
|
|
94
|
-
export { type PlatformAdapter } from "./platform-adapter.ts";
|
|
90
|
+
import { applySharedPrefix } from "./shared-prefix.ts";
|
|
91
|
+
import { cwdDisplayName, sessionChatName } from "./session-name.ts";
|
|
92
|
+
import { reloadRuntimeConfig } from "./runtime-reload.ts";
|
|
93
|
+
import { acquireUpdateCommandGuard } from "./update-command-guard.ts";
|
|
94
|
+
export { type PlatformAdapter } from "./platform-adapter.ts";
|
|
95
95
|
import type { ChatAvatarUsageHints, PlatformAdapter } from "./platform-adapter.ts";
|
|
96
96
|
import type { CodexUsageSummary } from "./feishu-api.ts";
|
|
97
97
|
|
|
@@ -135,7 +135,7 @@ function formatCodexUsageSummary(usage: CodexUsageSummary, chatGptSubscription:
|
|
|
135
135
|
return `(约 ${parts.join("")}后)`;
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
-
const formatResetTime = (balance: NonNullable<CodexUsageSummary["fiveHour"]>) => {
|
|
138
|
+
const formatResetTime = (balance: NonNullable<CodexUsageSummary["fiveHour"]>) => {
|
|
139
139
|
if (balance.resetAtEpochSeconds === null) return "暂无数据";
|
|
140
140
|
const date = new Date(balance.resetAtEpochSeconds * 1000);
|
|
141
141
|
const pad = (value: number) => String(value).padStart(2, "0");
|
|
@@ -263,8 +263,8 @@ function formatCodexUsageSummary(usage: CodexUsageSummary, chatGptSubscription:
|
|
|
263
263
|
formatChatGptSubscriptionFailure(),
|
|
264
264
|
formatResetCredits(),
|
|
265
265
|
"",
|
|
266
|
-
usage.fiveHour ? formatWindow("5h", usage.fiveHour) : "",
|
|
267
|
-
usage.weekly ? formatWindow("7天", usage.weekly) : "",
|
|
266
|
+
usage.fiveHour ? formatWindow("5h", usage.fiveHour) : "",
|
|
267
|
+
usage.weekly ? formatWindow("7天", usage.weekly) : "",
|
|
268
268
|
].filter((line, index, arr) => line !== "" || (index > 0 && arr[index - 1] !== "")).join("\n");
|
|
269
269
|
}
|
|
270
270
|
|
|
@@ -325,7 +325,7 @@ function formatCursorUsageSummary(usage: CursorUsageSummary): string {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
function usageHelpLine(tool: string): string {
|
|
328
|
-
if (tool === "codex") return "\n发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。";
|
|
328
|
+
if (tool === "codex") return "\n发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。";
|
|
329
329
|
if (tool === "cursor") return "\n发送 **/usage** 查看 Cursor 用量。";
|
|
330
330
|
return "";
|
|
331
331
|
}
|
|
@@ -410,42 +410,10 @@ function shouldSendWechatProcessingAck(
|
|
|
410
410
|
return platform.kind === "wechat" && chatType === "p2p" && !isCommandText;
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
async function cleanupNonWechatP2pBinding(
|
|
418
|
-
platform: PlatformAdapter,
|
|
419
|
-
chatId: string,
|
|
420
|
-
chatType: string,
|
|
421
|
-
tid: string,
|
|
422
|
-
): Promise<void> {
|
|
423
|
-
if (!isNonWechatP2p(platform, chatType)) return;
|
|
424
|
-
|
|
425
|
-
try {
|
|
426
|
-
const registry = await loadSessionRegistryForBinding();
|
|
427
|
-
const record = registry[chatId];
|
|
428
|
-
if (!record?.sessionId) return;
|
|
429
|
-
|
|
430
|
-
unbindChatFromSession(record.sessionId, chatId);
|
|
431
|
-
displayCards.delete(chatId);
|
|
432
|
-
cancelQueuedMessage(record.sessionId);
|
|
433
|
-
sessionInfoMap.delete(chatId);
|
|
434
|
-
await removeSessionRegistryRecord(chatId);
|
|
435
|
-
logTrace(tid, "BRANCH", {
|
|
436
|
-
reason: "cleanup_non_wechat_p2p_binding",
|
|
437
|
-
chatId,
|
|
438
|
-
oldSessionId: record.sessionId,
|
|
439
|
-
});
|
|
440
|
-
console.log(
|
|
441
|
-
`[${ts()}] [P2P] Removed non-WeChat p2p binding: chat=${chatId} session=${record.sessionId}`,
|
|
442
|
-
);
|
|
443
|
-
} catch (err) {
|
|
444
|
-
console.log(
|
|
445
|
-
`[${ts()}] [INFO] Cannot cleanup p2p registry for ${chatId}: ${(err as Error).message}`,
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
413
|
+
/** 飞书私聊是固定会话容器;显式 /new 才创建独立群聊。 */
|
|
414
|
+
function isFeishuP2p(platform: PlatformAdapter, chatType: string): boolean {
|
|
415
|
+
return chatType === "p2p" && platform.kind === "feishu";
|
|
416
|
+
}
|
|
449
417
|
|
|
450
418
|
/** 检测当前进程是否从 npm 全局安装启动 */
|
|
451
419
|
function isRunningFromGlobalNpm(): boolean {
|
|
@@ -530,46 +498,46 @@ function syncUpdateAndRestart(): void {
|
|
|
530
498
|
// handleCommand — 平台无关的命令分发
|
|
531
499
|
// ---------------------------------------------------------------------------
|
|
532
500
|
|
|
533
|
-
export async function handleCommand(
|
|
534
|
-
platform: PlatformAdapter,
|
|
535
|
-
text: string,
|
|
536
|
-
chatId: string,
|
|
537
|
-
openId: string,
|
|
538
|
-
msgTimestamp: number,
|
|
539
|
-
chatType = "group",
|
|
540
|
-
traceId?: string,
|
|
541
|
-
|
|
501
|
+
export async function handleCommand(
|
|
502
|
+
platform: PlatformAdapter,
|
|
503
|
+
text: string,
|
|
504
|
+
chatId: string,
|
|
505
|
+
openId: string,
|
|
506
|
+
msgTimestamp: number,
|
|
507
|
+
chatType = "group",
|
|
508
|
+
traceId?: string,
|
|
509
|
+
commandId?: string,
|
|
510
|
+
): Promise<void> {
|
|
542
511
|
const tid = traceId ?? makeTraceId();
|
|
543
512
|
const sharedPrefix = applySharedPrefix(text);
|
|
544
513
|
const promptText = sharedPrefix.text;
|
|
545
514
|
text = sharedPrefix.body;
|
|
546
515
|
const textLower = text.toLowerCase();
|
|
547
|
-
const isCommandText = !sharedPrefix.matched && textLower.startsWith("/");
|
|
548
|
-
recordChatPlatform(chatId, platform);
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (isCommandText && textLower === "/restart") {
|
|
516
|
+
const isCommandText = !sharedPrefix.matched && textLower.startsWith("/");
|
|
517
|
+
recordChatPlatform(chatId, platform);
|
|
518
|
+
|
|
519
|
+
if (isCommandText && textLower === "/reload") {
|
|
520
|
+
logTrace(tid, "BRANCH", { cmd: "/reload" });
|
|
521
|
+
try {
|
|
522
|
+
const result = await reloadRuntimeConfig("chat-command");
|
|
523
|
+
await platform.sendText(
|
|
524
|
+
chatId,
|
|
525
|
+
[
|
|
526
|
+
"配置已重新加载。",
|
|
527
|
+
`默认 Agent: ${toolDisplayName(result.defaultAgent)}`,
|
|
528
|
+
`配置文件: ${result.configPath}`,
|
|
529
|
+
"后续新会话会使用最新配置;正在生成的会话不会被中断。",
|
|
530
|
+
].join("\n"),
|
|
531
|
+
).catch(() => {});
|
|
532
|
+
logTrace(tid, "DONE", { outcome: "reload", defaultAgent: result.defaultAgent });
|
|
533
|
+
} catch (err) {
|
|
534
|
+
await platform.sendText(chatId, `配置重载失败:${(err as Error).message}`).catch(() => {});
|
|
535
|
+
logTrace(tid, "DONE", { outcome: "reload_fail", error: (err as Error).message });
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (isCommandText && textLower === "/restart") {
|
|
573
541
|
logTrace(tid, "BRANCH", { cmd: "/restart" });
|
|
574
542
|
await platform.sendText(chatId, "重启中...请几秒后发消息唤醒我").catch(() => {});
|
|
575
543
|
logTrace(tid, "DONE", { outcome: "restart" });
|
|
@@ -604,16 +572,40 @@ export async function handleCommand(
|
|
|
604
572
|
return;
|
|
605
573
|
}
|
|
606
574
|
|
|
607
|
-
if (isCommandText && textLower === "/update") {
|
|
608
|
-
logTrace(tid, "BRANCH", { cmd: "/update" });
|
|
609
|
-
const isGlobal = isRunningFromGlobalNpm();
|
|
610
|
-
appendStartupTrace("update: command received", { isGlobal, chatId });
|
|
575
|
+
if (isCommandText && textLower === "/update") {
|
|
576
|
+
logTrace(tid, "BRANCH", { cmd: "/update" });
|
|
577
|
+
const isGlobal = isRunningFromGlobalNpm();
|
|
578
|
+
appendStartupTrace("update: command received", { isGlobal, chatId });
|
|
611
579
|
if (!isGlobal) {
|
|
612
580
|
await platform.sendText(chatId, "当前进程非 npm 全局安装,无法使用 /update 更新。请通过 npm install -g chatccc 安装后使用。").catch(() => {});
|
|
613
|
-
logTrace(tid, "DONE", { outcome: "update_not_global" });
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
|
|
581
|
+
logTrace(tid, "DONE", { outcome: "update_not_global" });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// `/update` 会主动重启进程,内存 processedMessages 随之丢失。必须在发送
|
|
586
|
+
// “正在更新”以及执行 npm 命令之前同步落盘,才能挡住新进程收到的飞书重投。
|
|
587
|
+
// 该护栏只位于此分支,不改变普通消息和 `/restart` 的现有去重行为。
|
|
588
|
+
const updateGuard = acquireUpdateCommandGuard({ commandId });
|
|
589
|
+
appendStartupTrace("update: command guard checked", {
|
|
590
|
+
allowed: updateGuard.allowed,
|
|
591
|
+
reason: updateGuard.reason,
|
|
592
|
+
hasCommandId: Boolean(commandId),
|
|
593
|
+
});
|
|
594
|
+
if (!updateGuard.allowed) {
|
|
595
|
+
if (updateGuard.reason === "duplicate_id") {
|
|
596
|
+
// 同一条飞书消息的重投静默丢弃,避免用户再次看到重复提示。
|
|
597
|
+
logTrace(tid, "DONE", { outcome: "update_duplicate_id" });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
await platform.sendText(
|
|
601
|
+
chatId,
|
|
602
|
+
"无法写入更新保护状态。为避免连续更新和重启,本次 /update 未执行。",
|
|
603
|
+
).catch(() => {});
|
|
604
|
+
logTrace(tid, "DONE", { outcome: "update_guard_write_failed" });
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
await platform.sendText(chatId, "正在更新并重启,请稍候...").catch(() => {});
|
|
617
609
|
logTrace(tid, "DONE", { outcome: "update" });
|
|
618
610
|
appendStartupTrace("update: sync update begin", { fromPid: process.pid });
|
|
619
611
|
syncUpdateAndRestart();
|
|
@@ -763,7 +755,7 @@ export async function handleCommand(
|
|
|
763
755
|
const toolArg = text.slice(5).trim().toLowerCase();
|
|
764
756
|
const tool = toolArg || resolveDefaultAgentTool();
|
|
765
757
|
logTrace(tid, "BRANCH", { cmd: "/new", tool });
|
|
766
|
-
const validTools = ["claude", "cursor", "codex", "ccc"];
|
|
758
|
+
const validTools = ["claude", "cursor", "codex", "ccc"];
|
|
767
759
|
if (!validTools.includes(tool)) {
|
|
768
760
|
logTrace(tid, "DONE", { outcome: "new_invalid_tool", tool });
|
|
769
761
|
await platform.sendCard(
|
|
@@ -815,9 +807,9 @@ export async function handleCommand(
|
|
|
815
807
|
const cwd = sessionCwd;
|
|
816
808
|
const initialName = sessionChatName("新会话", cwd);
|
|
817
809
|
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
if (chatType === "p2p" && platform.kind === "wechat") {
|
|
810
|
+
// /new 的平台语义保持不同:微信在当前私聊新建 session;飞书显式
|
|
811
|
+
// /new 始终创建独立群聊。飞书私聊自己的常驻 session 不在这里切换。
|
|
812
|
+
if (chatType === "p2p" && platform.kind === "wechat") {
|
|
821
813
|
// 先解绑旧 session(如果存在),避免旧 session 的 display loop
|
|
822
814
|
// 继续往同一个 chat 推送内容(/newh 走 switchChatBinding 已有此逻辑,
|
|
823
815
|
// 但 /new p2p 之前遗漏了解绑)。
|
|
@@ -837,11 +829,12 @@ export async function handleCommand(
|
|
|
837
829
|
tool,
|
|
838
830
|
});
|
|
839
831
|
await setDefaultCwd(cwd, chatId);
|
|
840
|
-
await recordSessionRegistry({
|
|
841
|
-
chatId,
|
|
842
|
-
sessionId,
|
|
843
|
-
tool,
|
|
844
|
-
|
|
832
|
+
await recordSessionRegistry({
|
|
833
|
+
chatId,
|
|
834
|
+
sessionId,
|
|
835
|
+
tool,
|
|
836
|
+
chatType,
|
|
837
|
+
chatName: initialName,
|
|
845
838
|
turnCount: 0,
|
|
846
839
|
startTime: Date.now(),
|
|
847
840
|
running: false,
|
|
@@ -923,11 +916,12 @@ export async function handleCommand(
|
|
|
923
916
|
// 让新群的默认工作目录继承当前会话的 cwd
|
|
924
917
|
await setDefaultCwd(cwd, newChatId);
|
|
925
918
|
bindChatToSession(sessionId, newChatId);
|
|
926
|
-
await recordSessionRegistry({
|
|
927
|
-
chatId: newChatId,
|
|
928
|
-
sessionId,
|
|
929
|
-
tool,
|
|
930
|
-
|
|
919
|
+
await recordSessionRegistry({
|
|
920
|
+
chatId: newChatId,
|
|
921
|
+
sessionId,
|
|
922
|
+
tool,
|
|
923
|
+
chatType: "group",
|
|
924
|
+
chatName: initialName,
|
|
931
925
|
turnCount: 0,
|
|
932
926
|
startTime: Date.now(),
|
|
933
927
|
running: false,
|
|
@@ -962,7 +956,8 @@ export async function handleCommand(
|
|
|
962
956
|
return;
|
|
963
957
|
}
|
|
964
958
|
|
|
965
|
-
// 检测会话上下文:群聊从 description
|
|
959
|
+
// 检测会话上下文:群聊从 description 获取,飞书/微信私聊都从
|
|
960
|
+
// session-registry 获取。私聊 chatId 是稳定容器,进程重启后仍恢复绑定。
|
|
966
961
|
let sessionId: string | null = null;
|
|
967
962
|
let descriptionTool: string | null = null;
|
|
968
963
|
let toolLabel: string | null = null;
|
|
@@ -988,17 +983,30 @@ export async function handleCommand(
|
|
|
988
983
|
`[${ts()}] [INFO] Cannot get chat info for ${chatId}: ${(err as Error).message}`,
|
|
989
984
|
);
|
|
990
985
|
}
|
|
991
|
-
} else if (platform.kind === "wechat") {
|
|
992
|
-
//
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
986
|
+
} else if (platform.kind === "wechat" || platform.kind === "feishu") {
|
|
987
|
+
// 私聊没有可写的群描述,因此会话绑定只持久化在 session-registry.json。
|
|
988
|
+
try {
|
|
989
|
+
const registry = await loadSessionRegistryForBinding();
|
|
990
|
+
const record = registry[chatId];
|
|
991
|
+
// 旧版飞书曾把私聊视为建群入口,并会清理私聊 registry。没有 p2p
|
|
992
|
+
// 标记的残留记录不能证明它是在固定用户目录创建的,因此只迁移一次:
|
|
993
|
+
// 先解除旧绑定;若本条是普通消息,随后在用户目录创建新的私聊 session。
|
|
994
|
+
if (platform.kind === "feishu" && record?.sessionId && record.chatType !== "p2p") {
|
|
995
|
+
unbindChatFromSession(record.sessionId, chatId);
|
|
996
|
+
displayCards.delete(chatId);
|
|
997
|
+
cancelQueuedMessage(record.sessionId);
|
|
998
|
+
sessionInfoMap.delete(chatId);
|
|
999
|
+
await removeSessionRegistryRecord(chatId);
|
|
1000
|
+
logTrace(tid, "BRANCH", {
|
|
1001
|
+
reason: "migrate_legacy_feishu_p2p_binding",
|
|
1002
|
+
chatId,
|
|
1003
|
+
oldSessionId: record.sessionId,
|
|
1004
|
+
});
|
|
1005
|
+
} else if (record && record.sessionId && record.tool) {
|
|
1006
|
+
sessionId = record.sessionId;
|
|
999
1007
|
descriptionTool = record.tool;
|
|
1000
1008
|
toolLabel = toolDisplayName(descriptionTool);
|
|
1001
|
-
//
|
|
1009
|
+
// 确保内存状态在冷启动后恢复;bindChatToSession 是幂等的。
|
|
1002
1010
|
if (!sessionInfoMap.has(chatId)) {
|
|
1003
1011
|
sessionInfoMap.set(chatId, {
|
|
1004
1012
|
sessionId,
|
|
@@ -1008,8 +1016,8 @@ export async function handleCommand(
|
|
|
1008
1016
|
tool: descriptionTool,
|
|
1009
1017
|
});
|
|
1010
1018
|
}
|
|
1011
|
-
bindChatToSession(sessionId, chatId);
|
|
1012
|
-
}
|
|
1019
|
+
bindChatToSession(sessionId, chatId);
|
|
1020
|
+
}
|
|
1013
1021
|
} catch (err) {
|
|
1014
1022
|
console.log(
|
|
1015
1023
|
`[${ts()}] [INFO] Cannot load registry for p2p ${chatId}: ${(err as Error).message}`,
|
|
@@ -1060,11 +1068,11 @@ export async function handleCommand(
|
|
|
1060
1068
|
}
|
|
1061
1069
|
}
|
|
1062
1070
|
|
|
1063
|
-
//
|
|
1064
|
-
if (
|
|
1065
|
-
chatType === "p2p" &&
|
|
1066
|
-
platform.kind === "wechat" &&
|
|
1067
|
-
!isCommandText
|
|
1071
|
+
// P2P:首条非指令消息只更新 registry 中的展示名,不修改私聊信息。
|
|
1072
|
+
if (
|
|
1073
|
+
chatType === "p2p" &&
|
|
1074
|
+
(platform.kind === "wechat" || platform.kind === "feishu") &&
|
|
1075
|
+
!isCommandText
|
|
1068
1076
|
) {
|
|
1069
1077
|
try {
|
|
1070
1078
|
const reg = await loadSessionRegistryForBinding();
|
|
@@ -1093,12 +1101,12 @@ export async function handleCommand(
|
|
|
1093
1101
|
() => {},
|
|
1094
1102
|
);
|
|
1095
1103
|
console.log(
|
|
1096
|
-
`[${ts()}] [RENAME]
|
|
1104
|
+
`[${ts()}] [RENAME] ${platform.kind} P2P → "${newName2}"`,
|
|
1097
1105
|
);
|
|
1098
1106
|
}
|
|
1099
1107
|
} catch (err) {
|
|
1100
1108
|
console.error(
|
|
1101
|
-
`[${ts()}] [RENAME]
|
|
1109
|
+
`[${ts()}] [RENAME] ${platform.kind} P2P failed: ${(err as Error).message}`,
|
|
1102
1110
|
);
|
|
1103
1111
|
}
|
|
1104
1112
|
}
|
|
@@ -1211,10 +1219,11 @@ export async function handleCommand(
|
|
|
1211
1219
|
const allSessions = await getAllSessionsStatus();
|
|
1212
1220
|
const now = Date.now();
|
|
1213
1221
|
const cardData = allSessions.map((s) => ({
|
|
1214
|
-
sessionId: s.sessionId,
|
|
1215
|
-
chatName: s.chatName,
|
|
1216
|
-
chatId: s.chatId,
|
|
1217
|
-
|
|
1222
|
+
sessionId: s.sessionId,
|
|
1223
|
+
chatName: s.chatName,
|
|
1224
|
+
chatId: s.chatId,
|
|
1225
|
+
chatType: s.chatType,
|
|
1226
|
+
active: s.active,
|
|
1218
1227
|
turnCount: s.turnCount,
|
|
1219
1228
|
elapsedSeconds: s.active
|
|
1220
1229
|
? Math.floor((now - s.startTime) / 1000)
|
|
@@ -1222,7 +1231,10 @@ export async function handleCommand(
|
|
|
1222
1231
|
model: s.model,
|
|
1223
1232
|
tool: s.tool,
|
|
1224
1233
|
}));
|
|
1225
|
-
const card = buildSessionsCard(cardData, {
|
|
1234
|
+
const card = buildSessionsCard(cardData, {
|
|
1235
|
+
defaultToolLabel: toolDisplayName(resolveDefaultAgentTool()),
|
|
1236
|
+
fixedPrivateSession: isFeishuP2p(platform, chatType),
|
|
1237
|
+
});
|
|
1226
1238
|
const ok = await platform.sendRawCard(chatId, card);
|
|
1227
1239
|
console.log(
|
|
1228
1240
|
`[${ts()}] [SESSIONS] card sent, ok=${ok}, count=${cardData.length}`,
|
|
@@ -1231,16 +1243,22 @@ export async function handleCommand(
|
|
|
1231
1243
|
return;
|
|
1232
1244
|
}
|
|
1233
1245
|
|
|
1234
|
-
if (isCommandText && textLower === "/newh") {
|
|
1235
|
-
logTrace(tid, "BRANCH", { cmd: "/newh" });
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
cwd =
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1246
|
+
if (isCommandText && textLower === "/newh") {
|
|
1247
|
+
logTrace(tid, "BRANCH", { cmd: "/newh" });
|
|
1248
|
+
let cwd: string;
|
|
1249
|
+
if (isFeishuP2p(platform, chatType)) {
|
|
1250
|
+
// 飞书私聊不支持切换工作目录。即使 /cd 已为后续 /new 群聊
|
|
1251
|
+
// 保存了其他默认目录,/newh 仍必须在运行 ChatCCC 的用户目录重建。
|
|
1252
|
+
cwd = homedir();
|
|
1253
|
+
} else {
|
|
1254
|
+
const adapter = getAdapterForTool(descriptionTool, sessionId);
|
|
1255
|
+
try {
|
|
1256
|
+
const info = await adapter.getSessionInfo(sessionId);
|
|
1257
|
+
cwd = info?.cwd ?? (await getDefaultCwd(chatId));
|
|
1258
|
+
} catch {
|
|
1259
|
+
cwd = await getDefaultCwd(chatId);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1244
1262
|
|
|
1245
1263
|
// 第一步:创建新 session(此时尚未碰任何内存绑定,失败可直接返回,
|
|
1246
1264
|
// 旧 session 状态完全保留)。
|
|
@@ -1299,13 +1317,13 @@ export async function handleCommand(
|
|
|
1299
1317
|
.setChatAvatar(chatId, descriptionTool, "new")
|
|
1300
1318
|
.catch(() => {});
|
|
1301
1319
|
|
|
1302
|
-
await platform.sendCard(
|
|
1303
|
-
chatId,
|
|
1304
|
-
`${toolLabel} Session Reset`,
|
|
1305
|
-
`会话已重置为新的 **${toolLabel}** 会话。\n\n` +
|
|
1306
|
-
`**Session ID:** ${newSessionId}\n` +
|
|
1307
|
-
`**工作目录:** \`${cwd}
|
|
1308
|
-
`直接在这里发消息即可继续对话。\n` +
|
|
1320
|
+
await platform.sendCard(
|
|
1321
|
+
chatId,
|
|
1322
|
+
`${toolLabel} Session Reset`,
|
|
1323
|
+
`会话已重置为新的 **${toolLabel}** 会话。\n\n` +
|
|
1324
|
+
`**Session ID:** ${newSessionId}\n` +
|
|
1325
|
+
`**工作目录:** \`${cwd}\`${isFeishuP2p(platform, chatType) ? "(飞书私聊固定使用系统用户目录)" : "(沿用当前会话目录)"}\n\n` +
|
|
1326
|
+
`直接在这里发消息即可继续对话。\n` +
|
|
1309
1327
|
`发送 **/cd** 可切换新建会话的默认目录。\n` +
|
|
1310
1328
|
`发送 **/model** 查看或切换当前会话的模型。`,
|
|
1311
1329
|
"green",
|
|
@@ -1356,9 +1374,22 @@ export async function handleCommand(
|
|
|
1356
1374
|
}
|
|
1357
1375
|
|
|
1358
1376
|
// /session <number>:切换到 /sessions 列表中的指定会话
|
|
1359
|
-
const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
|
|
1360
|
-
if (sessionMatch) {
|
|
1361
|
-
|
|
1377
|
+
const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
|
|
1378
|
+
if (sessionMatch) {
|
|
1379
|
+
// 飞书私聊有自己唯一的常驻 session;历史群聊 session 只能回到对应群聊
|
|
1380
|
+
// 继续,禁止通过 /session 把它们重新绑定进私聊。
|
|
1381
|
+
if (isFeishuP2p(platform, chatType)) {
|
|
1382
|
+
await platform.sendCard(
|
|
1383
|
+
chatId,
|
|
1384
|
+
"/session",
|
|
1385
|
+
"飞书私聊使用固定的专属会话,不能切换到历史群聊会话。请回到对应群聊继续,或发送 /new 新建群聊。",
|
|
1386
|
+
"yellow",
|
|
1387
|
+
);
|
|
1388
|
+
logTrace(tid, "DONE", { outcome: "session_switch_disabled_feishu_p2p" });
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const index = parseInt(sessionMatch[1], 10) - 1;
|
|
1362
1393
|
logTrace(tid, "BRANCH", { cmd: "/session", index: index + 1 });
|
|
1363
1394
|
const allSessions = await getAllSessionsStatus();
|
|
1364
1395
|
const claudeOrdered = allSessions.filter(
|
|
@@ -1555,96 +1586,96 @@ export async function handleCommand(
|
|
|
1555
1586
|
}
|
|
1556
1587
|
|
|
1557
1588
|
// /git <args>:在「当前会话工作目录」执行 git 命令
|
|
1558
|
-
if (isCommandText && textLower === "/effort clear") {
|
|
1559
|
-
logTrace(tid, "BRANCH", { cmd: "/effort clear", sessionId, tool: descriptionTool });
|
|
1560
|
-
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1561
|
-
const toolLabel = toolDisplayName(descriptionTool);
|
|
1562
|
-
if (efforts.length === 0) {
|
|
1563
|
-
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1564
|
-
await (platform.kind === "wechat"
|
|
1565
|
-
? platform.sendText(chatId, msg)
|
|
1566
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1567
|
-
).catch(() => {});
|
|
1568
|
-
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
clearSessionEffortOverride(sessionId);
|
|
1573
|
-
const defaultEffort = getDefaultEffortForTool(descriptionTool as AgentTool);
|
|
1574
|
-
const msg = `已清除当前 ${toolLabel} 会话的 effort 覆盖,恢复使用: \`${defaultEffort || "(未指定)"}\``;
|
|
1575
|
-
await (platform.kind === "wechat"
|
|
1576
|
-
? platform.sendText(chatId, msg)
|
|
1577
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1578
|
-
).catch(() => {});
|
|
1579
|
-
logTrace(tid, "DONE", { outcome: "effort_cleared", sessionId, tool: descriptionTool });
|
|
1580
|
-
return;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1584
|
-
const effortArg = text.slice(8).trim();
|
|
1585
|
-
if (!effortArg) return;
|
|
1586
|
-
logTrace(tid, "BRANCH", { cmd: "/effort", arg: effortArg, sessionId, tool: descriptionTool });
|
|
1587
|
-
|
|
1588
|
-
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1589
|
-
const toolLabel = toolDisplayName(descriptionTool);
|
|
1590
|
-
if (efforts.length === 0) {
|
|
1591
|
-
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1592
|
-
await (platform.kind === "wechat"
|
|
1593
|
-
? platform.sendText(chatId, msg)
|
|
1594
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1595
|
-
).catch(() => {});
|
|
1596
|
-
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1597
|
-
return;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
const target = findModelMatch(effortArg, efforts);
|
|
1601
|
-
if (!target) {
|
|
1602
|
-
const msg = `未找到匹配 "${effortArg}" 的 effort。当前 ${toolLabel} 可选 effort:\n${efforts.map(e => ` \`${e}\``).join("\n")}`;
|
|
1603
|
-
await (platform.kind === "wechat"
|
|
1604
|
-
? platform.sendText(chatId, msg)
|
|
1605
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1606
|
-
).catch(() => {});
|
|
1607
|
-
logTrace(tid, "DONE", { outcome: "effort_not_found", arg: effortArg, tool: descriptionTool });
|
|
1608
|
-
return;
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
setSessionEffortOverride(sessionId, target);
|
|
1612
|
-
const msg = `已切换当前 ${toolLabel} 会话 effort 为: \`${target}\``;
|
|
1613
|
-
await (platform.kind === "wechat"
|
|
1614
|
-
? platform.sendText(chatId, msg)
|
|
1615
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1616
|
-
).catch(() => {});
|
|
1617
|
-
logTrace(tid, "DONE", { outcome: "effort_switched", arg: effortArg, target, sessionId, tool: descriptionTool });
|
|
1618
|
-
return;
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
if (isCommandText && textLower === "/effort") {
|
|
1622
|
-
logTrace(tid, "BRANCH", { cmd: "/effort", sessionId, tool: descriptionTool });
|
|
1623
|
-
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1624
|
-
const currentEffort = getEffectiveEffortForTool(descriptionTool, sessionId);
|
|
1625
|
-
const toolLabel = toolDisplayName(descriptionTool);
|
|
1626
|
-
|
|
1627
|
-
if (efforts.length === 0) {
|
|
1628
|
-
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1629
|
-
await (platform.kind === "wechat"
|
|
1630
|
-
? platform.sendText(chatId, msg)
|
|
1631
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1632
|
-
).catch(() => {});
|
|
1633
|
-
} else if (platform.kind === "wechat") {
|
|
1634
|
-
const lines = [currentEffort ? `当前 effort (${toolLabel}): ${currentEffort}` : `当前 effort (${toolLabel}): 未指定`];
|
|
1635
|
-
lines.push("", "可切换 effort:");
|
|
1636
|
-
for (const e of efforts) lines.push(` ${e}`);
|
|
1637
|
-
lines.push("", "输入 /effort <effort> 切换 effort");
|
|
1638
|
-
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1639
|
-
} else {
|
|
1640
|
-
const card = buildEffortCard(currentEffort, efforts, descriptionTool);
|
|
1641
|
-
await platform.sendRawCard(chatId, card);
|
|
1642
|
-
}
|
|
1643
|
-
logTrace(tid, "DONE", { outcome: "effort_query", tool: descriptionTool });
|
|
1644
|
-
return;
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
|
|
1589
|
+
if (isCommandText && textLower === "/effort clear") {
|
|
1590
|
+
logTrace(tid, "BRANCH", { cmd: "/effort clear", sessionId, tool: descriptionTool });
|
|
1591
|
+
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1592
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
1593
|
+
if (efforts.length === 0) {
|
|
1594
|
+
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1595
|
+
await (platform.kind === "wechat"
|
|
1596
|
+
? platform.sendText(chatId, msg)
|
|
1597
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1598
|
+
).catch(() => {});
|
|
1599
|
+
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
clearSessionEffortOverride(sessionId);
|
|
1604
|
+
const defaultEffort = getDefaultEffortForTool(descriptionTool as AgentTool);
|
|
1605
|
+
const msg = `已清除当前 ${toolLabel} 会话的 effort 覆盖,恢复使用: \`${defaultEffort || "(未指定)"}\``;
|
|
1606
|
+
await (platform.kind === "wechat"
|
|
1607
|
+
? platform.sendText(chatId, msg)
|
|
1608
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1609
|
+
).catch(() => {});
|
|
1610
|
+
logTrace(tid, "DONE", { outcome: "effort_cleared", sessionId, tool: descriptionTool });
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1615
|
+
const effortArg = text.slice(8).trim();
|
|
1616
|
+
if (!effortArg) return;
|
|
1617
|
+
logTrace(tid, "BRANCH", { cmd: "/effort", arg: effortArg, sessionId, tool: descriptionTool });
|
|
1618
|
+
|
|
1619
|
+
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1620
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
1621
|
+
if (efforts.length === 0) {
|
|
1622
|
+
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1623
|
+
await (platform.kind === "wechat"
|
|
1624
|
+
? platform.sendText(chatId, msg)
|
|
1625
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1626
|
+
).catch(() => {});
|
|
1627
|
+
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const target = findModelMatch(effortArg, efforts);
|
|
1632
|
+
if (!target) {
|
|
1633
|
+
const msg = `未找到匹配 "${effortArg}" 的 effort。当前 ${toolLabel} 可选 effort:\n${efforts.map(e => ` \`${e}\``).join("\n")}`;
|
|
1634
|
+
await (platform.kind === "wechat"
|
|
1635
|
+
? platform.sendText(chatId, msg)
|
|
1636
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1637
|
+
).catch(() => {});
|
|
1638
|
+
logTrace(tid, "DONE", { outcome: "effort_not_found", arg: effortArg, tool: descriptionTool });
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
setSessionEffortOverride(sessionId, target);
|
|
1643
|
+
const msg = `已切换当前 ${toolLabel} 会话 effort 为: \`${target}\``;
|
|
1644
|
+
await (platform.kind === "wechat"
|
|
1645
|
+
? platform.sendText(chatId, msg)
|
|
1646
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1647
|
+
).catch(() => {});
|
|
1648
|
+
logTrace(tid, "DONE", { outcome: "effort_switched", arg: effortArg, target, sessionId, tool: descriptionTool });
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if (isCommandText && textLower === "/effort") {
|
|
1653
|
+
logTrace(tid, "BRANCH", { cmd: "/effort", sessionId, tool: descriptionTool });
|
|
1654
|
+
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1655
|
+
const currentEffort = getEffectiveEffortForTool(descriptionTool, sessionId);
|
|
1656
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
1657
|
+
|
|
1658
|
+
if (efforts.length === 0) {
|
|
1659
|
+
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1660
|
+
await (platform.kind === "wechat"
|
|
1661
|
+
? platform.sendText(chatId, msg)
|
|
1662
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1663
|
+
).catch(() => {});
|
|
1664
|
+
} else if (platform.kind === "wechat") {
|
|
1665
|
+
const lines = [currentEffort ? `当前 effort (${toolLabel}): ${currentEffort}` : `当前 effort (${toolLabel}): 未指定`];
|
|
1666
|
+
lines.push("", "可切换 effort:");
|
|
1667
|
+
for (const e of efforts) lines.push(` ${e}`);
|
|
1668
|
+
lines.push("", "输入 /effort <effort> 切换 effort");
|
|
1669
|
+
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1670
|
+
} else {
|
|
1671
|
+
const card = buildEffortCard(currentEffort, efforts, descriptionTool);
|
|
1672
|
+
await platform.sendRawCard(chatId, card);
|
|
1673
|
+
}
|
|
1674
|
+
logTrace(tid, "DONE", { outcome: "effort_query", tool: descriptionTool });
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
|
|
1648
1679
|
const args = text === "/git" ? "" : text.slice(5).trim();
|
|
1649
1680
|
logTrace(tid, "BRANCH", { cmd: "/git", args: args || "(none)" });
|
|
1650
1681
|
if (!args) {
|
|
@@ -1802,53 +1833,54 @@ export async function handleCommand(
|
|
|
1802
1833
|
}
|
|
1803
1834
|
|
|
1804
1835
|
// 无会话上下文 → /sessions 仍是有效指令,不触发飞书私聊自动建群。
|
|
1805
|
-
if (isCommandText && textLower === "/effort") {
|
|
1806
|
-
const defaultTool = resolveDefaultAgentTool();
|
|
1807
|
-
const efforts = getAllEffortsForTool(defaultTool);
|
|
1808
|
-
const currentEffort = getDefaultEffortForTool(defaultTool);
|
|
1809
|
-
const toolLabel = toolDisplayName(defaultTool);
|
|
1810
|
-
|
|
1811
|
-
if (efforts.length === 0) {
|
|
1812
|
-
const msg = `当前默认 agent (${toolLabel}) 不支持 effort 切换。`;
|
|
1813
|
-
await (platform.kind === "wechat"
|
|
1814
|
-
? platform.sendText(chatId, msg)
|
|
1815
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1816
|
-
).catch(() => {});
|
|
1817
|
-
} else if (platform.kind === "wechat") {
|
|
1818
|
-
const lines = [currentEffort ? `当前默认 effort (${toolLabel}): ${currentEffort}` : `当前默认 effort (${toolLabel}): 未指定`];
|
|
1819
|
-
lines.push("", "可切换 effort:");
|
|
1820
|
-
for (const e of efforts) lines.push(` ${e}`);
|
|
1821
|
-
lines.push("", "在会话中输入 /effort <effort> 切换 effort");
|
|
1822
|
-
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1823
|
-
} else {
|
|
1824
|
-
const card = buildEffortCard(currentEffort, efforts, defaultTool);
|
|
1825
|
-
await platform.sendRawCard(chatId, card);
|
|
1826
|
-
}
|
|
1827
|
-
logTrace(tid, "DONE", { outcome: "effort_query", defaultTool });
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1832
|
-
const defaultTool = resolveDefaultAgentTool();
|
|
1833
|
-
const toolLabel = toolDisplayName(defaultTool);
|
|
1834
|
-
const msg = `当前没有绑定会话。请先进入 Claude/Codex 会话,再输入 /effort <effort> 切换当前会话的 effort。当前默认 agent: ${toolLabel}`;
|
|
1835
|
-
await (platform.kind === "wechat"
|
|
1836
|
-
? platform.sendText(chatId, msg)
|
|
1837
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "yellow")
|
|
1838
|
-
).catch(() => {});
|
|
1839
|
-
logTrace(tid, "DONE", { outcome: "effort_no_session", defaultTool });
|
|
1840
|
-
return;
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
if (isCommandText && textLower === "/sessions") {
|
|
1836
|
+
if (isCommandText && textLower === "/effort") {
|
|
1837
|
+
const defaultTool = resolveDefaultAgentTool();
|
|
1838
|
+
const efforts = getAllEffortsForTool(defaultTool);
|
|
1839
|
+
const currentEffort = getDefaultEffortForTool(defaultTool);
|
|
1840
|
+
const toolLabel = toolDisplayName(defaultTool);
|
|
1841
|
+
|
|
1842
|
+
if (efforts.length === 0) {
|
|
1843
|
+
const msg = `当前默认 agent (${toolLabel}) 不支持 effort 切换。`;
|
|
1844
|
+
await (platform.kind === "wechat"
|
|
1845
|
+
? platform.sendText(chatId, msg)
|
|
1846
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1847
|
+
).catch(() => {});
|
|
1848
|
+
} else if (platform.kind === "wechat") {
|
|
1849
|
+
const lines = [currentEffort ? `当前默认 effort (${toolLabel}): ${currentEffort}` : `当前默认 effort (${toolLabel}): 未指定`];
|
|
1850
|
+
lines.push("", "可切换 effort:");
|
|
1851
|
+
for (const e of efforts) lines.push(` ${e}`);
|
|
1852
|
+
lines.push("", "在会话中输入 /effort <effort> 切换 effort");
|
|
1853
|
+
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1854
|
+
} else {
|
|
1855
|
+
const card = buildEffortCard(currentEffort, efforts, defaultTool);
|
|
1856
|
+
await platform.sendRawCard(chatId, card);
|
|
1857
|
+
}
|
|
1858
|
+
logTrace(tid, "DONE", { outcome: "effort_query", defaultTool });
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1863
|
+
const defaultTool = resolveDefaultAgentTool();
|
|
1864
|
+
const toolLabel = toolDisplayName(defaultTool);
|
|
1865
|
+
const msg = `当前没有绑定会话。请先进入 Claude/Codex 会话,再输入 /effort <effort> 切换当前会话的 effort。当前默认 agent: ${toolLabel}`;
|
|
1866
|
+
await (platform.kind === "wechat"
|
|
1867
|
+
? platform.sendText(chatId, msg)
|
|
1868
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "yellow")
|
|
1869
|
+
).catch(() => {});
|
|
1870
|
+
logTrace(tid, "DONE", { outcome: "effort_no_session", defaultTool });
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
if (isCommandText && textLower === "/sessions") {
|
|
1844
1875
|
logTrace(tid, "BRANCH", { cmd: "/sessions", scope: "global" });
|
|
1845
1876
|
const allSessions = await getAllSessionsStatus();
|
|
1846
1877
|
const now = Date.now();
|
|
1847
1878
|
const cardData = allSessions.map((s) => ({
|
|
1848
|
-
sessionId: s.sessionId,
|
|
1849
|
-
chatName: s.chatName,
|
|
1850
|
-
chatId: s.chatId,
|
|
1851
|
-
|
|
1879
|
+
sessionId: s.sessionId,
|
|
1880
|
+
chatName: s.chatName,
|
|
1881
|
+
chatId: s.chatId,
|
|
1882
|
+
chatType: s.chatType,
|
|
1883
|
+
active: s.active,
|
|
1852
1884
|
turnCount: s.turnCount,
|
|
1853
1885
|
elapsedSeconds: s.active
|
|
1854
1886
|
? Math.floor((now - s.startTime) / 1000)
|
|
@@ -1856,7 +1888,10 @@ export async function handleCommand(
|
|
|
1856
1888
|
model: s.model,
|
|
1857
1889
|
tool: s.tool,
|
|
1858
1890
|
}));
|
|
1859
|
-
const card = buildSessionsCard(cardData, {
|
|
1891
|
+
const card = buildSessionsCard(cardData, {
|
|
1892
|
+
defaultToolLabel: toolDisplayName(resolveDefaultAgentTool()),
|
|
1893
|
+
fixedPrivateSession: isFeishuP2p(platform, chatType),
|
|
1894
|
+
});
|
|
1860
1895
|
const ok = await platform.sendRawCard(chatId, card);
|
|
1861
1896
|
console.log(
|
|
1862
1897
|
`[${ts()}] [SESSIONS] card sent, ok=${ok}, count=${cardData.length}`,
|
|
@@ -1865,53 +1900,63 @@ export async function handleCommand(
|
|
|
1865
1900
|
return;
|
|
1866
1901
|
}
|
|
1867
1902
|
|
|
1868
|
-
//
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
);
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
tool
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
);
|
|
1903
|
+
// 飞书私聊普通消息:首次使用时在当前私聊创建并持久化一个专属 session,
|
|
1904
|
+
// 随后的消息会在上面的 registry 路由中继续该 session。只有显式 /new 才建群。
|
|
1905
|
+
if (isFeishuP2p(platform, chatType) && !isCommandText) {
|
|
1906
|
+
const tool = resolveDefaultAgentTool();
|
|
1907
|
+
const toolLabel = toolDisplayName(tool);
|
|
1908
|
+
// 私聊 cwd 故意不读取 /cd 的 chatId 默认值:/cd 只为之后显式
|
|
1909
|
+
// /new 创建的群聊服务,飞书私聊始终从 ChatCCC 运行账号的用户目录启动。
|
|
1910
|
+
const cwd = homedir();
|
|
1911
|
+
logTrace(tid, "BRANCH", { cmd: "auto_new_feishu_p2p", tool, cwd });
|
|
1912
|
+
|
|
1913
|
+
try {
|
|
1914
|
+
const init = await initClaudeSession(tool, cwd);
|
|
1915
|
+
const sessionId = init.sessionId;
|
|
1916
|
+
const chatName = sessionChatName(text.slice(0, 10) || "私聊会话", cwd);
|
|
1917
|
+
const switchResult = await switchChatBinding({
|
|
1918
|
+
chatId,
|
|
1919
|
+
chatType,
|
|
1920
|
+
oldSessionId: null,
|
|
1921
|
+
newSessionId: sessionId,
|
|
1922
|
+
tool,
|
|
1923
|
+
chatName,
|
|
1924
|
+
newDescription: `${sessionPrefixForTool(tool)} ${sessionId}`,
|
|
1925
|
+
updateChatInfoFn: (cid, name, desc) =>
|
|
1926
|
+
platform.updateChatInfo(cid, name, desc),
|
|
1927
|
+
});
|
|
1928
|
+
if (!switchResult.ok) {
|
|
1929
|
+
throw switchResult.error ?? new Error("Failed to bind Feishu private session");
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
await resumeAndPrompt(
|
|
1933
|
+
sessionId,
|
|
1934
|
+
promptText,
|
|
1935
|
+
platform,
|
|
1936
|
+
chatId,
|
|
1937
|
+
msgTimestamp,
|
|
1938
|
+
tool,
|
|
1939
|
+
tid,
|
|
1940
|
+
);
|
|
1941
|
+
logTrace(tid, "DONE", {
|
|
1942
|
+
outcome: "auto_new_feishu_p2p_prompt_done",
|
|
1943
|
+
chatId,
|
|
1944
|
+
sessionId,
|
|
1945
|
+
tool,
|
|
1946
|
+
cwd,
|
|
1947
|
+
});
|
|
1948
|
+
} catch (err) {
|
|
1949
|
+
console.error(`[${ts()}] [AUTO-P2P] FAIL: ${(err as Error).message}`);
|
|
1950
|
+
logTrace(tid, "DONE", {
|
|
1951
|
+
outcome: "auto_new_feishu_p2p_fail",
|
|
1952
|
+
error: (err as Error).message,
|
|
1953
|
+
});
|
|
1954
|
+
await platform.sendCard(
|
|
1955
|
+
chatId,
|
|
1956
|
+
"Error",
|
|
1957
|
+
`Failed to create ${toolLabel} private session:\n${(err as Error).message}`,
|
|
1958
|
+
"red",
|
|
1959
|
+
);
|
|
1915
1960
|
}
|
|
1916
1961
|
return;
|
|
1917
1962
|
}
|