dsh-dbhub-live 5.2.0-dev.3 → 6.0.0-rc.1

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 (58) hide show
  1. package/README.en.md +36 -33
  2. package/README.md +37 -34
  3. package/lib/adhoc.mjs +108 -12
  4. package/lib/capability.mjs +97 -13
  5. package/lib/client.js +705 -315
  6. package/lib/config.mjs +490 -99
  7. package/lib/i18n.mjs +68 -2
  8. package/lib/index.mjs +386 -260
  9. package/lib/mcp.mjs +245 -49
  10. package/lib/meta.mjs +275 -267
  11. package/lib/options.mjs +11 -33
  12. package/lib/policy.mjs +92 -92
  13. package/lib/runtime.mjs +80 -11
  14. package/lib/state.mjs +112 -117
  15. package/lib/toml.mjs +10 -0
  16. package/lib/tools.mjs +118 -24
  17. package/lib/update.mjs +254 -244
  18. package/package.json +4 -20
  19. package/AGENTS.md +0 -116
  20. package/doc/ARCHITECTURE.md +0 -108
  21. package/doc/CLIENT.md +0 -124
  22. package/doc/DESENSITIZE.md +0 -56
  23. package/doc/DEVELOPMENT.md +0 -139
  24. package/doc/IMPACT-MATRIX.md +0 -36
  25. package/doc/RELEASE.md +0 -93
  26. package/doc/REQUIREMENTS.md +0 -177
  27. package/doc/images/chat-query.png +0 -0
  28. package/doc/images/row-editor.png +0 -0
  29. package/doc/images/settings-page.png +0 -0
  30. package/scripts/desensitize-check.mjs +0 -331
  31. package/scripts/desensitize.config.mjs +0 -255
  32. package/scripts/docs-check.mjs +0 -214
  33. package/scripts/release-preflight.mjs +0 -180
  34. package/scripts/render-check.mjs +0 -1366
  35. package/test/adhoc-toml.test.mjs +0 -365
  36. package/test/auto-dsn.test.mjs +0 -126
  37. package/test/bridge.test.mjs +0 -227
  38. package/test/capability.test.mjs +0 -231
  39. package/test/client-format.test.mjs +0 -515
  40. package/test/configure-options.test.mjs +0 -244
  41. package/test/configure-ssh.test.mjs +0 -232
  42. package/test/copy-from.test.mjs +0 -155
  43. package/test/env-options.test.mjs +0 -456
  44. package/test/i18n.test.mjs +0 -37
  45. package/test/init.test.mjs +0 -163
  46. package/test/meta.test.mjs +0 -234
  47. package/test/options.test.mjs +0 -160
  48. package/test/policy.test.mjs +0 -132
  49. package/test/resolve-source.test.mjs +0 -82
  50. package/test/resolve-workspace.test.mjs +0 -115
  51. package/test/row-cache.test.mjs +0 -237
  52. package/test/ssh-probe.test.mjs +0 -834
  53. package/test/state.test.mjs +0 -85
  54. package/test/test-op-fastpath.test.mjs +0 -208
  55. package/test/toml.test.mjs +0 -352
  56. package/test/update.test.mjs +0 -270
  57. package/test/util.test.mjs +0 -275
  58. package/test/zero-knowledge.test.mjs +0 -121
package/README.en.md CHANGED
@@ -15,7 +15,7 @@ Install it and you can simply tell the AI "show me the last 10 rows of the order
15
15
  <details>
16
16
  <summary>📷 Preview: Settings → DBHub Database Tools</summary>
17
17
 
18
- ![DBHub Database Tools configure page](doc/images/settings-page.png)
18
+ ![DBHub Database Tools configure page](https://raw.githubusercontent.com/mr-mihu/dsh-dbhub-live/dev/doc/images/settings-page.png)
19
19
 
20
20
  </details>
21
21
 
@@ -33,26 +33,24 @@ Install it and you can simply tell the AI "show me the last 10 rows of the order
33
33
 
34
34
  Supports **MySQL · PostgreSQL · MariaDB · SQLite · SQL Server**.
35
35
 
36
- <!-- release-test:versions:begin -->
37
- ## Supported version range
38
-
39
- **From DSH 0.1.5 (lines 0.1.5 / 0.1.6 / 0.1.7 / 0.2.0 / 0.2.1, prereleases included)** — 6 declared versions verified (last verified 2026-10-24).
40
-
41
- <details>
42
- <summary>Per-version verification details (expand)</summary>
43
-
44
- | DSH version | UI | Boot | Hard checks | Result | Last verified |
45
- | --- | --- | --- | --- | --- | --- |
46
- | `0.1.5-rc.3` | legacy | 7.3s | 19 | ⚠️ pass with notes · manual | 2026-10-24 · 4f6a854a |
47
- | `0.1.6-alpha.2` | legacy | 3.5s | 19 | ⚠️ pass with notes · manual | 2026-10-24 · 4f6a854a |
48
- | `0.1.7-rc.2` | legacy | 4.7s | 20 | ⚠️ pass with notes · manual | 2026-10-24 · 4f6a854a |
49
- | `0.2.0-rc.2` | legacy | 4.9s | 20 | ⚠️ pass with notes · manual | 2026-10-24 · 4f6a854a |
50
- | `0.2.1-alpha.1` | modern | 5.1s | 20 | ⚠️ pass with notes · manual | 2026-10-24 · 4f6a854a |
51
- | `0.2.1-rc.2` | modern | 5.3s | 20 | ⚠️ pass with notes · manual | 2026-10-24 · 4f6a854a |
52
-
53
- </details>
54
-
55
- Full evidence, review rules and known drift live in [doc/SUPPORT-MATRIX.md](doc/SUPPORT-MATRIX.md).
36
+ <!-- release-test:versions:begin -->
37
+ ## Supported version range
38
+
39
+ **From DSH 0.2.1 (lines 0.2.1 / 0.2.2, prereleases included)** — 4 declared versions not all verified yet (last verified 2026-10-24).
40
+
41
+ <details>
42
+ <summary>Per-version verification details (expand)</summary>
43
+
44
+ | DSH version | UI | Boot | Hard checks | Result | Last verified |
45
+ | --- | --- | --- | --- | --- | --- |
46
+ | `0.2.1-rc.2` | modern | 16.9s | 20 | ⚠️ pass with notes · manual | 2026-10-24 · 91642fa2 |
47
+ | `0.2.1-rc.5` | modern | — | — | — | 未验证 |
48
+ | `0.2.2-rc.1` | modern | — | — | — | 未验证 |
49
+ | `0.2.2-rc.2` | modern | — | — | — | 未验证 |
50
+
51
+ </details>
52
+
53
+ Full evidence, review rules and known drift live in [doc/SUPPORT-MATRIX.md](https://github.com/mr-mihu/dsh-dbhub-live/blob/dev/doc/SUPPORT-MATRIX.md).
56
54
  <!-- release-test:versions:end -->
57
55
 
58
56
  ## Installation
@@ -73,7 +71,7 @@ On desktop you can also **install entirely through the UI**: **left menu → Plu
73
71
 
74
72
  Once installed, **restart `dsh web`** and open **Settings → DBHub Database Tools** to see the configure page (there is also a sidebar shortcut, and the Configure control on the plugin's row on the Plugins page — all three entries lead to the same page).
75
73
 
76
- > Requirements: every `dsh` release line from 0.1.5 on (0.2.x included); **the new UI needs 0.2.1+** (0.2.1 prereleases included) — the wide full page, the version/update header and the inline read-only switch only render on 0.2.1 and later; **0.1.x is still supported but keeps the old layout and behaviour**. Node.js ≥ 18 is recommended locally (it is used to install dbhub automatically). **Read-only mode and SSH tunnels need dbhub 1.4.0+** — the plugin detects this and gives you upgrade guidance. Per-version verification results live in [doc/SUPPORT-MATRIX.md](doc/SUPPORT-MATRIX.md) (generated by the release test, which must pass before every release).
74
+ > Requirements: **DSH 0.2.1 or later** (0.2.1 prereleases included; **0.1.x and 0.2.0 are no longer supported**). On an older host the plugin does not go blank: the configure page shows one bilingual notice naming the required and the running version, and every tool, connection and setting keeps working — but upgrade DSH soon. Node.js ≥ 18 is recommended locally (it is used to install dbhub automatically). **Read-only mode and SSH tunnels need dbhub 1.4.0+** — the plugin detects this and gives you upgrade guidance. Per-version verification results live in [doc/SUPPORT-MATRIX.md](https://github.com/mr-mihu/dsh-dbhub-live/blob/dev/doc/SUPPORT-MATRIX.md) (generated by the release test, which must pass before every release).
77
75
 
78
76
  ## Updating
79
77
 
@@ -87,9 +85,9 @@ Then **restart `dsh web`**.
87
85
 
88
86
  **Desktop**: uninstall the plugin on the **left menu → Plugins** page and reinstall the latest version (desktop updates go through the visual Plugins page). **Connections and credentials survive the reinstall** — they live in `~/.dsh/storages/dsh-dbhub-live`, independent of the module directory.
89
87
 
90
- **No need to remember version numbers**: the configure page header shows the plugin name and the current version; when a newer version exists a quiet "**↑ Upgrade to <latest>**" text link appears and **clicking it copies** `dsh-dbhub-live@<latest>`; the "Check for updates" button next to it checks on demand. Opening the page also triggers one automatic check (throttled, so it does not hit the network on every open). Checks use the **npm registry first, with GitHub Releases as the fallback**; release notes live in [GitHub Releases](https://github.com/mr-mihu/dsh-dbhub-live/releases).
88
+ **No need to remember version numbers**: the configure page header shows the plugin name and the current version; when a newer version exists a quiet "**↑ Upgrade to <latest>**" text link appears and **clicking it opens a panel where you copy** `dsh-dbhub-live@<latest>`; the "Check for updates" button next to it checks on demand. Opening the page also triggers one automatic check (throttled, so it does not hit the network on every open). Checks use the **npm registry first, with GitHub Releases as the fallback**; release notes live in [GitHub Releases](https://github.com/mr-mihu/dsh-dbhub-live/releases).
91
89
 
92
- > The version/update hint is part of the new UI and needs plugin 5.2.0+ and DSH 0.2.1+; on 0.1.x it is not shown (see "Configure Page").
90
+ > The version/update hint needs plugin 5.2.0+ and DSH 0.2.1+ (see "Configure Page").
93
91
 
94
92
  ## Quick Start
95
93
 
@@ -105,7 +103,7 @@ AI: … (query results)
105
103
  <details>
106
104
  <summary>📷 In action: ask the AI, get the query results</summary>
107
105
 
108
- ![Asking the AI and getting query results](doc/images/chat-query.png)
106
+ ![Asking the AI and getting query results](https://raw.githubusercontent.com/mr-mihu/dsh-dbhub-live/dev/doc/images/chat-query.png)
109
107
 
110
108
  </details>
111
109
 
@@ -149,11 +147,11 @@ While the plugin is enabled it contributes a **system-prompt section** (about 40
149
147
  <details>
150
148
  <summary>📷 Row editor: read-only mode and SSH tunnel</summary>
151
149
 
152
- ![Connection row and "Test SSH tunnel"](doc/images/row-editor.png)
150
+ ![Connection row and "Test SSH tunnel"](https://raw.githubusercontent.com/mr-mihu/dsh-dbhub-live/dev/doc/images/row-editor.png)
153
151
 
154
152
  </details>
155
153
 
156
- The same component offers **three layouts**, picked automatically from the host surface and the window width (the new UI needs plugin 5.2.0+ and **DSH 0.2.1+**; on DSH 0.1.x the old layout and behaviour are kept, see the end of this section):
154
+ The same component offers **three layouts**, picked automatically from the host surface and the window width (needs plugin 5.2.0+ and **DSH 0.2.1+**, see the end of this section):
157
155
 
158
156
  | Layout | How to reach it | Shape |
159
157
  | --- | --- | --- |
@@ -163,16 +161,17 @@ The same component offers **three layouts**, picked automatically from the host
163
161
 
164
162
  **Header: version and upgrade entry**: the title is the plugin name with the current version (small, muted) right next to it; when a newer version exists a quiet "**↑ Upgrade to <latest>**" text link appears on the right, and **clicking it expands** the update notes — the `dsh-dbhub-live@<latest>` spec (click to copy), the update command / steps for this deployment, and a link to the release notes; the "↻" button checks on demand. Opening the page also triggers an automatic check (throttled). Checks use the npm registry first, with GitHub Releases as the fallback. **An update is neutral news, never styled as an error.**
165
163
 
166
- - **Status line**: it shows only "running / disabled · N connections". Implementation facts — tool count, one-shot execution mode, dbhub version — live in the collapsed "ⓘ Details & diagnostics" block at the bottom of the page.
164
+ - **Status line**: it shows only "running / disabled · N connections". Implementation facts — tool count, one-shot execution mode, dbhub version — live in the collapsed "ⓘ Details" block below the status line.
167
165
  - **Connection list**: organised by workspace. When every workspace has a single environment the rows render as flat cards; as soon as one workspace owns several environments they fold into per-workspace groups (click a group head to expand; the most recently used one is open by default). Each connection takes one line: environment name · type / host / port / **database** · **read-only switch** · the `source` handle (click it to copy) · test / edit / delete. In a narrow window the host and port are truncated first, while **the database name always stays visible**.
168
166
  - **Settings (DSH-native style, deliberately quiet)**: on the wide page the four controls collapse into **one compact settings line** — `Plugin settings · Enable plugin [enabled] · Sidebar entry [shown] · Access policy [on] · Auto-update [7] days [save] · ⓘ details`; the modal and the phone use compact stacked rows. Fonts and switches are kept small so the **connection list** stays the main content. The controls are DSH's own switches (theme/skin aware, with state words); every explanatory paragraph lives in "ⓘ Details & diagnostics".
169
167
  - **Read-only switch (outside the edit form in all three layouts)**: right next to the datasource on every connection row there is a small "RO" toggle — one click flips it (open lock + "Writable" = writable; closed lock + "RO" = read-only) without opening the edit form; the modal and the phone layout have it too. The tick-box in the edit form stays as well; both are the same state. **The connection string itself no longer carries a lock icon** — it used to read like "this row needs a password" and clashed with the read-only lock.
170
- - **Add / edit / delete**: use ✏ on a row to change the connection string or the environment name; "+ Add connection" accepts Chinese environment names. **Saving onto an environment name that already exists asks "overwrite?" first** — cancel and not a single byte is written.
171
- - **Connection test**: runs `SELECT 1` over **the effective connection that environment will really use** (read-only / tunnel included) and shows the elapsed time while it runs. The result is one-shot feedback: nothing is persisted and no connection state changes.
168
+ - **Add / edit / delete**: use ✏ on a row to change the connection string or the environment name; "+ Add connection" accepts Chinese environment names. **Saving onto an environment name that already exists asks "overwrite?" first** — cancel and not a single byte is written. **Deleting asks first too** (it cannot be undone; cancelling writes nothing either).
169
+ - **A failed write is always visible**: when a save / switch / delete is refused (401, offline, a Host refusal) the page shows an error line and **never** claims "Saved ✓"; fixing it and clicking again is the retry. A failed plugin-state read shows its own error line plus "Retry" instead of silently turning into "no connections". "Auto-update interval" only accepts a whole number of days from 0 to 365 — an empty box or a fraction is rejected on the spot and no request is sent.
170
+ - **Connection test**: runs `SELECT 1` over **the effective connection that environment will really use** (read-only / tunnel included) and shows the elapsed time while it runs. The result is one-shot feedback: nothing is persisted and no connection state changes. When the command never reaches the Host (bridge 401 / offline) it says "the test command never reached the host" immediately instead of reporting a timeout 30 seconds later.
172
171
 
173
172
  **"Show the sidebar entry" is not disabling the plugin**: switching it off merely hides the sidebar shortcut — the plugin keeps working and the model can still query; use "Disable" to stop the plugin as a whole (a disabled plugin always hides the entry). In either state, **the settings page and the Plugins row's Configure control always stay reachable**, so the change can be undone at any time.
174
173
 
175
- **Compatibility path for DSH 0.1.x**: the header version / upgrade entry, the wide full page, the row read-only chip and the settings rows all render only on **DSH 0.2.1+** (prereleases included); on 0.1.x the plugin keeps its previous layout and behaviour and **shows no upgrade hint** — that is deliberate, not a defect.
174
+ **When DSH is too old**: the configure page gains one warning at the top (naming "DSH 0.2.1 or later" and the running version); the page, connections, read-only switches and tools all keep working — the plugin is not broken, the host just needs an upgrade.
176
175
 
177
176
  ## 🔒 Read-only Mode and SSH Tunnels
178
177
 
@@ -238,14 +237,18 @@ On desktop you can also uninstall directly on the **left menu → Plugins** page
238
237
  | The read-only / SSH controls are greyed out, or dbhub is reported as too old | Both need dbhub 1.4.0+: upgrade dbhub, or delete `~/.dsh/storages/dsh-dbhub-live` so the plugin installs a version that satisfies it. |
239
238
  | A connection test fails and you cannot tell why | Read the **end** of the message (the diagnostic is the tail of dbhub's output and marks the layer: [SSH layer] / [database layer] / [plugin layer]). **A [database layer] report means the tunnel is already up**; with a tunnel configured, press "Test SSH tunnel" first to verify the SSH layer on its own. |
240
239
  | The configure page is not visible | Check the plugin is installed: restart `dsh web` on the web side; on desktop use the Configure control on the Plugins row or the DBHub entry in the left menu. The page only shows in the Web / desktop UI and does not affect tool usage in terminal environments. |
241
- | The old UI is still shown after upgrading | The new UI needs **DSH 0.2.1+** (prereleases included) and plugin 5.2.0+; restart after upgrading dsh. On DSH 0.1.x the plugin deliberately keeps the old layout and behaviour and shows no update hint. |
240
+ | The configure page says the DSH version is too old | The plugin needs **DSH 0.2.1+** (prereleases included); upgrade dsh and restart. The tools and the page keep working until you do — the notice is only a reminder. |
242
241
  | No "Upgrade to …" hint appears in the header | You may already be on the latest version, or this check could not reach the npm registry / GitHub Releases; press "Check for updates" to retry. A failed check is only a hint and does not affect plugin functionality. |
243
242
  | How do I upgrade on desktop | Uninstall on the **left menu → Plugins** page, then reinstall the latest version; connections and credentials live in `~/.dsh/storages/dsh-dbhub-live` and are still there after the reinstall. |
244
- | After a dsh upgrade the plugin is reported as incompatible | Upgrade the plugin to 5.0.0+ (it supports both 0.1.x and 0.2.x); forcing it through with `allow-version` buys nothing. |
243
+ | After a dsh upgrade the plugin is reported as incompatible | Check that dsh is 0.2.1 or later, then upgrade the plugin to the latest version; forcing it through with `allow-version` buys nothing. |
245
244
  | Automatic dbhub updates are not wanted | Set "Auto-update interval (days)" to `0` on the configure page and save, or set `DSH_DBHUB_UPDATE_DAYS=0`. |
246
245
 
247
246
  **Environment variables** (only these two, not exposed in the UI): `DSH_DBHUB_PACKAGE` (the package that gets auto-installed, default `@bytebase/dbhub`), `DSH_DBHUB_UPDATE_DAYS` (the seed for the auto-update interval, default `7`, overridden by the value saved on the configure page).
248
247
 
248
+ ## Changelog
249
+
250
+ Highlights of every released version live in [doc/CHANGELOG.md](https://github.com/mr-mihu/dsh-dbhub-live/blob/dev/doc/CHANGELOG.md); per-release notes are on [GitHub Releases](https://github.com/mr-mihu/dsh-dbhub-live/releases).
251
+
249
252
  ## License
250
253
 
251
254
  [MIT](./LICENSE)
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  <details>
16
16
  <summary>📷 界面预览:设置 → DBHub 数据库工具</summary>
17
17
 
18
- ![DBHub 数据库工具配置页](doc/images/settings-page.png)
18
+ ![DBHub 数据库工具配置页](https://raw.githubusercontent.com/mr-mihu/dsh-dbhub-live/dev/doc/images/settings-page.png)
19
19
 
20
20
  </details>
21
21
 
@@ -33,26 +33,24 @@
33
33
 
34
34
  支持 **MySQL · PostgreSQL · MariaDB · SQLite · SQL Server**。
35
35
 
36
- <!-- release-test:versions:begin -->
37
- ## 支持的版本范围
38
-
39
- **DSH 0.1.5 起(0.1.5 / 0.1.6 / 0.1.7 / 0.2.0 / 0.2.1 共 5 条版本线,含预发布)** — 6 个声明版本已实测通过(最近验证 2026-10-24)。
40
-
41
- <details>
42
- <summary>逐版本实测明细(点开查看)</summary>
43
-
44
- | DSH 版本 | 界面 | 启动 | 硬检查 | 结论 | 最近验证 |
45
- | --- | --- | --- | --- | --- | --- |
46
- | `0.1.5-rc.3` | 旧版 | 7.3s | 19 | ⚠️ 通过(有告警) · 手工装配 | 2026-10-24 · 4f6a854a |
47
- | `0.1.6-alpha.2` | 旧版 | 3.5s | 19 | ⚠️ 通过(有告警) · 手工装配 | 2026-10-24 · 4f6a854a |
48
- | `0.1.7-rc.2` | 旧版 | 4.7s | 20 | ⚠️ 通过(有告警) · 手工装配 | 2026-10-24 · 4f6a854a |
49
- | `0.2.0-rc.2` | 旧版 | 4.9s | 20 | ⚠️ 通过(有告警) · 手工装配 | 2026-10-24 · 4f6a854a |
50
- | `0.2.1-alpha.1` | 新版 | 5.1s | 20 | ⚠️ 通过(有告警) · 手工装配 | 2026-10-24 · 4f6a854a |
51
- | `0.2.1-rc.2` | 新版 | 5.3s | 20 | ⚠️ 通过(有告警) · 手工装配 | 2026-10-24 · 4f6a854a |
52
-
53
- </details>
54
-
55
- 完整证据、判定规则与已知偏差见 [doc/SUPPORT-MATRIX.md](doc/SUPPORT-MATRIX.md)。
36
+ <!-- release-test:versions:begin -->
37
+ ## 支持的版本范围
38
+
39
+ **DSH 0.2.1 起(0.2.1 / 0.2.2 共 2 条版本线,含预发布)** — 4 个声明版本尚未全部通过(最近验证 2026-10-24)。
40
+
41
+ <details>
42
+ <summary>逐版本实测明细(点开查看)</summary>
43
+
44
+ | DSH 版本 | 界面 | 启动 | 硬检查 | 结论 | 最近验证 |
45
+ | --- | --- | --- | --- | --- | --- |
46
+ | `0.2.1-rc.2` | 新版 | 16.9s | 20 | ⚠️ 通过(有告警) · 手工装配 | 2026-10-24 · 91642fa2 |
47
+ | `0.2.1-rc.5` | 新版 | — | — | — | 未验证 |
48
+ | `0.2.2-rc.1` | 新版 | — | — | — | 未验证 |
49
+ | `0.2.2-rc.2` | 新版 | — | — | — | 未验证 |
50
+
51
+ </details>
52
+
53
+ 完整证据、判定规则与已知偏差见 [doc/SUPPORT-MATRIX.md](https://github.com/mr-mihu/dsh-dbhub-live/blob/dev/doc/SUPPORT-MATRIX.md)。
56
54
  <!-- release-test:versions:end -->
57
55
 
58
56
  ## 安装
@@ -73,7 +71,7 @@ npx @deepseek-ai/dsh plugin --profile desktop add dsh-dbhub-live
73
71
 
74
72
  装完 web 端**重启 `dsh web`**,打开 **设置 → DBHub 数据库工具** 即可看到配置页(也有左侧栏快捷入口和插件页那行的「配置」,三个入口是同一个页面)。
75
73
 
76
- > 环境要求:`dsh` 0.1.5 起的全部版本线(含 0.2.x);**新界面需要 0.2.1+**(含 0.2.1 的预发布版)——宽屏整页、页头版本与升级提示、行内只读开关都只在 0.2.1 及以上出现;**0.1.x 仍受支持,但保持旧版布局与行为**。建议本机有 Node.js ≥ 18(用于自动安装 dbhub)。**只读模式与 SSH 隧道需要 dbhub 1.4.0+**,插件会自动检测并给出升级指引。每个版本的实测记录见 [doc/SUPPORT-MATRIX.md](doc/SUPPORT-MATRIX.md)(由发布测试自动生成,发版前必跑)。
74
+ > 环境要求:**DSH 0.2.1 或更高版本**(含 0.2.1 的预发布版;**0.1.x 与 0.2.0 不再支持**)。宿主版本过旧时插件不会变成空白:配置页顶部会显示一条中英双语提示(写明所需版本与当前版本),工具、连接与设置照常可用——但请尽快升级 DSH。建议本机有 Node.js ≥ 18(用于自动安装 dbhub)。**只读模式与 SSH 隧道需要 dbhub 1.4.0+**,插件会自动检测并给出升级指引。每个版本的实测记录见 [doc/SUPPORT-MATRIX.md](https://github.com/mr-mihu/dsh-dbhub-live/blob/dev/doc/SUPPORT-MATRIX.md)(由发布测试自动生成,发版前必跑)。
77
75
 
78
76
  ## 更新
79
77
 
@@ -87,9 +85,9 @@ dsh plugin --profile web up dsh-dbhub-live@<最新版本号>
87
85
 
88
86
  **桌面端**:在 **左侧菜单 → 插件** 页里卸载该插件,再重新安装最新版本(桌面端更新走可视化插件页)。**连接与凭据不会被重装清掉**——它们都在 `~/.dsh/storages/dsh-dbhub-live`,与模块目录无关。
89
87
 
90
- **不用自己记版本号**:配置页页头显示插件名与当前版本,检测到更新时会出现一行低调的「**↑ 可升级到 <latest>**」文字链接,**点一下就会复制** `dsh-dbhub-live@<latest>`;旁边的「检查更新」按钮可以随时手动检查。打开页面时也会自动检查一次(有节流,不会每次打开都打网络)。检查以 **npm registry 为主、GitHub Releases 为兜底**,发布说明见 [GitHub Releases](https://github.com/mr-mihu/dsh-dbhub-live/releases)。
88
+ **不用自己记版本号**:配置页页头显示插件名与当前版本,检测到更新时会出现一行低调的「**↑ 可升级到 <latest>**」文字链接,**点开后在面板里点一下即可复制** `dsh-dbhub-live@<latest>`;旁边的「检查更新」按钮可以随时手动检查。打开页面时也会自动检查一次(有节流,不会每次打开都打网络)。检查以 **npm registry 为主、GitHub Releases 为兜底**,发布说明见 [GitHub Releases](https://github.com/mr-mihu/dsh-dbhub-live/releases)。
91
89
 
92
- > 版本与升级提示属于新界面,需要插件 5.2.0+ 且 DSH 0.2.1+;0.1.x 上不显示(见「配置页」)。
90
+ > 版本与升级提示需要插件 5.2.0+ 且 DSH 0.2.1+(见「配置页」)。
93
91
 
94
92
  ## 快速开始
95
93
 
@@ -105,7 +103,7 @@ AI:…(返回查询结果)
105
103
  <details>
106
104
  <summary>📷 实际效果:向 AI 提问,直接拿到查询结果</summary>
107
105
 
108
- ![向 AI 提问并拿到查询结果](doc/images/chat-query.png)
106
+ ![向 AI 提问并拿到查询结果](https://raw.githubusercontent.com/mr-mihu/dsh-dbhub-live/dev/doc/images/chat-query.png)
109
107
 
110
108
  </details>
111
109
 
@@ -149,11 +147,11 @@ AI:…(返回查询结果)
149
147
  <details>
150
148
  <summary>📷 连接行编辑器:只读模式与 SSH 隧道</summary>
151
149
 
152
- ![连接行与「测试 SSH 隧道」](doc/images/row-editor.png)
150
+ ![连接行与「测试 SSH 隧道」](https://raw.githubusercontent.com/mr-mihu/dsh-dbhub-live/dev/doc/images/row-editor.png)
153
151
 
154
152
  </details>
155
153
 
156
- 同一套组件提供**三种布局**,按承载位与窗口宽度自动切换(新界面需要插件 5.2.0+ 且 **DSH 0.2.1+**;DSH 0.1.x 上保持旧版布局与行为,见本节末尾):
154
+ 同一套组件提供**三种布局**,按承载位与窗口宽度自动切换(需要插件 5.2.0+ 且 **DSH 0.2.1+**,见本节末尾):
157
155
 
158
156
  | 布局 | 进入方式 | 形态 |
159
157
  | --- | --- | --- |
@@ -163,16 +161,17 @@ AI:…(返回查询结果)
163
161
 
164
162
  **页头:版本与升级入口**:标题是插件名,紧跟当前版本(灰色小字);有更新时右侧出现一行低调的「**↑ 可升级到 <latest>**」文字链接,**点开才展开**更新说明——里面是 `dsh-dbhub-live@<latest>`(点击复制)、按部署方式给的更新命令 / 步骤,以及更新说明链接;「↻」按钮随时手动检查一次。打开页面时也会自动检查(有节流)。检查以 npm registry 为主、GitHub Releases 为兜底。**更新是中性提示,不会用报错红。**
165
163
 
166
- - **状态行**:只显示「运行中 / 已停用 · N 个连接」。工具数量、一次性执行模式、dbhub 版本这类实现细节收进页面底部的「ⓘ 说明与技术详情」折叠区,默认不展开。
164
+ - **状态行**:只显示「运行中 / 已禁用 · N 个连接」。工具数量、一次性执行模式、dbhub 版本这类实现细节收进状态行下方的「ⓘ 说明」折叠区,默认不展开。
167
165
  - **连接列表**:按工作区组织。所有工作区都只有一个环境时用平铺卡片;任一工作区有多个环境时按工作区分组折叠(点组头展开,默认展开最近使用的那个)。每个连接只占一行:环境名 · 类型 / 主机 / 端口 / **库名** · **只读开关** · `source` 句柄(点它即复制) · 测试 / 修改 / 删除。窄窗口下先截断主机端口,**库名始终可见**。
168
- - **设置区(DSH 主体样式,刻意弱化)**:宽屏把四项收成**一行紧凑设置条**——`插件设置 · 启用插件[启用] · 侧边栏入口[显示] · 访问策略[开启] · 自动更新[7]天[保存] · ⓘ说明`;弹窗与手机是紧凑设置行。字号与开关都收小,让**连接列表**成为页面主体。控件用 DSH 自己的开关(跟随主题 / 皮肤,带汉字状态词);解释性长文字都在「ⓘ 说明与技术详情」里。
166
+ - **设置区(DSH 主体样式,刻意弱化)**:宽屏把四项收成**一行紧凑设置条**——`插件设置 · 启用插件[启用] · 侧边栏入口[显示] · 访问策略[开启] · 自动更新[7]天[保存] · ⓘ说明`;弹窗与手机是紧凑设置行。字号与开关都收小,让**连接列表**成为页面主体。控件用 DSH 自己的开关(跟随主题 / 皮肤,带汉字状态词);解释性长文字都在「ⓘ 说明」里。
169
167
  - **只读开关(三种布局都在外面)**:每条连接行的数据源旁边就是该环境的「只读」小开关,点一下即改(开锁图标 + 「可写」= 可写;闭锁图标 + 「只读」= 已只读),不必打开编辑表单;弹窗 / 手机同样可用。编辑表单里的勾选也保留,两处是同一个状态。**连接串本身不再带锁形图标**——过去容易被误读成"需要密码",也和只读锁冲突。
170
- - **增删改**:行内 ✏ 改连接串或环境名;「+ 添加连接」支持中文环境名。**同名环境保存前会先问「是否覆盖」**,取消则一个字都不写。
171
- - **连接测试**:按该环境**真正会被使用的有效连接**(含只读 / 隧道)执行 `SELECT 1`,测试中显示已耗时。结果是一次性反馈,不持久化、不改变连接状态。
168
+ - **增删改**:行内 ✏ 改连接串或环境名;「+ 添加连接」支持中文环境名。**同名环境保存前会先问「是否覆盖」**,取消则一个字都不写。**删除也要先确认**(这条不可撤销,取消同样零写入)。
169
+ - **写失败一定看得见**:保存 / 开关 / 删除被拒(401、离线、宿主拒绝)时页面上会出现一行错误提示,**不会**再显示「已保存 ✓」,改好后再点一次就是重试;读取不到插件状态时也会给出错误行 + 「重试」,不会悄悄变成"没有连接"。「自动更新间隔」只接受 0–365 的整数天,留空或填小数会当场提示、不发请求。
170
+ - **连接测试**:按该环境**真正会被使用的有效连接**(含只读 / 隧道)执行 `SELECT 1`,测试中显示已耗时。结果是一次性反馈,不持久化、不改变连接状态。命令没能送到宿主(例如桥接 401 / 离线)时会立刻说「测试命令未能送达宿主」,而不是等 30 秒后报「测试超时」。
172
171
 
173
172
  **「在侧边栏显示入口」不是禁用插件**:关掉它只是隐藏左侧栏的快捷入口,插件照常工作、模型照常可以查询;想整体停用才用「禁用」(禁用后入口一律隐藏)。无论哪种状态,**设置页与插件页那行的「配置」始终能进**,随时可以撤销。
174
173
 
175
- **DSH 0.1.x 的兼容路径**:页头版本 / 升级提示、宽屏整页、行内只读开关与开关样式的设置区都只在 **DSH 0.2.1+**(含预发布版)渲染;0.1.x 上插件保持原有布局与行为,**不显示升级提示**——这是刻意保留的,不是缺陷。
174
+ **DSH 版本过低时**:配置页顶部会多出一条警告(写明「需要 DSH 0.2.1 或更高版本」与本机版本),页面、连接、只读开关与工具全部照常工作——这不是"插件坏了",只需升级 DSH。
176
175
 
177
176
  ## 🔒 只读模式与 SSH 隧道
178
177
 
@@ -238,14 +237,18 @@ dsh plugin --profile desktop remove dsh-dbhub-live
238
237
  | 只读 / SSH 控件是灰的,或提示 dbhub 版本过旧 | 两项都需要 dbhub 1.4.0+:升级 dbhub,或删除 `~/.dsh/storages/dsh-dbhub-live` 让插件自动装一个满足要求的版本。 |
239
238
  | 连接测试失败,看不出原因 | 读提示的**末尾**(诊断给的是 dbhub 输出的尾部,会标出【SSH 层】/【数据库层】/【插件层】)。**报【数据库层】就说明隧道已经通了**;有隧道时先点「测试 SSH 隧道」把 SSH 层单独验一遍。 |
240
239
  | 看不到配置页 | 确认插件已安装:web 端重启过 `dsh web`;桌面端在「插件」页该行点「配置」,或从左侧菜单的 DBHub 入口进。配置页只在 Web / 桌面界面显示,终端环境下不影响工具使用。 |
241
- | 升级到新版本后仍是旧界面 | 新界面需要 **DSH 0.2.1+**(含其预发布版)且插件 5.2.0+;升级 dsh 后重启。DSH 0.1.x 上插件有意保持旧布局与行为,也不显示升级提示。 |
240
+ | 配置页顶部提示「DSH 版本过旧」 | 插件最低支持 **DSH 0.2.1**(含其预发布版);升级 dsh 后重启即可。升级前工具与页面仍可用,提示只是提醒。 |
242
241
  | 页头没有出现「升级到 …」提示 | 可能已是最新版本,或这次检查没连上 npm registry / GitHub Releases;点「检查更新」重试即可。检查失败只是提示,不影响插件功能。 |
243
242
  | 桌面端怎么升级 | 在 **左侧菜单 → 插件** 页卸载后重装最新版本;连接与凭据在 `~/.dsh/storages/dsh-dbhub-live`,重装后仍在。 |
244
- | 升级 dsh 后提示插件与本版不兼容 | 升级插件到 5.0.0+(同时支持 0.1.x 与 0.2.x);用 `allow-version` 强行豁免没有意义。 |
243
+ | 升级 dsh 后提示插件与本版不兼容 | 先确认 dsh ≥ 0.2.1,再把插件升级到最新版;用 `allow-version` 强行豁免没有意义。 |
245
244
  | 不希望自动更新 dbhub | 配置页「自动更新间隔(天)」填 `0` 保存,或设环境变量 `DSH_DBHUB_UPDATE_DAYS=0`。 |
246
245
 
247
246
  **环境变量**(仅这两项,不在 UI 暴露):`DSH_DBHUB_PACKAGE`(自动安装的包名,默认 `@bytebase/dbhub`)、`DSH_DBHUB_UPDATE_DAYS`(自动更新间隔种子,默认 `7`,被配置页保存的值覆盖)。
248
247
 
248
+ ## 版本历史
249
+
250
+ 每个正式版本的变更摘要见 [doc/CHANGELOG.md](https://github.com/mr-mihu/dsh-dbhub-live/blob/dev/doc/CHANGELOG.md);逐版本的发布说明在 [GitHub Releases](https://github.com/mr-mihu/dsh-dbhub-live/releases)。
251
+
249
252
  ## 许可证
250
253
 
251
254
  [MIT](./LICENSE)
package/lib/adhoc.mjs CHANGED
@@ -18,12 +18,14 @@
18
18
  // file holds placeholders only; the real secrets
19
19
  // ride the CHILD's environment (see toml.mjs).
20
20
 
21
- import { DATA_DIR, connLabel, scrubSecrets, normalizeSshOptions, expandHome, withDefaultSshKey, DEFAULT_SSH_KEY_PATH } from './config.mjs'
21
+ import { DATA_DIR, connLabel, scrubSecrets, normalizeSshOptions, expandHome, withDefaultSshKey, DEFAULT_SSH_KEY_PATH, warnOnce } from './config.mjs'
22
22
  import { buildSpawnArgv, resolveDbhubExe } from './runtime.mjs'
23
- import { createMcpClient, disabledMessage } from './mcp.mjs'
23
+ import { createMcpClient, disabledMessage, MCP_INIT_TIMEOUT_MS, MCP_CALL_TIMEOUT_MS } from './mcp.mjs'
24
24
  import { buildDbhubToml, writeTempToml, removeTempToml } from './toml.mjs'
25
25
  import { detectDbhubCapabilities } from './capability.mjs'
26
+ import { readPluginVersion } from './meta.mjs'
26
27
  import { currentT } from './i18n.mjs'
28
+ import { StringDecoder } from 'node:string_decoder'
27
29
  import { existsSync } from 'node:fs'
28
30
  import { connect } from 'node:net'
29
31
  import * as state from './state.mjs'
@@ -70,6 +72,14 @@ export function secretsOf(conn) {
70
72
  export const PROBE_TIMEOUT_MS = 25000
71
73
  export const PROBE_TIMEOUT_SSH_MS = 45000
72
74
 
75
+ /**
76
+ * How long a finished call waits for the child to actually exit before it stops
77
+ * waiting and simply warns. `waitForExit` never settles when a descendant escaped
78
+ * the managed process group (the host documents that as out of its control), and
79
+ * an unbounded await there would hang the call the timeouts above just bounded.
80
+ */
81
+ export const EXIT_WAIT_MS = 5000
82
+
73
83
  /** Host-side probe cap for one connection (tunnelled connections get more). */
74
84
  export function hostTimeoutFor(conn) {
75
85
  return needsToml(conn) && normalizeConn(conn).ssh ? PROBE_TIMEOUT_SSH_MS : PROBE_TIMEOUT_MS
@@ -383,11 +393,18 @@ export async function probeSshTunnel(subprocess, ssh, timeoutMs) {
383
393
 
384
394
  /**
385
395
  * Run ONE MCP tool call against a disposable dbhub process.
396
+ *
397
+ * Both requests carry a request-level budget (see lib/mcp.mjs): `exec.signal` only
398
+ * fires on a user cancel / turn interruption, so a dbhub that is alive but silent
399
+ * would otherwise hold this call — and the whole conversation round — forever.
400
+ * `exec.mcpTimeoutMs` shrinks the CALL budget (the robustness test proves the
401
+ * timeout path in milliseconds); production passes no value.
402
+ *
386
403
  * @param subprocess - host subprocess service.
387
404
  * @param conn - `{ dsn, ro, ssh }` (a bare DSN string means "no options").
388
405
  * @param rawName - the MCP tool name (`execute_sql` / `search_objects`).
389
406
  * @param mcpArgs - the tool arguments.
390
- * @param exec - `{ signal, agent? }`.
407
+ * @param exec - `{ signal, agent?, mcpTimeoutMs? }`.
391
408
  * @param label - optional pre-localized header line.
392
409
  * @returns `{ ok, text }` (already scrubbed and metadata-labelled).
393
410
  */
@@ -422,7 +439,14 @@ export async function runAdhoc(subprocess, conn, rawName, mcpArgs, exec, label)
422
439
  const built = buildDbhubToml({ dsn: c.dsn, ro: c.ro, ssh: c.ssh })
423
440
  tomlPath = writeTempToml(built.text)
424
441
  // Secrets travel in the CHILD's environment; the file only holds `${VAR}`.
425
- childEnv = { ...process.env, ...built.env }
442
+ // ONLY the generated names: spreading `process.env` here would hand the
443
+ // spawn a scrubbed-away ambient environment all over again (the host
444
+ // subprocess service strips `*KEY|PASSWORD|SECRET|TOKEN*` and `DSH_*` from
445
+ // the inherited env, and merges an explicit `env` AFTER that scrub), so the
446
+ // child — and every third-party package inside dbhub — could read the
447
+ // agent's own API keys. Absent names simply resolve from the scrubbed
448
+ // parent env the service provides.
449
+ childEnv = { ...built.env }
426
450
  argv = ['--transport', 'stdio', '--config', tomlPath]
427
451
  } catch (e) {
428
452
  removeTempToml(tomlPath)
@@ -447,17 +471,30 @@ export async function runAdhoc(subprocess, conn, rawName, mcpArgs, exec, label)
447
471
  return { ok: false, text: currentT('result.dbhubSpawnFailed', { msg: String((e && e.message) || e) }) }
448
472
  }
449
473
  let stderrTail = ''
474
+ const callTimeoutMs = Number.isFinite(exec && exec.mcpTimeoutMs) && exec.mcpTimeoutMs > 0
475
+ ? exec.mcpTimeoutMs
476
+ : MCP_CALL_TIMEOUT_MS
450
477
  try {
478
+ // Decode across chunk boundaries: dbhub's banner and connection errors are
479
+ // often non-ASCII, and a split multibyte character would surface as U+FFFD in
480
+ // the diagnostic tail the user has to read.
481
+ const stderrDecoder = new StringDecoder('utf8')
451
482
  const client = createMcpClient(handle, (chunk) => {
452
- stderrTail = (stderrTail + String(chunk)).slice(-2000)
483
+ const piece = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk)
484
+ stderrTail = (stderrTail + piece).slice(-2000)
453
485
  })
454
486
  await client.request('initialize', {
455
487
  protocolVersion: '2025-03-26',
456
488
  capabilities: {},
457
- clientInfo: { name: 'dsh-dbhub-adhoc', version: '3.0.0' },
458
- })
489
+ // dbhub logs this in its own diagnostics. The literal '3.0.0' that used to
490
+ // sit here named a version this plugin never was — a report from a user on
491
+ // 5.x pointed the maintainer at the wrong code. `meta` is a LEAF, so reading
492
+ // the installed version here cannot create a cycle; a detached copy of the
493
+ // package (no readable package.json) degrades to '0.0.0'.
494
+ clientInfo: { name: 'dsh-dbhub-adhoc', version: readPluginVersion() || '0.0.0' },
495
+ }, { timeoutMs: MCP_INIT_TIMEOUT_MS })
459
496
  client.notify('notifications/initialized')
460
- const res = await client.request('tools/call', { name: rawName, arguments: mcpArgs })
497
+ const res = await client.request('tools/call', { name: rawName, arguments: mcpArgs }, { timeoutMs: callTimeoutMs })
461
498
  const textOf = (r) => {
462
499
  if (r && Array.isArray(r.content)) {
463
500
  const parts = []
@@ -469,16 +506,45 @@ export async function runAdhoc(subprocess, conn, rawName, mcpArgs, exec, label)
469
506
  // Zero-knowledge surface: the header carries metadata only (host/port/db),
470
507
  // and every line — including dbhub's own error text — is scrubbed before
471
508
  // it can reach the model (the SSH password / passphrase included).
472
- const prefix = (label !== undefined && label !== null
509
+ // The prefix itself goes through the same scrubber: it is built from
510
+ // `connLabel` AND from a caller-supplied `label`, and a caller that ever
511
+ // pastes a DSN into it must not bypass the gate the body passes through.
512
+ const header = label !== undefined && label !== null
473
513
  ? String(label)
474
- : currentT('label.adhocConn', { dsn: connLabel(c.dsn) })) + '\n'
514
+ : currentT('label.adhocConn', { dsn: connLabel(c.dsn) })
515
+ const prefix = scrubSecrets(header, c.dsn, extraSecrets) + '\n'
475
516
  if (res && res.isError) return { ok: false, text: prefix + scrubSecrets(textOf(res), c.dsn, extraSecrets) }
476
517
  if (res && res.structuredContent !== undefined) {
477
518
  return { ok: true, text: prefix + scrubSecrets(JSON.stringify(res.structuredContent, null, 2), c.dsn, extraSecrets) }
478
519
  }
479
520
  return { ok: true, text: prefix + scrubSecrets(textOf(res), c.dsn, extraSecrets) }
480
521
  } catch (e) {
481
- let detail = String((e && e.message) || e)
522
+ const code = e && e.code
523
+ let detail
524
+ if (code === 'MCP_TIMEOUT' || code === 'MCP_TOO_LARGE') {
525
+ // Kill the wedged process BEFORE the report is assembled: `finally` runs
526
+ // after this frame, and a dbhub that ignored one request will ignore the
527
+ // next. The temp TOML is still removed by `finally`.
528
+ try {
529
+ handle.terminate()
530
+ } catch (err) {
531
+ /* ignore */
532
+ }
533
+ }
534
+ if (code === 'MCP_TIMEOUT') {
535
+ const sec = Math.max(1, Math.round((Number(e.timeoutMs) || 0) / 1000))
536
+ detail = currentT('result.mcpTimeout', { sec: String(sec) })
537
+ } else if (code === 'MCP_TOO_LARGE') {
538
+ const limitBytes = Number(e.limitBytes) || 0
539
+ detail = currentT('result.mcpTooLarge', {
540
+ what: currentT(e.limitKind === 'line' ? 'result.mcpTooLargeLine' : 'result.mcpTooLargeTotal'),
541
+ limit: String(Math.round((limitBytes / (1024 * 1024)) * 10) / 10) + ' MiB',
542
+ })
543
+ } else {
544
+ detail = String((e && e.message) || e)
545
+ }
546
+ // Never echo a query/params/DSN: only the localized sentence plus (already
547
+ // length-capped) stderr, which the scrubber below rewrites.
482
548
  if (stderrTail) detail += '\n[dbhub stderr] ' + stderrTail
483
549
  return { ok: false, text: scrubSecrets(detail, c.dsn, extraSecrets) }
484
550
  } finally {
@@ -487,10 +553,40 @@ export async function runAdhoc(subprocess, conn, rawName, mcpArgs, exec, label)
487
553
  } catch (e) {
488
554
  /* ignore */
489
555
  }
556
+ let exited
557
+ // The host contract answers `false` when the signal was already aborted and
558
+ // the process is STILL alive. The return value used to be dropped, so the
559
+ // temp config was deleted as if the child had exited (Windows: EBUSY) and
560
+ // the only trace was a once-per-process warnOnce.
561
+ //
562
+ // The wait itself is bounded: a descendant that escaped the managed process
563
+ // group can make `waitForExit` never settle, which would hang the very call
564
+ // this batch set out to make bounded. A timeout here is treated exactly like
565
+ // `false` (try once more, then warn).
566
+ let exitTimer = null
567
+ const exitWaitMs = Number.isFinite(exec && exec.exitWaitMs) && exec.exitWaitMs > 0
568
+ ? exec.exitWaitMs
569
+ : EXIT_WAIT_MS
490
570
  try {
491
- await handle.waitForExit(exec.signal)
571
+ exited = await Promise.race([
572
+ Promise.resolve(handle.waitForExit(exec.signal)).catch(() => undefined),
573
+ new Promise((resolve) => {
574
+ exitTimer = setTimeout(() => resolve('timeout'), exitWaitMs)
575
+ }),
576
+ ])
492
577
  } catch (e) {
493
578
  /* ignore */
579
+ } finally {
580
+ if (exitTimer) clearTimeout(exitTimer)
581
+ }
582
+ if (exited === false || exited === 'timeout') {
583
+ try {
584
+ handle.terminate()
585
+ } catch (e) {
586
+ /* ignore */
587
+ }
588
+ // No path, no DSN: just the fact (said once per process).
589
+ warnOnce('adhoc-exit-pending', currentT('result.processStillRunning'))
494
590
  }
495
591
  // Best effort: the temp file holds no secret, but it must not accumulate.
496
592
  removeTempToml(tomlPath)