@zaofan/dsh-qqbot 0.9.5 β†’ 1.0.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 (75) hide show
  1. package/README.md +394 -311
  2. package/README_EN.md +313 -281
  3. package/client/qqbot-settings.js +3214 -2986
  4. package/dist/channel-tools.d.ts +1 -1
  5. package/dist/channel-tools.d.ts.map +1 -1
  6. package/dist/channel-tools.js +134 -3
  7. package/dist/channel-tools.js.map +1 -1
  8. package/dist/commands/approve.d.ts +17 -0
  9. package/dist/commands/approve.d.ts.map +1 -0
  10. package/dist/commands/approve.js +24 -0
  11. package/dist/commands/approve.js.map +1 -0
  12. package/dist/commands/botplay.d.ts +15 -0
  13. package/dist/commands/botplay.d.ts.map +1 -0
  14. package/dist/commands/botplay.js +34 -0
  15. package/dist/commands/botplay.js.map +1 -0
  16. package/dist/commands/exit.d.ts +7 -0
  17. package/dist/commands/exit.d.ts.map +1 -0
  18. package/dist/commands/exit.js +85 -0
  19. package/dist/commands/exit.js.map +1 -0
  20. package/dist/commands/index.d.ts.map +1 -1
  21. package/dist/commands/index.js +14 -1
  22. package/dist/commands/index.js.map +1 -1
  23. package/dist/commands/permission.d.ts +18 -0
  24. package/dist/commands/permission.d.ts.map +1 -0
  25. package/dist/commands/permission.js +43 -0
  26. package/dist/commands/permission.js.map +1 -0
  27. package/dist/commands/session.d.ts +5 -1
  28. package/dist/commands/session.d.ts.map +1 -1
  29. package/dist/commands/session.js +51 -5
  30. package/dist/commands/session.js.map +1 -1
  31. package/dist/config.d.ts +55 -0
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +75 -0
  34. package/dist/config.js.map +1 -1
  35. package/dist/features/approval-switch.d.ts +22 -0
  36. package/dist/features/approval-switch.d.ts.map +1 -0
  37. package/dist/features/approval-switch.js +10 -0
  38. package/dist/features/approval-switch.js.map +1 -0
  39. package/dist/features/botplay.d.ts +141 -0
  40. package/dist/features/botplay.d.ts.map +1 -0
  41. package/dist/features/botplay.js +547 -0
  42. package/dist/features/botplay.js.map +1 -0
  43. package/dist/features/extension-store.d.ts +27 -0
  44. package/dist/features/extension-store.d.ts.map +1 -0
  45. package/dist/features/extension-store.js +152 -0
  46. package/dist/features/extension-store.js.map +1 -0
  47. package/dist/features/qq-approval.d.ts.map +1 -1
  48. package/dist/features/qq-approval.js +3 -1
  49. package/dist/features/qq-approval.js.map +1 -1
  50. package/dist/gateway/bootstrap.d.ts.map +1 -1
  51. package/dist/gateway/bootstrap.js +60 -1
  52. package/dist/gateway/bootstrap.js.map +1 -1
  53. package/dist/gateway/debounce.d.ts.map +1 -1
  54. package/dist/gateway/debounce.js +4 -2
  55. package/dist/gateway/debounce.js.map +1 -1
  56. package/dist/gateway/middleware-setup.d.ts +1 -1
  57. package/dist/gateway/middleware-setup.d.ts.map +1 -1
  58. package/dist/gateway/middleware-setup.js +10 -2
  59. package/dist/gateway/middleware-setup.js.map +1 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +19 -2
  62. package/dist/index.js.map +1 -1
  63. package/dist/model/model-resolver.d.ts +12 -0
  64. package/dist/model/model-resolver.d.ts.map +1 -1
  65. package/dist/model/model-resolver.js +24 -0
  66. package/dist/model/model-resolver.js.map +1 -1
  67. package/dist/model/prefs-store.d.ts +16 -0
  68. package/dist/model/prefs-store.d.ts.map +1 -1
  69. package/dist/model/prefs-store.js +48 -0
  70. package/dist/model/prefs-store.js.map +1 -1
  71. package/dist/session/session-manager.d.ts +37 -0
  72. package/dist/session/session-manager.d.ts.map +1 -1
  73. package/dist/session/session-manager.js +168 -16
  74. package/dist/session/session-manager.js.map +1 -1
  75. package/package.json +1 -1
package/README_EN.md CHANGED
@@ -1,281 +1,313 @@
1
- # @zaofan/dsh-qqbot
2
-
3
- An **enhanced fork** of the QQ Bot IM plugin for [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) (dsh): it drives the dsh agent loop with the QQ messaging platform as the frontend protocol, adding a local sticker library, rich media send/recall, scheduled tasks, multi-instance personas, and a visual settings panel.
4
-
5
- πŸ“¦ Repo: [gcry13067381632-jpg/dsh-qqbot](https://github.com/gcry13067381632-jpg/dsh-qqbot) (forked from [tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot))
6
-
7
- [δΈ­ζ–‡ζ–‡ζ‘£](./README.md) | English
8
-
9
- ## πŸ‹ This fork (enhanced edition)
10
-
11
- **In one line**: bring your dsh-powered QQ bot to life β€” it auto-collects stickers from group chats, picks and sends the right one when the mood hits, speaks up on schedule, and lets you run several bots with different personalities from one computer.
12
-
13
- This repo is an enhanced fork of [@tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot) (changes live outside the upstream source β€” reinstalling/upgrading upstream will wipe them).
14
-
15
- ### What she does for you
16
-
17
- **🀳 Group images become her sticker library automatically** β€” saved locally with dedup ("to-sort / favorite / trash"). Say *"send something happy"* and she searches, picks and sends on her own β€” with guardrails (no posting into a cold chat, rate limits, no repeat stickers).
18
-
19
- **⏰ She speaks up on time** β€” schedule a daily greeting, or tell her *"remind me to drink water in 30 seconds"* and she actually will.
20
-
21
- **πŸ§‘β€πŸ€β€πŸ§‘ Many bots, many personalities, one computer** β€” each with its own AppID, persona and working directory (stickers / timers / gates fully isolated). Adding one is a QR scan away in the Web panel.
22
-
23
- **πŸ’¬ Floating dock: her pocket console**
24
- The little ball at the corner of the settings panel opens a whole control deck: **πŸ’¬ Chat** (replay a group/DM conversation QQ-style β€” bubbles + avatars, zoomable images, inline local video, SILK voice converted to MP3 in pure JS, file download cards; compose text / images / files right below, long messages auto-split into safe chunks that never get swallowed by QQ); **πŸ“₯ join-request approval**, **πŸ”‡ mute** (when she is a group admin, she pings you on join requests β€” reply "approve/reject"); **βš™οΈ Outbound** (adaptive-active: recent human messages get quoted replies first, bursts auto-switch to independent messages, scheduled/background pushes never get dropped).
25
-
26
- **πŸ›‘οΈ Group-admin helper** β€” join approval + mute management over official APIs, with human-readable errors (not an admin / cannot mute the owner…).
27
-
28
- **πŸ–₯️ No config-file surgery** β€” reply pacing, sticker gates, scheduled wake-ups, outbound mode and per-bot personas are all in the panel; saving applies live (only adding/removing bots needs a restart). There is even a ✏️ persona editor to tweak her "personality file" right in the browser.
29
-
30
- **πŸ“¦ Clean & lean** β€” drive image sending / recall from plain text (`[MEDIA:image|path]` / `[RECALL]`); the repo contains no bot credentials or private data.
31
-
32
- ### πŸ“Έ Showcase
33
-
34
- Left: the dsh runtime backend β€” reasoning, tool calls and token usage are fully visible (paired with the `reply_gate` gate tool, the bot decides on its own whether to speak or stay silently idle);
35
- Middle: real QQ group conversation β€” hide-and-seek role-play, replying when it should and staying quiet when it shouldn't;
36
- Right: sticker-battle in action β€” the bot answers with stickers from its own library, image and text sent as separate messages.
37
-
38
- ![Runtime backend log (reasoning & tool calls visible)](docs/showcase-1-log.png)
39
-
40
- ![QQ group conversation (role-play / self-decided silence)](docs/showcase-2-chat.png)
41
-
42
- ![Sticker battle (replying with own sticker library)](docs/showcase-3-doutu.png)
43
-
44
- ### For developers
45
- - Standard tools available inside QQ sessions: `send_media` / `recall_message` / `list_stickers` / `sticker_tag` / `sticker_untagged` / `schedule_timer` / `schedule_cancel` …, routed per bot account.
46
- - **Group admin tools** (`group_join_requests` / `group_approve_join` / `group_mute_state` / `group_mute_member` …): join-request approval and mute management β€” requires the bot to be a group admin; inside a group session the current group is used, elsewhere the configured `manageGroup` applies.
47
- - **Plain text can send media or recall messages**: writing `[MEDIA:image|path-or-url]` in a reply turns it into a real image message (`voice`/`video`/`file` work the same); a lone `[RECALL]` line recalls the bot's own last message.
48
- - Host-level fixes (workspace session attachment, upstream PR #21) are included β€” idempotent and fully fail-soft.
49
-
50
- > πŸ›‘οΈ This repo contains **no** bot credentials, sticker data, logs or personal paths (cleaned before publishing). Inject AppID/AppSecret via env vars or the Web panel β€” **never commit them**.
51
-
52
- ### Build & deploy
53
-
54
- ```bash
55
- npm install # install deps (peer deps resolved by the dsh host)
56
- node node_modules/typescript/lib/tsc.js -p tsconfig.json # or npm run build
57
- # then copy dist/ over your dsh profile's
58
- # node_modules/@tencent-connect/dsh-qqbot/dist/ and restart dsh
59
- ```
60
-
61
- See the upstream "Installation" section below (`dsh plugin add` + QR onboarding both work).
62
-
63
- ---
64
-
65
- ## Architecture
66
-
67
- ```
68
- QQ User β†’ QQ WebSocket β†’ dsh-im-qqbot β†’ ctx.agents β†’ dsh agent loop β†’ LLM
69
- ↑ β”‚
70
- └── session/event β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
71
- (assistant reply β†’ QQ sendMarkdown)
72
- ```
73
-
74
- ## Installation
75
-
76
- > ⚠️ **Install into the `web` profile** (host of the `dsh web` settings panel); other profiles get a bare
77
- > environment with no settings UI. Do **not** `add @tencent-connect/dsh-qqbot` β€” that installs the upstream
78
- > official version (without this fork's features).
79
- >
80
- > βœ… **Single package, everything included**: QQ bot + the Web settings panel (host bridge + settings UI) all
81
- > ship in this one package β€” after install, dsh Web β†’ Settings shows a "QQ bot (im-qqbot)" page (one per bot
82
- > instance). **No separate dsh-qqbot-settings install needed.**
83
-
84
- ### Method 1 (after npm release): one command
85
-
86
- ```powershell
87
- npx @deepseek-ai/dsh plugin --profile web add @zaofan/dsh-qqbot
88
- ```
89
-
90
- > Until it is published to npm, use Method 2 below.
91
-
92
- ### Method 2: from source (recommended for now)
93
-
94
- **Windows (one-click script)**:
95
-
96
- ```powershell
97
- git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
98
- cd dsh-qqbot
99
- .\install.ps1 # npm install/build -> pack -> add tarball -> prints restart steps
100
- ```
101
-
102
- > If script execution is blocked: `powershell -ExecutionPolicy Bypass -File .\install.ps1`
103
-
104
- **macOS / Linux (manual)**:
105
-
106
- ```bash
107
- git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
108
- cd dsh-qqbot
109
- npm install && npm run build
110
- pnpm pack --pack-destination /tmp
111
- npx @deepseek-ai/dsh plugin --profile web add /tmp/zaofan-dsh-qqbot-0.4.0.tgz
112
- ```
113
-
114
- > πŸ’‘ Why a tarball instead of `add <source dir>`? Lessons from real installs:
115
- > β‘  a directory path containing spaces gets split at the spaces on Windows (pnpm reports `- isn't supported`);
116
- > β‘‘ `add <dir>` becomes a pnpm link (junction), so the plugin can't locate the profile by its code location,
117
- > and scanned credentials cannot be persisted (env-var-only fallback).
118
-
119
- ### Troubleshooting: npm install fails with ERESOLVE (backported from upstream PR #42)
120
-
121
- A fresh `npm install` may fail with `ERESOLVE could not resolve`. Cause: peer deps like
122
- `@deepseek-ai/dsh-tools` / `dsh-agent` are still on prerelease (-rc) version lines, and npm 7+
123
- strict resolution rejects non-intersecting combinations. **This is an upstream version-line issue, not a plugin bug.** Two workarounds:
124
-
125
- ```bash
126
- npm install --legacy-peer-deps # install-time resolution only; runtime behavior unchanged
127
- # or: install deps, then build & pack manually (peers are resolved by the dsh host)
128
- ```
129
-
130
- > Tracked: once upstream #37 is fixed and version lines converge, this section can be removed.
131
-
132
- ### First launch & binding
133
-
134
- Start `dsh web`. If credentials are missing, the **QR flow** starts automatically: a QR code is printed in the
135
- terminal β†’ scan it with the QQ mobile app β†’ credentials are saved and survive restarts (you can also use
136
- "QR bind" / edit accounts in the settings panel at any time).
137
-
138
- ![QR code scan example](./docs/assets/qrcode.png)
139
-
140
- > **Note**: Use `0.4.0` or later for browser-link scanning, which avoids QR code misalignment in some terminals.
141
-
142
- ### Don't have a QQ bot yet? Register one (get AppID / AppSecret)
143
-
144
- 1. Open the [QQ Open Platform](https://q.qq.com) and sign in with your QQ account;
145
- 2. Go to "Bot" β†’ "Create Bot" and fill in name, avatar and description;
146
- 3. After creation, copy the **AppID** and **AppSecret** from the bot detail page;
147
- 4. Enter them in dsh Web β†’ Settings β†’ "QQ bot" β†’ "Accounts & presets" and save
148
- (or set env vars `QQBOT_APPID` / `QQBOT_SECRET`);
149
- 5. Enable the needed **single-chat / group-chat** message permissions on the platform
150
- (group chat usually requires a use-case review).
151
-
152
- > πŸ’‘ Easier: once the bot exists, just use the **QR scan bind** on first launch β€” no need to type credentials.
153
-
154
- ### For developers: --patch dev mode
155
-
156
- ```bash
157
- export QQBOT_APPID="yourAppID" QQBOT_SECRET="yourAppSecret"
158
- npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
159
- ```
160
-
161
- ## Remote approval over QQ (optional)
162
-
163
- When an Agent tool needs access **outside the workspace**, dsh raises a permission request. With this
164
- feature on, the bot forwards the request to the **QQ conversation of the task's initiator**, and you
165
- allow/deny right from QQ:
166
-
167
- > ⚠️ **DSH permission request**
168
- > Tool: pwsh
169
- > Reason: needs access outside the workspace
170
- >
171
- > Allow this operation: `/approve A1B2C3`
172
- > Deny this operation: `/deny A1B2C3`
173
- > One-time only; auto-denied after 120 s.
174
-
175
- **Enable** (either way; takes effect for new requests right after saving, no restart needed):
176
- - **Web settings panel**: Settings β†’ "QQ bot" β†’ β‘€ QQ remote approval β†’ tick it on;
177
- - or add two lines to the instance `config` in `cordis.patch.yml`, then restart:
178
-
179
- ```yaml
180
- - id: im-qqbot
181
- config:
182
- enableApprovals: true # default false
183
- approvalTimeoutMs: 120000 # wait window; auto-deny on timeout
184
- ```
185
-
186
- **Security boundaries**: one-time code; only the **initiator in the same conversation** may decide
187
- (others in a group chat cannot approve even if they see the code); grants only the current operation;
188
- auto-cancelled when the agent is cancelled or dsh exits.
189
-
190
- > Idea source: QQ-approval design of wang-22-code/dsh-qqbot-bridge (host dsh `approval/request`
191
- > standard event β€” the same wiring used by official dsh-acp and the Web approval dialog).
192
-
193
- ## Configuration
194
-
195
- | Config | Type | Default | Description |
196
- |------|------|--------|------|
197
- | `appId` | string | **required** | QQ Bot AppID (or via `QQBOT_APPID` env var) |
198
- | `appSecret` | string | **required** | QQ Bot AppSecret (or via `QQBOT_SECRET` env var) |
199
- | `provider` | string | `deepseek-official` | LLM provider name |
200
- | `model` | string | `deepseek-chat` | Model name |
201
- | `preset` | string | - | Agent preset id |
202
- | `cwd` | string | `process.cwd()` | Agent working directory |
203
- | `requireMention` | boolean | `true` | Whether group messages require @bot to trigger |
204
- | `groupPrompt` | string | - | Extra system prompt for group chats |
205
- | `directPrompt` | string | - | Extra system prompt for direct chats |
206
- | `textChunkLimit` | number | `4500` | Max chars per message |
207
- | `sessionIdleTimeout` | number | `1800000` | Session idle timeout (ms), default 30 min |
208
- | `debug` | boolean | `false` | Debug mode |
209
-
210
- ## Built-in Commands
211
-
212
- | Command | Description |
213
- |------|------|
214
- | `/bot-reset` | Reset the current session (clear context) |
215
- | `/bot-model` | View or switch model |
216
- | `/bot-status` | View current session status |
217
- | `/bot-help` | View all commands |
218
-
219
- ## Core Modules
220
-
221
- ```
222
- src/
223
- β”œβ”€β”€ index.ts # Cordis plugin entry (async apply)
224
- β”œβ”€β”€ config.ts # Config schema
225
- β”œβ”€β”€ types.ts # Global types
226
- β”œβ”€β”€ setup.ts # Credential binding (QR)
227
- β”œβ”€β”€ transport/ # Transport layer
228
- β”‚ β”œβ”€β”€ inbound.ts # QQ inbound message β†’ agent.followup()
229
- β”‚ β”œβ”€β”€ outbound.ts # session/event β†’ QQ sendMarkdown
230
- β”‚ β”œβ”€β”€ outbound-buffer.ts # Streaming buffer
231
- β”‚ └── chunker.ts # Markdown chunking
232
- β”œβ”€β”€ session/ # Session management
233
- β”‚ β”œβ”€β”€ session-manager.ts # QQ peer β†’ Agent mapping
234
- β”‚ └── idle-evictor.ts # Idle eviction
235
- β”œβ”€β”€ model/ # Model routing
236
- β”‚ β”œβ”€β”€ model-resolver.ts # Route resolution
237
- β”‚ β”œβ”€β”€ prefs-store.ts # Per-peer preference persistence
238
- β”‚ └── settings-reader.ts # settings.yaml read-only
239
- β”œβ”€β”€ shared/ # Shared utilities
240
- β”‚ β”œβ”€β”€ utils.ts # Common helpers
241
- β”‚ β”œβ”€β”€ scope.ts # scope/peer extraction
242
- β”‚ └── send-helper.ts # Chunked send
243
- β”œβ”€β”€ commands/ # Slash commands
244
- └── typings/ # External module declarations
245
- ```
246
-
247
- ## Session Routing
248
-
249
- sessionKey: `qqbot:${appId}:${scope}:${peerId}`, with the SessionId derived deterministically via SHA-256 so sessions survive restarts.
250
-
251
- Resolution strategy: in-process reuse β†’ persisted resume β†’ fresh create.
252
-
253
- ## Design Principles
254
-
255
- - **Pure Cordis plugin** β€” follows the dsh "Plugins, not loop changes" principle
256
- - **Declarative dependencies** β€” `inject = ['agents']`, no direct coupling to other plugins
257
- - **Session isolation** β€” one independent Agent per QQ direct user / group
258
- - **Preset support** β€” mount presets (toolkits, prompts, etc.) via the `agent-presets` service
259
- - **Idle eviction** β€” auto-dispose Agents on timeout to prevent memory leaks
260
- - **Markdown output** β€” replies sent as Markdown with code-block/table-aware chunking
261
-
262
- ## Local Development
263
-
264
- ```bash
265
- # Install dependencies
266
- pnpm install
267
-
268
- # Build
269
- pnpm build
270
-
271
- # Dev mode (watch)
272
- pnpm dev
273
-
274
- # Debug via --patch
275
- export QQBOT_APPID="xxx" QQBOT_SECRET="xxx"
276
- npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
277
- ```
278
-
279
- ## License
280
-
281
- [MIT](./LICENSE)
1
+ # @zaofan/dsh-qqbot
2
+
3
+ An **enhanced fork** of the QQ Bot IM plugin for [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) (dsh): it drives the dsh agent loop with the QQ messaging platform as the frontend protocol, adding a local sticker library, rich media send/recall, scheduled tasks, multi-instance personas, and a visual settings panel.
4
+
5
+ πŸ“¦ Repo: [gcry13067381632-jpg/dsh-qqbot](https://github.com/gcry13067381632-jpg/dsh-qqbot) (forked from [tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot))
6
+
7
+ [δΈ­ζ–‡ζ–‡ζ‘£](./README.md) | English
8
+
9
+ ## πŸ‹ This fork (enhanced edition)
10
+
11
+ **In one line**: bring your dsh-powered QQ bot to life β€” it auto-collects stickers from group chats, picks and sends the right one when the mood hits, speaks up on schedule, and lets you run several bots with different personalities from one computer.
12
+
13
+ This repo is an enhanced fork of [@tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot) (changes live outside the upstream source β€” reinstalling/upgrading upstream will wipe them).
14
+
15
+ ### What she does for you
16
+
17
+ **🀳 Group images become her sticker library automatically** β€” saved locally with dedup ("to-sort / favorite / trash"). Say *"send something happy"* and she searches, picks and sends on her own β€” with guardrails (no posting into a cold chat, rate limits, no repeat stickers).
18
+
19
+ **⏰ She speaks up on time** β€” schedule a daily greeting, or tell her *"remind me to drink water in 30 seconds"* and she actually will.
20
+
21
+ **πŸ§‘β€πŸ€β€πŸ§‘ Many bots, many personalities, one computer** β€” each with its own AppID, persona and working directory (stickers / timers / gates fully isolated). Adding one is a QR scan away in the Web panel.
22
+
23
+ **πŸ’¬ Floating dock: her pocket console**
24
+ The little ball at the corner of the settings panel opens a whole control deck: **πŸ’¬ Chat** (replay a group/DM conversation QQ-style β€” bubbles + avatars, zoomable images, inline local video, SILK voice converted to MP3 in pure JS, file download cards; compose text / images / files right below, long messages auto-split into safe chunks that never get swallowed by QQ); **πŸ“₯ join-request approval**, **πŸ”‡ mute** (when she is a group admin, she pings you on join requests β€” reply "approve/reject"); **βš™οΈ Outbound** (adaptive-active: recent human messages get quoted replies first, bursts auto-switch to independent messages, scheduled/background pushes never get dropped).
25
+
26
+ **πŸ›‘οΈ Group-admin helper** β€” join approval + mute management over official APIs, with human-readable errors (not an admin / cannot mute the owner…).
27
+
28
+ **πŸ–₯️ No config-file surgery** β€” reply pacing, sticker gates, scheduled wake-ups, outbound mode and per-bot personas are all in the panel; saving applies live (only adding/removing bots needs a restart). There is even a ✏️ persona editor to tweak her "personality file" right in the browser.
29
+
30
+ **πŸ“¦ Clean & lean** β€” drive image sending / recall from plain text (`[MEDIA:image|path]` / `[RECALL]`); the repo contains no bot credentials or private data.
31
+
32
+ ### πŸ“Έ Showcase
33
+
34
+ Left: the dsh runtime backend β€” reasoning, tool calls and token usage are fully visible (paired with the `reply_gate` gate tool, the bot decides on its own whether to speak or stay silently idle);
35
+ Middle: real QQ group conversation β€” hide-and-seek role-play, replying when it should and staying quiet when it shouldn't;
36
+ Right: sticker-battle in action β€” the bot answers with stickers from its own library, image and text sent as separate messages.
37
+
38
+ ![Runtime backend log (reasoning & tool calls visible)](docs/showcase-1-log.png)
39
+
40
+ ![QQ group conversation (role-play / self-decided silence)](docs/showcase-2-chat.png)
41
+
42
+ ![Sticker battle (replying with own sticker library)](docs/showcase-3-doutu.png)
43
+
44
+ ### For developers
45
+ - Standard tools available inside QQ sessions: `send_media` / `recall_message` / `list_stickers` / `sticker_tag` / `sticker_untagged` / `schedule_timer` / `schedule_cancel` …, routed per bot account.
46
+ - **Group admin tools** (`group_join_requests` / `group_approve_join` / `group_mute_state` / `group_mute_member` …): join-request approval and mute management β€” requires the bot to be a group admin; inside a group session the current group is used, elsewhere the configured `manageGroup` applies.
47
+ - **Plain text can send media or recall messages**: writing `[MEDIA:image|path-or-url]` in a reply turns it into a real image message (`voice`/`video`/`file` work the same); a lone `[RECALL]` line recalls the bot's own last message.
48
+ - Host-level fixes (workspace session attachment, upstream PR #21) are included β€” idempotent and fully fail-soft.
49
+
50
+ > πŸ›‘οΈ This repo contains **no** bot credentials, sticker data, logs or personal paths (cleaned before publishing). Inject AppID/AppSecret via env vars or the Web panel β€” **never commit them**.
51
+
52
+ ### Build & deploy
53
+
54
+ ```bash
55
+ npm install # install deps (peer deps resolved by the dsh host)
56
+ node node_modules/typescript/lib/tsc.js -p tsconfig.json # or npm run build
57
+ # then copy dist/ over your dsh profile's
58
+ # node_modules/@tencent-connect/dsh-qqbot/dist/ and restart dsh
59
+ ```
60
+
61
+ See the upstream "Installation" section below (`dsh plugin add` + QR onboarding both work).
62
+
63
+ ---
64
+
65
+ ## Architecture
66
+
67
+ ```
68
+ QQ User β†’ QQ WebSocket β†’ dsh-im-qqbot β†’ ctx.agents β†’ dsh agent loop β†’ LLM
69
+ ↑ β”‚
70
+ └── session/event β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
71
+ (assistant reply β†’ QQ sendMarkdown)
72
+ ```
73
+
74
+ ## Installation
75
+
76
+ > ⚠️ **Install into the `web` profile** (host of the `dsh web` settings panel); other profiles get a bare
77
+ > environment with no settings UI. Do **not** `add @tencent-connect/dsh-qqbot` β€” that installs the upstream
78
+ > official version (without this fork's features).
79
+ >
80
+ > βœ… **Single package, everything included**: QQ bot + the Web settings panel (host bridge + settings UI) all
81
+ > ship in this one package β€” after install, dsh Web β†’ Settings shows a "QQ bot (im-qqbot)" page (one per bot
82
+ > instance). **No separate dsh-qqbot-settings install needed.**
83
+
84
+ ### Method 1 (after npm release): one command
85
+
86
+ ```powershell
87
+ npx @deepseek-ai/dsh plugin --profile web add @zaofan/dsh-qqbot
88
+ ```
89
+
90
+ > Until it is published to npm, use Method 2 below.
91
+
92
+ ### Method 2: from source (recommended for now)
93
+
94
+ **Windows (one-click script)**:
95
+
96
+ ```powershell
97
+ git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
98
+ cd dsh-qqbot
99
+ .\install.ps1 # npm install/build -> pack -> add tarball -> prints restart steps
100
+ ```
101
+
102
+ > If script execution is blocked: `powershell -ExecutionPolicy Bypass -File .\install.ps1`
103
+
104
+ **macOS / Linux (manual)**:
105
+
106
+ ```bash
107
+ git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
108
+ cd dsh-qqbot
109
+ npm install && npm run build
110
+ pnpm pack --pack-destination /tmp
111
+ npx @deepseek-ai/dsh plugin --profile web add /tmp/zaofan-dsh-qqbot-0.4.0.tgz
112
+ ```
113
+
114
+ > πŸ’‘ Why a tarball instead of `add <source dir>`? Lessons from real installs:
115
+ > β‘  a directory path containing spaces gets split at the spaces on Windows (pnpm reports `- isn't supported`);
116
+ > β‘‘ `add <dir>` becomes a pnpm link (junction), so the plugin can't locate the profile by its code location,
117
+ > and scanned credentials cannot be persisted (env-var-only fallback).
118
+
119
+ ### Troubleshooting: npm install fails with ERESOLVE (backported from upstream PR #42)
120
+
121
+ A fresh `npm install` may fail with `ERESOLVE could not resolve`. Cause: peer deps like
122
+ `@deepseek-ai/dsh-tools` / `dsh-agent` are still on prerelease (-rc) version lines, and npm 7+
123
+ strict resolution rejects non-intersecting combinations. **This is an upstream version-line issue, not a plugin bug.** Two workarounds:
124
+
125
+ ```bash
126
+ npm install --legacy-peer-deps # install-time resolution only; runtime behavior unchanged
127
+ # or: install deps, then build & pack manually (peers are resolved by the dsh host)
128
+ ```
129
+
130
+ > Tracked: once upstream #37 is fixed and version lines converge, this section can be removed.
131
+
132
+ ### First launch & binding
133
+
134
+ Start `dsh web`. If credentials are missing, the **QR flow** starts automatically: a QR code is printed in the
135
+ terminal β†’ scan it with the QQ mobile app β†’ credentials are saved and survive restarts (you can also use
136
+ "QR bind" / edit accounts in the settings panel at any time).
137
+
138
+ ![QR code scan example](./docs/assets/qrcode.png)
139
+
140
+ > **Note**: Use `0.4.0` or later for browser-link scanning, which avoids QR code misalignment in some terminals.
141
+
142
+ ### Don't have a QQ bot yet? Register one (get AppID / AppSecret)
143
+
144
+ 1. Open the [QQ Open Platform](https://q.qq.com) and sign in with your QQ account;
145
+ 2. Go to "Bot" β†’ "Create Bot" and fill in name, avatar and description;
146
+ 3. After creation, copy the **AppID** and **AppSecret** from the bot detail page;
147
+ 4. Enter them in dsh Web β†’ Settings β†’ "QQ bot" β†’ "Accounts & presets" and save
148
+ (or set env vars `QQBOT_APPID` / `QQBOT_SECRET`);
149
+ 5. Enable the needed **single-chat / group-chat** message permissions on the platform
150
+ (group chat usually requires a use-case review).
151
+
152
+ > πŸ’‘ Easier: once the bot exists, just use the **QR scan bind** on first launch β€” no need to type credentials.
153
+
154
+ ### For developers: --patch dev mode
155
+
156
+ ```bash
157
+ export QQBOT_APPID="yourAppID" QQBOT_SECRET="yourAppSecret"
158
+ npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
159
+ ```
160
+
161
+ ## Remote approval over QQ (optional)
162
+
163
+ When an Agent tool needs access **outside the workspace**, dsh raises a permission request. With this
164
+ feature on, the bot forwards the request to the **QQ conversation of the task's initiator**, and you
165
+ allow/deny right from QQ:
166
+
167
+ > ⚠️ **DSH permission request**
168
+ > Tool: pwsh
169
+ > Reason: needs access outside the workspace
170
+ >
171
+ > Allow this operation: `/approve A1B2C3`
172
+ > Deny this operation: `/deny A1B2C3`
173
+ > One-time only; auto-denied after 120 s.
174
+
175
+ **Enable** (either way; takes effect for new requests right after saving, no restart needed):
176
+ - **Web settings panel**: Settings β†’ "QQ bot" β†’ β‘€ QQ remote approval β†’ tick it on;
177
+ - or add two lines to the instance `config` in `cordis.patch.yml`, then restart:
178
+
179
+ ```yaml
180
+ - id: im-qqbot
181
+ config:
182
+ enableApprovals: true # default false
183
+ approvalTimeoutMs: 120000 # wait window; auto-deny on timeout
184
+ ```
185
+
186
+ **Security boundaries**: one-time code; only the **initiator in the same conversation** may decide
187
+ (others in a group chat cannot approve even if they see the code); grants only the current operation;
188
+ auto-cancelled when the agent is cancelled or dsh exits.
189
+
190
+ > Idea source: QQ-approval design of wang-22-code/dsh-qqbot-bridge (host dsh `approval/request`
191
+ > standard event β€” the same wiring used by official dsh-acp and the Web approval dialog).
192
+
193
+ ## Configuration
194
+
195
+ | Config | Type | Default | Description |
196
+ |------|------|--------|------|
197
+ | `appId` | string | **required** | QQ Bot AppID (or via `QQBOT_APPID` env var) |
198
+ | `appSecret` | string | **required** | QQ Bot AppSecret (or via `QQBOT_SECRET` env var) |
199
+ | `provider` | string | `deepseek-official` | LLM provider name |
200
+ | `model` | string | `deepseek-chat` | Model name |
201
+ | `preset` | string | - | Agent preset id |
202
+ | `cwd` | string | `process.cwd()` | Agent working directory |
203
+ | `requireMention` | boolean | `true` | Whether group messages require @bot to trigger |
204
+ | `groupPrompt` | string | - | Extra system prompt for group chats |
205
+ | `directPrompt` | string | - | Extra system prompt for direct chats |
206
+ | `textChunkLimit` | number | `4500` | Max chars per message |
207
+ | `sessionIdleTimeout` | number | `1800000` | Session idle timeout (ms), default 30 min |
208
+ | `debug` | boolean | `false` | Debug mode |
209
+
210
+ ## Built-in Commands
211
+
212
+ | Command | Description |
213
+ |------|------|
214
+ | `/bot-reset` | Reset the current session (clear context) |
215
+ | `/bot-model` | View or switch model |
216
+ | `/bot-status` | View current session status |
217
+ | `/bot-restart` | Self-restart the dsh host (~4s, auto relaunch) |
218
+ | `/botplay` | List assembled interactive events; `/botplay <name>` triggers a keyboard card |
219
+ | `/bot-help` | View all commands |
220
+
221
+ ## User Extensions (custom slash commands / QQ tools) (v0.9.8+)
222
+
223
+ Extensions live under the **account workspace** (never inside the plugin package), so upgrading the plugin never overwrites them.
224
+
225
+ ```
226
+ .qqbot-extensions/
227
+ β”œβ”€β”€ commands/ # custom slash commands (take effect after /bot-restart)
228
+ └── tools/ # custom QQ tools callable by the AI (hot-reload via /tools-reload)
229
+ ```
230
+
231
+ **Slash command** (`.qqbot-extensions/commands/hello.mjs`):
232
+ ```js
233
+ export default {
234
+ name: ['hello', 'δ½ ε₯½'],
235
+ description: 'say hi',
236
+ handler: (ctx) => `πŸ‘‹ hi ${ctx.command.raw || ''}`.trim(),
237
+ };
238
+ ```
239
+
240
+ **QQ tool** (`.qqbot-extensions/tools/roll_dice.mjs`):
241
+ ```js
242
+ export default {
243
+ name: 'roll_dice',
244
+ description: 'roll an N-sided die',
245
+ inputSchema: { sides: { type: 'integer', description: 'sides, default 6' } }, // optional params: no `required` key
246
+ run: async (args, env) => ({ ok: true, msg: `🎲 ${1 + Math.floor(Math.random() * 6)}` }),
247
+ };
248
+ ```
249
+ Then run `/tools-reload` (or ask the AI to call the `tools_reload` tool). Commands need a host restart.
250
+
251
+ ## Core Modules
252
+
253
+ ```
254
+ src/
255
+ β”œβ”€β”€ index.ts # Cordis plugin entry (async apply)
256
+ β”œβ”€β”€ config.ts # Config schema
257
+ β”œβ”€β”€ types.ts # Global types
258
+ β”œβ”€β”€ setup.ts # Credential binding (QR)
259
+ β”œβ”€β”€ transport/ # Transport layer
260
+ β”‚ β”œβ”€β”€ inbound.ts # QQ inbound message β†’ agent.followup()
261
+ β”‚ β”œβ”€β”€ outbound.ts # session/event β†’ QQ sendMarkdown
262
+ β”‚ β”œβ”€β”€ outbound-buffer.ts # Streaming buffer
263
+ β”‚ └── chunker.ts # Markdown chunking
264
+ β”œβ”€β”€ session/ # Session management
265
+ β”‚ β”œβ”€β”€ session-manager.ts # QQ peer β†’ Agent mapping
266
+ β”‚ └── idle-evictor.ts # Idle eviction
267
+ β”œβ”€β”€ model/ # Model routing
268
+ β”‚ β”œβ”€β”€ model-resolver.ts # Route resolution
269
+ β”‚ β”œβ”€β”€ prefs-store.ts # Per-peer preference persistence
270
+ β”‚ └── settings-reader.ts # settings.yaml read-only
271
+ β”œβ”€β”€ shared/ # Shared utilities
272
+ β”‚ β”œβ”€β”€ utils.ts # Common helpers
273
+ β”‚ β”œβ”€β”€ scope.ts # scope/peer extraction
274
+ β”‚ └── send-helper.ts # Chunked send
275
+ β”œβ”€β”€ commands/ # Slash commands
276
+ └── typings/ # External module declarations
277
+ ```
278
+
279
+ ## Session Routing
280
+
281
+ sessionKey: `qqbot:${appId}:${scope}:${peerId}`, with the SessionId derived deterministically via SHA-256 so sessions survive restarts.
282
+
283
+ Resolution strategy: in-process reuse β†’ persisted resume β†’ fresh create.
284
+
285
+ ## Design Principles
286
+
287
+ - **Pure Cordis plugin** β€” follows the dsh "Plugins, not loop changes" principle
288
+ - **Declarative dependencies** β€” `inject = ['agents']`, no direct coupling to other plugins
289
+ - **Session isolation** β€” one independent Agent per QQ direct user / group
290
+ - **Preset support** β€” mount presets (toolkits, prompts, etc.) via the `agent-presets` service
291
+ - **Idle eviction** β€” auto-dispose Agents on timeout to prevent memory leaks
292
+ - **Markdown output** β€” replies sent as Markdown with code-block/table-aware chunking
293
+
294
+ ## Local Development
295
+
296
+ ```bash
297
+ # Install dependencies
298
+ pnpm install
299
+
300
+ # Build
301
+ pnpm build
302
+
303
+ # Dev mode (watch)
304
+ pnpm dev
305
+
306
+ # Debug via --patch
307
+ export QQBOT_APPID="xxx" QQBOT_SECRET="xxx"
308
+ npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
309
+ ```
310
+
311
+ ## License
312
+
313
+ [MIT](./LICENSE)