chatccc 0.2.46 → 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.46",
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);
@@ -288,7 +289,8 @@ describe("getAllSessionsStatus", () => {
288
289
  const result = await getAllSessionsStatus();
289
290
  expect(result).toHaveLength(2);
290
291
  expect(result[0].chatId).toBe("chat1");
291
- expect(result[0].active).toBe(true);
292
+ // running=true in registry doesn't make it active — must be in activePrompts
293
+ expect(result[0].active).toBe(false);
292
294
  expect(result[0].turnCount).toBe(2);
293
295
  expect(result[0].chatName).toBe("test-chat-1");
294
296
  expect(result[1].chatId).toBe("chat2");
@@ -314,7 +316,7 @@ describe("getAllSessionsStatus", () => {
314
316
  expect(result.some((r) => r.chatId === "chat-4")).toBe(false);
315
317
  });
316
318
 
317
- it("marks disk-running sessions as active without checking chatSessionMap", async () => {
319
+ it("shows session as inactive when not in activePrompts, regardless of registry running field", async () => {
318
320
  await recordSessionRegistry({
319
321
  chatId: "chat1",
320
322
  sessionId: "s1",
@@ -323,6 +325,22 @@ describe("getAllSessionsStatus", () => {
323
325
  updatedAt: 1000,
324
326
  });
325
327
 
328
+ const result = await getAllSessionsStatus();
329
+ // After restart, activePrompts is cleared; registry running=true should not show as active
330
+ expect(result.find(r => r.chatId === "chat1")!.active).toBe(false);
331
+ });
332
+
333
+ it("shows session as active when in activePrompts", async () => {
334
+ await recordSessionRegistry({
335
+ chatId: "chat1",
336
+ sessionId: "s1",
337
+ tool: "claude",
338
+ running: false, // registry says false, but activePrompts wins
339
+ updatedAt: 1000,
340
+ });
341
+ mockSessionInfo("chat1", { sessionId: "s1" });
342
+ mockActiveSession("chat1");
343
+
326
344
  const result = await getAllSessionsStatus();
327
345
  expect(result.find(r => r.chatId === "chat1")!.active).toBe(true);
328
346
  });
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,
@@ -111,6 +113,8 @@ import {
111
113
  isSessionRunning,
112
114
  activePrompts,
113
115
  rebuildSessionChatsFromRegistry,
116
+ displayCards,
117
+ recordLastActiveChat,
114
118
  } from "./session-chat-binding.ts";
115
119
  import { fixStaleStreamStates } from "./stream-state.ts";
116
120
 
@@ -528,6 +532,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
528
532
 
529
533
  // 让新群的默认工作目录继承当前会话的 cwd
530
534
  await setDefaultCwd(cwd, newChatId);
535
+ bindChatToSession(sessionId, newChatId);
531
536
  await recordSessionRegistry({
532
537
  chatId: newChatId,
533
538
  sessionId,
@@ -683,6 +688,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
683
688
  const cardData = others.map(s => ({
684
689
  sessionId: s.sessionId,
685
690
  chatName: s.chatName,
691
+ chatId: s.chatId,
686
692
  active: s.active,
687
693
  turnCount: s.turnCount,
688
694
  elapsedSeconds: s.active ? Math.floor((now - s.startTime) / 1000) : null,
@@ -710,6 +716,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
710
716
  // 不 abort 旧 session,只解绑当前 chat
711
717
  // 旧 session 如果正在跑,display loop 继续服务其他群
712
718
  unbindChatFromSession(sessionId, chatId);
719
+ displayCards.delete(chatId);
713
720
 
714
721
  let newSessionId: string;
715
722
  try {
@@ -723,6 +730,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
723
730
 
724
731
  // 绑定新 session
725
732
  bindChatToSession(newSessionId, chatId);
733
+ recordLastActiveChat(newSessionId, chatId);
726
734
 
727
735
  const descPrefix = sessionPrefixForTool(descriptionTool);
728
736
  const newName = sessionChatName("新会话", cwd);
@@ -769,6 +777,35 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
769
777
  return;
770
778
  }
771
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
+
772
809
  // /session <number>:切换到 /sessions 列表中的指定会话
773
810
  const sessionMatch = textLower.match(/^\/session\s+(\d+)$/);
774
811
  if (sessionMatch) {
@@ -795,6 +832,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
795
832
  // 不 abort 当前 chat 的旧 session,只解绑再重新绑定
796
833
  if (sessionId) {
797
834
  unbindChatFromSession(sessionId, chatId);
835
+ displayCards.delete(chatId);
798
836
  }
799
837
 
800
838
  const targetAdapter = getAdapterForTool(target.tool);
@@ -808,6 +846,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
808
846
 
809
847
  // 绑定到新 session
810
848
  bindChatToSession(target.sessionId, chatId);
849
+ recordLastActiveChat(target.sessionId, chatId);
811
850
 
812
851
  const descPrefix2 = sessionPrefixForTool(target.tool);
813
852
  const newName2 = target.chatName || sessionChatName("新会话", cwd2);