dsh-dbhub-live 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +104 -35
- package/README.en.md +35 -31
- package/README.md +35 -31
- package/doc/REQUIREMENTS.md +56 -10
- package/lib/bridge.mjs +125 -0
- package/lib/client.js +1188 -427
- package/lib/config.mjs +250 -12
- package/lib/i18n.mjs +96 -28
- package/lib/index.mjs +371 -91
- package/lib/mcp.mjs +175 -27
- package/lib/options.mjs +77 -11
- package/lib/tools.mjs +420 -150
- package/package.json +18 -6
- package/scripts/desensitize-check.mjs +324 -0
- package/scripts/desensitize.config.mjs +237 -0
- package/scripts/render-check.mjs +843 -0
- package/test/auto-dsn.test.mjs +126 -0
- package/test/bridge.test.mjs +185 -0
- package/test/client-format.test.mjs +352 -96
- package/test/i18n.test.mjs +3 -3
- package/test/init.test.mjs +2 -2
- package/test/options.test.mjs +123 -39
- package/test/resolve-workspace.test.mjs +115 -0
- package/test/row-cache.test.mjs +138 -0
- package/test/util.test.mjs +146 -12
- package/test/zero-knowledge.test.mjs +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**简体中文** · [English](README.en.md)
|
|
4
4
|
|
|
5
|
-
> 让 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/DeepSeek-Harness) 直接、安全地操作数据库:**凭据零知识**(密码永不经模型)+ **一次性进程执行**(无常驻服务、天然并发与多实例安全)+ 按工作区×环境的连接管理 +
|
|
5
|
+
> 让 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/DeepSeek-Harness) 直接、安全地操作数据库:**凭据零知识**(密码永不经模型)+ **一次性进程执行**(无常驻服务、天然并发与多实例安全)+ 按工作区×环境的连接管理 + 浏览器配置页。
|
|
6
6
|
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
[](#安装)
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
- **一次性进程执行** — 没有常驻 dbhub 服务:每次调用 spawn 一条独立进程执行完即回收。单条查询挂掉只影响它自己;多任务并行、多个 DSH 实例同时跑互不干扰(无共享端口、无互杀)。
|
|
19
19
|
- **工具声明恒定 4 个** — `dbhub_configure` / `dbhub_list_sources` / `dbhub_execute_sql` / `dbhub_search_objects`,环境再多也不膨胀模型上下文。
|
|
20
20
|
- **按工作区 × 环境管理连接** — 一个工作区可配多个环境(default / prod / dev / test…),通过 `source` 值区分,互不混淆。
|
|
21
|
-
- **鉴权失败闭环** —
|
|
21
|
+
- **鉴权失败闭环** — 凭据或连接信息不对时,工具给出明确指引;模型引导你在界面更新密码(不会向模型索要密码),或你直接在设置卡片修改。**配置时自动试连**:说的是非敏感连接信息会先在宿主侧实测——可连通就直接生效(免密码库零输入),只差密码时弹窗只问「账号(未知时)+ 密码」,不再让你重选输入方式;若试连明确报“空账号被拒”,账号为必填(不再提供「留空(使用空账号)」选项),避免把同一个必失败的配置再存回去。
|
|
22
22
|
- **启用 / 禁用开关** — 关闭后所有 dbhub 工具立即返回「插件已禁用」友好提示,无需重启。
|
|
23
|
-
-
|
|
23
|
+
- **浏览器配置页** — 三个入口同一个页面:**设置 → DBHub 数据库工具**(一级设置页,推荐)、**左侧栏「DBHub 数据库工具」快捷入口**(可在设置页用开关关闭)、插件页 dsh-dbhub-live 那行的「配置」。实时展示状态徽章、工具数与环境数,并提供启用/禁用开关、连接增删改查、**环境改名**与连接测试。**连接列表按工作区组织**:所有工作区都只有一个环境时用平铺卡片(最多 3 列,一眼看全);任一工作区有多个环境时按工作区分组折叠,**点组头展开/收起**,默认展开你最近使用的那个工作区,另有「全部展开/折叠」。每个连接**只占一行**(环境 · 🔒 类型/主机/端口/**库名** · source 句柄 · 图标操作):窄窗口下会先截断主机/端口,**库名始终可见**;行内操作是 ⚡ 测试 / ✏ 修改 / 🗑 删除 三个小图标,不再用文字按钮占半行;`source` 句柄做成一个虚线小片(显示 `…` + 尾段,如 `…1a2b3c`),点它即复制、悬浮看全文(它就是让模型定位这条连接的句柄,不是复制整行连接串)。连接「来源」(手填 / 扫描 / 复制 / 自动发现)不占行宽,悬浮在环境名上即可看到(自动发现且未保存的会明确标出)。自动更新间隔与侧边栏开关收进「⚙ 设置」条,添加连接也默认收起(分组头右上角「+ 环境」可直接在该工作区新增,工作区自动带入)。配色全部走 DSH 主题令牌,**跟随主题/皮肤与深色模式,包括半透明背景的主题**。写入前会拦截覆盖:**同名环境保存前先问「是否覆盖」**(取消则一个字都不写、表单原样留着让你改名),把环境改名落到已存在的环境时同样会问。
|
|
24
24
|
- **开箱即用** — 未安装 `dbhub` 时首次执行自动安装,之后按设置间隔自动更新。
|
|
25
25
|
|
|
26
26
|
## 支持的数据源
|
|
@@ -30,6 +30,7 @@ MySQL · PostgreSQL · MariaDB · SQLite · SQL Server
|
|
|
30
30
|
## 环境要求
|
|
31
31
|
|
|
32
32
|
- DeepSeek Harness 的 `dsh` CLI(`dsh web` 负责 GUI 运行)
|
|
33
|
+
- 兼容 **dsh 0.1.5 起的所有版本线**(含 0.2.x):配置页在 0.1.x 走设置命名空间、在 0.1.7+/0.2.x 走插件自带的鉴权 HTTP 桥(`/api/dsh-dbhub-live/*`),两条通道展示同一份状态
|
|
33
34
|
- 推荐本机有 Node.js ≥ 18(含 `npm`)——首次执行会自动安装 `dbhub`
|
|
34
35
|
|
|
35
36
|
## 安装
|
|
@@ -42,10 +43,12 @@ dsh plugin --profile web add dsh-dbhub-live
|
|
|
42
43
|
npx @deepseek-ai/dsh plugin --profile web add dsh-dbhub-live
|
|
43
44
|
|
|
44
45
|
# 更新到指定版本(推荐写明当前发布的版本号,避免 pnpm 判「Already up to date」跳过)
|
|
45
|
-
dsh plugin --profile web update dsh-dbhub-live@
|
|
46
|
+
dsh plugin --profile web update dsh-dbhub-live@5.0.0
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
安装后**重启 `dsh web`** 生效(重启后到
|
|
49
|
+
安装后**重启 `dsh web`** 生效(重启后到 **设置 → DBHub 数据库工具** 可看到配置页)。
|
|
50
|
+
|
|
51
|
+
> 从 4.x 升级:dsh 0.2.x 删除了旧版配置页依赖的 settings 命名空间接口,4.x 在 0.2.x 上会被 dsh 拒绝加载。5.0.0 起同时支持新旧两条 dsh 版本线,升级插件后配置页与既有连接、既有选项都会保留。
|
|
49
52
|
|
|
50
53
|
## 快速开始
|
|
51
54
|
|
|
@@ -66,9 +69,9 @@ dbhub_list_sources
|
|
|
66
69
|
|
|
67
70
|
| 工具 | 说明 |
|
|
68
71
|
| --- | --- |
|
|
69
|
-
| `dbhub_configure(workspace?, env?, type?, host?, port?, database?, user?)` | 为工作区配置/持久化数据库连接。**不接受 dsn 参数**——密码/连接串一律在界面输入(不经过模型);type/host/port/database/user
|
|
70
|
-
| `dbhub_list_sources()` |
|
|
71
|
-
| `dbhub_execute_sql(source, sql)` | 在指定数据源上执行 SQL;`source` 见 `dbhub_list_sources
|
|
72
|
+
| `dbhub_configure(workspace?, env?, renameFrom?, copyFrom?, type?, host?, port?, database?, user?)` | 为工作区配置/持久化数据库连接。**不接受 dsn 参数**——密码/连接串一律在界面输入(不经过模型);type/host/port/database/user 可作为非敏感预填。**预填够用时插件会先自动试连**:可连通 → 直接保存并返回 source 值;提示要密码 → 弹出最小「账号(未知时)+ 密码」输入框;信息不全或其它失败 → 才弹出完整方式选择。**改名**:`env=新名` + `renameFrom=旧名`(连接与凭据原样保留,可用于 `default` → `prod` 或中英文互改;目标已存在时先请用户确认)。**跨工作区复制**:`copyFrom=其他工作区的 source 值` + `env=本工作区环境名`,插件在宿主侧复制连接,密码不经模型。 |
|
|
73
|
+
| `dbhub_list_sources()` | 列出全部连接源,**按工作区分组**并标出【当前工作区】:仅元数据(类型/主机/端口/库)+ 来源徽章 + 对应 **source 值**。 |
|
|
74
|
+
| `dbhub_execute_sql(source, sql)` | 在指定数据源上执行 SQL;`source` 见 `dbhub_list_sources`。**每个 source 只属于一个工作区**,优先用当前工作区的;要复用其他工作区的连接,先用 `dbhub_configure` 的 `copyFrom` 复制到当前工作区,不要直接拿别的工作区的 source 查询。每次调用为独立一次性连接,多语句用 `;` 分隔。 |
|
|
72
75
|
| `dbhub_search_objects(source, object_type, ...)` | 在指定数据源搜索数据库对象(表/视图/列/索引等)。 |
|
|
73
76
|
|
|
74
77
|
> **安全**:模型无法通过本插件拿到任何密码——结果与列表只标注 `mysql://host:3306/db` 这类元数据;dbhub 的报错文本在返回前也会被清洗。查询失败时按提示到界面更新密码即可。
|
|
@@ -77,44 +80,44 @@ dbhub_list_sources
|
|
|
77
80
|
|
|
78
81
|
### 配置方式
|
|
79
82
|
|
|
83
|
+
0. **自动试连(默认优先)** — 你在对话里说出连接信息(如「mysql 198.51.100.1:3307/mydb」)后,插件先用这些非敏感信息实测:能连上就直接保存(数据库不需要密码时全程零输入);提示需要密码 → 弹窗只问「账号(未知时)+ 密码」,其余信息自动沿用,并附失败原因与该连接信息不对时的「改用其他方式」入口;若试连明确报“空账号被拒”(如 `Access denied for user ''`),账号为必填(不提供「留空」选项);信息不完整或其它原因失败,才进入下面三种方式。若你说的是另一个库/主机/端口(与已存连接不同),插件会按新信息重新试连判断。
|
|
80
84
|
1. **显式 DSN** — 在界面输入完整连接串,如 `mysql://user:pass@host:3306/db`。
|
|
81
|
-
2. **填写分项** — 在界面按类型 / 主机 / 端口 / 账号 / 密码 /
|
|
85
|
+
2. **填写分项** — 在界面按类型 / 主机 / 端口 / 账号 / 密码 / 库名依次填写;已从对话中得知的字段会自动带入,只补缺项即可。
|
|
82
86
|
3. **授权扫描** — 授权后扫描项目配置文件(`.env`、`application*.yml`、`docker-compose`、`jdbc.properties` 等),列出候选(只显示主机/端口/库,密码不显示,由插件直接读取)供你确认。
|
|
83
87
|
|
|
84
88
|
工作区若已有 `mise env` 或 `.env`(`DSN` / `DB_*`),插件会自动发现,无需手动配置。
|
|
85
89
|
|
|
86
|
-
##
|
|
87
|
-
|
|
88
|
-
设置 → 插件 → dsh-dbhub-live:插件会把状态与配置实时同步到 Web 设置面板(仅 `dsh web` 端可见)。卡片为**整行折叠**样式(与其它插件设置卡一致):
|
|
90
|
+
## 配置页
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
三个入口,同一个页面、同一份状态:
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
1. **设置 → DBHub 数据库工具**(推荐):插件在设置面板注册的一级页面,任何部署都有。
|
|
95
|
+
2. **左侧栏「DBHub 数据库工具」**:快捷入口,默认开启;不想占用左侧栏时,在设置页把「在侧边栏显示入口」关掉即可(即时生效,无需重启/刷新)。
|
|
96
|
+
3. **插件 → dsh-dbhub-live → 配置**:dsh 0.1.6 起官方「插件」页给每个插件行提供的配置控件(该页由官方插件管理器提供,缺失时用前两个入口)。
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
> 关闭侧边栏入口**不会**让你失去配置页:设置页与插件页那行的「配置」始终可用。
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
配置页全部文案(名称、状态、配置项、按钮、连接行)跟随 dsh 界面语言(中 / 英,设置 → 通用 → 语言)切换;模型的报错/反馈与宿主日志同样跟随。
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
- 工具声明:`4 个(固定)`;环境:`N 个 · 已保存 M`。
|
|
100
|
-
- 最近错误(异常时红色展示)。
|
|
102
|
+
**状态**:状态徽章(🟢 运行中 / ⚪ 已禁用)、**启用/禁用开关**、工作模式(一次性连接)、工具声明(`4 个(固定)`)、环境数(`N 个 · 已保存 M`)、最近错误(异常时红色展示)。
|
|
101
103
|
|
|
102
104
|
**配置**(编辑后点「保存配置」即时生效并持久化):
|
|
103
105
|
|
|
104
106
|
| 参数 | 说明 | 默认 |
|
|
105
107
|
| --- | --- | --- |
|
|
106
108
|
| 自动更新间隔(天) | 自动更新 dbhub 的间隔天数,`0` 关闭 | `7` |
|
|
109
|
+
| 在侧边栏显示入口 | 是否在左侧栏显示「DBHub 数据库工具」快捷入口;改动即时生效 | 开 |
|
|
107
110
|
|
|
108
|
-
|
|
111
|
+
优先级:**配置页保存的设置 > 进程环境变量(默认值种子)> 内置默认**;从 0.1.x 老版本升级时,原先存在设置文档里的选项会被读进来作为更低一层(不会被静默重置)。在 dsh 0.1.7 及以后,这两项同时是本插件的声明式配置(volatile Config):也可以直接写进 `cordis.patch.yml` 的该插件行,或在官方「插件」页的表单里改——**任一处改动都会被采纳,本插件配置页会显示同一个值**(配置页保存也会同步写回该配置,两边不会打架)。自动安装包不在 UI 中(`DSH_DBHUB_PACKAGE` 环境变量单独控制,默认 `@bytebase/dbhub`)。
|
|
109
112
|
|
|
110
113
|
**工作区连接**:
|
|
111
114
|
|
|
112
|
-
- 列出每个工作区 × 环境的连接:工作区名、环境名、**source 值**(模型调用 `dbhub_execute_sql` 时填这个,等宽字体显示)、**连接元数据**(🔒 `mysql://host:3306/db
|
|
113
|
-
- `已保存`:你配置过(`dbhub_configure`
|
|
115
|
+
- 列出每个工作区 × 环境的连接:工作区名、环境名、**source 值**(模型调用 `dbhub_execute_sql` 时填这个,等宽字体显示)、**连接元数据**(🔒 `mysql://host:3306/db`——不含账号密码,密码永不出现在页面上)、来源徽章(`已保存` / `自动`)与来源明细(`已保存·用户` / `已保存·扫描` / `已保存·复制` / `自动·mise env` / `自动·.env`)。
|
|
116
|
+
- `已保存`:你配置过(`dbhub_configure` 或配置页添加)。
|
|
114
117
|
- `自动`:未保存,来自 `mise env` / `.env` 自动发现——不持久化,随源文件变化;自动发现不对时可直接「修改」为手动配置覆盖。
|
|
115
|
-
-
|
|
118
|
+
- 每行可「测试」(连接探活,见下)、「修改」(改连接串 **或改环境名**;改名连接与凭据原样保留,只填名字不填连接串即为纯改名)或「删除」(仅已保存项)。
|
|
116
119
|
- **连接测试**:点「测试」后 Host 用该环境的真实 DSN 走一次性临时连接(独立 dbhub 进程执行 `SELECT 1`)实测可达性,成功 / 失败即时在行内提示。结果是一次性反馈:不持久化、约 10 秒后自动消失;失败不会标记、限制或改动这条连接,不影响其他环境与查询(慢库 / 不通库最长等待约 30 秒)。
|
|
117
|
-
- **同一工作区可添加多个环境**:表单填「工作区(路径或标题,留空=默认当前工作区)+ 环境名 +
|
|
120
|
+
- **同一工作区可添加多个环境**:表单填「工作区(路径或标题,留空=默认当前工作区)+ 环境名 + 连接串」点「添加连接」。环境名**支持中文**(如 `线上` / `测试`):显示用原名,source 值中的环境段用 `env-<短哈希>` 保证**不同中文名不会撞车**(纯 ASCII 名如 `test` / `dev` 的 source 值保持不变)。
|
|
118
121
|
|
|
119
122
|
## dbhub 环境变量
|
|
120
123
|
|
|
@@ -126,10 +129,10 @@ dbhub_list_sources
|
|
|
126
129
|
## 🔄 dbhub 自动安装与更新
|
|
127
130
|
|
|
128
131
|
- **首次使用自动安装**:本机没有 `dbhub` 时,插件会在第一次执行时自动安装,之后离线也可用。
|
|
129
|
-
-
|
|
132
|
+
- **自动保持更新**:后台静默更新到最新版(间隔见「配置页 → 可配置参数」),失败则沿用现有版本。
|
|
130
133
|
- **不碰你的配置**:通过 PATH / mise 自行安装的 `dbhub` 不会被插件改动。
|
|
131
134
|
|
|
132
|
-
>
|
|
135
|
+
> 更新间隔默认值也可用环境变量播种,见「配置页 → 配置」;一旦在设置卡片保存过,即以设置值为准。
|
|
133
136
|
|
|
134
137
|
## 数据位置
|
|
135
138
|
|
|
@@ -152,13 +155,14 @@ dsh plugin --profile web remove dsh-dbhub-live
|
|
|
152
155
|
| 现象 | 处理 |
|
|
153
156
|
| --- | --- |
|
|
154
157
|
| 首次使用报「无法获取 dbhub」 | 确认本机有 npm 且能联网;离线可手动安装 `dbhub` 并加入 PATH。 |
|
|
155
|
-
| 某个环境的查询报连接被拒 / 认证失败 | 该环境为独立一次性连接:不可达只让该次调用报错,其他环境与调用不受影响。错误末尾会附指引——凭据或连接信息有误时,可让 AI 调用 `dbhub_configure` 引导你在界面更新,或直接在
|
|
156
|
-
| 工具显示「插件已禁用」 | 打开
|
|
157
|
-
|
|
|
158
|
+
| 某个环境的查询报连接被拒 / 认证失败 | 该环境为独立一次性连接:不可达只让该次调用报错,其他环境与调用不受影响。错误末尾会附指引——凭据或连接信息有误时,可让 AI 调用 `dbhub_configure` 引导你在界面更新,或直接在 插件 → dsh-dbhub-live → 配置 中修改。 |
|
|
159
|
+
| 工具显示「插件已禁用」 | 打开 插件 → dsh-dbhub-live → 配置,点击「启用」。 |
|
|
160
|
+
| 看不到配置页 | 确认插件已安装并重启 `dsh web`;配置页只在 Web 设置面板(`dsh web`)显示,在无设置面板的终端环境下不影响工具使用。 |
|
|
161
|
+
| 升级 dsh 后提示插件与本版 dsh 不兼容 | 这是 dsh 的 peer 版本检查:升级插件到 5.0.0 及以上(同时支持 0.1.x 与 0.2.x)。用 `dsh plugin --profile web allow-version` 强行豁免没有意义——4.x 依赖的接口在 0.2.x 已被删除,加载后配置页依然是空的。 |
|
|
158
162
|
| 扫描不到配置文件 | 默认跳过 `node_modules` / `.git` / `target` / `dist` 等目录,可改用「输入 DSN」或「填写分项」。 |
|
|
159
163
|
| 需要自定义 dbhub 版本 | 设置环境变量 `DSH_DBHUB_PACKAGE`(如 `@bytebase/dbhub@1.2.1`)后重启;或删除 `~/.dsh/storages/dsh-dbhub-live` 重新自动安装。 |
|
|
160
|
-
| 不希望自动更新 dbhub |
|
|
161
|
-
| 密码无法在对话中更新(模型问你要密码) | 这是设计如此:模型不应接触密码。让 AI 调用 `dbhub_configure`,在界面弹出的输入框中填密码即可;或自行到
|
|
164
|
+
| 不希望自动更新 dbhub | 配置页「自动更新间隔(天)」填 `0` 并保存;或设置环境变量 `DSH_DBHUB_UPDATE_DAYS=0`。 |
|
|
165
|
+
| 密码无法在对话中更新(模型问你要密码) | 这是设计如此:模型不应接触密码。让 AI 调用 `dbhub_configure`,在界面弹出的输入框中填密码即可;或自行到 插件 → dsh-dbhub-live → 配置 修改。 |
|
|
162
166
|
|
|
163
167
|
## 许可证
|
|
164
168
|
|
package/doc/REQUIREMENTS.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# dsh-dbhub-live 需求事实记录
|
|
2
2
|
|
|
3
3
|
> 本文只记录「产品必须满足什么」的**需求事实**(概念层面),不含实现细节;开发实现说明见 `AGENTS.md`,用户安装使用见 `README.md`。
|
|
4
|
-
> 版本基线:4.0
|
|
4
|
+
> 版本基线:5.0(正式版;4.0 的零知识凭据 + 一次性进程执行架构之上,新增工作区隔离与来源解析、环境改名、设置入口与侧边栏开关,以及 dsh 0.2.x 双栈适配)。
|
|
5
|
+
> 当前迭代:5.0 — dsh 0.2.x 兼容(在 4.1/4.2 能力之上重做浏览器↔宿主通道;见第十节 R56-R58)。
|
|
5
6
|
|
|
6
7
|
## 一、产品定位
|
|
7
8
|
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
|
|
41
42
|
## 六、设置与状态界面
|
|
42
43
|
|
|
43
|
-
- R21.
|
|
44
|
+
- R21. 浏览器端提供配置页:展示启用状态、工具声明数、环境数、最近错误(工作模式作为状态行提示),并支持启用/禁用开关、连接的增删改查、环境改名与连接测试。在 dsh 0.1.6 及以后,该配置页承载于官方「插件」页的插件行上(`plugins.row.config`),不再是设置面板里的独立卡片。
|
|
44
45
|
- R22. 面板文案与模型侧反馈跟随 DSH 界面语言(中/英)。
|
|
45
46
|
|
|
46
47
|
## 七、存储与实例隔离
|
|
@@ -49,11 +50,56 @@
|
|
|
49
50
|
- R24. 配置持久化失败不应导致崩溃,可降级继续运行。
|
|
50
51
|
- R25. 升级或手工修改遗留的旧格式配置应被自动清洗/迁移,未知字段予以保留。
|
|
51
52
|
|
|
52
|
-
##
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
53
|
+
## 八、连接配置交互原则(零打扰优先)
|
|
54
|
+
|
|
55
|
+
> 本节由 4.0.x 交互优化迭代(探连优先 + 凭据最小询问)沉淀,覆盖用户侧三轮反馈:①可用直接可用、缺什么问什么;②已存连接凭据缺失时应诊断并只补缺失项;③连接信息本身不对或重新描述了目标时应能脱身并按新信息重走判断。
|
|
56
|
+
|
|
57
|
+
- R26. 对**已配置且校验可用的连接**,配置过程必须自动校验;通过时直接确认「已配置可连通」并提供使用指引,**不要求用户重新输入任何信息**。
|
|
58
|
+
- R27. 仅当环境未配置、校验失败或自动试连未成功时才向用户提问;**提问不得索要已知或重复信息**——完整连接串已含密码时,不得再次索取密码。
|
|
59
|
+
- R28. 未配置环境下,若已从对话中得知足够的连接事实(类型/主机/库名;SQLite 除外),应**先自动试连**、按结果分流,而不是直接提问:
|
|
60
|
+
- 试连成功 → 按已有信息完成配置并返回可用来源标识,**全程零输入**(免密码连接、信息即用即存);
|
|
61
|
+
- 试连提示缺凭据 → **只询问缺失项**(账号未知时连账号一起问),其余信息自动沿用,补完即生效;
|
|
62
|
+
- 信息不完整或试连因其它原因失败 → 才提供完整录入方式(输入连接串 / 分项填写 / 授权扫描)。
|
|
63
|
+
- R29. 凭据询问必须**说明为什么需要**(试连失败原因),并允许用户表明「连接信息本身不对」从而改走完整录入,避免用户被困在反复补凭据的循环中。**若试连诊断明确指向“空账号被拒”(如 `Access denied for user ''@…`),账号为必填**:凭据询问不得提供「留空(使用空账号)」选择,用户仍提交空账号时应拒绝保存并说明原因,避免把同一个必失败的 DSN 再存回去。
|
|
64
|
+
- R30. 用户重新描述的目标(库/主机/端口等)与已存连接不同时,必须**按新描述重新执行判断流程**,不得沿用旧连接的结果。
|
|
65
|
+
- R31. 「使用现有连接」是**字面沿用**:不修改、不补填任何缺失字段(如账号/密码);其文案必须让用户明确这一点,不得暗示会修复缺失项。
|
|
66
|
+
- R32. 录入表单与凭据询问都应**预先带入已从对话中得知的字段**,用户只需补充缺失项,避免重复录入。
|
|
67
|
+
- R33. 连接保存或被采纳后,应**立即自检连通性**并把结果(可连通 / 失败原因)随回执一并返回,避免用户通过后续尝试才得知失败。
|
|
68
|
+
- R34. 连接信息输入界面只提供一个取消入口(由界面自身提供),不得出现重复取消项。
|
|
69
|
+
|
|
70
|
+
## 九、工作区隔离与来源解析
|
|
71
|
+
|
|
72
|
+
> 本节由 4.1 迭代沉淀:多工作区各自配置了相似(甚至同名)连接时,模型必须能分辨「这是哪个工作区的哪个环境」,不允许把不同工作区的连接当成同一个使用。
|
|
73
|
+
|
|
74
|
+
- R35. 每条连接只属于一个工作区;不同工作区的连接是彼此独立的目标,即使连接信息看起来相同也不得视为同一条(不共享、不互相覆盖、不互相替代)。
|
|
75
|
+
- R36. 模型侧应优先使用**当前会话工作区**的连接;连接清单必须按工作区分组并显式标出当前工作区。
|
|
76
|
+
- R37. 当模型给出的来源标识可能命中多个工作区(同名工作区、同名环境或仅给标题)时,系统必须返回候选清单要求明确指定,**不得按列表顺序或长度猜测**。
|
|
77
|
+
- R38. 需要复用其他工作区已配置的连接时,必须先取得用户确认,再由插件在宿主侧把连接**复制**到当前工作区(新建一条属于当前工作区的连接);凭据与连接串不得因此进入模型上下文,也不得直接用其他工作区的来源标识执行查询。
|
|
78
|
+
- R39. 环境名支持非 ASCII(如中文「线上」「测试」):显示与存储保留原名,而用于来源标识的环境段必须**唯一且稳定**——不同中文环境名不得解析到同一个来源;纯 ASCII 名的既有来源标识保持不变。
|
|
79
|
+
- R40. 环境可改名(模型可调、界面可改):改名只更换名字,连接与凭据原样保留、无需重新输入;目标名已存在时必须先经用户确认(该操作会覆盖目标环境的连接)。
|
|
80
|
+
- R41. 配置目标必须明确:显式给出的工作区不存在时应报错并列出可用工作区,不得静默改用其他工作区;未给出工作区时以当前会话工作区为准,无法确定时应要求明确指定。
|
|
81
|
+
|
|
82
|
+
## 十、配置页与桌面端承载(dsh 版本适配)
|
|
83
|
+
|
|
84
|
+
- R42. 配置页必须可通过**设置**进入:插件在设置面板注册一级页面(设置 → DBHub 数据库工具),该承载位属核心界面、任何部署都存在,用户也在此寻找连接管理;同时在官方「插件」页按当前 dsh 版本提供的单插件配置槽位(0.1.6 起为 `<包名>#<行 id>` 的配置页)再挂一份。三处进入的是同一个页面与同一份状态。
|
|
85
|
+
- R43. 插件另提供**侧边栏快捷入口**,且该入口的显示与否由设置页上的开关(`在侧边栏显示入口`,默认开)控制,改动即时生效、无需重启或刷新。
|
|
86
|
+
- R44. 官方槽位变化或缺失(如 dsh 升级移除旧槽位)不得导致配置页不可达;关闭侧边栏入口**不得**造成自锁——设置页与插件页入口必须始终可用。
|
|
87
|
+
- R56. 插件必须同时支持两条 dsh 版本线(0.1.x 与 0.2.x)的配置页数据通道:dsh 升级删除插件所依赖的宿主接口时(如 0.1.7 起 settings 命名空间被 Config 表单取代、0.2.x 删除浏览器侧 `settingsScope` 服务),插件**不得**因等待一个永远不会出现的服务而静默失活(表现为配置页/侧边栏/插件页入口全部消失且无任何报错)。浏览器半必须主动探测可用通道并降级;页面数据与写入能力在两条通道上必须等价。
|
|
88
|
+
- R57. 新增通道不得削弱既有安全边界:浏览器↔宿主的状态读取只承载元数据(延续 R3/R4),连接串只在写入方向从浏览器直达宿主;写入路由必须与 dsh 自身的接口鉴权一致(未鉴权的请求必须被拒绝),不得因为"插件自有路由"而绕过鉴权。
|
|
89
|
+
- R58. 插件选项(自动更新间隔、侧边栏入口)必须在两条版本线上都能持久化,并且升级插件不得静默重置用户已保存的值(旧版本的设置文档作为更低一层被读入)。在 0.1.7 及以后,这两项同时作为插件的声明式配置暴露:可经 `cordis.patch.yml` 或官方插件表单设置,且**与插件自有配置页共享同一个有效值**——任一处的改动都必须在另一处立刻可见(不得出现"改了表单但页面不变"或反之)。
|
|
90
|
+
|
|
91
|
+
## 十一、配置页布局与空间占用
|
|
92
|
+
|
|
93
|
+
> 本节由 4.2 迭代沉淀:连接数量增长后,原「每个连接三到四行 + 常显添加表单」的平铺列表把大量高度花在重复的工作区标题与固定块上;下列要求把列表改为按工作区组织、每连接一行。
|
|
94
|
+
|
|
95
|
+
- R45. 连接列表必须**按工作区组织**,并在两种呈现间自动选择:**所有工作区都恰好只有一个环境**时使用**平铺**(一格一个工作区,信息全露、无需展开);**任一工作区有多个环境**时使用**按工作区分组**。判定只取决于"是否存在多环境工作区",不引入数量阈值。
|
|
96
|
+
- R46. 工作区分组必须支持**点击组头展开/折叠**,组头显示该工作区的环境数与已保存数;分组以**工作区路径**为身份(同名工作区是不同目标,不得仅凭标题合并,同名时必须可见地消歧)。
|
|
97
|
+
- R47. 默认展开态:单个工作区自动展开;多工作区时展开**用户最近使用的工作区**,其余折叠;无法判定最近工作区时全部折叠。必须提供「全部展开 / 全部折叠」总控。用户的展开/折叠选择在同一次页面会话内保留(跨重挂载有效),**不跨刷新持久化**。
|
|
98
|
+
- R48. 列表中**每条连接只占一行**:环境名、连接元数据(类型/主机/端口/库名)与操作;`source` 句柄不得再单独占据整行,改为**可复制入口**(保持可复制性,且必须让人看得出复制的是 source 句柄而不是整行连接信息)。**库名必须始终可见**:窄行下可截断主机/端口前缀,不得把库名截掉。行内操作以图标承载(必须带 `title`/`aria-label` 说明),不得用文字按钮占掉行宽。**连接的「来源」(手填/扫描/复制/自动发现)不得作为行内徽章常显**——它对绝大多数行都一样,却会抢走行的视觉焦点;应改为按需查看(悬浮提示),其中「自动发现且未持久化」这一例外需明确提示(该条尚未保存)。列表内不得出现密码或完整 DSN(延续 R3)。
|
|
99
|
+
- R49. 低频内容必须收拢:状态与开关收成页面顶部一行,自动更新间隔与侧边栏开关放进默认收起的设置条;**添加连接表单默认收起**。分组内新增连接必须自动带入该工作区(不要求用户重复填写/选择工作区)。
|
|
100
|
+
- R50. 界面配色必须使用 DSH 主题令牌,随主题/皮肤与**半透明背景**自适应:样式表中不得出现不透明色字面量(含兜底值),正文与操作文案的对比度不得依赖固定浅灰。用户可能安装主题/皮肤插件覆盖或省略令牌,此时界面仍需可读(表面继承页面背景而不是涂上白块)。
|
|
101
|
+
- R51. 工作区分组与其环境行之间必须有**可见的层级区分**(缩进、引导线、边框或阴影等),不得让组头与环境行看起来是同一层。
|
|
102
|
+
- R52. 界面写入连接时,若目标工作区的目标环境名**已经存在**(新增落到已存在环境,或改名落到已存在环境),必须**先提示"将覆盖该连接"并要求用户确认**;取消则不得产生任何写入,并保留用户已填内容以便改名。判定必须使用与宿主一致的环境名规范化规则(去控制字符、trim、空值→`default`、限长),否则会出现"看起来不同、实际是同一个键"的漏判。仅修改**同一个环境**的连接不属此列(那正是编辑的目的,不需二次确认)。
|
|
103
|
+
- R53. 界面操作的**结果必须即时可见**:新增/删除/改名/连接测试提交后,列表不得等到"重新扫描全部工作区"才更新,也不得出现"先整块消失、稍后再出现"的空窗。理想顺序是:宿主应用写入后立刻按已知状态回推,再以权威扫描校正(扫描失败时保留最后一次可用列表)。
|
|
104
|
+
- R54. 从界面复制连接信息(连接串标签等)必须得到**可直接使用**的字符串:不得因为渲染分层(例如把类型/主机段与库名拆成两个盒子)而混入空格、换行等分隔符。
|
|
105
|
+
- R55. **连接发现不得被单个工作区拖死**:自动发现(mise env / .env)的每一次外部探测都必须在有限时间内返回(超时即放弃该工作区的自动发现,不影响其他工作区与已配置连接),且同一目录的结果在短时间内可复用(避免每次列表/执行都重新探测)。**界面显示连接与模型侧看到连接必须一致**:卡片写入所用的工作区标识必须与发现/查询所用的同一份注册表,不得出现"设置里有、模型列不出来"。
|
package/lib/bridge.mjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// dsh-dbhub-live: the Host <-> browser bridge.
|
|
2
|
+
//
|
|
3
|
+
// Why this exists: dsh 0.1.7 replaced the settings namespace this plugin used to
|
|
4
|
+
// mirror its live state through (host `settings.register(...)` scope with
|
|
5
|
+
// `replace`/`watch`) with Config-derived forms, and 0.2.x removed the client's
|
|
6
|
+
// `settingsScope` reader entirely. The bridge is the replacement channel: four
|
|
7
|
+
// exact Fetch routes on the shared `/api` surface, registered through
|
|
8
|
+
// `ctx.connection.fetch`, which exists on both dsh lines (0.1.6-alpha.2 and
|
|
9
|
+
// 0.2.x both ship `HostConnectionFetch`) and — unlike a raw `webServer` route —
|
|
10
|
+
// inherits Connection's Host/Origin fence and browser authentication.
|
|
11
|
+
//
|
|
12
|
+
// Zero-knowledge contract is unchanged: the read routes carry the SAME view the
|
|
13
|
+
// settings namespace carried (status + options + METADATA-only workspace
|
|
14
|
+
// summaries + transient test reports), never a DSN or username. DSNs travel
|
|
15
|
+
// browser -> Host only, on the write routes, exactly as they did through the
|
|
16
|
+
// settings document — they never enter a model context.
|
|
17
|
+
//
|
|
18
|
+
// This module is pure: it owns request/response marshalling and nothing else.
|
|
19
|
+
// The Host half injects the state machine, the options store and the one-way
|
|
20
|
+
// command handler.
|
|
21
|
+
|
|
22
|
+
/** Prefix every bridge route shares. */
|
|
23
|
+
export const BRIDGE_BASE = '/api/dsh-dbhub-live'
|
|
24
|
+
|
|
25
|
+
/** Exact route paths (what `connection.fetch.register` expects). */
|
|
26
|
+
export const BRIDGE_ROUTES = {
|
|
27
|
+
state: BRIDGE_BASE + '/state',
|
|
28
|
+
op: BRIDGE_BASE + '/op',
|
|
29
|
+
enabled: BRIDGE_BASE + '/enabled',
|
|
30
|
+
options: BRIDGE_BASE + '/options',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const JSON_HEADERS = {
|
|
34
|
+
'content-type': 'application/json; charset=utf-8',
|
|
35
|
+
'cache-control': 'no-store',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function json(payload, status) {
|
|
39
|
+
return new Response(JSON.stringify(payload), { status: status || 200, headers: JSON_HEADERS })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function messageOf(error) {
|
|
43
|
+
return String((error && error.message) || error || 'unknown error')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// A buffered body is capped by the transport, but a non-JSON or non-object body
|
|
47
|
+
// is still ours to reject: `undefined` means "not an object we can act on".
|
|
48
|
+
async function readObjectBody(request) {
|
|
49
|
+
try {
|
|
50
|
+
const body = await request.json()
|
|
51
|
+
return body && typeof body === 'object' && !Array.isArray(body) ? body : undefined
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build the bridge routes.
|
|
59
|
+
* @param deps - `getView()` returns the current merged view;
|
|
60
|
+
* `handleOp(op)` applies one workspace-connection command and resolves its
|
|
61
|
+
* patches; `setEnabled(bool)` flips the plugin toggle; `setOptions(patch)`
|
|
62
|
+
* applies a user option patch and persists it.
|
|
63
|
+
* @returns route descriptors for `connection.fetch.register`.
|
|
64
|
+
*/
|
|
65
|
+
export function createBridgeRoutes(deps) {
|
|
66
|
+
const { getView, handleOp, setEnabled, setOptions } = deps
|
|
67
|
+
const routes = []
|
|
68
|
+
|
|
69
|
+
const read = (fetchBody) => async (request) => {
|
|
70
|
+
try {
|
|
71
|
+
return await fetchBody(request)
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return json({ ok: false, error: messageOf(e) }, 500)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
routes.push({
|
|
78
|
+
path: BRIDGE_ROUTES.state,
|
|
79
|
+
methods: ['GET'],
|
|
80
|
+
requestBody: 'buffered',
|
|
81
|
+
fetch: read(async () => json({ ok: true, value: getView() })),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
routes.push({
|
|
85
|
+
path: BRIDGE_ROUTES.enabled,
|
|
86
|
+
methods: ['POST'],
|
|
87
|
+
requestBody: 'buffered',
|
|
88
|
+
fetch: read(async (request) => {
|
|
89
|
+
const body = await readObjectBody(request)
|
|
90
|
+
if (!body || typeof body.enabled !== 'boolean') {
|
|
91
|
+
return json({ ok: false, error: 'expected { enabled: boolean }' }, 400)
|
|
92
|
+
}
|
|
93
|
+
await setEnabled(body.enabled)
|
|
94
|
+
return json({ ok: true, value: getView() })
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
routes.push({
|
|
99
|
+
path: BRIDGE_ROUTES.options,
|
|
100
|
+
methods: ['POST'],
|
|
101
|
+
requestBody: 'buffered',
|
|
102
|
+
fetch: read(async (request) => {
|
|
103
|
+
const body = await readObjectBody(request)
|
|
104
|
+
if (!body) return json({ ok: false, error: 'expected an option patch object' }, 400)
|
|
105
|
+
await setOptions(body)
|
|
106
|
+
return json({ ok: true, value: getView() })
|
|
107
|
+
}),
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
routes.push({
|
|
111
|
+
path: BRIDGE_ROUTES.op,
|
|
112
|
+
methods: ['POST'],
|
|
113
|
+
requestBody: 'buffered',
|
|
114
|
+
fetch: read(async (request) => {
|
|
115
|
+
const body = await readObjectBody(request)
|
|
116
|
+
if (!body || body.op === undefined) {
|
|
117
|
+
return json({ ok: false, error: 'expected { op: <command> }' }, 400)
|
|
118
|
+
}
|
|
119
|
+
const patches = await handleOp(body.op)
|
|
120
|
+
return json({ ok: true, value: getView(), patches: Array.isArray(patches) ? patches : [] })
|
|
121
|
+
}),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return routes
|
|
125
|
+
}
|