chatccc 0.2.183 → 0.2.184
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/im-skills/feishu-skill/skill.md +2 -1
- package/package.json +1 -1
- package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -0
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/agent-delegate-task-rpc.ts +153 -0
- package/src/agent-delegate-task.ts +81 -0
- package/src/codex-reset-actions.ts +184 -184
- package/src/format-message.ts +293 -293
- package/src/index.ts +9 -2
- package/src/orchestrator.ts +136 -237
- package/src/session-name.ts +8 -0
- package/src/session.ts +1 -0
package/src/orchestrator.ts
CHANGED
|
@@ -38,14 +38,14 @@ import {
|
|
|
38
38
|
import {
|
|
39
39
|
buildHelpCard,
|
|
40
40
|
buildModelCard,
|
|
41
|
-
buildStatusCard,
|
|
42
|
-
buildCdContent,
|
|
43
|
-
buildCdCard,
|
|
44
|
-
buildSessionsCard,
|
|
45
|
-
buildQueuedCard,
|
|
46
|
-
buildQueueFullCard,
|
|
47
|
-
buildCodexUsageCard,
|
|
48
|
-
} from "./cards.ts";
|
|
41
|
+
buildStatusCard,
|
|
42
|
+
buildCdContent,
|
|
43
|
+
buildCdCard,
|
|
44
|
+
buildSessionsCard,
|
|
45
|
+
buildQueuedCard,
|
|
46
|
+
buildQueueFullCard,
|
|
47
|
+
buildCodexUsageCard,
|
|
48
|
+
} from "./cards.ts";
|
|
49
49
|
import {
|
|
50
50
|
formatGitResult,
|
|
51
51
|
gitResultHeaderTemplate,
|
|
@@ -79,10 +79,12 @@ import {
|
|
|
79
79
|
enqueueMessage,
|
|
80
80
|
cancelQueuedMessage,
|
|
81
81
|
} from "./session-chat-binding.ts";
|
|
82
|
-
import { getCodexUsageSummary, getTenantAccessToken, sendPostMessage } from "./feishu-platform.ts";
|
|
83
|
-
import { getCursorUsageSummary, type CursorUsageSummary } from "./cursor-usage.ts";
|
|
84
|
-
import {
|
|
85
|
-
|
|
82
|
+
import { getCodexUsageSummary, getTenantAccessToken, sendPostMessage } from "./feishu-platform.ts";
|
|
83
|
+
import { getCursorUsageSummary, type CursorUsageSummary } from "./cursor-usage.ts";
|
|
84
|
+
import { delegateAgentTask } from "./agent-delegate-task.ts";
|
|
85
|
+
import { applySharedPrefix } from "./shared-prefix.ts";
|
|
86
|
+
import { cwdDisplayName, sessionChatName } from "./session-name.ts";
|
|
87
|
+
export { type PlatformAdapter } from "./platform-adapter.ts";
|
|
86
88
|
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
87
89
|
import type { CodexUsageSummary } from "./feishu-api.ts";
|
|
88
90
|
|
|
@@ -90,15 +92,6 @@ import type { CodexUsageSummary } from "./feishu-api.ts";
|
|
|
90
92
|
// 辅助函数
|
|
91
93
|
// ---------------------------------------------------------------------------
|
|
92
94
|
|
|
93
|
-
export function cwdDisplayName(cwd: string): string {
|
|
94
|
-
const trimmed = cwd.trim().replace(/[\\/]+$/, "");
|
|
95
|
-
return trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed || "cwd";
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function sessionChatName(left: string, cwd: string): string {
|
|
99
|
-
return `${left}-${cwdDisplayName(cwd)}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
95
|
/** 模型模糊匹配:精确匹配优先,否则找子串匹配(模型名越短越优先) */
|
|
103
96
|
function findModelMatch(input: string, models: string[]): string | null {
|
|
104
97
|
if (models.length === 0) return null;
|
|
@@ -114,7 +107,7 @@ function findModelMatch(input: string, models: string[]): string | null {
|
|
|
114
107
|
return candidates[0] ?? null;
|
|
115
108
|
}
|
|
116
109
|
|
|
117
|
-
function formatCodexUsageSummary(usage: CodexUsageSummary): string {
|
|
110
|
+
function formatCodexUsageSummary(usage: CodexUsageSummary): string {
|
|
118
111
|
const progressBar = (usedPercent: number) => {
|
|
119
112
|
const width = 20;
|
|
120
113
|
const usedBlocks = Math.max(0, Math.min(width, Math.round((usedPercent / 100) * width)));
|
|
@@ -153,54 +146,54 @@ function formatCodexUsageSummary(usage: CodexUsageSummary): string {
|
|
|
153
146
|
return `${absolute}${formatDuration(balance.resetAfterSeconds)}`;
|
|
154
147
|
};
|
|
155
148
|
|
|
156
|
-
const formatWindow = (label: string, balance: CodexUsageSummary["fiveHour"] | null) => {
|
|
157
|
-
if (!balance) return `**${label}:** 暂无数据`;
|
|
158
|
-
return [
|
|
159
|
-
`**${label}:** 已用 ${balance.usedPercent}%,剩余 ${balance.remainingPercent}%,重置: ${formatResetTime(balance)}`,
|
|
160
|
-
progressBar(balance.usedPercent),
|
|
161
|
-
].join("\n");
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const formatResetCredits = () => {
|
|
165
|
-
if (usage.rateLimitResetCreditsAvailable === null) return "**主动重置:** 暂无数据";
|
|
166
|
-
const lines = [`**主动重置:** 剩余 ${usage.rateLimitResetCreditsAvailable} 次`];
|
|
167
|
-
const credits = usage.rateLimitResetCredits ?? [];
|
|
168
|
-
if (credits.length > 0) {
|
|
169
|
-
const pad = (value: number) => String(value).padStart(2, "0");
|
|
170
|
-
const formatExpiresAt = (value: string) => {
|
|
171
|
-
const date = new Date(value);
|
|
172
|
-
if (!Number.isFinite(date.getTime())) return value;
|
|
173
|
-
return [
|
|
174
|
-
date.getFullYear(),
|
|
175
|
-
"-",
|
|
176
|
-
pad(date.getMonth() + 1),
|
|
177
|
-
"-",
|
|
178
|
-
pad(date.getDate()),
|
|
179
|
-
" ",
|
|
180
|
-
pad(date.getHours()),
|
|
181
|
-
":",
|
|
182
|
-
pad(date.getMinutes()),
|
|
183
|
-
":",
|
|
184
|
-
pad(date.getSeconds()),
|
|
185
|
-
].join("");
|
|
186
|
-
};
|
|
187
|
-
lines.push("**过期时间:**");
|
|
188
|
-
for (const credit of credits) {
|
|
189
|
-
lines.push(`- ${formatExpiresAt(credit.expiresAt)}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return lines.join("\n");
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
return [
|
|
196
|
-
"Codex 用量:",
|
|
197
|
-
"",
|
|
198
|
-
formatResetCredits(),
|
|
199
|
-
"",
|
|
200
|
-
formatWindow("5h", usage.fiveHour),
|
|
201
|
-
formatWindow("周", usage.weekly),
|
|
202
|
-
].join("\n");
|
|
203
|
-
}
|
|
149
|
+
const formatWindow = (label: string, balance: CodexUsageSummary["fiveHour"] | null) => {
|
|
150
|
+
if (!balance) return `**${label}:** 暂无数据`;
|
|
151
|
+
return [
|
|
152
|
+
`**${label}:** 已用 ${balance.usedPercent}%,剩余 ${balance.remainingPercent}%,重置: ${formatResetTime(balance)}`,
|
|
153
|
+
progressBar(balance.usedPercent),
|
|
154
|
+
].join("\n");
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const formatResetCredits = () => {
|
|
158
|
+
if (usage.rateLimitResetCreditsAvailable === null) return "**主动重置:** 暂无数据";
|
|
159
|
+
const lines = [`**主动重置:** 剩余 ${usage.rateLimitResetCreditsAvailable} 次`];
|
|
160
|
+
const credits = usage.rateLimitResetCredits ?? [];
|
|
161
|
+
if (credits.length > 0) {
|
|
162
|
+
const pad = (value: number) => String(value).padStart(2, "0");
|
|
163
|
+
const formatExpiresAt = (value: string) => {
|
|
164
|
+
const date = new Date(value);
|
|
165
|
+
if (!Number.isFinite(date.getTime())) return value;
|
|
166
|
+
return [
|
|
167
|
+
date.getFullYear(),
|
|
168
|
+
"-",
|
|
169
|
+
pad(date.getMonth() + 1),
|
|
170
|
+
"-",
|
|
171
|
+
pad(date.getDate()),
|
|
172
|
+
" ",
|
|
173
|
+
pad(date.getHours()),
|
|
174
|
+
":",
|
|
175
|
+
pad(date.getMinutes()),
|
|
176
|
+
":",
|
|
177
|
+
pad(date.getSeconds()),
|
|
178
|
+
].join("");
|
|
179
|
+
};
|
|
180
|
+
lines.push("**过期时间:**");
|
|
181
|
+
for (const credit of credits) {
|
|
182
|
+
lines.push(`- ${formatExpiresAt(credit.expiresAt)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return [
|
|
189
|
+
"Codex 用量:",
|
|
190
|
+
"",
|
|
191
|
+
formatResetCredits(),
|
|
192
|
+
"",
|
|
193
|
+
formatWindow("5h", usage.fiveHour),
|
|
194
|
+
formatWindow("周", usage.weekly),
|
|
195
|
+
].join("\n");
|
|
196
|
+
}
|
|
204
197
|
|
|
205
198
|
function formatCursorUsageSummary(usage: CursorUsageSummary): string {
|
|
206
199
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -259,7 +252,7 @@ function formatCursorUsageSummary(usage: CursorUsageSummary): string {
|
|
|
259
252
|
}
|
|
260
253
|
|
|
261
254
|
function usageHelpLine(tool: string): string {
|
|
262
|
-
if (tool === "codex") return "\n发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡。";
|
|
255
|
+
if (tool === "codex") return "\n发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡。";
|
|
263
256
|
if (tool === "cursor") return "\n发送 **/usage** 查看 Cursor 用量。";
|
|
264
257
|
return "";
|
|
265
258
|
}
|
|
@@ -284,16 +277,16 @@ async function sendUsageSummary(platform: PlatformAdapter, chatId: string, tool:
|
|
|
284
277
|
return;
|
|
285
278
|
}
|
|
286
279
|
|
|
287
|
-
const usage = await getCodexUsageSummary();
|
|
288
|
-
const content = formatCodexUsageSummary(usage);
|
|
289
|
-
if (platform.kind === "wechat") {
|
|
290
|
-
await platform.sendText(chatId, content).catch(() => {});
|
|
291
|
-
} else if (platform.kind === "feishu") {
|
|
292
|
-
await platform.sendRawCard(chatId, buildCodexUsageCard(content, usage.rateLimitResetCreditsAvailable));
|
|
293
|
-
} else {
|
|
294
|
-
await platform.sendCard(chatId, "Codex Usage", content, "blue");
|
|
295
|
-
}
|
|
296
|
-
}
|
|
280
|
+
const usage = await getCodexUsageSummary();
|
|
281
|
+
const content = formatCodexUsageSummary(usage);
|
|
282
|
+
if (platform.kind === "wechat") {
|
|
283
|
+
await platform.sendText(chatId, content).catch(() => {});
|
|
284
|
+
} else if (platform.kind === "feishu") {
|
|
285
|
+
await platform.sendRawCard(chatId, buildCodexUsageCard(content, usage.rateLimitResetCreditsAvailable));
|
|
286
|
+
} else {
|
|
287
|
+
await platform.sendCard(chatId, "Codex Usage", content, "blue");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
297
290
|
|
|
298
291
|
async function sendUsageError(platform: PlatformAdapter, chatId: string, tool: "codex" | "cursor", err: unknown): Promise<void> {
|
|
299
292
|
const toolLabel = tool === "cursor" ? "Cursor" : "Codex";
|
|
@@ -309,13 +302,13 @@ function isUntitledSessionChatName(name: string): boolean {
|
|
|
309
302
|
return name === "新会话" || name.startsWith("新会话-");
|
|
310
303
|
}
|
|
311
304
|
|
|
312
|
-
function shouldSendWechatProcessingAck(
|
|
313
|
-
platform: PlatformAdapter,
|
|
314
|
-
isCommandText: boolean,
|
|
315
|
-
chatType: string,
|
|
316
|
-
): boolean {
|
|
317
|
-
return platform.kind === "wechat" && chatType === "p2p" && !isCommandText;
|
|
318
|
-
}
|
|
305
|
+
function shouldSendWechatProcessingAck(
|
|
306
|
+
platform: PlatformAdapter,
|
|
307
|
+
isCommandText: boolean,
|
|
308
|
+
chatType: string,
|
|
309
|
+
): boolean {
|
|
310
|
+
return platform.kind === "wechat" && chatType === "p2p" && !isCommandText;
|
|
311
|
+
}
|
|
319
312
|
|
|
320
313
|
function isNonWechatP2p(platform: PlatformAdapter, chatType: string): boolean {
|
|
321
314
|
return chatType === "p2p" && platform.kind !== "wechat";
|
|
@@ -445,17 +438,17 @@ export async function handleCommand(
|
|
|
445
438
|
msgTimestamp: number,
|
|
446
439
|
chatType = "group",
|
|
447
440
|
traceId?: string,
|
|
448
|
-
): Promise<void> {
|
|
449
|
-
const tid = traceId ?? makeTraceId();
|
|
450
|
-
const sharedPrefix = applySharedPrefix(text);
|
|
451
|
-
const promptText = sharedPrefix.text;
|
|
452
|
-
text = sharedPrefix.body;
|
|
453
|
-
const textLower = text.toLowerCase();
|
|
454
|
-
const isCommandText = !sharedPrefix.matched && textLower.startsWith("/");
|
|
455
|
-
recordChatPlatform(chatId, platform);
|
|
441
|
+
): Promise<void> {
|
|
442
|
+
const tid = traceId ?? makeTraceId();
|
|
443
|
+
const sharedPrefix = applySharedPrefix(text);
|
|
444
|
+
const promptText = sharedPrefix.text;
|
|
445
|
+
text = sharedPrefix.body;
|
|
446
|
+
const textLower = text.toLowerCase();
|
|
447
|
+
const isCommandText = !sharedPrefix.matched && textLower.startsWith("/");
|
|
448
|
+
recordChatPlatform(chatId, platform);
|
|
456
449
|
await cleanupNonWechatP2pBinding(platform, chatId, chatType, tid);
|
|
457
450
|
|
|
458
|
-
if (isCommandText && textLower === "/restart") {
|
|
451
|
+
if (isCommandText && textLower === "/restart") {
|
|
459
452
|
logTrace(tid, "BRANCH", { cmd: "/restart" });
|
|
460
453
|
await platform.sendText(chatId, "重启中...请几秒后发消息唤醒我").catch(() => {});
|
|
461
454
|
logTrace(tid, "DONE", { outcome: "restart" });
|
|
@@ -490,7 +483,7 @@ export async function handleCommand(
|
|
|
490
483
|
return;
|
|
491
484
|
}
|
|
492
485
|
|
|
493
|
-
if (isCommandText && textLower === "/update") {
|
|
486
|
+
if (isCommandText && textLower === "/update") {
|
|
494
487
|
logTrace(tid, "BRANCH", { cmd: "/update" });
|
|
495
488
|
const isGlobal = isRunningFromGlobalNpm();
|
|
496
489
|
appendStartupTrace("update: command received", { isGlobal, chatId });
|
|
@@ -507,7 +500,7 @@ export async function handleCommand(
|
|
|
507
500
|
return;
|
|
508
501
|
}
|
|
509
502
|
|
|
510
|
-
if (isCommandText && textLower === "/usage") {
|
|
503
|
+
if (isCommandText && textLower === "/usage") {
|
|
511
504
|
const usageTool = await resolveUsageTool(chatId);
|
|
512
505
|
logTrace(tid, "BRANCH", { cmd: "/usage", tool: usageTool });
|
|
513
506
|
try {
|
|
@@ -520,7 +513,7 @@ export async function handleCommand(
|
|
|
520
513
|
return;
|
|
521
514
|
}
|
|
522
515
|
|
|
523
|
-
if (isCommandText && (textLower === "/cd" || textLower.startsWith("/cd "))) {
|
|
516
|
+
if (isCommandText && (textLower === "/cd" || textLower.startsWith("/cd "))) {
|
|
524
517
|
logTrace(tid, "BRANCH", {
|
|
525
518
|
cmd: "/cd",
|
|
526
519
|
arg: text.slice(3).trim() || "(none)",
|
|
@@ -643,7 +636,7 @@ export async function handleCommand(
|
|
|
643
636
|
return;
|
|
644
637
|
}
|
|
645
638
|
|
|
646
|
-
if (isCommandText && (textLower === "/new" || textLower.startsWith("/new "))) {
|
|
639
|
+
if (isCommandText && (textLower === "/new" || textLower.startsWith("/new "))) {
|
|
647
640
|
const toolArg = text.slice(5).trim().toLowerCase();
|
|
648
641
|
const tool = toolArg || resolveDefaultAgentTool();
|
|
649
642
|
logTrace(tid, "BRANCH", { cmd: "/new", tool });
|
|
@@ -904,7 +897,7 @@ export async function handleCommand(
|
|
|
904
897
|
if (sessionId && descriptionTool && toolLabel) {
|
|
905
898
|
// 有会话上下文 — 路由到命令处理或 prompt
|
|
906
899
|
logTrace(tid, "BRANCH", { sessionId, tool: descriptionTool });
|
|
907
|
-
const routeKind = isCommandText ? "command" : "prompt";
|
|
900
|
+
const routeKind = isCommandText ? "command" : "prompt";
|
|
908
901
|
const chatKind = chatType === "p2p" ? "p2p chat" : "session group";
|
|
909
902
|
console.log(
|
|
910
903
|
`[${ts()}] [ROUTE] ${toolLabel} ${chatKind} ${routeKind} detected, session=${sessionId} tool=${descriptionTool}`,
|
|
@@ -913,7 +906,7 @@ export async function handleCommand(
|
|
|
913
906
|
if (
|
|
914
907
|
chatType !== "p2p" &&
|
|
915
908
|
isUntitledSessionChatName(chatInfo!.name) &&
|
|
916
|
-
!isCommandText
|
|
909
|
+
!isCommandText
|
|
917
910
|
) {
|
|
918
911
|
const MAX_PREFIX = 10;
|
|
919
912
|
const prefix = text.slice(0, MAX_PREFIX);
|
|
@@ -948,7 +941,7 @@ export async function handleCommand(
|
|
|
948
941
|
if (
|
|
949
942
|
chatType === "p2p" &&
|
|
950
943
|
platform.kind === "wechat" &&
|
|
951
|
-
!isCommandText
|
|
944
|
+
!isCommandText
|
|
952
945
|
) {
|
|
953
946
|
try {
|
|
954
947
|
const reg = await loadSessionRegistryForBinding();
|
|
@@ -987,7 +980,7 @@ export async function handleCommand(
|
|
|
987
980
|
}
|
|
988
981
|
}
|
|
989
982
|
|
|
990
|
-
if (isCommandText && textLower === "/stop") {
|
|
983
|
+
if (isCommandText && textLower === "/stop") {
|
|
991
984
|
logTrace(tid, "BRANCH", { cmd: "/stop" });
|
|
992
985
|
if (stopSession(sessionId)) {
|
|
993
986
|
console.log(`[${ts()}] [STOP] User sent /stop, session=${sessionId}`);
|
|
@@ -1001,7 +994,7 @@ export async function handleCommand(
|
|
|
1001
994
|
return;
|
|
1002
995
|
}
|
|
1003
996
|
|
|
1004
|
-
if (isCommandText && textLower === "/cancel") {
|
|
997
|
+
if (isCommandText && textLower === "/cancel") {
|
|
1005
998
|
logTrace(tid, "BRANCH", { cmd: "/cancel" });
|
|
1006
999
|
if (cancelQueuedMessage(sessionId)) {
|
|
1007
1000
|
console.log(`[${ts()}] [CANCEL] Queue cancelled for session=${sessionId}`);
|
|
@@ -1014,7 +1007,7 @@ export async function handleCommand(
|
|
|
1014
1007
|
return;
|
|
1015
1008
|
}
|
|
1016
1009
|
|
|
1017
|
-
if (isCommandText && textLower === "/test") {
|
|
1010
|
+
if (isCommandText && textLower === "/test") {
|
|
1018
1011
|
logTrace(tid, "BRANCH", { cmd: "/test" });
|
|
1019
1012
|
const tableHeaders = ["名称", "版本", "状态"];
|
|
1020
1013
|
const tableRows = [
|
|
@@ -1051,7 +1044,7 @@ export async function handleCommand(
|
|
|
1051
1044
|
return;
|
|
1052
1045
|
}
|
|
1053
1046
|
|
|
1054
|
-
if (isCommandText && textLower === "/state") {
|
|
1047
|
+
if (isCommandText && textLower === "/state") {
|
|
1055
1048
|
logTrace(tid, "BRANCH", { cmd: "/state" });
|
|
1056
1049
|
const status = await getSessionStatus(chatId);
|
|
1057
1050
|
const isActive = isSessionRunning(sessionId);
|
|
@@ -1090,7 +1083,7 @@ export async function handleCommand(
|
|
|
1090
1083
|
return;
|
|
1091
1084
|
}
|
|
1092
1085
|
|
|
1093
|
-
if (isCommandText && textLower === "/sessions") {
|
|
1086
|
+
if (isCommandText && textLower === "/sessions") {
|
|
1094
1087
|
logTrace(tid, "BRANCH", { cmd: "/sessions" });
|
|
1095
1088
|
const allSessions = await getAllSessionsStatus();
|
|
1096
1089
|
const now = Date.now();
|
|
@@ -1115,7 +1108,7 @@ export async function handleCommand(
|
|
|
1115
1108
|
return;
|
|
1116
1109
|
}
|
|
1117
1110
|
|
|
1118
|
-
if (isCommandText && textLower === "/newh") {
|
|
1111
|
+
if (isCommandText && textLower === "/newh") {
|
|
1119
1112
|
logTrace(tid, "BRANCH", { cmd: "/newh" });
|
|
1120
1113
|
const adapter = getAdapterForTool(descriptionTool, sessionId);
|
|
1121
1114
|
let cwd: string;
|
|
@@ -1202,7 +1195,7 @@ export async function handleCommand(
|
|
|
1202
1195
|
return;
|
|
1203
1196
|
}
|
|
1204
1197
|
|
|
1205
|
-
if (isCommandText && textLower === "/deleteg") {
|
|
1198
|
+
if (isCommandText && textLower === "/deleteg") {
|
|
1206
1199
|
logTrace(tid, "BRANCH", { cmd: "/deleteg" });
|
|
1207
1200
|
if (chatType === "p2p") {
|
|
1208
1201
|
await platform
|
|
@@ -1240,7 +1233,7 @@ export async function handleCommand(
|
|
|
1240
1233
|
}
|
|
1241
1234
|
|
|
1242
1235
|
// /session <number>:切换到 /sessions 列表中的指定会话
|
|
1243
|
-
const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
|
|
1236
|
+
const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
|
|
1244
1237
|
if (sessionMatch) {
|
|
1245
1238
|
const index = parseInt(sessionMatch[1], 10) - 1;
|
|
1246
1239
|
logTrace(tid, "BRANCH", { cmd: "/session", index: index + 1 });
|
|
@@ -1366,7 +1359,7 @@ export async function handleCommand(
|
|
|
1366
1359
|
}
|
|
1367
1360
|
|
|
1368
1361
|
// /model clear — 清除当前 session 的模型覆盖
|
|
1369
|
-
if (isCommandText && textLower === "/model clear") {
|
|
1362
|
+
if (isCommandText && textLower === "/model clear") {
|
|
1370
1363
|
logTrace(tid, "BRANCH", { cmd: "/model clear", sessionId });
|
|
1371
1364
|
clearSessionModelOverride(sessionId);
|
|
1372
1365
|
const defaultModel = getEffectiveModelForTool(descriptionTool);
|
|
@@ -1381,7 +1374,7 @@ export async function handleCommand(
|
|
|
1381
1374
|
}
|
|
1382
1375
|
|
|
1383
1376
|
// /model <name> — 切换当前 session 的模型(支持所有 agent,模糊匹配)
|
|
1384
|
-
if (isCommandText && textLower.startsWith("/model ")) {
|
|
1377
|
+
if (isCommandText && textLower.startsWith("/model ")) {
|
|
1385
1378
|
const modelArg = text.slice(7).trim();
|
|
1386
1379
|
if (!modelArg) return; // 纯 "/model " 不处理,交给上面的 /model 分支
|
|
1387
1380
|
logTrace(tid, "BRANCH", { cmd: "/model", arg: modelArg, sessionId, tool: descriptionTool });
|
|
@@ -1415,7 +1408,7 @@ export async function handleCommand(
|
|
|
1415
1408
|
}
|
|
1416
1409
|
|
|
1417
1410
|
// /model — 查看当前会话的可用模型(根据会话 Agent 类型)
|
|
1418
|
-
if (isCommandText && textLower === "/model") {
|
|
1411
|
+
if (isCommandText && textLower === "/model") {
|
|
1419
1412
|
logTrace(tid, "BRANCH", { cmd: "/model", sessionId, tool: descriptionTool });
|
|
1420
1413
|
const models = getAllModelsForTool(descriptionTool as AgentTool);
|
|
1421
1414
|
const currentModel = getEffectiveModelForTool(descriptionTool, sessionId);
|
|
@@ -1439,7 +1432,7 @@ export async function handleCommand(
|
|
|
1439
1432
|
}
|
|
1440
1433
|
|
|
1441
1434
|
// /git <args>:在「当前会话工作目录」执行 git 命令
|
|
1442
|
-
if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
|
|
1435
|
+
if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
|
|
1443
1436
|
const args = text === "/git" ? "" : text.slice(5).trim();
|
|
1444
1437
|
logTrace(tid, "BRANCH", { cmd: "/git", args: args || "(none)" });
|
|
1445
1438
|
if (!args) {
|
|
@@ -1508,9 +1501,9 @@ export async function handleCommand(
|
|
|
1508
1501
|
|
|
1509
1502
|
// 并发检查:同一 session 只能有一个活跃 prompt,多余消息进入队列
|
|
1510
1503
|
if (isSessionRunning(sessionId)) {
|
|
1511
|
-
const queued = enqueueMessage(sessionId, {
|
|
1512
|
-
text: promptText, chatId, openId, msgTimestamp, chatType, traceId: tid,
|
|
1513
|
-
});
|
|
1504
|
+
const queued = enqueueMessage(sessionId, {
|
|
1505
|
+
text: promptText, chatId, openId, msgTimestamp, chatType, traceId: tid,
|
|
1506
|
+
});
|
|
1514
1507
|
if (queued) {
|
|
1515
1508
|
logTrace(tid, "QUEUED", { sessionId });
|
|
1516
1509
|
console.log(
|
|
@@ -1535,16 +1528,16 @@ export async function handleCommand(
|
|
|
1535
1528
|
return;
|
|
1536
1529
|
}
|
|
1537
1530
|
|
|
1538
|
-
if (shouldSendWechatProcessingAck(platform, isCommandText, chatType)) {
|
|
1531
|
+
if (shouldSendWechatProcessingAck(platform, isCommandText, chatType)) {
|
|
1539
1532
|
await platform.sendText(chatId, "生成中...").catch(() => {});
|
|
1540
1533
|
}
|
|
1541
1534
|
|
|
1542
1535
|
try {
|
|
1543
1536
|
logTrace(tid, "RESUME", { sessionId, tool: descriptionTool });
|
|
1544
1537
|
await resumeAndPrompt(
|
|
1545
|
-
sessionId,
|
|
1546
|
-
promptText,
|
|
1547
|
-
platform,
|
|
1538
|
+
sessionId,
|
|
1539
|
+
promptText,
|
|
1540
|
+
platform,
|
|
1548
1541
|
chatId,
|
|
1549
1542
|
msgTimestamp,
|
|
1550
1543
|
descriptionTool,
|
|
@@ -1570,7 +1563,7 @@ export async function handleCommand(
|
|
|
1570
1563
|
}
|
|
1571
1564
|
|
|
1572
1565
|
// 无会话上下文 → 检查是否是 /model 查询
|
|
1573
|
-
if (isCommandText && textLower === "/model") {
|
|
1566
|
+
if (isCommandText && textLower === "/model") {
|
|
1574
1567
|
const defaultTool = resolveDefaultAgentTool();
|
|
1575
1568
|
const models = getAllModelsForTool(defaultTool);
|
|
1576
1569
|
let currentModel = "";
|
|
@@ -1597,7 +1590,7 @@ export async function handleCommand(
|
|
|
1597
1590
|
}
|
|
1598
1591
|
|
|
1599
1592
|
// 无会话上下文 → /sessions 仍是有效指令,不触发飞书私聊自动建群。
|
|
1600
|
-
if (isCommandText && textLower === "/sessions") {
|
|
1593
|
+
if (isCommandText && textLower === "/sessions") {
|
|
1601
1594
|
logTrace(tid, "BRANCH", { cmd: "/sessions", scope: "global" });
|
|
1602
1595
|
const allSessions = await getAllSessionsStatus();
|
|
1603
1596
|
const now = Date.now();
|
|
@@ -1623,7 +1616,7 @@ export async function handleCommand(
|
|
|
1623
1616
|
}
|
|
1624
1617
|
|
|
1625
1618
|
// 飞书私聊普通消息:不再绑定私聊本身,而是自动创建会话群并把私聊内容作为首轮 prompt。
|
|
1626
|
-
if (isNonWechatP2p(platform, chatType) && !isCommandText) {
|
|
1619
|
+
if (isNonWechatP2p(platform, chatType) && !isCommandText) {
|
|
1627
1620
|
const tool = resolveDefaultAgentTool();
|
|
1628
1621
|
const toolLabel = toolDisplayName(tool);
|
|
1629
1622
|
logTrace(tid, "BRANCH", { cmd: "auto_new_from_p2p", tool });
|
|
@@ -1639,128 +1632,34 @@ export async function handleCommand(
|
|
|
1639
1632
|
return;
|
|
1640
1633
|
}
|
|
1641
1634
|
|
|
1642
|
-
let sessionId: string;
|
|
1643
|
-
let sessionCwd: string;
|
|
1644
1635
|
try {
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
error: (err as Error).message,
|
|
1636
|
+
const cwd = await getDefaultCwd(chatId);
|
|
1637
|
+
const result = await delegateAgentTask({
|
|
1638
|
+
platform,
|
|
1639
|
+
tool,
|
|
1640
|
+
cwd,
|
|
1641
|
+
promptText,
|
|
1642
|
+
openIds: [openId],
|
|
1643
|
+
chatNamePrefix: text.slice(0, 10) || "新会话",
|
|
1644
|
+
msgTimestamp,
|
|
1645
|
+
traceId: tid,
|
|
1656
1646
|
});
|
|
1657
|
-
await platform.sendCard(
|
|
1658
|
-
chatId,
|
|
1659
|
-
"Error",
|
|
1660
|
-
`Failed to initialize ${toolLabel} session:\n${(err as Error).message}`,
|
|
1661
|
-
"red",
|
|
1662
|
-
);
|
|
1663
|
-
return;
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
const cwd = sessionCwd;
|
|
1667
|
-
const initialName = sessionChatName(text.slice(0, 10) || "新会话", cwd);
|
|
1668
|
-
let newChatId: string;
|
|
1669
|
-
try {
|
|
1670
|
-
newChatId = await platform.createGroup(initialName, [openId]);
|
|
1671
|
-
console.log(
|
|
1672
|
-
`[${ts()}] [AUTO-P2P 2/5] Created Feishu group: ${newChatId} → OK`,
|
|
1673
|
-
);
|
|
1674
|
-
} catch (err) {
|
|
1675
|
-
console.error(`[${ts()}] [AUTO-P2P 2/5] FAIL: ${(err as Error).message}`);
|
|
1676
1647
|
logTrace(tid, "DONE", {
|
|
1677
|
-
outcome: "
|
|
1678
|
-
|
|
1648
|
+
outcome: "auto_new_p2p_prompt_done",
|
|
1649
|
+
newChatId: result.chatId,
|
|
1650
|
+
sessionId: result.sessionId,
|
|
1651
|
+
tool,
|
|
1679
1652
|
});
|
|
1680
|
-
await platform.sendCard(
|
|
1681
|
-
chatId,
|
|
1682
|
-
"Error",
|
|
1683
|
-
`Failed to create group:\n${(err as Error).message}`,
|
|
1684
|
-
"red",
|
|
1685
|
-
);
|
|
1686
|
-
return;
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
try {
|
|
1690
|
-
const descPrefix = sessionPrefixForTool(tool);
|
|
1691
|
-
await platform.updateChatInfo(
|
|
1692
|
-
newChatId,
|
|
1693
|
-
initialName,
|
|
1694
|
-
`${descPrefix} ${sessionId}`,
|
|
1695
|
-
);
|
|
1696
|
-
console.log(
|
|
1697
|
-
`[${ts()}] [AUTO-P2P 3/5] Renamed group → name="${initialName}" (${toolLabel}) → OK`,
|
|
1698
|
-
);
|
|
1699
1653
|
} catch (err) {
|
|
1700
|
-
console.error(`[${ts()}] [AUTO-P2P
|
|
1654
|
+
console.error(`[${ts()}] [AUTO-P2P] FAIL: ${(err as Error).message}`);
|
|
1701
1655
|
logTrace(tid, "DONE", {
|
|
1702
|
-
outcome: "
|
|
1656
|
+
outcome: "auto_new_p2p_delegate_fail",
|
|
1703
1657
|
error: (err as Error).message,
|
|
1704
1658
|
});
|
|
1705
1659
|
await platform.sendCard(
|
|
1706
1660
|
chatId,
|
|
1707
1661
|
"Error",
|
|
1708
|
-
`
|
|
1709
|
-
"yellow",
|
|
1710
|
-
);
|
|
1711
|
-
return;
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
await setDefaultCwd(cwd, newChatId);
|
|
1715
|
-
bindChatToSession(sessionId, newChatId);
|
|
1716
|
-
await recordSessionRegistry({
|
|
1717
|
-
chatId: newChatId,
|
|
1718
|
-
sessionId,
|
|
1719
|
-
tool,
|
|
1720
|
-
chatName: initialName,
|
|
1721
|
-
turnCount: 0,
|
|
1722
|
-
startTime: Date.now(),
|
|
1723
|
-
running: false,
|
|
1724
|
-
});
|
|
1725
|
-
await saveSessionTool(sessionId, tool, initialName);
|
|
1726
|
-
|
|
1727
|
-
await platform.sendCard(
|
|
1728
|
-
newChatId,
|
|
1729
|
-
`${toolLabel} Session Ready`,
|
|
1730
|
-
`已根据私聊消息创建 **${toolLabel}** 会话群。\n\n` +
|
|
1731
|
-
`**Session ID:** ${sessionId}\n` +
|
|
1732
|
-
`**工作目录:** \`${cwd}\`\n\n` +
|
|
1733
|
-
`下面会自动把你的私聊消息作为第一句话发送给 ${toolLabel}。\n\n` +
|
|
1734
|
-
`发送 **/model** 查看或切换当前会话的模型。\n` +
|
|
1735
|
-
`发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。`,
|
|
1736
|
-
"green",
|
|
1737
|
-
);
|
|
1738
|
-
platform.setChatAvatar(newChatId, tool, "new").catch(() => {});
|
|
1739
|
-
console.log(`[${ts()}] [AUTO-P2P 4/5] Replied to new group → OK`);
|
|
1740
|
-
|
|
1741
|
-
try {
|
|
1742
|
-
logTrace(tid, "RESUME", { sessionId, tool, trigger: "auto_new_from_p2p" });
|
|
1743
|
-
await resumeAndPrompt(
|
|
1744
|
-
sessionId,
|
|
1745
|
-
promptText,
|
|
1746
|
-
platform,
|
|
1747
|
-
newChatId,
|
|
1748
|
-
msgTimestamp,
|
|
1749
|
-
tool,
|
|
1750
|
-
tid,
|
|
1751
|
-
);
|
|
1752
|
-
console.log(`[${ts()}] [AUTO-P2P 5/5] First prompt sent → OK`);
|
|
1753
|
-
logTrace(tid, "DONE", { outcome: "auto_new_p2p_prompt_done", newChatId, sessionId, tool });
|
|
1754
|
-
} catch (err) {
|
|
1755
|
-
console.error(`[${ts()}] [AUTO-P2P 5/5] FAIL: ${(err as Error).message}`);
|
|
1756
|
-
logTrace(tid, "DONE", {
|
|
1757
|
-
outcome: "auto_new_p2p_prompt_fail",
|
|
1758
|
-
error: (err as Error).message,
|
|
1759
|
-
});
|
|
1760
|
-
await platform.sendCard(
|
|
1761
|
-
newChatId,
|
|
1762
|
-
"Error",
|
|
1763
|
-
`Failed to send first prompt:\n${(err as Error).message}`,
|
|
1662
|
+
`Failed to create ${toolLabel} delegated task:\n${(err as Error).message}`,
|
|
1764
1663
|
"red",
|
|
1765
1664
|
);
|
|
1766
1665
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function cwdDisplayName(cwd: string): string {
|
|
2
|
+
const trimmed = cwd.trim().replace(/[\\/]+$/, "");
|
|
3
|
+
return trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed || "cwd";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function sessionChatName(left: string, cwd: string): string {
|
|
7
|
+
return `${left}-${cwdDisplayName(cwd)}`;
|
|
8
|
+
}
|
package/src/session.ts
CHANGED
|
@@ -957,6 +957,7 @@ export async function runAgentSession(
|
|
|
957
957
|
cwd,
|
|
958
958
|
session_id: sessionId,
|
|
959
959
|
im_skills_cache_dir: imSkillsCacheDir,
|
|
960
|
+
delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
|
|
960
961
|
send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
|
|
961
962
|
send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
|
|
962
963
|
send_image_script: join(feishuSkillDir, "send-image.mjs"),
|