← 返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

安装 DSH 扩展(源码 fork 流水线)

从源码安装 DSH 扩展的全流程:fork 上游 → 克隆 fork → 装成 profile 插件或技能分组仓 → 重启生效;MCP server 则免 fork、只改 patch 配置。触发词:安装扩展、装插件、装 DSH 扩展、安装 DSH 插件、装 MCP、接入 MCP、安装 mcp server、从源码安装、fork 上游、vendor 插件、install extension、装个技能组。含三条链路(插件 vendor/ + 技能 skills/ + MCP patch)、dsh plugin 自动 reconcile bundles、upstream 远端约定、MCP 热加载与 failOnStartupError 静默消失陷阱、重启不中断报告的延时做法与常见坑。"

person作者: Sxgx123hubOpenAPI

安装 DSH 扩展(源码 fork 流水线)

装扩展 = 拿到源码的归属权,再让 harness 引用它。本机总纲(ADR-0007):生产 profile 里不出现 npm 交付的依赖,一切走 link: 源码安装;所以"装扩展"永远是"fork → 克隆 → link:"三步,不是 pnpm add <包名>。

与 dsh-extension-dev 的分工:那个技能管「怎么写/改扩展」,本技能管「怎么把现成扩展装进来」。写完一个新扩展要装进生产 → 用本技能。

先分形态,再选链路

问一句「这个扩展是插件还是技能?」,后续每一步都不同:

| | 插件形态 | 技能分组仓形态 | MCP 形态 | |---|---|---|---| | 是什么 | 独立 npm 包(有 package.json、dsh.bundle) | 一组 SKILL.md 目录 | 外部 MCP server 的接入配置 | | 落点 | dsh-extensions/vendor/<仓库名>/ | dsh-extensions/skills/<上游仓库名>/ | ~/.dsh/profiles/web/cordis.patch.yml | | 接入 | profiles/web/package.json 的 link: 依赖 | install-skill.sh 递归扫 SKILL.md 软链 | insert 一行 @deepseek-ai/dsh-mcp-client | | 生效 | 必须重启 | 不必重启(技能按需扫描) | 不必重启(patch 层热加载) | | 父仓指针 | submodule | submodule |

两者都是 git submodule,装完必须回 dsh-extensions 更新指针(ADR-0005:这是固定成本,不是收尾可选项)。

不属于本技能:动态 Cordis 插件(cordis_define/cordis_run,进程内、重启即失,见 cordis-plugin-development);MCP 服务接入(改 cordis.patch.yml 挂 mcp 行,不是源码扩展)。

通用侦察(插件与技能形态相同;MCP 只需第 2 步)

MCP 形态跳过第 1、3 步——它不 fork、不上游,只需第 2 步确认「是不是已经接过了」,然后直接进链路 C。

1. 定位上游仓库

要 fork 就得先有确切的上游 URL。来源通常是用户给的链接或包名。若只给了包名:

# 已发布到 npm 的扩展:从 registry 反查仓库地址
npm view <包名> repository.url

拿到上游后确认它真的是 DSH 扩展(package.json 里有 dsh.bundle 或 dsh.client;技能则是仓库里有 SKILL.md)。

2. 本地侦察——可能早就装过了

# 已装的 profile 依赖(link: 的都是源码插件)
cat ~/.dsh/profiles/web/package.json | python3 -m json.tool | grep -A20 dependencies
# 已装的技能
ls ~/.dsh/skills/ | grep -i <关键词>
# 已接入的 MCP(profile 与 home 两处 user patch 层)
grep -n 'serverName' ~/.dsh/profiles/web/cordis.patch.yml ~/.dsh/cordis.patch.yml 2>/dev/null
# 已在 vendor/ 或 skills/ 里的
ls ~/projects/MyAI/dsh-extensions/vendor/ ~/projects/MyAI/dsh-extensions/skills/

已存在就不要重装:直接改现成的那份(回到 dsh-extension-dev 规矩一的复用分支)。

3. 验证可达性(决定能否 fork)

git ls-remote <上游URL> HEAD    # exit 0 = 可 clone
gh repo view <owner>/<repo> --json name,visibility,isFork

git ls-remote 失败通常是网络/代理问题,见「常见坑」。

场景:它已经装好了怎么办

第 2 步侦察要是发现目标已装,不要重装——重跑 pnpm install 会重算 lockfile 引入漂移风险,而且不会带来任何新能力。改成巡检,逐条只读核实,哪条不过才补哪条:

# ① 依赖与层栈
grep -E '<包名>|link:' ~/.dsh/profiles/web/package.json
# ② 软链真的落在源码上
ls -la ~/.dsh/profiles/web/node_modules/<包名>
# ③ 产物新鲜(见 A5)
find <源码>/src -newer <源码>/lib/index.js -type f | head
# ④ submodule 指针已提交(见「验收清单」第 1 项)
cd ~/projects/MyAI/dsh-extensions && git ls-tree HEAD vendor/<repo>
# ⑤ 无悬空软链(改名/移动过检出时的典型故障)
find ~/projects/MyAI/dsh-extensions -xtype l | wc -l    # 期望 0
# ⑥ 服务健康
systemctl --user show dsh-web -p NRestarts               # 期望不是很大的数

最强的证据是运行时:插件的 Tool 出没出现在工具表、它带来的技能出没出现在会话技能目录。文件摆对只说明「摆对了」,功能真的可见才证明「加载起来了」。

巡检结论是四层都不用提交时,这本身就是正确交付——不要为了"做了点什么"而制造改动。

链路 A:插件形态

A1. fork 上游到自己的账号

# 账号 xgx1(gh 已登录)。不指定 --org 时 fork 到当前登录账号
gh repo fork <owner>/<repo> --clone=false
# → 得到 https://github.com/xgx1/<repo>
gh repo view xgx1/<repo> --json name,isFork,parent   # 确认 fork 真的建好了

默认走 fork:fork 之后 origin 是自己的仓,本机改动才能推送回去、才能跨设备重建;上游降级为 upstream 远端只作对照。

但要如实区分现状,别把两个分组的设计搞混(2026-09-13 实测):

| 分组 | 现状 | 设计 | |---|---|---| | skills/ | 14/14 都是 xgx1/* fork(或自建仓) | fork——本机改编版是主内容,要能推(ADR-0006) | | vendor/ | 3/3 都直连上游(omdsh-dev/*、loudMore/*),没有 upstream 远端 | 上游克隆,只跟踪(ADR-0005) |

所以「必须 fork」是 skills/ 的纪律,不是 vendor/ 的现状。在 vendor/ 里装新插件时:

  • 你打算改这个插件的源码 → 先 fork,再 git submodule add fork 的 URL(origin=fork → 能推)
  • 只跟着上游走、不改 → 可以直连上游,但那样你推不了任何修复;要推送就必须先 fork

存量迁移(把已直连上游的 vendor 子模块改成 fork,非必须):

gh repo fork <owner>/<repo> --clone=false
cd ~/projects/MyAI/dsh-extensions
git submodule set-url vendor/<repo> https://github.com/xgx1/<repo>.git
git submodule sync vendor/<repo> && git -C vendor/<repo> remote set-url origin https://github.com/xgx1/<repo>.git
git -C vendor/<repo> remote add upstream <上游URL>
git add .gitmodules vendor/<repo> && git commit -m "chore: vendor/<repo> 改指 fork"

重建 submodule 期间 link: 会短暂指向不可用路径,不要在生产运行时段做。

A2. 克隆 fork 并登记为 submodule

关键:用 git submodule add,不要先 git clone 再补。前者一次性写进 .gitmodules,后者只会留下一个未登记的 gitlink,父仓记不住它是什么。

cd ~/projects/MyAI/dsh-extensions
git submodule add https://github.com/xgx1/<repo>.git vendor/<repo>   # A1 走了 fork 时
cd vendor/<repo>
git remote add upstream <上游URL>          # 上游只作对照,不覆盖本地
git fetch upstream
git log --oneline -1 upstream/<分支>        # 确认对照基线拿得到

A3. 确认包名——不等于仓库名

python3 -c "import json;d=json.load(open('package.json'));print(d['name']);print('bundle:',d.get('dsh',{}).get('bundle',{}).get('patch'))"

name 和仓库名经常不同(例:仓库 dsh-genui → 包名 @changfenhuang/dsh-genui)。后续 link: 的键必须用这个 name。

A4. 接入 profile(dsh plugin add 一次搞定两件事)

dsh plugin --profile web add link:/home/sx/projects/MyAI/dsh-extensions/vendor/<repo>

它是一条 pnpm 转发器,会依次做三件事:

  1. 在 ~/.dsh/profiles/web/ 里执行 pnpm add link:<绝对路径> → 写入 dependencies
  2. 因 cwd 是 profile 目录,相对路径会被锚到调用处;生产配置里一律写绝对路径
  3. 自动 reconcile dsh.profile.bundles:把声明了 dsh.bundle 的依赖追加进 bundles 层栈(源码见 deepseek-harness/apps/cli/src/plugin.ts 的 reconcilePlugins)

所以 bundles 不需要手改。仅在 dsh plugin add 不可用时(比如 pnpm 报错后手工修的)才手工补:编辑 ~/.dsh/profiles/web/package.json,依赖键用 A3 查到的 name,并把它加进 dsh.profile.bundles 数组。

装完复查一次:

grep -E '<包名>|link:' ~/.dsh/profiles/web/package.json

A5. 构建产物必须新鲜

link: 引用的是源码,但 profile 真正加载的是构建产物。只检查目录存在是不够的——源码改过、产物没重建,是最隐蔽的失效方式(插件照常加载,跑的是旧代码)。

# 1. 产物在不在(按 package.json 的 main/exports 找,通常是 lib/ 或 dist/)
ls lib/ dist/ 2>/dev/null

# 2. 产物新不新:有没有 src 文件比入口产物更晚
find src -newer lib/index.js -type f 2>/dev/null | head    # 有输出 = 陈旧,需要重建

需要重建时:

pnpm install     # vendor/ 下的克隆常没有 node_modules(实测 dsh-genui 就没有)
pnpm build       # 按上游 README;产物目录通常被上游 .gitignore 忽略,不进任何仓库

pnpm install 在这类仓库里有两个已知拦路虎:ERR_PNPM_IGNORED_BUILDS(按报错点名的键加进该目录 pnpm-workspace.yaml 的 allowBuilds)与网络代理(见「常见坑」)。

运行时依赖必须能解析(2026-09-13 实测):link: 插件的 import 由 Node 从 vendor 真实路径解析,不走 profile 的 node_modules;插件的 lib/*.js 里 externalize 的 @deepseek-ai/* 不会自动看到安装闭包,缺一个就整棵插件树加载失败(dsh-web 进入崩溃重启循环,且报错先撞上哪个包就报哪个)。先看产物到底 import 了什么:

cd ~/projects/MyAI/dsh-extensions/vendor/<repo>
grep -rhoE 'from "[^.][^"]*"' lib/*.js | sort -u

修法(按优先级):

  1. 把 lib/ 里出现的 @deepseek-ai/* 声明进 dependencies(推荐,pnpm install --prod 也能重建)。版本对齐生产 harness,例如 "@deepseek-ai/dsh-llm": "^0.1.5-rc.2"。
  2. 同时把 devDependencies 里对应包对齐到同一生产版本:pnpm install(完整安装)必须能产出可解析的运行时,否则重建时又回到坑里。注意 npm 上部分 dsh-client-* 包停在旧版本(如 dsh-client-runtime 只有 0.1.1-rc.2),client 包以实际可得的最近版本为准,只用于类型/构建,不进运行时。
  3. 不要手工把 vendor node_modules/@deepseek-ai/* 软链到 ~/.dsh/profiles/node_modules:pnpm install 会覆盖,实例/版本又随 profile heal 漂移,无法从 git 重建(已弃用)。

判断插件的 import 是否全部可解析,不必起服务:

cd ~/.dsh/profiles/web && node --input-type=module -e "await import('<包名>'); console.log('import OK')"

版本上跳(如 0.1.1 → 0.1.5)时,client 插件要额外核对:服务改名(例:conversationEvents → uiConversation.events)、slot 名与 owner props、dsh.client.inject 的模块行是否存在、以及服务端事件形状(例:系统提示词从 request/header 移到 system/message)。推荐流程:pnpm typecheck 定位类型漂移 → pnpm test → pnpm build → 无头浏览器开一次页面看 Failed to load plugins 横幅与插件控件。

A6. 重启(见下方「重启规则」)

为什么 bundle 装完必须重启(不是凭经验,是机制):profile-boot.ts 的 composeLive() 只重读两个 user patch 文件——profiles/web/cordis.patch.yml 与 ~/.dsh/cordis.patch.yml;bundle 层的 patch 在 boot 时就被快照进 composed.bundlePatches,不参与热重载。所以:

| 改什么 | 生效方式 | |---|---| | profiles/web/cordis.patch.yml、~/.dsh/cordis.patch.yml | 热重载,立即生效 | | 新增 bundle / 改 bundle 自己的 cordis.patch.yml | 必须重启 | | bundle 的 lib 产物 | 必须重启 |

⚠️ profile patch 里那句「DSH 会热加载本 patch,改完立刻生效」只对它自己这个文件成立,不能外推到 bundle 层。

链路 B:技能分组仓形态

B1. 确定分组

ls ~/projects/MyAI/dsh-extensions/skills/     # 已有分组
  • 有对应上游 → fork 后放 skills/<上游仓库名>/,组内目录与上游一一对应
  • 本机自写、无上游 → 放进 self-* 自建组(self-dsh 自研 DSH 运维 / self-ops 系统运维 / self-ue UE 实践)

B2. fork + 克隆(有上游时)

gh repo fork <owner>/<repo> --clone=false
cd ~/projects/MyAI/dsh-extensions
git submodule add https://github.com/xgx1/<repo>.git skills/<上游仓库名>
cd skills/<上游仓库名> && git remote add upstream <上游URL> && git fetch upstream

有上游的组必须带 upstream 远端(ADR-0006)——git diff upstream/<分支> 就是「本机改了什么」的权威答案。self-* 自建组没有 upstream。

B3. 放置技能目录

技能目录 = 含 SKILL.md 的目录,放哪个层级都行(安装器按 SKILL.md 递归发现,不限深度)。上游的层级原样保留(skills/<名>/、.agents/skills/<名>/、plugins/<插件>/skills/<名>/);本机独有技能放到该组的主技能根下。

一个技能 = 一个目录,最少一个 SKILL.md。frontmatter 至少 name 与 description;name 用字母数字连字符,安装器优先取 frontmatter 的 name:,没有才用目录名。

B4. 软链 + 验证

cd ~/projects/MyAI/dsh-extensions
./install-skill.sh --dry-run     # 预演:确认「新建 N」与实际新增数一致
./install-skill.sh               # 真正软链进 ~/.dsh/skills/
./install-skill.sh --list | grep <技能名>
ls -la ~/.dsh/skills/<技能名>     # 应是指向分组仓的符号链接

技能够了,不必重启:DSH 按需扫描技能目录,软链建好即可用。

链路 C:MCP 形态

MCP 是配置层,不是依赖层——不要 fork、不要 git submodule add、不要改 package.json。 @deepseek-ai/dsh-mcp-client 是 DSH 自带包,每个 MCP server 只是 /profiles/web/cordis.patch.yml 里的一个 insert 块。这也和"一切走源码安装"的总纲(ADR-0007)不冲突:那条总纲针对的是要装进 profile 的插件依赖,MCP 没有依赖可装。

C1. 先确认 server 本体可用

MCP 只负责"接进来",不负责"装上"。接入前先确保那台 server 真的能跑:

# stdio 型:命令必须在 PATH 里(DSH 在它自己的进程环境里解析命令)
command -v <command> && <command> --help 2>&1 | head -3

# http 型:服务得先起着
curl -sS -o /dev/null -w '%{http_code}\n' <url>

stdio 型 server 的安装属于普通软件安装(npm i -g / uv tool install / pacman…),做完要确认命令落在 PATH 上——MCP 子进程继承的是 dsh-web 的环境。

C2. 写 insert 块

在 ~/.dsh/profiles/web/cordis.patch.yml 末尾追加(一个 server 一个块,不要塞进已有的块里):

# stdio 型:DSH 自己拉起子进程
- insert:
    - id: mcp-<名字>
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: <名字>          # 工具前缀就是 mcp__<名字>__
        transport: stdio
        command: <命令>
        args:
          - '<参数>'
# http 型:连已存在的服务
- insert:
    - id: mcp-<名字>
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: <名字>
        transport: streamable-http
        url: http://127.0.0.1:<端口>/mcp

字段说明:id 与 serverName 都要唯一(同一个 serverName 注册两次会冲突);transport 只有 stdio 与 streamable-http 两种。

C3. 首次务必让失败可见(failOnStartupError)

默认(不写这个字段)是 true:MCP 起不来就报错。第一次接一个 server 时保持默认——错了立刻知道。

反过来,这种配置会静默消失:failOnStartupError: false + 命令不存在 / 端口没服务。本机就踩过——headroom 的旧配置写着一个从不存在的 127.0.0.1:8791,又带着 false,于是 DSH 默默跳过,会话里一直没有 mcp__headroom__* 工具,没人发现。所以:

  • 调试期用默认(true),确认稳定后再决定是否放宽
  • 写 failOnStartupError: false 时必须配一条注释说明为什么可以容忍它不在(例:mcp-ue 依赖编辑器开着)

C4. 生效:热加载,不用重启

profiles/web/cordis.patch.yml 属于 user patch 层,DSH 会热加载它——改完立即重拉 MCP 子进程。这条只对 user patch 层成立(profiles/web/cordis.patch.yml 与 ~/.dsh/cordis.patch.yml);bundle 层 patch 不参与热加载,那是插件形态才要重启的原因(见 A6)。

验证:

# ① 子进程真的被拉起来了(父进程应为 dsh-web 的 MainPID)
pgrep -af '<命令>' | head
systemctl --user show dsh-web.service -p MainPID
# ② 起不来时看日志
journalctl --user -u dsh-web -n 60 | grep -i mcp
# ③ 最终判据:新会话的工具表里出现 mcp__<serverName>__*

若配置写坏导致 dsh-web 崩溃循环:直接在那个块里加 disabled: true 整个禁用它,或删掉该块;热加载会让它立刻退场。别让服务留在重启循环里(每 3s 一次,NRestarts 飙升)。

C5. 提交(这一步才是"换台设备也能用")

好消息:profiles/web/cordis.patch.yml 本来就在 ~/.dsh 仓库的跟踪范围内,所以改完它天然会随仓库走:

cd ~/.dsh
git add profiles/web/cordis.patch.yml
git commit -m "feat: 接入 <名字> MCP"
git push

但配置能同步 ≠ 那台设备就能用:stdio 型依赖那台设备的 PATH 里有那个命令,http 型依赖那台设备的服务在跑。所以注释里要写清前置条件,否则新设备上就是 C3 那种静默消失。写注释时至少交代:server 本体怎么装、依赖什么外部服务、代理要不要清(本机 headroom 那条就写明了必须清 all_proxy,否则 MCP 子进程连本机 8787 会报 socksio 缺失)。

重启规则

插件形态装完必须重启 dsh-web.service 才会加载新代码。但重启会杀掉当前会话——包括正在执行本流程的 agent——所以顺序和时机是硬要求:

  1. 先做完一切:报告、验收证据、后续指令全部在重启命令之前完成。重启后再补一句是不可能的。
  2. 再延时重启,用 systemd 的定时器把重启与本进程解耦,让当前会话有机会先把话说完:
# 10 秒后重启:定时单元独立于本进程,报告来得及发出去
systemd-run --user --collect --on-active=10 --unit=dsh-restart-once \
  systemctl --user restart dsh-web.service
  • --collect 让一次性单元执行后自动清理,单元名可以重复使用(不加它,第二次装扩展会因单元名已存在而失败)。它不影响取消:延迟窗口内 systemctl --user stop dsh-restart-once.timer 仍可撤销(实测)。
  • 命令返回即表示重启已排程,此刻还没断线——本轮最终回复必须在 10 秒内说完;排程后不要再发新一轮工具调用(每个调用都在消耗延迟窗口)。
  • 延迟取 10 秒:给「排程返回 → 报告说完」留余量。3 秒会让长报告说到一半就被切断。

不要用 nohup ... sleep 3 ... & 兜底:dsh-web.service 是 KillMode=control-group(已实测),重启会杀掉整个 cgroup,连你的后台延时进程一起带走,重启永远不会发生。systemd-run 不在同组,所以只有它能做这件事。

若环境无 systemd-run:放弃延时,把报告发完之后直接执行下面这条,并明确告知用户页面会短暂中断:

systemctl --user restart dsh-web.service
  1. 报告里必须写明:重启已排程、页面会自动恢复、刷新即可;并给出恢复后的验证命令:
systemctl --user is-active dsh-web.service     # 期望 active
systemctl --user show dsh-web -p NRestarts     # 期望不是很大的数
journalctl --user -u dsh-web -n 60             # 起不来时看这里

重启失败会导致 dsh-web 进入崩溃重启循环(每 3s 一次,NRestarts 飙升),排查见「常见坑」。

Windows(PowerShell)

本机无 Windows 实例,以下为等价形态、未在本机验证:

# 延时重启:无 systemd,用计划任务或分离进程
Start-Process powershell -ArgumentList '-Command','Start-Sleep 3; Restart-Service dsh-web'
Get-Service dsh-web

验收清单

装完逐项确认,缺一项就不算装好。前 4 项两条链路都要;后 2 项按形态二选一——插件形态不看技能项,技能形态不看插件项:

通用(插件、技能都要)

  • [ ] submodule 已提交:git ls-tree HEAD <路径> 返回 160000 commit …。别用 git status 判断——指针提交后 status 本来就是干净的,它无法区分「已提交」与「根本没登记」
  • [ ] 上游可对照:有上游的仓库 git remote -v 里能看到 upstream
  • [ ] dsh-extensions 的 git status 不再显示该 submodule 为 modified(这才是 git status 的正确用法:看漏更新指针)
  • [ ] 功能实际可见(新 Tool / 新界面 / 设置项 / 新技能)

插件形态专属

  • [ ] ~/.dsh/profiles/web/package.json 的 dependencies 里有指向 fork 的 link:,路径是绝对路径
  • [ ] dsh.profile.bundles 里出现了该包名(dsh plugin add 自动加;手工装的要自己加)
  • [ ] ls -la ~/.dsh/profiles/web/node_modules/<包名> 指向 vendor 源码
  • [ ] 重启后 systemctl --user is-active dsh-web.service = active,页面能打开

技能形态专属

  • [ ] ./install-skill.sh --list 能列出该技能,~/.dsh/skills/<名> 是指向分组仓的软链

插件自带技能时不看上面的技能项:有些插件通过 ctx.skills.registerProvider() 在运行时提供技能(例:dsh-genui 的 ~/.dsh/skills/genui 不存在是正常的)。install-skill.sh 只扫 skills/、update-app/skills/、各项目 .dsh/skills/ 三个源,不扫 vendor/——所以在 ~/.dsh/skills/ 里找不到它不代表装漏了,看会话技能目录里有没有出现即可。

回滚

dsh plugin --profile web remove <包名>      # 依赖与 bundles 一并退场
cd ~/projects/MyAI/dsh-extensions && git rm vendor/<repo> && git commit
systemd-run --user --collect --on-active=10 --unit=dsh-restart-once systemctl --user restart dsh-web.service

常见坑

| 症状 | 原因与修法 | |---|---| | pnpm 超时 / fetch failed | 代理没设。Linux:export https_proxy=http://127.0.0.1:7897 http_proxy=http://127.0.0.1:7897 all_proxy=socks5://127.0.0.1:7897;Windows:$env:HTTPS_PROXY="http://127.0.0.1:7897" | | ERR_PNPM_IGNORED_BUILDS | 依赖带 postinstall 被 pnpm 拦下。按报错点名的确切键加进 ~/.dsh/profiles/web/pnpm-workspace.yaml 的 allowBuilds,再重跑 | | lockfile 漂移报错 | 加 --no-frozen-lockfile | | 重启后 plugin tree failed to load、页面打不开 | 新插件没激活(多半 bundles 缺项,或它自己的 cordis.patch.yml 有语法错)。别让它留在崩溃循环里:先 journalctl --user -u dsh-web -n 60 看报错,改完再重启 | | Cannot find package '@deepseek-ai/…' imported from …/vendor/… | 两种原因:① 检出被改名/移动过,vendor 里的绝对符号链接悬空——find ~/projects/MyAI/dsh-extensions -xtype l 排查后重指;② 插件 externalize 了 @deepseek-ai/*,但 vendor 的 node_modules 里没有可解析副本(没装依赖,或 devDeps 还是旧版本)。修法见 A5「运行时依赖必须能解析」,装好再重启 | | 重启后当前对话断了、agent 没来得及报告 | 重启前没把话说完。回到「重启规则」第 1 步 | | 装完 dsh-extensions 一直显示 dirty | 没更新父仓指针。git add <submodule 路径> && git commit | | git submodule add 报路径已存在 | 目录已被 git clone 占位。删掉重新用 submodule add,或 git submodule add --force |

提交纪律

装完要让改动可重建,三个仓库按层级各提交一次(ADR-0005 的两层拓扑):

# 1. 上游 fork 仓(只在改了它的源码时)
cd dsh-extensions/vendor/<repo> && git add -A && git commit -m "..." && git push origin main

# 2. dsh-extensions:更新 submodule 指针
cd ~/projects/MyAI/dsh-extensions && git add .gitmodules vendor/<repo> && git commit -m "..." && git push

# 3. MyAI:更新 dsh-extensions 指针
cd ~/projects/MyAI && git add dsh-extensions && git commit -m "..." && git push

~/.dsh 是独立仓库(xgx1/dsh-home),profile 的 link: 改动记在它的仓库里:

cd ~/.dsh && git add profiles/web/package.json profiles/web/pnpm-lock.yaml && git commit -m "..." && git push

这是四个仓库——漏掉任何一个,换台设备就重建不出来。