chatccc 0.2.201 → 0.2.202

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.
Files changed (92) hide show
  1. package/README.md +17 -17
  2. package/agent-prompts/claude_specific.md +45 -45
  3. package/agent-prompts/codex_specific.md +2 -2
  4. package/agent-prompts/cursor_specific.md +13 -13
  5. package/bin/cccagent.mjs +17 -17
  6. package/config.sample.json +27 -27
  7. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  8. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  9. package/im-skills/feishu-skill/skill.md +3 -3
  10. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  11. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  12. package/im-skills/wechat-file-skill/skill.md +10 -10
  13. package/im-skills/wechat-image-skill/skill.md +10 -10
  14. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  15. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  16. package/im-skills/wechat-video-skill/skill.md +10 -10
  17. package/package.json +1 -1
  18. package/scripts/postinstall-sharp-check.mjs +58 -58
  19. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  20. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  21. package/src/__tests__/builtin-chat-session.test.ts +277 -277
  22. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  23. package/src/__tests__/builtin-config.test.ts +33 -33
  24. package/src/__tests__/builtin-context.test.ts +163 -163
  25. package/src/__tests__/builtin-file-tools.test.ts +224 -224
  26. package/src/__tests__/builtin-session-select.test.ts +116 -116
  27. package/src/__tests__/builtin-sigint.test.ts +56 -56
  28. package/src/__tests__/card-plain-text.test.ts +5 -5
  29. package/src/__tests__/cardkit.test.ts +60 -60
  30. package/src/__tests__/cards.test.ts +133 -122
  31. package/src/__tests__/ccc-adapter.test.ts +114 -114
  32. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  33. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  34. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  35. package/src/__tests__/claude-adapter.test.ts +592 -592
  36. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  37. package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
  38. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  39. package/src/__tests__/config-reload.test.ts +10 -10
  40. package/src/__tests__/config-sample.test.ts +18 -18
  41. package/src/__tests__/cursor-adapter.test.ts +226 -226
  42. package/src/__tests__/feishu-api.test.ts +60 -60
  43. package/src/__tests__/feishu-avatar.test.ts +90 -90
  44. package/src/__tests__/feishu-platform.test.ts +22 -22
  45. package/src/__tests__/format-message.test.ts +47 -47
  46. package/src/__tests__/jsonl-stream.test.ts +79 -79
  47. package/src/__tests__/orchestrator.test.ts +464 -301
  48. package/src/__tests__/privacy.test.ts +198 -198
  49. package/src/__tests__/raw-stream-log.test.ts +106 -106
  50. package/src/__tests__/session.test.ts +46 -44
  51. package/src/__tests__/shared-prefix.test.ts +36 -36
  52. package/src/__tests__/sim-platform.test.ts +12 -12
  53. package/src/__tests__/stream-state.test.ts +42 -42
  54. package/src/__tests__/web-ui.test.ts +209 -209
  55. package/src/adapters/ccc-adapter.ts +121 -121
  56. package/src/adapters/claude-adapter.ts +603 -603
  57. package/src/adapters/claude-session-meta-store.ts +120 -120
  58. package/src/adapters/codex-adapter.ts +380 -380
  59. package/src/adapters/cursor-adapter.ts +673 -673
  60. package/src/adapters/jsonl-stream.ts +157 -157
  61. package/src/adapters/raw-stream-log.ts +124 -124
  62. package/src/adapters/resource-monitor.ts +140 -140
  63. package/src/agent-delegate-task-rpc.ts +153 -153
  64. package/src/agent-delegate-task.ts +81 -81
  65. package/src/agent-reload-config-rpc.ts +34 -34
  66. package/src/agent-stop-stuck.ts +129 -129
  67. package/src/builtin/cli.ts +473 -473
  68. package/src/builtin/context.ts +323 -323
  69. package/src/builtin/file-tools.ts +1072 -1072
  70. package/src/builtin/index.ts +404 -404
  71. package/src/builtin/session-select.ts +48 -48
  72. package/src/builtin/sigint.ts +50 -50
  73. package/src/cards.ts +222 -210
  74. package/src/chatgpt-subscription-rpc.ts +27 -27
  75. package/src/chatgpt-subscription.ts +299 -299
  76. package/src/chrome-devtools-guard.ts +318 -318
  77. package/src/codex-reset-actions.ts +184 -184
  78. package/src/config.ts +125 -125
  79. package/src/feishu-api.ts +118 -118
  80. package/src/feishu-platform.ts +20 -20
  81. package/src/format-message.ts +293 -293
  82. package/src/litellm-proxy.ts +374 -374
  83. package/src/orchestrator.ts +348 -329
  84. package/src/privacy.ts +118 -118
  85. package/src/runtime-reload.ts +34 -34
  86. package/src/session-chat-binding.ts +6 -6
  87. package/src/session-name.ts +8 -8
  88. package/src/session.ts +177 -169
  89. package/src/shared-prefix.ts +29 -29
  90. package/src/sim-platform.ts +20 -20
  91. package/src/turn-cards.ts +117 -117
  92. package/src/web-ui.ts +205 -205
@@ -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,10 @@ 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 { delegateAgentTask } from "./agent-delegate-task.ts";
91
- import { applySharedPrefix } from "./shared-prefix.ts";
92
- import { cwdDisplayName, sessionChatName } from "./session-name.ts";
93
- import { reloadRuntimeConfig } from "./runtime-reload.ts";
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
+ export { type PlatformAdapter } from "./platform-adapter.ts";
95
94
  import type { ChatAvatarUsageHints, PlatformAdapter } from "./platform-adapter.ts";
96
95
  import type { CodexUsageSummary } from "./feishu-api.ts";
97
96
 
@@ -135,7 +134,7 @@ function formatCodexUsageSummary(usage: CodexUsageSummary, chatGptSubscription:
135
134
  return `(约 ${parts.join("")}后)`;
136
135
  };
137
136
 
138
- const formatResetTime = (balance: NonNullable<CodexUsageSummary["fiveHour"]>) => {
137
+ const formatResetTime = (balance: NonNullable<CodexUsageSummary["fiveHour"]>) => {
139
138
  if (balance.resetAtEpochSeconds === null) return "暂无数据";
140
139
  const date = new Date(balance.resetAtEpochSeconds * 1000);
141
140
  const pad = (value: number) => String(value).padStart(2, "0");
@@ -263,8 +262,8 @@ function formatCodexUsageSummary(usage: CodexUsageSummary, chatGptSubscription:
263
262
  formatChatGptSubscriptionFailure(),
264
263
  formatResetCredits(),
265
264
  "",
266
- usage.fiveHour ? formatWindow("5h", usage.fiveHour) : "",
267
- usage.weekly ? formatWindow("7天", usage.weekly) : "",
265
+ usage.fiveHour ? formatWindow("5h", usage.fiveHour) : "",
266
+ usage.weekly ? formatWindow("7天", usage.weekly) : "",
268
267
  ].filter((line, index, arr) => line !== "" || (index > 0 && arr[index - 1] !== "")).join("\n");
269
268
  }
270
269
 
@@ -325,7 +324,7 @@ function formatCursorUsageSummary(usage: CursorUsageSummary): string {
325
324
  }
326
325
 
327
326
  function usageHelpLine(tool: string): string {
328
- if (tool === "codex") return "\n发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。";
327
+ if (tool === "codex") return "\n发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。";
329
328
  if (tool === "cursor") return "\n发送 **/usage** 查看 Cursor 用量。";
330
329
  return "";
331
330
  }
@@ -410,42 +409,10 @@ function shouldSendWechatProcessingAck(
410
409
  return platform.kind === "wechat" && chatType === "p2p" && !isCommandText;
411
410
  }
412
411
 
413
- function isNonWechatP2p(platform: PlatformAdapter, chatType: string): boolean {
414
- return chatType === "p2p" && platform.kind !== "wechat";
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
- }
412
+ /** 飞书私聊是固定会话容器;显式 /new 才创建独立群聊。 */
413
+ function isFeishuP2p(platform: PlatformAdapter, chatType: string): boolean {
414
+ return chatType === "p2p" && platform.kind === "feishu";
415
+ }
449
416
 
450
417
  /** 检测当前进程是否从 npm 全局安装启动 */
451
418
  function isRunningFromGlobalNpm(): boolean {
@@ -544,32 +511,31 @@ export async function handleCommand(
544
511
  const promptText = sharedPrefix.text;
545
512
  text = sharedPrefix.body;
546
513
  const textLower = text.toLowerCase();
547
- const isCommandText = !sharedPrefix.matched && textLower.startsWith("/");
548
- recordChatPlatform(chatId, platform);
549
- await cleanupNonWechatP2pBinding(platform, chatId, chatType, tid);
550
-
551
- if (isCommandText && textLower === "/reload") {
552
- logTrace(tid, "BRANCH", { cmd: "/reload" });
553
- try {
554
- const result = await reloadRuntimeConfig("chat-command");
555
- await platform.sendText(
556
- chatId,
557
- [
558
- "配置已重新加载。",
559
- `默认 Agent: ${toolDisplayName(result.defaultAgent)}`,
560
- `配置文件: ${result.configPath}`,
561
- "后续新会话会使用最新配置;正在生成的会话不会被中断。",
562
- ].join("\n"),
563
- ).catch(() => {});
564
- logTrace(tid, "DONE", { outcome: "reload", defaultAgent: result.defaultAgent });
565
- } catch (err) {
566
- await platform.sendText(chatId, `配置重载失败:${(err as Error).message}`).catch(() => {});
567
- logTrace(tid, "DONE", { outcome: "reload_fail", error: (err as Error).message });
568
- }
569
- return;
570
- }
571
-
572
- if (isCommandText && textLower === "/restart") {
514
+ const isCommandText = !sharedPrefix.matched && textLower.startsWith("/");
515
+ recordChatPlatform(chatId, platform);
516
+
517
+ if (isCommandText && textLower === "/reload") {
518
+ logTrace(tid, "BRANCH", { cmd: "/reload" });
519
+ try {
520
+ const result = await reloadRuntimeConfig("chat-command");
521
+ await platform.sendText(
522
+ chatId,
523
+ [
524
+ "配置已重新加载。",
525
+ `默认 Agent: ${toolDisplayName(result.defaultAgent)}`,
526
+ `配置文件: ${result.configPath}`,
527
+ "后续新会话会使用最新配置;正在生成的会话不会被中断。",
528
+ ].join("\n"),
529
+ ).catch(() => {});
530
+ logTrace(tid, "DONE", { outcome: "reload", defaultAgent: result.defaultAgent });
531
+ } catch (err) {
532
+ await platform.sendText(chatId, `配置重载失败:${(err as Error).message}`).catch(() => {});
533
+ logTrace(tid, "DONE", { outcome: "reload_fail", error: (err as Error).message });
534
+ }
535
+ return;
536
+ }
537
+
538
+ if (isCommandText && textLower === "/restart") {
573
539
  logTrace(tid, "BRANCH", { cmd: "/restart" });
574
540
  await platform.sendText(chatId, "重启中...请几秒后发消息唤醒我").catch(() => {});
575
541
  logTrace(tid, "DONE", { outcome: "restart" });
@@ -763,7 +729,7 @@ export async function handleCommand(
763
729
  const toolArg = text.slice(5).trim().toLowerCase();
764
730
  const tool = toolArg || resolveDefaultAgentTool();
765
731
  logTrace(tid, "BRANCH", { cmd: "/new", tool });
766
- const validTools = ["claude", "cursor", "codex", "ccc"];
732
+ const validTools = ["claude", "cursor", "codex", "ccc"];
767
733
  if (!validTools.includes(tool)) {
768
734
  logTrace(tid, "DONE", { outcome: "new_invalid_tool", tool });
769
735
  await platform.sendCard(
@@ -815,9 +781,9 @@ export async function handleCommand(
815
781
  const cwd = sessionCwd;
816
782
  const initialName = sessionChatName("新会话", cwd);
817
783
 
818
- // 微信私聊:不创建群,直接绑定 session 到当前私聊
819
- // 飞书私聊:也要建群
820
- if (chatType === "p2p" && platform.kind === "wechat") {
784
+ // /new 的平台语义保持不同:微信在当前私聊新建 session;飞书显式
785
+ // /new 始终创建独立群聊。飞书私聊自己的常驻 session 不在这里切换。
786
+ if (chatType === "p2p" && platform.kind === "wechat") {
821
787
  // 先解绑旧 session(如果存在),避免旧 session 的 display loop
822
788
  // 继续往同一个 chat 推送内容(/newh 走 switchChatBinding 已有此逻辑,
823
789
  // 但 /new p2p 之前遗漏了解绑)。
@@ -837,11 +803,12 @@ export async function handleCommand(
837
803
  tool,
838
804
  });
839
805
  await setDefaultCwd(cwd, chatId);
840
- await recordSessionRegistry({
841
- chatId,
842
- sessionId,
843
- tool,
844
- chatName: initialName,
806
+ await recordSessionRegistry({
807
+ chatId,
808
+ sessionId,
809
+ tool,
810
+ chatType,
811
+ chatName: initialName,
845
812
  turnCount: 0,
846
813
  startTime: Date.now(),
847
814
  running: false,
@@ -923,11 +890,12 @@ export async function handleCommand(
923
890
  // 让新群的默认工作目录继承当前会话的 cwd
924
891
  await setDefaultCwd(cwd, newChatId);
925
892
  bindChatToSession(sessionId, newChatId);
926
- await recordSessionRegistry({
927
- chatId: newChatId,
928
- sessionId,
929
- tool,
930
- chatName: initialName,
893
+ await recordSessionRegistry({
894
+ chatId: newChatId,
895
+ sessionId,
896
+ tool,
897
+ chatType: "group",
898
+ chatName: initialName,
931
899
  turnCount: 0,
932
900
  startTime: Date.now(),
933
901
  running: false,
@@ -962,7 +930,8 @@ export async function handleCommand(
962
930
  return;
963
931
  }
964
932
 
965
- // 检测会话上下文:群聊从 description 获取,私聊从 session-registry 获取
933
+ // 检测会话上下文:群聊从 description 获取,飞书/微信私聊都从
934
+ // session-registry 获取。私聊 chatId 是稳定容器,进程重启后仍恢复绑定。
966
935
  let sessionId: string | null = null;
967
936
  let descriptionTool: string | null = null;
968
937
  let toolLabel: string | null = null;
@@ -988,17 +957,30 @@ export async function handleCommand(
988
957
  `[${ts()}] [INFO] Cannot get chat info for ${chatId}: ${(err as Error).message}`,
989
958
  );
990
959
  }
991
- } else if (platform.kind === "wechat") {
992
- // 微信私聊:从 session-registry.json 获取绑定的 session
993
- // 飞书私聊不再作为持久会话入口,避免历史 registry 残留误路由到旧 session。
994
- try {
995
- const registry = await loadSessionRegistryForBinding();
996
- const record = registry[chatId];
997
- if (record && record.sessionId && record.tool) {
998
- sessionId = record.sessionId;
960
+ } else if (platform.kind === "wechat" || platform.kind === "feishu") {
961
+ // 私聊没有可写的群描述,因此会话绑定只持久化在 session-registry.json。
962
+ try {
963
+ const registry = await loadSessionRegistryForBinding();
964
+ const record = registry[chatId];
965
+ // 旧版飞书曾把私聊视为建群入口,并会清理私聊 registry。没有 p2p
966
+ // 标记的残留记录不能证明它是在固定用户目录创建的,因此只迁移一次:
967
+ // 先解除旧绑定;若本条是普通消息,随后在用户目录创建新的私聊 session。
968
+ if (platform.kind === "feishu" && record?.sessionId && record.chatType !== "p2p") {
969
+ unbindChatFromSession(record.sessionId, chatId);
970
+ displayCards.delete(chatId);
971
+ cancelQueuedMessage(record.sessionId);
972
+ sessionInfoMap.delete(chatId);
973
+ await removeSessionRegistryRecord(chatId);
974
+ logTrace(tid, "BRANCH", {
975
+ reason: "migrate_legacy_feishu_p2p_binding",
976
+ chatId,
977
+ oldSessionId: record.sessionId,
978
+ });
979
+ } else if (record && record.sessionId && record.tool) {
980
+ sessionId = record.sessionId;
999
981
  descriptionTool = record.tool;
1000
982
  toolLabel = toolDisplayName(descriptionTool);
1001
- // 确保 sessionInfoMap 中有该私聊的信息
983
+ // 确保内存状态在冷启动后恢复;bindChatToSession 是幂等的。
1002
984
  if (!sessionInfoMap.has(chatId)) {
1003
985
  sessionInfoMap.set(chatId, {
1004
986
  sessionId,
@@ -1008,8 +990,8 @@ export async function handleCommand(
1008
990
  tool: descriptionTool,
1009
991
  });
1010
992
  }
1011
- bindChatToSession(sessionId, chatId);
1012
- }
993
+ bindChatToSession(sessionId, chatId);
994
+ }
1013
995
  } catch (err) {
1014
996
  console.log(
1015
997
  `[${ts()}] [INFO] Cannot load registry for p2p ${chatId}: ${(err as Error).message}`,
@@ -1060,11 +1042,11 @@ export async function handleCommand(
1060
1042
  }
1061
1043
  }
1062
1044
 
1063
- // 微信 P2P:首条非指令消息 → 更新 registry 中的会话名
1064
- if (
1065
- chatType === "p2p" &&
1066
- platform.kind === "wechat" &&
1067
- !isCommandText
1045
+ // P2P:首条非指令消息只更新 registry 中的展示名,不修改私聊信息。
1046
+ if (
1047
+ chatType === "p2p" &&
1048
+ (platform.kind === "wechat" || platform.kind === "feishu") &&
1049
+ !isCommandText
1068
1050
  ) {
1069
1051
  try {
1070
1052
  const reg = await loadSessionRegistryForBinding();
@@ -1093,12 +1075,12 @@ export async function handleCommand(
1093
1075
  () => {},
1094
1076
  );
1095
1077
  console.log(
1096
- `[${ts()}] [RENAME] WeChat P2P → "${newName2}"`,
1078
+ `[${ts()}] [RENAME] ${platform.kind} P2P → "${newName2}"`,
1097
1079
  );
1098
1080
  }
1099
1081
  } catch (err) {
1100
1082
  console.error(
1101
- `[${ts()}] [RENAME] WeChat P2P failed: ${(err as Error).message}`,
1083
+ `[${ts()}] [RENAME] ${platform.kind} P2P failed: ${(err as Error).message}`,
1102
1084
  );
1103
1085
  }
1104
1086
  }
@@ -1211,10 +1193,11 @@ export async function handleCommand(
1211
1193
  const allSessions = await getAllSessionsStatus();
1212
1194
  const now = Date.now();
1213
1195
  const cardData = allSessions.map((s) => ({
1214
- sessionId: s.sessionId,
1215
- chatName: s.chatName,
1216
- chatId: s.chatId,
1217
- active: s.active,
1196
+ sessionId: s.sessionId,
1197
+ chatName: s.chatName,
1198
+ chatId: s.chatId,
1199
+ chatType: s.chatType,
1200
+ active: s.active,
1218
1201
  turnCount: s.turnCount,
1219
1202
  elapsedSeconds: s.active
1220
1203
  ? Math.floor((now - s.startTime) / 1000)
@@ -1222,7 +1205,10 @@ export async function handleCommand(
1222
1205
  model: s.model,
1223
1206
  tool: s.tool,
1224
1207
  }));
1225
- const card = buildSessionsCard(cardData, { defaultToolLabel: toolDisplayName(resolveDefaultAgentTool()) });
1208
+ const card = buildSessionsCard(cardData, {
1209
+ defaultToolLabel: toolDisplayName(resolveDefaultAgentTool()),
1210
+ fixedPrivateSession: isFeishuP2p(platform, chatType),
1211
+ });
1226
1212
  const ok = await platform.sendRawCard(chatId, card);
1227
1213
  console.log(
1228
1214
  `[${ts()}] [SESSIONS] card sent, ok=${ok}, count=${cardData.length}`,
@@ -1231,16 +1217,22 @@ export async function handleCommand(
1231
1217
  return;
1232
1218
  }
1233
1219
 
1234
- if (isCommandText && textLower === "/newh") {
1235
- logTrace(tid, "BRANCH", { cmd: "/newh" });
1236
- const adapter = getAdapterForTool(descriptionTool, sessionId);
1237
- let cwd: string;
1238
- try {
1239
- const info = await adapter.getSessionInfo(sessionId);
1240
- cwd = info?.cwd ?? (await getDefaultCwd(chatId));
1241
- } catch {
1242
- cwd = await getDefaultCwd(chatId);
1243
- }
1220
+ if (isCommandText && textLower === "/newh") {
1221
+ logTrace(tid, "BRANCH", { cmd: "/newh" });
1222
+ let cwd: string;
1223
+ if (isFeishuP2p(platform, chatType)) {
1224
+ // 飞书私聊不支持切换工作目录。即使 /cd 已为后续 /new 群聊
1225
+ // 保存了其他默认目录,/newh 仍必须在运行 ChatCCC 的用户目录重建。
1226
+ cwd = homedir();
1227
+ } else {
1228
+ const adapter = getAdapterForTool(descriptionTool, sessionId);
1229
+ try {
1230
+ const info = await adapter.getSessionInfo(sessionId);
1231
+ cwd = info?.cwd ?? (await getDefaultCwd(chatId));
1232
+ } catch {
1233
+ cwd = await getDefaultCwd(chatId);
1234
+ }
1235
+ }
1244
1236
 
1245
1237
  // 第一步:创建新 session(此时尚未碰任何内存绑定,失败可直接返回,
1246
1238
  // 旧 session 状态完全保留)。
@@ -1299,13 +1291,13 @@ export async function handleCommand(
1299
1291
  .setChatAvatar(chatId, descriptionTool, "new")
1300
1292
  .catch(() => {});
1301
1293
 
1302
- await platform.sendCard(
1303
- chatId,
1304
- `${toolLabel} Session Reset`,
1305
- `会话已重置为新的 **${toolLabel}** 会话。\n\n` +
1306
- `**Session ID:** ${newSessionId}\n` +
1307
- `**工作目录:** \`${cwd}\`(沿用当前会话目录)\n\n` +
1308
- `直接在这里发消息即可继续对话。\n` +
1294
+ await platform.sendCard(
1295
+ chatId,
1296
+ `${toolLabel} Session Reset`,
1297
+ `会话已重置为新的 **${toolLabel}** 会话。\n\n` +
1298
+ `**Session ID:** ${newSessionId}\n` +
1299
+ `**工作目录:** \`${cwd}\`${isFeishuP2p(platform, chatType) ? "(飞书私聊固定使用系统用户目录)" : "(沿用当前会话目录)"}\n\n` +
1300
+ `直接在这里发消息即可继续对话。\n` +
1309
1301
  `发送 **/cd** 可切换新建会话的默认目录。\n` +
1310
1302
  `发送 **/model** 查看或切换当前会话的模型。`,
1311
1303
  "green",
@@ -1356,9 +1348,22 @@ export async function handleCommand(
1356
1348
  }
1357
1349
 
1358
1350
  // /session <number>:切换到 /sessions 列表中的指定会话
1359
- const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
1360
- if (sessionMatch) {
1361
- const index = parseInt(sessionMatch[1], 10) - 1;
1351
+ const sessionMatch = isCommandText ? textLower.match(/^\/session\s+(\d+)$/) : null;
1352
+ if (sessionMatch) {
1353
+ // 飞书私聊有自己唯一的常驻 session;历史群聊 session 只能回到对应群聊
1354
+ // 继续,禁止通过 /session 把它们重新绑定进私聊。
1355
+ if (isFeishuP2p(platform, chatType)) {
1356
+ await platform.sendCard(
1357
+ chatId,
1358
+ "/session",
1359
+ "飞书私聊使用固定的专属会话,不能切换到历史群聊会话。请回到对应群聊继续,或发送 /new 新建群聊。",
1360
+ "yellow",
1361
+ );
1362
+ logTrace(tid, "DONE", { outcome: "session_switch_disabled_feishu_p2p" });
1363
+ return;
1364
+ }
1365
+
1366
+ const index = parseInt(sessionMatch[1], 10) - 1;
1362
1367
  logTrace(tid, "BRANCH", { cmd: "/session", index: index + 1 });
1363
1368
  const allSessions = await getAllSessionsStatus();
1364
1369
  const claudeOrdered = allSessions.filter(
@@ -1555,96 +1560,96 @@ export async function handleCommand(
1555
1560
  }
1556
1561
 
1557
1562
  // /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")) {
1563
+ if (isCommandText && textLower === "/effort clear") {
1564
+ logTrace(tid, "BRANCH", { cmd: "/effort clear", sessionId, tool: descriptionTool });
1565
+ const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
1566
+ const toolLabel = toolDisplayName(descriptionTool);
1567
+ if (efforts.length === 0) {
1568
+ const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
1569
+ await (platform.kind === "wechat"
1570
+ ? platform.sendText(chatId, msg)
1571
+ : platform.sendCard(chatId, "Effort 切换", msg, "red")
1572
+ ).catch(() => {});
1573
+ logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
1574
+ return;
1575
+ }
1576
+
1577
+ clearSessionEffortOverride(sessionId);
1578
+ const defaultEffort = getDefaultEffortForTool(descriptionTool as AgentTool);
1579
+ const msg = `已清除当前 ${toolLabel} 会话的 effort 覆盖,恢复使用: \`${defaultEffort || "(未指定)"}\``;
1580
+ await (platform.kind === "wechat"
1581
+ ? platform.sendText(chatId, msg)
1582
+ : platform.sendCard(chatId, "Effort 切换", msg, "green")
1583
+ ).catch(() => {});
1584
+ logTrace(tid, "DONE", { outcome: "effort_cleared", sessionId, tool: descriptionTool });
1585
+ return;
1586
+ }
1587
+
1588
+ if (isCommandText && textLower.startsWith("/effort ")) {
1589
+ const effortArg = text.slice(8).trim();
1590
+ if (!effortArg) return;
1591
+ logTrace(tid, "BRANCH", { cmd: "/effort", arg: effortArg, sessionId, tool: descriptionTool });
1592
+
1593
+ const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
1594
+ const toolLabel = toolDisplayName(descriptionTool);
1595
+ if (efforts.length === 0) {
1596
+ const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
1597
+ await (platform.kind === "wechat"
1598
+ ? platform.sendText(chatId, msg)
1599
+ : platform.sendCard(chatId, "Effort 切换", msg, "red")
1600
+ ).catch(() => {});
1601
+ logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
1602
+ return;
1603
+ }
1604
+
1605
+ const target = findModelMatch(effortArg, efforts);
1606
+ if (!target) {
1607
+ const msg = `未找到匹配 "${effortArg}" 的 effort。当前 ${toolLabel} 可选 effort:\n${efforts.map(e => ` \`${e}\``).join("\n")}`;
1608
+ await (platform.kind === "wechat"
1609
+ ? platform.sendText(chatId, msg)
1610
+ : platform.sendCard(chatId, "Effort 切换", msg, "red")
1611
+ ).catch(() => {});
1612
+ logTrace(tid, "DONE", { outcome: "effort_not_found", arg: effortArg, tool: descriptionTool });
1613
+ return;
1614
+ }
1615
+
1616
+ setSessionEffortOverride(sessionId, target);
1617
+ const msg = `已切换当前 ${toolLabel} 会话 effort 为: \`${target}\``;
1618
+ await (platform.kind === "wechat"
1619
+ ? platform.sendText(chatId, msg)
1620
+ : platform.sendCard(chatId, "Effort 切换", msg, "green")
1621
+ ).catch(() => {});
1622
+ logTrace(tid, "DONE", { outcome: "effort_switched", arg: effortArg, target, sessionId, tool: descriptionTool });
1623
+ return;
1624
+ }
1625
+
1626
+ if (isCommandText && textLower === "/effort") {
1627
+ logTrace(tid, "BRANCH", { cmd: "/effort", sessionId, tool: descriptionTool });
1628
+ const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
1629
+ const currentEffort = getEffectiveEffortForTool(descriptionTool, sessionId);
1630
+ const toolLabel = toolDisplayName(descriptionTool);
1631
+
1632
+ if (efforts.length === 0) {
1633
+ const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
1634
+ await (platform.kind === "wechat"
1635
+ ? platform.sendText(chatId, msg)
1636
+ : platform.sendCard(chatId, "Effort 切换", msg, "red")
1637
+ ).catch(() => {});
1638
+ } else if (platform.kind === "wechat") {
1639
+ const lines = [currentEffort ? `当前 effort (${toolLabel}): ${currentEffort}` : `当前 effort (${toolLabel}): 未指定`];
1640
+ lines.push("", "可切换 effort:");
1641
+ for (const e of efforts) lines.push(` ${e}`);
1642
+ lines.push("", "输入 /effort <effort> 切换 effort");
1643
+ await platform.sendText(chatId, lines.join("\n")).catch(() => {});
1644
+ } else {
1645
+ const card = buildEffortCard(currentEffort, efforts, descriptionTool);
1646
+ await platform.sendRawCard(chatId, card);
1647
+ }
1648
+ logTrace(tid, "DONE", { outcome: "effort_query", tool: descriptionTool });
1649
+ return;
1650
+ }
1651
+
1652
+ if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
1648
1653
  const args = text === "/git" ? "" : text.slice(5).trim();
1649
1654
  logTrace(tid, "BRANCH", { cmd: "/git", args: args || "(none)" });
1650
1655
  if (!args) {
@@ -1802,53 +1807,54 @@ export async function handleCommand(
1802
1807
  }
1803
1808
 
1804
1809
  // 无会话上下文 → /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") {
1810
+ if (isCommandText && textLower === "/effort") {
1811
+ const defaultTool = resolveDefaultAgentTool();
1812
+ const efforts = getAllEffortsForTool(defaultTool);
1813
+ const currentEffort = getDefaultEffortForTool(defaultTool);
1814
+ const toolLabel = toolDisplayName(defaultTool);
1815
+
1816
+ if (efforts.length === 0) {
1817
+ const msg = `当前默认 agent (${toolLabel}) 不支持 effort 切换。`;
1818
+ await (platform.kind === "wechat"
1819
+ ? platform.sendText(chatId, msg)
1820
+ : platform.sendCard(chatId, "Effort 切换", msg, "red")
1821
+ ).catch(() => {});
1822
+ } else if (platform.kind === "wechat") {
1823
+ const lines = [currentEffort ? `当前默认 effort (${toolLabel}): ${currentEffort}` : `当前默认 effort (${toolLabel}): 未指定`];
1824
+ lines.push("", "可切换 effort:");
1825
+ for (const e of efforts) lines.push(` ${e}`);
1826
+ lines.push("", "在会话中输入 /effort <effort> 切换 effort");
1827
+ await platform.sendText(chatId, lines.join("\n")).catch(() => {});
1828
+ } else {
1829
+ const card = buildEffortCard(currentEffort, efforts, defaultTool);
1830
+ await platform.sendRawCard(chatId, card);
1831
+ }
1832
+ logTrace(tid, "DONE", { outcome: "effort_query", defaultTool });
1833
+ return;
1834
+ }
1835
+
1836
+ if (isCommandText && textLower.startsWith("/effort ")) {
1837
+ const defaultTool = resolveDefaultAgentTool();
1838
+ const toolLabel = toolDisplayName(defaultTool);
1839
+ const msg = `当前没有绑定会话。请先进入 Claude/Codex 会话,再输入 /effort <effort> 切换当前会话的 effort。当前默认 agent: ${toolLabel}`;
1840
+ await (platform.kind === "wechat"
1841
+ ? platform.sendText(chatId, msg)
1842
+ : platform.sendCard(chatId, "Effort 切换", msg, "yellow")
1843
+ ).catch(() => {});
1844
+ logTrace(tid, "DONE", { outcome: "effort_no_session", defaultTool });
1845
+ return;
1846
+ }
1847
+
1848
+ if (isCommandText && textLower === "/sessions") {
1844
1849
  logTrace(tid, "BRANCH", { cmd: "/sessions", scope: "global" });
1845
1850
  const allSessions = await getAllSessionsStatus();
1846
1851
  const now = Date.now();
1847
1852
  const cardData = allSessions.map((s) => ({
1848
- sessionId: s.sessionId,
1849
- chatName: s.chatName,
1850
- chatId: s.chatId,
1851
- active: s.active,
1853
+ sessionId: s.sessionId,
1854
+ chatName: s.chatName,
1855
+ chatId: s.chatId,
1856
+ chatType: s.chatType,
1857
+ active: s.active,
1852
1858
  turnCount: s.turnCount,
1853
1859
  elapsedSeconds: s.active
1854
1860
  ? Math.floor((now - s.startTime) / 1000)
@@ -1856,7 +1862,10 @@ export async function handleCommand(
1856
1862
  model: s.model,
1857
1863
  tool: s.tool,
1858
1864
  }));
1859
- const card = buildSessionsCard(cardData, { defaultToolLabel: toolDisplayName(resolveDefaultAgentTool()) });
1865
+ const card = buildSessionsCard(cardData, {
1866
+ defaultToolLabel: toolDisplayName(resolveDefaultAgentTool()),
1867
+ fixedPrivateSession: isFeishuP2p(platform, chatType),
1868
+ });
1860
1869
  const ok = await platform.sendRawCard(chatId, card);
1861
1870
  console.log(
1862
1871
  `[${ts()}] [SESSIONS] card sent, ok=${ok}, count=${cardData.length}`,
@@ -1865,53 +1874,63 @@ export async function handleCommand(
1865
1874
  return;
1866
1875
  }
1867
1876
 
1868
- // 飞书私聊普通消息:不再绑定私聊本身,而是自动创建会话群并把私聊内容作为首轮 prompt。
1869
- if (isNonWechatP2p(platform, chatType) && !isCommandText) {
1870
- const tool = resolveDefaultAgentTool();
1871
- const toolLabel = toolDisplayName(tool);
1872
- logTrace(tid, "BRANCH", { cmd: "auto_new_from_p2p", tool });
1873
-
1874
- if (!openId) {
1875
- logTrace(tid, "DONE", { outcome: "auto_new_p2p_no_openid" });
1876
- await platform.sendCard(
1877
- chatId,
1878
- "Error",
1879
- "Cannot identify sender.",
1880
- "red",
1881
- );
1882
- return;
1883
- }
1884
-
1885
- try {
1886
- const cwd = await getDefaultCwd(chatId);
1887
- const result = await delegateAgentTask({
1888
- platform,
1889
- tool,
1890
- cwd,
1891
- promptText,
1892
- openIds: [openId],
1893
- chatNamePrefix: text.slice(0, 10) || "新会话",
1894
- msgTimestamp,
1895
- traceId: tid,
1896
- });
1897
- logTrace(tid, "DONE", {
1898
- outcome: "auto_new_p2p_prompt_done",
1899
- newChatId: result.chatId,
1900
- sessionId: result.sessionId,
1901
- tool,
1902
- });
1903
- } catch (err) {
1904
- console.error(`[${ts()}] [AUTO-P2P] FAIL: ${(err as Error).message}`);
1905
- logTrace(tid, "DONE", {
1906
- outcome: "auto_new_p2p_delegate_fail",
1907
- error: (err as Error).message,
1908
- });
1909
- await platform.sendCard(
1910
- chatId,
1911
- "Error",
1912
- `Failed to create ${toolLabel} delegated task:\n${(err as Error).message}`,
1913
- "red",
1914
- );
1877
+ // 飞书私聊普通消息:首次使用时在当前私聊创建并持久化一个专属 session,
1878
+ // 随后的消息会在上面的 registry 路由中继续该 session。只有显式 /new 才建群。
1879
+ if (isFeishuP2p(platform, chatType) && !isCommandText) {
1880
+ const tool = resolveDefaultAgentTool();
1881
+ const toolLabel = toolDisplayName(tool);
1882
+ // 私聊 cwd 故意不读取 /cd 的 chatId 默认值:/cd 只为之后显式
1883
+ // /new 创建的群聊服务,飞书私聊始终从 ChatCCC 运行账号的用户目录启动。
1884
+ const cwd = homedir();
1885
+ logTrace(tid, "BRANCH", { cmd: "auto_new_feishu_p2p", tool, cwd });
1886
+
1887
+ try {
1888
+ const init = await initClaudeSession(tool, cwd);
1889
+ const sessionId = init.sessionId;
1890
+ const chatName = sessionChatName(text.slice(0, 10) || "私聊会话", cwd);
1891
+ const switchResult = await switchChatBinding({
1892
+ chatId,
1893
+ chatType,
1894
+ oldSessionId: null,
1895
+ newSessionId: sessionId,
1896
+ tool,
1897
+ chatName,
1898
+ newDescription: `${sessionPrefixForTool(tool)} ${sessionId}`,
1899
+ updateChatInfoFn: (cid, name, desc) =>
1900
+ platform.updateChatInfo(cid, name, desc),
1901
+ });
1902
+ if (!switchResult.ok) {
1903
+ throw switchResult.error ?? new Error("Failed to bind Feishu private session");
1904
+ }
1905
+
1906
+ await resumeAndPrompt(
1907
+ sessionId,
1908
+ promptText,
1909
+ platform,
1910
+ chatId,
1911
+ msgTimestamp,
1912
+ tool,
1913
+ tid,
1914
+ );
1915
+ logTrace(tid, "DONE", {
1916
+ outcome: "auto_new_feishu_p2p_prompt_done",
1917
+ chatId,
1918
+ sessionId,
1919
+ tool,
1920
+ cwd,
1921
+ });
1922
+ } catch (err) {
1923
+ console.error(`[${ts()}] [AUTO-P2P] FAIL: ${(err as Error).message}`);
1924
+ logTrace(tid, "DONE", {
1925
+ outcome: "auto_new_feishu_p2p_fail",
1926
+ error: (err as Error).message,
1927
+ });
1928
+ await platform.sendCard(
1929
+ chatId,
1930
+ "Error",
1931
+ `Failed to create ${toolLabel} private session:\n${(err as Error).message}`,
1932
+ "red",
1933
+ );
1915
1934
  }
1916
1935
  return;
1917
1936
  }