dsh-qqchat 0.2.0

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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +276 -0
  3. package/README.md +211 -0
  4. package/client-src/plugin.cts +15 -0
  5. package/client-src/settings.cts +216 -0
  6. package/client-src/shared.cts +104 -0
  7. package/client-src/workspace.cts +164 -0
  8. package/cordis.patch.yml +7 -0
  9. package/docs/ARCHITECTURE.en.md +346 -0
  10. package/docs/ARCHITECTURE.md +411 -0
  11. package/docs/DEVELOPMENT.md +69 -0
  12. package/docs/MEDIA_AND_QUOTES.md +644 -0
  13. package/docs/MEMORY.md +99 -0
  14. package/docs/MEMORY_CONTEXT.md +254 -0
  15. package/docs/QUOTES.md +355 -0
  16. package/docs/README.md +22 -0
  17. package/docs/SECURITY.md +62 -0
  18. package/docs/STREAMING.md +347 -0
  19. package/docs/assets//346/211/253/347/240/201/351/223/276/346/216/245.jpg +0 -0
  20. package/docs/assets//347/276/244/350/201/212/347/225/214/351/235/242.jpg +0 -0
  21. package/docs/assets//350/256/260/345/277/206/347/263/273/347/273/237.jpg +0 -0
  22. package/docs/assets//350/256/276/347/275/256/347/225/214/351/235/242.jpg +0 -0
  23. package/docs/devlog.md +24 -0
  24. package/lib/client.js +433 -0
  25. package/lib/commands/dispatch.d.ts +16 -0
  26. package/lib/commands/dispatch.js +48 -0
  27. package/lib/config.d.ts +3 -0
  28. package/lib/config.js +63 -0
  29. package/lib/gateway/api.d.ts +30 -0
  30. package/lib/gateway/api.js +202 -0
  31. package/lib/gateway/auth.d.ts +35 -0
  32. package/lib/gateway/auth.js +80 -0
  33. package/lib/gateway/crypto.d.ts +2 -0
  34. package/lib/gateway/crypto.js +17 -0
  35. package/lib/gateway/gateway.d.ts +32 -0
  36. package/lib/gateway/gateway.js +219 -0
  37. package/lib/gateway/normalize.d.ts +6 -0
  38. package/lib/gateway/normalize.js +248 -0
  39. package/lib/index.d.ts +14 -0
  40. package/lib/index.js +35 -0
  41. package/lib/media/image-tool.d.ts +4 -0
  42. package/lib/media/image-tool.js +62 -0
  43. package/lib/media/media-tools.d.ts +3 -0
  44. package/lib/media/media-tools.js +68 -0
  45. package/lib/media/store.d.ts +14 -0
  46. package/lib/media/store.js +176 -0
  47. package/lib/session/agent-bridge.d.ts +56 -0
  48. package/lib/session/agent-bridge.js +613 -0
  49. package/lib/session/memory-snapshot.d.ts +23 -0
  50. package/lib/session/memory-snapshot.js +27 -0
  51. package/lib/session/runtime.d.ts +41 -0
  52. package/lib/session/runtime.js +395 -0
  53. package/lib/shared/augmentations.d.ts +40 -0
  54. package/lib/shared/augmentations.js +1 -0
  55. package/lib/shared/logging.d.ts +14 -0
  56. package/lib/shared/logging.js +41 -0
  57. package/lib/storage/db.d.ts +82 -0
  58. package/lib/storage/db.js +669 -0
  59. package/lib/storage/memory.d.ts +50 -0
  60. package/lib/storage/memory.js +566 -0
  61. package/lib/transport/rpc.d.ts +18 -0
  62. package/lib/transport/rpc.js +318 -0
  63. package/lib/types.d.ts +416 -0
  64. package/lib/types.js +1 -0
  65. package/package.json +115 -0
  66. package/scripts/wrap-client.mjs +20 -0
  67. package/src/commands/dispatch.ts +75 -0
  68. package/src/config.ts +70 -0
  69. package/src/gateway/api.ts +260 -0
  70. package/src/gateway/auth.ts +99 -0
  71. package/src/gateway/crypto.ts +16 -0
  72. package/src/gateway/gateway.ts +207 -0
  73. package/src/gateway/normalize.ts +236 -0
  74. package/src/index.ts +46 -0
  75. package/src/media/image-tool.ts +62 -0
  76. package/src/media/media-tools.ts +66 -0
  77. package/src/media/store.ts +157 -0
  78. package/src/session/agent-bridge.ts +624 -0
  79. package/src/session/memory-snapshot.ts +41 -0
  80. package/src/session/runtime.ts +368 -0
  81. package/src/shared/augmentations.ts +45 -0
  82. package/src/shared/logging.ts +36 -0
  83. package/src/storage/db.ts +760 -0
  84. package/src/storage/memory.ts +581 -0
  85. package/src/transport/rpc.ts +316 -0
  86. package/src/types.ts +456 -0
  87. package/tsconfig.build.json +12 -0
  88. package/tsconfig.client.json +14 -0
  89. package/tsconfig.json +23 -0
package/lib/types.d.ts ADDED
@@ -0,0 +1,416 @@
1
+ import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
2
+ export type ChatType = 'c2c' | 'group';
3
+ export type MessageDirection = 'inbound' | 'outbound';
4
+ export type MemoryScopeType = 'group' | 'member';
5
+ export type MemoryDocType = 'profile' | 'summary' | 'daily' | 'memory' | 'pattern';
6
+ export type ProfileEntryType = 'name' | 'address' | 'pronoun' | 'background' | 'preference' | 'note';
7
+ export interface ProfileEntry {
8
+ type: ProfileEntryType;
9
+ text: string;
10
+ ts: number;
11
+ }
12
+ export interface DailyEntry {
13
+ date: string;
14
+ note: string;
15
+ }
16
+ export type ReplyFormat = 'smart' | 'markdown' | 'compat';
17
+ export type GroupReceiveMode = 'auto' | 'mention' | 'silent';
18
+ export type GatewayStatus = 'offline' | 'connecting' | 'online' | 'error';
19
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
20
+ export type QQMediaKind = 'image' | 'audio' | 'video' | 'voice' | 'file';
21
+ export interface QQAttachmentInput {
22
+ sourceUrl?: string;
23
+ filename: string;
24
+ contentType?: string;
25
+ size?: number;
26
+ width?: number;
27
+ height?: number;
28
+ durationMs?: number;
29
+ platformFileId?: string;
30
+ quoted?: boolean;
31
+ kind?: QQMediaKind;
32
+ attachmentId?: string;
33
+ }
34
+ export interface QQQuoteInput {
35
+ messageId?: string;
36
+ senderId?: string;
37
+ senderName?: string;
38
+ text: string;
39
+ attachments: QQAttachmentInput[];
40
+ }
41
+ export interface StoredAttachmentSummary {
42
+ id: string;
43
+ kind: QQMediaKind;
44
+ filename: string;
45
+ contentType?: string;
46
+ sizeBytes: number;
47
+ quoted: boolean;
48
+ /** Platform file identity used to preserve one-to-one input mapping. */
49
+ sourceFileId?: string;
50
+ /** Position in the original QQ attachment list; used only as a lossless fallback. */
51
+ sourceIndex?: number;
52
+ localPath?: string;
53
+ imageRef?: ImageAttachmentRef;
54
+ }
55
+ export interface QQChatRuntimeSettings {
56
+ memoryEnabled: boolean;
57
+ groupReceiveMode: GroupReceiveMode;
58
+ groupReplyFormat: ReplyFormat;
59
+ directReplyFormat: ReplyFormat;
60
+ directStreamingEnabled: boolean;
61
+ groupMembersCanUseTools: boolean;
62
+ groupMembersCanReceiveMedia: boolean;
63
+ groupMembersCanReadMedia: boolean;
64
+ ownerUserId: string;
65
+ }
66
+ export interface QQChatRuntimeSettingsPatch {
67
+ memoryEnabled?: boolean;
68
+ groupReceiveMode?: GroupReceiveMode;
69
+ groupReplyFormat?: ReplyFormat;
70
+ directReplyFormat?: ReplyFormat;
71
+ directStreamingEnabled?: boolean;
72
+ groupMembersCanUseTools?: boolean;
73
+ groupMembersCanReceiveMedia?: boolean;
74
+ groupMembersCanReadMedia?: boolean;
75
+ ownerUserId?: string;
76
+ }
77
+ export interface PluginLogEntry {
78
+ id: number;
79
+ time: number;
80
+ level: LogLevel;
81
+ message: string;
82
+ }
83
+ export interface LoggerLike {
84
+ error?: (...args: unknown[]) => void;
85
+ warn?: (...args: unknown[]) => void;
86
+ info?: (...args: unknown[]) => void;
87
+ debug?: (...args: unknown[]) => void;
88
+ }
89
+ export interface QQChatConfigInput {
90
+ dataDir?: string;
91
+ source?: string;
92
+ sandbox?: boolean;
93
+ agentPreset?: string;
94
+ provider?: string;
95
+ model?: string;
96
+ maxTokens?: number;
97
+ groupChatEnabled?: boolean;
98
+ groupRequiresAt?: boolean;
99
+ groupReadEnabled?: boolean;
100
+ replyFormat?: ReplyFormat;
101
+ recentGroupMessages?: number;
102
+ reflectionIdleMs?: number;
103
+ reflectionBatchSize?: number;
104
+ reflectionMaxMessages?: number;
105
+ memoryMaxTokens?: number;
106
+ memoryCompressionMaxTokens?: number;
107
+ }
108
+ export interface QQChatConfig {
109
+ readonly dataDir: string;
110
+ readonly source: string;
111
+ readonly sandbox: boolean;
112
+ readonly agentPreset?: string;
113
+ readonly provider?: string;
114
+ readonly model?: string;
115
+ readonly maxTokens?: number;
116
+ readonly groupChatEnabled: boolean;
117
+ readonly groupRequiresAt: boolean;
118
+ readonly groupReadEnabled: boolean;
119
+ readonly replyFormat: ReplyFormat;
120
+ readonly recentGroupMessages: number;
121
+ readonly reflectionIdleMs: number;
122
+ readonly reflectionBatchSize: number;
123
+ readonly reflectionMaxMessages: number;
124
+ readonly memoryMaxTokens: number;
125
+ readonly memoryCompressionMaxTokens: number;
126
+ }
127
+ export interface AccountRow {
128
+ id: number;
129
+ app_id: string;
130
+ app_secret: string;
131
+ bot_user_id: string | null;
132
+ enabled: 0 | 1;
133
+ sandbox: 0 | 1;
134
+ gateway_status: GatewayStatus;
135
+ gateway_last_error: string | null;
136
+ created_at: number;
137
+ updated_at: number;
138
+ }
139
+ export interface PublicAccountRow extends Omit<AccountRow, 'app_secret'> {
140
+ }
141
+ export interface AuthTaskRow {
142
+ task_id: string;
143
+ aes_key: string;
144
+ expires_at: number;
145
+ }
146
+ export interface GroupRow {
147
+ id: number;
148
+ account_id: number;
149
+ platform_group_id: string;
150
+ name: string | null;
151
+ enabled: 0 | 1;
152
+ read_enabled: 0 | 1;
153
+ dsh_session_id: string | null;
154
+ created_at: number;
155
+ updated_at: number;
156
+ }
157
+ export interface GroupListRow extends GroupRow {
158
+ last_message_at: number | null;
159
+ member_count: number;
160
+ message_count: number;
161
+ }
162
+ export interface DirectChatListRow extends MemberRow {
163
+ last_message_at: number | null;
164
+ message_count: number;
165
+ }
166
+ export interface ChatTargetRow {
167
+ chatType: ChatType;
168
+ rowId: number;
169
+ accountId: number;
170
+ platformId: string;
171
+ displayName: string;
172
+ dshSessionId: string | null;
173
+ lastMessageAt: number | null;
174
+ messageCount: number;
175
+ }
176
+ export interface GroupDefaults {
177
+ name?: string;
178
+ enabled?: boolean;
179
+ readEnabled?: boolean;
180
+ }
181
+ export interface GroupPatch {
182
+ name?: string;
183
+ enabled?: boolean;
184
+ readEnabled?: boolean;
185
+ }
186
+ export interface MemberRow {
187
+ id: number;
188
+ account_id: number;
189
+ platform_user_id: string;
190
+ display_name: string | null;
191
+ dsh_session_id: string | null;
192
+ first_seen_at: number;
193
+ last_seen_at: number;
194
+ }
195
+ export interface GroupMemberRow {
196
+ id: number;
197
+ platform_user_id: string;
198
+ global_display_name: string | null;
199
+ display_name: string | null;
200
+ first_seen_at: number;
201
+ last_seen_at: number;
202
+ aliases_json: string | null;
203
+ nicknames_json: string | null;
204
+ message_count: number;
205
+ }
206
+ export interface MessageRow {
207
+ id: number;
208
+ account_id: number;
209
+ platform_message_id: string | null;
210
+ chat_type: ChatType;
211
+ group_id: number | null;
212
+ member_id: number | null;
213
+ direction: MessageDirection;
214
+ content: string;
215
+ quoted_text: string | null;
216
+ mentioned: 0 | 1;
217
+ created_at: number;
218
+ raw_json: string | null;
219
+ attachments_json: string | null;
220
+ quote_json: string | null;
221
+ platform_user_id?: string | null;
222
+ display_name?: string | null;
223
+ }
224
+ export interface InsertMessageInput {
225
+ accountId: number;
226
+ platformMessageId?: string;
227
+ chatType: ChatType;
228
+ groupId?: number;
229
+ memberId?: number;
230
+ direction: MessageDirection;
231
+ content: string;
232
+ quotedText?: string;
233
+ mentioned?: boolean;
234
+ createdAt?: number;
235
+ raw?: unknown;
236
+ attachments?: StoredAttachmentSummary[];
237
+ quote?: QQQuoteInput;
238
+ }
239
+ export interface ReflectionStateRow {
240
+ group_id: number;
241
+ last_message_id: number;
242
+ last_reflected_at: number | null;
243
+ }
244
+ export interface OutboxRow {
245
+ id: number;
246
+ account_id: number;
247
+ chat_type: ChatType;
248
+ target_id: string;
249
+ content: string;
250
+ status: 'pending' | 'sent' | 'failed';
251
+ scheduled_at: number;
252
+ sent_at: number | null;
253
+ error: string | null;
254
+ }
255
+ export type MemoryDocuments = Partial<Record<MemoryDocType, string>>;
256
+ export interface QQChatDisplayEvent {
257
+ messageId: string;
258
+ chatType: ChatType;
259
+ chatId: string;
260
+ direction: MessageDirection;
261
+ senderId: string;
262
+ senderName: string;
263
+ isOwner?: boolean;
264
+ content: string;
265
+ quotedText: string;
266
+ mentioned: boolean;
267
+ createdAt: number;
268
+ sessionId?: string;
269
+ attachments?: StoredAttachmentSummary[];
270
+ quote?: QQQuoteInput;
271
+ }
272
+ export interface QQNormalizedMessage {
273
+ platform: 'qq';
274
+ accountId: number;
275
+ chatType: ChatType;
276
+ chatId: string;
277
+ senderId: string;
278
+ senderName: string;
279
+ groupOpenid?: string;
280
+ messageId: string;
281
+ msgIdx?: string;
282
+ refMsgIdx?: string;
283
+ text: string;
284
+ quotedText: string;
285
+ attachments: QQAttachmentInput[];
286
+ quote?: QQQuoteInput;
287
+ mentioned: boolean;
288
+ botMentionId: string;
289
+ raw: Record<string, unknown>;
290
+ }
291
+ export interface QQQuoteIndexInput {
292
+ accountId: number;
293
+ chatType: ChatType;
294
+ chatId: string;
295
+ msgIdx: string;
296
+ platformMessageId?: string;
297
+ senderId: string;
298
+ senderName?: string;
299
+ content: string;
300
+ attachments: QQAttachmentInput[];
301
+ createdAt: number;
302
+ expiresAt: number;
303
+ }
304
+ export interface QQQuoteIndexRow extends QQQuoteIndexInput {
305
+ id: number;
306
+ }
307
+ export interface QQAuthor {
308
+ user_openid?: string;
309
+ member_openid?: string;
310
+ id?: string;
311
+ username?: string;
312
+ nickname?: string;
313
+ [key: string]: unknown;
314
+ }
315
+ export interface QQMention {
316
+ bot?: boolean;
317
+ is_you?: boolean;
318
+ id?: string;
319
+ user_openid?: string;
320
+ member_openid?: string;
321
+ openid?: string;
322
+ [key: string]: unknown;
323
+ }
324
+ export interface QQDispatchData extends Record<string, unknown> {
325
+ id?: string;
326
+ content?: string;
327
+ group_openid?: string;
328
+ author?: QQAuthor;
329
+ mentions?: QQMention[];
330
+ message_reference?: Record<string, unknown>;
331
+ reference?: Record<string, unknown>;
332
+ quote?: Record<string, unknown>;
333
+ msg_elements?: Array<Record<string, unknown>>;
334
+ message_scene?: {
335
+ ext?: unknown[];
336
+ };
337
+ attachments?: Array<Record<string, unknown>>;
338
+ }
339
+ export interface QQAccessTokenPayload {
340
+ access_token?: string;
341
+ expires_in?: number | string;
342
+ [key: string]: unknown;
343
+ }
344
+ export interface QQGatewayPayload {
345
+ url?: string;
346
+ [key: string]: unknown;
347
+ }
348
+ export interface QQBindCreatePayload {
349
+ retcode?: number;
350
+ msg?: string;
351
+ data?: {
352
+ task_id?: string;
353
+ };
354
+ }
355
+ export interface QQBindPollPayload {
356
+ retcode?: number;
357
+ msg?: string;
358
+ data?: {
359
+ status?: number;
360
+ bot_appid?: string | number;
361
+ bot_encrypt_secret?: string;
362
+ };
363
+ }
364
+ export interface QQGatewayFrame {
365
+ op: number;
366
+ s?: number;
367
+ t?: string;
368
+ d?: Record<string, unknown> & {
369
+ heartbeat_interval?: number;
370
+ session_id?: string;
371
+ user?: {
372
+ id?: string;
373
+ openid?: string;
374
+ };
375
+ };
376
+ }
377
+ export interface QQSendOptions {
378
+ group?: boolean;
379
+ messageId?: string | null;
380
+ format?: ReplyFormat;
381
+ }
382
+ export interface ModelRoute {
383
+ provider: string;
384
+ model: string;
385
+ sessionId?: string;
386
+ }
387
+ export interface PendingReply {
388
+ text: string;
389
+ onTextDelta?: (delta: string) => void;
390
+ }
391
+ export interface ReflectionGroupUpdate {
392
+ profile?: unknown;
393
+ summary?: unknown;
394
+ daily?: unknown;
395
+ memory?: unknown;
396
+ }
397
+ export interface ReflectionMemberUpdate {
398
+ senderId?: unknown;
399
+ profile?: unknown;
400
+ pattern?: unknown;
401
+ summary?: unknown;
402
+ memory?: unknown;
403
+ nicknames?: unknown;
404
+ }
405
+ export interface ReflectionPayload {
406
+ group?: ReflectionGroupUpdate;
407
+ members?: ReflectionMemberUpdate[];
408
+ }
409
+ export interface MemoryViewMember extends GroupMemberRow {
410
+ memory: MemoryDocuments;
411
+ }
412
+ export interface MemoryView {
413
+ group: GroupRow;
414
+ groupMemory: MemoryDocuments;
415
+ members: MemoryViewMember[];
416
+ }
package/lib/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,115 @@
1
+ {
2
+ "name": "dsh-qqchat",
3
+ "version": "0.2.0",
4
+ "description": "DeepSeek Harness 的 QQ 私聊/群聊插件,支持扫码授权、原生 Session、群记忆与工具权限。",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": "^22.19.0 || >=24.0.0"
9
+ },
10
+ "main": "./lib/index.js",
11
+ "types": "./lib/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./lib/index.d.ts",
15
+ "default": "./lib/index.js"
16
+ },
17
+ "./client": "./lib/client.js",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "lib",
22
+ "src",
23
+ "client-src",
24
+ "cordis.patch.yml",
25
+ "README.md",
26
+ "README.en.md",
27
+ "docs",
28
+ "scripts",
29
+ "tsconfig.json",
30
+ "tsconfig.build.json",
31
+ "tsconfig.client.json"
32
+ ],
33
+ "scripts": {
34
+ "clean": "node -e \"const fs=require('node:fs');fs.rmSync('lib',{recursive:true,force:true});fs.rmSync('.client-build',{recursive:true,force:true})\"",
35
+ "build:host": "tsc -p tsconfig.build.json",
36
+ "build:client": "tsc -p tsconfig.client.json && node scripts/wrap-client.mjs",
37
+ "build": "npm run clean && npm run build:host && npm run build:client",
38
+ "typecheck": "tsc -p tsconfig.json --noEmit",
39
+ "test": "tsx --test tests/*.test.ts",
40
+ "check": "npm run typecheck && npm test && npm run build",
41
+ "prepublishOnly": "npm run check",
42
+ "prepare": "npm run build"
43
+ },
44
+ "dependencies": {
45
+ "qrcode": "^1.5.4"
46
+ },
47
+ "peerDependencies": {
48
+ "@deepseek-ai/cordis": "^4.0.1",
49
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.8",
50
+ "@deepseek-ai/dsh-commands": "^0.1.0-rc.8",
51
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.8",
52
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
53
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.8",
54
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8",
55
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.8",
56
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
57
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
58
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.8"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@deepseek-ai/dsh-client-connection": {
62
+ "optional": true
63
+ },
64
+ "@deepseek-ai/dsh-client-ui-settings": {
65
+ "optional": true
66
+ },
67
+ "@deepseek-ai/dsh-client-runtime": {
68
+ "optional": true
69
+ },
70
+ "@deepseek-ai/dsh-client-ui-conversation": {
71
+ "optional": true
72
+ }
73
+ },
74
+ "devDependencies": {
75
+ "@deepseek-ai/dsh-agent-default-model": "^0.1.0-rc.8",
76
+ "@deepseek-ai/dsh-commands": "^0.1.0-rc.8",
77
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.8",
78
+ "@types/node": "^22.18.0",
79
+ "@types/qrcode": "^1.5.5",
80
+ "@types/react": "^19.1.0",
81
+ "tsx": "^4.20.0",
82
+ "typescript": "^5.9.0"
83
+ },
84
+ "dsh": {
85
+ "bundle": {
86
+ "patch": "./cordis.patch.yml"
87
+ },
88
+ "client": {
89
+ "platform": "web",
90
+ "inject": [
91
+ "@deepseek-ai/dsh-client-connection",
92
+ "@deepseek-ai/dsh-client-runtime",
93
+ "@deepseek-ai/dsh-client-ui-conversation",
94
+ "@deepseek-ai/dsh-client-ui-settings"
95
+ ]
96
+ }
97
+ },
98
+ "repository": {
99
+ "type": "git",
100
+ "url": "git+ssh://git@github.com/Coffeiz/dsh-qqchat.git"
101
+ },
102
+ "keywords": [
103
+ "deepseek-harness",
104
+ "dsh",
105
+ "qq",
106
+ "qqbot",
107
+ "chat",
108
+ "memory",
109
+ "cordis",
110
+ "plugin"
111
+ ],
112
+ "publishConfig": {
113
+ "access": "public"
114
+ }
115
+ }
@@ -0,0 +1,20 @@
1
+ import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'
2
+ import { relative, resolve, sep } from 'node:path'
3
+
4
+ const root = resolve('.client-build')
5
+ const names = (await readdir(root, { recursive: true }))
6
+ .filter(name => name.endsWith('.cjs'))
7
+ .sort()
8
+
9
+ const modules = []
10
+ for (const name of names) {
11
+ const absolute = resolve(root, name)
12
+ const id = `./${relative(root, absolute).split(sep).join('/')}`
13
+ const body = await readFile(absolute, 'utf8')
14
+ modules.push(`${JSON.stringify(id)}: function(module, exports, require) {\n${body}\n}`)
15
+ }
16
+
17
+ const wrapped = `window.__ModuleLoader__.load({ id: "dsh-qqchat", factory: (require) => {\nvar __qqModules = {\n${modules.join(',\n')}\n};\nvar __qqCache = Object.create(null);\nfunction __qqRequire(id) {\n var factory = __qqModules[id];\n if (!factory) return require(id);\n var cached = __qqCache[id];\n if (cached) return cached.exports;\n var module = { exports: {} };\n __qqCache[id] = module;\n factory(module, module.exports, __qqRequire);\n return module.exports;\n}\nreturn __qqRequire("./plugin.cjs");\n} })\n`
18
+ await mkdir('lib', { recursive: true })
19
+ await writeFile('lib/client.js', wrapped, 'utf8')
20
+ await rm('.client-build', { recursive: true, force: true })
@@ -0,0 +1,75 @@
1
+ import type { Agent } from '@deepseek-ai/dsh-agent'
2
+ import type { CommandDescriptor, CommandExecution, CommandRuntime } from '@deepseek-ai/dsh-commands'
3
+
4
+ export interface QQCommandReply {
5
+ handled: boolean
6
+ text?: string
7
+ }
8
+
9
+ export interface QQCommandScope {
10
+ chatType: 'group' | 'c2c'
11
+ isOwner: boolean
12
+ }
13
+
14
+ const READ_ONLY_GROUP_COMMANDS = new Set([
15
+ 'help', 'qqhelp', 'qqcommands', 'qqping', 'qqversion', 'qqstatus',
16
+ ])
17
+
18
+ /** Normalize a QQ command that may be prefixed by the bot mention. */
19
+ export function qqCommandText(text: string, mentioned: boolean): string | undefined {
20
+ const input = text.trim()
21
+ if (/^\/[a-z][a-z0-9_-]*(?:\s|$)/u.test(input)) return input
22
+ if (!mentioned) return undefined
23
+ const prefixed = /^(?:<@!?[^>]+>|@[^\s]+)\s*(\/[a-z][a-z0-9_-]*(?:\s|$).*)$/su.exec(input)
24
+ return prefixed?.[1]?.trim()
25
+ }
26
+
27
+ /** Return the command name from a complete slash-command line. */
28
+ export function slashCommandName(input: string): string | undefined {
29
+ const match = /^\/([a-z][a-z0-9_-]*)(?=\s|$)/u.exec(input)
30
+ return match?.[1]
31
+ }
32
+
33
+ function renderCommandList(commands: readonly CommandDescriptor[]): string {
34
+ if (commands.length === 0) return '当前 DSH 没有可用命令。'
35
+ return ['可用 DSH 命令:', ...commands.map(command => {
36
+ const hint = command.input?.hint ? ` ${command.input.hint}` : ''
37
+ return `/${command.name}${hint} — ${command.description}`
38
+ })].join('\n')
39
+ }
40
+
41
+ /** Dispatch one QQ slash command through DSH's native command registry. */
42
+ export async function dispatchQQCommand(
43
+ commands: CommandRuntime,
44
+ agent: Agent,
45
+ input: string,
46
+ scope: QQCommandScope = { chatType: 'c2c', isOwner: true },
47
+ ): Promise<QQCommandReply> {
48
+ if (!input.startsWith('/')) return { handled: false }
49
+
50
+ const name = slashCommandName(input)
51
+ if (name === undefined) return { handled: true, text: '命令格式无效。发送 /help 查看可用命令。' }
52
+
53
+ // Native commands bypass the tools/pre-execute seam. Keep group control
54
+ // commands owner-only so they cannot change or stop a shared group Session.
55
+ if (scope.chatType === 'group' && !scope.isOwner && !READ_ONLY_GROUP_COMMANDS.has(name)) {
56
+ return { handled: true, text: '普通群成员只能使用查询类命令。' }
57
+ }
58
+
59
+ // QQ has no browser download channel for the Web-only command.
60
+ if (name === 'export' && commands.find(agent, name) !== undefined) {
61
+ return { handled: true, text: '/export 仅支持 DSH Web 下载会话日志,QQ 中暂不支持。' }
62
+ }
63
+
64
+ const execution: CommandExecution | undefined = await commands.execute(
65
+ agent,
66
+ input,
67
+ [],
68
+ new AbortController().signal,
69
+ )
70
+ if (execution === undefined) {
71
+ return { handled: true, text: `未知或格式无效的命令:/${name}\n\n${renderCommandList(commands.list(agent))}` }
72
+ }
73
+
74
+ return { handled: true, text: execution.result.text?.trim() || `/${name} 已执行。` }
75
+ }
package/src/config.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { homedir } from 'node:os'
2
+ import { join, resolve } from 'node:path'
3
+ import type { GroupReceiveMode, QQChatConfig, QQChatConfigInput, QQChatRuntimeSettings, ReplyFormat } from './types.js'
4
+
5
+ const DEFAULT_CONFIG = Object.freeze({
6
+ source: 'dsh-qqchat',
7
+ sandbox: false,
8
+ groupChatEnabled: true,
9
+ groupRequiresAt: true,
10
+ groupReadEnabled: true,
11
+ replyFormat: 'smart' as ReplyFormat,
12
+ recentGroupMessages: 40,
13
+ reflectionIdleMs: 120_000,
14
+ reflectionBatchSize: 20,
15
+ reflectionMaxMessages: 80,
16
+ memoryMaxTokens: 1400,
17
+ memoryCompressionMaxTokens: 15000,
18
+ })
19
+
20
+ export function resolveConfig(input: QQChatConfigInput = {}): QQChatConfig {
21
+ const home = process.env.DSH_HOME || join(homedir(), '.dsh')
22
+ const replyFormat: ReplyFormat = ['smart', 'markdown', 'compat'].includes(input.replyFormat ?? '')
23
+ ? input.replyFormat as ReplyFormat
24
+ : DEFAULT_CONFIG.replyFormat
25
+
26
+ return Object.freeze({
27
+ dataDir: resolve(input.dataDir || join(home, 'plugins', 'dsh-qqchat')),
28
+ source: input.source || DEFAULT_CONFIG.source,
29
+ sandbox: input.sandbox === true,
30
+ agentPreset: input.agentPreset,
31
+ provider: input.provider,
32
+ model: input.model,
33
+ maxTokens: positiveIntOrUndefined(input.maxTokens),
34
+ groupChatEnabled: input.groupChatEnabled !== false,
35
+ groupRequiresAt: input.groupRequiresAt !== false,
36
+ groupReadEnabled: input.groupReadEnabled !== false,
37
+ replyFormat,
38
+ recentGroupMessages: positiveInt(input.recentGroupMessages, DEFAULT_CONFIG.recentGroupMessages),
39
+ reflectionIdleMs: positiveInt(input.reflectionIdleMs, DEFAULT_CONFIG.reflectionIdleMs),
40
+ reflectionBatchSize: positiveInt(input.reflectionBatchSize, DEFAULT_CONFIG.reflectionBatchSize),
41
+ reflectionMaxMessages: positiveInt(input.reflectionMaxMessages, DEFAULT_CONFIG.reflectionMaxMessages),
42
+ memoryMaxTokens: positiveInt(input.memoryMaxTokens, DEFAULT_CONFIG.memoryMaxTokens),
43
+ memoryCompressionMaxTokens: positiveInt(input.memoryCompressionMaxTokens, DEFAULT_CONFIG.memoryCompressionMaxTokens),
44
+ })
45
+ }
46
+
47
+ export function defaultRuntimeSettings(config: QQChatConfig): QQChatRuntimeSettings {
48
+ const groupReceiveMode: GroupReceiveMode = !config.groupChatEnabled
49
+ ? 'silent'
50
+ : config.groupRequiresAt ? 'mention' : 'auto'
51
+ return {
52
+ memoryEnabled: true,
53
+ groupReceiveMode,
54
+ groupReplyFormat: 'compat',
55
+ directReplyFormat: config.replyFormat,
56
+ directStreamingEnabled: config.replyFormat !== 'compat',
57
+ groupMembersCanUseTools: false,
58
+ groupMembersCanReceiveMedia: true,
59
+ groupMembersCanReadMedia: false,
60
+ ownerUserId: '',
61
+ }
62
+ }
63
+
64
+ function positiveInt(value: unknown, fallback: number): number {
65
+ return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback
66
+ }
67
+
68
+ function positiveIntOrUndefined(value: unknown): number | undefined {
69
+ return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : undefined
70
+ }