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.
@@ -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 { 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 { 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 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,
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: "auto_new_p2p_group_fail",
1678
- error: (err as Error).message,
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 3/5] FAIL: ${(err as Error).message}`);
1654
+ console.error(`[${ts()}] [AUTO-P2P] FAIL: ${(err as Error).message}`);
1701
1655
  logTrace(tid, "DONE", {
1702
- outcome: "auto_new_p2p_rename_fail",
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
- `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}`,
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"),