chatccc 0.2.183 → 0.2.185

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.
@@ -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,13 @@ 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 { applySharedPrefix } from "./shared-prefix.ts";
85
- export { type PlatformAdapter } from "./platform-adapter.ts";
82
+ import { getCodexUsageSummary, getTenantAccessToken, sendPostMessage } from "./feishu-platform.ts";
83
+ import { getCursorUsageSummary, type CursorUsageSummary } from "./cursor-usage.ts";
84
+ import { getChatGptSubscriptionStatus, type ChatGptSubscriptionResult } from "./chatgpt-subscription.ts";
85
+ import { delegateAgentTask } from "./agent-delegate-task.ts";
86
+ import { applySharedPrefix } from "./shared-prefix.ts";
87
+ import { cwdDisplayName, sessionChatName } from "./session-name.ts";
88
+ export { type PlatformAdapter } from "./platform-adapter.ts";
86
89
  import type { PlatformAdapter } from "./platform-adapter.ts";
87
90
  import type { CodexUsageSummary } from "./feishu-api.ts";
88
91
 
@@ -90,15 +93,6 @@ import type { CodexUsageSummary } from "./feishu-api.ts";
90
93
  // 辅助函数
91
94
  // ---------------------------------------------------------------------------
92
95
 
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
96
  /** 模型模糊匹配:精确匹配优先,否则找子串匹配(模型名越短越优先) */
103
97
  function findModelMatch(input: string, models: string[]): string | null {
104
98
  if (models.length === 0) return null;
@@ -114,7 +108,7 @@ function findModelMatch(input: string, models: string[]): string | null {
114
108
  return candidates[0] ?? null;
115
109
  }
116
110
 
117
- function formatCodexUsageSummary(usage: CodexUsageSummary): string {
111
+ function formatCodexUsageSummary(usage: CodexUsageSummary, chatGptSubscription: ChatGptSubscriptionResult | null = null): string {
118
112
  const progressBar = (usedPercent: number) => {
119
113
  const width = 20;
120
114
  const usedBlocks = Math.max(0, Math.min(width, Math.round((usedPercent / 100) * width)));
@@ -153,54 +147,87 @@ function formatCodexUsageSummary(usage: CodexUsageSummary): string {
153
147
  return `${absolute}${formatDuration(balance.resetAfterSeconds)}`;
154
148
  };
155
149
 
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
- }
150
+ const formatWindow = (label: string, balance: CodexUsageSummary["fiveHour"] | null) => {
151
+ if (!balance) return `**${label}:** 暂无数据`;
152
+ return [
153
+ `**${label}:** 已用 ${balance.usedPercent}%,剩余 ${balance.remainingPercent}%,重置: ${formatResetTime(balance)}`,
154
+ progressBar(balance.usedPercent),
155
+ ].join("\n");
156
+ };
157
+
158
+ const formatResetCredits = () => {
159
+ if (usage.rateLimitResetCreditsAvailable === null) return "**主动重置:** 暂无数据";
160
+ const lines = [`**主动重置:** 剩余 ${usage.rateLimitResetCreditsAvailable} 次`];
161
+ const credits = usage.rateLimitResetCredits ?? [];
162
+ if (credits.length > 0) {
163
+ const pad = (value: number) => String(value).padStart(2, "0");
164
+ const formatExpiresAt = (value: string) => {
165
+ const date = new Date(value);
166
+ if (!Number.isFinite(date.getTime())) return value;
167
+ return [
168
+ date.getFullYear(),
169
+ "-",
170
+ pad(date.getMonth() + 1),
171
+ "-",
172
+ pad(date.getDate()),
173
+ " ",
174
+ pad(date.getHours()),
175
+ ":",
176
+ pad(date.getMinutes()),
177
+ ":",
178
+ pad(date.getSeconds()),
179
+ ].join("");
180
+ };
181
+ lines.push("**过期时间:**");
182
+ for (const credit of credits) {
183
+ lines.push(`- ${formatExpiresAt(credit.expiresAt)}`);
184
+ }
185
+ }
186
+ return lines.join("\n");
187
+ };
188
+
189
+ const formatChatGptSubscription = () => {
190
+ if (!chatGptSubscription?.ok || !chatGptSubscription.subscription) return "";
191
+ const subscription = chatGptSubscription.subscription;
192
+ const pad = (value: number) => String(value).padStart(2, "0");
193
+ const formatExpiresAt = (value: string | null) => {
194
+ if (!value) return "暂无数据";
195
+ const date = new Date(value);
196
+ if (!Number.isFinite(date.getTime())) return value;
197
+ return [
198
+ date.getFullYear(),
199
+ "-",
200
+ pad(date.getMonth() + 1),
201
+ "-",
202
+ pad(date.getDate()),
203
+ " ",
204
+ pad(date.getHours()),
205
+ ":",
206
+ pad(date.getMinutes()),
207
+ ].join("");
208
+ };
209
+ const remaining = typeof subscription.remainingDays === "number"
210
+ ? `(剩余 ${subscription.remainingDays} 天)`
211
+ : "";
212
+ return [
213
+ "**ChatGPT 订阅:**",
214
+ `- 套餐: ${subscription.plan ?? "暂无数据"}`,
215
+ `- 到期: ${formatExpiresAt(subscription.expiresAt)}${remaining}`,
216
+ `- 自动续费: ${subscription.willRenew === null ? "暂无数据" : subscription.willRenew ? "是" : "否"}`,
217
+ ].join("\n");
218
+ };
219
+
220
+ return [
221
+ "Codex 用量:",
222
+ "",
223
+ formatChatGptSubscription(),
224
+ chatGptSubscription?.ok ? "" : "",
225
+ formatResetCredits(),
226
+ "",
227
+ formatWindow("5h", usage.fiveHour),
228
+ formatWindow("周", usage.weekly),
229
+ ].filter((line, index, arr) => line !== "" || (index > 0 && arr[index - 1] !== "")).join("\n");
230
+ }
204
231
 
205
232
  function formatCursorUsageSummary(usage: CursorUsageSummary): string {
206
233
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -259,7 +286,7 @@ function formatCursorUsageSummary(usage: CursorUsageSummary): string {
259
286
  }
260
287
 
261
288
  function usageHelpLine(tool: string): string {
262
- if (tool === "codex") return "\n发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡。";
289
+ if (tool === "codex") return "\n发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡。";
263
290
  if (tool === "cursor") return "\n发送 **/usage** 查看 Cursor 用量。";
264
291
  return "";
265
292
  }
@@ -284,16 +311,19 @@ async function sendUsageSummary(platform: PlatformAdapter, chatId: string, tool:
284
311
  return;
285
312
  }
286
313
 
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
- }
314
+ const [usage, chatGptSubscription] = await Promise.all([
315
+ getCodexUsageSummary(),
316
+ getChatGptSubscriptionStatus().catch(() => null),
317
+ ]);
318
+ const content = formatCodexUsageSummary(usage, chatGptSubscription);
319
+ if (platform.kind === "wechat") {
320
+ await platform.sendText(chatId, content).catch(() => {});
321
+ } else if (platform.kind === "feishu") {
322
+ await platform.sendRawCard(chatId, buildCodexUsageCard(content, usage.rateLimitResetCreditsAvailable));
323
+ } else {
324
+ await platform.sendCard(chatId, "Codex Usage", content, "blue");
325
+ }
326
+ }
297
327
 
298
328
  async function sendUsageError(platform: PlatformAdapter, chatId: string, tool: "codex" | "cursor", err: unknown): Promise<void> {
299
329
  const toolLabel = tool === "cursor" ? "Cursor" : "Codex";
@@ -309,13 +339,13 @@ function isUntitledSessionChatName(name: string): boolean {
309
339
  return name === "新会话" || name.startsWith("新会话-");
310
340
  }
311
341
 
312
- function shouldSendWechatProcessingAck(
313
- platform: PlatformAdapter,
314
- isCommandText: boolean,
315
- chatType: string,
316
- ): boolean {
317
- return platform.kind === "wechat" && chatType === "p2p" && !isCommandText;
318
- }
342
+ function shouldSendWechatProcessingAck(
343
+ platform: PlatformAdapter,
344
+ isCommandText: boolean,
345
+ chatType: string,
346
+ ): boolean {
347
+ return platform.kind === "wechat" && chatType === "p2p" && !isCommandText;
348
+ }
319
349
 
320
350
  function isNonWechatP2p(platform: PlatformAdapter, chatType: string): boolean {
321
351
  return chatType === "p2p" && platform.kind !== "wechat";
@@ -445,17 +475,17 @@ export async function handleCommand(
445
475
  msgTimestamp: number,
446
476
  chatType = "group",
447
477
  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);
478
+ ): Promise<void> {
479
+ const tid = traceId ?? makeTraceId();
480
+ const sharedPrefix = applySharedPrefix(text);
481
+ const promptText = sharedPrefix.text;
482
+ text = sharedPrefix.body;
483
+ const textLower = text.toLowerCase();
484
+ const isCommandText = !sharedPrefix.matched && textLower.startsWith("/");
485
+ recordChatPlatform(chatId, platform);
456
486
  await cleanupNonWechatP2pBinding(platform, chatId, chatType, tid);
457
487
 
458
- if (isCommandText && textLower === "/restart") {
488
+ if (isCommandText && textLower === "/restart") {
459
489
  logTrace(tid, "BRANCH", { cmd: "/restart" });
460
490
  await platform.sendText(chatId, "重启中...请几秒后发消息唤醒我").catch(() => {});
461
491
  logTrace(tid, "DONE", { outcome: "restart" });
@@ -490,7 +520,7 @@ export async function handleCommand(
490
520
  return;
491
521
  }
492
522
 
493
- if (isCommandText && textLower === "/update") {
523
+ if (isCommandText && textLower === "/update") {
494
524
  logTrace(tid, "BRANCH", { cmd: "/update" });
495
525
  const isGlobal = isRunningFromGlobalNpm();
496
526
  appendStartupTrace("update: command received", { isGlobal, chatId });
@@ -507,7 +537,7 @@ export async function handleCommand(
507
537
  return;
508
538
  }
509
539
 
510
- if (isCommandText && textLower === "/usage") {
540
+ if (isCommandText && textLower === "/usage") {
511
541
  const usageTool = await resolveUsageTool(chatId);
512
542
  logTrace(tid, "BRANCH", { cmd: "/usage", tool: usageTool });
513
543
  try {
@@ -520,7 +550,7 @@ export async function handleCommand(
520
550
  return;
521
551
  }
522
552
 
523
- if (isCommandText && (textLower === "/cd" || textLower.startsWith("/cd "))) {
553
+ if (isCommandText && (textLower === "/cd" || textLower.startsWith("/cd "))) {
524
554
  logTrace(tid, "BRANCH", {
525
555
  cmd: "/cd",
526
556
  arg: text.slice(3).trim() || "(none)",
@@ -643,7 +673,7 @@ export async function handleCommand(
643
673
  return;
644
674
  }
645
675
 
646
- if (isCommandText && (textLower === "/new" || textLower.startsWith("/new "))) {
676
+ if (isCommandText && (textLower === "/new" || textLower.startsWith("/new "))) {
647
677
  const toolArg = text.slice(5).trim().toLowerCase();
648
678
  const tool = toolArg || resolveDefaultAgentTool();
649
679
  logTrace(tid, "BRANCH", { cmd: "/new", tool });
@@ -904,7 +934,7 @@ export async function handleCommand(
904
934
  if (sessionId && descriptionTool && toolLabel) {
905
935
  // 有会话上下文 — 路由到命令处理或 prompt
906
936
  logTrace(tid, "BRANCH", { sessionId, tool: descriptionTool });
907
- const routeKind = isCommandText ? "command" : "prompt";
937
+ const routeKind = isCommandText ? "command" : "prompt";
908
938
  const chatKind = chatType === "p2p" ? "p2p chat" : "session group";
909
939
  console.log(
910
940
  `[${ts()}] [ROUTE] ${toolLabel} ${chatKind} ${routeKind} detected, session=${sessionId} tool=${descriptionTool}`,
@@ -913,7 +943,7 @@ export async function handleCommand(
913
943
  if (
914
944
  chatType !== "p2p" &&
915
945
  isUntitledSessionChatName(chatInfo!.name) &&
916
- !isCommandText
946
+ !isCommandText
917
947
  ) {
918
948
  const MAX_PREFIX = 10;
919
949
  const prefix = text.slice(0, MAX_PREFIX);
@@ -948,7 +978,7 @@ export async function handleCommand(
948
978
  if (
949
979
  chatType === "p2p" &&
950
980
  platform.kind === "wechat" &&
951
- !isCommandText
981
+ !isCommandText
952
982
  ) {
953
983
  try {
954
984
  const reg = await loadSessionRegistryForBinding();
@@ -987,7 +1017,7 @@ export async function handleCommand(
987
1017
  }
988
1018
  }
989
1019
 
990
- if (isCommandText && textLower === "/stop") {
1020
+ if (isCommandText && textLower === "/stop") {
991
1021
  logTrace(tid, "BRANCH", { cmd: "/stop" });
992
1022
  if (stopSession(sessionId)) {
993
1023
  console.log(`[${ts()}] [STOP] User sent /stop, session=${sessionId}`);
@@ -1001,7 +1031,7 @@ export async function handleCommand(
1001
1031
  return;
1002
1032
  }
1003
1033
 
1004
- if (isCommandText && textLower === "/cancel") {
1034
+ if (isCommandText && textLower === "/cancel") {
1005
1035
  logTrace(tid, "BRANCH", { cmd: "/cancel" });
1006
1036
  if (cancelQueuedMessage(sessionId)) {
1007
1037
  console.log(`[${ts()}] [CANCEL] Queue cancelled for session=${sessionId}`);
@@ -1014,7 +1044,7 @@ export async function handleCommand(
1014
1044
  return;
1015
1045
  }
1016
1046
 
1017
- if (isCommandText && textLower === "/test") {
1047
+ if (isCommandText && textLower === "/test") {
1018
1048
  logTrace(tid, "BRANCH", { cmd: "/test" });
1019
1049
  const tableHeaders = ["名称", "版本", "状态"];
1020
1050
  const tableRows = [
@@ -1051,7 +1081,7 @@ export async function handleCommand(
1051
1081
  return;
1052
1082
  }
1053
1083
 
1054
- if (isCommandText && textLower === "/state") {
1084
+ if (isCommandText && textLower === "/state") {
1055
1085
  logTrace(tid, "BRANCH", { cmd: "/state" });
1056
1086
  const status = await getSessionStatus(chatId);
1057
1087
  const isActive = isSessionRunning(sessionId);
@@ -1090,7 +1120,7 @@ export async function handleCommand(
1090
1120
  return;
1091
1121
  }
1092
1122
 
1093
- if (isCommandText && textLower === "/sessions") {
1123
+ if (isCommandText && textLower === "/sessions") {
1094
1124
  logTrace(tid, "BRANCH", { cmd: "/sessions" });
1095
1125
  const allSessions = await getAllSessionsStatus();
1096
1126
  const now = Date.now();
@@ -1115,7 +1145,7 @@ export async function handleCommand(
1115
1145
  return;
1116
1146
  }
1117
1147
 
1118
- if (isCommandText && textLower === "/newh") {
1148
+ if (isCommandText && textLower === "/newh") {
1119
1149
  logTrace(tid, "BRANCH", { cmd: "/newh" });
1120
1150
  const adapter = getAdapterForTool(descriptionTool, sessionId);
1121
1151
  let cwd: string;
@@ -1202,7 +1232,7 @@ export async function handleCommand(
1202
1232
  return;
1203
1233
  }
1204
1234
 
1205
- if (isCommandText && textLower === "/deleteg") {
1235
+ if (isCommandText && textLower === "/deleteg") {
1206
1236
  logTrace(tid, "BRANCH", { cmd: "/deleteg" });
1207
1237
  if (chatType === "p2p") {
1208
1238
  await platform
@@ -1240,7 +1270,7 @@ export async function handleCommand(
1240
1270
  }
1241
1271
 
1242
1272
  // /session <number>:切换到 /sessions 列表中的指定会话
1243
- const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
1273
+ const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
1244
1274
  if (sessionMatch) {
1245
1275
  const index = parseInt(sessionMatch[1], 10) - 1;
1246
1276
  logTrace(tid, "BRANCH", { cmd: "/session", index: index + 1 });
@@ -1366,7 +1396,7 @@ export async function handleCommand(
1366
1396
  }
1367
1397
 
1368
1398
  // /model clear — 清除当前 session 的模型覆盖
1369
- if (isCommandText && textLower === "/model clear") {
1399
+ if (isCommandText && textLower === "/model clear") {
1370
1400
  logTrace(tid, "BRANCH", { cmd: "/model clear", sessionId });
1371
1401
  clearSessionModelOverride(sessionId);
1372
1402
  const defaultModel = getEffectiveModelForTool(descriptionTool);
@@ -1381,7 +1411,7 @@ export async function handleCommand(
1381
1411
  }
1382
1412
 
1383
1413
  // /model <name> — 切换当前 session 的模型(支持所有 agent,模糊匹配)
1384
- if (isCommandText && textLower.startsWith("/model ")) {
1414
+ if (isCommandText && textLower.startsWith("/model ")) {
1385
1415
  const modelArg = text.slice(7).trim();
1386
1416
  if (!modelArg) return; // 纯 "/model " 不处理,交给上面的 /model 分支
1387
1417
  logTrace(tid, "BRANCH", { cmd: "/model", arg: modelArg, sessionId, tool: descriptionTool });
@@ -1415,7 +1445,7 @@ export async function handleCommand(
1415
1445
  }
1416
1446
 
1417
1447
  // /model — 查看当前会话的可用模型(根据会话 Agent 类型)
1418
- if (isCommandText && textLower === "/model") {
1448
+ if (isCommandText && textLower === "/model") {
1419
1449
  logTrace(tid, "BRANCH", { cmd: "/model", sessionId, tool: descriptionTool });
1420
1450
  const models = getAllModelsForTool(descriptionTool as AgentTool);
1421
1451
  const currentModel = getEffectiveModelForTool(descriptionTool, sessionId);
@@ -1439,7 +1469,7 @@ export async function handleCommand(
1439
1469
  }
1440
1470
 
1441
1471
  // /git <args>:在「当前会话工作目录」执行 git 命令
1442
- if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
1472
+ if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
1443
1473
  const args = text === "/git" ? "" : text.slice(5).trim();
1444
1474
  logTrace(tid, "BRANCH", { cmd: "/git", args: args || "(none)" });
1445
1475
  if (!args) {
@@ -1508,9 +1538,9 @@ export async function handleCommand(
1508
1538
 
1509
1539
  // 并发检查:同一 session 只能有一个活跃 prompt,多余消息进入队列
1510
1540
  if (isSessionRunning(sessionId)) {
1511
- const queued = enqueueMessage(sessionId, {
1512
- text: promptText, chatId, openId, msgTimestamp, chatType, traceId: tid,
1513
- });
1541
+ const queued = enqueueMessage(sessionId, {
1542
+ text: promptText, chatId, openId, msgTimestamp, chatType, traceId: tid,
1543
+ });
1514
1544
  if (queued) {
1515
1545
  logTrace(tid, "QUEUED", { sessionId });
1516
1546
  console.log(
@@ -1535,16 +1565,16 @@ export async function handleCommand(
1535
1565
  return;
1536
1566
  }
1537
1567
 
1538
- if (shouldSendWechatProcessingAck(platform, isCommandText, chatType)) {
1568
+ if (shouldSendWechatProcessingAck(platform, isCommandText, chatType)) {
1539
1569
  await platform.sendText(chatId, "生成中...").catch(() => {});
1540
1570
  }
1541
1571
 
1542
1572
  try {
1543
1573
  logTrace(tid, "RESUME", { sessionId, tool: descriptionTool });
1544
1574
  await resumeAndPrompt(
1545
- sessionId,
1546
- promptText,
1547
- platform,
1575
+ sessionId,
1576
+ promptText,
1577
+ platform,
1548
1578
  chatId,
1549
1579
  msgTimestamp,
1550
1580
  descriptionTool,
@@ -1570,7 +1600,7 @@ export async function handleCommand(
1570
1600
  }
1571
1601
 
1572
1602
  // 无会话上下文 → 检查是否是 /model 查询
1573
- if (isCommandText && textLower === "/model") {
1603
+ if (isCommandText && textLower === "/model") {
1574
1604
  const defaultTool = resolveDefaultAgentTool();
1575
1605
  const models = getAllModelsForTool(defaultTool);
1576
1606
  let currentModel = "";
@@ -1597,7 +1627,7 @@ export async function handleCommand(
1597
1627
  }
1598
1628
 
1599
1629
  // 无会话上下文 → /sessions 仍是有效指令,不触发飞书私聊自动建群。
1600
- if (isCommandText && textLower === "/sessions") {
1630
+ if (isCommandText && textLower === "/sessions") {
1601
1631
  logTrace(tid, "BRANCH", { cmd: "/sessions", scope: "global" });
1602
1632
  const allSessions = await getAllSessionsStatus();
1603
1633
  const now = Date.now();
@@ -1623,7 +1653,7 @@ export async function handleCommand(
1623
1653
  }
1624
1654
 
1625
1655
  // 飞书私聊普通消息:不再绑定私聊本身,而是自动创建会话群并把私聊内容作为首轮 prompt。
1626
- if (isNonWechatP2p(platform, chatType) && !isCommandText) {
1656
+ if (isNonWechatP2p(platform, chatType) && !isCommandText) {
1627
1657
  const tool = resolveDefaultAgentTool();
1628
1658
  const toolLabel = toolDisplayName(tool);
1629
1659
  logTrace(tid, "BRANCH", { cmd: "auto_new_from_p2p", tool });
@@ -1639,128 +1669,34 @@ export async function handleCommand(
1639
1669
  return;
1640
1670
  }
1641
1671
 
1642
- let sessionId: string;
1643
- let sessionCwd: string;
1644
1672
  try {
1645
- const init = await initClaudeSession(tool, undefined, chatId);
1646
- sessionId = init.sessionId;
1647
- sessionCwd = init.cwd;
1648
- console.log(
1649
- `[${ts()}] [AUTO-P2P 1/5] ${toolLabel} session created: ${sessionId} → OK`,
1650
- );
1651
- } catch (err) {
1652
- console.error(`[${ts()}] [AUTO-P2P 1/5] FAIL: ${(err as Error).message}`);
1653
- logTrace(tid, "DONE", {
1654
- outcome: "auto_new_p2p_session_fail",
1655
- error: (err as Error).message,
1673
+ const cwd = await getDefaultCwd(chatId);
1674
+ const result = await delegateAgentTask({
1675
+ platform,
1676
+ tool,
1677
+ cwd,
1678
+ promptText,
1679
+ openIds: [openId],
1680
+ chatNamePrefix: text.slice(0, 10) || "新会话",
1681
+ msgTimestamp,
1682
+ traceId: tid,
1656
1683
  });
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
1684
  logTrace(tid, "DONE", {
1677
- outcome: "auto_new_p2p_group_fail",
1678
- error: (err as Error).message,
1685
+ outcome: "auto_new_p2p_prompt_done",
1686
+ newChatId: result.chatId,
1687
+ sessionId: result.sessionId,
1688
+ tool,
1679
1689
  });
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
1690
  } catch (err) {
1700
- console.error(`[${ts()}] [AUTO-P2P 3/5] FAIL: ${(err as Error).message}`);
1691
+ console.error(`[${ts()}] [AUTO-P2P] FAIL: ${(err as Error).message}`);
1701
1692
  logTrace(tid, "DONE", {
1702
- outcome: "auto_new_p2p_rename_fail",
1693
+ outcome: "auto_new_p2p_delegate_fail",
1703
1694
  error: (err as Error).message,
1704
1695
  });
1705
1696
  await platform.sendCard(
1706
1697
  chatId,
1707
1698
  "Error",
1708
- `Group created but rename failed:\n${(err as Error).message}`,
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}`,
1699
+ `Failed to create ${toolLabel} delegated task:\n${(err as Error).message}`,
1764
1700
  "red",
1765
1701
  );
1766
1702
  }
@@ -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
+ }