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
@@ -0,0 +1,411 @@
1
+ # dsh-qqchat 架构
2
+
3
+ [English](./ARCHITECTURE.en.md)
4
+
5
+ ## 核心边界
6
+
7
+ `dsh-qqchat` 是独立的 DSH out-of-tree 插件,不 fork DeepSeek Harness。
8
+
9
+ ```text
10
+ dsh-qqchat
11
+ QQ protocol / auth / identity
12
+ QQ real history
13
+ receive policy
14
+ group + member memory
15
+ tool authority
16
+ proactive sending
17
+ QQ-specific Client presentation
18
+
19
+ DSH
20
+ Agent loop
21
+ Session lifecycle
22
+ model routing
23
+ tools
24
+ Session list
25
+ Conversation shell
26
+ ```
27
+
28
+ QQ Chat 不维护第二套 AgentLoop,也不维护第二套聊天导航。
29
+
30
+ ## Session 是主导航单位
31
+
32
+ 每个 QQ peer 映射一个正常 DSH Session:
33
+
34
+ ```text
35
+ group -> groups.dsh_session_id
36
+ c2c -> members.dsh_session_id
37
+
38
+ sessionId = qqchat-<uuid>
39
+ ```
40
+
41
+ 标题通过 DSH `sessionTitle` 写入:
42
+
43
+ ```text
44
+ QQ群 · <群名>
45
+ QQ私聊 · <昵称>
46
+ ```
47
+
48
+ Client 不再注册 `sidebar.footer.action` 会话选择器。QQ Session 直接由 DSH 自己的 Session/Workspace Browser 展示和打开。
49
+
50
+ DSH 用 `turn/start` 判断一个 Session 是否仍为 blank。只有静默记录的新 QQ peer 没有正常 Agent turn,因此插件创建 Session 后提交一次 `qq-chat-bootstrap` wake,并在 `agent/pre-step` 立即 reject。结果是:
51
+
52
+ - 有 durable turn boundary;
53
+ - Session 能进入 DSH 正常列表;
54
+ - 没有 model step;
55
+ - 不调用 LLM;
56
+ - 不发送 QQ 消息;
57
+ - bootstrap 不进入模型 history。
58
+
59
+ ## Settings 边界
60
+
61
+ `settings.section: qqchat` 只放控制面:
62
+
63
+ ```text
64
+ 扫码 / 连接状态
65
+ 自动回应 / @回复 / 静默记录
66
+ 群聊消息兼容格式
67
+ 私聊消息兼容格式
68
+ 群成员可用工具
69
+ Owner stable ID
70
+ 查看日志
71
+ ```
72
+
73
+ Settings 不包含群列表、私聊列表、聊天记录或记忆主界面。
74
+
75
+ ## Conversation 边界
76
+
77
+ QQ Session 使用 DSH 主 Conversation。
78
+
79
+ ```text
80
+ conversation.chat.node
81
+ qqchat-message # 仅静默/未触发 Agent 的 QQ transcript
82
+
83
+ conversation.composer
84
+ QQ Session composer # 主动发 QQ,不触发本地 Agent
85
+
86
+ conversation.session.header.utilities
87
+ QQ 记忆
88
+ ```
89
+
90
+ ### 触发 Agent 的 QQ 消息
91
+
92
+ 接收策略决定需要回复时,消息直接走 DSH 原生 `user/message`:
93
+
94
+ ```text
95
+ QQ inbound
96
+ -> SQLite
97
+ -> context snapshot
98
+ -> Agent.followup()
99
+ -> user/message
100
+ -> DSH Agent loop
101
+ -> assistant/tool events
102
+ -> QQ API
103
+ ```
104
+
105
+ Source 的 `kind` 保持 `user`,同时附带 QQ metadata:
106
+
107
+ ```text
108
+ channel=qq
109
+ botId
110
+ chatType
111
+ chatId
112
+ senderId
113
+ senderName
114
+ messageId
115
+ mentioned
116
+ ```
117
+
118
+ 因此 DSH Conversation 把它当普通 user turn;插件不再为同一条消息额外 append `qqchat/message`,避免双份显示。
119
+
120
+ ### 不触发 Agent 的 QQ 消息
121
+
122
+ `@回复` 下未 @Bot 的消息、以及 `静默记录` 下的群消息:
123
+
124
+ ```text
125
+ session.append('qqchat/message', ...)
126
+ ```
127
+
128
+ `qqchat/message` 是 log-only 自定义 Session event:
129
+
130
+ - Client 可渲染 QQ 群友气泡;
131
+ - 不属于 DSH model surface;
132
+ - 不会唤醒 Agent;
133
+ - 不会调用 LLM。
134
+
135
+ 插件不再从 SQLite 重放整段历史到 DSH Session。DSH Session 保存它实际经历过的 display/Agent events,完整 QQ 世界历史始终由 SQLite 负责。
136
+
137
+ ## Host / Client
138
+
139
+ ```text
140
+ DSH Web Client
141
+ │
142
+ │ Connection RPC /qqchat
143
+ ▼
144
+ ┌──────────────────────────────────────┐
145
+ │ dsh-qqchat Host │
146
+ │ │
147
+ │ QQBindService │
148
+ │ QQGateway ── QQApiClient │
149
+ │ │ │
150
+ │ ▼ │
151
+ │ QQChatRuntime │
152
+ │ │ │ │
153
+ │ │ ├── QQChatDatabase │
154
+ │ │ ├── MemoryEngine │
155
+ │ │ └── QQChatLogger │
156
+ │ ▼ │
157
+ │ DshQQBridge │
158
+ └───┬──────────────────────────────────┘
159
+ ▼
160
+ DSH Agent / Session / Tools
161
+ ```
162
+
163
+ QQ credential、Token、扫码 AES 临时 key 都只存在 Host。
164
+
165
+ ## 三个数据面
166
+
167
+ ### 1. QQ 真实数据面
168
+
169
+ SQLite 是 QQ 世界事实来源:
170
+
171
+ ```text
172
+ accounts
173
+ groups
174
+ members
175
+ group_members
176
+ messages
177
+ memory_documents
178
+ reflection_state
179
+ plugin_settings
180
+ outbox
181
+ ```
182
+
183
+ ### 2. DSH Session / UI 数据面
184
+
185
+ 记录:
186
+
187
+ - Agent 真正参与的 turn;
188
+ - assistant/tool events;
189
+ - 不唤醒 Agent 的 log-only QQ transcript;
190
+ - Session title 和 turn boundary。
191
+
192
+ ### 3. Model surface
193
+
194
+ 模型只看到 DSH 正常 surface 以及插件明确注入的 context snapshot。QQChat 的
195
+ 记忆快照使用 DSH 官方 `@deepseek-ai/dsh-system-prompt` runtime-context 来源,
196
+ 由 DSH 的 projection 机制替换旧快照,避免每轮重复追加完整记忆消息。
197
+
198
+ 完整群聊不会因为存在 SQLite 或 QQ UI transcript 就自动进入模型 history。
199
+
200
+ ## 入站状态机
201
+
202
+ ```text
203
+ QQ dispatch
204
+ ↓
205
+ normalize stable identity
206
+ ↓
207
+ upsert member/group
208
+ ↓
209
+ insert SQLite message
210
+ ↓
211
+ receive mode
212
+ ├─ silent
213
+ │ └─ qqchat/message -> stop
214
+ │
215
+ ├─ mention + not @
216
+ │ └─ qqchat/message -> stop
217
+ │
218
+ └─ auto / mentioned / c2c
219
+ ├─ assemble memory/context
220
+ ├─ Agent.followup(user message)
221
+ ├─ DSH tools/model loop
222
+ └─ QQ outbound
223
+ ```
224
+
225
+ 三种模式都会留下真实 QQ 历史并参与记忆活动;区别只是是否进入 Agent turn。
226
+
227
+ ## Identity
228
+
229
+ 稳定身份优先级:
230
+
231
+ ```text
232
+ author.user_openid
233
+ || author.member_openid
234
+ || author.id
235
+ ```
236
+
237
+ 原则:
238
+
239
+ ```text
240
+ senderId -> 身份 / 权限 / 记忆主键
241
+ senderName -> 仅展示
242
+ ```
243
+
244
+ 不根据昵称推断身份。
245
+
246
+ ## Agent context
247
+
248
+ 群聊每次 Agent turn 前从 SQLite 重新装配:
249
+
250
+ ```text
251
+ recent group history
252
+ + group profile
253
+ + group summary
254
+ + group memory
255
+ + group daily
256
+ + current member profile
257
+ + current member pattern
258
+ + current member summary
259
+ + stable sender/group metadata
260
+ ```
261
+
262
+ 当前 QQ 消息本身以正常 user prompt 进入 turn;可靠身份信息则由 source metadata 和 context snapshot 提供。私聊快照包含 member 的 profile、pattern、summary、daily 和 memory;群聊快照包含群级文档、当前成员文档和近期群聊记录。
263
+
264
+ ## 工具权限
265
+
266
+ 使用 DSH 官方:
267
+
268
+ ```text
269
+ tools/pre-execute
270
+ ```
271
+
272
+ 当前 QQ Agent turn 临时记录:
273
+
274
+ ```text
275
+ sessionId -> { chatType, senderId }
276
+ ```
277
+
278
+ 规则:
279
+
280
+ ```text
281
+ 非 QQ group turn -> next()
282
+ groupMembersCanUseTools == true -> next()
283
+ senderId == ownerUserId -> next()
284
+ 其他 -> deny
285
+ ```
286
+
287
+ 不修改 Tool Runtime 或 AgentLoop。
288
+
289
+ ## Memory scope
290
+
291
+ ### Group
292
+
293
+ ```text
294
+ profile
295
+ summary
296
+ daily
297
+ memory
298
+ ```
299
+
300
+ ### Member
301
+
302
+ ```text
303
+ profile
304
+ pattern
305
+ summary
306
+ ```
307
+
308
+ Group scope 不跨群;同一 Bot 下的 Member scope 可以跨群连续。
309
+
310
+ 反思以 idle debounce / batch threshold 触发,并使用 stable sender ID 做归属。
311
+
312
+ ## 记忆 UI
313
+
314
+ QQ Session 顶部 `QQ 记忆`:
315
+
316
+ 群聊:
317
+
318
+ ```text
319
+ group profile / summary / memory / daily
320
+ member list
321
+ -> 点击成员
322
+ -> member profile / pattern / summary / memory
323
+ ```
324
+
325
+ 私聊:
326
+
327
+ ```text
328
+ member profile / pattern / summary
329
+ ```
330
+
331
+ 私聊 `daily` 和 `memory` 仍属于 member scope 的持久化文档,参与反思和压缩,但当前 UI 尚未单独展示。
332
+
333
+ ## 主动发送
334
+
335
+ QQ Session composer 调用:
336
+
337
+ ```text
338
+ chat/send
339
+ ```
340
+
341
+ Host:
342
+
343
+ ```text
344
+ group -> /v2/groups/.../messages
345
+ c2c -> /v2/users/.../messages
346
+ ```
347
+
348
+ 主动发送直接进 QQ,不再作为本地 Agent prompt。
349
+
350
+ `outbox` 独立于 Session live 生命周期,留给定时/主动任务。
351
+
352
+ ## RPC
353
+
354
+ Host 注册 loopback RPC:
355
+
356
+ ```text
357
+ /qqchat
358
+ ```
359
+
360
+ 主要 endpoints:
361
+
362
+ ```text
363
+ status
364
+ auth/start
365
+ auth/poll
366
+ settings/get
367
+ settings/update
368
+ logs/list
369
+ chat/send
370
+ chat/info
371
+ ```
372
+
373
+ 旧的 `chats/list` / `chat/ensure` / `group/*` 仍可作为兼容 API,但当前主导航不再依赖它们。
374
+
375
+ ## SQLite 与 DSH persistence 的关系
376
+
377
+ ```text
378
+ qqchat.sqlite
379
+ QQ 真实历史 / identity / memory / settings / outbox
380
+
381
+ DSH Session persistence
382
+ Agent turns / model output / tools / DSH transcript
383
+ ```
384
+
385
+ 二者互不替代。
386
+
387
+ ## TypeScript 构建
388
+
389
+ ```text
390
+ src/
391
+ index.ts / config.ts / types.ts
392
+ commands/ DSH 原生命令的 QQ 分发适配
393
+ gateway/ QQ API、授权、Gateway、消息解析
394
+ session/ Agent bridge、Session runtime
395
+ transport/ DSH RPC
396
+ storage/ SQLite、记忆
397
+ shared/ 日志、DSH 类型扩展
398
+ client-src/*.cts
399
+ tests/*.test.ts
400
+ ```
401
+
402
+ Host 使用 `tsc`;Client 编译到临时 CJS 后由 `scripts/wrap-client.mjs` 包装成 DSH Client factory。
403
+
404
+ `lib/` 是生成物,不作为源码真相。
405
+
406
+ ## 开发与安全文档
407
+
408
+ - [开发与验证](DEVELOPMENT.md):安装、构建、测试、启动和浏览器验证。
409
+ - [记忆系统](MEMORY.md):scope、daily、反思和压缩契约。
410
+ - [安全与数据边界](SECURITY.md):凭据、身份、权限、日志和隔离。
411
+ - [开发日志](devlog.md):具体排查和设计决策。
@@ -0,0 +1,69 @@
1
+ # 开发与验证
2
+
3
+ ## 易读概述
4
+
5
+ QQChat 是 TypeScript 插件,Host 和 Client 一起构建。日常开发不需要单独运行一套 QQChat Web;插件应加载到 DSH Web 中验证。
6
+
7
+ ## 环境要求
8
+
9
+ - Node.js `^22.19.0 || >=24.0.0`
10
+ - 可用的 DSH workspace
11
+ - 需要真实网关验证时,准备 QQ 官方 Bot 凭据;凭据只放在本地 Host 配置中
12
+
13
+ 仓库当前不提交 `package-lock.json`,使用 `npm install` 安装依赖。
14
+
15
+ ## 本地安装与检查
16
+
17
+ ```bash
18
+ npm install
19
+ npm run typecheck
20
+ npm test
21
+ npm run build
22
+ ```
23
+
24
+ 完整检查:
25
+
26
+ ```bash
27
+ npm run check
28
+ ```
29
+
30
+ 构建步骤:
31
+
32
+ ```text
33
+ src/*.ts -> lib/*.js Host
34
+ client-src/*.cts -> 临时 CJS Client
35
+ 临时 CJS -> lib/client.js DSH ModuleLoader factory
36
+ ```
37
+
38
+ `lib/` 和临时 Client 构建目录是生成物,不是源码真相。
39
+
40
+ ## 启动 DSH Web
41
+
42
+ 在 DSH 仓库中:
43
+
44
+ ```bash
45
+ pnpm dsh --profile qqchat --no-open --port 3080
46
+ ```
47
+
48
+ 打开 `http://127.0.0.1:3080`,进入设置中的 QQ Chat,检查:
49
+
50
+ 1. 设置页面宽度、换行和弹窗滚动。
51
+ 2. 群聊接收的三个按钮状态。
52
+ 3. 群聊/私聊消息格式是否独立保存。
53
+ 4. 消息发送人的用户名、@用户名和引用显示。
54
+ 5. 记忆弹窗、日志弹窗在长内容下是否只在内部滚动。
55
+
56
+ 修改 Client 后必须重新执行 `npm run build`,再重启 DSH;仅刷新浏览器不能保证 Host 已加载新的 `lib/client.js`。
57
+
58
+ ## 测试约定
59
+
60
+ - 纯逻辑变化补充 `tests/*.test.ts` 回归测试。
61
+ - 测试名称说明触发路径和关键结果。
62
+ - 不通过删除断言、`skip` 或放宽校验来恢复绿色。
63
+ - 涉及真实 QQ Gateway、扫码、Session 恢复或 DSH Web 布局的变化,除了单测,还要做真实环境验证。
64
+
65
+ ## Git 约定
66
+
67
+ - 提交前运行 `git diff --check`。
68
+ - 不提交 `qqchat.sqlite`、日志、截图、凭据和临时构建目录。
69
+ - 不使用 force push;需要同步远端时保留已有历史。