DSH Plugins Marketplace

DSH Plugins

Plugins

/

Tools & Capabilities

/

dsh-profile-sync

w

dsh-profile-sync

Manifest valid★ 5

Plugin migration between profiles: safely migrate the plugin set from the web version (web) to the desktop (desktop) — compute differences, pre-check, generate scripts to run after exit, verify after restart

UI (client)hasBundlePatchMachine translated

dsh-profile-sync

在 dsh 的各个 profile 之间迁移插件。默认方向是 网页版 web → 桌面版 desktop。

它的定位很窄:自己不装任何东西。它只做四件事 —— 算差异、预检、写一个「退出后执行的脚本」、 下次启动后核对是否真的落地;真正的安装交给官方通道(桌面端走应用内管理器,其它 profile 走 dsh plugin)。

装完的效果:左侧栏多一个「插件迁移」面板 —— 选源/目标 → 算差异 → 生成执行脚本 → 核对上次迁移。


两条写入路径(以及为什么不是"一键")

dsh 里改一个 profile 有两条合法路径,取决于那个 profile 归谁管:

目标 profile谁有权写走哪条应用时机
desktop(归桌面应用管)应用内的官方插件管理器installBundle(cordis 服务 pluginManager,就是「设置 → 插件」用的那个)当场生效,不用退出
web / headless(归 CLI 管)dsh CLIdsh plugin --profile <p> install,或用本插件生成的离线脚本目标端退出后

桌面端 profile 禁止用 CLI 改 —— 这是硬拦,不是"要先退出"。 @deepseek-ai/dsh/lib/bin.js 里有一条按名字拦住一切 CLI 调用的守卫:

function rejectElectronProfile(program, profile) {
  if (profile.toLowerCase() === 'desktop')
    program.error('error: profile "desktop" is managed exclusively by the Electron application')
}

它对所有 CLI 调用生效,连 dsh --profile desktop --dump-config 都会被拒 (dshmarket 源码里也留着同一句注释:Never fall back to dsh plugin --profile desktop: that CLI is forbidden.)。

所以桌面端这一侧,本插件只调用官方管理器:改 dependencies、注册 dsh.profile.bundles、 跑兼容性校验、失败回滚都由它负责 —— 本插件不自己写 manifest。命令行的 plan / apply 是给 web / headless 这类由 CLI 拥有的 profile 用的。

要搬的不是 node_modules,是五样东西

少一样就会静默失效:

要搬的漏掉 / 搬错的后果
package.json 的 dependencies包装不上,加载失败
package.json 的 dsh.profile.bundles包装了但永远不会加载,而且不报错 —— 最阴的一种失败
pnpm-workspace.yaml 的 allowBuildspnpm 静默跳过原生构建(比如 node-pty 的 conpty.dll 铺不出来)
compatibility.json 里的精确版本豁免目标核心不认识该插件时,只给你一句看不懂的拒绝
cordis.patch.yml故意不同步,见下

故意不同步的那一份:cordis.patch.yml

里面装的是实例配置,不是插件配置:端口(web 绑 3080、桌面端绑 19387)、宠物坐标、 remote-web-ui 的 Tailscale 地址。整份抄过去会直接端口冲突。 所以本插件只把差异列出来给你看,一个字节都不写 —— 这是设计,不是没做完。


安装

桌面端不用退出 —— 恰恰相反,它必须在运行(要调用的那个官方管理器就在应用里面):

1. 打开 DeepSeek Harness(正在运行就对了)
2. 双击 install.cmd
3. 刷新页面 —— 左侧栏出现「插件迁移」

install.cmd 做的事:装前自检 → 确认官方的 /api/plugin-manager 端点在跑 → 把 link:<本目录> 提交给应用内的官方管理器 → 轮询到包真的出现在已装列表里才算成功。

宿主半装完通过 HMR 当场生效(工具立即可用);左侧栏面板刷新页面即可。

需要 Node.js 20+ 在 PATH 上。install.cmd / sync-plan.cmd / 生成的 apply.cmd 都会自己找 node,找不到就明确报错退出 —— 不会退回去执行桌面端 exe(原因见「修过的 bug」第 1 条)。

不装插件也能先看效果:bin/plan-cli.mjs 完全独立,不依赖 DSH 在跑 —— 现在就可以双击 sync-plan.cmd 看一份真实的差异报告。

装前自检(node bin/check.mjs)

install.cmd 在调官方通道之前会先跑一次自检:

  • 清单不变式:package.json 的 name、cordis.patch.yml 里的挂载名、客户端 loader 的 id 三者必须一致;dsh.client.inject 必须和客户端代码里的 inject 一致 (前者写包名、后者写服务名,写混是这个生态的经典错误)。
  • 真加载:真的 import 一次宿主半和客户端半,确认 apply 导出了、 客户端半确实按模块加载器契约注册了自己。语法对 ≠ 加载得起来 —— 顶层 import 一个不存在的相对路径就能骗过 --check。

挡的是最坏的失败模式:bundle 被写进 dsh.profile.bundles 却加载不起来 → profile 组装失败 → 桌面端连窗口都打不开,而那时候你已经在应用外面了,只能用文本编辑器改 package.json 才救得回来。

自检不通过会拒绝安装(退出码 1),不会硬着头皮往下走。


用法

双击

文件作用
install.cmd装前自检 + 把本插件装进 profile(桌面端要在运行状态)
sync-plan.cmd算差异并生成计划 + apply.cmd

命令行

# 算差异并落成产物(plans/<时间>/plan.json + plan.txt + apply.cmd)
node bin/plan-cli.mjs plan --source web --target desktop --write

# 只看报告
node bin/plan-cli.mjs plan

# 核对上一次迁移是否真的落地
node bin/plan-cli.mjs status

# 应用计划(会先查目标端是否还在运行)
node bin/apply.mjs --plan "<plans/.../plan.json>"

# 只算不写,看看会改什么
node bin/apply.mjs --plan "<...>" --dry-run

# 退回最近一次快照
node bin/apply.mjs --rollback

# 只做进程检查
node bin/apply.mjs --guard-only --profile desktop

# 装前自检(清单不变式 + 真加载;也可以对别的插件目录用 --dir)
node bin/check.mjs

退出码(方便写脚本串起来):

bin/plan-cli.mjs:

码含义
0计划无阻断
1用法 / 运行时错误
3有阻断项(不会生成 apply.cmd)

bin/check.mjs:0 = 可以装;1 = 不要装。

bin/apply.mjs:

码含义
0成功
1用法 / 运行时错误
2目标端还在运行(或被守卫拦下)—— 退出应用后重跑
3核对不通过(含 manifest-only:manifest 写对了但包没装)
4官方安装通道失败,已自动回滚到快照

面板

左侧栏「插件迁移」:选源/目标 → 算差异 → 生成执行脚本 → 核对上次迁移。 显示每个 profile 的端口和插件数,计划里给出「新增 / 变更 / 仅钉法不同 / bundle 新增 / allowBuilds 新增 / 阻断 / 提醒」的分项摘要。

给 agent 的工具

profile_sync,action 取 plan / write / status / profiles。


预检会挡住什么

装之前查这五件事(官方 dsh plugin add 不查第 2、3 条):

  1. 本机路径型 spec:link: / file: 指的本机路径是否存在、类型对不对。
  2. cordis entry id 撞车:两个 bundle 插入同一个 loader entry id 时, cordis 会拒绝启动整棵树,而不是跳过其中一个。官方通道不查这件事, 所以必须在装之前自查 —— 这是 blockers 里的 entry-id-collision。 只统计 insert: 块里的 id:patch 里另一类是「给别人的行改配置」,那类不算。
  3. 没有可加载入口的包:源码签出没有 lib/ 的包被提升进 bundle 层, 下一次启动就是 ERR_MODULE_NOT_FOUND —— 整个 profile 起不来。
  4. 声明了 dsh.bundle.patch 吗:进了 bundles 列表却没声明的包, 官方只会当普通依赖装(并打印同样的警告),不会成为 profile 层。
  5. 引擎范围(建议性):只认 >=x.y.z / >x.y.z / ^x.y.z / 裸版本, 并且正确处理预发布号(这个生态全是 0.2.0-rc.2;只比数字三元组会把 >=0.2.0-rc.3 对着 0.2.0-rc.2 判成满足)。复合范围老实说「不认识」。 权威判断交给官方通道。

钉法不同 ≠ 版本变更

有些 profile 是故意把版本钉成精确号的(例如桌面端 0.11.3),另一些用范围号 (例如网页端 ^0.11.3)。两者指向同一个版本,只是钉的松紧不同。

朴素 diff 会把它当成「变更」,从而覆盖掉目标端那个加固。所以本插件单独归一类 repin,只报告、默认不动。

allowBuilds 连值一起搬,且不会把 false 翻成 true

pnpm-workspace.yaml 的 allowBuilds 里,值本身是有语义的:显式写 false 是 「有意关掉这个包的原生构建脚本」(有的 profile 就是靠这个把 cloudflared / node-pty / ssh2 / cpu-features 的构建默认关着)。

所以同步时:

  • 新增键沿用源侧的值 —— 源侧 node-pty: false 就搬成 false,不会一律写 true 把人家有意关掉的东西静默打开。
  • 两边都有但值不同 → 归入 valueMismatch,只报告、不自动改(目标侧可能是 故意的,跟 repin 同一个道理)。
  • 拼写与引号规则沿用 dshmarket 那套(作用域包名要加引号、CRLF 要保住、已经坏成两个 allowBuilds: 块的会合并成一个)。

面板上的端口是怎么读出来的

端口不在 profile 的 cordis.patch.yml 里 —— 它现在的归属是启动参数 (@deepseek-ai/dsh-web-app 的 port: !!js ctx.webStartup.port ?? 3080;桌面端宿主则把 19387 硬编码在它的启动参数里)。所以 profilePortInfo() 按权威性分三档:

  1. 当前 profile → 用 DSH_WEB_URL 解析(对 --port 0 的随机端口也准);
  2. 该 profile 自己的 cordis.patch.yml 里的 port:(旧版本 DSH 或用户手写的场景);
  3. 只有名字是 web 的 profile 才退回出厂默认 3080(因为它可能被 --port 覆盖, 所以面板会把来源一并标出来)。

其它 profile 读不到就老实返回 null 并说明原因 —— 不给所有 profile 都套一个 3080, 那样只会把请求发到错误的进程上。

执行顺序(以及为什么是这个顺序)

1. 快照 package.json / pnpm-workspace.yaml / pnpm-lock.yaml
        / cordis.patch.yml / compatibility.json
2. 先合并 allowBuilds   ← 必须在 install 之前,否则 pnpm 静默跳过原生构建
3. 写 pending 日志      ← 必须在 install 之前,进程被杀也还知道「本来期望什么」
4. 原子写 package.json(自己写,见下)
5. dsh plugin --profile <p> install   ← 官方:兼容性校验 + 自动回滚 + reconcile 激活
6. 失败就把快照整个退回去

第 4 步为什么不直接用 dsh plugin add name@^0.11.3: Windows 上 dsh 是 .cmd,spawn 必须走 shell,而 cmd.exe 把 ^ 当转义字符 —— dsh-x@^0.11.3 传到 pnpm 手里就成了 dsh-x@0.11.3,静默把范围号变成精确号。 所以改成自己原子写 manifest(可单测、无转义问题),再让官方做安装。

重启后核对

官方 install 退出码为 0,只说明 pnpm 装完了 —— 说明不了新 bundle 真的进了加载层(可能没声明 dsh.bundle、 可能被上层 patch 覆盖、也可能装完又被兼容性拒绝回滚)。

唯一可信的确认是下次启动后读 manifest 和 bundles 对一遍。 pending.json 就是那个对账依据,插件在每次启动时自动核对并打印结果。


状态目录

~/.dsh/profile-sync/
  plans/<时间>/     plan.json(可复核)+ plan.txt + apply.cmd
  backups/<时间>/   快照 + meta.json(记着哪些文件原本不存在)
  pending.json      上次 apply 的期望值,重启后用它核对

测试

node test-plan.mjs         # 18 项:纯函数 + 合成 fixture + 对真实 profile 算一遍
node test-apply.mjs        #  7 项:快照/回滚/原子写/dry-run/拒绝条件(用合成 profile)
node test-host.mjs         # 12 项:路由与工具契约、客户端席位注册与注销
node test-regressions.mjs  # 13 项:每个用例对应一个**真实修过的 bug**
node test-manifest.mjs     # 14 项:清单不变式 + 真加载(坏包必须被挡住)
node test-managed.mjs      # 15 项:官方管理器解析、进程内应用、HTTP 端点通道

共 79 项,都不启动 DSH、不占端口、不跑 pnpm(runner 是注入的假函数)。 test-apply.mjs / test-regressions.mjs 会在 ~/.dsh/profiles/ 下建 synctest-* 合成 profile,跑完删掉 —— 不碰真实的 web / desktop。

客户端启动验证(bin/verify-client.mjs)

上面那些测试碰不到客户端启动审计 —— 而这正是一个踩过的坑:宿主半装载成功、 日志一切正常,客户端那一半却是死的,桌面端直接打不开窗口。

node bin/verify-client.mjs                 # 验本插件
node bin/verify-client.mjs --plugin-dir X  # 验别处一份(阳性对照用)

它做的事:建一个一次性 profile(名字硬拒绝 desktop / web / headless)→ link 装入目标插件 → 起服务(加 --no-open,绝不许弹用户的浏览器)→ 用无头 Chrome/Edge 的 --dump-dom 抓那个页面的真实文本 → 断言 「不含 did not activate」且「含面板标签」→ 无论成败都杀进程、删 profile、删浏览器临时目录。

两个实测出来的坑,别改回去:

  • 不能加 --virtual-time-budget:页面有 SSE 长连接(HMR 用的),虚拟时间永远 等不到「网络空闲」,--dump-dom 就永不返回(实测 40 秒超时、0 字节)。
  • 带 token 的 URL 是异步打印的:端口先能连上,?token=… 那一行稍后才出现。 早一步拿就只能拿到 authentication required 的 222 字节页面。

它必须配阳性对照才有意义:先把 client.js 的 inject 故意改回包名(用一份 临时副本,不碰源码),验证器必须报失败;再改回服务名,必须通过。 没有这一步,它就可能变成第二个「假安全网」。

修过的 bug(test-regressions.mjs 盯着不让回来)

  1. 面板生成的 apply.cmd 会去再启一遍桌面端。 规划时用的是 process.execPath, 而在 Electron 里那是 DeepSeek Harness.exe。这个构建的 ELECTRON_RUN_AS_NODE=1 还是失效的(实测无输出、无退出码 —— 打包时 RunAsNode fuse 被关了), 所以只能去 PATH 上找真 node;找不到就明确报错,绝不退回应用 exe。 (之前测出来是对的,只因为 plan-cli.mjs 是在真 node 下跑的,掩盖了这条。)
  2. --prune 之后自己报「缺依赖」。 期望值在 writePending 里照计划又推导了一遍, 而 apply 层是按 prune 删的 —— 两份推导逻辑一漂就自己骗自己。 现在期望值只从「即将写入的那份 manifest」抽一次。
  3. --no-install 会在核对时假装「已落地」。 现在 pending 里如实记 installed, 核对结果多一个 manifest-only 状态,明说「包没装」。
  4. 目标没有 pnpm-workspace.yaml 时会凭空造一个残废文件(只含 allowBuilds, 丢掉 nodeLinker: hoisted,pnpm 会用 isolated 把安装搞坏)。现在跳过并警告。
  5. 校验函数自己会崩。 它先记下「add 必须是数组」,然后又拿那个非数组值 去 .entries() —— 校验函数崩了等于没校验。
  6. 预发布号被当成不存在。 >=0.2.0-rc.3 对着运行时 0.2.0-rc.2 会被判成「满足」。 这个生态全是 rc 版本,属常态;现在按 semver 正确比较预发布段。

诚实的边界

  • 不自动同步配置层。cordis.patch.yml 只报告差异,这是设计,不是没做完。
  • 默认不删目标端多出来的依赖(只报告);要删得显式 --prune。
  • 兼容性判断以官方为准。本插件的引擎检查是建议性的,读不到运行时版本时说「不知道」。
  • 不支持显式目录形式的 profile。dsh plugin --profile 按 profile 名解析目录, 所以 apply 会拒绝目标目录不等于 profiles/<名字> 的情况。
  • allowBuilds 的 YAML 处理是抄 dshmarket 的(CRLF、作用域包名要加引号、 已经坏成两个 allowBuilds: 块的会合并成一个)。那块被真实 bug 打磨过,不重写。
  • 进程守卫靠进程名 + 命令行。桌面端命令行里没有 --profile desktop (是 DeepSeek Harness.exe + host 脚本),只能按进程名认;两路检查都跑不起来时 会拒绝并要求 --yes,而不是默默继续。

Versions

Latest versionPublishedSize
0.1.0——
0.1.1——
0.1.3——

Comments

Loading…

Similar plugins

dsh-mobile-adaptive

by zzh799

dsh web 移动端适配插件:侧边栏抽屉 + 设置页单列布局;目录浏览移动端化 + 本地上传到工作区 ./上传/

Manifest valid

★ 0

MIT

TypeScript

Aug 16, 2026

dsh plugin --profile web add dsh-mobile-adaptive

by ygcdsj

一个插件,把本地的 DSH 配置(皮肤、预设、注入包)打包成文件,到另一台 Windows 上直接还原。

Workflow & AutomationManifest valid

★ 3

↓ 63/wk

MIT

TypeScript

Aug 23, 2026

dsh plugin --profile web add dsh-migrate

by webkong

DSH 插件管理器:在 Web 设置页管理内置与三方插件——安装、卸载、启用、停用,支持 GitHub 搜索安装与非 bundle 插件一键装载

Development & InfrastructureManifest valid

★ 4

MIT

JavaScript

Sep 1, 2026

dsh plugin --profile web add @webkong/dsh-plugin-manager

by CaT-Hode

Windows desktop plugin for DeepSeek Harness — shared Web sessions and plugins, startup logs and update recovery. DSH 桌面插件:共用 Web 对话与插件。

UI & ExperienceDevelopment & InfrastructureManifest valid

★ 0

MIT

JavaScript

Sep 28, 2026

dsh plugin --profile web add dsh-app

by anywhere-labs

为 DeepSeek Harness (DSH) 插件生态打造的现代化桌面端解决方案。万物皆「插件」,桌面本身也是「插件」。

UI & ExperienceManifest valid

★ 30.4k

↓ 108/wk

MIT

TypeScript

Oct 10, 2026

dsh plugin --profile web add dsh-plugin-desktop

by baihejiangnan

本地插件开发管理面板:主动登记通过 file:/link: 安装的真实路径项目,并通过带风险确认的工作区会话协助开发与发布。

Terminal & ClientsManifest valid

★ 0

MIT

JavaScript

Aug 16, 2026

dsh plugin --profile web add @baihejiangnan/dsh-plugin-dev