@lijian-ui/dsh-im-gateway 0.1.0 → 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.
- package/README.en.md +297 -0
- package/README.md +188 -76
- package/lib/client.js +176 -63
- package/lib/{index-2mMnMMFx.d.ts → index-DcKTwqN6.d.ts} +256 -1
- package/lib/index.js +1806 -259
- package/package.json +8 -3
- package/README.zh-CN.md +0 -207
package/README.en.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# @lijian-ui/dsh-im-gateway
|
|
2
|
+
|
|
3
|
+
English | [简体中文](./README.md)
|
|
4
|
+
|
|
5
|
+
> A multi-channel IM gateway plugin for **DeepSeek Harness (dsh)** — connect DingTalk, QQ and WeChat (iLink) bots to your harness agent, with QR-scan binding, streaming replies, tool approval, interactive questions, long-text splitting, multi-segment merge, and bilingual UI.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@lijian-ui/dsh-im-gateway)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
### Channels & Core
|
|
15
|
+
|
|
16
|
+
- **Unified gateway service** — one plugin, three channels. Every channel routes through a single `ctx.imGateway` core: session management, slash commands, streaming, status broadcast.
|
|
17
|
+
- **DingTalk** — outbound WebSocket stream connection, group + single chats, @-mention filtering, **AI card streaming** (real-time incremental replies), slash commands.
|
|
18
|
+
- **QQ** — WebSocket gateway (official `qqbot-nodejs` SDK), private (c2c) + group chats, **QR-scan bot binding** (no manual console setup), **streaming messages** (c2c).
|
|
19
|
+
- **WeChat (iLink)** — official iLink long-poll protocol, **QR-scan login + pairing code**, single-chat only, media (AES-128-ECB CDN) in/out.
|
|
20
|
+
- **Multi-bot instances** — the same channel type can appear many times (e.g. two DingTalk bots), each with its own credentials.
|
|
21
|
+
- **Settings UI** — a full web settings page ("IM 通道") rendered inside the official dsh web UI; QR-scan binding happens right there.
|
|
22
|
+
- **Streaming replies** — DingTalk AI Card, QQ stream_messages; plain-text fallback when a channel doesn't support streaming.
|
|
23
|
+
- **Instance lock** — file-based `DSH_HOME` lock prevents multiple instances from corrupting the session log.
|
|
24
|
+
|
|
25
|
+
### Interaction Enhancements
|
|
26
|
+
|
|
27
|
+
- **Tool approval bridge** — when the agent calls a tool requiring approval, reply "approve" or "reject" directly in IM; timeout falls back to dsh's native approval system.
|
|
28
|
+
- **Interactive question bridge** — when the agent calls `ask_user_question`, the question is pushed to IM; reply with option numbers/text to answer. Races against the Web UI — first answer wins.
|
|
29
|
+
- **Long reply splitting** — replies exceeding the channel's single-message limit are auto-split at sentence boundaries with `(1/3)` segment prefixes, sent as separate messages.
|
|
30
|
+
- **Multi-segment input merge** — consecutive messages are auto-merged into one (configurable timeout window); use `..` to continue merging, `!!` to submit immediately.
|
|
31
|
+
- **File-sending tool** — the agent can call `im_send_file` to send workspace files (images/videos/documents) directly to the current IM session.
|
|
32
|
+
- **Bilingual UI** — set `language: 'zh' | 'en'` to switch all user-visible replies between Chinese and English.
|
|
33
|
+
|
|
34
|
+
### Session Management
|
|
35
|
+
|
|
36
|
+
- **Built-in slash commands** — `/help`, `/model`, `/status`, `/new`, `/reset`, `/stop`, `/sessions`, `/continue`, `/workspaces`, `/workspace` (see below).
|
|
37
|
+
- **Session list & continue** — `/sessions` lists recent sessions (with titles); `/continue <id>` resumes a specific session.
|
|
38
|
+
- **Workspace management** — `/workspaces` lists all workspaces with session counts; `/workspace <path>` switches workspace (resets the current session; next message creates a new session in the new workspace).
|
|
39
|
+
- **User allowlist** — configure `allowAllUsers` or `allowedUserIds` to control who can use the bot.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
Requires DeepSeek Harness (dsh) — the plugin is a standard **dsh bundle** and installs through the official plugin channel.
|
|
46
|
+
|
|
47
|
+
### From npm (recommended)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
dsh plugin --profile web add @lijian-ui/dsh-im-gateway
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The npm package ships pre-built `lib/` — **no build authorization needed** (`allowBuilds` is not required).
|
|
54
|
+
|
|
55
|
+
### From tarball
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm pack @lijian-ui/dsh-im-gateway
|
|
59
|
+
dsh plugin --profile web add ./dsh-im-gateway-0.1.1.tgz
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### From GitHub
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
dsh plugin --profile web add github:lijian-ui/dsh-im-gateway
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> Git installs fetch **source**, so the first install requires approving the package's `prepare` build script (pnpm ≥ 10). Add the package key to the profile's `pnpm-workspace.yaml` → `allowBuilds` when prompted. Prefer npm/tarball to skip this.
|
|
69
|
+
|
|
70
|
+
### Verify installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dsh --profile web --dump-config # should show a "# == @lijian-ui/dsh-im-gateway" config layer
|
|
74
|
+
dsh --profile web # open Settings → "IM 通道" in the browser
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
1. Open the dsh web UI → **Settings → IM 通道**.
|
|
82
|
+
2. Click **添加通道** (Add channel).
|
|
83
|
+
3. Pick a channel type:
|
|
84
|
+
- **QQ**: click **扫码登录** → scan with mobile QQ → credentials auto-fill → save.
|
|
85
|
+
- **个人微信** (WeChat): click **扫码登录** → scan with mobile WeChat → (enter the pairing code if asked) → credentials auto-fill → save.
|
|
86
|
+
- **钉钉** (DingTalk): fill in AppKey / AppSecret manually (or edit the config file) → save.
|
|
87
|
+
4. Message your bot from the IM client — replies stream back in real time.
|
|
88
|
+
|
|
89
|
+
> Config is stored under `~/.dsh/settings.yaml` (`im-gateway.channels`). Saving from the UI **hot-reloads** the channels (no restart needed).
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Slash Commands
|
|
94
|
+
|
|
95
|
+
Sent to the bot in any IM channel:
|
|
96
|
+
|
|
97
|
+
| Command | Description |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| `/help` | List available commands |
|
|
100
|
+
| `/model` | List models with emoji numbers; `/model 1` or `/model <name>` to switch (no session yet → sets default for next conversation) |
|
|
101
|
+
| `/status` | Channel / cwd / current model / agent state |
|
|
102
|
+
| `/new` `/reset` `/clear` | Start a fresh conversation |
|
|
103
|
+
| `/stop` | Abort the current reply |
|
|
104
|
+
| `/sessions` | List the 10 most recent sessions (current session marked, with titles) |
|
|
105
|
+
| `/continue <session-id>` | Continue a specific session (use `/sessions` for IDs) |
|
|
106
|
+
| `/workspaces` | List all workspaces (sorted by recent activity, with session counts) |
|
|
107
|
+
| `/workspace <path>` | Switch to the specified workspace (resets current session; next message creates a new session in the new workspace) |
|
|
108
|
+
| `/workspace` | Without arguments, same as `/workspaces` |
|
|
109
|
+
|
|
110
|
+
### Multi-segment Input Suffixes
|
|
111
|
+
|
|
112
|
+
| Suffix | Effect |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| (none) | Enters the merge window, waits for more messages (auto-submits after 3s by default) |
|
|
115
|
+
| `..` | Continue merging: add this message to the buffer, keep waiting |
|
|
116
|
+
| `!!` | Submit now: merge the buffer + this message and send to the agent immediately |
|
|
117
|
+
|
|
118
|
+
### Approval Replies
|
|
119
|
+
|
|
120
|
+
When the agent calls a tool that requires approval, reply directly:
|
|
121
|
+
|
|
122
|
+
| Reply | Effect |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `approve` / `yes` / `y` / `allow` | Allow the tool execution |
|
|
125
|
+
| `reject` / `no` / `n` / `deny` | Block the tool execution |
|
|
126
|
+
|
|
127
|
+
(Chinese equivalents: `批准` / `同意` for approve, `拒绝` for reject.)
|
|
128
|
+
|
|
129
|
+
After timeout, the request falls back to dsh's native approval system.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Configuration
|
|
134
|
+
|
|
135
|
+
Everything is editable from the settings UI; the underlying schema lives in `~/.dsh/settings.yaml`:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
im-gateway:
|
|
139
|
+
language: zh # UI language: zh (Chinese) | en (English)
|
|
140
|
+
approvalTimeoutSecs: 120 # Tool approval timeout (seconds)
|
|
141
|
+
questionTimeoutSecs: 600 # Interactive question timeout (seconds)
|
|
142
|
+
mergeTimeoutSecs: 3 # Multi-segment input merge window (seconds)
|
|
143
|
+
allowAllUsers: false # Allow all users (dev only)
|
|
144
|
+
allowedUserIds: # Allowlist: { channelId: string[] } or use '*' for any channel
|
|
145
|
+
"*":
|
|
146
|
+
- user-abc
|
|
147
|
+
channels:
|
|
148
|
+
- id: dingtalk-main
|
|
149
|
+
type: dingtalk
|
|
150
|
+
name: 主机器人
|
|
151
|
+
enabled: true
|
|
152
|
+
config:
|
|
153
|
+
clientId: "..."
|
|
154
|
+
clientSecret: "..."
|
|
155
|
+
# callbackBaseUrl, appId, botAppId, baseUrl, botId, cdnBaseUrl, pollIntervalMs...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Gateway-level Config
|
|
159
|
+
|
|
160
|
+
| Field | Default | Description |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| `language` | `zh` | UI language (`zh` Chinese / `en` English); affects all user-visible replies |
|
|
163
|
+
| `streamThrottleMs` | `800` | Throttle interval for streaming pushes (ms) |
|
|
164
|
+
| `slashCommands` | `true` | Enable slash commands |
|
|
165
|
+
| `approvalTimeoutSecs` | `120` | Tool approval IM wait timeout (seconds); falls back to dsh native approval on timeout |
|
|
166
|
+
| `questionTimeoutSecs` | `600` | Interactive question IM wait timeout (seconds); falls back to Web UI on timeout |
|
|
167
|
+
| `mergeTimeoutSecs` | `3` | Multi-segment input merge window (seconds); consecutive messages merged into one |
|
|
168
|
+
| `allowAllUsers` | `false` | Allow all users (dev only; do not enable in production) |
|
|
169
|
+
| `allowedUserIds` | `{}` | Allowlist; key is channelId (`*` matches any), value is array of user IDs |
|
|
170
|
+
|
|
171
|
+
### Channel-level Config
|
|
172
|
+
|
|
173
|
+
| Field | Applies to | Meaning |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| `clientId` / `clientSecret` | dingtalk | DingTalk app key / secret (Stream mode) |
|
|
176
|
+
| `appId` / `clientSecret` | qq | QQ Open Platform credentials (QR-bound) |
|
|
177
|
+
| `token` / `botId` / `baseUrl` / `cdnBaseUrl` | weixin | iLink credentials (QR-bound) |
|
|
178
|
+
| `enabled` | all | Whether this instance connects |
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Architecture
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
IM client ──► channel adapter (dingtalk / qq / weixin)
|
|
186
|
+
│ ImInboundMessage
|
|
187
|
+
▼
|
|
188
|
+
ctx.imGateway (core)
|
|
189
|
+
│ multi-segment merge → allowlist check → approval/question intercept → slash commands
|
|
190
|
+
│ ensureSession → agent.followup
|
|
191
|
+
▼
|
|
192
|
+
dsh harness agent (LLM loop)
|
|
193
|
+
│ session events (turn/start, assistant/chunk, tool/call, turn/end)
|
|
194
|
+
▼
|
|
195
|
+
EventDispatcher → streaming reply / splitting / tool hints
|
|
196
|
+
│ (AI Card / stream_messages / plain text fallback)
|
|
197
|
+
▼
|
|
198
|
+
IM client
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Module Structure
|
|
202
|
+
|
|
203
|
+
| Module | Responsibility |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| `im-gateway.ts` | Core service ImGatewayService: session management, message routing, tool registration |
|
|
206
|
+
| `events.ts` | EventDispatcher: SessionEvent → IM channel operations (streaming, splitting, tool hints) |
|
|
207
|
+
| `commands.ts` | CommandHandler: slash commands (/help /reset /model /status /stop /sessions /continue /workspaces /workspace) |
|
|
208
|
+
| `stream.ts` | StreamThrottle: batch text-delta and push at intervals |
|
|
209
|
+
| `approval.ts` | ApprovalBroker: tool approval bridge, holds approval/request pending IM reply |
|
|
210
|
+
| `questions.ts` | QuestionBroker: interactive question bridge, holds ask_user_question pending IM reply |
|
|
211
|
+
| `split.ts` | splitText: long-text splitting at sentence boundaries with segment prefixes |
|
|
212
|
+
| `merge.ts` | SessionMerger: multi-segment input merge, supports `..` / `!!` control suffixes |
|
|
213
|
+
| `i18n.ts` | Translator: Chinese/English bilingual translation table |
|
|
214
|
+
| `instance-lock.ts` | acquireInstanceLock: DSH_HOME file lock, prevents concurrent session log corruption |
|
|
215
|
+
| `types.ts` | Interface definitions: ImChannelAdapter, ImGatewayConfig, ImGateway, etc. |
|
|
216
|
+
|
|
217
|
+
- **Host half** (node): `src/index.ts` (apply), `src/gateway/` (core + above modules), `src/channels/` (dingtalk / qq / weixin + protocol helpers), `src/remote.ts` (Typert RPC for the settings UI), `src/sync.ts` (channel reload on config save).
|
|
218
|
+
- **Client half** (browser): `src/client/` — settings page "IM 通道" (add/edit modal + QR-scan login + status dots).
|
|
219
|
+
- **Multi-bot**: `channels` is an array; the same `type` may appear multiple times.
|
|
220
|
+
|
|
221
|
+
### Extension Points
|
|
222
|
+
|
|
223
|
+
Third parties can register their own channel without forking:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { ImChannelAdapter } from '@lijian-ui/dsh-im-gateway' // peerDependency on the core
|
|
227
|
+
|
|
228
|
+
class MyChannelAdapter implements ImChannelAdapter { /* ... */ }
|
|
229
|
+
ctx.imGateway.registerChannel(myAdapter)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`ImChannelAdapter` interface optional methods:
|
|
233
|
+
|
|
234
|
+
| Method | Description |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| `sendText(convId, text)` | **Required**. Send a plain-text message |
|
|
237
|
+
| `sendMedia(convId, filePath, caption?)` | Optional. Send a file/image/video (used by im_send_file tool) |
|
|
238
|
+
| `beginStream(convId)` | Optional. Open a streaming reply (called on first text delta) |
|
|
239
|
+
| `streamText(convId, text)` | Optional. Streaming overwrite update (throttled) |
|
|
240
|
+
| `endStream(convId, fullText)` | Optional. Close a streaming reply (called on turn/end) |
|
|
241
|
+
| `updateCard(convId, text)` | Optional. Legacy single-shot card update |
|
|
242
|
+
| `authorizes(userId)` | Optional. Channel-local authorization check (return false to block) |
|
|
243
|
+
| `maxMessageChars` | Optional. Max chars per message (default 4000, used for splitting) |
|
|
244
|
+
| `label` | Optional. Channel display name (used in question receipts) |
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Development
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
git clone https://github.com/lijian-ui/dsh-im-gateway.git
|
|
252
|
+
cd dsh-im-gateway
|
|
253
|
+
npm install
|
|
254
|
+
npm run build # tsdown → lib/
|
|
255
|
+
npm run watch # rebuild on save
|
|
256
|
+
npm run typecheck
|
|
257
|
+
npm test # node --test tests/*.test.mjs
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Local link into a dsh profile:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
dsh plugin --profile web add ./ # install from this directory (link)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
> **Windows note**: the dsh subprocess loads `lib/index.js` from `package.json` `main` — after editing `src/`, always `npm run build` then restart the dsh process (its require cache keeps the old module).
|
|
267
|
+
|
|
268
|
+
### Tests
|
|
269
|
+
|
|
270
|
+
Tests use Node.js's built-in test runner (`node:test`), in the `tests/` directory:
|
|
271
|
+
|
|
272
|
+
| Test file | Module covered | Tests |
|
|
273
|
+
| --- | --- | --- |
|
|
274
|
+
| `approval.test.mjs` | ApprovalBroker | 8 |
|
|
275
|
+
| `questions.test.mjs` | QuestionBroker + parseQuestionReply + formatQuestionPrompt | 8 |
|
|
276
|
+
| `split.test.mjs` | splitText | 8 |
|
|
277
|
+
| `merge.test.mjs` | SessionMerger + stripControlSuffix | 9 |
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Troubleshooting
|
|
282
|
+
|
|
283
|
+
- **No logs from the plugin** — cordis buffers `ctx.logger.*` in memory by default. The plugin registers a console exporter on apply, so logs appear in the dsh subprocess stderr (desktop shells prefix them with `[dsh]`).
|
|
284
|
+
- **QQ client shows "连接中" (connecting) forever** — streaming was opened too early or never closed. This plugin opens the stream on the first assistant text delta and always closes it on `turn/end` (fixed in 0.1.x).
|
|
285
|
+
- **Chat works but replies are not streaming** — the channel fell back to plain text (e.g. QQ group chats don't support `stream_messages`; WeChat has no streaming concept). This is by design.
|
|
286
|
+
- **Replies split into multiple messages** — auto-splitting kicks in when text exceeds the channel's `maxMessageChars` limit, with `(1/3)` prefixes. This is by design, not a bug.
|
|
287
|
+
- **Multiple messages got merged** — consecutive messages within the 3s merge window are combined into one. Send `!!` to submit immediately, or increase `mergeTimeoutSecs`.
|
|
288
|
+
- **Approval/question timed out** — increase `approvalTimeoutSecs` / `questionTimeoutSecs`. On timeout, the request automatically falls back to the Web UI.
|
|
289
|
+
- **Some text still in Chinese after switching to English** — separators in `formatAnswerSummary` (`、` `;`) and `(空)` are intentionally fixed as they are format symbols, not natural language.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT © lijian-ui
|
|
296
|
+
|
|
297
|
+
Built for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) — independent plugin, not affiliated with or endorsed by DeepSeek.
|