@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 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
+ [![npm version](https://img.shields.io/npm/v/@lijian-ui/dsh-im-gateway)](https://www.npmjs.com/package/@lijian-ui/dsh-im-gateway)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](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.