Repository navigation
Releases
v0.13.0
升级须知
remote sync、pull、adopt、remote add被中断(进程被杀、恢复失败)后,plan、status、apply、pull、adopt、remote add/sync/remove以退出码 3 拒绝,并给出dshenv rollback <快照 id> --yes;apply被中断只警告,重跑apply后消失。profiles/<名字>/已存在但package.json缺失、损坏或缺dsh.profile时,该 Profile 的计划为 BLOCKED,不再当作新建。- 团队 lock 只能包含团队 manifest(或团队 overlay)声明的插件;团队仓库限 1 万个文件、单文件 10MB、总计 100MB。
- 不带
-p的source clone/sync不能再指向envctl/sources下的托管克隆。 - 新快照带递增序号,
rollback与gc按序号排新旧,不再受时钟回拨影响;旧快照视为比所有新快照更早。
新增
remote add/remote sync新增--expect <commit>:要接受的 commit 不是它时以退出码 3 拒绝;预览末尾给出带--expect的命令。
修复
apply失败回滚不再删除已存在的 Profile 目录(package.json损坏时曾连同 sessions 一起删除)。- 恢复软链接的 skills 目录时按真实路径解析链接目标;envctl 本身是软链接、skills 是相对链接时,
rollback曾删掉旁边同名的无关目录。 - 两个
migrate --to同一空目录不再删掉先完成的那份 envctl;目标只在为空时原子替换,staging 名带随机后缀,中断后重跑可以续做。 purge对克隆目录及其父目录取真实路径校验,中间层软链接不能再把 envctl 外的仓库移进 trash。gc删除快照前先改名为隐藏目录,中途被杀不会留下能被rollback选中的半份快照;gc也清理 DSH skills 目录里残留的.tmp-*。dsh plugin add装好但以非 0 退出的插件,失败的 apply 也记录归属,之后可以照常卸载。- skill 的临时目录改为
.tmp-开头,被杀后不再被当成新 skill;跨设备移动 skill 时先复制到临时目录再改名,不会只删掉一半。 apply的撤销步骤失败时写进错误信息与 journal;中断时已提交的apply先说明已完成再退出;Ctrl-C 也会立即停止 DSH 版本探测与--dump-config。pull写入开始/结束记录,pull、adopt恢复失败时写明原因并给出快照 id;adopt的归属检查与随后的pull在同一把锁内,写入前建快照。- 环境锁:不接管另一个 pid 命名空间的锁;释放前核对内容;修正刚释放的锁文件 inode 被复用时把新持有者的锁当成陈旧锁删除的竞争;写锁失败时删除空锁文件;残留的 reclaim 目录超过 5 秒自动清除。
- Profile 锁在
dshenv退出时释放,包括第二次 Ctrl-C;快照与 journal 写入后 fsync;rollback恢复 skills 先复制到临时目录再替换,中途失败的报错给出 pre-rollback 快照 id。 - 读 DSH 目录时跳过 FIFO 等非普通文件,不再永久挂起;悬空软链接目标拒绝写入;root 运行时保留被覆盖文件的属主。
- 团队仓库改为
git cat-file --batch一次读取;overlay use写选择文件时持环境锁。
v0.12.1
修复
- 同一进程内并发获取环境锁时,后到者可能把刚创建、还没登记的锁误判为前一次运行留下的锁并删掉,两方同时持锁,其中一方的清单改动丢失。现在锁在创建成功的同时登记,并按每次持锁唯一的内容识别。
v0.12.0
升级须知
rollback <id>只接受完整快照 id 或完整 operation id,不再按任意-后缀匹配;rollback ''报错而不是恢复最新快照。- envctl 目录不能是 DSH home 本身,也不能位于
<home>/profiles、<home>/skills之内;已这样配置的需先把目录移走。 config set/tools config set的值以{/[开头却不是合法 JSON 时报错,作字符串写入要加引号('"[WIP] x"');超出安全整数或非有限的数字同样拒绝。- overlay 名不能以
.或-开头;--as不能以.或-开头、不能含/或\;profile 名限 100 字符,npm 包名按 npm 规则限 214 字符。
修复
- scp 形式(
user:pass@host:path)的 git URL 也按内嵌凭据拒绝;URL 带?token=时报错不再回显 token。 - 拒绝
ext::等<transport>::git URL,所有 git 调用限定GIT_ALLOW_PROTOCOL=file:git:http:https:ssh。 - git ref 与 remote 分支名按
git check-ref-format主要规则校验,空 ref 在克隆前拒绝;a#b#c这种多个#的 spec 报错。 install遇到损坏的 lock 不再在写入 manifest 后报失败;update在 lock 损坏时不改 manifest。- 显式传入的空参数(
--as ''、--ref ''、capture -o ''、status ''、adopt ''、--harness-source '')报错,不再当作没传;file:空路径报错。 web start --port拒绝 fetch 不会连接的端口;web stop -p写错 profile 名时报错并提示相近名;pull -p、capture -p同样提示相近名。source clone认git+前缀,--as/--package/--new-profile没给--profile时报错,--package在克隆前校验;source show目录不存在时报错。--json下config set不再往 stderr 写纯文本警告;tools config unset与config unset统一校验配置路径。
v0.11.0
升级须知
source sync(旧名source pull)改为不带--yes只预览,与remote sync一致:脚本里直接移动检出和 lock 的调用要加--yes。- 本地插件与 skill 的源码摘要改为带长度边界和类型标记的新格式,lock/state 里的旧摘要不再兼容。升级后首次
plan会为本地插件列出一次update,apply成功后写入新摘要。请在 DSH 里修改 skill 之前先执行一次apply --yes重建 skill 基线;否则只在 DSH 改过的 skill 会被plan报为清单改动,pull报双侧冲突,需要用--prefer指定保留哪一侧。回滚到升级前的快照同理,其中的 skill 基线失效,原有 skill 按未管理处理。 - 新快照会保存单个软链接 skill 目录的实际内容;恢复时该 skill 变为独立目录,不改写外部链接目标。旧快照只有链接的内容无法追溯补齐。
新增
source sync新增--dry-run/--yes:不带--yes时 fetch 后预览检出与 lock 会移到哪个 commit,有变化时退出码 2,不移动任何东西。verify --timeout <秒>:插件还在热加载时继续查询,最多等这么久,与apply --verify-timeout相同;默认只查一次。dshenv plugins official列出已安装的 DSH 自带的官方 bundle(默认关闭、在 Profile 里选中才加载的那些,随 DSH 版本变化),标出清单在哪些 Profile 声明了它们,并给出install in-box:命令;--json输出dshVersion与bundles。- Profile 还不存在、名字是 DSH 的模板 Profile(
acp、headless、sdk、sdk-minimal、web)时,apply先让 DSH 按自己的模板创建它(dsh --profile <名字> --dump-config),再写 bundle 与补丁。此前只声明官方 bundle 或补丁的新 Profile 一律blocked,因为只有安装 npm 插件才会创建 Profile。预览在Profiles DSH creates from its own template first下列出,plan --json新增createdProfiles。 doctor注明数据目录来自--envctl-dir、DSHENV_HOME还是默认位置,--json新增paths.managerDirSource。- 管理全局补丁
$DSH_HOME/cordis.patch.yml:清单与 overlay 新增顶层patches:,apply写进该文件的受管块,pull(不带--profile时)收回手写的条目,plan列出未纳管的全局条目,并提示被全局条目覆盖、实际不生效的 Profile 条目。--profile不涉及全局文件,但仍提示全局条目对该 Profile 的覆盖;覆盖按字段比较,全局条目写了disabled的 id 单独注明(DSH 插件页启停不了它);plan --json新增homePatchOperations、unmanagedHomePatches、shadowedPatches(项里的disabled列出这类 id)。 - dshenv 数据目录可以放在 DSH 主目录之外:
--envctl-dir <path>或环境变量DSHENV_HOME指定,默认仍是<DSH 主目录>/envctl。DSH 不读这个目录,无需改动 DSH。 dshenv migrate --to <dir> --yes把现有数据目录搬到新位置:- 先复制并核对,再在持有环境锁时先写下记着新位置的
dshenv.moved,然后清空旧目录(先删manifest.yaml、lock.json、state.json)。之后在旧位置运行的命令——包括搬迁时正在等锁的——以退出码 3 拒绝并给出新位置,不会在旧位置悄悄建出新环境;清空中途失败时旧位置也已有标记。 - 软链接形式的数据目录按内容复制,链接与目标都保留,只在目标里写
dshenv.moved;路径结尾带分隔符时同样按软链接处理。 - 不是 dshenv 数据目录(没有
manifest.yaml、lock.json、state.json)的不搬;再次对旧位置运行会说明已移到哪里,也可以把数据目录移回原处。 - 清单、overlay、lock 与
backups/快照(含existing-overlays/)中指向旧数据目录内部的路径改写为新位置;读不了的快照原样保留并列出。之后plan/apply --yes让 DSH 按新路径重装对应插件。 - 数据目录与 DSH 主目录在不同文件系统上时,skill 移入 trash 改为复制后删除。
- 先复制并核对,再在持有环境锁时先写下记着新位置的
init持环境锁执行。- 明文密钥检查:补丁里凭据类的键名(以
apiKey、secret、secretKey、privateKey、accessKey、password、token、Authorization、credentials结尾,或以PAT结尾,如OPENAI_API_KEY、clientSecret、AWS_SECRET_ACCESS_KEY、GH_PAT;以Env结尾的如apiKeyEnv除外,maxTokens、max_token这类计数也不算)下的非空字符串,password类键下的数字,任何键下带密码或 token 的 URL(如postgres://u:pw@host/db),以及 MCP 客户端行(@deepseek-ai/dsh-mcp-client)headers里的全部值;它的env按键名判断。输出只给出目标 / 条目 / 键路径,从不打印值;判断"新的"明文按值比较,清单里已有的同一个值不算新。pull不收含新明文的条目,把它们留在cordis.patch.yml里、放到 dshenv 受管块之后(它可能覆盖块里条目插入的行),同一 Profile 的其他条目照常同步。新明文写进了 dshenv 受管块时,这个目标的补丁不收,apply也被阻断,直到在 DSH 里改成*Env写法;--prefer skip时它算作跳过。清单里已有的明文旁边改了别的字段,照常收回。config set、tools config set等写清单或 overlay 的命令拒绝写入新的明文值(退出码 3),包括替换已有的明文值。plan、status、doctor在清单或 overlay 已有明文时在 stderr 警告,不影响退出码。- 团队仓库的清单或 overlay 含明文时,
remote add/sync拒绝。
apply先用dsh --dump-config在临时的 DSH 主目录副本里组合一遍要写入的 Profile 补丁与全局补丁:预览列出 DSH 会因为 id 匹配不到(或name不符)而跳过的条目。apply --yes只在两种情况下停下、不做任何改动:Profile 条目的 id 现在在任何一层都匹配得到、写入后却匹配不到;全局条目的 id 在每个已有 Profile 里都没有对应的行。已经匹配不到的 id(例如 0.2 起移进 preset 的time-context)换层也照常放行;本次 apply 要装、启用、停用或卸载插件的 Profile 不检查,因为它的行要等插件步骤之后才确定。预览的--json新增patchTargets。- 全局补丁有变化时,
apply探测每个已有 Profile 的热重载状态,热重载关闭的列为需要重启。 status与doctor报告 DSH 加载时跳过的 bundle(dsh: skipping profile bundle …),status因此为degraded(退出码 5)。status只在 DSH 版本通过门禁时才运行--dump-config,DSH 每次运行它都会重写 Profile 的cordis.yml。status还列出各 Profilecompatibility.json里 DSH 认可的版本豁免(不合规的记录在 stderr 警告),它们绑定精确的 DSH 版本,不进清单;以及清单仍启用的退役 bundle。给出插件时只列与它有关的项。status --json新增skippedBundles、versionExemptions、retiredBundles。pull --prefer skip:两边都改过的补丁目标(一个 Profile 或全局层)和 skill 原样跳过,基线不动,下次pull仍报告,其余照常收进清单。有跳过时退出码为 6,--json的skipped列出它们。- 清单声明并启用了已退役的 bundle(
@deepseek-ai/dsh-experimental-schedule-bundle,DSH 0.2.1-alpha.1 退役)时,plan警告并给出remove命令(别名只在 overlay 里声明时带--layer overlay):DSH 0.2.1 每次加载都会把它从 Profile 删掉,它会一直显示为漂移。plan --json新增retiredBundles。
变更
-
声明 DSH 官方 bundle 时(
install in-box:、capture),默认别名去掉dsh-experimental-前缀和-profile、-bundle后缀,如agent-team、voice-input;已有的别名不变。 -
用旧名
source pull时在 stderr 提醒它就是source sync,方向与dshenv pull相反。 -
帮助文字:
self-update说明它直接执行、不需要--yes;overlay use --none与--no-overlay互相说明区别;plan提示用status <alias>看单个插件。 -
清单与 overlay 允许
${...}:DSH 不做插值,按原文读取。此前一律拒绝,含${的配置(如提示词模板)所在的整个 Profile 都无法用pull收回,config set也不会抄 DSH 组合出的配置。 -
版本门禁放行 DSH
0.2.1版本族:冒烟(scripts/smoke-dsh.sh)默认改用 DSH 官方 bundle@deepseek-ai/dsh-experimental-agent-team-profile,它的版本跟随 DSH,0.1.7-rc.2、0.2.0-rc.2、0.2.1-alpha.1 都不带豁免 13 步全部通过;compat 矩阵把0.2.1-alpha.1列为必须通过。第三方插件是否支持 0.2.1 仍看它自己的 peer 范围(如@nanmicoder/dsh-agent-teams@0.1.22只到 0.2.0-rc.2,DSH 会拒装)。dshenv new生成的 peer 范围加上>=0.2.1-0 <0.3.0-0,否则按 npm 默认语义0.2.1-alpha.1不匹配。 -
版本门禁放行 DSH
0.2.0版本族(npm0.2.0-rc.2冒烟全部通过),0.1.7照常放行。dshenv new生成的插件 peer 范围改为>=0.1.7-0 <0.2.0-0 || >=0.2.0-0 <0.3.0-0,可以装进 DSH 0.2(按 npm 的预发布版规则,每个 minor 单写一段,0.2.0-rc.2才能匹配)。兼容性冒烟的默认插件改为@nanmicoder/dsh-agent-teams@0.1.22。 -
数据目录本身或其中任何顶层条目(文件或目录)是软链接时,命令以退出码 3 拒绝,并提示改用
DSHENV_HOME与migrate。此前用软链接把它们放到别处的环境,升级后先运行dshenv migrate --to <新位置> --yes,再把export DSHENV_HOME=<新位置>写进 shell 配置、CI 与容器环境;输出里有rewrote时再dshenv apply --yes,让 DSH 按新路径重装本地插件。
修复
source sync -p从读取清单、检查团队所有权到更新 Git 与 lock 全程持有环境锁,损坏的 lock 在改源码之前拒绝;提交校验或 lock 写入失败时恢复原提交和分支,如期间出现外部修改则保留并报告恢复失败。- 源码摘要明确分隔文件路径、内容、类型和可执行标记,避免不同文件树因拼接结果相同而漏报变化。
- 单个 skill 目录为软链接时,快照不再随外部目录的修改或删除而改变;技能内部的软链接仍按原样保存。
pull把insert行里以./、../开头的插件名视为本机路径,收进本机 overlay:DSH 按补丁文件所在目录解析它,换一台机器或换一层(Profile 层 / 全局层)就指向别处。此前它进了团队共享的 base 清单。- 停用的插件不再在
cordis.patch.yml里留着补丁块:DSH 不加载停用的插件,它的补丁每次启动都报entry not found,plan却显示已同步。补丁仍保留在清单里,重新启用后apply写回。(升级后第一次plan会为带补丁的停用插件列出一次configure。) - 文档说明改清单的命令会按固定格式重写
manifest.yaml与 overlay 文件,去掉其中的注释(包括overlay create生成的示例注释)。 - 团队 skill 目录里本机新增的文件算作漂移:
remote show列为added locally,remote sync拒绝,--discard-local-changes时一并删掉;此前不报漂移,团队删掉这个 skill 后只剩本机文件,apply会把一个没有SKILL.md的 skill 装进 DSH。 - 团队仓库的 skill 里有
node_modules/或.tmp-*文件时,remote add/sync以退出码 3 说明它们不会复制进 DSH;此前接受下来,却永远到不了 DSH(.tmp-*的改动也不会触发更新)。skill 的复制与摘要现在跳过同一组文件。 - 团队仓库里只差大小写的 skill 名、目录或文件(如
skills/Foo与skills/foo)以退出码 3 拒绝;在 macOS/Windows 不区分大小写的文件系统上它们是同一个,此前之后每次 sync 都会以本机改动为由拒绝。 - 对 overlay 已
remove掉的插件install --layer overlay同一来源时,只撤掉这次删除,不再顺带把来源固定在 overlay 里;update --layer overlay到当前生效的版本时报告没有变化、不写入。此前这两种情况都会把 overlay 固定在 base 的来源上,之后 base 升级到不了这台机器。 - 本地 overlay 可以用
update --layer overlay改团队 lock 固定的 npm 插件(与install --layer overlay一致),团队的 lock 条目保持不变;此前以「pinned by the team lock」拒绝。 remove --layer base后,若选中的 overlay 还有这个别名的条目,在 stderr 提示它会套到之后以同一别名声明的插件上,并给出remove <别名> --layer overlay;这条命令现在也能删掉 base 已没有的别名在 overlay 里的残留条目。- 没有 npm 时
self-update说明 npm 不在 PATH 里;此前报「exit code undefined」。 purge预览与真正执行一样拒绝指向 DSH_HOME 之外的补丁文件或受管克隆;此前预览说「Would purge」,加--yes才拒绝。init遇到没有清单但已有lock.json或state.json时,写任何文件之前就拒绝并说明;此前先写出清单再失败,之后再init报「already initialized」,state.json一直没写出。- 容器示例 README 的版本 tag 改为
v<版本>占位(此前停在v0.3.0);DSH 版本升级文档说明门禁由VERIFIED_FAMILIES决定,isCompatibleDshVersion与knownDshFamily都读它。
v0.10.2
升级须知
tools系列与plugins config set也检查 DSH 版本:tools遇到未测试的 DSH 版本以退出码 4 结束(可加--allow-untested-dsh),找不到 DSH 时退出码 4(此前 3)。- 选中的 overlay 无法与 base 合并时,
doctor以退出码 3 结束(0.10.1 只警告)。 - 插件补丁 id 对不上 DSH 加载它的那一行时,下一次
plugins config set会把补丁改到正确的 id,清单随之变化。 - 0.10.1 的「别名不能以
@开头」放宽为只拒绝@profile和@mount:…;按 0.10.1 须知改过名的别名无需改回。
修复
- 插件别名只拒绝 dshenv 自己的块名
@profile和以@mount:开头的名字,lock.json不再套用这条限制。0.10.1 拒绝所有以@开头的别名,清单里已有@x这类别名时remove、install --as、plan全部以退出码 3 失败,升级须知里的办法也执行不了;手工改好清单后lock.json里的旧别名还会让plan报错。 - 撤销一次清掉了 overlay 选择的 rollback(
dshenv rollback --yes恢复它留下的快照)时,重新选中那个 overlay;此前 overlay 文件回来了但选择丢了,之后的plan/apply只用 base,会卸载 overlay 声明的插件。期间已另选了 overlay 时保持不变。 - 被结束的 DSH 没有被回收、成了僵尸进程时(如 dshenv 在容器里以 PID 1 运行且没加
--init),中断apply后照样释放它留下的package.json.lock;dshenv.lock的持有者是僵尸进程时同样接管。此前锁一直留着,之后的命令要等到超时。 dshenv.lock里的 pid 正好是当前进程(容器重启后 pid 相同)时按陈旧锁接管;拿不到锁时报错写出持有者的 pid、主机名和加锁时间,并说明 pid 被复用或主机名变了时可删除锁文件。此前一直报Environment lock is already held,没有处理办法。- 再次
adopt由 dshenv 安装的 git 插件时,保留 base 声明的ref/commit和 URL 写法;此前 DSH 里记的 URL 带git+前缀,与清单对不上,ref/commit被丢掉、URL 被改写。 source clone --profile用另一种写法(结尾的/、.git有无)指向清单里的同一个仓库时,仍克隆并锁定清单固定的 commit,清单与 lock 沿用清单的 URL 写法;此前会悄悄删掉固定的 commit,lock 锁到最新提交。- Windows 上
DSH_CLI指向 PATH 里没有的命令时也以退出码 4 结束(此前为 5):命令名先按 PATH 与 PATHEXT 查找。 plugins config set发现插件已有的补丁 id 对不上 DSH 加载它的那一行时(apply 前写的、或 0.9.x 留下的),把补丁移到正确的 id 上并说明;在 overlay 里写而补丁属于 base 时以退出码 3 提示先用--layer base。此前补丁永远不生效,plan却显示已同步。tools config set写 base 时,base 已有这一条但没有自己的 config(如只做过disable),同样不抄 overlay 补丁里的值或本机路径(退出码 3)。doctor选中的 overlay 无法与 base 合并时以退出码 3 报错(0.10.1 起被当作 base 无效、只警告);lock.json无效时在 stderr 警告。tools系列与plugins config set读取 DSH 组合配置前检查 DSH 版本:tools遇到不支持的版本或找不到 DSH 时以退出码 4 结束(此前照用旧 DSH 的结果写清单,找不到时退出码 3),config set不抄组合配置并说明原因。envctl/skills是软链接时,rollback 到没有 skills 的快照只删除链接本身;此前会把链接指向的外部目录(如 dotfiles 里的 skills,连同其中非 skill 的文件)整个删掉,且不进 trash。链接悬空时内容写回原目标并保留链接。- 旧写法
tools config <tool> <路径> <值>带--help、--version(或写成help tools config …)时只显示帮助或版本;此前会真的写入清单并固定整个预设。 - 团队配置里写成
link:…、file:/…、workspace:…的 Git 地址按本机路径拒绝;此前被当成网络地址放行,pnpm 会把它当本地目录链接安装。
v0.10.1
升级须知
- 插件别名不能再以
@开头;清单里已有这样的别名时,先用--as换个名字重新声明。 install in-box:<包>遇到不是 bundle 的包(能从 DSH 安装位置找到时)以退出码 3 拒绝。- skill 摘要计入可执行位:
envctl/skills与 DSH 副本只差可执行位的 skill,升级后第一次plan会列为更新。 - 退出码调整:
DSH_CLI指向 PATH 里没有的命令为 4(此前 5),self-update --to <不存在的版本>为 3(此前 1)。
修复
- 团队提交只改了可执行位时,
remote sync也会同步,skill 摘要同样计入可执行位,改动会一直到 DSH 里的副本;此前显示「Files: no changes」,副本一直不可执行。(升级后envctl/skills与 DSH 副本只差可执行位的 skill 会在plan中列为更新。) remote sync --ref <同一分支上更旧的 commit>说明它比固定的 commit 旧、要退回请用rollback;此前报成「历史被改写」。- 子命令组里的
help(如dshenv plugins help config set、dshenv web help foo)与顶层help一样:显示所指命令的帮助,未知命令以退出码 3 报错;此前显示的是上一级的帮助并以 0 结束。 DSH_CLI指向 PATH 里没有的命令时以退出码 4(找不到 DSH)结束,与指向不存在的路径一致;此前退出码 5、只提示「probe execution failed」。- 选中 overlay 时 base 清单无效,
doctor也只警告并照常探测 DSH;此前直接以退出码 3 结束。 - dsh web 已在运行时,
web start --port <另一个端口>以退出码 3 说明它在哪个端口、需先web stop;此前静默返回原来的地址。 self-update --to <npm 上没有的版本>以退出码 3 报「npm has no version …」,与install一致;此前退出码 1。- 被生效 overlay
remove: true去掉的插件,enable/disable --layer base也在 stderr 提示这台机器上不会变(此前只有update、install提示)。 - 插件别名不能以
@开头;此前--as @mount:foo被接受,其受管块与foo的挂载块同名,apply 会删掉foo的挂载。 install in-box:<包>在能从 DSH 安装位置找到该包时,拒绝不是 bundle 的包(退出码 3);此前写进清单、apply 后 DSH 每次启动都跳过它,plan却显示已同步。purge被拒绝(软链接指向 DSH_HOME 之外)时不再留下空的 trash 目录和只有开始的 journal 记录。- 结束 DSH 子进程树时,没有
ps(如 slim 容器镜像)的 Linux 改从/proc找后代进程;此前只结束直接子进程,pnpm --dir … dsh的孙进程留着管道,超时失效、环境锁一直被占。 rollback与gc --dry-run的帮助说明按实际范围更正。
v0.10.0
升级须知
- 同时设置了
DSH_CLI又传--harness-source时,现在用--harness-source指定的源码目录(此前DSH_CLI优先)。 DSH_HOME开头的~会展开为用户主目录,为空时回退到~/.dsh;此前因此被管理在别处(如当前目录下的~/)的环境需要迁移过去。- skill 的摘要改为算整个目录:
envctl/skills与 DSH 副本在package.jsonfiles名单外的文件上不一致时,升级后第一次plan会列出更新,apply用清单的副本(DSH 副本移进 trash)。 tools写 base 遇到已生效的 overlay 补丁或本机路径、config set遇到全部禁用的补丁时,以退出码 3 拒绝;改用--layer overlay,或先启用补丁。- 已用相对路径订阅的
remote.json不会自动改写;remote remove --yes后重新remote add一次即记为file://地址。
变更
- 显式的
--harness-source优先于环境变量DSH_CLI(此前被DSH_CLI静默覆盖,doctor还提示「请提供--harness-source」);清单的environment.harness.sourceDir仍排在DSH_CLI之后。
修复
DSH_HOME、--dsh-home开头的~(~、~/、~\)按 DSH 的规则展开,DSH_HOME为空或只有空白时视为未设置;此前 dshenv 管的是当前目录下字面量~的目录或直接报错,与 DSH 实际读取的目录不同。remote add <本机路径>把相对路径记为file://地址;此前原样记下,换个目录再 clone(如 rollback 后remote sync)会失败或拿到别的仓库,origin 校验也被跳过。- 选中的 overlay 对某个插件只改了
enabled(或补丁)时,adopt仍把 base 更新到实际安装的版本,base 自己的enabled不变;此前直接跳过,base 停在旧版本,下一次apply会降级。 tools config set、tools enable/disable写 base 时,若 base 还没有这一条而 DSH 当前组合出的配置已含 dshenv 补丁(如已 apply 的 overlay 写的),或含本机路径,以退出码 3 拒绝;此前把 overlay 的值(本机路径等)抄进共享的 base。install --layer overlay装的版本与生效的来源相同时不再写 overlay;此前提示「nothing changed」却在 overlay 里固定了来源,之后 base 的升级到不了这台机器。plugins config set只把键写进enabled不为false的补丁,全部禁用时以退出码 3 拒绝;config get同样跳过禁用的补丁。此前写进禁用的补丁,apply跳过它,值永远到不了 DSH。- rollback 到
remote add(或 pull)之前时,若删掉的正是选中的 overlay,会清掉选择并在输出里说明;此前选择悬空,之后所有命令都报Overlay 'team' not found。 - rollback 后仍在安装着、清单仍声明同一路径的本地来源插件,保留回滚前
lock.json记录的摘要;此前恢复旧 lock 丢掉摘要,清单没变也会重装并要求重启。 - skill 的摘要算整个目录,不再套用其
package.json的files过滤;此前名单外文件(如run.sh)的改动在plan中显示已同步,永远到不了 DSH。 plugins config set新建补丁时用 DSH 实际加载该插件的那一行的 id(bundle 插件、--as改名的插件与别名不同),并在 stderr 说明;此前用别名,DSH 报entry not found、配置从不生效,plan却显示已同步。- 再次
adopt时,只有 overlay 声明插件的 Profile 保留它在lock.json里的条目和所有权记录;此前输出「Nothing to adopt」却把它们清空,之后plan要求重装、锁定的版本或 commit 丢失。 plugins config set只在没有 dshenv 补丁生效时抄 DSH 组合出的配置;此前有生效 overlay 给该插件写了补丁时,--layer base会把 overlay 的值(本机路径、内网地址等)抄进共享的 base。抄来的配置含本机路径时,写 base 以退出码 3 拒绝。plugins config set读不到 DSH 的组合配置(Profile 未创建、找不到 DSH、--dump-config失败)时在 stderr 说明原因;此前静默只写这个键,与 0.9.0 的说明不符。- 成功的
apply从lock.json删掉 base 与所有 overlay 都不再声明的别名的条目(团队条目保留);此前删掉的 Git 插件用同一别名、另一个 ref 重新加入时,plan不报 blocked,apply装回旧 commit。 - 再次
adopt由 dshenv 安装的 Git 插件时保留 base 声明的ref/commit(commit跟随实际安装的版本);此前被替换成只有 URL 的来源。 source clone --profile读到的package.json包名与install按仓库名记下的不同时,同一仓库地址的条目改用读到的包名;此前以别名冲突拒绝。install --package的帮助改为说明 Git 来源默认取仓库名。- Git 地址里的本机路径(相对路径、不带
#的/abs/x.git)统一记为file://地址;此前repos/x会被 pnpm 当成 GitHub 简写。install把ssh://、git://地址按 Git 处理;此前不带.git时被当成 npm 包名。 source sync --profile可以把受管 clone 退回到更旧的 commit,plan提示的--ref <清单 commit>由此生效(此前输出「Updated … from X to X」并以 0 退出,什么都没变);detached HEAD 不给--ref时跟随清单的ref或远端默认分支(此前报错);移动后与清单锁定的commit不一致时在 stderr 提示。对envctl之外的检出指向更旧的 commit 时以退出码 3 拒绝,不再静默成功。install <url>#<commit>与 lock 中的 commit 不同时提示如何锁定。- 写 base 的命令(
install等、adopt、pull、remote sync)在--no-overlay或--overlay <其他>下也检查新 base 能否与本机保存的选中 overlay 合并,不能时以退出码 3 拒绝并说明;此前会写成之后所有命令都报cannot change package或Duplicate package的状态。 - 快照与 rollback(以及 pull、sync 失败时的恢复)原样保留
envctl/skills里的软链接;此前相对链接被改成绝对路径,plan随之报告 skill 改动。 skills目录下的node_modules(在 skills 目录里npm install留下的)不再被当作 skill,团队仓库的skills/node_modules/也被忽略;此前pull --yes、adopt --yes因此报 ENOENT 失败。- 子命令层面的未知命令加
--help(如dshenv web foo --help、dshenv help web foo)与未知命令加--version(dshenv foo --version)按未知命令处理,退出码 3;dshenv help web start显示web start的帮助,不再是web的。 - 文档:使用教程关于 rollback 跨过
remote add --replace与「不带 id 的 rollback」的说明、锁的重试间隔已更正;GitHub Actions 示例的注释不再说plan会以 4 结束;模板 README 改用mark-restarted;README 说明 DSH 配置了webserver.port时忽略--port。 install --layer base在生效 overlay 用remove: true去掉或改了来源的插件上,像update一样在 stderr 提示这台机器上实际没变(update、config set/unset写被 overlay 去掉的插件时同样提示)。install的本地路径必须是目录;此前一个文件(如打包的.tgz)被登记成链接目录,到apply才失败。- DSH 组合出的插件配置含
${...}时,config set不再抄它(清单不允许),只写这个键并说明;此前以退出码 3 失败。 config unset从该插件所有设置了这个键的补丁里删掉它;此前只删第一个,其余补丁里的值仍然生效。adopt选别名时也避开lock.json里已有的别名(它们可能属于其他 overlay);此前会覆盖那条锁定。- 选中的团队 overlay 只因 DSH 里删掉了它的条目而需要改写时,
pull的预览也以退出码 3 拒绝;此前预览通过,--yes才被拒绝。 envctl/skills本身是软链接时,快照保存其内容、rollback 写回链接指向的目录;此前快照只存链接,回滚不会恢复内容。- 插件改了别名后,
purge <包名>清理新别名下的配置块;此前按所有权记录里的旧别名查找,报「Nothing to purge」。 purge在 git 无法判断受管 clone 是否干净时拒绝执行;还没有提交的 clone 里的未跟踪文件也算未提交改动。此前这两种情况都当作干净,把 clone 移进 trash。remote系列命令调用 git 时不再继承GIT_WORK_TREE、GIT_DIR等变量(source系列 0.8.0 已如此);此前在 git hook 里运行时remote add报仓库没有envctl/manifest.yaml。
v0.9.0
升级须知
plugins config set为插件新建补丁时,把 DSH 当前为该插件组合出的全部配置抄进清单,之后的config set只改各自的键;DSH 还没有该插件时照旧只写这个键并在 stderr 提示。已有的只含个别键的补丁不会自动补全:要找回丢失的默认值,先config unset去掉这些键、apply,再重新config set。source clone <本机路径>把路径记为file://地址;install本地路径时要求目录存在,带#<ref>的本地路径被拒绝(退出码 3)。- 没有清单时
status只输出缺少清单的错误(--json时 stdout 为空)。
修复
- 没有清单时
status只报缺少清单(与plan相同,--json时是 stderr 上的 JSON 错误),不再同时输出一份degraded状态报告;0.8.0 只改了退出码。 - 插件的受管配置块已经清掉后,再次
purge输出「Nothing to purge」、退出码 0,不再每次把整个cordis.patch.yml复制进 trash。 - 输出接到提前关闭的管道(如
dshenv --help | head -1)时不再以 EPIPE 调用栈崩溃;命令照常执行完,之后的输出被丢弃。 source sync不带目录也不带--profile时作用于当前目录,与 README 及source show一致;此前报「requires or --profile」。source clone <本机路径> --profile把路径记为file://地址;此前记成普通路径,apply时 pnpm 把它当本地目录链接,失败并在 Profile 的package.json里留下名字带#<commit>的坏依赖。install本地路径时检查目录存在(退出码 3),file://地址按其路径解析,带#<ref>的本地路径被拒绝并提示git+file://;此前file:///repo#<sha>会登记成别名repo#<sha>、路径不存在的本地插件。- 有生效 overlay 时,
update --layer base、plugins config set/unset --layer base写的值被 overlay 覆盖时在 stderr 提示(与enable/disable相同);此前静默写入,这台机器上实际没变。 remote add --replace与remote sync --discard-local-changes的预览末尾给出的接受命令保留这个参数;此前照提示执行会以退出码 3 失败。remote add遇到同名本机 skill 时提示「then run remote add again」;此前提示「sync again」。plugins config set为插件新建补丁时,先把 DSH 当前为该插件组合出的全部配置(dsh --dump-config)抄进补丁再改这个键,与tools config相同;此前补丁只含这个键,而 DSH 用它整体替换插件配置,apply 后插件的其他默认值(如 agent-teams 的memberProvider)全部丢失。DSH 还没有这个插件时照旧只写这个键,并在 stderr 说明。- 插件的受管配置块把
{ __jsExpr }写回!!js表达式(与 Profile 块相同);此前会写成字面的__jsExpr映射。
v0.8.1
修复
- 失败的
apply恢复lock.json后,补记失败前已装上的本地来源插件的摘要;此前下一次plan会以「Local source has no recorded digest」重装同样的代码,并标记为需要重启。 rollback不再把mark-restarted已清除的restart-required带回来;重启状态以回滚前为准(DSH 进程不随 rollback 改变)。- 没有
state.json时,一次「已同步」的apply也记录 DSH 已有的声明 skill 的所有权与生效 overlay;此前什么都不记录,之后从清单删掉这类 skill 时被当作未管理,不会移除。 pull需要写的 overlay(选中的,或要新建的local)归团队所有时,预览就以退出码 3 拒绝;此前预览说会写进去,加--yes才被拒绝。remote sync的预览不再把用软链接放进envctl/skills的本机 skill 列为「remove」;只为这次同步涉及的 skill 重算摘要。remote sync --no-overlay(或--overlay其他名字)不再绕过「本机选中的 overlay 被团队删除」的检查;此前同步成功后,之后的命令都报Overlay '<名字>' not found。doctor只原样显示常见的预发布标签(如rc.2、beta.1),其他标签显示为「(a prerelease)」;此前完整打印DSH_CLI包装脚本输出的版本,可能带出凭据(不支持版本的报错早已如此处理)。dshenv help <不存在的命令>、dshenv <不存在的命令> --help按未知命令处理,退出码 3 并给出相近命令(--json时输出 JSON 错误);此前打印根帮助、退出码 0。- README 关于
verify地址来源的说明前后矛盾(「只从DSHENV_DSH_URL读取」),改为说明显式地址只能经该环境变量给出;使用教程不再说web start的地址「只打印这一次」,并统一用mark-restarted。
v0.8.0
升级须知
gc --yes现在也删除envctl/backups中早于--older-than(默认 7 天)的快照,始终保留最近 10 个;rollback只能回到还在的快照。需要更早的快照时,先用--older-than调大期限,或在gc前备份envctl/backups。--harness-source指向不存在的目录时以退出码 3 报错,不再改用清单的sourceDir或 PATH 上的dsh;相对路径按当前目录解析。source clone --profile --ref <ref>把该 ref 写进清单(替换原来声明的commit/ref);不带--ref时克隆并锁定清单已声明的commit或ref,不再总是锁定默认分支的 HEAD。别名已指向另一个包时拒绝(退出码 3),默认别名去掉dsh-plugin-、dsh-前缀。- 包名(含作用域)不能以
-开头。 - 没有清单时
status以退出码 3 结束(此前为 5);tools list --all --preset以退出码 3 拒绝。
修复
install <git 地址>#<分支或 tag>把#后的内容记为ref,不再因「不是 commit」报 schema 错误;README 的 Git 示例改用会锁定 commit 的source clone --profile。- 包名(含作用域)不能以
-开头,避免被 pnpm 当成选项。 source show/sync/clone调用 git 时不再继承GIT_DIR、GIT_WORK_TREE等变量;此前从 git hook 或 CI 步骤里运行时会检查、快进或锁定另一个仓库。remote sync发现本地克隆的 origin 与remote.json的 URL 不一致(如 rollback 跨过了一次remote add)时重新克隆;此前会从旧仓库取内容。- 团队仓库中直接放在
skills/下的文件(如README.md、.DS_Store)被忽略,不再让整个提交被拒;使用教程的团队仓库布局补上skills/与__jsExpr限制。 - 有生效 overlay 时,
--layer base的enable、disable、remove等能改 overlay 用remove: true去掉的 base 插件;此前按合并后的清单找别名,报「not found」。 - 没有清单时
status以退出码 3 结束(与plan一致),不再报告为degraded(退出码 5)。 tools list --all与--preset同时使用时报错(退出码 3),不再静默忽略--preset。- 对已经全部纳管的候选执行
adopt --yes时输出「Nothing to adopt」,不再报告「Adopted N plugin(s)」并列出它们。 - 写 overlay 的命令(
plugins config set、tools config set、install等加--layer overlay)拒绝含${...}的值(退出码 3),与写 base 一致;此前会写进 overlay,之后所有读取它的命令都以退出码 3 失败,只能手改文件。 - 有生效 overlay 时,
update --layer base与plugins config set --layer base能改 overlay 用remove: true去掉的 base 插件,update --layer base按 base 中的来源判断是否为 npm;此前前者崩溃(退出码 1),后者按 overlay 覆盖后的来源拒绝。 - overlay 里只调整某个 base 插件(没有
package)、而 base 已不再声明它时,install/source clone --profile加--layer overlay写入完整的package;此前输出「Added」,插件却不在生效清单中。 source clone --profile克隆并锁定清单为该别名声明的commit或ref(同一仓库时),清单里保留它;给了--ref时写入该 ref。此前总是锁定默认分支的 HEAD 并删掉清单里的commit/ref,照plan的提示操作会悄悄换掉固定的代码。source clone --profile的别名已指向另一个包时拒绝(退出码 3,base 与 overlay 都是),与install一致;此前写 base 时整条替换、丢掉原包的patches与启用状态,写 overlay 时让清单与 lock 的包名不一致。source clone的默认别名与install相同(去掉dsh-plugin-、dsh-前缀);此前install之后按 README 再source clone会因重复包报错。- base 已声明、但本机 DSH 从本地路径(
link:、file:)安装的插件,adopt --yes保留 base 中的条目,把本机路径写进 overlay(没有生效 overlay 时新建并选中local);此前会把本机路径写进共享的 base,推到团队仓库时被拒。 - 对 base 已按安装方式声明的本地来源插件再次
adopt --yes,保留lock.json中 apply 记录的摘要并输出「Nothing to adopt」;此前会抹掉摘要,之后plan对每个这类插件都提示更新。 pull与adopt给新插件选别名时避开当前 overlay 新增的别名;此前与 overlay 别名相同时,前者整体失败、后者被拒绝(「an overlay cannot change package」)。- 团队 skill 里同一路径在文件与目录之间互换时,
remote sync能正常预览与接受(先删除再写入);此前预览报EEXIST(退出码 1),或误报该路径「not owned by the remote」(退出码 3),之后每次 sync 都失败。目录里仍有本机文件时照旧拒绝。 - 快照另存当时所有 overlay 文件;
rollback跨过一次remote add --replace时,把被团队接管的本机 overlay 恢复为原内容;此前会删除它。 apply删除插件后,purge <包名>能清理它留在envctl/sources下的 clone(清单不再声明该包时);此前因所有权记录已去掉而拒绝,与remove帮助所说的流程不符。--harness-source指向不存在的目录时以退出码 3 报错;此前静默改用清单的sourceDir或 PATH 上的dsh,doctor、apply、web start跑的是另一个 DSH。--harness-source的相对路径按当前目录解析;此前被解析两次(hs/hs),相对路径总是失败。- 容器示例的
.dockerignore排除envctl/run/(web start的记录,含带 token 的 dsh web 登录地址)、remote.json、remote/与锁的.wanted/.reclaim文件;此前用过web start的配置仓库构建镜像时,token 会进入镜像层。 - 帮助的环境变量部分补上
DSHENV_NPM_CHECK;README 与使用教程中需要--layer的命令补上plugins config unset与tools reset;README 说明没有快照时的rollback与没有订阅时的remote remove以退出码 3 结束。
变更
gc同时删除envctl/backups中早于--older-than的快照(始终保留最近 10 个),以及被中断的操作留下、超过同一期限的.…partial临时目录;此前快照只增不减,每次有变更的apply都会复制一份envctl/skills。