dsh-connect 0.7.2 → 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 (153) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +196 -43
  3. package/README.zh.md +171 -42
  4. package/client/client.js +817 -0
  5. package/client/client.js.map +7 -0
  6. package/client/locale.mjs +227 -0
  7. package/client/panel-state.mjs +54 -0
  8. package/client/settings-client.mjs +389 -0
  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/examples/minimal.config.json +16 -0
  16. package/lib/binding.d.ts.map +1 -1
  17. package/lib/binding.js +2 -2
  18. package/lib/binding.js.map +1 -1
  19. package/lib/channels/dingtalk/adapter.d.ts +59 -0
  20. package/lib/channels/dingtalk/adapter.d.ts.map +1 -0
  21. package/lib/channels/dingtalk/adapter.js +123 -0
  22. package/lib/channels/dingtalk/adapter.js.map +1 -0
  23. package/lib/channels/dingtalk/i18n.d.ts +14 -0
  24. package/lib/channels/dingtalk/i18n.d.ts.map +1 -0
  25. package/lib/channels/dingtalk/i18n.js +16 -0
  26. package/lib/channels/dingtalk/i18n.js.map +1 -0
  27. package/lib/channels/dingtalk/index.d.ts +127 -0
  28. package/lib/channels/dingtalk/index.d.ts.map +1 -0
  29. package/lib/channels/dingtalk/index.js +144 -0
  30. package/lib/channels/dingtalk/index.js.map +1 -0
  31. package/lib/channels/dingtalk/message.d.ts +49 -0
  32. package/lib/channels/dingtalk/message.d.ts.map +1 -0
  33. package/lib/channels/dingtalk/message.js +50 -0
  34. package/lib/channels/dingtalk/message.js.map +1 -0
  35. package/lib/channels/dingtalk/stomp.d.ts +36 -0
  36. package/lib/channels/dingtalk/stomp.d.ts.map +1 -0
  37. package/lib/channels/dingtalk/stomp.js +107 -0
  38. package/lib/channels/dingtalk/stomp.js.map +1 -0
  39. package/lib/channels/dingtalk/stream.d.ts +53 -0
  40. package/lib/channels/dingtalk/stream.d.ts.map +1 -0
  41. package/lib/channels/dingtalk/stream.js +199 -0
  42. package/lib/channels/dingtalk/stream.js.map +1 -0
  43. package/lib/channels/dingtalk/webhook.d.ts +84 -0
  44. package/lib/channels/dingtalk/webhook.d.ts.map +1 -0
  45. package/lib/channels/dingtalk/webhook.js +143 -0
  46. package/lib/channels/dingtalk/webhook.js.map +1 -0
  47. package/lib/channels/feishu/adapter.d.ts +133 -0
  48. package/lib/channels/feishu/adapter.d.ts.map +1 -0
  49. package/lib/channels/feishu/adapter.js +662 -0
  50. package/lib/channels/feishu/adapter.js.map +1 -0
  51. package/lib/channels/feishu/i18n.d.ts +30 -0
  52. package/lib/channels/feishu/i18n.d.ts.map +1 -0
  53. package/lib/channels/feishu/i18n.js +42 -0
  54. package/lib/channels/feishu/i18n.js.map +1 -0
  55. package/lib/channels/feishu/index.d.ts +82 -0
  56. package/lib/channels/feishu/index.d.ts.map +1 -0
  57. package/lib/channels/feishu/index.js +103 -0
  58. package/lib/channels/feishu/index.js.map +1 -0
  59. package/lib/channels/feishu/onboard.d.ts +16 -0
  60. package/lib/channels/feishu/onboard.d.ts.map +1 -0
  61. package/lib/channels/feishu/onboard.js +86 -0
  62. package/lib/channels/feishu/onboard.js.map +1 -0
  63. package/lib/channels/telegram/adapter.d.ts +70 -0
  64. package/lib/channels/telegram/adapter.d.ts.map +1 -0
  65. package/lib/channels/telegram/adapter.js +460 -0
  66. package/lib/channels/telegram/adapter.js.map +1 -0
  67. package/lib/channels/telegram/client.d.ts +115 -0
  68. package/lib/channels/telegram/client.d.ts.map +1 -0
  69. package/lib/channels/telegram/client.js +198 -0
  70. package/lib/channels/telegram/client.js.map +1 -0
  71. package/lib/channels/telegram/i18n.d.ts +14 -0
  72. package/lib/channels/telegram/i18n.d.ts.map +1 -0
  73. package/lib/channels/telegram/i18n.js +18 -0
  74. package/lib/channels/telegram/i18n.js.map +1 -0
  75. package/lib/channels/telegram/index.d.ts +46 -0
  76. package/lib/channels/telegram/index.d.ts.map +1 -0
  77. package/lib/channels/telegram/index.js +54 -0
  78. package/lib/channels/telegram/index.js.map +1 -0
  79. package/lib/channels/web/adapter.d.ts +103 -0
  80. package/lib/channels/web/adapter.d.ts.map +1 -0
  81. package/lib/channels/web/adapter.js +161 -0
  82. package/lib/channels/web/adapter.js.map +1 -0
  83. package/lib/channels/web/index.d.ts +47 -0
  84. package/lib/channels/web/index.d.ts.map +1 -0
  85. package/lib/channels/web/index.js +59 -0
  86. package/lib/channels/web/index.js.map +1 -0
  87. package/lib/chat-key.d.ts +29 -0
  88. package/lib/chat-key.d.ts.map +1 -0
  89. package/lib/chat-key.js +38 -0
  90. package/lib/chat-key.js.map +1 -0
  91. package/lib/index.d.ts +78 -11
  92. package/lib/index.d.ts.map +1 -1
  93. package/lib/index.js +312 -9
  94. package/lib/index.js.map +1 -1
  95. package/lib/runner.d.ts +55 -2
  96. package/lib/runner.d.ts.map +1 -1
  97. package/lib/runner.js +158 -23
  98. package/lib/runner.js.map +1 -1
  99. package/lib/scheduler.d.ts.map +1 -1
  100. package/lib/scheduler.js +2 -2
  101. package/lib/scheduler.js.map +1 -1
  102. package/lib/service.d.ts +17 -1
  103. package/lib/service.d.ts.map +1 -1
  104. package/lib/service.js +47 -3
  105. package/lib/service.js.map +1 -1
  106. package/lib/settings/channel-runtime.d.ts +75 -0
  107. package/lib/settings/channel-runtime.d.ts.map +1 -0
  108. package/lib/settings/channel-runtime.js +165 -0
  109. package/lib/settings/channel-runtime.js.map +1 -0
  110. package/lib/settings/channels.d.ts +62 -0
  111. package/lib/settings/channels.d.ts.map +1 -0
  112. package/lib/settings/channels.js +132 -0
  113. package/lib/settings/channels.js.map +1 -0
  114. package/lib/settings/credential-store.d.ts +85 -0
  115. package/lib/settings/credential-store.d.ts.map +1 -0
  116. package/lib/settings/credential-store.js +142 -0
  117. package/lib/settings/credential-store.js.map +1 -0
  118. package/lib/settings/index.d.ts +14 -0
  119. package/lib/settings/index.d.ts.map +1 -0
  120. package/lib/settings/index.js +14 -0
  121. package/lib/settings/index.js.map +1 -0
  122. package/lib/settings/namespace.d.ts +175 -0
  123. package/lib/settings/namespace.d.ts.map +1 -0
  124. package/lib/settings/namespace.js +200 -0
  125. package/lib/settings/namespace.js.map +1 -0
  126. package/lib/settings/rpc-client.d.ts +36 -0
  127. package/lib/settings/rpc-client.d.ts.map +1 -0
  128. package/lib/settings/rpc-client.js +45 -0
  129. package/lib/settings/rpc-client.js.map +1 -0
  130. package/lib/settings/secret-disclosure.d.ts +50 -0
  131. package/lib/settings/secret-disclosure.d.ts.map +1 -0
  132. package/lib/settings/secret-disclosure.js +132 -0
  133. package/lib/settings/secret-disclosure.js.map +1 -0
  134. package/lib/settings/settings-model.d.ts +124 -0
  135. package/lib/settings/settings-model.d.ts.map +1 -0
  136. package/lib/settings/settings-model.js +118 -0
  137. package/lib/settings/settings-model.js.map +1 -0
  138. package/lib/settings/settings-rpc.d.ts +130 -0
  139. package/lib/settings/settings-rpc.d.ts.map +1 -0
  140. package/lib/settings/settings-rpc.js +233 -0
  141. package/lib/settings/settings-rpc.js.map +1 -0
  142. package/lib/settings/settings-service.d.ts +35 -0
  143. package/lib/settings/settings-service.d.ts.map +1 -0
  144. package/lib/settings/settings-service.js +171 -0
  145. package/lib/settings/settings-service.js.map +1 -0
  146. package/lib/state-dir.d.ts +28 -0
  147. package/lib/state-dir.d.ts.map +1 -0
  148. package/lib/state-dir.js +33 -0
  149. package/lib/state-dir.js.map +1 -0
  150. package/lib/stream.d.ts.map +1 -1
  151. package/lib/stream.js +3 -2
  152. package/lib/stream.js.map +1 -1
  153. package/package.json +56 -12
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
@@ -2,16 +2,16 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- The **channel-agnostic core** for connecting [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (**DSH**) agents to chat platforms (Feishu / Lark, Telegram, DingTalk; more to come): session binding, agent driving, streaming reply bridging, interactive menu cards, and local commands.
5
+ The **all-in-one plugin** for connecting [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (**DSH**) agents to chat platforms (Feishu / Lark, Telegram, DingTalk, and the Web mirror; more to come): session binding, agent driving, streaming reply bridging, interactive menu cards, local commands, and the web-settings stack.
6
6
 
7
- > Install together with a channel adapter — e.g. [dsh-connect-feishu](https://www.npmjs.com/package/dsh-connect-feishu), [dsh-connect-telegram](https://www.npmjs.com/package/dsh-connect-telegram) or the push-only [dsh-connect-dingtalk](https://www.npmjs.com/package/dsh-connect-dingtalk) — or the optional [dsh-connect-web](https://www.npmjs.com/package/dsh-connect-web) mirror monitor.
7
+ > One install, one config block: the core `connect` service, every channel adapter (feishu / telegram / dingtalk / web), and the web-settings stack all live in this single package. Enable the channels you use via the `channels` selector. The former split packages (`dsh-connect-feishu`, `dsh-connect-telegram`, `dsh-connect-dingtalk`, `dsh-connect-web`) and the `dsh-connect-all` bundle no longer exist.
8
8
 
9
9
  ## Overview
10
10
 
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 **channel-agnostic core** for connecting [DeepSeek Harness](https://github.c
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
 
@@ -37,31 +46,28 @@ The plugin runs on the DSH **Host plane** (process-level singleton services), no
37
46
  Plugin management is a thin wrapper over pnpm in the DSH profile:
38
47
 
39
48
  ```sh
40
- # Install (core + Feishu adapter)
41
- dsh plugin --profile web add dsh-connect dsh-connect-feishu
42
-
43
- # Optional: Web mirror monitor
44
- dsh plugin --profile web add dsh-connect-web
49
+ # Install the single all-in-one plugin (core + all channel adapters + web-settings)
50
+ dsh plugin --profile web add dsh-connect
45
51
  ```
46
52
 
47
53
  **Upgrade**
48
54
 
49
55
  ```sh
50
- dsh plugin --profile web update dsh-connect dsh-connect-feishu
56
+ dsh plugin --profile web update dsh-connect
51
57
  ```
52
58
 
53
- **Disable** — override the bundle-registered entries with `disabled: true` in the profile patch (see `~/.dsh/profiles/<profile>/cordis.patch.yml`):
59
+ **Disable** — override the bundle-registered entry with `disabled: true` in the profile patch (see `~/.dsh/profiles/<profile>/cordis.patch.yml`):
54
60
 
55
61
  ```yaml
56
- - id: connect # set disabled: true (and connect-feishu) to disable
62
+ - id: connect
57
63
  name: dsh-connect
58
64
  disabled: true
59
65
  ```
60
66
 
61
- **Complete removal** — uninstall the packages and delete the data they created:
67
+ **Complete removal** — uninstall the package and delete the data it created:
62
68
 
63
69
  ```sh
64
- dsh plugin --profile web remove dsh-connect dsh-connect-feishu
70
+ dsh plugin --profile web remove dsh-connect
65
71
  # then remove the plugin data (see "Permissions & data" below):
66
72
  rm -rf .dsh-connect # binding route store (stateDir)
67
73
  rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
@@ -70,36 +76,37 @@ rm -f ~/.dsh/.dsh-connect/feishu-credentials.json
70
76
  ## Quick start
71
77
 
72
78
  1. **Install the plugins** (see above).
73
- 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 plugins register themselves via their bundle manifests, so only override their config — do **not** `insert` them again (duplicate ids crash 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):
74
80
 
75
81
  ```yaml
76
82
  - id: connect
77
83
  name: dsh-connect
78
84
  # workDir: D:\your\workdir # agent working directory (default: process cwd)
79
- - id: connect-feishu
80
- name: dsh-connect-feishu
81
85
  config:
82
- appId: cli_xxxx
83
- appSecret: cli_secret_xxxx
84
- transport: websocket
85
- requireMention: true
86
- dmMode: open
86
+ channels: [feishu] # which channels to activate; omit = all built-in
87
+ # channelDefaults: { language: zh } # keys applied to every channel that doesn't set its own
88
+ feishu:
89
+ appId: cli_xxxx
90
+ appSecret: cli_secret_xxxx
91
+ transport: websocket
92
+ requireMention: true
93
+ dmMode: open
87
94
  ```
88
95
 
89
- 3. **Start the host** — `dsh web` (or `dsh run`). With no credentials configured, `dsh-connect-feishu` enters **one-click onboarding**: scan the QR / open the link from the log to authorize the bot.
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.
90
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).
91
98
 
92
- 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).
93
100
 
94
101
  ## Configuration
95
102
 
96
- Configuration lives in the DSH profile patch (`cordis.patch.yml`) under each plugin's `config:`. `dsh.shared.config.json` in the project root (or its parent) can supply workspace/state defaults that take precedence for those keys.
103
+ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under the plugin's `config:`. `dsh.shared.config.json` in the project root (or its parent) can supply workspace/state defaults that take precedence for those keys.
97
104
 
98
105
  ### `dsh-connect` (core)
99
106
 
100
107
  | Key | Default | Description |
101
108
  |---|---|---|
102
- | `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. |
103
110
  | `workDir` | process cwd | Absolute working directory for each bound agent |
104
111
  | `workspaces` | `[]` | Extra workspaces offered by the `/dir` picker |
105
112
  | `visionModel` | auto-detected | `{ provider, model }` used to describe images when the main model can't see them |
@@ -109,10 +116,18 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under each plu
109
116
  | `stateDir` | `.dsh-connect` | Directory holding the `bindings.json` route store (env `DSH_CONNECT_STATE_DIR` overrides) |
110
117
  | `autoMirror` | `true` | Automatically create a Web GUI mirror for every new session |
111
118
  | `streamHeartbeatMs` | `60000` | Liveness heartbeat interval (ms) for the streaming card; `0` disables it |
112
- | `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` |
113
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` |
114
121
 
115
- ### `dsh-connect-feishu` (adapter)
122
+ ### Shared (all channels)
123
+
124
+ | Key | Default | Description |
125
+ |---|---|---|
126
+ | `channels` | all built-in | Which channels to activate: `feishu` / `telegram` / `dingtalk` / `web`. Omit to activate all built-in channels. |
127
+ | `channelDefaults` | `{}` | Keys applied to every channel that doesn't set its own (e.g. `{ language: "zh" }`). |
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. |
129
+
130
+ ### `feishu` (Feishu / Lark channel)
116
131
 
117
132
  | Key | Default | Description |
118
133
  |---|---|---|
@@ -137,11 +152,42 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under each plu
137
152
 
138
153
  **Sensitive items** — `appSecret`, `verificationToken`, `encryptKey`, and `feishu-credentials.json`. Prefer environment variables or one-click onboarding; keep them out of version control.
139
154
 
155
+ ### `telegram` (Telegram channel)
156
+
157
+ | Key | Default | Description |
158
+ |---|---|---|
159
+ | `botToken` | env `TELEGRAM_BOT_TOKEN` | Telegram bot token from @BotFather (**secret**) |
160
+ | `requireMention` | `true` | Groups only respond when the bot is @mentioned (or replying to the bot's own message) |
161
+ | `pollingTimeoutSeconds` | `50` | `getUpdates` long-poll timeout in seconds |
162
+ | `baseUrl` | — | Optional Bot API base URL override (e.g. a local Bot API server) |
163
+ | `language` | `zh` | User-facing message language: `zh` / `en` |
164
+
165
+ ### `dingtalk` (DingTalk channel)
166
+
167
+ | Key | Default | Description |
168
+ |---|---|---|
169
+ | `webhookUrl` | env `DINGTALK_WEBHOOK_URL` | Group custom-robot webhook URL (proactive push) |
170
+ | `secret` | env `DINGTALK_WEBHOOK_SECRET` | Signing secret (`SEC…`) only when signing is enabled |
171
+ | `stream.clientId` / `stream.clientSecret` | env `DINGTALK_STREAM_CLIENT_ID` / `DINGTALK_STREAM_CLIENT_SECRET` | Bidirectional stream-mode app credentials (**secret**, nested under `stream`) |
172
+ | `stream.requireMention` | `true` | Group replies need an @-mention (stream mode) |
173
+ | `defaultAt` | — | Default @-mentions merged into every push (`{ mobiles, userIds, all }`) |
174
+ | `language` | `zh` | User-facing message language: `zh` / `en` |
175
+
176
+ ### `web` (Web mirror channel)
177
+
178
+ | Key | Default | Description |
179
+ |---|---|---|
180
+ | `pollIntervalMs` | `1000` | Mirror-session polling interval (ms) |
181
+
182
+ Environment variables (`FEISHU_*`, `TELEGRAM_*`, `DINGTALK_*`, `DSH_CONNECT_STATE_DIR`, `DSH_HOME`) and the DSH credential store are the preferred way to supply per-channel secrets — the web settings pane writes them to the credential store and `injectSecrets` populates them on load.
183
+
140
184
  ## Permissions & data
141
185
 
142
186
  - **Files written**
143
187
  - `<stateDir>/bindings.json` (default `.dsh-connect/`) — the chat ⇄ session route store (chat keys, session ids, mirror and lock state).
144
- - `~/.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.
145
191
  - `<workDir>/.dsh-connect-images/` — user images/attachments staged for the agent's tools.
146
192
  - DSH's own session logs and settings under `~/.dsh/` (sessions, settings, etc.).
147
193
  - **Network**
@@ -149,6 +195,107 @@ Configuration lives in the DSH profile patch (`cordis.patch.yml`) under each plu
149
195
  - LLM provider APIs used by DSH for the agent's model (e.g. DeepSeek), plus the optional vision model.
150
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.
151
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
+
152
299
  ## Troubleshooting
153
300
 
154
301
  Logs come from the DSH host logger (run `dsh web` in a terminal); plugin messages are prefixed `connect:` / `connect-feishu:`.
@@ -157,44 +304,50 @@ Logs come from the DSH host logger (run `dsh web` in a terminal); plugin message
157
304
  |---|---|
158
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. |
159
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`). |
160
308
  | Session-locked notices | Another client (Feishu or Web) holds the write lock. Use `/unlock` or wait for the lock timeout. |
161
- | 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`. |
162
310
  | `[用户发送了图片,但下载失败…]` | Feishu `im:resource` permission is missing on the app; grant it and re-approve. |
163
- | 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`. |
164
- | 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`. |
165
313
  | Menu cards don't update / expire | Cards auto-close after 60 s idle by design; re-open the menu. |
166
314
 
167
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.
168
316
 
169
317
  ## Development
170
318
 
171
- This is a pnpm workspace; the plugins are independent npm packages under `packages/`:
319
+ This is a pnpm workspace; `dsh-connect` is the single package under `packages/`:
172
320
 
173
321
  ```
174
322
  packages/
175
- connect/ # this package — channel-agnostic core
176
- connect-feishu/ # Feishu / Lark adapter
177
- connect-telegram/ # Telegram adapter (getUpdates long polling)
178
- connect-dingtalk/ # DingTalk group-webhook push channel
179
- connect-web/ # optional Web mirror monitor
323
+ connect/ # this package — the all-in-one plugin
324
+ src/ # core: runner, service, binding, commands, menus, chat keys …
325
+ src/channels/ # channel adapters: feishu / telegram / dingtalk / web
326
+ src/settings/ # web-settings stack: host RPC, credential store, disclosure policy
327
+ client/ # web-settings frontend plugin + its pure, testable modules
328
+ test/ # node:test suites (run-all.mjs imports every suite)
329
+ docs/images/ # screenshots used by this README
330
+ examples/ # minimal.config.json
180
331
  ```
181
332
 
182
333
  ```sh
183
334
  pnpm install
184
335
 
185
- # build & typecheck one package
336
+ # build & typecheck the package
186
337
  pnpm --filter dsh-connect build
187
338
  pnpm --filter dsh-connect typecheck
188
339
 
189
- # unit tests (node:test)
340
+ # unit tests (node:test) — run-all.mjs imports every suite in-process
190
341
  pnpm test
191
342
  # or run one suite
192
343
  node packages/connect/test/unit.test.mjs
193
344
  ```
194
345
 
195
- **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/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`.
196
349
 
197
- **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.
198
351
 
199
352
  ## License & security
200
353