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 +1 -1
- package/src/__tests__/feishu-platform.test.ts +56 -54
- package/src/__tests__/session.test.ts +20 -2
- package/src/cards.ts +3 -1
- package/src/feishu-api.ts +12 -0
- package/src/feishu-platform.ts +138 -133
- package/src/index.ts +39 -0
- package/src/session-chat-binding.ts +102 -86
- package/src/session.ts +104 -91
- package/src/sim-platform.ts +152 -147
- package/src/sim-store.ts +316 -312
package/package.json
CHANGED
|
@@ -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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(await
|
|
42
|
-
expect(await
|
|
43
|
-
|
|
44
|
-
expect(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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" },
|
package/src/feishu-platform.ts
CHANGED
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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);
|