@anxi78/dsh-yuanbao 0.0.0-stage → 0.2.1

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +522 -2
  3. package/README_EN.md +335 -0
  4. package/client.js +327 -0
  5. package/cordis.patch.yml +12 -0
  6. package/dist/bridge.d.ts +30 -0
  7. package/dist/bridge.js +135 -0
  8. package/dist/chat-history.d.ts +54 -0
  9. package/dist/chat-history.js +71 -0
  10. package/dist/commands.d.ts +12 -0
  11. package/dist/commands.js +25 -0
  12. package/dist/config.d.ts +3 -0
  13. package/dist/config.js +44 -0
  14. package/dist/env-loader.d.ts +7 -0
  15. package/dist/env-loader.js +44 -0
  16. package/dist/gateway/auth.d.ts +16 -0
  17. package/dist/gateway/auth.js +97 -0
  18. package/dist/gateway/env.d.ts +7 -0
  19. package/dist/gateway/env.js +29 -0
  20. package/dist/gateway/index.d.ts +53 -0
  21. package/dist/gateway/index.js +242 -0
  22. package/dist/gateway/protocol/biz-codec.d.ts +72 -0
  23. package/dist/gateway/protocol/biz-codec.js +391 -0
  24. package/dist/gateway/protocol/client.d.ts +105 -0
  25. package/dist/gateway/protocol/client.js +767 -0
  26. package/dist/gateway/protocol/conn-codec.d.ts +99 -0
  27. package/dist/gateway/protocol/conn-codec.js +168 -0
  28. package/dist/gateway/protocol/proto/biz.json +593 -0
  29. package/dist/gateway/protocol/proto/conn.json +391 -0
  30. package/dist/gateway/protocol/types.d.ts +189 -0
  31. package/dist/gateway/protocol/types.js +14 -0
  32. package/dist/gateway/sanitize.d.ts +1 -0
  33. package/dist/gateway/sanitize.js +3 -0
  34. package/dist/inbound.d.ts +14 -0
  35. package/dist/inbound.js +322 -0
  36. package/dist/index.d.ts +6 -0
  37. package/dist/index.js +227 -0
  38. package/dist/logger.d.ts +4 -0
  39. package/dist/logger.js +45 -0
  40. package/dist/media.d.ts +62 -0
  41. package/dist/media.js +163 -0
  42. package/dist/outbound-media.d.ts +85 -0
  43. package/dist/outbound-media.js +258 -0
  44. package/dist/quote.d.ts +70 -0
  45. package/dist/quote.js +134 -0
  46. package/dist/services.d.ts +28 -0
  47. package/dist/services.js +50 -0
  48. package/dist/session/idle-evictor.d.ts +11 -0
  49. package/dist/session/idle-evictor.js +28 -0
  50. package/dist/session/manager.d.ts +35 -0
  51. package/dist/session/manager.js +268 -0
  52. package/dist/stickers.d.ts +25 -0
  53. package/dist/stickers.js +104 -0
  54. package/dist/tools.d.ts +15 -0
  55. package/dist/tools.js +335 -0
  56. package/dist/transport/chunker.d.ts +1 -0
  57. package/dist/transport/chunker.js +29 -0
  58. package/dist/transport/heartbeat.d.ts +13 -0
  59. package/dist/transport/heartbeat.js +56 -0
  60. package/dist/transport/outbound-buffer.d.ts +18 -0
  61. package/dist/transport/outbound-buffer.js +59 -0
  62. package/dist/transport/outbound.d.ts +31 -0
  63. package/dist/transport/outbound.js +179 -0
  64. package/dist/transport/sender.d.ts +83 -0
  65. package/dist/transport/sender.js +337 -0
  66. package/dist/types.d.ts +215 -0
  67. package/dist/types.js +6 -0
  68. package/package.json +87 -3
  69. package/src/bridge.ts +150 -0
  70. package/src/chat-history.ts +95 -0
  71. package/src/commands.ts +40 -0
  72. package/src/config.ts +47 -0
  73. package/src/dsh-tools.d.ts +13 -0
  74. package/src/env-loader.ts +45 -0
  75. package/src/gateway/auth.ts +127 -0
  76. package/src/gateway/env.ts +37 -0
  77. package/src/gateway/index.ts +255 -0
  78. package/src/gateway/protocol/biz-codec.ts +547 -0
  79. package/src/gateway/protocol/client.ts +1001 -0
  80. package/src/gateway/protocol/conn-codec.ts +217 -0
  81. package/src/gateway/protocol/proto/biz/get_group_member_list.proto +29 -0
  82. package/src/gateway/protocol/proto/biz/inbound_message.proto +44 -0
  83. package/src/gateway/protocol/proto/biz/msg_body.proto +59 -0
  84. package/src/gateway/protocol/proto/biz/openclaw_proxy_svr.proto +29 -0
  85. package/src/gateway/protocol/proto/biz/query_group_info.proto +30 -0
  86. package/src/gateway/protocol/proto/biz/send_c2c_message.proto +33 -0
  87. package/src/gateway/protocol/proto/biz/send_group_heartbeat.proto +27 -0
  88. package/src/gateway/protocol/proto/biz/send_group_message.proto +33 -0
  89. package/src/gateway/protocol/proto/biz/send_private_heartbeat.proto +30 -0
  90. package/src/gateway/protocol/proto/biz.json +593 -0
  91. package/src/gateway/protocol/proto/conn.json +391 -0
  92. package/src/gateway/protocol/proto/conn.proto +176 -0
  93. package/src/gateway/protocol/types.ts +219 -0
  94. package/src/gateway/sanitize.ts +3 -0
  95. package/src/inbound.ts +352 -0
  96. package/src/index.ts +249 -0
  97. package/src/logger.ts +55 -0
  98. package/src/media.ts +207 -0
  99. package/src/outbound-media.ts +363 -0
  100. package/src/quote.ts +157 -0
  101. package/src/services.ts +59 -0
  102. package/src/session/idle-evictor.ts +29 -0
  103. package/src/session/manager.ts +296 -0
  104. package/src/stickers.ts +131 -0
  105. package/src/tools.ts +407 -0
  106. package/src/transport/chunker.ts +27 -0
  107. package/src/transport/heartbeat.ts +74 -0
  108. package/src/transport/outbound-buffer.ts +59 -0
  109. package/src/transport/outbound.ts +252 -0
  110. package/src/transport/sender.ts +344 -0
  111. package/src/types.ts +207 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YuanbaoTeam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,523 @@
1
- # Temporary Holding Version
1
+ # dsh-yuanbao
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 中文 | [English](./README_EN.md)
4
+
5
+ 基于 [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) 的元宝 IM 通道插件,将元宝开放平台 WebSocket 消息接入 dsh agent runtime。
6
+
7
+ npm: [`@anxi78/dsh-yuanbao`](https://www.npmjs.com/package/@anxi78/dsh-yuanbao)
8
+
9
+ > **⚠️ 本仓库是定制分支**
10
+ >
11
+ > 上游:[YuanbaoTeam/dsh-yuanbao](https://github.com/YuanbaoTeam/dsh-yuanbao)
12
+ > 本分支: [anxi78/dsh-yuanbao](https://github.com/anxi78/dsh-yuanbao)
13
+ >
14
+ > 相比上游新增了**媒体消息、表情包、Agent 工具、设置面板**等能力,
15
+ > 具体差异见下方「[与上游的差异](#与上游的差异)」。
16
+ > 同步上游:`git fetch upstream && git merge upstream/main`
17
+
18
+
19
+ ## 与上游的差异
20
+
21
+ > 对比基准:上游 `9446ebd`(fix: support yuanbao group replies)
22
+ > 统计:**18 个文件,+2591 / -70 行**
23
+
24
+ ### 一、新增功能
25
+
26
+ #### 1. 媒体消息(图片 / 文件)
27
+
28
+ | 文件 | 作用 |
29
+ |---|---|
30
+ | `src/media.ts` | 入站媒体:从消息体提取图片/文件、下载到附件存储、转成 agent 可读的 content block |
31
+ | `src/outbound-media.ts` | 出站媒体:COS 上传(腾讯云对象存储)、图片尺寸探测、MIME 推断、构造图片消息体 |
32
+
33
+ - **入站**:元宝发来的图片/文件会被下载并挂到 agent 上下文,agent 能真正"看到"图片,而不是只收到一句占位文本
34
+ - **出站**:agent 可以把本地图片/文件发回给用户,走 COS 上传后发送
35
+
36
+ #### 2. 表情包
37
+
38
+ | 文件 | 作用 |
39
+ |---|---|
40
+ | `src/stickers.ts` | 表情搜索、按引用查找、构造表情消息体 |
41
+ | `assets/builtin-stickers.json` | 内置表情库(542 行) |
42
+
43
+ - 支持关键词搜索表情
44
+ - agent 可以主动发表情
45
+
46
+ #### 3. Agent 工具(5 个)
47
+
48
+ `src/tools.ts` 注册了 5 个可被 agent 调用的工具:
49
+
50
+ | 工具名 | 用途 |
51
+ |---|---|
52
+ | `yuanbao_send_media` | 发送图片或文件到当前会话 |
53
+ | `yuanbao_send_sticker` | 发送表情包 |
54
+ | `yuanbao_search_stickers` | 搜索可用表情 |
55
+ | `yuanbao_send_message` | 发送纯文本消息 |
56
+ | `yuanbao_list_members` | 列出群成员 |
57
+
58
+ **意义**:上游只能"被动回复",现在 agent 可以主动发消息、发图、发表情。
59
+
60
+ #### 4. 设置面板
61
+
62
+ `client.js`(219 行)注册了一个设置页「元宝」,支持**会话路由**两种模式:
63
+
64
+ | 模式 | 行为 |
65
+ |---|---|
66
+ | `per-peer`(默认) | 原版行为:每个群聊/私聊各开一个独立会话 |
67
+ | `pinned` | 所有消息都路由到指定的那一个会话 |
68
+
69
+ 配置项(`src/config.ts`、`src/types.ts`):
70
+
71
+ ```yaml
72
+ sessionMode: 'per-peer' | 'pinned' # 会话路由模式
73
+ pinnedSessionId: '' # pinned 模式下的目标会话 ID
74
+ ```
75
+
76
+ **留空自动回退到 per-peer**,不会因为配置错误把消息塞进空会话。
77
+
78
+ ### 二、修复的问题
79
+
80
+ #### 1. 群消息发送失败(`bot_id is required`)
81
+
82
+ **问题**:`bridge.ts` 里原本会单独构造一个 account 对象,导致 `botId` 为 undefined,群消息发送时被平台拒绝。
83
+
84
+ **修复**:复用 gateway 已解析的 account 对象(`gateway.account`),保证 `botId` 一致。
85
+
86
+ ```ts
87
+ // Reuse the gateway's account: sign-token resolution stored the bot id on
88
+ // that exact object. Building a second account here would leave `botId`
89
+ // undefined and make every group send fail the `fromAccount` check.
90
+ const account = gateway.account ?? gatewayAccountFromConfig(this.config);
91
+ ```
92
+
93
+ #### 2. @提及判定失效
94
+
95
+ **问题**:群聊里判断"有没有 @ 我"时,如果拿不到 botId 就静默判定为"没被提及",导致**所有群消息都不回复**。
96
+
97
+ **修复**:优先用配置的 `botId`,否则回退到 sign-token 交换时解析出的 id;并区分「确实没人 @ 我」和「我没有身份可比对」两种情况。
98
+
99
+ #### 3. 发送结果谎报成功
100
+
101
+ **问题**:发送失败时仍然返回成功,工具层显示"已发送"但实际什么都没发出去。
102
+
103
+ **修复**:`sender.ts` 传播真实传输结果,失败会抛错。
104
+
105
+ ```ts
106
+ // Propagate the transport outcome: reporting success unconditionally is
107
+ // what made the tool claim "sent" while nothing left the device.
108
+ ```
109
+
110
+ #### 4. 附件被静默丢弃
111
+
112
+ **问题**:出站消息只处理纯文本,带图片/文件的消息会被过滤掉,附件消失。
113
+
114
+ **修复**:`outbound.ts` 把文本和媒体分开收集,任何组合都能正确发送。
115
+
116
+ #### 5. 媒体消息的文本占位
117
+
118
+ **问题**:图片/文件消息体本身没有文本,agent 收到的消息是空的。
119
+
120
+ **修复**:为媒体元素生成占位符,让 agent 知道"这里有个图/文件"。
121
+
122
+ #### 6. @提及显示为原始 JSON
123
+
124
+ **问题**:消息里的 @ 元素被原样传给 agent,agent 看到的是 JSON 片段。
125
+
126
+ **修复**:渲染成人类可读的形式(如 `@用户名`)。
127
+
128
+ ### 三、架构改动
129
+
130
+ #### 服务读取封装(`src/services.ts`)
131
+
132
+ Cordis 插件读取未声明的服务会抛错。新增的 `createServiceReader` 把这类读取包起来,读取失败降级为 warn 日志而不是崩溃。
133
+
134
+ ```ts
135
+ // Cordis throws `cannot get property "X" without inject` when a plugin reads
136
+ // a service it did not declare.
137
+ ```
138
+
139
+ #### 会话管理器增强(`src/session/manager.ts`)
140
+
141
+ - `sessionKey()` 支持 `pinned` 模式:所有消息共用同一个 key
142
+ - `deriveSessionId()` 在 pinned 模式下直接返回指定 ID,不再哈希成新 UUID
143
+ - 队列串行化,避免同一会话并发处理
144
+
145
+ ### 四、文件清单
146
+
147
+ **新增(8 个)**
148
+
149
+ ```
150
+ assets/builtin-stickers.json 542 行 内置表情库
151
+ client.js 219 行 设置面板
152
+ src/media.ts 203 行 入站媒体
153
+ src/outbound-media.ts 363 行 出站媒体
154
+ src/services.ts 59 行 服务读取封装
155
+ src/stickers.ts 131 行 表情包
156
+ src/tools.ts 407 行 Agent 工具
157
+ src/dsh-tools.d.ts 13 行 工具类型声明
158
+ ```
159
+
160
+ **修改(10 个)**
161
+
162
+ ```
163
+ src/bridge.ts +71 复用 gateway account、注册工具
164
+ src/transport/sender.ts +173 发送结果传播、身份校验
165
+ src/transport/outbound.ts +116 媒体消息支持
166
+ src/session/manager.ts +104 会话路由
167
+ src/inbound.ts +106 @提及判定、媒体提取
168
+ src/index.ts +91 服务注入、配置
169
+ src/gateway/index.ts +33 account 暴露、httpAuth
170
+ src/config.ts +6 会话路由配置
171
+ src/types.ts +8 类型定义
172
+ package.json +16 client 入口声明
173
+ ```
174
+
175
+ ### 五、同步上游
176
+
177
+ ```bash
178
+ git fetch upstream
179
+ git merge upstream/main
180
+ ```
181
+
182
+ 本分支的改动集中在新增文件和少数函数的局部修改,**冲突面较小**。
183
+
184
+
185
+ ## 架构
186
+
187
+ ```text
188
+ 元宝用户 → 元宝 WebSocket Gateway → dsh-yuanbao → ctx.agents → dsh agent loop → LLM / Tools
189
+ ↑ │
190
+ └── session/event ──────────┘
191
+ assistant reply → 元宝发送接口
192
+ ```
193
+
194
+ `dsh-yuanbao` 作为 Cordis 插件运行在 deepseek-harness 进程内:
195
+
196
+ - 入站:元宝 WebSocket push → 元宝消息解码 → `agent.followup()`
197
+ - 出站:监听 `session/event` → 聚合 `assistant/chunk` → 发送元宝 C2C / 群消息
198
+ - 状态:通过元宝 heartbeat 发送 `RUNNING` / `FINISH`,驱动元宝 loading / 思考气泡
199
+
200
+ ## 安装
201
+
202
+ ### 方式一:从 npm registry 安装(推荐)
203
+
204
+ ```bash
205
+ # 安装到 web profile
206
+ dsh plugin --profile web add @anxi78/dsh-yuanbao
207
+
208
+ # 启动
209
+ dsh --profile web
210
+ ```
211
+
212
+ ### 方式二:本地路径安装
213
+
214
+ ```bash
215
+ # 构建插件
216
+ cd /path/to/dsh-yuanbao
217
+ pnpm install
218
+ pnpm build
219
+
220
+ # 安装到 web profile
221
+ dsh plugin --profile web add /path/to/dsh-yuanbao
222
+
223
+ # 启动
224
+ dsh --profile web
225
+ ```
226
+
227
+ ## 环境变量
228
+
229
+ 推荐在 deepseek-harness 启动目录放置 `.env`:
230
+
231
+ ```env
232
+ YUANBAO_APP_KEY=你的元宝AppKey
233
+ YUANBAO_APP_SECRET=你的元宝AppSecret
234
+ # 可选:如果已经有预签名 token,可直接配置
235
+ YUANBAO_TOKEN=
236
+
237
+ # 可选:Agent 工作目录
238
+ DSH_YUANBAO_CWD=/path/to/workspace
239
+ ```
240
+
241
+ 如果使用 DeepSeek 官方模型,还需要:
242
+
243
+ ```env
244
+ DEEPSEEK_API_KEY=你的DeepSeek API Key
245
+ ```
246
+
247
+ 如果使用 Ollama 本地模型,可在 `~/.dsh/settings.yaml` 中配置 `agent-default-model` 指向 Ollama provider。
248
+
249
+ ## 模型配置
250
+
251
+ `dsh-yuanbao` 默认不直接绑定模型,而是继承 deepseek-harness 的宿主默认模型:
252
+
253
+ ```yaml
254
+ agent-default-model:
255
+ provider: <provider-id>
256
+ model: <model-id>
257
+ ```
258
+
259
+ 模型 provider 通常配置在:
260
+
261
+ ```text
262
+ ~/.dsh/settings.yaml
263
+ ```
264
+
265
+ 如果设置了 `DSH_HOME`,则配置文件位于:
266
+
267
+ ```text
268
+ $DSH_HOME/settings.yaml
269
+ ```
270
+
271
+ ### OpenAI 示例
272
+
273
+ 在 deepseek-harness 启动目录的 `.env` 中配置:
274
+
275
+ ```env
276
+ OPENAI_API_KEY=你的OpenAI API Key
277
+ ```
278
+
279
+ 在 `~/.dsh/settings.yaml` 中配置:
280
+
281
+ ```yaml
282
+ llm-pi-ai:
283
+ providers:
284
+ openai:
285
+ displayName: OpenAI
286
+ apiKeyEnv: OPENAI_API_KEY
287
+ api: openai-completions
288
+ baseURL: https://api.openai.com/v1
289
+ models:
290
+ - id: gpt-4o-mini
291
+ name: GPT-4o mini
292
+ contextWindow: 128000
293
+ maxTokens: 16384
294
+ input:
295
+ - text
296
+
297
+ agent-default-model:
298
+ provider: openai
299
+ model: gpt-4o-mini
300
+ ```
301
+
302
+ ### Claude 示例
303
+
304
+ 在 deepseek-harness 启动目录的 `.env` 中配置:
305
+
306
+ ```env
307
+ ANTHROPIC_API_KEY=你的Anthropic API Key
308
+ ```
309
+
310
+ 在 `~/.dsh/settings.yaml` 中配置:
311
+
312
+ ```yaml
313
+ llm-pi-ai:
314
+ providers:
315
+ anthropic:
316
+ displayName: Anthropic
317
+ apiKeyEnv: ANTHROPIC_API_KEY
318
+ api: anthropic-messages
319
+ baseURL: https://api.anthropic.com
320
+ models:
321
+ - id: claude-3-5-sonnet-20241022
322
+ name: Claude 3.5 Sonnet
323
+ contextWindow: 200000
324
+ maxTokens: 8192
325
+ input:
326
+ - text
327
+
328
+ agent-default-model:
329
+ provider: anthropic
330
+ model: claude-3-5-sonnet-20241022
331
+ ```
332
+
333
+ ### Ollama 示例
334
+
335
+ 在 deepseek-harness 启动目录的 `.env` 中配置一个占位 key:
336
+
337
+ ```env
338
+ OLLAMA_API_KEY=ollama
339
+ ```
340
+
341
+ 在 `~/.dsh/settings.yaml` 中配置:
342
+
343
+ ```yaml
344
+ llm-pi-ai:
345
+ providers:
346
+ ollama:
347
+ displayName: Ollama
348
+ apiKeyEnv: OLLAMA_API_KEY
349
+ api: openai-completions
350
+ baseURL: http://127.0.0.1:11434/v1
351
+ models:
352
+ - id: qwen3.5
353
+ name: Qwen3.5
354
+ contextWindow: 32768
355
+ maxTokens: 4096
356
+ input:
357
+ - text
358
+
359
+ agent-default-model:
360
+ provider: ollama
361
+ model: qwen3.5
362
+ ```
363
+
364
+ 也可以在 `cordis.patch.yml` 中给 `dsh-yuanbao` 显式指定模型,覆盖宿主默认模型:
365
+
366
+ ```yaml
367
+ - insert:
368
+ - id: dsh-yuanbao
369
+ name: '@anxi78/dsh-yuanbao'
370
+ config:
371
+ provider: openai
372
+ model: gpt-4o-mini
373
+ ```
374
+
375
+ ## 配置项
376
+
377
+ | 配置 | 类型 | 默认值 | 说明 |
378
+ |------|------|--------|------|
379
+ | `appKey` | string | `''` | 元宝 AppKey,默认读取 `YUANBAO_APP_KEY` |
380
+ | `appSecret` | string | `''` | 元宝 AppSecret,默认读取 `YUANBAO_APP_SECRET` |
381
+ | `token` | string | - | 预签名元宝 WebSocket token,默认读取 `YUANBAO_TOKEN` |
382
+ | `apiDomain` | string | `bot.yuanbao.tencent.com` | 元宝开放平台 API 域名 |
383
+ | `wsGatewayUrl` | string | `wss://bot-wss.yuanbao.tencent.com/wss/connection` | 元宝 WebSocket Gateway 地址 |
384
+ | `routeEnv` | string | - | 元宝路由环境 |
385
+ | `botId` | string | - | 已知元宝 bot id |
386
+ | `provider` | string | - | DSH LLM provider;为空时继承宿主默认模型 |
387
+ | `model` | string | - | DSH model;为空时继承宿主默认模型 |
388
+ | `preset` | string | - | Agent preset id |
389
+ | `cwd` | string | `process.cwd()` | Agent 工作目录 |
390
+ | `requireMention` | boolean | `true` | 群聊是否需要 @bot 才触发 |
391
+ | `directPrompt` | string | - | 私聊额外 system prompt |
392
+ | `groupPrompt` | string | - | 群聊额外 system prompt |
393
+ | `textChunkLimit` | number | `3000` | 单条出站消息最大字符数 |
394
+ | `sessionIdleTimeout` | number | `1800000` | 会话闲置超时,默认 30 分钟 |
395
+ | `sessionMode` | `per-peer` / `pinned` | `per-peer` | **本分支新增**:会话路由模式 |
396
+ | `pinnedSessionId` | string | `''` | **本分支新增**:pinned 模式下的目标会话 ID,留空回退 per-peer |
397
+ | `processingTimeoutMs` | number | `120000` | Agent 处理超时 |
398
+ | `maxReconnectAttempts` | number | `100` | WebSocket 最大重连次数 |
399
+ | `access.c2cMode` | `open` / `allowlist` / `disabled` | `open` | 私聊访问控制 |
400
+ | `access.c2cAllow` | string[] | `[]` | 私聊 allowlist |
401
+ | `access.groupMode` | `open` / `allowlist` / `disabled` | `open` | 群聊访问控制 |
402
+ | `access.groupAllow` | string[] | `[]` | 群聊 allowlist |
403
+ | `debug` | boolean | `false` | 调试模式 |
404
+
405
+ 默认 bundle patch:
406
+
407
+ ```yaml
408
+ - insert:
409
+ - id: dsh-yuanbao
410
+ name: '@anxi78/dsh-yuanbao'
411
+ config:
412
+ appKey: !!js process.env.YUANBAO_APP_KEY ?? ''
413
+ appSecret: !!js process.env.YUANBAO_APP_SECRET ?? ''
414
+ token: !!js process.env.YUANBAO_TOKEN ?? ''
415
+ cwd: !!js process.env.DSH_YUANBAO_CWD ?? process.cwd()
416
+ requireMention: true
417
+ textChunkLimit: 3000
418
+ sessionIdleTimeout: 1800000
419
+ ```
420
+
421
+ ## 内置命令
422
+
423
+ | 命令 | 说明 |
424
+ |------|------|
425
+ | `/yb-help` / `/yuanbao-help` | 查看所有指令 |
426
+ | `/yb-status` / `/yuanbao-status` | 查看当前会话状态 |
427
+ | `/yb-reset` / `/yuanbao-reset` | 重置当前会话上下文 |
428
+
429
+ ## 核心模块
430
+
431
+ ```text
432
+ src/
433
+ ├── index.ts # Cordis 插件入口
434
+ ├── bridge.ts # Gateway / SessionManager / 出入站桥接
435
+ ├── config.ts # 配置 Schema
436
+ ├── types.ts # 全局类型定义
437
+ ├── env-loader.ts # 本地 .env 加载
438
+ ├── logger.ts # 统一日志入口
439
+ ├── inbound.ts # 元宝入站消息 → agent.followup()
440
+ ├── commands.ts # 内置命令
441
+ ├── gateway/ # 元宝 Gateway 层
442
+ │ ├── index.ts # WebSocket 生命周期与 push 分发
443
+ │ ├── auth.ts # appKey / appSecret 签票鉴权
444
+ │ ├── env.ts # User-Agent / 设备信息
445
+ │ └── protocol/ # WebSocket / protobuf 协议编解码
446
+ ├── session/ # 会话管理层
447
+ │ ├── manager.ts # 元宝 peer → dsh Agent 映射
448
+ │ └── idle-evictor.ts # 闲置回收
449
+ └── transport/ # 出站传输层
450
+ ├── sender.ts # C2C / 群消息 / heartbeat 发送
451
+ ├── outbound.ts # session/event → 元宝消息
452
+ ├── outbound-buffer.ts # 流式输出缓冲
453
+ ├── heartbeat.ts # loading / 思考气泡心跳控制
454
+ └── chunker.ts # 文本切分
455
+ ```
456
+
457
+ ## 会话路由
458
+
459
+ 元宝私聊或群聊会稳定映射到一个 dsh session:
460
+
461
+ ```text
462
+ sessionKey = yuanbao:${accountId}:${scope}:${peerId}
463
+ sessionId = sha256(sessionKey) 派生
464
+ ```
465
+
466
+ 解析策略:
467
+
468
+ ```text
469
+ 进程内复用 → 持久化恢复 → 全新创建
470
+ ```
471
+
472
+ 这样可以保证:
473
+
474
+ - 同一个元宝私聊用户上下文连续
475
+ - 同一个元宝群聊上下文连续
476
+ - dsh 重启后可恢复历史 session
477
+ - 不直接暴露元宝用户标识
478
+
479
+ ### pinned 模式(本分支新增)
480
+
481
+ 把 `sessionMode` 设为 `pinned` 并填上 `pinnedSessionId` 后,**所有群聊和私聊的消息都会路由到同一个会话**:
482
+
483
+ ```text
484
+ sessionKey = yuanbao:${accountId}:pinned:${pinnedSessionId}
485
+ sessionId = pinnedSessionId(直接使用,不再哈希)
486
+ ```
487
+
488
+ 使用场景:想让所有渠道的消息都汇总到一个"总控"会话里,由同一个 agent 上下文统一处理。
489
+
490
+ 在「设置 → 元宝」里可以直接切换,会话列表从 DSH 实时读取,不用手填 UUID。
491
+
492
+ **留空 `pinnedSessionId` 会自动回退到 per-peer**,不会把所有消息塞进一个空会话。
493
+
494
+ ## 设计原则
495
+
496
+ - **纯 Cordis 插件** — 不修改 deepseek-harness agent loop
497
+ - **通道适配独立** — 元宝协议转换在插件内完成,Agent runtime 仍由 dsh 管理
498
+ - **会话隔离** — 每个私聊 / 群聊独立映射 Agent session
499
+ - **模型继承** — 默认继承宿主 `agent-default-model`,也可显式配置 `provider` / `model`
500
+ - **流式缓冲** — 聚合 `assistant/chunk`,避免 IM 侧逐 token 刷屏
501
+ - **思考气泡** — 用元宝 heartbeat `RUNNING` / `FINISH` 驱动 loading 状态
502
+ - **安全默认** — 密钥只从环境变量读取,日志会对 secret / token 等字段脱敏
503
+
504
+ ## 本地开发
505
+
506
+ ```bash
507
+ # 安装依赖
508
+ pnpm install
509
+
510
+ # 构建
511
+ pnpm build
512
+
513
+ # watch
514
+ pnpm dev
515
+
516
+ # 本地安装到 web profile
517
+ dsh plugin --profile web add /path/to/dsh-yuanbao
518
+ dsh --profile web
519
+ ```
520
+
521
+ ## License
522
+
523
+ MIT