dsh-connect 0.8.0 → 0.9.2
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.i18n.yaml +2 -2
- package/README.md +236 -18
- package/README.zh.md +185 -18
- package/client/client.js +517 -84
- package/client/client.js.map +4 -4
- package/client/locale.mjs +227 -0
- package/client/panel-state.mjs +54 -0
- package/client/settings-client.mjs +282 -61
- package/docs/images/settings-advanced-en.png +0 -0
- package/docs/images/settings-advanced-zh.png +0 -0
- package/docs/images/settings-defaults-en.png +0 -0
- package/docs/images/settings-defaults-zh.png +0 -0
- package/docs/images/settings-overview-en.png +0 -0
- package/docs/images/settings-overview-zh.png +0 -0
- package/lib/binding.d.ts.map +1 -1
- package/lib/binding.js +2 -2
- package/lib/binding.js.map +1 -1
- package/lib/channels/dingtalk/index.d.ts +49 -49
- package/lib/channels/dingtalk/index.d.ts.map +1 -1
- package/lib/channels/dingtalk/message.d.ts.map +1 -1
- package/lib/channels/dingtalk/message.js +7 -0
- package/lib/channels/dingtalk/message.js.map +1 -1
- package/lib/channels/feishu/adapter.d.ts +14 -13
- package/lib/channels/feishu/adapter.d.ts.map +1 -1
- package/lib/channels/feishu/adapter.js +122 -56
- package/lib/channels/feishu/adapter.js.map +1 -1
- package/lib/channels/feishu/i18n.d.ts +2 -0
- package/lib/channels/feishu/i18n.d.ts.map +1 -1
- package/lib/channels/feishu/i18n.js +2 -0
- package/lib/channels/feishu/i18n.js.map +1 -1
- package/lib/channels/feishu/index.d.ts +48 -27
- package/lib/channels/feishu/index.d.ts.map +1 -1
- package/lib/channels/feishu/index.js +32 -6
- package/lib/channels/feishu/index.js.map +1 -1
- package/lib/channels/telegram/adapter.d.ts.map +1 -1
- package/lib/channels/telegram/adapter.js +7 -1
- package/lib/channels/telegram/adapter.js.map +1 -1
- package/lib/channels/telegram/index.d.ts +13 -13
- package/lib/channels/telegram/index.d.ts.map +1 -1
- package/lib/channels/web/adapter.d.ts +3 -1
- package/lib/channels/web/adapter.d.ts.map +1 -1
- package/lib/channels/web/adapter.js +3 -1
- package/lib/channels/web/adapter.js.map +1 -1
- package/lib/channels/web/index.d.ts +5 -5
- package/lib/channels/web/index.d.ts.map +1 -1
- package/lib/chat-key.d.ts +29 -0
- package/lib/chat-key.d.ts.map +1 -0
- package/lib/chat-key.js +38 -0
- package/lib/chat-key.js.map +1 -0
- package/lib/index.d.ts +57 -63
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +330 -55
- package/lib/index.js.map +1 -1
- package/lib/interaction.d.ts +90 -50
- package/lib/interaction.d.ts.map +1 -1
- package/lib/interaction.js +233 -272
- package/lib/interaction.js.map +1 -1
- package/lib/retry.d.ts.map +1 -1
- package/lib/retry.js +7 -1
- package/lib/retry.js.map +1 -1
- package/lib/runner.d.ts +54 -0
- package/lib/runner.d.ts.map +1 -1
- package/lib/runner.js +158 -23
- package/lib/runner.js.map +1 -1
- package/lib/scheduler.d.ts.map +1 -1
- package/lib/scheduler.js +2 -2
- package/lib/scheduler.js.map +1 -1
- package/lib/service.d.ts +16 -0
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +46 -4
- package/lib/service.js.map +1 -1
- package/lib/settings/channel-runtime.d.ts +75 -0
- package/lib/settings/channel-runtime.d.ts.map +1 -0
- package/lib/settings/channel-runtime.js +165 -0
- package/lib/settings/channel-runtime.js.map +1 -0
- package/lib/settings/channels.d.ts +11 -0
- package/lib/settings/channels.d.ts.map +1 -1
- package/lib/settings/channels.js +31 -0
- package/lib/settings/channels.js.map +1 -1
- package/lib/settings/credential-store.d.ts +52 -6
- package/lib/settings/credential-store.d.ts.map +1 -1
- package/lib/settings/credential-store.js +92 -18
- package/lib/settings/credential-store.js.map +1 -1
- package/lib/settings/legacy-import.d.ts +190 -0
- package/lib/settings/legacy-import.d.ts.map +1 -0
- package/lib/settings/legacy-import.js +373 -0
- package/lib/settings/legacy-import.js.map +1 -0
- package/lib/settings/namespace.d.ts +263 -0
- package/lib/settings/namespace.d.ts.map +1 -0
- package/lib/settings/namespace.js +287 -0
- package/lib/settings/namespace.js.map +1 -0
- package/lib/settings/secret-disclosure.d.ts +50 -0
- package/lib/settings/secret-disclosure.d.ts.map +1 -0
- package/lib/settings/secret-disclosure.js +132 -0
- package/lib/settings/secret-disclosure.js.map +1 -0
- package/lib/settings/settings-model.d.ts +17 -0
- package/lib/settings/settings-model.d.ts.map +1 -1
- package/lib/settings/settings-model.js +18 -1
- package/lib/settings/settings-model.js.map +1 -1
- package/lib/settings/settings-rpc.d.ts +71 -3
- package/lib/settings/settings-rpc.d.ts.map +1 -1
- package/lib/settings/settings-rpc.js +167 -24
- package/lib/settings/settings-rpc.js.map +1 -1
- package/lib/settings/settings-service.d.ts +22 -1
- package/lib/settings/settings-service.d.ts.map +1 -1
- package/lib/settings/settings-service.js +145 -8
- package/lib/settings/settings-service.js.map +1 -1
- package/lib/state-dir.d.ts +28 -0
- package/lib/state-dir.d.ts.map +1 -0
- package/lib/state-dir.js +33 -0
- package/lib/state-dir.js.map +1 -0
- package/lib/stream.d.ts.map +1 -1
- package/lib/stream.js +3 -2
- package/lib/stream.js.map +1 -1
- package/lib/types.d.ts +14 -1
- package/lib/types.d.ts.map +1 -1
- package/package.json +13 -10
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
3
|
# editing either side, bring the other along and re-record with:
|
|
4
4
|
# git hash-object README.md README.zh.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 66b5e97ca9fcfabb7c9d9a63d9f7d6c610185c39
|
|
6
|
+
README.zh.md: c3d1f3118d2c4e45c4cea67436df869c1a3f87ac
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The **all-in-one plugin** for connecting [DeepSeek Harness](https://github.com/d
|
|
|
11
11
|
`dsh-connect` binds a chat conversation to a DSH agent session and drives it end to end:
|
|
12
12
|
|
|
13
13
|
- **Session binding & routing** — one chat ⇄ one agent session, persisted in a `bindings.json` route store; sessions can be created, resumed, switched, cleared and mirrored to the DSH Web GUI.
|
|
14
|
-
- **Streaming replies** —
|
|
14
|
+
- **Streaming replies** — the model's live deltas are bridged into the channel's native streaming (Feishu typewriter cards): the thinking hint opens the reasoning phase, reasoning streams live with readable paragraph breaks, tool calls appear as `🔧` progress lines, and a liveness heartbeat keeps the card moving even through long silent stretches (long first-token waits, heavy tool runs) so it never sits frozen on "Thinking…". The runner keeps two subscriptions, because `0.1.5-rc.2` split what used to be one: the durable `session/event` stream carries turns, tools and settlement, while the transient `agent/assistant-stream` frames carry the deltas — the `assistant/chunk` session event that used to carry both is gone.
|
|
15
15
|
- **Notification levels** — per-chat control over how much of the process is streamed: `尽量输出过程` (full process) / `输出重要节点` (key milestones) / `只输出结果` (result only). Switch any time via the settings menu or `/notify`; the choice is persisted per chat and applies immediately.
|
|
16
16
|
- **Task-end stats** — after every task a compact card reports the model used, input/output tokens, elapsed time and context-window usage, and suggests `/compact` when the context is getting full.
|
|
17
17
|
- **Interactive menus** — button cards for status, tasks, history, goals, schedule, model/effort switching, workspace picking, language, and more (see the in-chat `/` commands).
|
|
@@ -25,10 +25,24 @@ The **all-in-one plugin** for connecting [DeepSeek Harness](https://github.com/d
|
|
|
25
25
|
|
|
26
26
|
| Aspect | Value |
|
|
27
27
|
|---|---|
|
|
28
|
-
| DSH version | `^0.
|
|
28
|
+
| DSH version | `^0.2.0-rc.2` (peer `@deepseek-ai/dsh-agent`, `dsh-llm`, `dsh-session`) |
|
|
29
29
|
| Cordis | `^4.0.1` |
|
|
30
30
|
| Node.js | ≥ 20 (ESM, `NodeNext`) |
|
|
31
|
-
| Last verified | **2026-
|
|
31
|
+
| Last verified | **2026-10-02** against DSH `0.2.0-rc.2` on Windows (host load, Feishu WebSocket transport, web-settings pane) |
|
|
32
|
+
|
|
33
|
+
**Keep the peer range in step with the host.** Upstream ships no changelog or
|
|
34
|
+
migration guide, so a stale range is the only thing standing between this plugin
|
|
35
|
+
and silent breakage: DSH `0.1.5-rc.2` deleted the `Session.events` accessor and
|
|
36
|
+
the `assistant/chunk` event type outright, and every `dsh-*` package is versioned
|
|
37
|
+
on the same line. When you upgrade DSH, bump `peerDependencies` (and
|
|
38
|
+
`devDependencies`) for `dsh-agent`, `dsh-llm` and `dsh-session` together, re-run
|
|
39
|
+
`tsc`, and re-run the test suite — a range that no longer overlaps the host
|
|
40
|
+
version is the signal that the bridge needs another migration.
|
|
41
|
+
|
|
42
|
+
DSH refuses to load a plugin whose range does not cover it, so a stale range is
|
|
43
|
+
at least loud: installing `0.9.0` on `0.2.0-rc.2` is rejected with *"may cause
|
|
44
|
+
crashes or data loss"* before anything runs. `0.9.2` is the version that covers
|
|
45
|
+
`0.2.0-rc.2`; see [Upgrading from 0.9.0](#upgrading-from-090).
|
|
32
46
|
|
|
33
47
|
The plugin runs on the DSH **Host plane** (process-level singleton services), not inside an agent preset.
|
|
34
48
|
|
|
@@ -67,7 +81,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
|
|
|
67
81
|
## Quick start
|
|
68
82
|
|
|
69
83
|
1. **Install the plugins** (see above).
|
|
70
|
-
2. **Add the minimal config** to `~/.dsh/profiles/<profile>/cordis.patch.yml`
|
|
84
|
+
2. **Add the minimal config** to `~/.dsh/profiles/<profile>/cordis.patch.yml` — the shape is also in [`examples/minimal.config.json`](examples/minimal.config.json), and the fully commented version is [`examples/profile-cordis.patch.yml`](https://github.com/IvanWu2015/dsh-connect/blob/main/examples/profile-cordis.patch.yml) in the repository (that path is outside the published tarball, hence the absolute link). The plugin registers itself via its bundle manifest, so only override its config — do **not** `insert` it again (a duplicate id crashes dsh at boot):
|
|
71
85
|
|
|
72
86
|
```yaml
|
|
73
87
|
- id: connect
|
|
@@ -87,7 +101,19 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
|
|
|
87
101
|
3. **Start the host** — `dsh web` (or `dsh run`). With no credentials configured, the `feishu` channel enters **one-click onboarding**: scan the QR / open the link from the log to authorize the bot.
|
|
88
102
|
4. **Send a message** to the bot in Feishu. The bot replies with a streaming card; `/help` lists all commands; the conversation also appears in the DSH Web GUI automatically (auto-mirror).
|
|
89
103
|
|
|
90
|
-
A fully reproducible example is the `examples/` folder plus
|
|
104
|
+
A fully reproducible example is the [`examples/`](examples/) folder plus the repository's [Feishu setup manual](https://github.com/IvanWu2015/dsh-connect/blob/main/docs/feishu-setup.md) (Feishu app creation, event subscriptions, publishing).
|
|
105
|
+
|
|
106
|
+
## Questions and approvals in a conversation
|
|
107
|
+
|
|
108
|
+
When the agent needs a decision from you it stops and asks, and the asking happens in the chat — no switching to the Web GUI:
|
|
109
|
+
|
|
110
|
+
- **A question with options** renders as a card with one button per option. One tap answers it and the card immediately moves on to the next question.
|
|
111
|
+
- **A question without options** has no buttons to offer: the bot sends the question as a prompt and you **reply in the chat**. That message is the answer.
|
|
112
|
+
- **A tool approval** (an action that needs your go-ahead) is a card too, with **Allow once** / **Reject** buttons. This one accepts **only a tap** — a plain chat message sent while an approval is waiting is not recorded as its result.
|
|
113
|
+
|
|
114
|
+
One chat holds one pending card at a time. A second request is handed back to the host's own path (the Web GUI) rather than fighting the first card for the same message — and so is a card that could not be delivered, or a request cancelled before you answered; in each case the chat is **released**, because a leaked pending entry would silently swallow your next message.
|
|
115
|
+
|
|
116
|
+
"This action is no longer active" means the card has expired — it auto-closes after 60 s idle, so ask again. A double tap, or a tap landing right after the previous question was answered, falls inside the card's redraw window and is ignored silently rather than misreported as expired.
|
|
91
117
|
|
|
92
118
|
## Configuration
|
|
93
119
|
|
|
@@ -97,7 +123,7 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under the plug
|
|
|
97
123
|
|
|
98
124
|
| Key | Default | Description |
|
|
99
125
|
|---|---|---|
|
|
100
|
-
| `agentPreset` | roster default | Agent preset id composed into each bound session |
|
|
126
|
+
| `agentPreset` | roster default | Agent preset id composed into each bound session. Resolution is best-effort: the configured id is tried, then `standard` (or the roster's first mountable row), and if neither composes the agent is built without a preset so the turn still runs. A stale id therefore degrades instead of failing every message — it is logged, not thrown. |
|
|
101
127
|
| `workDir` | process cwd | Absolute working directory for each bound agent |
|
|
102
128
|
| `workspaces` | `[]` | Extra workspaces offered by the `/dir` picker |
|
|
103
129
|
| `visionModel` | auto-detected | `{ provider, model }` used to describe images when the main model can't see them |
|
|
@@ -107,7 +133,7 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under the plug
|
|
|
107
133
|
| `stateDir` | `.dsh-connect` | Directory holding the `bindings.json` route store (env `DSH_CONNECT_STATE_DIR` overrides) |
|
|
108
134
|
| `autoMirror` | `true` | Automatically create a Web GUI mirror for every new session |
|
|
109
135
|
| `streamHeartbeatMs` | `60000` | Liveness heartbeat interval (ms) for the streaming card; `0` disables it |
|
|
110
|
-
| `notifyLevel` | `
|
|
136
|
+
| `notifyLevel` | `result` | Default notification level: `full` (stream everything) / `important` (key milestones) / `result` (answer only, the default); per-chat override via settings menu or `/notify` |
|
|
111
137
|
| `progressTimeoutMs` | `300000` | Proactive progress-notice interval (ms): when a turn has sent no standalone card/text for this long, a status card reports the latest milestone; `0` disables; per-chat override via settings menu or `/progress` |
|
|
112
138
|
|
|
113
139
|
### Shared (all channels)
|
|
@@ -116,7 +142,7 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under the plug
|
|
|
116
142
|
|---|---|---|
|
|
117
143
|
| `channels` | all built-in | Which channels to activate: `feishu` / `telegram` / `dingtalk` / `web`. Omit to activate all built-in channels. |
|
|
118
144
|
| `channelDefaults` | `{}` | Keys applied to every channel that doesn't set its own (e.g. `{ language: "zh" }`). |
|
|
119
|
-
| `settingsStatePath` |
|
|
145
|
+
| `settingsStatePath` | `<stateDir>/dsh-connect-settings.json` | Where the web-settings pane mirrors non-secret config. Defaults to `dsh-connect-settings.json` *inside* `stateDir`, so it lands beside `bindings.json` and can never disagree with the stores; set it to override. The pane's authoritative store is this plugin's entry in the active profile patch (see [User settings](#user-settings)); this file is only the compatibility mirror the legacy `/dsh-connect` RPC reads and writes, and a pre-0.2 install's copy of it is read back once by the upgrade (see [Upgrading from 0.9.0](#upgrading-from-090)). |
|
|
120
146
|
|
|
121
147
|
### `feishu` (Feishu / Lark channel)
|
|
122
148
|
|
|
@@ -176,7 +202,10 @@ Environment variables (`FEISHU_*`, `TELEGRAM_*`, `DINGTALK_*`, `DSH_CONNECT_STAT
|
|
|
176
202
|
|
|
177
203
|
- **Files written**
|
|
178
204
|
- `<stateDir>/bindings.json` (default `.dsh-connect/`) — the chat ⇄ session route store (chat keys, session ids, mirror and lock state).
|
|
179
|
-
-
|
|
205
|
+
- `<stateDir>/dsh-connect-settings.json` (default `.dsh-connect/`) — the non-secret compatibility mirror, see `settingsStatePath`.
|
|
206
|
+
- `<profile dir>/.dsh-connect-legacy-imported` — a marker recording that the one-shot upgrade import ran. It sits beside the profile entry the import writes to (see [Upgrading from 0.9.0](#upgrading-from-090)), and its contents are a sentence saying where the settings came from; nothing is stored in it.
|
|
207
|
+
- this plugin's entry in the active **profile patch** (`profileContext.patchPath`, `cordis.patch.yml`) — written through DSH's first-party `settings` service (atomic, file-locked, comment-preserving).
|
|
208
|
+
- `~/.dsh/.dsh-connect/feishu-credentials.json` — **legacy**, read-only. One-click onboarding used to save Feishu credentials here instead of the credential store, so a user who had just scanned the QR code still saw `未配置凭据` forever. Onboarding now writes to the credential store, and an existing install is backfilled from this file once on boot; after that it is never read or written again.
|
|
180
209
|
- `<workDir>/.dsh-connect-images/` — user images/attachments staged for the agent's tools.
|
|
181
210
|
- DSH's own session logs and settings under `~/.dsh/` (sessions, settings, etc.).
|
|
182
211
|
- **Network**
|
|
@@ -184,20 +213,205 @@ Environment variables (`FEISHU_*`, `TELEGRAM_*`, `DINGTALK_*`, `DSH_CONNECT_STAT
|
|
|
184
213
|
- LLM provider APIs used by DSH for the agent's model (e.g. DeepSeek), plus the optional vision model.
|
|
185
214
|
- **User data** — message text and attachments flow through the bot to the agent session; they are stored in the DSH session log like any DSH conversation. The allowlists (`allowUsers` / `allowChats`) limit who can drive the bot.
|
|
186
215
|
|
|
216
|
+
## The settings pane
|
|
217
|
+
|
|
218
|
+
`dsh-connect` adds its own page under **Settings → dsh-connect**. It is a channel
|
|
219
|
+
tab strip over collapsible cards: each card is headed by a button, its
|
|
220
|
+
low-frequency fields sit behind a second-level **Advanced** fold, and
|
|
221
|
+
Save/status stay pinned to the bottom of the scroll region.
|
|
222
|
+
|
|
223
|
+
| Channels & credentials | Advanced fields opened |
|
|
224
|
+
|---|---|
|
|
225
|
+
|  |  |
|
|
226
|
+
|
|
227
|
+

|
|
228
|
+
|
|
229
|
+
English captures: [overview](docs/images/settings-overview-en.png) · [advanced](docs/images/settings-advanced-en.png) · [defaults](docs/images/settings-defaults-en.png).
|
|
230
|
+
|
|
231
|
+
> Captured from a throwaway profile whose credentials are all placeholders. Nothing
|
|
232
|
+
> above contains a real secret — and it could not, because the host masks every
|
|
233
|
+
> stored value before it reaches the browser (see [below](#seeing-and-masking-stored-values)).
|
|
234
|
+
|
|
235
|
+
Cards default to the channels you have enabled (the first one, if none are), and
|
|
236
|
+
clicking a tab opens that card and scrolls it into view *without* closing the
|
|
237
|
+
others — several open at once is a legitimate state. Collapsing a card unmounts
|
|
238
|
+
its body rather than hiding it, which is safe because an unsaved secret you typed
|
|
239
|
+
lives in the pane's own state, not in the card. Ticking a channel's enable box
|
|
240
|
+
opens it too.
|
|
241
|
+
|
|
242
|
+
## User settings
|
|
243
|
+
|
|
244
|
+
The web settings pane (`dsh-connect` under **Settings**) edits **this plugin's
|
|
245
|
+
entry in the active profile patch** — the same
|
|
246
|
+
`~/.dsh/profiles/<profile>/cordis.patch.yml` you would edit by hand — through
|
|
247
|
+
DSH's own first-party `settings` service. That service writes atomically under a
|
|
248
|
+
file lock and preserves your comments, and the loader hot-reloads the result, so
|
|
249
|
+
a save takes effect without a restart.
|
|
250
|
+
|
|
251
|
+
The fields the pane owns are declared `volatile` in the plugin's config schema.
|
|
252
|
+
That declaration is what makes a save *reconcile* instead of remounting: the
|
|
253
|
+
loader hands the plugin a live reference for each declared field, and a settings
|
|
254
|
+
write commits them in place, so the running adapters pick the values up on their
|
|
255
|
+
next message. Fields the pane does not own are simply not declared — which is
|
|
256
|
+
the mechanism that keeps it from writing them.
|
|
257
|
+
|
|
258
|
+
Values resolve in three layers, most specific last:
|
|
259
|
+
|
|
260
|
+
1. the schema defaults shipped with the plugin;
|
|
261
|
+
2. the config the plugin is composed with (its inherited entry);
|
|
262
|
+
3. this plugin's entry in the active profile patch — both what you hand-write
|
|
263
|
+
there and what the pane saves.
|
|
264
|
+
|
|
265
|
+
A save is projected onto the declared fields only, so an undeclared key — a
|
|
266
|
+
credential, `settingsStatePath`, something you added yourself — cannot reach the
|
|
267
|
+
document even if a caller sends it. The converse is load-bearing too: the host
|
|
268
|
+
resets a declared field that an update *omits* to its inherited value, so a save
|
|
269
|
+
always writes the complete declared section. Undeclared keys you hand-wrote in
|
|
270
|
+
that entry are preserved across a pane save, untouched.
|
|
271
|
+
|
|
272
|
+
**The pane never writes credentials.** A profile patch is a plain document users
|
|
273
|
+
are invited to paste into bug reports, so a secret you type into the pane goes to
|
|
274
|
+
the DSH credential store (`ctx.credentials`) instead — which is also where
|
|
275
|
+
one-click onboarding and the `FEISHU_*`-style environment variables put them. A
|
|
276
|
+
secret you hand-wrote in the entry yourself is left where it is.
|
|
277
|
+
|
|
278
|
+
The legacy `/dsh-connect` HTTP RPC is retained for panel compatibility; it now
|
|
279
|
+
reads and writes the same namespace, and mirrors non-secret config to
|
|
280
|
+
`settingsStatePath` (see [Shared](#shared-all-channels)) for older panels.
|
|
281
|
+
|
|
282
|
+
### Seeing and masking stored values
|
|
283
|
+
|
|
284
|
+
The pane shows each stored credential as a read-only *current value* line under
|
|
285
|
+
its input (`not configured` when nothing is stored), so you can confirm what you
|
|
286
|
+
configured without retyping it. **The masking happens on the host**, in one shared table
|
|
287
|
+
(`src/settings/secret-disclosure.ts`) that both the host and the pane read — the
|
|
288
|
+
pane only decides whether to render the input as a `password` or a `text` field,
|
|
289
|
+
so the display and the policy cannot drift apart.
|
|
290
|
+
|
|
291
|
+
| Field | How it is shown |
|
|
292
|
+
|---|---|
|
|
293
|
+
| `appId`, `clientId` | **In full.** These are identifiers, not authenticators: they appear in every outbound API call and in the vendor console, so hiding them protects nothing. |
|
|
294
|
+
| `appSecret`, `clientSecret`, `botToken`, `secret` | Head and tail only — `a1b2…z9y8`. Values too short to survive partial disclosure show a fixed `••••••` instead. |
|
|
295
|
+
| `webhookUrl` (DingTalk) | URL-aware. Origin, path and parameter *names* are kept and only the token's middle is masked, because DingTalk puts the token in the query string — masking the whole URL (`https…bcde`) would confirm nothing. |
|
|
296
|
+
| any other key | Masked by default. |
|
|
297
|
+
|
|
298
|
+
Two properties hold regardless:
|
|
299
|
+
|
|
300
|
+
- **No usable secret crosses the wire.** The value is masked before it leaves the
|
|
301
|
+
host, so a browser tab — or a screenshot of one — never holds one.
|
|
302
|
+
- **A mask can never be written back.** The preview is text *beside* the input,
|
|
303
|
+
never the input's value. Inputs always start blank; blank means "leave the
|
|
304
|
+
stored value alone", so saving the config alone writes no credential at all.
|
|
305
|
+
|
|
306
|
+
The pane's own strings (channel names, field labels, option text, status) all
|
|
307
|
+
come from `client/locale.mjs`, which ships `zh` and `en`. A test asserts both
|
|
308
|
+
languages cover exactly the same key set — the host resolves a missing key by
|
|
309
|
+
silently falling back to the other language, so an untranslated string shows up
|
|
310
|
+
as mixed-language text rather than as an error.
|
|
311
|
+
|
|
312
|
+
### Credential groups
|
|
313
|
+
|
|
314
|
+
A channel counts as *configured* when **any one** of its credential groups is
|
|
315
|
+
fully satisfied, and each group is satisfied only when **all** of its refs are
|
|
316
|
+
set — all-of within a group, any-of across groups. A channel with no groups
|
|
317
|
+
(`web`) is configured by definition and never shows a warning badge.
|
|
318
|
+
|
|
319
|
+
The grouping exists because a channel can have more than one mutually exclusive
|
|
320
|
+
way to authenticate, and requiring all of them would wrongly report a working
|
|
321
|
+
bot as unconfigured:
|
|
322
|
+
|
|
323
|
+
| Channel | Groups |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `feishu` | app id + app secret |
|
|
326
|
+
| `telegram` | bot token |
|
|
327
|
+
| `dingtalk` | webhook URL + sign secret — *or* — stream client id + client secret |
|
|
328
|
+
| `web` | none |
|
|
329
|
+
|
|
330
|
+
So a DingTalk bot using only webhook push (no stream credentials) is correctly
|
|
331
|
+
reported as configured, as is one using only stream mode.
|
|
332
|
+
|
|
333
|
+
## Upgrading from 0.9.0
|
|
334
|
+
|
|
335
|
+
Applies to the in-repo `0.9.1` as well — it was committed but never published
|
|
336
|
+
to npm, so `0.9.0` is the version users are actually upgrading from.
|
|
337
|
+
|
|
338
|
+
DSH 0.2 keeps per-plugin settings in the **profile patch**, not in
|
|
339
|
+
`$DSH_HOME/settings.yaml`, and it does not know the old document's `dsh-connect:`
|
|
340
|
+
section — its own migration renames that file and imports the sections it
|
|
341
|
+
recognises, leaving ours to be dropped with a warning. Without help, an
|
|
342
|
+
upgrading user's channels keep working (their config is in the patch) but every
|
|
343
|
+
pane-only choice silently reverts to its default the first time the pane opens.
|
|
344
|
+
|
|
345
|
+
So **0.9.2 imports it once, on the first boot after the upgrade**:
|
|
346
|
+
|
|
347
|
+
- It looks for the `dsh-connect:` section in `$DSH_HOME/settings.yaml` first,
|
|
348
|
+
then in `settings.yaml.imported` (where the host's own migration renames the
|
|
349
|
+
document, and which may have happened before or after this ran), and finally in
|
|
350
|
+
this plugin's own `dsh-connect-settings.json` — the fallback store a user who
|
|
351
|
+
never had a live settings peer would be carrying all their choices in.
|
|
352
|
+
- The section is projected onto the fields the pane owns, so **credentials cannot
|
|
353
|
+
travel**: they are in the credential store, and anything else in that file
|
|
354
|
+
stays where it is.
|
|
355
|
+
- It **merges, it does not replace.** The values in force are the base and the
|
|
356
|
+
legacy values are layered on top, because the host resets a declared field that
|
|
357
|
+
an update omits — an import carrying only per-channel keys would otherwise
|
|
358
|
+
clear `channels` and switch every adapter off.
|
|
359
|
+
- It writes through the same path a pane save uses, so the running adapters
|
|
360
|
+
reconcile immediately — no restart.
|
|
361
|
+
- **Nothing is deleted or renamed.** Unlike the host's own import, ours never
|
|
362
|
+
writes to `settings.yaml`.
|
|
363
|
+
- The outcome is recorded once in a marker named after the profile entry itself
|
|
364
|
+
(`profileContext.patchPath`), so it lives at
|
|
365
|
+
`<profile dir>/.dsh-connect-legacy-imported` — including the benign "there was
|
|
366
|
+
nothing to import" case, so a later boot cannot re-apply the old values over
|
|
367
|
+
edits you have made since. The anchor is the entry, not the state file it used
|
|
368
|
+
to sit beside: the state path is yours to move (`stateDir`,
|
|
369
|
+
`DSH_CONNECT_STATE_DIR`, `settingsStatePath`) and to delete, and either would
|
|
370
|
+
have made the next boot believe the import had never run. A profile directory
|
|
371
|
+
moves only if the profile itself does, which is the one case where re-importing
|
|
372
|
+
is right.
|
|
373
|
+
|
|
374
|
+
The retry rule is narrower than "any failure is retried", and deliberately so.
|
|
375
|
+
Three outcomes are final and get the marker: a document that is not there, a
|
|
376
|
+
document with no `dsh-connect:` section, and a successful import. Everything else
|
|
377
|
+
— a document that will not parse, one that is **there but cannot be read**
|
|
378
|
+
(a permission, or a `settings.yaml` that is really a directory), a refused write,
|
|
379
|
+
a host with no settings service — imports nothing, **leaves both files alone, and
|
|
380
|
+
writes no marker**, so the next boot simply tries again once you have fixed the
|
|
381
|
+
cause. That last one is why "not there" and "could not be read" are told apart:
|
|
382
|
+
until `0.9.2` they were the same outcome, so a single unreadable document ended
|
|
383
|
+
the migration for good, silently.
|
|
384
|
+
|
|
385
|
+
Each of those failures is one `connect: …` line naming the file and the reason;
|
|
386
|
+
the troubleshooting table below lists them. A marker that cannot be **written** is
|
|
387
|
+
reported too — without it every boot would re-run the migration and layer the
|
|
388
|
+
legacy values back over your newer edits. If you never used the pane, none of
|
|
389
|
+
this is visible.
|
|
390
|
+
|
|
187
391
|
## Troubleshooting
|
|
188
392
|
|
|
189
393
|
Logs come from the DSH host logger (run `dsh web` in a terminal); plugin messages are prefixed `connect:` / `connect-feishu:`.
|
|
190
394
|
|
|
191
395
|
| Symptom | Likely cause / fix |
|
|
192
396
|
|---|---|
|
|
397
|
+
| Installing `dsh-connect@0.9.0` on DSH `0.2.0-rc.2` is refused: *"`dsh-connect@0.9.0` 与 DSH `0.2.0-rc.2` 不兼容 … 运行它可能导致崩溃或数据丢失"* | Not a bug and not a warning to click past: DSH's compatibility gate rejects any plugin whose declared peer range does not cover the running host, and `0.9.0` predates the `0.2.0` line. Install **`0.9.2`** (or newer), whose peers require `^0.2.0-rc.2`. |
|
|
193
398
|
| `connect-feishu: adapter init failed` / `start failed` | Bad credentials, app not published, or network blocked. Check `appId`/`appSecret`, re-run onboarding, verify the bot is online in the Feishu console. |
|
|
194
399
|
| `connect: resume of <id> failed, creating fresh session` | The persisted session could not be resumed (missing workdir, persistence issue). Check `workDir` and `~/.dsh/sessions`. |
|
|
400
|
+
| The bot answers every message with a raw `agent-presets: preset "…" not found` line, and nothing reaches the agent | A stale `agent-presets.default` in `$DSH_HOME/settings.yaml` names an id no installed build ships. Fixed in **0.9.0**, which retries `standard` and logs the decision rather than failing the turn; on an older build, set the key to a shipped id (`standard`). |
|
|
195
401
|
| Session-locked notices | Another client (Feishu or Web) holds the write lock. Use `/unlock` or wait for the lock timeout. |
|
|
196
|
-
| Model switch in the Web GUI appears ignored | Fixed in
|
|
402
|
+
| Model switch in the Web GUI appears ignored | Fixed in **0.9.0**: the plugin no longer pins a static default model over the Web GUI's session selection. Upgrade, then restart `dsh web`. |
|
|
197
403
|
| `[用户发送了图片,但下载失败…]` | Feishu `im:resource` permission is missing on the app; grant it and re-approve. |
|
|
198
|
-
| Streaming reply is one unbroken blob | Fixed in
|
|
199
|
-
| Card frozen on "Thinking…" with no progress on a long task | Fixed in
|
|
200
|
-
|
|
|
404
|
+
| Streaming reply is one unbroken blob | Fixed in **0.9.0**: block boundaries and the reasoning/answer split now insert blank lines (and reasoning soft breaks are expanded for Feishu cards). Upgrade, then restart `dsh web`. |
|
|
405
|
+
| Card frozen on "Thinking…" with no progress on a long task | Fixed in **0.9.0**: reasoning now streams live, tool calls show as `🔧` progress lines, and a liveness heartbeat updates the card during silent stretches. Upgrade, then restart `dsh web`. |
|
|
406
|
+
| The agent offers options / asks for tool approval and nothing appears in Feishu | Fixed in **0.9.2** (the in-repo `0.9.1` was never published): the bridge subscribed to a host service that does not exist, so every question fell silently back to the host. Upgrade, then restart `dsh web`. |
|
|
407
|
+
| Tapping a card button says it is no longer active, on a card that was just posted | Fixed in **0.9.2** (the in-repo `0.9.1` was never published): a tap landing while the card was being redrawn (a double tap, or one right after the previous question was answered) was misread as a stale action. Upgrade, then restart `dsh web`. |
|
|
408
|
+
| `connect: the legacy settings at <path> could not be parsed …` | The pre-0.2 document has a YAML error, so the one-shot migration ([Upgrading from 0.9.0](#upgrading-from-090)) skipped it and left it in place. Fix the YAML and restart; nothing is imported until then, and no marker is written, so the retry is automatic. |
|
|
409
|
+
| `connect: could not import the legacy dsh-connect settings from <path> …` | The migration found the section but DSH refused the write (usually a value that fails validation). The section is still in the file — fix the named field and restart. |
|
|
410
|
+
| `connect: could not read the legacy settings candidate at <path> …` | The candidate is *there* but could not be read: a permission, a path that is really a directory, or a `~` in `$DSH_HOME` that was not expanded. This is the one failure that does **not** mark the migration done — nothing is imported, nothing is marked, and the next start retries on its own, so fixing the cause is all that is needed. The distinction from "not there" is the point: the two were the same outcome until 0.9.2, so **a single `EACCES` ended the entire migration silently and permanently**. |
|
|
411
|
+
| `connect: could not write the one-shot import marker at <path> …` | The import itself succeeded; the marker that records it could not be written (usually a read-only home). Not harmless: with no marker every start re-runs the whole migration and layers the legacy values back over whatever you changed in the pane after upgrading — which shows up as settings reverting on their own. Fix the profile directory's write permission, or create the marker file by hand. |
|
|
412
|
+
| `connect: the import marker at <path> could not be read …` | A marker exists but cannot be read. It is treated as **already imported** and reported: better to skip an import than to re-apply old values over your newer settings. Delete the marker and restart to trigger the import again. |
|
|
413
|
+
| Saving the pane fails with *`Configuration for "connect" is overridden by a home patch or command-line overlay`* | The pane writes the profile patch, but resolution layers `bundle → profile → $DSH_HOME/cordis.patch.yml → --patch`, so a value set in one of the last two wins over anything the pane saves and DSH refuses the write rather than let a save that could never take effect look successful. Edit the home patch (or drop the overlay) if you want the pane to own these settings. |
|
|
414
|
+
| Menu cards don't update / expire | Cards auto-close after 60 s idle by design; re-open the menu. Question and approval cards behave the same — see [Questions and approvals in a conversation](#questions-and-approvals-in-a-conversation). |
|
|
201
415
|
|
|
202
416
|
**Rollback** — reinstall a previous release (`dsh plugin --profile web add dsh-connect@<version>` after removing the current one), or `git checkout` the pinned commit in a source install.
|
|
203
417
|
|
|
@@ -208,11 +422,13 @@ This is a pnpm workspace; `dsh-connect` is the single package under `packages/`:
|
|
|
208
422
|
```
|
|
209
423
|
packages/
|
|
210
424
|
connect/ # this package — the all-in-one plugin
|
|
211
|
-
src/ # core: runner, service, binding, commands,
|
|
425
|
+
src/ # core: runner, service, binding, commands, menus, chat keys …
|
|
212
426
|
src/channels/ # channel adapters: feishu / telegram / dingtalk / web
|
|
213
|
-
src/settings/ # web-settings stack: host RPC, credential store,
|
|
427
|
+
src/settings/ # web-settings stack: host RPC, credential store, disclosure policy
|
|
428
|
+
client/ # web-settings frontend plugin + its pure, testable modules
|
|
214
429
|
test/ # node:test suites (run-all.mjs imports every suite)
|
|
215
|
-
|
|
430
|
+
docs/images/ # screenshots used by this README
|
|
431
|
+
examples/ # minimal.config.json
|
|
216
432
|
```
|
|
217
433
|
|
|
218
434
|
```sh
|
|
@@ -228,9 +444,11 @@ pnpm test
|
|
|
228
444
|
node packages/connect/test/unit.test.mjs
|
|
229
445
|
```
|
|
230
446
|
|
|
231
|
-
**Structure** — `src/runner.ts` owns the per-chat agent driver and the streaming bridge (`applyStreamChunk` is the pure, unit-tested chunk assembler); `src/service.ts` owns the adapter registry and routing; `src/channels/` holds the feishu / telegram / dingtalk / web channel adapters; `src/settings/` holds the web-settings stack (host RPC, credential store,
|
|
447
|
+
**Structure** — `src/runner.ts` owns the per-chat agent driver and the streaming bridge (`applyStreamChunk` is the pure, unit-tested chunk assembler); `src/service.ts` owns the adapter registry and routing; `src/channels/` holds the feishu / telegram / dingtalk / web channel adapters; `src/settings/` holds the web-settings stack (host RPC, credential store, disclosure policy); `src/binding.ts` is the route store.
|
|
448
|
+
|
|
449
|
+
Two things that used to live in `src/` moved to `client/` so they could be unit-tested without React: **`client/locale.mjs`** holds every user-visible pane string in `zh` and `en` (keep the key sets identical — the host resolves a missing key by silently rendering the *other* language, so an untranslated string shows up as half-English text, not as an error), and **`client/panel-state.mjs`** holds the card open/advanced rules. Both are plain ESM with no dependencies and are asserted directly in `test/locale.test.mjs` / `test/panel-state.test.mjs`.
|
|
232
450
|
|
|
233
|
-
**Contributing** — PRs welcome at [github.com/IvanWu2015/dsh-connect](https://github.com/IvanWu2015/dsh-connect). For user-facing strings, add the key to both `zh` and `en` in `
|
|
451
|
+
**Contributing** — PRs welcome at [github.com/IvanWu2015/dsh-connect](https://github.com/IvanWu2015/dsh-connect). For user-facing pane strings, add the key to both `zh` and `en` in `client/locale.mjs`, then rebuild the bundle (`node scripts/build-client.mjs`) — `test/client-bundle.test.mjs` runs the **built** artifact and fails if it is stale. Release notes live in `CHANGELOG.md`; see [`docs/PUBLISHING.md`](https://github.com/IvanWu2015/dsh-connect/blob/main/docs/PUBLISHING.md) for the release flow.
|
|
234
452
|
|
|
235
453
|
## License & security
|
|
236
454
|
|