@cxyhhhhh/dsh-qqbot 0.1.0-dev.202608141525
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/README.md +198 -0
- package/cordis.patch.yml +24 -0
- package/cordis.yml +69 -0
- package/dist/chunker.d.ts +14 -0
- package/dist/chunker.d.ts.map +1 -0
- package/dist/chunker.js +83 -0
- package/dist/chunker.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/inbound.d.ts +8 -0
- package/dist/inbound.d.ts.map +1 -0
- package/dist/inbound.js +71 -0
- package/dist/inbound.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/mention.d.ts +15 -0
- package/dist/mention.d.ts.map +1 -0
- package/dist/mention.js +25 -0
- package/dist/mention.js.map +1 -0
- package/dist/outbound.d.ts +28 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +162 -0
- package/dist/outbound.js.map +1 -0
- package/dist/session-manager.d.ts +106 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +201 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/install.sh +108 -0
- package/package.json +70 -0
- package/pnpm-workspace.yaml +16 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mention 检测和清理工具
|
|
3
|
+
*
|
|
4
|
+
* QQ 群消息中 @bot 的格式为: <@OPENID> (十六进制字符串)
|
|
5
|
+
* 例如: <@BD9386750A680BA80CB124E27A7B8089>
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 检测消息是否包含 @mention
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectMention(content: string | undefined): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 移除消息中的 @mention,返回干净的用户意图文本
|
|
13
|
+
*/
|
|
14
|
+
export declare function stripMention(content: string): string;
|
|
15
|
+
//# sourceMappingURL=mention.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention.d.ts","sourceRoot":"","sources":["../src/mention.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGlE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIpD"}
|
package/dist/mention.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mention 检测和清理工具
|
|
3
|
+
*
|
|
4
|
+
* QQ 群消息中 @bot 的格式为: <@OPENID> (十六进制字符串)
|
|
5
|
+
* 例如: <@BD9386750A680BA80CB124E27A7B8089>
|
|
6
|
+
*/
|
|
7
|
+
/** QQ @mention 格式: <@OPENID> 或 <@!OPENID>,OPENID 是十六进制或数字字符串 */
|
|
8
|
+
const MENTION_PATTERN = /<@!?[A-Fa-f0-9]+>/g;
|
|
9
|
+
/**
|
|
10
|
+
* 检测消息是否包含 @mention
|
|
11
|
+
*/
|
|
12
|
+
export function detectMention(content) {
|
|
13
|
+
if (!content)
|
|
14
|
+
return false;
|
|
15
|
+
return MENTION_PATTERN.test(content);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 移除消息中的 @mention,返回干净的用户意图文本
|
|
19
|
+
*/
|
|
20
|
+
export function stripMention(content) {
|
|
21
|
+
return content
|
|
22
|
+
.replace(MENTION_PATTERN, '')
|
|
23
|
+
.trim();
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=mention.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention.js","sourceRoot":"","sources":["../src/mention.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,gEAAgE;AAChE,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAE7C;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,OAA2B;IACvD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,OAAO,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,OAAO;SACX,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;SAC5B,IAAI,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 出站处理器 — dsh session/event → QQ 消息发送
|
|
3
|
+
*/
|
|
4
|
+
import type { SessionManager } from './session-manager.js';
|
|
5
|
+
import type { ImQQBotConfig } from './config.js';
|
|
6
|
+
import type { Logger, ReplyTarget } from './types.js';
|
|
7
|
+
/** QQ Bot 发送接口 */
|
|
8
|
+
export interface QQBotSender {
|
|
9
|
+
sendMarkdown(target: ReplyTarget, content: string): Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** dsh SessionEvent 简化类型 */
|
|
12
|
+
export interface SessionEvent {
|
|
13
|
+
type: string;
|
|
14
|
+
data: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/** dsh Session 简化类型 */
|
|
17
|
+
export interface SessionLike {
|
|
18
|
+
header: {
|
|
19
|
+
id: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 创建出站事件处理器
|
|
24
|
+
*
|
|
25
|
+
* 返回一个 handler 函数,应注册到 ctx.on('session/event', handler)
|
|
26
|
+
*/
|
|
27
|
+
export declare function createOutboundHandler(manager: SessionManager, bot: QQBotSender, config: ImQQBotConfig, logger: Logger): (session: SessionLike, event: SessionEvent) => void;
|
|
28
|
+
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGtD,kBAAkB;AAClB,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,4BAA4B;AAC5B,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,uBAAuB;AACvB,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CACxB;AAqED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,CA0BrD"}
|
package/dist/outbound.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { chunkMarkdownText } from './chunker.js';
|
|
2
|
+
/**
|
|
3
|
+
* 出站文本缓冲 — 收集流式 chunk 并 debounce 发送
|
|
4
|
+
*/
|
|
5
|
+
class OutboundBuffer {
|
|
6
|
+
record;
|
|
7
|
+
bot;
|
|
8
|
+
limit;
|
|
9
|
+
logger;
|
|
10
|
+
buffer = '';
|
|
11
|
+
timer = null;
|
|
12
|
+
flushing = false;
|
|
13
|
+
constructor(record, bot, limit, logger) {
|
|
14
|
+
this.record = record;
|
|
15
|
+
this.bot = bot;
|
|
16
|
+
this.limit = limit;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
/** 追加文本增量 */
|
|
20
|
+
append(text) {
|
|
21
|
+
this.buffer += text;
|
|
22
|
+
}
|
|
23
|
+
/** 获取当前累积文本 */
|
|
24
|
+
get text() {
|
|
25
|
+
return this.buffer;
|
|
26
|
+
}
|
|
27
|
+
/** 安排延迟发送(debounce) */
|
|
28
|
+
scheduleSend(delayMs) {
|
|
29
|
+
if (this.timer)
|
|
30
|
+
return;
|
|
31
|
+
this.timer = setTimeout(() => {
|
|
32
|
+
this.timer = null;
|
|
33
|
+
void this.flush();
|
|
34
|
+
}, delayMs);
|
|
35
|
+
}
|
|
36
|
+
/** 立即发送所有累积文本 */
|
|
37
|
+
async flush() {
|
|
38
|
+
if (this.flushing || !this.buffer.trim())
|
|
39
|
+
return;
|
|
40
|
+
this.flushing = true;
|
|
41
|
+
if (this.timer) {
|
|
42
|
+
clearTimeout(this.timer);
|
|
43
|
+
this.timer = null;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const chunks = chunkMarkdownText(this.buffer, this.limit);
|
|
47
|
+
for (const chunk of chunks) {
|
|
48
|
+
await this.bot.sendMarkdown(this.record.replyTarget, chunk);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
this.logger.error(`im-qqbot: sendMarkdown failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
this.buffer = '';
|
|
56
|
+
this.flushing = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** 取消未发送的定时器 */
|
|
60
|
+
cancel() {
|
|
61
|
+
if (this.timer) {
|
|
62
|
+
clearTimeout(this.timer);
|
|
63
|
+
this.timer = null;
|
|
64
|
+
}
|
|
65
|
+
this.buffer = '';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 创建出站事件处理器
|
|
70
|
+
*
|
|
71
|
+
* 返回一个 handler 函数,应注册到 ctx.on('session/event', handler)
|
|
72
|
+
*/
|
|
73
|
+
export function createOutboundHandler(manager, bot, config, logger) {
|
|
74
|
+
const buffers = new Map();
|
|
75
|
+
const limit = config.textChunkLimit;
|
|
76
|
+
return (session, event) => {
|
|
77
|
+
const sessionId = session.header.id;
|
|
78
|
+
const record = manager.findBySessionId(sessionId);
|
|
79
|
+
if (!record)
|
|
80
|
+
return; // 非本插件管理的会话
|
|
81
|
+
switch (event.type) {
|
|
82
|
+
case 'assistant/chunk': {
|
|
83
|
+
handleChunk(sessionId, record, event, buffers, bot, limit, logger);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'assistant/message': {
|
|
87
|
+
handleMessage(sessionId, record, event, buffers, bot, limit, logger);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'turn/end': {
|
|
91
|
+
handleTurnEnd(sessionId, buffers, logger);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** 处理流式文本增量 */
|
|
98
|
+
function handleChunk(sessionId, record, event, buffers, bot, limit, logger) {
|
|
99
|
+
const chunk = event.data.chunk;
|
|
100
|
+
if (!chunk || chunk.type !== 'text-delta' || !chunk.text)
|
|
101
|
+
return;
|
|
102
|
+
let buffer = buffers.get(sessionId);
|
|
103
|
+
if (!buffer) {
|
|
104
|
+
buffer = new OutboundBuffer(record, bot, limit, logger);
|
|
105
|
+
buffers.set(sessionId, buffer);
|
|
106
|
+
}
|
|
107
|
+
buffer.append(chunk.text);
|
|
108
|
+
// 不实时发 chunk,等 assistant/message 完整输出后一起发
|
|
109
|
+
// 如需流式体验,可改为 scheduleSend(500) 定期推送
|
|
110
|
+
}
|
|
111
|
+
/** 处理完整 assistant 消息 */
|
|
112
|
+
function handleMessage(sessionId, record, event, buffers, bot, limit, logger) {
|
|
113
|
+
// 如果已有 buffer 积累了 chunk,直接用 buffer(chunk 更完整)
|
|
114
|
+
const buffer = buffers.get(sessionId);
|
|
115
|
+
if (buffer && buffer.text.trim()) {
|
|
116
|
+
void buffer.flush();
|
|
117
|
+
buffers.delete(sessionId);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// 否则从 message content 中提取文本
|
|
121
|
+
const message = event.data;
|
|
122
|
+
const blocks = message?.message?.content;
|
|
123
|
+
if (!blocks || !Array.isArray(blocks))
|
|
124
|
+
return;
|
|
125
|
+
const textParts = [];
|
|
126
|
+
for (const block of blocks) {
|
|
127
|
+
if (block.type === 'text' && block.text) {
|
|
128
|
+
textParts.push(block.text);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const fullText = textParts.join('\n');
|
|
132
|
+
if (!fullText.trim())
|
|
133
|
+
return;
|
|
134
|
+
const chunks = chunkMarkdownText(fullText, limit);
|
|
135
|
+
void (async () => {
|
|
136
|
+
for (const chunk of chunks) {
|
|
137
|
+
try {
|
|
138
|
+
await bot.sendMarkdown(record.replyTarget, chunk);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
logger.error(`im-qqbot: sendMarkdown failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
})();
|
|
145
|
+
buffers.delete(sessionId);
|
|
146
|
+
}
|
|
147
|
+
/** 处理轮次结束,清理 buffer */
|
|
148
|
+
function handleTurnEnd(sessionId, buffers, logger) {
|
|
149
|
+
const buffer = buffers.get(sessionId);
|
|
150
|
+
if (buffer) {
|
|
151
|
+
// flush 任何残留
|
|
152
|
+
if (buffer.text.trim()) {
|
|
153
|
+
void buffer.flush();
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
buffer.cancel();
|
|
157
|
+
}
|
|
158
|
+
buffers.delete(sessionId);
|
|
159
|
+
}
|
|
160
|
+
logger.debug(`im-qqbot: turn/end sessionId=${sessionId}`);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=outbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { ChatScope, Logger, ReplyTarget } from './types.js';
|
|
3
|
+
import type { ImQQBotConfig } from './config.js';
|
|
4
|
+
/** AgentSetup hook 类型 */
|
|
5
|
+
export type AgentSetup = (agentCtx: Context) => Promise<void> | void;
|
|
6
|
+
/** dsh Agent 简化接口 */
|
|
7
|
+
export interface DshAgent {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly ctx: Context;
|
|
10
|
+
cancel(cause: {
|
|
11
|
+
kind: string;
|
|
12
|
+
}): void;
|
|
13
|
+
followup(message: unknown): void;
|
|
14
|
+
whenIdle(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface DshAgentHandle {
|
|
17
|
+
agent: DshAgent;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export interface DshAgentRegistry {
|
|
21
|
+
/** 获取进程内已存活的 agent */
|
|
22
|
+
get(sessionId: string): DshAgent | undefined;
|
|
23
|
+
/** 从持久化存储恢复 session */
|
|
24
|
+
resume(options: {
|
|
25
|
+
resumeSessionId: string;
|
|
26
|
+
agentOptions?: {
|
|
27
|
+
provider?: string;
|
|
28
|
+
model?: string;
|
|
29
|
+
};
|
|
30
|
+
setup?: AgentSetup;
|
|
31
|
+
}): Promise<DshAgentHandle>;
|
|
32
|
+
/** 创建全新 session */
|
|
33
|
+
create(options: {
|
|
34
|
+
sessionId: string;
|
|
35
|
+
meta?: {
|
|
36
|
+
cwd?: string;
|
|
37
|
+
agentPreset?: string;
|
|
38
|
+
};
|
|
39
|
+
agentOptions?: {
|
|
40
|
+
provider?: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
};
|
|
43
|
+
setup?: AgentSetup;
|
|
44
|
+
}): Promise<DshAgentHandle>;
|
|
45
|
+
}
|
|
46
|
+
/** agent-presets 服务接口(可选,部署中可能没有) */
|
|
47
|
+
export interface AgentPresetsLike {
|
|
48
|
+
readonly defaultId: string;
|
|
49
|
+
resolve(id?: string): Promise<{
|
|
50
|
+
id: string;
|
|
51
|
+
}>;
|
|
52
|
+
mount(agentCtx: Context, id?: string): Promise<unknown>;
|
|
53
|
+
}
|
|
54
|
+
/** 单个会话记录 */
|
|
55
|
+
export interface SessionRecord {
|
|
56
|
+
sessionKey: string;
|
|
57
|
+
sessionId: string;
|
|
58
|
+
agent: DshAgent;
|
|
59
|
+
handle: DshAgentHandle;
|
|
60
|
+
replyTarget: ReplyTarget;
|
|
61
|
+
scope: ChatScope;
|
|
62
|
+
peerId: string;
|
|
63
|
+
senderId: string;
|
|
64
|
+
lastActivity: number;
|
|
65
|
+
agentPreset?: string;
|
|
66
|
+
}
|
|
67
|
+
export declare class SessionManager {
|
|
68
|
+
private readonly ctx;
|
|
69
|
+
private readonly agents;
|
|
70
|
+
private readonly config;
|
|
71
|
+
private readonly logger;
|
|
72
|
+
private sessions;
|
|
73
|
+
private idleTimer;
|
|
74
|
+
constructor(ctx: Context, agents: DshAgentRegistry, config: ImQQBotConfig, logger: Logger);
|
|
75
|
+
/**
|
|
76
|
+
* 生成 sessionKey
|
|
77
|
+
*
|
|
78
|
+
* 格式: qqbot:${appId}:${kind}:${peerId}
|
|
79
|
+
*/
|
|
80
|
+
private sessionKey;
|
|
81
|
+
/**
|
|
82
|
+
* 从 sessionKey 确定性生成 SessionId
|
|
83
|
+
*/
|
|
84
|
+
private deriveSessionId;
|
|
85
|
+
/**
|
|
86
|
+
* 组合 preset:解析 preset id 并生成 setup hook
|
|
87
|
+
*
|
|
88
|
+
* 如果部署中没有 agent-presets 服务,返回空组合(使用宿主默认配置)
|
|
89
|
+
*/
|
|
90
|
+
private composePreset;
|
|
91
|
+
/** 获取或恢复或创建会话(get → resume → create) */
|
|
92
|
+
getOrCreate(scope: ChatScope, peerId: string, senderId: string, replyTarget: ReplyTarget): Promise<SessionRecord>;
|
|
93
|
+
/** 根据 sessionId 查找 record */
|
|
94
|
+
findBySessionId(sessionId: string): SessionRecord | undefined;
|
|
95
|
+
/** 根据 agent 查找 record */
|
|
96
|
+
findByAgent(agent: DshAgent): SessionRecord | undefined;
|
|
97
|
+
/** 删除并销毁指定会话 */
|
|
98
|
+
remove(scope: ChatScope, peerId: string): Promise<void>;
|
|
99
|
+
/** 定期回收闲置会话 */
|
|
100
|
+
private evictIdle;
|
|
101
|
+
/** 关闭所有会话并释放资源 */
|
|
102
|
+
disposeAll(): Promise<void>;
|
|
103
|
+
/** 当前活跃会话数 */
|
|
104
|
+
get size(): number;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=session-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionManager — 管理 QQ peer → dsh Agent 的映射和生命周期
|
|
3
|
+
*
|
|
4
|
+
* sessionKey 格式: `qqbot:${appId}:${kind}:${peerId}`
|
|
5
|
+
* - c2c: peerId = senderId (用户 openid)
|
|
6
|
+
* - group: peerId = groupOpenid
|
|
7
|
+
*
|
|
8
|
+
* SessionId 由 sessionKey 确定性派生(SHA-256),
|
|
9
|
+
* 保证同一个用户/群的消息始终路由到同一个会话,
|
|
10
|
+
* 重启后可根据 key 恢复 session。
|
|
11
|
+
*
|
|
12
|
+
* 支持 agent-presets 系统:通过 setup hook 在 create/resume 时
|
|
13
|
+
* 挂载 preset(工具集、prompt sections 等),实现场景化配置。
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
17
|
+
export class SessionManager {
|
|
18
|
+
ctx;
|
|
19
|
+
agents;
|
|
20
|
+
config;
|
|
21
|
+
logger;
|
|
22
|
+
sessions = new Map();
|
|
23
|
+
idleTimer = null;
|
|
24
|
+
constructor(ctx, agents, config, logger) {
|
|
25
|
+
this.ctx = ctx;
|
|
26
|
+
this.agents = agents;
|
|
27
|
+
this.config = config;
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
if (config.sessionIdleTimeout > 0) {
|
|
30
|
+
this.idleTimer = setInterval(() => this.evictIdle(), 60_000);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 生成 sessionKey
|
|
35
|
+
*
|
|
36
|
+
* 格式: qqbot:${appId}:${kind}:${peerId}
|
|
37
|
+
*/
|
|
38
|
+
sessionKey(scope, peerId) {
|
|
39
|
+
return `qqbot:${this.config.appId}:${scope}:${peerId}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 从 sessionKey 确定性生成 SessionId
|
|
43
|
+
*/
|
|
44
|
+
deriveSessionId(sessionKey) {
|
|
45
|
+
const hash = createHash('sha256').update(sessionKey).digest('hex');
|
|
46
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 组合 preset:解析 preset id 并生成 setup hook
|
|
50
|
+
*
|
|
51
|
+
* 如果部署中没有 agent-presets 服务,返回空组合(使用宿主默认配置)
|
|
52
|
+
*/
|
|
53
|
+
async composePreset(presetId) {
|
|
54
|
+
const presets = this.ctx.agentPresets
|
|
55
|
+
?? this.ctx.get?.('agentPresets');
|
|
56
|
+
if (!presets)
|
|
57
|
+
return {};
|
|
58
|
+
try {
|
|
59
|
+
const resolved = await presets.resolve(presetId);
|
|
60
|
+
const resolvedId = resolved.id;
|
|
61
|
+
return {
|
|
62
|
+
agentPreset: resolvedId,
|
|
63
|
+
setup: async (agentCtx) => {
|
|
64
|
+
await presets.mount(agentCtx, resolvedId);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
this.logger.warn(`im-qqbot: preset ${presetId ?? '(default)'} unavailable: ${err instanceof Error ? err.message : String(err)} — using host composition`);
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** 获取或恢复或创建会话(get → resume → create) */
|
|
74
|
+
async getOrCreate(scope, peerId, senderId, replyTarget) {
|
|
75
|
+
const key = this.sessionKey(scope, peerId);
|
|
76
|
+
const existing = this.sessions.get(key);
|
|
77
|
+
if (existing) {
|
|
78
|
+
existing.replyTarget = replyTarget;
|
|
79
|
+
existing.lastActivity = Date.now();
|
|
80
|
+
return existing;
|
|
81
|
+
}
|
|
82
|
+
const sessionId = SessionId(this.deriveSessionId(key));
|
|
83
|
+
const route = {
|
|
84
|
+
provider: this.config.provider || 'deepseek-official',
|
|
85
|
+
model: this.config.model || 'deepseek-chat',
|
|
86
|
+
};
|
|
87
|
+
let agent;
|
|
88
|
+
let handle;
|
|
89
|
+
let agentPreset;
|
|
90
|
+
// 1. 检查进程内是否已有活跃 agent
|
|
91
|
+
const live = this.agents.get(sessionId);
|
|
92
|
+
if (live) {
|
|
93
|
+
agent = live;
|
|
94
|
+
console.log(`[im-qqbot] reusing live agent: key=${key}`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// 2. 尝试从持久化存储恢复(带 setup hook)
|
|
98
|
+
try {
|
|
99
|
+
const composed = await this.composePreset(this.config.preset);
|
|
100
|
+
agentPreset = composed.agentPreset;
|
|
101
|
+
const resumed = await this.agents.resume({
|
|
102
|
+
resumeSessionId: sessionId,
|
|
103
|
+
agentOptions: route,
|
|
104
|
+
...(composed.setup ? { setup: composed.setup } : {}),
|
|
105
|
+
});
|
|
106
|
+
agent = resumed.agent;
|
|
107
|
+
handle = resumed;
|
|
108
|
+
console.log(`[im-qqbot] resumed session: key=${key} preset=${agentPreset ?? 'none'}`);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// 3. 恢复失败,创建全新 session(带 setup hook)
|
|
112
|
+
const composed = await this.composePreset(this.config.preset);
|
|
113
|
+
agentPreset = composed.agentPreset;
|
|
114
|
+
const created = await this.agents.create({
|
|
115
|
+
sessionId,
|
|
116
|
+
meta: {
|
|
117
|
+
cwd: this.config.cwd || process.cwd(),
|
|
118
|
+
...(agentPreset ? { agentPreset } : {}),
|
|
119
|
+
},
|
|
120
|
+
agentOptions: route,
|
|
121
|
+
...(composed.setup ? { setup: composed.setup } : {}),
|
|
122
|
+
});
|
|
123
|
+
agent = created.agent;
|
|
124
|
+
handle = created;
|
|
125
|
+
console.log(`[im-qqbot] created new session: key=${key} preset=${agentPreset ?? 'none'}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const record = {
|
|
129
|
+
sessionKey: key,
|
|
130
|
+
sessionId,
|
|
131
|
+
agent,
|
|
132
|
+
handle: handle ?? { agent, dispose: async () => { } },
|
|
133
|
+
replyTarget,
|
|
134
|
+
scope,
|
|
135
|
+
peerId,
|
|
136
|
+
senderId,
|
|
137
|
+
lastActivity: Date.now(),
|
|
138
|
+
agentPreset,
|
|
139
|
+
};
|
|
140
|
+
this.sessions.set(key, record);
|
|
141
|
+
return record;
|
|
142
|
+
}
|
|
143
|
+
/** 根据 sessionId 查找 record */
|
|
144
|
+
findBySessionId(sessionId) {
|
|
145
|
+
for (const record of this.sessions.values()) {
|
|
146
|
+
if (record.sessionId === sessionId)
|
|
147
|
+
return record;
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
/** 根据 agent 查找 record */
|
|
152
|
+
findByAgent(agent) {
|
|
153
|
+
for (const record of this.sessions.values()) {
|
|
154
|
+
if (record.agent === agent)
|
|
155
|
+
return record;
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
/** 删除并销毁指定会话 */
|
|
160
|
+
async remove(scope, peerId) {
|
|
161
|
+
const key = this.sessionKey(scope, peerId);
|
|
162
|
+
const record = this.sessions.get(key);
|
|
163
|
+
if (!record)
|
|
164
|
+
return;
|
|
165
|
+
this.sessions.delete(key);
|
|
166
|
+
record.agent.cancel({ kind: 'user' });
|
|
167
|
+
await record.handle.dispose().catch(() => { });
|
|
168
|
+
console.log(`[im-qqbot] session removed: key=${key}`);
|
|
169
|
+
}
|
|
170
|
+
/** 定期回收闲置会话 */
|
|
171
|
+
evictIdle() {
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
for (const [key, record] of this.sessions) {
|
|
174
|
+
if (now - record.lastActivity > this.config.sessionIdleTimeout) {
|
|
175
|
+
console.log(`[im-qqbot] evicting idle session: key=${key}`);
|
|
176
|
+
this.sessions.delete(key);
|
|
177
|
+
record.agent.cancel({ kind: 'user' });
|
|
178
|
+
void record.handle.dispose().catch(() => { });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** 关闭所有会话并释放资源 */
|
|
183
|
+
async disposeAll() {
|
|
184
|
+
if (this.idleTimer) {
|
|
185
|
+
clearInterval(this.idleTimer);
|
|
186
|
+
this.idleTimer = null;
|
|
187
|
+
}
|
|
188
|
+
const records = [...this.sessions.values()];
|
|
189
|
+
this.sessions.clear();
|
|
190
|
+
for (const record of records) {
|
|
191
|
+
record.agent.cancel({ kind: 'user' });
|
|
192
|
+
}
|
|
193
|
+
await Promise.allSettled(records.map((r) => r.handle.dispose()));
|
|
194
|
+
console.log(`[im-qqbot] all sessions disposed (count=${records.length})`);
|
|
195
|
+
}
|
|
196
|
+
/** 当前活跃会话数 */
|
|
197
|
+
get size() {
|
|
198
|
+
return this.sessions.size;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=session-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-im-qqbot 插件内部类型定义
|
|
3
|
+
*/
|
|
4
|
+
/** 会话作用域 */
|
|
5
|
+
export type ChatScope = 'c2c' | 'group';
|
|
6
|
+
/** QQ 回复目标 */
|
|
7
|
+
export interface ReplyTarget {
|
|
8
|
+
scope: ChatScope;
|
|
9
|
+
targetId: string;
|
|
10
|
+
msgId?: string;
|
|
11
|
+
}
|
|
12
|
+
/** QQ 入站消息(简化) */
|
|
13
|
+
export interface QQInboundMessage {
|
|
14
|
+
content?: string;
|
|
15
|
+
senderId: string;
|
|
16
|
+
groupOpenid?: string;
|
|
17
|
+
replyTarget: ReplyTarget;
|
|
18
|
+
attachments?: QQAttachment[];
|
|
19
|
+
}
|
|
20
|
+
/** QQ 附件 */
|
|
21
|
+
export interface QQAttachment {
|
|
22
|
+
contentType: string;
|
|
23
|
+
filename?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
size?: number;
|
|
26
|
+
}
|
|
27
|
+
/** 插件 Logger 接口 */
|
|
28
|
+
export interface Logger {
|
|
29
|
+
info(msg: string, ...args: unknown[]): void;
|
|
30
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
31
|
+
error(msg: string, ...args: unknown[]): void;
|
|
32
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAExC,cAAc;AACd,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,kBAAkB;AAClB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;CAC9B;AAED,YAAY;AACZ,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mBAAmB;AACnB,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC9C"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|