Skip to content

Releases

v0.1.14 — 修复系统目录对话框不弹出

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 14:12

为什么系统对话框一直没弹出来

不是我猜的——直接在运行中的实例上打接口,拿到的是 502:

POST /path-lockbox/api/pick  {"kind":"folders"}
-> 502 {"native":true,"error":"Cannot read properties of undefined (reading 'aborted')"}

native:true 说明宿主确实提供了原生能力,错在调用方:

// dsh-host-directory-picker-native
async function pickWin32Directory(signal) {
  if (signal.aborted) throw new Error("native directory picker aborted");  // ← 第一行就用 signal

0.1.9 我调用时写的是 capability.pick(),没传 AbortSignal → 立即抛错 → 前端把 5xx 当成"没有原生能力"→ 回退内置浏览器。所以每次看到的都是那个简陋弹窗。

修复

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10 * 60 * 1000); // 兜底,abort 会关闭对话框
try {
  const picked = await capability.pick(controller.signal);   // ← 补上 signal
  return json({ native: true, path: picked ? String(picked) : null, cancelled: !picked });
} finally {
  clearTimeout(timer);
}

同时修内置浏览器的「点了没反应」

  • 旧逻辑:pickOk 里 if (!pick.selPath) return; —— 双击进入目标文件夹后直接点「确定」,什么都不发生;
    现在未选中条目时回退到当前路径。
  • 增加一行提示:双击进入文件夹,单击选中,点「确定」添加;未选中时添加当前路径。

验证(不弹真实对话框)

给 Cordis 测试夹具注入一个 mock directoryPicker,记录 pick 收到的参数:

POST api/pick (folders) -> 200 {"native":true,"path":"D:\\mock-picked","cancelled":false}
pick(signal) 记录       -> [{"isAbortSignal":true,"aborted":false}]
POST api/pick (files)   -> 200 {"native":false,"reason":"directory-only"}
RESULT: PASS

node --test 27/27。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.14"

重启后:文件夹 / 分区点「+」应弹出 Windows 系统文件夹选择框;文件仍走内置浏览器(原生能力是目录选择器)。

v0.1.13 — 选择器弹窗标题

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 14:07

选择器弹窗标题与类别对齐

原 现
选择文件 选择要锁定的文件
选择文件夹 选择要锁定的文件夹
选择硬盘 选择要锁定的分区

只改 KINDS[kind].pickTitle 这一处,弹窗标题、以及原生选择器不可用时的回退路径都会跟着变($("pickTitle").textContent = KINDS[kind].pickTitle)。

提示:文件夹 / 硬盘在正常环境下走系统对话框,这个弹窗只在宿主没有 directoryPicker 能力时才出现;文件类始终走这个弹窗。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.13"

安装后重启 dsh。

验证

  • 产物含三段标题(esbuild 转义后逐字匹配)
  • node --test 27/27

v0.1.12 — 按钮精简(+ / 选择路径…)

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 14:04

按钮精简

原 现
添加… +(32px 紧凑主按钮,蓝色)
添加自定义 选择路径…

随动同步:

  • 弹窗标题 添加自定义 → 选择路径
  • 空态提示 暂无条目,点「添加…」或「添加自定义」 → 暂无条目,点「+」或「选择路径…」

行为不变:

  • + 对文件夹 / 硬盘走系统原生目录对话框,文件类走内置浏览器;
  • 选择路径… 仍是手动输入绝对路径的弹窗(customHint 提示文件/目录/盘符写法)。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.12"

安装后重启 dsh。

验证

  • 产物含 data-add title="添加">+<、选择路径…、新空态文案与 .btn.plus 样式
  • node --test 27/27

v0.1.11 — 类别改名与说明文字

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 14:02

三个类别改名并加说明

原 现 说明(浅灰小字)
文件 锁定文件 支持所有文件类型
文件夹 锁定文件夹 打开要锁定的文件夹
硬盘 锁定分区 锁定的硬盘分区

实现:卡片头部新增 <span class="card-desc">,样式 color: var(--lb-fg3); font-size: 12px;
(跟随主题的第三级前景色,浅灰);标题保持 font-weight: 500。计数仍在最右(margin-left: auto)。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.11"

安装后重启 dsh(页面由宿主路由加载,重启后刷新)。

验证

  • 构建产物含三段文案(esbuild 以 \uXXXX 转义,逐字匹配通过)与 .card-desc 样式
  • node --test 27/27

v0.1.10 — 锁图标

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 13:59

插件图标改为「锁」

插件列表 / 详情页的图标来自包清单的 icon 字段(宿主 plugin-inventory 会把它转成
base64 data URL)。dsh-better-sidebar、dsh-codex-ui、dsh-ops 都是这么声明的。

本次:

  • 新增 assets/lock.svg:36×36 描边挂锁,与内置图标的 36px 网格一致
  • package.json 增加 "icon": "./assets/lock.svg"
  • 版本 0.1.10

一个限制要说明

设置页左侧导航的图标改不了。 dsh-client-ui-settings 里没有任何 icon 逻辑,
导航字形是按栏目由宿主固定的;settings.section 注册项也没有 icon 字段可传。
要改得改 DSH 本身(让设置栏目的导航读取注册项里的图标)。

所以本次改到的是插件自身的图标(插件列表、插件详情页标题旁那个)。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.10"

安装后重启 dsh。图标由宿主在启动时从清单读取,不重启不会刷新。

验证

  • assets/lock.svg 可解析(36×36,viewBox="0 0 36 36")
  • npm pack 产物包含 package/assets/lock.svg
  • node --test 27/27

v0.1.9 — 系统原生目录对话框

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 13:56

文件夹 / 硬盘改用系统原生目录对话框

之前「添加…」只弹插件内置的浏览器。现在目录类直接调用操作系统的原生选择器:

ctx.directoryPicker.capability() -> { kind: "native", pick(signal) }

Windows 上是 IFileOpenDialog + FOS_PICKFOLDERS(宿主自带实现,走独立子进程 + Win32 绑定)。

改动

  • 宿主新增 POST /path-lockbox/api/pick
    • 用 ctx.get("directoryPicker") 取服务(不写进 inject,宿主没有该服务时插件照常激活)
    • 要求 capability.kind === "native";用 busy 标记串行化,避免同时弹两个对话框
    • 返回 { native, path } / { native, cancelled } / { native: false }
    • 文件类直接返回 { native: false, reason: "directory-only" }:native 能力是目录选择器,选不了文件
  • 前端 src/ui.js
    • 文件夹 / 硬盘的「添加…」先请求该接口,拿到路径直接入库
    • 宿主没有该能力或调用失败 → 自动回退到原内置浏览器
    • 文件类保持内置浏览器
    • 入库逻辑抽成 addPicked(),两条路径的保存/刷新行为一致

验证

node --test   # 27/27

Cordis 最小上下文:注册路由由 5 条变为 6 条,POST /api/pick 在无 picker 的宿主上返回
{"native":false} —— 正是前端判断回退的信号。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.9"

安装后重启 dsh。之后在「设置 → 文件锁」里点文件夹或硬盘卡片的「添加…」,弹出的就是
系统目录选择框;文件类仍是内置浏览器。

版本脉络

版本 内容
0.1.3 宿主激活、cfg() 用 apply 实参、mountRoute 适配 webServer.register
0.1.4 client 半身改用 ctx.slots
0.1.5 声明 inject = ["slots"];UI 注册失败可降级
0.1.6 Config schema(volatile)→ 自动设置项
0.1.7 settings.section → 设置里显示「文件锁」
0.1.8 去掉 .volatile() → 只保留「文件锁」一个入口
0.1.9 文件夹/硬盘改用系统原生目录对话框,失败回退内置浏览器

v0.1.8 — 只保留「文件锁」一个设置入口

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 13:47

只保留「文件锁」一个设置入口

0.1.6 把整个 Config 标了 .volatile(),于是 dsh-settings 会自动生成一个以
命名空间 id(path-lockbox)为标题的配置表单。0.1.7 又加了正式的 settings.section
栏目(中文名「文件锁」),两者并存就成了两个入口。

本次去掉 .volatile():

- export const Config = z.object({ ... }).volatile();
+ export const Config = z.object({ ... });
  • volatileForm(schema) 只认 volatile 字段 → 自动表单不再生成
  • schema 仍保留,用于校验与默认值(apply() 拿到的 config 恢复为普通对象)
  • readConfig() 仍兼容 volatile 引用(typeof config.get === "function"),防止以后改回来
  • tests/host.test.js 新增断言:Config 必须导出、带 toJSON、且刻意非 volatile,
    防止重复入口悄悄回来

验证

node --test   # 27/27 通过

产物:lib/index.js 已不含 .volatile();lib/client.js 仍是
settings.section + label: () => "文件锁" + inject = ["slots"]。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.8"

安装后重启 dsh:设置里只有 文件锁 一个入口,点开即插件自带界面。

版本脉络

版本 内容
0.1.3 宿主激活、cfg() 用 apply 实参、mountRoute 适配 webServer.register
0.1.4 client 半身改用 ctx.slots
0.1.5 声明 inject = ["slots"];UI 注册失败可降级
0.1.6 Config schema(volatile)→ 自动设置项
0.1.7 settings.section → 设置里显示「文件锁」,栏目内嵌自带界面
0.1.8 去掉 .volatile() → 只保留「文件锁」一个入口

v0.1.7 — 设置里显示「文件锁」

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 13:44

「设置」里出现中文名 文件锁

设置页左侧导航由 settings.section 槽构建,每个条目提供 id / order / label。
之前只有自动生成的配置表单(标题是原始命名空间 path-lockbox),现在额外注册了一个
正式栏目:

ctx.slots.inject("settings.section", () =>
  ctx.slots.register(
    { name: "settings.section", id: "path-lockbox", order: 50, label: () => "文件锁" },
    PathLockboxSection,   // 直接嵌入 /path-lockbox/ui
  ),
);
  • label 是函数,宿主每次渲染重新取值,所以显示就是 文件锁
  • 栏目内容复用插件自带界面(iframe → /path-lockbox/ui),不是自动生成的表单
  • plugins.detail.section(插件详情页配置区)保留,两处入口并存
  • dsh.client.inject 补上 @deepseek-ai/dsh-client-ui-settings(它声明了 settings.section)
  • 两个槽各自独立 try/catch:任一槽缺失只跳过该入口,绝不让 entry 激活失败
    (那是启动级故障)

验证

node --test   # 26/26 通过(新增 3 项:栏目注册、中文 label、共用界面)

产物检查:含 settings.section、plugins.detail.section、exports.inject=["slots"]、
无顶层 export。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.7"

安装后重启 dsh,「设置」里会出现 文件锁 一节。

版本脉络

版本 内容
0.1.3 宿主激活、cfg() 用 apply 实参、mountRoute 适配 webServer.register
0.1.4 client 半身改用 ctx.slots
0.1.5 声明 inject = ["slots"];UI 注册失败可降级
0.1.6 Config schema(volatile)→ 自动设置项
0.1.7 settings.section → 设置里显示中文名「文件锁」,栏目内嵌自带界面

v0.1.6 — 出现在「设置」页(volatile Config)

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 13:40

现在会出现在「设置」页

宿主的 dsh-settings 会遍历所有已激活的配置项:只要插件的 runtime.Config
带 toJSON(),且 volatileForm() 能从中切出 volatile 字段,就自动生成一个设置项。
dsh-plugin-marketplace 用的正是这套机制(z.object({...}).volatile())。

本次改动

  • 新增 Config schema:files / folders / drives(每项 path + label +
    recursive + watch)、matchCase、guidance、extraPathArgs、extraCommandArgs,
    整体 .volatile()
  • 新增依赖 @deepseek-ai/schemastery(构建时打包进 lib/index.js;宿主按鸭子类型
    'toJSON' in schema 识别,不依赖模块实例同一性)
  • 标了 .volatile() 后 apply(ctx, config) 收到的是可读取引用,故新增
    readConfig() 解包 config.get()(普通对象仍然直读,单测不受影响)

验证

  • node --test 23/23
  • Config.toJSON() 经 new schemastery(...) 往返后 meta.volatile 保留,7 个字段可切出
  • 带 schema 激活:5 条路由照常注册,/path-lockbox/ui 200、/api/config 200、
    DELETE 405

规则优先级(重要)

$DSH_HOME\path-lockbox.json   ← 插件自带 UI 写入
        ↓ 文件不存在时才回落到
profile config                ← 设置页编辑的就是它

两者同时可用,但自带 UI 优先:一旦用自带 UI 保存过规则,设置页的列表就不生效
(删掉那个 JSON 后会重新以设置为准)。需要反过来(设置优先)说一声,改一行即可。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.6"

安装后重启 dsh,在「设置」里应能看到 path-lockbox 一节。

版本脉络

版本 修复/功能
0.1.3 宿主激活(inject)、cfg() 用 apply 实参、mountRoute 适配 webServer.register
0.1.4 client 半身改用 ctx.slots;补齐 dsh.client.inject;react external
0.1.5 声明 inject = ["slots"];UI 注册失败可降级,不再影响启动
0.1.6 Config schema(volatile)→ 出现在「设置」页

v0.1.5 — 修复启动崩溃(声明 slots 服务)

Choose a tag to compare

@thson-xue thson-xue released this 22 Oct 13:28

修复 0.1.4 引入的启动崩溃

0.1.4 让设置界面能注册了,但直接读 ctx.slots 却没有声明它。Cordis 的注入守卫会拒绝该属性访问:

cannot get property "slots" without inject

这个 client entry 因此激活失败,而宿主把「任一条目未激活」判定为整次启动失败:

web boot: 1 entry did not activate
dsh-path-lockbox: failed

修法

  1. 声明服务依赖:export const inject = ["slots"],与 dsh-ops 的
    exports.inject = ['slots', 'locale', 'remote'] 同形。客户端插件和宿主插件一样,
    只有声明过的服务才能用 ctx.<name> 读取。
  2. 插件绝不能拖垮外壳:require("react")、ctx.slots 读取、槽注册全部收进
    try/catch。UI 注册失败时只退化为「直接访问 /path-lockbox/ui」,
    不能再让整个应用起不来。
  3. tests/client.test.js 增加 inject 断言 —— 漏掉它是启动级回归。

验证

node --test   # 23/23 通过

构建产物:var inject = ["slots"]、plugins.detail.section 注册、无顶层 export、
require("react") 处于 try 内。

升级

"dsh-path-lockbox": "github:thson-xue/dsh-path-lockbox#v0.1.5"

安装后重启 dsh。若界面仍未出现,说明 slots 服务在该宿主未就绪,插件会安全退化为
URL 直达(/path-lockbox/ui),不会再影响启动。

版本脉络

版本 修复
0.1.3 宿主激活(inject 去掉 connection)、cfg() 用 apply(ctx, config)、mountRoute 适配 webServer.register
0.1.4 client 半身改用 ctx.slots;补齐 dsh.client.inject;react external;客户端测试
0.1.5 声明 inject = ["slots"];全部 UI 注册失败可降级,不再影响启动