chatccc 0.2.47 → 0.2.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,55 +1,57 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
- import { getPlatform, setPlatform, getTenantAccessToken, getChatInfo, createGroupChat, updateChatInfo, sendTextReply, sendCardReply, extractSessionInfo, formatDelayNotice, reportPermissionResults, verifyAllPermissions, addReaction, recallMessage, updateCardMessage, setChatAvatar, sendRestartCard } from "../feishu-platform.ts";
3
- import type { FeishuPlatform } from "../feishu-platform.ts";
4
-
5
- const realPlatform = getPlatform();
6
-
7
- describe("feishu-platform", () => {
8
- it("默认使用真实实现", () => {
9
- expect(realPlatform).toBeDefined();
10
- expect(typeof realPlatform.getTenantAccessToken).toBe("function");
11
- expect(typeof realPlatform.sendTextReply).toBe("function");
12
- });
13
-
14
- it("setPlatform 可替换实现,包装函数委托到新实现", async () => {
15
- const mock: FeishuPlatform = {
16
- getTenantAccessToken: async () => "mock_token",
17
- sendTextReply: async () => true,
18
- sendCardReply: async () => true,
19
- sendImageReply: async () => true,
20
- sendFileReply: async () => true,
21
- addReaction: async () => {},
22
- recallMessage: async () => false,
23
- updateCardMessage: async () => true,
24
- createGroupChat: async () => "mock_chat",
25
- updateChatInfo: async () => {},
26
- getChatInfo: async () => ({ name: "x", description: "y" }),
27
- setChatAvatar: async () => {},
28
- getOrDownloadImage: async () => "/tmp/img.png",
29
- verifyAllPermissions: async () => [],
30
- reportPermissionResults: realPlatform.reportPermissionResults,
31
- extractSessionInfo: realPlatform.extractSessionInfo,
32
- extractSessionId: realPlatform.extractSessionId,
33
- formatDelayNotice: realPlatform.formatDelayNotice,
34
- sendRestartCard: async () => {},
35
- };
36
-
37
- setPlatform(mock);
38
- try {
39
- expect(await getTenantAccessToken()).toBe("mock_token");
40
- expect(await sendTextReply("t", "c", "hi")).toBe(true);
41
- expect(await createGroupChat("t", "g", [])).toBe("mock_chat");
42
- expect(await getChatInfo("t", "mock_chat")).toEqual({ name: "x", description: "y" });
43
- const perms = await verifyAllPermissions("t");
44
- expect(perms).toEqual([]);
45
- } finally {
46
- // 恢复到真实实现,避免影响后续测试
47
- setPlatform(realPlatform);
48
- }
49
- });
50
-
51
- it("恢复真实实现后函数正常工作", async () => {
52
- setPlatform(realPlatform);
53
- expect(getPlatform()).toBe(realPlatform);
54
- });
1
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
+ import { getPlatform, setPlatform, getTenantAccessToken, getChatInfo, createGroupChat, updateChatInfo, sendTextReply, sendCardReply, sendRawCard, extractSessionInfo, formatDelayNotice, reportPermissionResults, verifyAllPermissions, addReaction, recallMessage, updateCardMessage, setChatAvatar, disbandChat, sendRestartCard } from "../feishu-platform.ts";
3
+ import type { FeishuPlatform } from "../feishu-platform.ts";
4
+
5
+ const realPlatform = getPlatform();
6
+
7
+ describe("feishu-platform", () => {
8
+ it("默认使用真实实现", () => {
9
+ expect(realPlatform).toBeDefined();
10
+ expect(typeof realPlatform.getTenantAccessToken).toBe("function");
11
+ expect(typeof realPlatform.sendTextReply).toBe("function");
12
+ });
13
+
14
+ it("setPlatform 可替换实现,包装函数委托到新实现", async () => {
15
+ const mock: FeishuPlatform = {
16
+ getTenantAccessToken: async () => "mock_token",
17
+ sendTextReply: async () => true,
18
+ sendCardReply: async () => true,
19
+ sendRawCard: async () => true,
20
+ sendImageReply: async () => true,
21
+ sendFileReply: async () => true,
22
+ addReaction: async () => {},
23
+ recallMessage: async () => false,
24
+ updateCardMessage: async () => true,
25
+ createGroupChat: async () => "mock_chat",
26
+ updateChatInfo: async () => {},
27
+ getChatInfo: async () => ({ name: "x", description: "y" }),
28
+ disbandChat: async () => {},
29
+ setChatAvatar: async () => {},
30
+ getOrDownloadImage: async () => "/tmp/img.png",
31
+ verifyAllPermissions: async () => [],
32
+ reportPermissionResults: realPlatform.reportPermissionResults,
33
+ extractSessionInfo: realPlatform.extractSessionInfo,
34
+ extractSessionId: realPlatform.extractSessionId,
35
+ formatDelayNotice: realPlatform.formatDelayNotice,
36
+ sendRestartCard: async () => {},
37
+ };
38
+
39
+ setPlatform(mock);
40
+ try {
41
+ expect(await getTenantAccessToken()).toBe("mock_token");
42
+ expect(await sendTextReply("t", "c", "hi")).toBe(true);
43
+ expect(await createGroupChat("t", "g", [])).toBe("mock_chat");
44
+ expect(await getChatInfo("t", "mock_chat")).toEqual({ name: "x", description: "y" });
45
+ const perms = await verifyAllPermissions("t");
46
+ expect(perms).toEqual([]);
47
+ } finally {
48
+ // 恢复到真实实现,避免影响后续测试
49
+ setPlatform(realPlatform);
50
+ }
51
+ });
52
+
53
+ it("恢复真实实现后函数正常工作", async () => {
54
+ setPlatform(realPlatform);
55
+ expect(getPlatform()).toBe(realPlatform);
56
+ });
55
57
  });
@@ -237,6 +237,7 @@ describe("getAllSessionsStatus", () => {
237
237
  beforeEach(async () => {
238
238
  chatSessionMap.clear();
239
239
  sessionInfoMap.clear();
240
+ activePrompts.clear();
240
241
  const dir = await mkdtemp(join(tmpdir(), "chatccc-session-registry-"));
241
242
  registryFile = join(dir, "session-registry.json");
242
243
  _setSessionRegistryFileForTest(registryFile);
@@ -337,6 +338,7 @@ describe("getAllSessionsStatus", () => {
337
338
  running: false, // registry says false, but activePrompts wins
338
339
  updatedAt: 1000,
339
340
  });
341
+ mockSessionInfo("chat1", { sessionId: "s1" });
340
342
  mockActiveSession("chat1");
341
343
 
342
344
  const result = await getAllSessionsStatus();
package/src/cards.ts CHANGED
@@ -255,6 +255,7 @@ export function buildCdCard(
255
255
  export function buildSessionsCard(sessions: Array<{
256
256
  sessionId: string;
257
257
  chatName: string;
258
+ chatId: string;
258
259
  active: boolean;
259
260
  turnCount: number;
260
261
  elapsedSeconds: number | null;
@@ -293,7 +294,8 @@ export function buildSessionsCard(sessions: Array<{
293
294
  }
294
295
  const toolLabel = s.tool === "cursor" ? "Cursor" : s.tool === "codex" ? "Codex" : "Claude Code";
295
296
  const namePart = s.chatName ? `**${s.chatName}** ` : "";
296
- return `**${i + 1}.** ${namePart}\`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
297
+ const groupTag = s.chatId && s.chatId.startsWith("oc_") ? " (群聊)" : "";
298
+ return `**${i + 1}.** ${namePart}${groupTag} \`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
297
299
  };
298
300
 
299
301
  const lines: string[] = [`共 **${sessions.length}** 个会话:`, ""];
package/src/feishu-api.ts CHANGED
@@ -236,6 +236,18 @@ export async function getChatInfo(
236
236
  };
237
237
  }
238
238
 
239
+ export async function disbandChat(
240
+ token: string,
241
+ chatId: string
242
+ ): Promise<void> {
243
+ const resp = await fetch(`${BASE_URL}/im/v1/chats/${chatId}`, {
244
+ method: "DELETE",
245
+ headers: { Authorization: `Bearer ${token}` },
246
+ });
247
+ const data = (await resp.json()) as { code: number; msg?: string };
248
+ if (data.code !== 0) throw new Error(`[${data.code}] ${data.msg}`);
249
+ }
250
+
239
251
  export function extractSessionInfo(description: string): { sessionId: string; tool: string } | null {
240
252
  const PREFIXES: Array<{ prefix: string; tool: string }> = [
241
253
  { prefix: CLAUDE_SESSION_PREFIX, tool: "claude" },
@@ -1,134 +1,139 @@
1
- /**
2
- * feishu-platform.ts — 可替换的飞书 API 实现层
3
- *
4
- * 默认情况下所有函数直接委托给 feishu-api.ts(真实飞书 API)。
5
- * 在 --simulate 模式下通过 setPlatform() 整体替换为 SimulatedPlatform。
6
- *
7
- * 设计:每个导出函数都是一个"通过 _impl 代理"的包装器,
8
- * 消费者不需要感知底层是真实飞书还是模拟实现。
9
- */
10
-
11
- import * as realApi from "./feishu-api.ts";
12
-
13
- // ---------------------------------------------------------------------------
14
- // 平台接口:覆盖 feishu-api.ts 的所有公开导出函数签名
15
- // ---------------------------------------------------------------------------
16
-
17
- export interface FeishuPlatform {
18
- getTenantAccessToken: typeof realApi.getTenantAccessToken;
19
- sendTextReply: typeof realApi.sendTextReply;
20
- sendCardReply: typeof realApi.sendCardReply;
21
- sendRawCard: typeof realApi.sendRawCard;
22
- sendImageReply: typeof realApi.sendImageReply;
23
- sendFileReply: typeof realApi.sendFileReply;
24
- addReaction: typeof realApi.addReaction;
25
- recallMessage: typeof realApi.recallMessage;
26
- updateCardMessage: typeof realApi.updateCardMessage;
27
- createGroupChat: typeof realApi.createGroupChat;
28
- updateChatInfo: typeof realApi.updateChatInfo;
29
- getChatInfo: typeof realApi.getChatInfo;
30
- setChatAvatar: typeof realApi.setChatAvatar;
31
- getOrDownloadImage: typeof realApi.getOrDownloadImage;
32
- verifyAllPermissions: typeof realApi.verifyAllPermissions;
33
- reportPermissionResults: typeof realApi.reportPermissionResults;
34
- extractSessionInfo: typeof realApi.extractSessionInfo;
35
- extractSessionId: typeof realApi.extractSessionId;
36
- formatDelayNotice: typeof realApi.formatDelayNotice;
37
- sendRestartCard: typeof realApi.sendRestartCard;
38
- }
39
-
40
- let _impl: FeishuPlatform = realApi;
41
-
42
- /** 替换当前平台实现(模拟模式入口) */
43
- export function setPlatform(impl: FeishuPlatform): void {
44
- _impl = impl;
45
- }
46
-
47
- /** 获取当前平台实现(仅供诊断/测试) */
48
- export function getPlatform(): FeishuPlatform {
49
- return _impl;
50
- }
51
-
52
- // ---------------------------------------------------------------------------
53
- // 包装器:每个函数直接委托到 _impl,签名与原函数完全一致
54
- // ---------------------------------------------------------------------------
55
-
56
- export function getTenantAccessToken(): ReturnType<typeof realApi.getTenantAccessToken> {
57
- return _impl.getTenantAccessToken();
58
- }
59
-
60
- export function sendTextReply(...args: Parameters<typeof realApi.sendTextReply>): ReturnType<typeof realApi.sendTextReply> {
61
- return _impl.sendTextReply(...args);
62
- }
63
-
64
- export function sendCardReply(...args: Parameters<typeof realApi.sendCardReply>): ReturnType<typeof realApi.sendCardReply> {
65
- return _impl.sendCardReply(...args);
66
- }
67
-
68
- export function sendRawCard(...args: Parameters<typeof realApi.sendRawCard>): ReturnType<typeof realApi.sendRawCard> {
69
- return _impl.sendRawCard(...args);
70
- }
71
-
72
- export function sendImageReply(...args: Parameters<typeof realApi.sendImageReply>): ReturnType<typeof realApi.sendImageReply> {
73
- return _impl.sendImageReply(...args);
74
- }
75
-
76
- export function sendFileReply(...args: Parameters<typeof realApi.sendFileReply>): ReturnType<typeof realApi.sendFileReply> {
77
- return _impl.sendFileReply(...args);
78
- }
79
-
80
- export function addReaction(...args: Parameters<typeof realApi.addReaction>): ReturnType<typeof realApi.addReaction> {
81
- return _impl.addReaction(...args);
82
- }
83
-
84
- export function recallMessage(...args: Parameters<typeof realApi.recallMessage>): ReturnType<typeof realApi.recallMessage> {
85
- return _impl.recallMessage(...args);
86
- }
87
-
88
- export function updateCardMessage(...args: Parameters<typeof realApi.updateCardMessage>): ReturnType<typeof realApi.updateCardMessage> {
89
- return _impl.updateCardMessage(...args);
90
- }
91
-
92
- export function createGroupChat(...args: Parameters<typeof realApi.createGroupChat>): ReturnType<typeof realApi.createGroupChat> {
93
- return _impl.createGroupChat(...args);
94
- }
95
-
96
- export function updateChatInfo(...args: Parameters<typeof realApi.updateChatInfo>): ReturnType<typeof realApi.updateChatInfo> {
97
- return _impl.updateChatInfo(...args);
98
- }
99
-
100
- export function getChatInfo(...args: Parameters<typeof realApi.getChatInfo>): ReturnType<typeof realApi.getChatInfo> {
101
- return _impl.getChatInfo(...args);
102
- }
103
-
104
- export function setChatAvatar(...args: Parameters<typeof realApi.setChatAvatar>): ReturnType<typeof realApi.setChatAvatar> {
105
- return _impl.setChatAvatar(...args);
106
- }
107
-
108
- export function getOrDownloadImage(...args: Parameters<typeof realApi.getOrDownloadImage>): ReturnType<typeof realApi.getOrDownloadImage> {
109
- return _impl.getOrDownloadImage(...args);
110
- }
111
-
112
- export function verifyAllPermissions(...args: Parameters<typeof realApi.verifyAllPermissions>): ReturnType<typeof realApi.verifyAllPermissions> {
113
- return _impl.verifyAllPermissions(...args);
114
- }
115
-
116
- export function reportPermissionResults(...args: Parameters<typeof realApi.reportPermissionResults>): ReturnType<typeof realApi.reportPermissionResults> {
117
- return _impl.reportPermissionResults(...args);
118
- }
119
-
120
- export function extractSessionInfo(...args: Parameters<typeof realApi.extractSessionInfo>): ReturnType<typeof realApi.extractSessionInfo> {
121
- return _impl.extractSessionInfo(...args);
122
- }
123
-
124
- export function extractSessionId(...args: Parameters<typeof realApi.extractSessionId>): ReturnType<typeof realApi.extractSessionId> {
125
- return _impl.extractSessionId(...args);
126
- }
127
-
128
- export function formatDelayNotice(...args: Parameters<typeof realApi.formatDelayNotice>): ReturnType<typeof realApi.formatDelayNotice> {
129
- return _impl.formatDelayNotice(...args);
130
- }
131
-
132
- export function sendRestartCard(...args: Parameters<typeof realApi.sendRestartCard>): ReturnType<typeof realApi.sendRestartCard> {
133
- return _impl.sendRestartCard(...args);
1
+ /**
2
+ * feishu-platform.ts — 可替换的飞书 API 实现层
3
+ *
4
+ * 默认情况下所有函数直接委托给 feishu-api.ts(真实飞书 API)。
5
+ * 在 --simulate 模式下通过 setPlatform() 整体替换为 SimulatedPlatform。
6
+ *
7
+ * 设计:每个导出函数都是一个"通过 _impl 代理"的包装器,
8
+ * 消费者不需要感知底层是真实飞书还是模拟实现。
9
+ */
10
+
11
+ import * as realApi from "./feishu-api.ts";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // 平台接口:覆盖 feishu-api.ts 的所有公开导出函数签名
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export interface FeishuPlatform {
18
+ getTenantAccessToken: typeof realApi.getTenantAccessToken;
19
+ sendTextReply: typeof realApi.sendTextReply;
20
+ sendCardReply: typeof realApi.sendCardReply;
21
+ sendRawCard: typeof realApi.sendRawCard;
22
+ sendImageReply: typeof realApi.sendImageReply;
23
+ sendFileReply: typeof realApi.sendFileReply;
24
+ addReaction: typeof realApi.addReaction;
25
+ recallMessage: typeof realApi.recallMessage;
26
+ updateCardMessage: typeof realApi.updateCardMessage;
27
+ createGroupChat: typeof realApi.createGroupChat;
28
+ updateChatInfo: typeof realApi.updateChatInfo;
29
+ getChatInfo: typeof realApi.getChatInfo;
30
+ disbandChat: typeof realApi.disbandChat;
31
+ setChatAvatar: typeof realApi.setChatAvatar;
32
+ getOrDownloadImage: typeof realApi.getOrDownloadImage;
33
+ verifyAllPermissions: typeof realApi.verifyAllPermissions;
34
+ reportPermissionResults: typeof realApi.reportPermissionResults;
35
+ extractSessionInfo: typeof realApi.extractSessionInfo;
36
+ extractSessionId: typeof realApi.extractSessionId;
37
+ formatDelayNotice: typeof realApi.formatDelayNotice;
38
+ sendRestartCard: typeof realApi.sendRestartCard;
39
+ }
40
+
41
+ let _impl: FeishuPlatform = realApi;
42
+
43
+ /** 替换当前平台实现(模拟模式入口) */
44
+ export function setPlatform(impl: FeishuPlatform): void {
45
+ _impl = impl;
46
+ }
47
+
48
+ /** 获取当前平台实现(仅供诊断/测试) */
49
+ export function getPlatform(): FeishuPlatform {
50
+ return _impl;
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // 包装器:每个函数直接委托到 _impl,签名与原函数完全一致
55
+ // ---------------------------------------------------------------------------
56
+
57
+ export function getTenantAccessToken(): ReturnType<typeof realApi.getTenantAccessToken> {
58
+ return _impl.getTenantAccessToken();
59
+ }
60
+
61
+ export function sendTextReply(...args: Parameters<typeof realApi.sendTextReply>): ReturnType<typeof realApi.sendTextReply> {
62
+ return _impl.sendTextReply(...args);
63
+ }
64
+
65
+ export function sendCardReply(...args: Parameters<typeof realApi.sendCardReply>): ReturnType<typeof realApi.sendCardReply> {
66
+ return _impl.sendCardReply(...args);
67
+ }
68
+
69
+ export function sendRawCard(...args: Parameters<typeof realApi.sendRawCard>): ReturnType<typeof realApi.sendRawCard> {
70
+ return _impl.sendRawCard(...args);
71
+ }
72
+
73
+ export function sendImageReply(...args: Parameters<typeof realApi.sendImageReply>): ReturnType<typeof realApi.sendImageReply> {
74
+ return _impl.sendImageReply(...args);
75
+ }
76
+
77
+ export function sendFileReply(...args: Parameters<typeof realApi.sendFileReply>): ReturnType<typeof realApi.sendFileReply> {
78
+ return _impl.sendFileReply(...args);
79
+ }
80
+
81
+ export function addReaction(...args: Parameters<typeof realApi.addReaction>): ReturnType<typeof realApi.addReaction> {
82
+ return _impl.addReaction(...args);
83
+ }
84
+
85
+ export function recallMessage(...args: Parameters<typeof realApi.recallMessage>): ReturnType<typeof realApi.recallMessage> {
86
+ return _impl.recallMessage(...args);
87
+ }
88
+
89
+ export function updateCardMessage(...args: Parameters<typeof realApi.updateCardMessage>): ReturnType<typeof realApi.updateCardMessage> {
90
+ return _impl.updateCardMessage(...args);
91
+ }
92
+
93
+ export function createGroupChat(...args: Parameters<typeof realApi.createGroupChat>): ReturnType<typeof realApi.createGroupChat> {
94
+ return _impl.createGroupChat(...args);
95
+ }
96
+
97
+ export function updateChatInfo(...args: Parameters<typeof realApi.updateChatInfo>): ReturnType<typeof realApi.updateChatInfo> {
98
+ return _impl.updateChatInfo(...args);
99
+ }
100
+
101
+ export function getChatInfo(...args: Parameters<typeof realApi.getChatInfo>): ReturnType<typeof realApi.getChatInfo> {
102
+ return _impl.getChatInfo(...args);
103
+ }
104
+
105
+ export function disbandChat(...args: Parameters<typeof realApi.disbandChat>): ReturnType<typeof realApi.disbandChat> {
106
+ return _impl.disbandChat(...args);
107
+ }
108
+
109
+ export function setChatAvatar(...args: Parameters<typeof realApi.setChatAvatar>): ReturnType<typeof realApi.setChatAvatar> {
110
+ return _impl.setChatAvatar(...args);
111
+ }
112
+
113
+ export function getOrDownloadImage(...args: Parameters<typeof realApi.getOrDownloadImage>): ReturnType<typeof realApi.getOrDownloadImage> {
114
+ return _impl.getOrDownloadImage(...args);
115
+ }
116
+
117
+ export function verifyAllPermissions(...args: Parameters<typeof realApi.verifyAllPermissions>): ReturnType<typeof realApi.verifyAllPermissions> {
118
+ return _impl.verifyAllPermissions(...args);
119
+ }
120
+
121
+ export function reportPermissionResults(...args: Parameters<typeof realApi.reportPermissionResults>): ReturnType<typeof realApi.reportPermissionResults> {
122
+ return _impl.reportPermissionResults(...args);
123
+ }
124
+
125
+ export function extractSessionInfo(...args: Parameters<typeof realApi.extractSessionInfo>): ReturnType<typeof realApi.extractSessionInfo> {
126
+ return _impl.extractSessionInfo(...args);
127
+ }
128
+
129
+ export function extractSessionId(...args: Parameters<typeof realApi.extractSessionId>): ReturnType<typeof realApi.extractSessionId> {
130
+ return _impl.extractSessionId(...args);
131
+ }
132
+
133
+ export function formatDelayNotice(...args: Parameters<typeof realApi.formatDelayNotice>): ReturnType<typeof realApi.formatDelayNotice> {
134
+ return _impl.formatDelayNotice(...args);
135
+ }
136
+
137
+ export function sendRestartCard(...args: Parameters<typeof realApi.sendRestartCard>): ReturnType<typeof realApi.sendRestartCard> {
138
+ return _impl.sendRestartCard(...args);
134
139
  }
package/src/index.ts CHANGED
@@ -77,6 +77,7 @@ import {
77
77
  setChatAvatar,
78
78
  updateCardMessage,
79
79
  updateChatInfo,
80
+ disbandChat,
80
81
  getOrDownloadImage,
81
82
  sendRestartCard,
82
83
  verifyAllPermissions,
@@ -103,6 +104,7 @@ import {
103
104
  getAdapterForTool,
104
105
  stopSession,
105
106
  loadSessionRegistryForBinding,
107
+ removeSessionRegistryRecord,
106
108
  } from "./session.ts";
107
109
  import {
108
110
  bindChatToSession,
@@ -112,6 +114,7 @@ import {
112
114
  activePrompts,
113
115
  rebuildSessionChatsFromRegistry,
114
116
  displayCards,
117
+ recordLastActiveChat,
115
118
  } from "./session-chat-binding.ts";
116
119
  import { fixStaleStreamStates } from "./stream-state.ts";
117
120
 
@@ -685,6 +688,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
685
688
  const cardData = others.map(s => ({
686
689
  sessionId: s.sessionId,
687
690
  chatName: s.chatName,
691
+ chatId: s.chatId,
688
692
  active: s.active,
689
693
  turnCount: s.turnCount,
690
694
  elapsedSeconds: s.active ? Math.floor((now - s.startTime) / 1000) : null,
@@ -726,6 +730,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
726
730
 
727
731
  // 绑定新 session
728
732
  bindChatToSession(newSessionId, chatId);
733
+ recordLastActiveChat(newSessionId, chatId);
729
734
 
730
735
  const descPrefix = sessionPrefixForTool(descriptionTool);
731
736
  const newName = sessionChatName("新会话", cwd);
@@ -772,6 +777,35 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
772
777
  return;
773
778
  }
774
779
 
780
+ if (textLower === "/deleteg") {
781
+ logTrace(tid, "BRANCH", { cmd: "/deleteg" });
782
+ if (chatType === "p2p") {
783
+ await sendTextReply(freshToken, chatId, "私聊无法使用 /deleteg,该指令仅用于群聊。").catch(() => {});
784
+ logTrace(tid, "DONE", { outcome: "deleteg_p2p" });
785
+ return;
786
+ }
787
+ console.log(`[${ts()}] [DELETEG] Disbanding group chat ${chatId}, session=${sessionId}`);
788
+
789
+ // 先解绑 session(不删除 Agent 会话)
790
+ unbindChatFromSession(sessionId, chatId);
791
+ displayCards.delete(chatId);
792
+ sessionInfoMap.delete(chatId);
793
+ await removeSessionRegistryRecord(chatId);
794
+
795
+ await sendTextReply(freshToken, chatId, "群聊已解散,Agent 会话保留。").catch(() => {});
796
+
797
+ // 解散群聊(飞书 API)
798
+ try {
799
+ await disbandChat(freshToken, chatId);
800
+ console.log(`[${ts()}] [DELETEG] Group disbanded: ${chatId}`);
801
+ } catch (err) {
802
+ console.error(`[${ts()}] [DELETEG] Disband API failed: ${(err as Error).message}`);
803
+ }
804
+
805
+ logTrace(tid, "DONE", { outcome: "deleteg", chatId, sessionId });
806
+ return;
807
+ }
808
+
775
809
  // /session <number>:切换到 /sessions 列表中的指定会话
776
810
  const sessionMatch = textLower.match(/^\/session\s+(\d+)$/);
777
811
  if (sessionMatch) {
@@ -812,6 +846,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
812
846
 
813
847
  // 绑定到新 session
814
848
  bindChatToSession(target.sessionId, chatId);
849
+ recordLastActiveChat(target.sessionId, chatId);
815
850
 
816
851
  const descPrefix2 = sessionPrefixForTool(target.tool);
817
852
  const newName2 = target.chatName || sessionChatName("新会话", cwd2);