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/docs/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # dsh-qqchat 文档导航
2
+
3
+ ## 易读概述
4
+
5
+ QQChat 是 DSH 的 out-of-tree QQ 官方 Bot 插件。文档按“如何开发、数据如何保存、边界如何保证”组织。
6
+
7
+ ## 专业细节
8
+
9
+ | 文档 | 内容 |
10
+ | --- | --- |
11
+ | [ARCHITECTURE.md](ARCHITECTURE.md) | DSH、QQ Gateway、SQLite、Agent、Client 的职责边界 |
12
+ | [DEVELOPMENT.md](DEVELOPMENT.md) | 安装、构建、测试、启动和 UI 验证 |
13
+ | [MEMORY.md](MEMORY.md) | group/member scope、daily、memory、反思和压缩 |
14
+ | [MEMORY_CONTEXT.md](MEMORY_CONTEXT.md) | DSH Session 内的记忆快照、TTL、compact 刷新和注入生命周期 |
15
+ | [MEDIA_AND_QUOTES.md](MEDIA_AND_QUOTES.md) | 媒体输入、引用消息、附件存储、安全和分阶段实现方案 |
16
+ | [QUOTES.md](QUOTES.md) | QQ 引用索引、引用回填、附件复用和 Web 展示实施方案 |
17
+ | [STREAMING.md](STREAMING.md) | QQ 私聊官方流式传输、队列、降级和故障复盘 |
18
+ | [SECURITY.md](SECURITY.md) | 凭据、身份、权限、日志和数据隔离 |
19
+ | [CHANGELOG.md](../CHANGELOG.md) | 面向用户的简短变更记录 |
20
+ | [devlog.md](devlog.md) | 具体排查、决策和踩坑记录 |
21
+
22
+ 根目录 [AGENTS.md](../AGENTS.md) 是修改代码前必须遵守的通用约定。
@@ -0,0 +1,62 @@
1
+ # 安全与数据边界
2
+
3
+ ## 易读概述
4
+
5
+ QQChat 同时接触 QQ 凭据、用户身份、群消息和 DSH Session。安全原则是:凭据留在 Host,身份使用 stable ID,数据按 account/group/member 隔离,日志不泄漏聊天正文。
6
+
7
+ ## 凭据边界
8
+
9
+ - AppSecret、QQ Token、扫码 AES 临时 key 只存在 Host。
10
+ - 浏览器只通过受限 RPC 获取状态和已经脱敏的展示数据。
11
+ - 凭据不写入 URL、日志、前端状态、测试 fixture 或 Git。
12
+ - SQLite 数据目录默认位于 `$DSH_HOME/plugins/dsh-qqchat/`,应由本地用户权限保护。
13
+
14
+ ## 身份边界
15
+
16
+ 稳定身份优先级:
17
+
18
+ ```text
19
+ author.user_openid
20
+ || author.member_openid
21
+ || author.id
22
+ ```
23
+
24
+ 规则:
25
+
26
+ - `senderId` 用于权限、记忆、Session 映射和数据归属。
27
+ - `senderName`、nickname、alias 只用于展示或画像。
28
+ - Owner 判断只比较 stable sender ID。
29
+ - 不因昵称变化而创建新的身份,不因昵称相同而合并身份。
30
+
31
+ ## 群与成员隔离
32
+
33
+ - 查询 group/member/message 时必须携带 account 和 scope 条件。
34
+ - group memory 不能因为当前成员查询而泄漏到其他群。
35
+ - member memory 可以按 stable sender 跨群连续,但不得混入特定群的关系事件。
36
+ - 日志、RPC 和 UI 不应显示不在当前会话范围内的成员数据。
37
+
38
+ ## 工具权限
39
+
40
+ 工具权限通过 DSH `tools/pre-execute` seam 判断:
41
+
42
+ ```text
43
+ 非 QQ 群 turn -> 放行
44
+ 群成员工具开关开启 -> 放行
45
+ senderId == ownerUserId -> 放行
46
+ 其他 -> 拒绝
47
+ ```
48
+
49
+ 没有配置 Owner 且关闭群成员工具时,群友触发的工具调用默认拒绝。
50
+
51
+ ## 日志与错误
52
+
53
+ - 可见日志不得包含聊天正文、完整 QQ payload、凭据或用户敏感信息。
54
+ - 错误消息应包含足够的 endpoint、阶段和错误类型,但不直接回显未经脱敏的上游响应。
55
+ - 调试真实问题时使用虚构用户名、群 ID 和 sender ID 写入测试、文档和 commit message。
56
+ - 临时探针和真实数据在问题定位后清理。
57
+
58
+ ## 外部请求
59
+
60
+ - QQ API 请求必须复用 QQApiClient 的认证、超时和发送格式逻辑。
61
+ - 不在新代码中直接拼接未校验的外部 URL。
62
+ - 非幂等发送不做无边界自动重试;发送失败交由现有 outbox/active fallback 处理。
@@ -0,0 +1,347 @@
1
+ # QQ 私聊流式传输设计方案
2
+
3
+ ## 文档目的
4
+
5
+ 本文记录 `dsh-qqchat` 当前采用的 QQ C2C 私聊流式回复方案,并总结已经遇到的故障,方便后续将同一套机制复用到咕咕或其他 QQ Bot 适配器中。
6
+
7
+ 本文讨论的是 QQ 官方 `stream_messages` 接口,不是 DSH Web 的前端打字机效果。QQ 负责更新客户端中的一条消息,DSH 负责产生文本增量,插件负责把增量可靠地转成 QQ 流式帧。
8
+
9
+ ## 结论先行
10
+
11
+ 推荐采用以下结构:
12
+
13
+ ```text
14
+ DSH assistant/chunk
15
+ -> StreamingWriter
16
+ -> 累计完整文本
17
+ -> 节流定时器
18
+ -> 串行发送队列
19
+ -> QQ POST /v2/users/{openid}/stream_messages
20
+ -> 首帧返回 stream_msg_id
21
+ -> 后续 replace 更新
22
+ -> 最终 DONE 帧
23
+ ```
24
+
25
+ 关键原则:
26
+
27
+ 1. 只对 C2C 私聊启用,群聊继续使用普通发送。
28
+ 2. 每一帧发送当前完整文本,使用 `input_mode: replace`。
29
+ 3. 同一条流式消息的所有帧共用 `msg_seq`,但不同流式会话要生成新的序列。
30
+ 4. 后续帧必须在真正发送前读取首帧返回的 `stream_msg_id`。
31
+ 5. 所有 HTTP 请求必须串行,不能让多个更新同时发出。
32
+ 6. 流式失败时:尚未成功发送任何帧才允许降级为普通消息;已经发送过部分内容时不能再发送完整文本,避免重复。
33
+
34
+ ## 官方协议
35
+
36
+ QQ 官方接口为:
37
+
38
+ ```text
39
+ POST /v2/users/{user_openid}/stream_messages
40
+ ```
41
+
42
+ 该接口只适用于 C2C 私聊。官方 Node SDK 的 `StreamSession` 使用 `replace` 模式,把“截至当前为止的完整文本”作为 `content_raw` 发送;首帧成功后保存返回的消息 ID,后续请求携带 `stream_msg_id`,最后使用 `input_state: 10` 结束流式消息。
43
+
44
+ 参考实现:
45
+
46
+ - [官方 dsh-qqbot outbound-buffer](https://raw.githubusercontent.com/tencent-connect/dsh-qqbot/main/src/transport/outbound-buffer.ts)
47
+ - [官方 dsh-qqbot streaming-writer](https://raw.githubusercontent.com/tencent-connect/dsh-qqbot/main/src/transport/streaming-writer.ts)
48
+ - [官方 QQ Node SDK streaming](https://raw.githubusercontent.com/tencent-connect/qqbot-nodejs/main/src/streaming.ts)
49
+ - [官方 QQ Node SDK 消息 API](https://raw.githubusercontent.com/tencent-connect/qqbot-nodejs/main/src/protocol/api/messages.ts)
50
+
51
+ ### 请求字段
52
+
53
+ 典型请求结构如下:
54
+
55
+ ```json
56
+ {
57
+ "input_mode": "replace",
58
+ "input_state": 1,
59
+ "content_type": "text",
60
+ "content_raw": "当前已经生成的完整文本",
61
+ "event_id": "入站消息 ID",
62
+ "msg_id": "入站消息 ID",
63
+ "msg_seq": 12345,
64
+ "index": 0
65
+ }
66
+ ```
67
+
68
+ 后续帧增加:
69
+
70
+ ```json
71
+ {
72
+ "stream_msg_id": "首帧响应返回的流式消息 ID"
73
+ }
74
+ ```
75
+
76
+ 结束帧使用 `input_state: 10`,并继续携带最后的完整文本和 `stream_msg_id`。
77
+
78
+ 字段约束:
79
+
80
+ | 字段 | 规则 |
81
+ | --- | --- |
82
+ | `input_mode` | 使用 `replace`,后续内容替换上一帧 |
83
+ | `input_state` | `1` 表示生成中,`10` 表示结束 |
84
+ | `content_raw` | 当前完整文本,不是本次增量 |
85
+ | `msg_seq` | 同一流内保持一致;新的流要生成新的值 |
86
+ | `index` | 从 `0` 开始递增,不能重复或跳号 |
87
+ | `msg_id` / `event_id` | 用入站消息或事件 ID 关联被动回复 |
88
+ | `stream_msg_id` | 只有首帧响应成功后才能获得,后续帧必须使用 |
89
+
90
+ ## 推荐组件设计
91
+
92
+ ### 1. StreamingWriter
93
+
94
+ `StreamingWriter` 是面向 DSH 的适配层,接收文本增量:
95
+
96
+ ```ts
97
+ writer.append(delta)
98
+ await writer.finish()
99
+ writer.abort()
100
+ ```
101
+
102
+ 内部状态建议包括:
103
+
104
+ ```ts
105
+ interface StreamingWriterState {
106
+ fullText: string
107
+ lastSentText: string
108
+ streamMsgId?: string
109
+ msgSeq: number
110
+ nextIndex: number
111
+ sentFrameCount: number
112
+ finished: boolean
113
+ failed: boolean
114
+ pendingTimer?: ReturnType<typeof setTimeout>
115
+ chain: Promise<void>
116
+ }
117
+ ```
118
+
119
+ ### 2. 累计全文,不累计分片
120
+
121
+ DSH 的 `assistant/chunk` 通常是增量文本:
122
+
123
+ ```text
124
+ "你好"
125
+ ",这里是"
126
+ "完整回复"
127
+ ```
128
+
129
+ 插件必须先累计:
130
+
131
+ ```text
132
+ "你好,这里是完整回复"
133
+ ```
134
+
135
+ 再将累计结果作为 `replace` 请求发送。不能把每个 delta 直接作为 `append` 请求发送,除非已经严格实现并验证 QQ 的 append 语义、序列和首帧关联。
136
+
137
+ ### 3. 节流
138
+
139
+ 不能每个 token 发一次 HTTP 请求,否则很容易触发 QQ 频率限制或造成客户端更新抖动。
140
+
141
+ 建议:
142
+
143
+ - 默认 500ms 一次;
144
+ - 不低于 300ms;
145
+ - 使用固定间隔 throttle,不使用持续输出时永远不触发的 debounce;
146
+ - 结束时取消定时器并立即发送最终内容。
147
+
148
+ ### 4. 串行队列
149
+
150
+ 所有更新进入同一个 Promise 队列:
151
+
152
+ ```ts
153
+ chain = chain.then(async () => {
154
+ const body = buildBodyFromCurrentState()
155
+ const response = await send(body)
156
+ updateStreamId(response)
157
+ })
158
+ ```
159
+
160
+ 重要的是:请求体必须在队列任务真正执行时构造,而不是在入队时构造。这样首帧返回 `stream_msg_id` 后,排队中的后续帧才能读取到最新 ID。
161
+
162
+ ## 生命周期
163
+
164
+ ### 开始
165
+
166
+ 收到第一个文本增量时创建 Writer,但可以延迟到第一个 throttle flush 时才调用 QQ 接口。
167
+
168
+ ### 更新
169
+
170
+ ```text
171
+ append(delta)
172
+ -> fullText += delta
173
+ -> 启动或等待 throttle timer
174
+ -> enqueue(GENERATING, fullText)
175
+ -> 首帧成功后保存 stream_msg_id
176
+ ```
177
+
178
+ ### 结束
179
+
180
+ ```text
181
+ finish(finalText)
182
+ -> 取消 timer
183
+ -> fullText = finalText
184
+ -> 等待已有更新队列
185
+ -> 发送最终 GENERATING 帧(必要时)
186
+ -> 发送 DONE 帧
187
+ -> 清理 Writer
188
+ ```
189
+
190
+ ### 中止
191
+
192
+ ```text
193
+ abort()
194
+ -> 标记 finished
195
+ -> 取消 timer
196
+ -> 不再接受新的 delta
197
+ -> 如果已打开流,尽力发送 complete/关闭请求
198
+ ```
199
+
200
+ ## 降级策略
201
+
202
+ 流式不是回复成功的唯一路径。发送逻辑必须记录 `sentFrameCount`:
203
+
204
+ | 状态 | 处理 |
205
+ | --- | --- |
206
+ | 首帧尚未成功 | 记录警告,使用普通文本/Markdown 发送完整回复 |
207
+ | 已成功发送部分流式内容 | 不发送完整普通消息,避免用户收到重复回复 |
208
+ | DONE 帧失败但已有内容 | 记录错误,不重复发送全文 |
209
+ | 模型没有文本输出 | 不创建流,沿用插件已有的空回复处理 |
210
+ | 设置关闭 | 完全跳过流式接口,直接使用普通发送 |
211
+
212
+ 普通发送也要继续使用已有的 Markdown/纯文本兼容格式和主动发送回退逻辑。
213
+
214
+ ## 设置设计
215
+
216
+ 建议将开关放在私聊消息设置中,而不是群聊设置中:
217
+
218
+ ```ts
219
+ interface QQChatRuntimeSettings {
220
+ directStreamingEnabled: boolean
221
+ }
222
+ ```
223
+
224
+ 推荐默认值:`true`,保持官方流式能力;如果目标客户端普遍看不到流式效果,也可以在产品层默认关闭。
225
+
226
+ 设置说明应明确:
227
+
228
+ > 私聊流式回复使用 QQ 官方流式接口逐步更新消息;部分 QQ 客户端可能看不到流式效果。关闭后使用普通一次性消息发送。
229
+
230
+ 群聊不应被这个开关影响,因为 QQ 官方 `stream_messages` 方案只覆盖 C2C 私聊。
231
+
232
+ ## 已遇到的问题与修复
233
+
234
+ ### 问题一:所有流式会话使用相同的 `msg_seq`
235
+
236
+ 现象:
237
+
238
+ ```text
239
+ QQ API HTTP 400 code=40054005
240
+ 消息被去重,请检查请求msgseq
241
+ ```
242
+
243
+ 早期实现使用“入站消息 ID → 本地序列”的方式生成流式 `msg_seq`。由于每个新消息的初始值都是 `1`,不同流式会话可能反复使用相同序列,增加了 QQ 判重风险。
244
+
245
+ 修复:
246
+
247
+ - 为每个新的流式会话生成新的 16 位 `msg_seq`;
248
+ - 同一流式会话的所有帧继续共用该值;
249
+ - 在进程内用单调序列避免并发流撞值;
250
+ - 保留跨会话测试。
251
+
252
+ 注意:`msg_seq` 的作用域必须结合实际 QQ API 行为验证,不能简单假设“每个入站消息都从 1 开始安全”。
253
+
254
+ ### 问题二:后续帧没有带上 `stream_msg_id`
255
+
256
+ 现象:
257
+
258
+ - 首帧可能成功;
259
+ - 后续请求报 `40054005`;
260
+ - 日志表现为流式失败并触发降级。
261
+
262
+ 根因:请求体在进入 Promise 队列时就已经构造完成。当多个更新快速进入队列时,首帧的 HTTP 响应还没有返回,后续请求体中的 `stream_msg_id` 仍然为空。
263
+
264
+ 错误模式:
265
+
266
+ ```ts
267
+ // 错误:入队时读取 streamMsgId
268
+ const body = { stream_msg_id: streamMsgId }
269
+ chain = chain.then(() => send(body))
270
+ ```
271
+
272
+ 正确模式:
273
+
274
+ ```ts
275
+ // 正确:执行队列任务时读取最新 streamMsgId
276
+ chain = chain.then(async () => {
277
+ const body = { stream_msg_id: streamMsgId }
278
+ const response = await send(body)
279
+ streamMsgId = response.id
280
+ })
281
+ ```
282
+
283
+ 这是迁移到咕咕时最需要保留的实现细节之一。
284
+
285
+ ### 问题三:流式失败后重复发送
286
+
287
+ 如果已经有一帧成功显示,又在异常处理中把完整回复作为普通消息再发送,用户会看到重复内容。
288
+
289
+ 修复:
290
+
291
+ ```ts
292
+ if (writer.sentFrameCount === 0) {
293
+ await sendNormal(fullText)
294
+ } else {
295
+ logFailure()
296
+ }
297
+ ```
298
+
299
+ ## 咕咕复用清单
300
+
301
+ 迁移时建议按以下顺序实现:
302
+
303
+ - [ ] 确认 QQ SDK 的 `stream_messages` 请求和响应字段;
304
+ - [ ] 仅为 C2C 私聊创建流式 Writer;
305
+ - [ ] 从 DSH assistant text-delta 累计全文;
306
+ - [ ] 使用 `replace`,不要直接把 delta 当作独立消息;
307
+ - [ ] 为每个流生成新的 `msg_seq`;
308
+ - [ ] `index` 从 0 开始严格递增;
309
+ - [ ] 首帧响应后再读取和保存 `stream_msg_id`;
310
+ - [ ] 所有请求通过串行队列;
311
+ - [ ] 采用 300–500ms throttle;
312
+ - [ ] 处理 429/50002 重试;
313
+ - [ ] 失败时区分“未发送”和“已发送部分”;
314
+ - [ ] 增加流式开关和普通发送回退;
315
+ - [ ] 增加模拟 HTTP 测试:首帧、后续帧、结束帧、队列竞态、失败降级;
316
+ - [ ] 真实 QQ 手机端和桌面端分别验证可见性;
317
+ - [ ] 记录 `msg_seq`、`index`、是否有 `stream_msg_id`,但不要记录完整敏感正文。
318
+
319
+ ## 测试矩阵
320
+
321
+ 至少覆盖:
322
+
323
+ | 场景 | 预期 |
324
+ | --- | --- |
325
+ | 短回复 | 首帧 + DONE 帧,内容一致 |
326
+ | 持续输出 | 按 throttle 更新,内容单调增长 |
327
+ | 快速连续 delta | 后续帧带首帧 `stream_msg_id`,无并发请求 |
328
+ | 多次私聊 | 不同流使用不同 `msg_seq` |
329
+ | 首帧失败 | 普通发送一次,不重复 |
330
+ | 中途失败 | 不发送完整重复消息 |
331
+ | 429/50002 | 按退避策略重试,超过次数后降级或报错 |
332
+ | 空回复 | 不发送空流式消息 |
333
+ | 设置关闭 | 不访问 `stream_messages` |
334
+ | 群聊 | 不访问 `stream_messages` |
335
+
336
+ ## 当前实现位置
337
+
338
+ 在 `dsh-qqchat` 中:
339
+
340
+ - QQ 流式 API:`src/gateway/api.ts`
341
+ - DSH 增量接收:`src/session/agent-bridge.ts`
342
+ - 私聊/群聊路由与降级:`src/session/runtime.ts`
343
+ - 设置类型:`src/types.ts`
344
+ - 设置持久化:`src/storage/db.ts`
345
+ - 设置 RPC:`src/transport/rpc.ts`
346
+ - Web 设置页面:`client-src/settings.cts`
347
+ - 协议测试:`tests/api.test.ts`
package/docs/devlog.md ADDED
@@ -0,0 +1,24 @@
1
+ # 开发日志
2
+
3
+ ## 2026-08-20 · 文档规范补充
4
+
5
+ ### 背景
6
+
7
+ QQChat 已经具备 DSH Session、QQ Gateway、SQLite 记忆和 Client UI,但原仓库只有 README 与架构文档,缺少面向贡献者的安全、测试和记忆维护约定。
8
+
9
+ ### 决策
10
+
11
+ - 采用 Gugu 的“根约定 + 领域文档 + devlog”结构。
12
+ - 将 Gugu 中与 Python、Vue、Mutagen、systemd 绑定的内容排除,只迁移根因调试、安全日志、测试回归和文档组织原则。
13
+ - 记忆文档明确记录 group/member scope、daily 格式、压缩阈值和失败保护,避免后续改动破坏已建立的数据契约。
14
+ - 把 DSH 原生 UI primitives、slot 和权限 seam 写入开发边界,防止插件重新实现宿主基础设施。
15
+
16
+ ### 验证
17
+
18
+ 文档补充不改变运行时代码。代码变更仍需执行:
19
+
20
+ ```bash
21
+ npm run typecheck
22
+ npm test
23
+ npm run build
24
+ ```