dsh-connect 0.8.0 → 0.9.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 (94) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +134 -17
  3. package/README.zh.md +110 -17
  4. package/client/client.js +510 -80
  5. package/client/client.js.map +4 -4
  6. package/client/locale.mjs +227 -0
  7. package/client/panel-state.mjs +54 -0
  8. package/client/settings-client.mjs +272 -60
  9. package/docs/images/settings-advanced-en.png +0 -0
  10. package/docs/images/settings-advanced-zh.png +0 -0
  11. package/docs/images/settings-defaults-en.png +0 -0
  12. package/docs/images/settings-defaults-zh.png +0 -0
  13. package/docs/images/settings-overview-en.png +0 -0
  14. package/docs/images/settings-overview-zh.png +0 -0
  15. package/lib/binding.d.ts.map +1 -1
  16. package/lib/binding.js +2 -2
  17. package/lib/binding.js.map +1 -1
  18. package/lib/channels/dingtalk/message.d.ts.map +1 -1
  19. package/lib/channels/dingtalk/message.js +7 -0
  20. package/lib/channels/dingtalk/message.js.map +1 -1
  21. package/lib/channels/feishu/adapter.d.ts +6 -12
  22. package/lib/channels/feishu/adapter.d.ts.map +1 -1
  23. package/lib/channels/feishu/adapter.js +2 -17
  24. package/lib/channels/feishu/adapter.js.map +1 -1
  25. package/lib/channels/feishu/i18n.d.ts +2 -0
  26. package/lib/channels/feishu/i18n.d.ts.map +1 -1
  27. package/lib/channels/feishu/i18n.js +2 -0
  28. package/lib/channels/feishu/i18n.js.map +1 -1
  29. package/lib/channels/feishu/index.d.ts +23 -2
  30. package/lib/channels/feishu/index.d.ts.map +1 -1
  31. package/lib/channels/feishu/index.js +32 -6
  32. package/lib/channels/feishu/index.js.map +1 -1
  33. package/lib/channels/telegram/adapter.d.ts.map +1 -1
  34. package/lib/channels/telegram/adapter.js +7 -1
  35. package/lib/channels/telegram/adapter.js.map +1 -1
  36. package/lib/chat-key.d.ts +29 -0
  37. package/lib/chat-key.d.ts.map +1 -0
  38. package/lib/chat-key.js +38 -0
  39. package/lib/chat-key.js.map +1 -0
  40. package/lib/index.d.ts +15 -5
  41. package/lib/index.d.ts.map +1 -1
  42. package/lib/index.js +201 -31
  43. package/lib/index.js.map +1 -1
  44. package/lib/runner.d.ts +54 -0
  45. package/lib/runner.d.ts.map +1 -1
  46. package/lib/runner.js +158 -23
  47. package/lib/runner.js.map +1 -1
  48. package/lib/scheduler.d.ts.map +1 -1
  49. package/lib/scheduler.js +2 -2
  50. package/lib/scheduler.js.map +1 -1
  51. package/lib/service.d.ts +16 -0
  52. package/lib/service.d.ts.map +1 -1
  53. package/lib/service.js +46 -2
  54. package/lib/service.js.map +1 -1
  55. package/lib/settings/channel-runtime.d.ts +75 -0
  56. package/lib/settings/channel-runtime.d.ts.map +1 -0
  57. package/lib/settings/channel-runtime.js +165 -0
  58. package/lib/settings/channel-runtime.js.map +1 -0
  59. package/lib/settings/channels.d.ts +11 -0
  60. package/lib/settings/channels.d.ts.map +1 -1
  61. package/lib/settings/channels.js +31 -0
  62. package/lib/settings/channels.js.map +1 -1
  63. package/lib/settings/credential-store.d.ts +52 -6
  64. package/lib/settings/credential-store.d.ts.map +1 -1
  65. package/lib/settings/credential-store.js +92 -18
  66. package/lib/settings/credential-store.js.map +1 -1
  67. package/lib/settings/namespace.d.ts +175 -0
  68. package/lib/settings/namespace.d.ts.map +1 -0
  69. package/lib/settings/namespace.js +200 -0
  70. package/lib/settings/namespace.js.map +1 -0
  71. package/lib/settings/secret-disclosure.d.ts +50 -0
  72. package/lib/settings/secret-disclosure.d.ts.map +1 -0
  73. package/lib/settings/secret-disclosure.js +132 -0
  74. package/lib/settings/secret-disclosure.js.map +1 -0
  75. package/lib/settings/settings-model.d.ts +17 -0
  76. package/lib/settings/settings-model.d.ts.map +1 -1
  77. package/lib/settings/settings-model.js +8 -0
  78. package/lib/settings/settings-model.js.map +1 -1
  79. package/lib/settings/settings-rpc.d.ts +71 -3
  80. package/lib/settings/settings-rpc.d.ts.map +1 -1
  81. package/lib/settings/settings-rpc.js +167 -24
  82. package/lib/settings/settings-rpc.js.map +1 -1
  83. package/lib/settings/settings-service.d.ts +9 -1
  84. package/lib/settings/settings-service.d.ts.map +1 -1
  85. package/lib/settings/settings-service.js +74 -6
  86. package/lib/settings/settings-service.js.map +1 -1
  87. package/lib/state-dir.d.ts +28 -0
  88. package/lib/state-dir.d.ts.map +1 -0
  89. package/lib/state-dir.js +33 -0
  90. package/lib/state-dir.js.map +1 -0
  91. package/lib/stream.d.ts.map +1 -1
  92. package/lib/stream.js +3 -2
  93. package/lib/stream.js.map +1 -1
  94. package/package.json +11 -9
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: 60d7d023edd88b0a136a308c90c33967b7a6bb9f
6
- README.zh.md: 0258195b0c6d92a2bcee8958830a41e9ed4e8bbc
5
+ README.md: ae5b3e1207735af0f2bb272229111153a4b048a8
6
+ README.zh.md: bcced73f7d395930285480d770cc4618d90af1c6
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** — DSH `assistant/chunk` events 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…".
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,19 @@ 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.1.0-rc.6` (peer `@deepseek-ai/dsh-agent`, `dsh-llm`, `dsh-session`) |
28
+ | DSH version | `^0.1.5-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-08-16** against DSH `0.1.0-rc.6` on Windows (Feishu WebSocket transport) |
31
+ | Last verified | **2026-09-21** against DSH `0.1.5-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.
32
41
 
33
42
  The plugin runs on the DSH **Host plane** (process-level singleton services), not inside an agent preset.
34
43
 
@@ -67,7 +76,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
67
76
  ## Quick start
68
77
 
69
78
  1. **Install the plugins** (see above).
70
- 2. **Add the minimal config** to `~/.dsh/profiles/<profile>/cordis.patch.yml` (also see [`examples/profile-cordis.patch.yml`](../../examples/profile-cordis.patch.yml)). 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):
79
+ 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
80
 
72
81
  ```yaml
73
82
  - id: connect
@@ -87,7 +96,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
87
96
  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
97
  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
98
 
90
- A fully reproducible example is the `examples/` folder plus `docs/feishu-setup.md` (Feishu app creation, event subscriptions, publishing).
99
+ 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).
91
100
 
92
101
  ## Configuration
93
102
 
@@ -97,7 +106,7 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under the plug
97
106
 
98
107
  | Key | Default | Description |
99
108
  |---|---|---|
100
- | `agentPreset` | roster default | Agent preset id composed into each bound session |
109
+ | `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
110
  | `workDir` | process cwd | Absolute working directory for each bound agent |
102
111
  | `workspaces` | `[]` | Extra workspaces offered by the `/dir` picker |
103
112
  | `visionModel` | auto-detected | `{ provider, model }` used to describe images when the main model can't see them |
@@ -107,7 +116,7 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under the plug
107
116
  | `stateDir` | `.dsh-connect` | Directory holding the `bindings.json` route store (env `DSH_CONNECT_STATE_DIR` overrides) |
108
117
  | `autoMirror` | `true` | Automatically create a Web GUI mirror for every new session |
109
118
  | `streamHeartbeatMs` | `60000` | Liveness heartbeat interval (ms) for the streaming card; `0` disables it |
110
- | `notifyLevel` | `important` | Default notification level: `full` (stream everything) / `important` (key milestones) / `result` (answer only); per-chat override via settings menu or `/notify` |
119
+ | `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
120
  | `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
121
 
113
122
  ### Shared (all channels)
@@ -116,7 +125,7 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under the plug
116
125
  |---|---|---|
117
126
  | `channels` | all built-in | Which channels to activate: `feishu` / `telegram` / `dingtalk` / `web`. Omit to activate all built-in channels. |
118
127
  | `channelDefaults` | `{}` | Keys applied to every channel that doesn't set its own (e.g. `{ language: "zh" }`). |
119
- | `settingsStatePath` | — | Optional path for the web-settings pane to persist non-secret config (e.g. `.dsh-connect/settings.json`). |
128
+ | `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. Since 0.9.0 the pane's authoritative store is the `dsh-connect` section of `$DSH_HOME/settings.yaml` (see [User settings](#user-settings)), and this file is only the compatibility mirror the legacy `/dsh-connect` RPC reads and writes. |
120
129
 
121
130
  ### `feishu` (Feishu / Lark channel)
122
131
 
@@ -176,7 +185,9 @@ Environment variables (`FEISHU_*`, `TELEGRAM_*`, `DINGTALK_*`, `DSH_CONNECT_STAT
176
185
 
177
186
  - **Files written**
178
187
  - `<stateDir>/bindings.json` (default `.dsh-connect/`) — the chat ⇄ session route store (chat keys, session ids, mirror and lock state).
179
- - `~/.dsh/.dsh-connect/feishu-credentials.json` — Feishu credentials saved by one-click onboarding.
188
+ - `<stateDir>/dsh-connect-settings.json` (default `.dsh-connect/`) — the non-secret compatibility mirror, see `settingsStatePath`.
189
+ - the `dsh-connect` section of `$DSH_HOME/settings.yaml` — written through DSH's first-party settings seam (atomic, file-locked, comment-preserving).
190
+ - `~/.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
191
  - `<workDir>/.dsh-connect-images/` — user images/attachments staged for the agent's tools.
181
192
  - DSH's own session logs and settings under `~/.dsh/` (sessions, settings, etc.).
182
193
  - **Network**
@@ -184,6 +195,107 @@ Environment variables (`FEISHU_*`, `TELEGRAM_*`, `DINGTALK_*`, `DSH_CONNECT_STAT
184
195
  - LLM provider APIs used by DSH for the agent's model (e.g. DeepSeek), plus the optional vision model.
185
196
  - **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
197
 
198
+ ## The settings pane
199
+
200
+ `dsh-connect` adds its own page under **Settings → dsh-connect**. It is a channel
201
+ tab strip over collapsible cards: each card is headed by a button, its
202
+ low-frequency fields sit behind a second-level **Advanced** fold, and
203
+ Save/status stay pinned to the bottom of the scroll region.
204
+
205
+ | Channels & credentials | Advanced fields opened |
206
+ |---|---|
207
+ | ![dsh-connect settings pane: the channel tab strip, the Feishu card expanded with its credential fields, and three folded channel cards each showing a credentials badge](docs/images/settings-overview-zh.png) | ![the same pane with a channel's Advanced fold opened, revealing the callback port and path fields](docs/images/settings-advanced-zh.png) |
208
+
209
+ ![the common-defaults card and the pinned save bar at the bottom of the pane](docs/images/settings-defaults-zh.png)
210
+
211
+ English captures: [overview](docs/images/settings-overview-en.png) · [advanced](docs/images/settings-advanced-en.png) · [defaults](docs/images/settings-defaults-en.png).
212
+
213
+ > Captured from a throwaway profile whose credentials are all placeholders. Nothing
214
+ > above contains a real secret — and it could not, because the host masks every
215
+ > stored value before it reaches the browser (see [below](#seeing-and-masking-stored-values)).
216
+
217
+ Cards default to the channels you have enabled (the first one, if none are), and
218
+ clicking a tab opens that card and scrolls it into view *without* closing the
219
+ others — several open at once is a legitimate state. Collapsing a card unmounts
220
+ its body rather than hiding it, which is safe because an unsaved secret you typed
221
+ lives in the pane's own state, not in the card. Ticking a channel's enable box
222
+ opens it too.
223
+
224
+ ## User settings
225
+
226
+ The web settings pane (`dsh-connect` under **Settings**) edits the `dsh-connect`
227
+ section of `$DSH_HOME/settings.yaml`, through DSH's own first-party settings
228
+ seam. That document is hot-reloaded, written atomically under a file lock, and
229
+ keeps your comments — so editing it by hand works too, and a change takes
230
+ effect without a restart.
231
+
232
+ Values resolve in three layers, most specific last:
233
+
234
+ 1. the schema defaults shipped with the plugin;
235
+ 2. the plugin's own `cordis.patch.yml` entry (your existing config is *not*
236
+ discarded — it is the registered base layer);
237
+ 3. the `dsh-connect` section in `settings.yaml`.
238
+
239
+ **Secrets are never written to `settings.yaml`.** It is a plain document users
240
+ are invited to paste into bug reports, so credentials stay in the DSH
241
+ credential store (`ctx.credentials`) — which is also where one-click onboarding
242
+ and the `FEISHU_*`-style environment variables put them.
243
+
244
+ The legacy `/dsh-connect` HTTP RPC is retained for panel compatibility; it now
245
+ reads and writes the same namespace, and mirrors non-secret config to
246
+ `settingsStatePath` (see [Shared](#shared-all-channels)) for older panels.
247
+
248
+ ### Seeing and masking stored values
249
+
250
+ The pane shows each stored credential as a read-only *current value* line under
251
+ its input (`not configured` when nothing is stored), so you can confirm what you
252
+ configured without retyping it. **The masking happens on the host**, in one shared table
253
+ (`src/settings/secret-disclosure.ts`) that both the host and the pane read — the
254
+ pane only decides whether to render the input as a `password` or a `text` field,
255
+ so the display and the policy cannot drift apart.
256
+
257
+ | Field | How it is shown |
258
+ |---|---|
259
+ | `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. |
260
+ | `appSecret`, `clientSecret`, `botToken`, `secret` | Head and tail only — `a1b2…z9y8`. Values too short to survive partial disclosure show a fixed `••••••` instead. |
261
+ | `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. |
262
+ | any other key | Masked by default. |
263
+
264
+ Two properties hold regardless:
265
+
266
+ - **No usable secret crosses the wire.** The value is masked before it leaves the
267
+ host, so a browser tab — or a screenshot of one — never holds one.
268
+ - **A mask can never be written back.** The preview is text *beside* the input,
269
+ never the input's value. Inputs always start blank; blank means "leave the
270
+ stored value alone", so saving the config alone writes no credential at all.
271
+
272
+ The pane's own strings (channel names, field labels, option text, status) all
273
+ come from `client/locale.mjs`, which ships `zh` and `en`. A test asserts both
274
+ languages cover exactly the same key set — the host resolves a missing key by
275
+ silently falling back to the other language, so an untranslated string shows up
276
+ as mixed-language text rather than as an error.
277
+
278
+ ### Credential groups
279
+
280
+ A channel counts as *configured* when **any one** of its credential groups is
281
+ fully satisfied, and each group is satisfied only when **all** of its refs are
282
+ set — all-of within a group, any-of across groups. A channel with no groups
283
+ (`web`) is configured by definition and never shows a warning badge.
284
+
285
+ The grouping exists because a channel can have more than one mutually exclusive
286
+ way to authenticate, and requiring all of them would wrongly report a working
287
+ bot as unconfigured:
288
+
289
+ | Channel | Groups |
290
+ |---|---|
291
+ | `feishu` | app id + app secret |
292
+ | `telegram` | bot token |
293
+ | `dingtalk` | webhook URL + sign secret — *or* — stream client id + client secret |
294
+ | `web` | none |
295
+
296
+ So a DingTalk bot using only webhook push (no stream credentials) is correctly
297
+ reported as configured, as is one using only stream mode.
298
+
187
299
  ## Troubleshooting
188
300
 
189
301
  Logs come from the DSH host logger (run `dsh web` in a terminal); plugin messages are prefixed `connect:` / `connect-feishu:`.
@@ -192,11 +304,12 @@ Logs come from the DSH host logger (run `dsh web` in a terminal); plugin message
192
304
  |---|---|
193
305
  | `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
306
  | `connect: resume of <id> failed, creating fresh session` | The persisted session could not be resumed (missing workdir, persistence issue). Check `workDir` and `~/.dsh/sessions`. |
307
+ | 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
308
  | 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 the current main: the plugin no longer pins a static default model over the Web GUI's session selection. Restart `dsh web` so the rebuilt plugin is loaded. |
309
+ | 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
310
  | `[用户发送了图片,但下载失败…]` | Feishu `im:resource` permission is missing on the app; grant it and re-approve. |
198
- | Streaming reply is one unbroken blob | Fixed in the current main: block boundaries and the reasoning/answer split now insert blank lines (and reasoning soft breaks are expanded for Feishu cards). Restart `dsh web`. |
199
- | Card frozen on "Thinking…" with no progress on a long task | Fixed in the current main: reasoning now streams live, tool calls show as `🔧` progress lines, and a liveness heartbeat updates the card during silent stretches. Restart `dsh web`. |
311
+ | 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`. |
312
+ | 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`. |
200
313
  | Menu cards don't update / expire | Cards auto-close after 60 s idle by design; re-open the menu. |
201
314
 
202
315
  **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.
@@ -208,11 +321,13 @@ This is a pnpm workspace; `dsh-connect` is the single package under `packages/`:
208
321
  ```
209
322
  packages/
210
323
  connect/ # this package — the all-in-one plugin
211
- src/ # core: runner, service, binding, commands, i18n, menus …
324
+ src/ # core: runner, service, binding, commands, menus, chat keys …
212
325
  src/channels/ # channel adapters: feishu / telegram / dingtalk / web
213
- src/settings/ # web-settings stack: host RPC, credential store, settings service/pane
326
+ src/settings/ # web-settings stack: host RPC, credential store, disclosure policy
327
+ client/ # web-settings frontend plugin + its pure, testable modules
214
328
  test/ # node:test suites (run-all.mjs imports every suite)
215
- client/ # web-settings frontend plugin
329
+ docs/images/ # screenshots used by this README
330
+ examples/ # minimal.config.json
216
331
  ```
217
332
 
218
333
  ```sh
@@ -228,9 +343,11 @@ pnpm test
228
343
  node packages/connect/test/unit.test.mjs
229
344
  ```
230
345
 
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, settings service); `src/i18n.ts` holds the `zh`/`en` dictionaries (keep keys in sync across both); `src/binding.ts` is the route store.
346
+ **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.
347
+
348
+ 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
349
 
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 `src/i18n.ts`. Release notes live in `CHANGELOG.md`; see `docs/PUBLISHING.md` for the release flow.
350
+ **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
351
 
235
352
  ## License & security
236
353
 
package/README.zh.md CHANGED
@@ -11,7 +11,7 @@
11
11
  `dsh-connect` 将聊天会话绑定到 DSH 智能体会话并端到端驱动它:
12
12
 
13
13
  - **会话绑定与路由** —— 一个聊天 ⇄ 一个智能体会话,持久化保存在 `bindings.json` 路由存储中;会话可以创建、恢复、切换、清除,并镜像到 DSH Web GUI。
14
- - **流式回复** —— DSH 的 `assistant/chunk` 事件被桥接到渠道的原生流式能力(飞书打字机卡片):思考提示开启推理阶段,推理内容带可读的段落分隔实时流出,工具调用显示为 `🔧` 进度行,心跳保活机制即使在长时间静默时(首个 token 等待过长、密集工具运行)也会让卡片保持更新,绝不会一直卡在「思考中…」。
14
+ - **流式回复** —— 模型的实时增量被桥接到渠道的原生流式能力(飞书打字机卡片):思考提示开启推理阶段,推理内容带可读的段落分隔实时流出,工具调用显示为 `🔧` 进度行,心跳保活机制即使在长时间静默时(首个 token 等待过长、密集工具运行)也会让卡片保持更新,绝不会一直卡在「思考中…」。runner 保持两路订阅,因为 `0.1.5-rc.2` 把原本合二为一的东西拆开了:持久的 `session/event` 流承载回合、工具与结算,瞬时的 `agent/assistant-stream` 帧承载模型增量 —— 过去同时承载两者的 `assistant/chunk` 会话事件已被删除。
15
15
  - **通知级别** —— 按聊天控制过程流式的详细程度:`尽量输出过程`(完整过程)/ `输出重要节点`(关键节点)/ `只输出结果`(仅结果)。可随时通过设置菜单或 `/notify` 切换;选择按聊天持久化并立即生效。
16
16
  - **任务结束统计** —— 每个任务结束后,一张紧凑卡片报告所用模型、输入/输出 token、耗时与上下文窗口占用,并在上下文接近占满时建议 `/compact`。
17
17
  - **交互式菜单** —— 状态、任务、历史、目标、日程、模型/努力度切换、工作区选择、语言等按钮卡片(参见聊天内的 `/` 命令)。
@@ -25,10 +25,12 @@
25
25
 
26
26
  | 方面 | 值 |
27
27
  |---|---|
28
- | DSH 版本 | `^0.1.0-rc.6`(peer `@deepseek-ai/dsh-agent`、`dsh-llm`、`dsh-session`) |
28
+ | DSH 版本 | `^0.1.5-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
- | 最后验证 | **2026-08-16**,在 Windows(飞书 WebSocket 传输)上针对 DSH `0.1.0-rc.6` 验证 |
31
+ | 最后验证 | **2026-09-21**,在 Windows 上针对 DSH `0.1.5-rc.2` 验证(宿主加载、飞书 WebSocket 传输、Web 设置面板) |
32
+
33
+ **peer 版本线必须与宿主保持同步。** 上游不提供 changelog 或迁移说明,因此过期的版本范围是插件与静默损坏之间唯一的屏障:DSH `0.1.5-rc.2` 直接删除了 `Session.events` 访问器和 `assistant/chunk` 事件类型,而所有 `dsh-*` 包共用同一条版本线。升级 DSH 时,请把 `dsh-agent`、`dsh-llm`、`dsh-session` 的 `peerDependencies`(以及 `devDependencies`)**一起**上调,重新运行 `tsc`,并重跑测试套件 —— 当版本范围与宿主版本不再有交集时,就是桥接需要再次迁移的信号。
32
34
 
33
35
  插件运行在 DSH **Host 平面**(进程级单例服务)上,而不是在智能体预设内部。
34
36
 
@@ -67,7 +69,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
67
69
  ## 快速开始
68
70
 
69
71
  1. **安装插件**(见上文)。
70
- 2. **添加最小配置**到 `~/.dsh/profiles/<profile>/cordis.patch.yml`(另见 [`examples/profile-cordis.patch.yml`](../../examples/profile-cordis.patch.yml))。该插件会通过其 bundle 清单自动注册,因此这里只需要**覆盖(override)**它的配置——**不要**再用 `insert` 重新插入(重复的 `id` 会让 dsh 启动失败):
72
+ 2. **添加最小配置**到 `~/.dsh/profiles/<profile>/cordis.patch.yml` —— 配置形状见 [`examples/minimal.config.json`](examples/minimal.config.json),带完整注释的版本是仓库里的 [`examples/profile-cordis.patch.yml`](https://github.com/IvanWu2015/dsh-connect/blob/main/examples/profile-cordis.patch.yml)(该路径不在发布产物内,故用绝对链接)。该插件会通过其 bundle 清单自动注册,因此这里只需要**覆盖(override)**它的配置——**不要**再用 `insert` 重新插入(重复的 `id` 会让 dsh 启动失败):
71
73
 
72
74
  ```yaml
73
75
  - id: connect
@@ -87,7 +89,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
87
89
  3. **启动宿主** —— `dsh web`(或 `dsh run`)。未配置凭据时,`feishu` 通道会进入**一键开通**流程:扫描日志中的二维码 / 打开链接以授权机器人。
88
90
  4. **在飞书中给机器人发一条消息**。机器人以流式卡片回复;`/help` 列出所有命令;会话也会自动出现在 DSH Web GUI 中(自动镜像)。
89
91
 
90
- 一个完全可复现的示例是 `examples/` 文件夹加上 `docs/feishu-setup.zh.md`(飞书应用创建、事件订阅、发布)。
92
+ 一个完全可复现的示例是 [`examples/`](examples/) 文件夹加上仓库里的[飞书配置手册](https://github.com/IvanWu2015/dsh-connect/blob/main/docs/feishu-setup.zh.md)(飞书应用创建、事件订阅、发布)。
91
93
 
92
94
  ## 配置
93
95
 
@@ -97,7 +99,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
97
99
 
98
100
  | 键 | 默认值 | 说明 |
99
101
  |---|---|---|
100
- | `agentPreset` | roster default | 组合进每个绑定会话的智能体预设 id |
102
+ | `agentPreset` | roster default | 组合进每个绑定会话的智能体预设 id。解析是尽力而为的:先试配置的 id,再试 `standard`(或 roster 中第一个可挂载项);两者都组合不出来时,智能体不带预设构建,回合照常运行。因此一个过期的 id 只会降级并留下日志,而不会让每条消息都失败。 |
101
103
  | `workDir` | process cwd | 每个绑定智能体的绝对工作目录 |
102
104
  | `workspaces` | `[]` | `/dir` 选择器提供的额外工作区 |
103
105
  | `visionModel` | auto-detected | 当主模型无法查看图片时,用于描述图片的 `{ provider, model }` |
@@ -107,7 +109,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
107
109
  | `stateDir` | `.dsh-connect` | 保存 `bindings.json` 路由存储的目录(环境变量 `DSH_CONNECT_STATE_DIR` 可覆盖) |
108
110
  | `autoMirror` | `true` | 为每个新会话自动创建 Web GUI 镜像 |
109
111
  | `streamHeartbeatMs` | `60000` | 流式卡片的心跳保活间隔(毫秒);`0` 表示禁用 |
110
- | `notifyLevel` | `important` | 默认通知级别:`full`(全部流式输出)/ `important`(关键节点)/ `result`(仅结果);可通过设置菜单或 `/notify` 按聊天覆盖 |
112
+ | `notifyLevel` | `result` | 默认通知级别:`full`(全部流式输出)/ `important`(关键节点)/ `result`(仅结果,默认);可通过设置菜单或 `/notify` 按聊天覆盖 |
111
113
  | `progressTimeoutMs` | `300000` | 主动进度通知间隔(毫秒):当一轮对话在此时间内没有发送独立卡片/文本时,状态卡片会报告最新节点;`0` 表示禁用;可通过设置菜单或 `/progress` 按聊天覆盖 |
112
114
 
113
115
  ### 公共(所有通道)
@@ -116,7 +118,7 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
116
118
  |---|---|---|
117
119
  | `channels` | 全部内置 | 启用哪些通道:`feishu` / `telegram` / `dingtalk` / `web`。省略则启用全部内置通道。 |
118
120
  | `channelDefaults` | `{}` | 应用到未单独设置该键的每个通道(如 `{ language: "zh" }`)。 |
119
- | `settingsStatePath` | — | Web 设置面板持久化非密钥配置的路径(如 `.dsh-connect/settings.json`)。 |
121
+ | `settingsStatePath` | `<stateDir>/dsh-connect-settings.json` | Web 设置面板镜像非密钥配置的路径。默认落在 `stateDir` **之内**的 `dsh-connect-settings.json`,与 `bindings.json` 同目录,二者不会各说各话;设置该键可覆盖。自 0.9.0 起,面板的权威数据源是 `$DSH_HOME/settings.yaml` 里的 `dsh-connect` 段(见[用户设置](#用户设置)),本文件只是旧版 `/dsh-connect` RPC 读写的兼容镜像。 |
120
122
 
121
123
  ### `feishu`(飞书 / Lark 通道)
122
124
 
@@ -176,7 +178,9 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
176
178
 
177
179
  - **写入的文件**
178
180
  - `<stateDir>/bindings.json`(默认 `.dsh-connect/`)—— 聊天 ⇄ 会话路由存储(聊天键、会话 id、镜像与锁状态)。
179
- - `~/.dsh/.dsh-connect/feishu-credentials.json` —— 一键开通时保存的飞书凭据。
181
+ - `<stateDir>/dsh-connect-settings.json`(默认 `.dsh-connect/`)—— 非密钥配置的兼容镜像,见 `settingsStatePath`。
182
+ - `$DSH_HOME/settings.yaml` 的 `dsh-connect` 段 —— 经由 DSH 第一方设置机制写入,原子、加锁、保留注释。
183
+ - `~/.dsh/.dsh-connect/feishu-credentials.json` —— **旧版、只读**。一键开通过去把飞书凭据存在这里而不是凭据库,导致刚扫码授权完的用户永远看到「未配置凭据」。现在开通流程写入凭据库,已有安装会在启动时从这个文件回填一次;此后不再读写它。
180
184
  - `<workDir>/.dsh-connect-images/` —— 为用户图片/附件暂存,供智能体工具使用。
181
185
  - DSH 自身在 `~/.dsh/` 下的会话日志与设置(sessions、settings 等)。
182
186
  - **网络**
@@ -184,6 +188,90 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
184
188
  - DSH 为智能体模型调用的 LLM 提供商 API(如 DeepSeek),以及可选的视觉模型。
185
189
  - **用户数据** —— 消息文本与附件经由机器人流向智能体会话;它们与任何 DSH 会话一样保存在 DSH 会话日志中。白名单(`allowUsers` / `allowChats`)限制了可以驱动机器人的人。
186
190
 
191
+ ## 设置面板
192
+
193
+ `dsh-connect` 在 **设置 → dsh-connect** 下有自己的页面。它是一条渠道页签条 + 若干可折叠卡片:
194
+ 每张卡片由一个按钮做标题行,低频字段收在第二级的**高级选项**折叠里,保存/状态固定在滚动区底部。
195
+
196
+ | 渠道与凭据 | 展开高级选项 |
197
+ |---|---|
198
+ | ![dsh-connect 设置面板:渠道页签条、展开的飞书卡片及其凭据字段,以及三张收起后仍显示凭据徽标的渠道卡片](docs/images/settings-overview-zh.png) | ![同一面板展开某渠道的「高级选项」折叠,露出回调端口与回调路径字段](docs/images/settings-advanced-zh.png) |
199
+
200
+ ![面板底部的公共默认卡片与固定保存条](docs/images/settings-defaults-zh.png)
201
+
202
+ 英文截图:[概览](docs/images/settings-overview-en.png) · [高级](docs/images/settings-advanced-en.png) · [公共默认](docs/images/settings-defaults-en.png)。
203
+
204
+ > 截图取自一个凭据全是占位符的一次性 profile。上面没有任何真实密钥 —— 也不可能有:宿主会在
205
+ > 值到达浏览器之前完成打码(见下文[面板回显与脱敏](#面板回显与脱敏))。
206
+
207
+ 卡片默认展开你已启用的渠道(一个都没启用时展开第一个)。点击页签会展开对应卡片并滚进视野,
208
+ **不会**收起其他卡片 —— 多张同时展开是合法状态。收起是**卸载**卡片主体而不是隐藏它,这之所
209
+ 以安全,是因为你刚输入但尚未保存的密钥存在面板自身的 state 里,而不在卡片里。勾选某渠道的
210
+ 启用框同样会展开它。
211
+
212
+ ## 用户设置
213
+
214
+ Web 设置面板(**设置** 下的 `dsh-connect`)编辑的是 `$DSH_HOME/settings.yaml` 里的
215
+ `dsh-connect` 段,走 DSH 自带的(第一方)设置机制。该文档支持热重载、在文件锁下原子写入、
216
+ 并保留你的注释——所以手工编辑它同样有效,改动无需重启即可生效。
217
+
218
+ 取值分三层解析,越靠后越具体:
219
+
220
+ 1. 插件内置的 schema 默认值;
221
+ 2. 插件自己的 `cordis.patch.yml` 条目(你现有的配置**不会**被丢弃,它注册为基础层);
222
+ 3. `settings.yaml` 中的 `dsh-connect` 段。
223
+
224
+ **密钥永远不会写入 `settings.yaml`。** 那是一份普通的、鼓励用户贴进 issue 的文档;凭据
225
+ 一律保存在 DSH 凭据库(`ctx.credentials`)——一键开通流程与 `FEISHU_*` 这类环境变量也
226
+ 正是写在那里。
227
+
228
+ 旧版 `/dsh-connect` HTTP RPC 为面板兼容而保留;它现在读写同一个 namespace,并把非密钥配置
229
+ 镜像到 `settingsStatePath`(见[公共(所有通道)](#公共所有通道)),以兼容旧面板。
230
+
231
+ ### 面板回显与脱敏
232
+
233
+ 每个已保存的密钥字段下方都有一行**只读**的「当前值:…」(没有值时显示「未配置」),这样你
234
+ 不用重新输入就能确认自己填了什么。**打码在宿主侧完成**,只有一张共用的策略表
235
+ (`src/settings/secret-disclosure.ts`)——宿主按它打码,面板按它决定输入框渲染成
236
+ `password` 还是 `text`,两边因此不可能各说各话。
237
+
238
+ | 字段 | 显示方式 |
239
+ |---|---|
240
+ | `appId`、`clientId` | **完整显示**。它们是标识符而不是口令:每次出站 API 调用都会带上,厂家控制台也明文可见,遮住并不能保护什么。 |
241
+ | `appSecret`、`clientSecret`、`botToken`、`secret` | 只留头尾,例如 `a1b2…z9y8`。太短、露头露尾就等于全露的值,改为固定长度的 `••••••`。 |
242
+ | `webhookUrl`(钉钉) | **URL 感知**。保留域名、路径与参数名,只对令牌的中段打码——钉钉把令牌放在查询串里,整串打码(`https…bcde`)等于什么也确认不了。 |
243
+ | 其他 / 未列出的键 | 一律按最保守的方式打码。 |
244
+
245
+ 有两条性质无论如何都成立:
246
+
247
+ - **可用密钥不会跨线。** 值在离开宿主前就已打码,所以浏览器标签页(以及它的截图)永远拿不到
248
+ 一个可用的密钥。
249
+ - **掩码不可能被写回。** 预览是输入框**旁边**的文本,不是输入框的 `value`。输入框始终为空,
250
+ 而「空」的含义是「不动已保存的值」——因此只保存配置时,一个凭据都不会被写入。
251
+
252
+ 面板自身的全部文案(通道名、字段标签、选项文字、状态)都来自 `client/locale.mjs`,其中同时
253
+ 提供 `zh` 与 `en`。有测试断言两种语言的键集合完全一致——宿主在缺键时会静默回退到另一种语言,
254
+ 所以漏译不会报错,只会让页面变成中英混杂。
255
+
256
+ ### 凭据分组
257
+
258
+ 当某通道的**任意一组**凭据被完整满足时,该通道即视为「已配置」;而单组内必须**全部**满足
259
+ ——组内是 all-of,组间是 any-of。没有任何分组的通道(`web`)按定义就是已配置,永远不显示
260
+ 告警徽标。
261
+
262
+ 之所以要分组,是因为一个通道可能有不止一种互斥的认证方式;若要求全部满足,就会把明明能用的
263
+ 机器人误报成未配置:
264
+
265
+ | 通道 | 分组 |
266
+ |---|---|
267
+ | `feishu` | app id + app secret |
268
+ | `telegram` | bot token |
269
+ | `dingtalk` | webhook URL + 签名密钥 —— *或* —— Stream 模式的 client id + client secret |
270
+ | `web` | 无 |
271
+
272
+ 因此,只用 webhook 推送(没有 Stream 凭据)的钉钉机器人会被正确判定为已配置,只用 Stream
273
+ 模式的同样如此。
274
+
187
275
  ## 故障排查
188
276
 
189
277
  日志来自 DSH 宿主日志器(在终端运行 `dsh web`);插件消息带有 `connect:` / `connect-feishu:` 前缀。
@@ -192,11 +280,12 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
192
280
  |---|---|
193
281
  | `connect-feishu: adapter init failed` / `start failed` | 凭据错误、应用未发布或网络被阻断。检查 `appId`/`appSecret`,重新运行开通流程,确认机器人在飞书开放平台后台处于在线状态。 |
194
282
  | `connect: resume of <id> failed, creating fresh session` | 持久化会话无法恢复(工作目录缺失、持久化问题)。检查 `workDir` 和 `~/.dsh/sessions`。 |
283
+ | 机器人对每条消息都回一行原始的 `agent-presets: preset "…" not found`,内容到不了智能体 | `$DSH_HOME/settings.yaml` 里的 `agent-presets.default` 指向了任何已安装版本都不提供的 id。**0.9.0** 已修复:改为重试 `standard` 并记录决策,而不是让这一轮失败;在更旧的版本上,请把该键改成一个确实存在的 id(`standard`)。 |
195
284
  | 会话锁定提示 | 另一个客户端(飞书或 Web)持有写锁。使用 `/unlock` 或等待锁超时。 |
196
- | Web GUI 中的模型切换似乎被忽略 | 已在当前 main 中修复:插件不再用静态默认模型覆盖 Web GUI 的会话选择。重启 `dsh web` 以加载重建后的插件。 |
285
+ | Web GUI 中的模型切换似乎被忽略 | 已在 **0.9.0** 修复:插件不再用静态默认模型覆盖 Web GUI 的会话选择。升级后重启 `dsh web`。 |
197
286
  | `[用户发送了图片,但下载失败…]` | 应用缺少飞书 `im:resource` 权限;授予该权限并重新授权。 |
198
- | 流式回复是一整块没有分段 | 已在当前 main 中修复:块边界与推理/回答分隔现在会插入空行(推理软换行已针对飞书卡片扩展)。重启 `dsh web`。 |
199
- | 长时间任务中卡片卡在「思考中…」没有进展 | 已在当前 main 中修复:推理现在实时流出,工具调用显示为 `🔧` 进度行,静默期间心跳保活会更新卡片。重启 `dsh web`。 |
287
+ | 流式回复是一整块没有分段 | 已在 **0.9.0** 修复:块边界与推理/回答分隔现在会插入空行(推理软换行已针对飞书卡片扩展)。升级后重启 `dsh web`。 |
288
+ | 长时间任务中卡片卡在「思考中…」没有进展 | 已在 **0.9.0** 修复:推理现在实时流出,工具调用显示为 `🔧` 进度行,静默期间心跳保活会更新卡片。升级后重启 `dsh web`。 |
200
289
  | 菜单卡片不更新 / 过期 | 设计如此:卡片空闲 60 秒后自动关闭;重新打开菜单即可。 |
201
290
 
202
291
  **回滚** —— 重新安装之前的版本(先移除当前版本,再执行 `dsh plugin --profile web add dsh-connect@<version>`),或在源码安装中 `git checkout` 到固定的提交。
@@ -208,11 +297,13 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
208
297
  ```
209
298
  packages/
210
299
  connect/ # 本包 — 多合一插件
211
- src/ # 核心:runner、service、binding、commands、i18n、menus …
300
+ src/ # 核心:runner、service、binding、commands、menus、chat key …
212
301
  src/channels/ # 通道适配器:feishu / telegram / dingtalk / web
213
- src/settings/ # Web 设置栈:宿主 RPC、凭据库、设置服务/面板
302
+ src/settings/ # Web 设置栈:宿主 RPC、凭据库、脱敏策略
303
+ client/ # Web 设置前端插件 + 其纯函数模块(locale、panel-state)
214
304
  test/ # node:test 套件(run-all.mjs 导入每个套件)
215
- client/ # Web 设置前端插件
305
+ docs/images/ # 本 README 引用的截图
306
+ examples/ # minimal.config.json
216
307
  ```
217
308
 
218
309
  ```sh
@@ -228,9 +319,11 @@ pnpm test
228
319
  node packages/connect/test/unit.test.mjs
229
320
  ```
230
321
 
231
- **结构** —— `src/runner.ts` 负责每个聊天的智能体驱动与流式桥接(`applyStreamChunk` 是纯函数、有单元测试的块组装器);`src/service.ts` 负责适配器注册表与路由;`src/channels/` 存放 feishu / telegram / dingtalk / web 通道适配器;`src/settings/` 存放 Web 设置栈(宿主 RPC、凭据库、设置服务);`src/i18n.ts` 存放 `zh`/`en` 词典(两种语言的关键字需保持同步);`src/binding.ts` 是路由存储。
322
+ **结构** —— `src/runner.ts` 负责每个聊天的智能体驱动与流式桥接(`applyStreamChunk` 是纯函数、有单元测试的块组装器);`src/service.ts` 负责适配器注册表与路由;`src/channels/` 存放 feishu / telegram / dingtalk / web 通道适配器;`src/settings/` 存放 Web 设置栈(宿主 RPC、凭据库、脱敏策略);`src/binding.ts` 是路由存储。
323
+
324
+ 有两样东西原来是放在 `src/` 的,现在移到了 `client/`,为的是能在不引入 React 的情况下单测:**`client/locale.mjs`** 存放设置面板的全部用户可见文案(`zh` 与 `en`,两边键集合必须一致 —— 宿主在缺键时会静默渲染**另一种**语言,所以漏译表现为中英混杂,而不是报错),**`client/panel-state.mjs`** 存放卡片的展开/高级折叠规则。两者都是零依赖的纯 ESM,分别由 `test/locale.test.mjs` 与 `test/panel-state.test.mjs` 直接断言。
232
325
 
233
- **贡献** —— 欢迎在 [github.com/IvanWu2015/dsh-connect](https://github.com/IvanWu2015/dsh-connect) 提交 PR。对于面向用户的字符串,请在 `src/i18n.ts` 中同时为 `zh` 和 `en` 添加关键字。发布说明在 `CHANGELOG.md` 中;发布流程参见 `docs/PUBLISHING.zh.md`。
326
+ **贡献** —— 欢迎在 [github.com/IvanWu2015/dsh-connect](https://github.com/IvanWu2015/dsh-connect) 提交 PR。对于面向用户的面板文案,请在 `client/locale.mjs` 中同时为 `zh` 和 `en` 添加键,然后重建产物(`node scripts/build-client.mjs`)—— `test/client-bundle.test.mjs` 跑的是**构建产物**,产物过期会直接失败。发布说明在 `CHANGELOG.md` 中;发布流程参见 [`docs/PUBLISHING.zh.md`](https://github.com/IvanWu2015/dsh-connect/blob/main/docs/PUBLISHING.zh.md)。
234
327
 
235
328
  ## 许可与安全
236
329