dsh-qqchat 0.2.5 → 0.2.6
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.en.md +5 -4
- package/README.md +3 -3
- package/client-src/plugin.cts +2 -2
- package/client-src/shared.cts +2 -2
- package/cordis.patch.yml +5 -0
- package/docs/ARCHITECTURE.en.md +1 -1
- package/docs/ARCHITECTURE.md +2 -1
- package/docs/DEVELOPMENT.md +3 -1
- package/docs/STREAMING.md +2 -2
- package/lib/client.js +2 -2
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/session/agent-bridge.d.ts +3 -0
- package/lib/session/agent-bridge.js +44 -15
- package/lib/session/runtime.js +1 -0
- package/package.json +23 -23
- package/src/index.ts +2 -2
- package/src/session/agent-bridge.ts +42 -14
- package/src/session/runtime.ts +1 -0
package/README.en.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
<p>
|
|
8
8
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a>
|
|
9
|
-
<img src="https://img.shields.io/badge/DSH-0.1.
|
|
10
|
-
<img src="https://img.shields.io/badge/QQChat-0.2.
|
|
9
|
+
<img src="https://img.shields.io/badge/DSH-0.1.6--alpha.1-6f42c1.svg" alt="DSH 0.1.6-alpha.1">
|
|
10
|
+
<img src="https://img.shields.io/badge/QQChat-0.2.6-2ea44f.svg" alt="QQChat 0.2.6">
|
|
11
11
|
<img src="https://img.shields.io/badge/TypeScript-5.x-3178c6.svg" alt="TypeScript">
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
<p><em>Chat in QQ, then view the complete Session, memory and tool activity in DSH Web.</em></p>
|
|
20
20
|
|
|
21
|
-
<p>This release is verified against DSH <code>0.1.
|
|
21
|
+
<p>This release is verified against DSH <code>0.1.6-alpha.1</code>. Install the plugin as-is; no DSH source patch or rebuild is required.</p>
|
|
22
22
|
|
|
23
23
|
`dsh-qqchat` is an official QQ Bot plugin for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness). It maps QQ groups and direct chats to ordinary DSH Sessions and uses DSH's native Conversation, Agent, command and tool surfaces.
|
|
24
24
|
|
|
@@ -126,7 +126,8 @@ npx @deepseek-ai/dsh plugin --profile qqchat add @deepseek-ai/dsh-web-app dsh-qq
|
|
|
126
126
|
npx @deepseek-ai/dsh --profile qqchat
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
Git installs
|
|
129
|
+
Git installs use the committed `lib/` runtime artifacts, so they do not need to run
|
|
130
|
+
an install-time build. For local development:
|
|
130
131
|
|
|
131
132
|
```bash
|
|
132
133
|
git clone git@github.com:Coffeiz/dsh-qqchat.git
|
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
<p>
|
|
8
8
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a>
|
|
9
|
-
<img src="https://img.shields.io/badge/DSH-0.1.
|
|
10
|
-
<img src="https://img.shields.io/badge/QQChat-0.2.
|
|
9
|
+
<img src="https://img.shields.io/badge/DSH-0.1.6--alpha.1-6f42c1.svg" alt="DSH 0.1.6-alpha.1">
|
|
10
|
+
<img src="https://img.shields.io/badge/QQChat-0.2.6-2ea44f.svg" alt="QQChat 0.2.6">
|
|
11
11
|
<img src="https://img.shields.io/badge/TypeScript-5.x-3178c6.svg" alt="TypeScript">
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<p><em>dsh-qqchat 是 DeepSeek Harness(DSH)的 QQ Bot 插件,支持 QQ 群聊、私聊、会话隔离、记忆系统和 DSH 原生命令。</em></p>
|
|
21
21
|
|
|
22
|
-
<p>当前版本基于 DSH <code>0.1.
|
|
22
|
+
<p>当前版本基于 DSH <code>0.1.6-alpha.1</code> 验证,安装插件即可使用,无需修改或重新构建 DSH。</p>
|
|
23
23
|
|
|
24
24
|
> 本项目是一个 Vibe Coding 项目,代码质量和工程完整性可能需要改进。如果你发现问题或有更好的实现建议,欢迎提交 [Issue](https://github.com/Coffeiz/dsh-qqchat/issues) 或 [Pull Request](https://github.com/Coffeiz/dsh-qqchat/pulls)。
|
|
25
25
|
|
package/client-src/plugin.cts
CHANGED
|
@@ -3,12 +3,12 @@ import type { Ctx } from './shared.cjs'
|
|
|
3
3
|
import { QQSettings } from './settings.cjs'
|
|
4
4
|
import { QQSessionUtility, QQTranscriptNode, createQQMessageDefinition } from './workspace.cjs'
|
|
5
5
|
|
|
6
|
-
exports.inject = ['slots', 'connection', '
|
|
6
|
+
exports.inject = ['slots', 'connection', 'uiConversation'] as const
|
|
7
7
|
exports.apply = function apply(ctx: Ctx) {
|
|
8
8
|
setQQRpc(ctx.connection.rpc)
|
|
9
9
|
ctx.effect(installStyles, 'dsh-qqchat: client styles')
|
|
10
10
|
ctx.effect(installQQSettingsIcon, 'dsh-qqchat: QQ settings icon')
|
|
11
|
-
ctx.
|
|
11
|
+
ctx.uiConversation.events.register(createQQMessageDefinition())
|
|
12
12
|
|
|
13
13
|
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'qqchat', order: 35, label: () => 'QQ Chat', inject: () => ({ rpc: ctx.connection.rpc }) }, QQSettings))
|
|
14
14
|
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'qqchat-message' }, QQTranscriptNode))
|
package/client-src/shared.cts
CHANGED
|
@@ -29,11 +29,11 @@ export interface SlotRegistry {
|
|
|
29
29
|
inject(name: string, fn: () => unknown): unknown
|
|
30
30
|
register<P>(options: Record<string, unknown>, component: import('react').ComponentType<P>): unknown
|
|
31
31
|
}
|
|
32
|
-
export interface
|
|
32
|
+
export interface UiConversation { events: { register(definition: Record<string, unknown>): unknown } }
|
|
33
33
|
export interface Ctx {
|
|
34
34
|
connection: { rpc: Rpc }
|
|
35
35
|
sessions: SessionsService
|
|
36
|
-
|
|
36
|
+
uiConversation: UiConversation
|
|
37
37
|
effect(fn: () => (() => void) | void, label: string): unknown
|
|
38
38
|
slots: SlotRegistry
|
|
39
39
|
}
|
package/cordis.patch.yml
CHANGED
|
@@ -5,3 +5,8 @@
|
|
|
5
5
|
name: dsh-qqchat
|
|
6
6
|
config:
|
|
7
7
|
dataDir: !!js dshHomePath('plugins/dsh-qqchat')
|
|
8
|
+
|
|
9
|
+
# DSH 0.1.6's Connection RPC registry owns route lifetimes on the
|
|
10
|
+
# connection fiber, so that fiber must also receive the Web server service.
|
|
11
|
+
- id: connection
|
|
12
|
+
inject: [webRuntime, webServer]
|
package/docs/ARCHITECTURE.en.md
CHANGED
|
@@ -339,7 +339,7 @@ client-src/*.cts
|
|
|
339
339
|
tests/*.test.ts
|
|
340
340
|
```
|
|
341
341
|
|
|
342
|
-
The Host is built with `tsc`. The Client is compiled to temporary CJS and then wrapped by `scripts/wrap-client.mjs` as a DSH Client factory. `lib/` is generated output
|
|
342
|
+
The Host is built with `tsc`. The Client is compiled to temporary CJS and then wrapped by `scripts/wrap-client.mjs` as a DSH Client factory. `lib/` is generated release output committed with fixed revisions so DSH Store can find the runtime entry without executing third-party install scripts; source remains the implementation truth.
|
|
343
343
|
|
|
344
344
|
## Development and security documentation
|
|
345
345
|
|
package/docs/ARCHITECTURE.md
CHANGED
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -60,7 +60,9 @@ client-src/*.cts -> 临时 CJS Client
|
|
|
60
60
|
临时 CJS -> lib/client.js DSH ModuleLoader factory
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
`lib/`
|
|
63
|
+
`lib/` 是由构建生成的发布产物,同时提交到仓库以支持 DSH Store 从固定 Commit
|
|
64
|
+
直接安装;修改源码后发布前必须重新构建并检查 `lib/` 的变更。临时 Client
|
|
65
|
+
构建目录仍是本地生成物,不提交。
|
|
64
66
|
|
|
65
67
|
## 启动 DSH Web
|
|
66
68
|
|
package/docs/STREAMING.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
推荐采用以下结构:
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
DSH assistant
|
|
14
|
+
DSH `agent/assistant-stream` 的 chunk frame
|
|
15
15
|
-> StreamingWriter
|
|
16
16
|
-> 累计完整文本
|
|
17
17
|
-> 节流定时器
|
|
@@ -118,7 +118,7 @@ interface StreamingWriterState {
|
|
|
118
118
|
|
|
119
119
|
### 2. 累计全文,不累计分片
|
|
120
120
|
|
|
121
|
-
DSH 的 `assistant
|
|
121
|
+
DSH 的 `agent/assistant-stream` chunk frame 通常携带增量文本:
|
|
122
122
|
|
|
123
123
|
```text
|
|
124
124
|
"你好"
|
package/lib/client.js
CHANGED
|
@@ -6,12 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const shared_cjs_1 = require("./shared.cjs");
|
|
7
7
|
const settings_cjs_1 = require("./settings.cjs");
|
|
8
8
|
const workspace_cjs_1 = require("./workspace.cjs");
|
|
9
|
-
exports.inject = ['slots', 'connection', '
|
|
9
|
+
exports.inject = ['slots', 'connection', 'uiConversation'];
|
|
10
10
|
exports.apply = function apply(ctx) {
|
|
11
11
|
(0, shared_cjs_1.setQQRpc)(ctx.connection.rpc);
|
|
12
12
|
ctx.effect(shared_cjs_1.installStyles, 'dsh-qqchat: client styles');
|
|
13
13
|
ctx.effect(shared_cjs_1.installQQSettingsIcon, 'dsh-qqchat: QQ settings icon');
|
|
14
|
-
ctx.
|
|
14
|
+
ctx.uiConversation.events.register((0, workspace_cjs_1.createQQMessageDefinition)());
|
|
15
15
|
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'qqchat', order: 35, label: () => 'QQ Chat', inject: () => ({ rpc: ctx.connection.rpc }) }, settings_cjs_1.QQSettings));
|
|
16
16
|
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'qqchat-message' }, workspace_cjs_1.QQTranscriptNode));
|
|
17
17
|
ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({ name: 'conversation.session.header.utilities', id: 'qqchat-memory', order: 30, inject: () => ({ rpc: ctx.connection.rpc }) }, workspace_cjs_1.QQSessionUtility));
|
package/lib/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import type { QQChatConfigInput } from './types.js';
|
|
3
3
|
export declare const name = "dsh-qqchat";
|
|
4
|
-
export declare const inject: readonly ["connection", "agents", "agentDefaultModel", "commands", "llm", "tools", "workspaceRegistry"];
|
|
4
|
+
export declare const inject: readonly ["connection", "agents", "agentDefaultModel", "commands", "llm", "tools", "webServer", "workspaceRegistry"];
|
|
5
5
|
export declare function apply(ctx: Context, inputConfig?: QQChatConfigInput): void;
|
|
6
6
|
export type { QQChatConfig, QQChatConfigInput } from './types.js';
|
|
7
7
|
export { QQChatDatabase } from './storage/db.js';
|
|
8
8
|
export { normalizeQQDispatch } from './gateway/normalize.js';
|
|
9
9
|
declare const _default: {
|
|
10
10
|
name: string;
|
|
11
|
-
inject: readonly ["connection", "agents", "agentDefaultModel", "commands", "llm", "tools", "workspaceRegistry"];
|
|
11
|
+
inject: readonly ["connection", "agents", "agentDefaultModel", "commands", "llm", "tools", "webServer", "workspaceRegistry"];
|
|
12
12
|
apply: typeof apply;
|
|
13
13
|
};
|
|
14
14
|
export default _default;
|
package/lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { QQChatRuntime } from './session/runtime.js';
|
|
|
13
13
|
import { registerQQChatSessionEventType } from './session/registration.js';
|
|
14
14
|
const qqChatSessionEventRegistrations = await registerQQChatSessionEventType();
|
|
15
15
|
export const name = 'dsh-qqchat';
|
|
16
|
-
export const inject = ['connection', 'agents', 'agentDefaultModel', 'commands', 'llm', 'tools', 'workspaceRegistry'];
|
|
16
|
+
export const inject = ['connection', 'agents', 'agentDefaultModel', 'commands', 'llm', 'tools', 'webServer', 'workspaceRegistry'];
|
|
17
17
|
export function apply(ctx, inputConfig = {}) {
|
|
18
18
|
const config = resolveConfig(inputConfig);
|
|
19
19
|
mkdirSync(config.dataDir, { recursive: true, mode: 0o700 });
|
|
@@ -29,7 +29,7 @@ export function apply(ctx, inputConfig = {}) {
|
|
|
29
29
|
const media = new QQMediaStore(db, ctx, logger);
|
|
30
30
|
const bridge = new DshQQBridge(ctx, db, memory, config, logger);
|
|
31
31
|
const runtime = new QQChatRuntime(ctx, db, api, auth, memory, bridge, config, logger, media);
|
|
32
|
-
ctx.connection.rpc.handle('/qqchat', createQQChatRpc(runtime)
|
|
32
|
+
ctx.connection.rpc.handle('/qqchat', createQQChatRpc(runtime));
|
|
33
33
|
ctx.effect(() => {
|
|
34
34
|
void (async () => {
|
|
35
35
|
await runtime.start();
|
|
@@ -23,11 +23,13 @@ export declare class DshQQBridge {
|
|
|
23
23
|
private readonly activeMediaReadable;
|
|
24
24
|
private readonly memorySnapshots;
|
|
25
25
|
private readonly disposeEvent;
|
|
26
|
+
private readonly disposeAssistantStream;
|
|
26
27
|
private readonly disposeToolGate;
|
|
27
28
|
private readonly disposeCommands;
|
|
28
29
|
private readonly disposeImageTool;
|
|
29
30
|
private readonly disposeMediaTools;
|
|
30
31
|
constructor(ctx: Context, db: QQChatDatabase, memory: MemoryEngine, config: QQChatConfig, logger?: LoggerLike);
|
|
32
|
+
attachMappedSessionsToWorkspace(): Promise<void>;
|
|
31
33
|
dispose(): Promise<void>;
|
|
32
34
|
ensureChatSession(chatType: ChatType, row: GroupRow | MemberRow): Promise<string>;
|
|
33
35
|
recordTranscript(event: QQChatDisplayEvent, row: GroupRow | MemberRow, createSession?: boolean): Promise<string | undefined>;
|
|
@@ -37,6 +39,7 @@ export declare class DshQQBridge {
|
|
|
37
39
|
private restoreMemorySnapshot;
|
|
38
40
|
private ensureAgent;
|
|
39
41
|
private isArchived;
|
|
42
|
+
private attachToWorkspace;
|
|
40
43
|
private rememberRoute;
|
|
41
44
|
private appendDisplayIfMissing;
|
|
42
45
|
private appendOwnerMessageIfMissing;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
4
|
-
import {
|
|
4
|
+
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-presets';
|
|
5
5
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
6
6
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
7
7
|
import '../shared/augmentations.js';
|
|
@@ -12,7 +12,10 @@ import { dispatchQQCommand, qqCommandText, slashCommandName } from '../commands/
|
|
|
12
12
|
import { MEMORY_SNAPSHOT_TTL_MS, memorySnapshotHash, restoreMemorySnapshotState, shouldRefreshMemorySnapshot } from './memory-snapshot.js';
|
|
13
13
|
const DSH_RUNTIME_CONTEXT_SOURCE = '@deepseek-ai/dsh-system-prompt';
|
|
14
14
|
export function resolveQQSessionPreset(header, events) {
|
|
15
|
-
|
|
15
|
+
let current = agentPresetProjectionDefinition.init(header);
|
|
16
|
+
for (const event of events)
|
|
17
|
+
current = agentPresetProjectionDefinition.apply(current, event);
|
|
18
|
+
return current || undefined;
|
|
16
19
|
}
|
|
17
20
|
const MEDIA_TOOL_NAMES = new Set(['qqchat_describe_image', 'qqchat_read_file', 'qqchat_media_info']);
|
|
18
21
|
const packageMetadata = createRequire(import.meta.url)('../../package.json');
|
|
@@ -34,6 +37,7 @@ export class DshQQBridge {
|
|
|
34
37
|
activeMediaReadable = new Map();
|
|
35
38
|
memorySnapshots = new Map();
|
|
36
39
|
disposeEvent;
|
|
40
|
+
disposeAssistantStream;
|
|
37
41
|
disposeToolGate;
|
|
38
42
|
disposeCommands;
|
|
39
43
|
disposeImageTool;
|
|
@@ -45,6 +49,11 @@ export class DshQQBridge {
|
|
|
45
49
|
this.config = config;
|
|
46
50
|
this.logger = logger;
|
|
47
51
|
this.disposeEvent = ctx.on('session/event', (session, event) => this.onSessionEvent(session, event));
|
|
52
|
+
this.disposeAssistantStream = ctx.on('agent/assistant-stream', ({ agent, frame }) => {
|
|
53
|
+
if (frame.type !== 'chunk' || frame.chunk.type !== 'text-delta' || !frame.chunk.text)
|
|
54
|
+
return;
|
|
55
|
+
this.pending.get(String(agent.id))?.onTextDelta?.(frame.chunk.text);
|
|
56
|
+
});
|
|
48
57
|
this.disposeToolGate = ctx.on('tools/pre-execute', async (exec, next) => {
|
|
49
58
|
const agent = exec.agent;
|
|
50
59
|
if (!agent)
|
|
@@ -71,8 +80,19 @@ export class DshQQBridge {
|
|
|
71
80
|
this.disposeMediaTools = registerQQMediaTools(ctx, db, canReadMedia);
|
|
72
81
|
this.disposeCommands = this.registerCommands();
|
|
73
82
|
}
|
|
83
|
+
async attachMappedSessionsToWorkspace() {
|
|
84
|
+
const sessionIds = [
|
|
85
|
+
...this.db.listGroups().map(row => row.dsh_session_id),
|
|
86
|
+
...this.db.listDirectChats().map(row => row.dsh_session_id),
|
|
87
|
+
];
|
|
88
|
+
for (const sessionId of sessionIds) {
|
|
89
|
+
if (sessionId)
|
|
90
|
+
await this.attachToWorkspace(sessionId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
74
93
|
async dispose() {
|
|
75
94
|
this.disposeEvent();
|
|
95
|
+
this.disposeAssistantStream();
|
|
76
96
|
this.disposeToolGate();
|
|
77
97
|
this.disposeImageTool();
|
|
78
98
|
this.disposeMediaTools();
|
|
@@ -212,13 +232,6 @@ export class DshQQBridge {
|
|
|
212
232
|
pending.text = text;
|
|
213
233
|
return;
|
|
214
234
|
}
|
|
215
|
-
if (event.type === 'assistant/chunk') {
|
|
216
|
-
const pending = this.pending.get(id);
|
|
217
|
-
const chunk = event.data.chunk;
|
|
218
|
-
if (pending?.onTextDelta && chunk.type === 'text-delta' && chunk.text)
|
|
219
|
-
pending.onTextDelta(chunk.text);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
235
|
if (event.type === 'request/header') {
|
|
223
236
|
const { config } = event.data.header;
|
|
224
237
|
if (config.provider && config.model)
|
|
@@ -250,7 +263,7 @@ export class DshQQBridge {
|
|
|
250
263
|
this.memorySnapshots.set(sessionId, previous);
|
|
251
264
|
}
|
|
252
265
|
restoreMemorySnapshot(session) {
|
|
253
|
-
return restoreMemorySnapshotState(session.
|
|
266
|
+
return restoreMemorySnapshotState(session.snapshotEvents(), DSH_RUNTIME_CONTEXT_SOURCE);
|
|
254
267
|
}
|
|
255
268
|
async ensureAgent(chatType, row) {
|
|
256
269
|
let sessionId = this.db.getChatSession(chatType, Number(row.id));
|
|
@@ -273,6 +286,7 @@ export class DshQQBridge {
|
|
|
273
286
|
if (live) {
|
|
274
287
|
this.ensureSelection(live);
|
|
275
288
|
this.ensureTitle(live.session, chatType, row);
|
|
289
|
+
await this.attachToWorkspace(sessionId);
|
|
276
290
|
this.rememberRoute(chatType, row, live, sessionId);
|
|
277
291
|
return { agent: live, sessionId };
|
|
278
292
|
}
|
|
@@ -280,6 +294,7 @@ export class DshQQBridge {
|
|
|
280
294
|
if (resumed) {
|
|
281
295
|
this.ensureSelection(resumed.agent);
|
|
282
296
|
this.ensureTitle(resumed.agent.session, chatType, row);
|
|
297
|
+
await this.attachToWorkspace(sessionId);
|
|
283
298
|
this.rememberRoute(chatType, row, resumed.agent, sessionId);
|
|
284
299
|
return { agent: resumed.agent, sessionId };
|
|
285
300
|
}
|
|
@@ -297,6 +312,7 @@ export class DshQQBridge {
|
|
|
297
312
|
this.handles.set(sessionId, handle);
|
|
298
313
|
this.ensureSelection(handle.agent);
|
|
299
314
|
this.db.setChatSession(chatType, Number(row.id), sessionId);
|
|
315
|
+
await this.attachToWorkspace(sessionId);
|
|
300
316
|
this.ensureTitle(handle.agent.session, chatType, row);
|
|
301
317
|
this.rememberRoute(chatType, row, handle.agent, sessionId);
|
|
302
318
|
return { agent: handle.agent, sessionId };
|
|
@@ -305,6 +321,18 @@ export class DshQQBridge {
|
|
|
305
321
|
const registry = this.ctx.workspaceRegistry;
|
|
306
322
|
return registry?.archivedSessionIds.includes(sessionId) ?? false;
|
|
307
323
|
}
|
|
324
|
+
async attachToWorkspace(sessionId) {
|
|
325
|
+
const registry = this.ctx.workspaceRegistry;
|
|
326
|
+
if (!registry)
|
|
327
|
+
return;
|
|
328
|
+
try {
|
|
329
|
+
const workspace = await registry.resolveByPath(process.cwd());
|
|
330
|
+
await workspace?.attachSession(SessionId(sessionId));
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
this.logger.warn?.(`[dsh-qqchat] QQ session ${sessionId} 未能挂载到当前工作区: ${error instanceof Error ? error.message : String(error)}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
308
336
|
rememberRoute(chatType, row, agent, sessionId) {
|
|
309
337
|
const provider = agent.options.provider || this.config.provider;
|
|
310
338
|
const model = agent.options.model || this.config.model;
|
|
@@ -316,7 +344,7 @@ export class DshQQBridge {
|
|
|
316
344
|
this.memory.setMemberRoute(Number(row.id), provider, model, sessionId);
|
|
317
345
|
}
|
|
318
346
|
appendDisplayIfMissing(session, event) {
|
|
319
|
-
if (session.
|
|
347
|
+
if (session.snapshotEvents().some(item => item.type === 'qqchat/message' && item.data.messageId === event.messageId))
|
|
320
348
|
return;
|
|
321
349
|
// Keep this on the public Session.append signature. Official DSH releases
|
|
322
350
|
// do not yet expose the optional ignorable envelope marker; if an older
|
|
@@ -325,7 +353,7 @@ export class DshQQBridge {
|
|
|
325
353
|
session.append('qqchat/message', displayEventPayload(event));
|
|
326
354
|
}
|
|
327
355
|
appendOwnerMessageIfMissing(session, event) {
|
|
328
|
-
if (session.
|
|
356
|
+
if (session.snapshotEvents().some(item => {
|
|
329
357
|
if (item.type !== 'user/message')
|
|
330
358
|
return false;
|
|
331
359
|
const source = item.data.source;
|
|
@@ -506,7 +534,7 @@ export class DshQQBridge {
|
|
|
506
534
|
const available = await presets.list();
|
|
507
535
|
return { kind: 'success', text: available.length ? `可用 Agent Preset:\n${available.map(preset => `/qqpreset ${preset.id}`).join('\n')}` : '当前没有可用 Agent Preset。' };
|
|
508
536
|
}
|
|
509
|
-
if (agent.session.
|
|
537
|
+
if (agent.session.snapshotEvents().some(event => event.type === 'turn/start')) {
|
|
510
538
|
return { kind: 'error', text: '当前 Session 已经开始对话,DSH 不允许更换 Agent Preset。请先使用 /qqnew 开启新 Session。' };
|
|
511
539
|
}
|
|
512
540
|
try {
|
|
@@ -524,8 +552,9 @@ export class DshQQBridge {
|
|
|
524
552
|
}
|
|
525
553
|
statusCommand(agent) {
|
|
526
554
|
const current = this.selection(agent)?.current;
|
|
527
|
-
const
|
|
528
|
-
const
|
|
555
|
+
const events = agent.session.snapshotEvents();
|
|
556
|
+
const messages = events.filter(event => event.type === 'user/message' || event.type === 'assistant/message').length;
|
|
557
|
+
const last = events.at(-1);
|
|
529
558
|
return {
|
|
530
559
|
kind: 'success',
|
|
531
560
|
text: ['📊 Session 状态', `Session:${String(agent.id)}`, `状态:${agent.status === 'running' ? '生成中' : '空闲'}`, `模型:${current ? `${current.provider}/${current.model}` : '未知'}`, `消息数:${messages}`, `最后事件:${last?.type || '无'}`].join('\n'),
|
package/lib/session/runtime.js
CHANGED
|
@@ -28,6 +28,7 @@ export class QQChatRuntime {
|
|
|
28
28
|
this.media = media;
|
|
29
29
|
}
|
|
30
30
|
async start() {
|
|
31
|
+
await this.bridge.attachMappedSessionsToWorkspace();
|
|
31
32
|
for (const account of this.db.enabledAccounts())
|
|
32
33
|
this.startGateway(account);
|
|
33
34
|
this.outboxTimer = setInterval(() => void this.flushOutbox(), 3000);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-qqchat",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "DeepSeek Harness 的 QQ 私聊/群聊插件,支持扫码授权、原生 Session、群记忆与工具权限。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,26 +39,25 @@
|
|
|
39
39
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
40
40
|
"test": "tsx --test tests/*.test.ts",
|
|
41
41
|
"check": "npm run typecheck && npm test && npm run build",
|
|
42
|
-
"prepublishOnly": "npm run check"
|
|
43
|
-
"prepare": "npm run build"
|
|
42
|
+
"prepublishOnly": "npm run check"
|
|
44
43
|
},
|
|
45
44
|
"dependencies": {
|
|
46
45
|
"qrcode": "^1.5.4"
|
|
47
46
|
},
|
|
48
47
|
"peerDependencies": {
|
|
49
48
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
50
|
-
"@deepseek-ai/dsh-attachment": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
52
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
55
|
-
"@deepseek-ai/dsh-client-
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
60
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
49
|
+
"@deepseek-ai/dsh-attachment": "^0.1.6-alpha.1",
|
|
50
|
+
"@deepseek-ai/dsh-agent": "^0.1.6-alpha.1",
|
|
51
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.6-alpha.1",
|
|
52
|
+
"@deepseek-ai/dsh-commands": "^0.1.6-alpha.1",
|
|
53
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-client-modules": "^0.1.6-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.6-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.6-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-tools": "^0.1.6-alpha.1"
|
|
62
61
|
},
|
|
63
62
|
"peerDependenciesMeta": {
|
|
64
63
|
"@deepseek-ai/dsh-client-connection": {
|
|
@@ -67,7 +66,7 @@
|
|
|
67
66
|
"@deepseek-ai/dsh-client-ui-settings": {
|
|
68
67
|
"optional": true
|
|
69
68
|
},
|
|
70
|
-
"@deepseek-ai/dsh-client-
|
|
69
|
+
"@deepseek-ai/dsh-client-modules": {
|
|
71
70
|
"optional": true
|
|
72
71
|
},
|
|
73
72
|
"@deepseek-ai/dsh-client-ui-conversation": {
|
|
@@ -75,11 +74,12 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"devDependencies": {
|
|
78
|
-
"@deepseek-ai/dsh-attachment": "^0.1.
|
|
79
|
-
"@deepseek-ai/dsh-agent-default-model": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.
|
|
81
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
82
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
77
|
+
"@deepseek-ai/dsh-attachment": "^0.1.6-alpha.1",
|
|
78
|
+
"@deepseek-ai/dsh-agent-default-model": "^0.1.6-alpha.1",
|
|
79
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.6-alpha.1",
|
|
80
|
+
"@deepseek-ai/dsh-commands": "^0.1.6-alpha.1",
|
|
81
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.1",
|
|
82
|
+
"@deepseek-ai/dsh-client-modules": "^0.1.6-alpha.1",
|
|
83
83
|
"@types/node": "^22.18.0",
|
|
84
84
|
"@types/qrcode": "^1.5.5",
|
|
85
85
|
"@types/react": "^19.1.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"platform": "web",
|
|
95
95
|
"inject": [
|
|
96
96
|
"@deepseek-ai/dsh-client-connection",
|
|
97
|
-
"@deepseek-ai/dsh-client-
|
|
97
|
+
"@deepseek-ai/dsh-client-modules",
|
|
98
98
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
99
99
|
"@deepseek-ai/dsh-client-ui-settings"
|
|
100
100
|
]
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
},
|
|
103
103
|
"repository": {
|
|
104
104
|
"type": "git",
|
|
105
|
-
"url": "
|
|
105
|
+
"url": "https://github.com/Coffeiz/dsh-qqchat"
|
|
106
106
|
},
|
|
107
107
|
"keywords": [
|
|
108
108
|
"deepseek-harness",
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { LoggerLike, QQChatConfigInput } from './types.js'
|
|
|
20
20
|
const qqChatSessionEventRegistrations = await registerQQChatSessionEventType()
|
|
21
21
|
|
|
22
22
|
export const name = 'dsh-qqchat'
|
|
23
|
-
export const inject = ['connection', 'agents', 'agentDefaultModel', 'commands', 'llm', 'tools', 'workspaceRegistry'] as const
|
|
23
|
+
export const inject = ['connection', 'agents', 'agentDefaultModel', 'commands', 'llm', 'tools', 'webServer', 'workspaceRegistry'] as const
|
|
24
24
|
|
|
25
25
|
export function apply(ctx: Context, inputConfig: QQChatConfigInput = {}): void {
|
|
26
26
|
const config = resolveConfig(inputConfig)
|
|
@@ -38,7 +38,7 @@ export function apply(ctx: Context, inputConfig: QQChatConfigInput = {}): void {
|
|
|
38
38
|
const bridge = new DshQQBridge(ctx, db, memory, config, logger)
|
|
39
39
|
const runtime = new QQChatRuntime(ctx, db, api, auth, memory, bridge, config, logger, media)
|
|
40
40
|
|
|
41
|
-
ctx.connection.rpc.handle('/qqchat', createQQChatRpc(runtime)
|
|
41
|
+
ctx.connection.rpc.handle('/qqchat', createQQChatRpc(runtime))
|
|
42
42
|
ctx.effect(() => {
|
|
43
43
|
void (async () => {
|
|
44
44
|
await runtime.start()
|
|
@@ -3,7 +3,7 @@ import { createRequire } from 'node:module'
|
|
|
3
3
|
import type { Context } from '@deepseek-ai/cordis'
|
|
4
4
|
import { installModelSelection } from '@deepseek-ai/dsh-agent'
|
|
5
5
|
import type { AgentHandle, AgentOptions, AgentSetup, ModelSelectionRef } from '@deepseek-ai/dsh-agent'
|
|
6
|
-
import {
|
|
6
|
+
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-presets'
|
|
7
7
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
8
8
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
9
9
|
import type { CommandInvocation, CommandRuntime, CommandResult } from '@deepseek-ai/dsh-commands'
|
|
@@ -41,12 +41,15 @@ interface SessionPersistenceService { inspect(id: SessionId): Promise<{ meta: Se
|
|
|
41
41
|
interface SessionTitleService { get(session: Session): unknown; rename(session: Session, title: string): unknown }
|
|
42
42
|
interface WorkspaceRegistryService {
|
|
43
43
|
archivedSessionIds: readonly string[]
|
|
44
|
+
resolveByPath(path: string): Promise<{ attachSession(sessionId: import('@deepseek-ai/dsh-session').SessionId): Promise<void> } | undefined>
|
|
44
45
|
}
|
|
45
46
|
interface Composition { presetId?: string; setup?: AgentSetup }
|
|
46
47
|
interface ActiveActor { chatType: ChatType; senderId: string }
|
|
47
48
|
|
|
48
49
|
export function resolveQQSessionPreset(header: Session['header'], events: readonly SessionEvent[]): string | undefined {
|
|
49
|
-
|
|
50
|
+
let current = agentPresetProjectionDefinition.init(header)
|
|
51
|
+
for (const event of events) current = agentPresetProjectionDefinition.apply(current, event)
|
|
52
|
+
return current || undefined
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
const MEDIA_TOOL_NAMES = new Set(['qqchat_describe_image', 'qqchat_read_file', 'qqchat_media_info'])
|
|
@@ -65,6 +68,7 @@ export class DshQQBridge {
|
|
|
65
68
|
private readonly activeMediaReadable = new Map<string, boolean>()
|
|
66
69
|
private readonly memorySnapshots = new Map<string, MemorySnapshotState>()
|
|
67
70
|
private readonly disposeEvent: () => void
|
|
71
|
+
private readonly disposeAssistantStream: () => void
|
|
68
72
|
private readonly disposeToolGate: () => void
|
|
69
73
|
private readonly disposeCommands: () => void
|
|
70
74
|
private readonly disposeImageTool: () => void
|
|
@@ -78,6 +82,10 @@ export class DshQQBridge {
|
|
|
78
82
|
private readonly logger: LoggerLike = console,
|
|
79
83
|
) {
|
|
80
84
|
this.disposeEvent = ctx.on('session/event', (session, event) => this.onSessionEvent(session, event))
|
|
85
|
+
this.disposeAssistantStream = ctx.on('agent/assistant-stream', ({ agent, frame }) => {
|
|
86
|
+
if (frame.type !== 'chunk' || frame.chunk.type !== 'text-delta' || !frame.chunk.text) return
|
|
87
|
+
this.pending.get(String(agent.id))?.onTextDelta?.(frame.chunk.text)
|
|
88
|
+
})
|
|
81
89
|
this.disposeToolGate = ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {
|
|
82
90
|
const agent = exec.agent
|
|
83
91
|
if (!agent) return next()
|
|
@@ -100,8 +108,19 @@ export class DshQQBridge {
|
|
|
100
108
|
this.disposeCommands = this.registerCommands()
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
async attachMappedSessionsToWorkspace(): Promise<void> {
|
|
112
|
+
const sessionIds = [
|
|
113
|
+
...this.db.listGroups().map(row => row.dsh_session_id),
|
|
114
|
+
...this.db.listDirectChats().map(row => row.dsh_session_id),
|
|
115
|
+
]
|
|
116
|
+
for (const sessionId of sessionIds) {
|
|
117
|
+
if (sessionId) await this.attachToWorkspace(sessionId)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
103
121
|
async dispose(): Promise<void> {
|
|
104
122
|
this.disposeEvent()
|
|
123
|
+
this.disposeAssistantStream()
|
|
105
124
|
this.disposeToolGate()
|
|
106
125
|
this.disposeImageTool()
|
|
107
126
|
this.disposeMediaTools()
|
|
@@ -235,12 +254,6 @@ export class DshQQBridge {
|
|
|
235
254
|
if (text.trim()) pending.text = text
|
|
236
255
|
return
|
|
237
256
|
}
|
|
238
|
-
if (event.type === 'assistant/chunk') {
|
|
239
|
-
const pending = this.pending.get(id)
|
|
240
|
-
const chunk = event.data.chunk
|
|
241
|
-
if (pending?.onTextDelta && chunk.type === 'text-delta' && chunk.text) pending.onTextDelta(chunk.text)
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
257
|
if (event.type === 'request/header') {
|
|
245
258
|
const { config } = event.data.header
|
|
246
259
|
if (config.provider && config.model) this.routes.set(id, { provider: config.provider, model: config.model })
|
|
@@ -271,7 +284,7 @@ export class DshQQBridge {
|
|
|
271
284
|
}
|
|
272
285
|
|
|
273
286
|
private restoreMemorySnapshot(session: Session): MemorySnapshotState | undefined {
|
|
274
|
-
return restoreMemorySnapshotState(session.
|
|
287
|
+
return restoreMemorySnapshotState(session.snapshotEvents() as unknown as Parameters<typeof restoreMemorySnapshotState>[0], DSH_RUNTIME_CONTEXT_SOURCE)
|
|
275
288
|
}
|
|
276
289
|
|
|
277
290
|
private async ensureAgent(chatType: ChatType, row: GroupRow | MemberRow): Promise<{ agent: AgentHandle['agent']; sessionId: string }> {
|
|
@@ -292,6 +305,7 @@ export class DshQQBridge {
|
|
|
292
305
|
if (live) {
|
|
293
306
|
this.ensureSelection(live)
|
|
294
307
|
this.ensureTitle(live.session, chatType, row)
|
|
308
|
+
await this.attachToWorkspace(sessionId)
|
|
295
309
|
this.rememberRoute(chatType, row, live, sessionId)
|
|
296
310
|
return { agent: live, sessionId }
|
|
297
311
|
}
|
|
@@ -299,6 +313,7 @@ export class DshQQBridge {
|
|
|
299
313
|
if (resumed) {
|
|
300
314
|
this.ensureSelection(resumed.agent)
|
|
301
315
|
this.ensureTitle(resumed.agent.session, chatType, row)
|
|
316
|
+
await this.attachToWorkspace(sessionId)
|
|
302
317
|
this.rememberRoute(chatType, row, resumed.agent, sessionId)
|
|
303
318
|
return { agent: resumed.agent, sessionId }
|
|
304
319
|
}
|
|
@@ -317,6 +332,7 @@ export class DshQQBridge {
|
|
|
317
332
|
this.handles.set(sessionId, handle)
|
|
318
333
|
this.ensureSelection(handle.agent)
|
|
319
334
|
this.db.setChatSession(chatType, Number(row.id), sessionId)
|
|
335
|
+
await this.attachToWorkspace(sessionId)
|
|
320
336
|
this.ensureTitle(handle.agent.session, chatType, row)
|
|
321
337
|
this.rememberRoute(chatType, row, handle.agent, sessionId)
|
|
322
338
|
return { agent: handle.agent, sessionId }
|
|
@@ -327,6 +343,17 @@ export class DshQQBridge {
|
|
|
327
343
|
return registry?.archivedSessionIds.includes(sessionId) ?? false
|
|
328
344
|
}
|
|
329
345
|
|
|
346
|
+
private async attachToWorkspace(sessionId: string): Promise<void> {
|
|
347
|
+
const registry = (this.ctx as unknown as { workspaceRegistry?: WorkspaceRegistryService }).workspaceRegistry
|
|
348
|
+
if (!registry) return
|
|
349
|
+
try {
|
|
350
|
+
const workspace = await registry.resolveByPath(process.cwd())
|
|
351
|
+
await workspace?.attachSession(SessionId(sessionId))
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this.logger.warn?.(`[dsh-qqchat] QQ session ${sessionId} 未能挂载到当前工作区: ${error instanceof Error ? error.message : String(error)}`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
330
357
|
private rememberRoute(chatType: ChatType, row: GroupRow | MemberRow, agent: AgentHandle['agent'], sessionId: string): void {
|
|
331
358
|
const provider = agent.options.provider || this.config.provider
|
|
332
359
|
const model = agent.options.model || this.config.model
|
|
@@ -336,7 +363,7 @@ export class DshQQBridge {
|
|
|
336
363
|
}
|
|
337
364
|
|
|
338
365
|
private appendDisplayIfMissing(session: Session, event: QQChatDisplayEvent): void {
|
|
339
|
-
if (session.
|
|
366
|
+
if (session.snapshotEvents().some(item => item.type === 'qqchat/message' && item.data.messageId === event.messageId)) return
|
|
340
367
|
// Keep this on the public Session.append signature. Official DSH releases
|
|
341
368
|
// do not yet expose the optional ignorable envelope marker; if an older
|
|
342
369
|
// DSH cannot restore this plugin-only event, ensureAgent falls back to a
|
|
@@ -345,7 +372,7 @@ export class DshQQBridge {
|
|
|
345
372
|
}
|
|
346
373
|
|
|
347
374
|
private appendOwnerMessageIfMissing(session: Session, event: QQChatDisplayEvent): void {
|
|
348
|
-
if (session.
|
|
375
|
+
if (session.snapshotEvents().some(item => {
|
|
349
376
|
if (item.type !== 'user/message') return false
|
|
350
377
|
const source = item.data.source as unknown as { messageId?: string }
|
|
351
378
|
return source.messageId === event.messageId
|
|
@@ -521,7 +548,7 @@ export class DshQQBridge {
|
|
|
521
548
|
const available = await presets.list()
|
|
522
549
|
return { kind: 'success', text: available.length ? `可用 Agent Preset:\n${available.map(preset => `/qqpreset ${preset.id}`).join('\n')}` : '当前没有可用 Agent Preset。' }
|
|
523
550
|
}
|
|
524
|
-
if (agent.session.
|
|
551
|
+
if (agent.session.snapshotEvents().some(event => event.type === 'turn/start')) {
|
|
525
552
|
return { kind: 'error', text: '当前 Session 已经开始对话,DSH 不允许更换 Agent Preset。请先使用 /qqnew 开启新 Session。' }
|
|
526
553
|
}
|
|
527
554
|
try {
|
|
@@ -539,8 +566,9 @@ export class DshQQBridge {
|
|
|
539
566
|
|
|
540
567
|
private statusCommand(agent: AgentHandle['agent']): CommandResult {
|
|
541
568
|
const current = this.selection(agent)?.current
|
|
542
|
-
const
|
|
543
|
-
const
|
|
569
|
+
const events = agent.session.snapshotEvents()
|
|
570
|
+
const messages = events.filter(event => event.type === 'user/message' || event.type === 'assistant/message').length
|
|
571
|
+
const last = events.at(-1)
|
|
544
572
|
return {
|
|
545
573
|
kind: 'success',
|
|
546
574
|
text: ['📊 Session 状态', `Session:${String(agent.id)}`, `状态:${agent.status === 'running' ? '生成中' : '空闲'}`, `模型:${current ? `${current.provider}/${current.model}` : '未知'}`, `消息数:${messages}`, `最后事件:${last?.type || '无'}`].join('\n'),
|
package/src/session/runtime.ts
CHANGED
|
@@ -29,6 +29,7 @@ export class QQChatRuntime {
|
|
|
29
29
|
) {}
|
|
30
30
|
|
|
31
31
|
async start(): Promise<void> {
|
|
32
|
+
await this.bridge.attachMappedSessionsToWorkspace()
|
|
32
33
|
for (const account of this.db.enabledAccounts()) this.startGateway(account)
|
|
33
34
|
this.outboxTimer = setInterval(() => void this.flushOutbox(), 3000)
|
|
34
35
|
this.outboxTimer.unref?.()
|