DSH 整合包(.dspack)的纯方案 B 插件 bundle:一个自包含的独立 npm 包,注入官方 DeepSeek Harness 桌面端,提供 .dspack 导出 / 安装 / 多 profile 切换 / 市场浏览 / 工作区配置 / 任务中心。只做 UI + 后端,不做 AI 驱动的整合包管理。
- 零官方源码改动:不改
apps/desktop/apps/desktop-host。 - 零第三方插件:只依赖 DSH 出厂运行时服务(
ctx.connection/ctx.webServer/ctx.slots/ctx.locale/ctx.profileContext)。 - 自包含:运行时依赖仅
fflate(ZIP)+proxy-agent(下载代理),其余全部 vendor 进仓库(src/core/、src/host-node.js),不依赖@dsh-packforge/*引擎包。 - 加载方式:官方 bundle 机制——
cordis.patch.yml插入 host 插件,dsh.client注入 client 插件。
完整设计见 dsh-packforge-app/docs/dsh-pack-方案.zh.md;格式契约见 specs/。
- 导出
.dspack:把某个 profile 打包成单文件整合包(profile 形态),或从源仓库导出(exportRepo)。UI 目前只暴露 profile 形态;dshhome(整机)形态的打包 / 安装 / 校验逻辑在 core 里就绪,未在设置面板接线。 - 安装
.dspack:本地文件 / URL / 市场包均可;按sha256+size校验完整性,pnpm重建依赖并对账,home 级resources与files[]逐项下载落位。 - 多 profile 切换:
profiles/desktop是激活指针(junction 换指),切换即换指 + 重启桌面,无需重装。 - home 级 skills / .agent-presets 隔离:per-profile 槽位(junction),换 profile 时一并换指(见下文)。
- 市场浏览:
index.json精简指针 +packs/<owner>.<repo>/懒加载完整清单 / README。 - 工作区配置
.dshpkcfg:每个 profile 一份,保存 / 读取 / 自动回填。 - 下载代理:About 页可配代理地址(存
config.proxy,覆盖一切);未配时依次回落环境变量(HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY)与 Windows 系统代理(含ProxyOverride旁路)。支持 http / https / socks / socks5,市场索引 / 包详情 /.dspack安装 / npm 拉取统一生效。 - 任务中心:
install/export/create非阻塞执行,设置面板内嵌视图显示阶段时间线与进程输出。
host 侧(Node) client 侧(浏览器 bundle)
src/index.js ─┐ src/client.js ── bundle ──► lib/client.js
src/rpc.js │ registerRpc(ctx, runtime) src/client-plugin.js(dspackforge)
src/endpoints.js → ctx.webServer 挂 /dsh-pack 前缀路由
src/channel.js ┘ src/client-rpc.js(rpc.call 封装)
src/profiles.js / junction.js src/settings.js(slots 设置页:管理/导出/市场)
src/migrate.js / migrate-helper.js src/progress.html
src/ensure-manager.js / tasks.js / progress.js
src/core/(vendored 引擎) src/locale(可选)
- RPC 通道:
CHANNEL = '/dsh-pack'(src/channel.js,host / client 共用)。host 侧自建前缀路由挂在ctx.webServer上(不走ctx.connection.rpc.handle——它对第三方插件不可用),并用ctx.connection的请求授权栅栏挡住非 client 调用;client 侧rpc.call静默收发,不在聊天栏显示任何工具调用。 - Host DI 边界:
src/core/不直接碰node:fs/node:crypto/node:child_process,一律经Host接口(src/host.js)注入,src/host-node.js是 Node 实现。core 因此可脱离 DSH 单独测试。除必选能力(读写文件 /download/exec等)外,Host 还提供可选能力(如pnpm(args, opts)):默认回退exec('pnpm', …)(依赖 PATH),宿主可覆盖为「复用 DSH 自带 node+pnpm 运行时(resources/runtime/)」,让桌面端装包重建依赖时不依赖用户机器 PATH 上的 node/pnpm。core用typeof host.pnpm === 'function'探测,旧宿主无此能力时自动回退,行为不变。 - 多 profile 切换(junction 换指):把
profiles/desktop换成 junction 指向目标 profile,切换 =unlink旧 junction +symlink(target, 'junction')。红线:用unlink(lstat 语义)删 junction,绝不rm -r——后者在 Windows 上会递归删掉 junction 目标目录的内容。
.dspack容器(pack-structure v3 / v3 r2):标准 ZIP;根放dspack.json({"format":"dspack","version":3}标记)+manifest.json。- profile 形态:机器文件(
package.json/pnpm-workspace.yaml/pnpm-lock.yaml)放根,其余内容放overrides/,home 级内容放home/。 - dshhome 形态:整机快照(
profiles/、presets/、skills/、instructions/、defaultProfile等)。 - vendored 依赖内嵌(v3 r2 §8):可选
vendor/目录携带依赖 tarball。导入侧已实现阶段 0 对账 + 逐 tarballsha256/size预验(装前拒装)、tarball 落盘vendor-blobs/+ 重建package.json时改写为file:引用 + 本地优先安装;闭包完整性检测(对照随包pnpm-lock.yaml)——覆盖完整自动切pnpm install --offline(零网络、缺件即报错),局部覆盖--prefer-offline(本地与网络互为兜底);DSHLvendor:方言包按隐式条目消费(直挂node_modules/,依赖剔除,不传给 pnpm)。 - 导出侧手动内嵌(v3 r2 §8.6):导出面板列出依赖清单(
pack/dependencies端点,npm/git/已内嵌三类)供手动勾选,叠加 vendor 档位(.dshpkcfgvendor键,workspace-config v1 r2):auto(默认,round-trip 保持 + 手动勾选,不联网探测)/off(禁用)/full(全部直接依赖,离线包形态);勾选后按坐标取件——vendor-blobs/round-trip 复用原 tarball 字节、npm 先取 registry 原件(字节一致)、取不到时从node_modules重打包并按规范加-local.N版本后缀(dependencies 值 /vendored[].version/ tarball 内 version 三处一致);体积阈值(>500 MB 警告 / >2 GiB 拒绝打包)。 - 导出侧兼容性编辑(v5 r2 §13/§14):导出面板「兼容性」组编辑
dshVersions(实测兼容版本枚举集,datalist 建议来自本机已装版本,首个即首选、未填dshVersion时作首选)与launchers(四个已认领启动器 ID 的支持/冲突 +minVersion/reason,简式/全式归一);两者持久化进.dshpkcfg(白名单已扩展)并在打包前强校验(dshVersion ∈ dshVersions等错误在导出时报出)。
- profile 形态:机器文件(
- manifest v5 / v5 r2:
type: "profile" | "dshhome"(见src/core/manifest.js)。r2 可选字段:vendored{}(§12,导入消费 + 结构校验已实现)、dshVersions(§13,交集决策已实现:dshVersion优先 → 集合内最新)、launchers(§14,结构校验已实现;安装端判定表待接线 UI 确认流)。前向兼容:未知字段不拒装。 - 安全规则(
src/core/security.js):node_modules/、dist/、密钥 / 凭据、嵌套压缩包、.dshpkcfg、.dsh-pack等一律不进包。 - 工作区配置(specs/workspace-config/v1,
src/core/workspace.js):.dshpkcfg为单个 UTF-8 JSON 对象,2 空格缩进 + 结尾换行;只认白名单字段(name/version/displayName/description/author/icon/dshVersion/out/exportContent/profileName/mode/content/defaultProfile);空字符串表示「未填写」。 - 市场索引(specs/index/index.md,schemaVersion 2,
src/core/market.js):index.json只放downloadUrl+sha256+size指针和元数据,完整 manifest / README 从packs/<owner>.<repo>/懒加载。
profiles/desktop是硬编码的激活指针(ACTIVE_NAME = 'desktop')。切换 = 删除旧 junction + 建立新 junction。- 首次迁移:真实
desktop目录需要先改名成default才能换指,而改名必须退出桌面进程;插件用脱管 helper(migrate.js经process.execPath+ELECTRON_RUN_AS_NODE=1、detached:true、cwd=home 派生migrate-helper.js)完成「杀桌面 → 换指 → 重启桌面」。 - skills / .agent-presets 隔离:两者都是 home 级内容(
$DSH_HOME/skills、$DSH_HOME/.agent-presets/<id>/agent.cordis.yml),不进 profile 目录。插件的做法是把$DSH_HOME/skills与$DSH_HOME/.agent-presets换成 junction,指向.dsh-pack/skills/<profile>与.dsh-pack/agent-presets/<profile>槽位;换 profile 时对两个工件各自独立做首次迁移判定(lstat,互不耦合、部分迁移幂等),归档用「活目录胜出」(rm -r目标 +rename),没有 skills 的 profile 建空槽位。映射逻辑见src/core/home-store.js(storeHomeRel),换指见src/junction.js(repointHomeArtifact)。
| 端点 | 作用 |
|---|---|
runtime/get |
返回 profile 运行时信息(home / profilesDir / profileDir / patchPath / startedBundles) |
config/get / config/set |
读写 home 下的 dsh-packforge.json 状态 |
profile/list |
列出 profiles(desktop junction 指向项不重复列出,真实 desktop 目录标为 default) |
profile/create |
新建 profile(非阻塞,立即返回 taskId;空整合包自带管理器基线,进度见任务中心) |
profile/delete |
删除 profile |
profile/switch |
切换激活 profile(junction 换指 + 可选 home skills/presets 换指) |
profile/switch-check |
预检切换(如首次迁移是否需杀桌面) |
profile/open-dir |
在系统文件管理器打开 profile 目录 |
pack/export |
导出 .dspack / 源仓库(非阻塞,立即返回 taskId) |
pack/config-load / pack/config-save |
读取 / 保存某 profile 的工作区配置 .dshpkcfg |
pack/view |
校验并查看 .dspack 内容 |
pack/install |
安装 .dspack(非阻塞,立即返回 taskId) |
pack/market |
浏览市场(index 列表 / 详情) |
pack/market-detail |
市场详情懒加载(manifest + README + r2 徽标:launchers 警示 / vendored / dshVersions) |
launchers/registry |
启动器注册表(机器可读版 launchers.json,1h 缓存;失败回落内置清单)——「兼容性」编辑器的认领 ID + 显示名数据源 |
pack/dependencies |
列出 profile 依赖清单(npm / git / 已内嵌三类,UI 手动勾选内嵌的数据源) |
plugin/check-update |
About 页检查更新:拉 registry 最新版本,与本地比较返回 {current, latest, outdated} |
plugin/open-url |
用系统默认浏览器打开 http/https 链接(About 页作者 / 仓库 / 求 Star) |
task/list / task/get |
列出 / 查询任务中心任务(状态、阶段时间线、进程输出) |
GET /dsh-pack/ball.js |
下发悬浮球脚本(同一 prefix 路由,非 RPC;no-store) |
所有端点返回 {ok:true, value} | {ok:false, error:{code,message,details}};非阻塞端点立即返回 {taskId},执行进度经任务中心(内存注册表,task/list / task/get RPC 直读)呈现。
整合包会装进各种 client 插件,有的会把侧边栏或设置页搞坏 —— 那时用户进不去设置界面,连换包、卸载都做不到。所以入口不能挂在 DSH 的 slot / React 树里:
- 由宿主注入(ball-host.js):走
webserver/index-inject结构化行(桌面壳唯一生效通道)+webServer.tapIndex(web 形态兜底),二者靠标记去重;行内容用内联 script 而非script-src行 —— 官方页面解释器对两类行处理不对称(内联无 await 不可能"加载失败",script-src失败会 reject 成致命错误把页面带下水),所以自己建<script src>并吞掉 onerror。宿主活在 Node 进程里,别的插件把前端拆了球还在。 - 球本体(ball.js →
lib/ball.js,esbuild iife):position:fixed自建 DOM、纯尺寸驱动;拖拽用 document 级 pointer 监听 + 3px 点击阈值,拖拽中transition:none、松手settle()吸附(四分之一区域、横纵轴独立、可组合出四角);位置只存「吸附边 + 离边偏移」(resize 仍贴边,脏数据夹回可视区);贴左整体镜像;关键样式!important顶回宿主皮肤的几何特征拦截。 - 点击 = 打开官方设置页(ball-settings.js):官方设置界面本身就是完整的弹窗,诉求只是「进不去」,不是「没有这个页面」,所以不另造页面。
SettingsRoot用组件内部 state 管开合、对外没有 API(ctx.remote.settings.openSettingsDocument()是「在本地打开设置文档」,不是打开这个弹窗),唯一的路是点它注册到settings.trigger的按钮——好在那个按钮属性是语义化的button[aria-haspopup="dialog"][aria-expanded](无哈希类名),而合成click()不要求元素可见:侧边栏被主题藏起来、被挤扁、变轨道态,只要弹窗组件还挂着就点得动。点开后再把导航切到本插件分区(认data-dspack-nav-icon标记,不依赖文案)。 - 挂载有两道保险:宿主注入(主)+ 客户端插件
mountBall()(补,幂等守卫去重)。 - 已知边界:若坏插件让侧边栏那棵 slot 子树整个崩掉(React 抛错),设置弹窗与触发按钮都不存在,此时谁也开不了官方设置页——球会如实提示「没找到官方设置入口」,不假装成功。那种场景要的是急救(禁用坏插件)或自建面板,本轮都不做。
window.__dspackBall暴露句柄:openSettings()/setAction(fn)/showTip()/settle()/destroy(),要接别的动作直接换。
src/ host 插件 + client 插件源码
index.js / index.d.ts host 插件入口(dspack-host)与类型声明
rpc.js 自建 /dsh-pack 前缀路由(registerRpc)
endpoints.js 全部 RPC 端点(ENDPOINTS 表)
channel.js CHANNEL / PROFILE_NAME_RE / RESERVED_PROFILE_NAMES
runtime.js resolveRuntime(profileContext 事实来源,含 env 兜底)
host.js / host-node.js Host DI 边界 / NodeHost 实现(node:fs/crypto/child_process)
profiles.js list / create / delete / state(dsh-packforge.json)
junction.js desktop + skills/.agent-presets junction 换指(repointHomeArtifact)
migrate.js / migrate-helper.js 首次迁移脱管 helper(杀桌面→换指→重启)
ensure-manager.js 空整合包管理器基线(复制 manager + fflate)
tasks.js 任务中心内存注册表(task/list / task/get)
progress.js / progress-main.cjs / progress.html / progress-window.ps1 切换进度窗(electron + 打包态 WPF)
packaged.js 打包态判定(切换进度窗走 electron GUI 还是 WPF)
settings.js 客户端设置面板(管理 / 导出 / 市场 / 关于 tab + 任务中心面板 + 弹窗)
client.js / client-plugin.js / client-rpc.js 客户端 bundle 入口 / 插件面(dspackforge)/ rpc 封装
ball.js 悬浮球(纯几何 + mountBall;entry 见 ball-entry.js)
ball-settings.js 球的默认动作:点官方触发按钮打开原设置页并切到本插件分区
ball-entry.js 悬浮球浏览器入口(自我挂载,打成 lib/ball.js)
ball-host.js 悬浮球宿主接线(index 注入行 / tapIndex / 脚本资源读取)
core/ vendored 整合包引擎(仅 import fflate)
index.js 公共 API 出口
host.js Host 接口(DI 边界)
dspack.js ZIP 容器(buildDspack / parseDspack / encodeText / decodeText)
pack.js packProfile / packHome / dspackEntryPath / summarizeHome
install.js 安装(installPack 等;home 级内容经 storeHomeRel 落槽位)
manifest.js manifest v5 构建 / 校验
scan.js 目录扫描(安全规则 + junction 跟随)
security.js 打包安全规则(DENY_*)
special.js 特殊目录识别
inspect.js 检视(inspectPack 等)
workspace.js 工作区配置 .dshpkcfg
repo.js repo 形态导出(exportRepo)
market.js 市场索引读取
discovery.js profiles / homes / DSH 版本发现
home-store.js home 级换指槽位布局(HOME_ARTIFACT_STORE / storeHomeRel)
lib/client.js client 浏览器 bundle(pnpm bundle 生成)
lib/ball.js 悬浮球浏览器 bundle(pnpm bundle 生成,宿主路由下发)
cordis.patch.yml bundle 注入 patch
scripts/bundle-client.mjs esbuild 打包脚本(client)
scripts/bundle-host.mjs esbuild 打包脚本(host,自包含 node 依赖)
scripts/bundle-ball.mjs esbuild 打包脚本(悬浮球,浏览器 iife)
test/ 单元测试(node --test)
pnpm install
pnpm test # 单元测试(node --test)
pnpm bundle # 生成 lib/client.js(浏览器)+ lib/host.js + lib/ball.js(悬浮球)- 自包含重构完成:无
@dsh-packforge/*依赖,仅 UI + 后端(AI 整合包管理已移除)。 - v5 r2 / v3 r2 导入侧已实现:vendored 阶段 0 预检 + 统一安装算法(
--prefer-offline/ 闭包完整自动--offline+file:引用)、DSHLvendor:方言直挂、dshVersions交集决策、r2 字段结构校验(test/vendored.test.js)。 - v5 r2 导出侧已实现:依赖清单端点(
pack/dependencies)+ 导出面板手动勾选内嵌(round-trip 复用 / registry 原件 / 本地重打包-local.N后缀三处一致)+ vendor 档位(auto/off/full,full 档沿 lockfile 收齐传递闭包:registry 原件 →.pnpm重打包回退 → skip 记档;闭包条目对账按 v5 §12 闭包规则放行)+dshVersions/launchers兼容性编辑(.dshpkcfg持久化 + 打包前强校验)+ 体积阈值(>500 MB 警告 / >2 GiB 拒绝)。 - 多 profile 切换(junction 换指 + 首次迁移脱管 helper)已实现并通过测试。
- skills / .agent-presets per-profile junction 隔离已实现并通过测试(真实 Windows junction 冒烟:skills/.agent-presets 被跟随、profiles/desktop 被跳过、.dsh-pack 被排除)。
- 工作区配置
.dshpkcfg、市场浏览、任务中心(非阻塞 + 内嵌面板)已接线。 - 待办(r2):安装端闭包 tarball 的 pnpm store 预填充(full 闭包包已校验内嵌,但传递依赖尚未喂给 pnpm,故含闭包条目时保守用
--prefer-offline而非--offline)、发版前离线 dry-run 自检、export/install端到端真实 pnpm 环境验证。 export/install端到端仍需真实 profile + pnpm 环境验证。