ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

家目录级补丁优先,TaoToken 写 $DSH_HOME

家目录级补丁优先,TaoToken 写 $DSH_HOME 把 llm 凭据写进$DSH_HOME/cordis.patch.yml之后pnpm dsh --profile web --dump-config依然打印旧的 baseUrl——这是我第一次给 DSH 换模型供应商时踩的坑。当时我以为补丁文件放对位置就行结果发现自己把 Key 的来源也搞错了随手填的中转地址让模型侧直接 401。后来统一改成从 TaoToken 官网 申请 KeyBase URL 固定写成https://taotoken.net/api配置文件才真正跑通。这篇笔记沿着「观察 → 修改 → 验证」这条线把 DSH 练习 4 里最容易翻车的几个点拆开讲家目录级补丁为什么压过 profile 级、dump-config怎么用来做优先级取证、以及disabled: true和整行替换这两个语义坑。1. 从一次「补丁没生效」的现场说起那次现象很典型$ pnpm dsh --profile web --dump-config | grep -A3 name: llm - name: llm config: baseUrl: https://old-endpoint.invalid/v1 apiKey: 而$DSH_HOME/cordis.patch.yml里明明写着新地址。排查了半小时才定位到两件事一是补丁文件里只有一行注释YAML 解析出来是空文档而不是空列表加载器直接判定结构非法整个补丁层被跳过二是我把apiKey写成了明文字符串但 schema 期望的是!!js表达式或者经过inject的服务引用两者对不上时该行被静默丢弃。修好之后我给自己定了一条规矩DSH 里任何一次配置改动都要走完整的三步取证——先 dump 最终值再定位来源层最后改对应层复验。不满足这三步的改动一律当成没改过。这条规矩在练习 4 里尤其重要因为练习 4 的目标不是写一条补丁而是证明这条补丁的优先级正确。证明不了就等于没写。2. 先读基线base.yml 与 full.yml 的分层对照在动手写凭据之前最好先把当前配置树的两层基线打出来。这一步对应练习 1但练习 4 也需要它做对照。# 只看 bundle 层不含任何用户补丁 pnpm dsh --profile web --dump-config --default-only /tmp/base.yml # 完整组合bundle profile 补丁 家目录补丁 命令行 --patch pnpm dsh --profile web --dump-config /tmp/full.yml # 看多出来的行来自哪一层 diff -u /tmp/base.yml /tmp/full.ymlbase.yml是出厂态full.yml是实际生效态。两者之间的差值就是所有用户补丁叠加后的净效果。当你在练习 4 里加了一条家目录级补丁再跑一次diff如果差值里出现了你写的那一行说明补丁被读进去了如果没出现说明它被更上层的补丁覆盖、被解析丢弃或者压根没落到正确的层。在full.yml里我会重点确认七个功能分组对应的行是否存在、name指向哪个包、关键config字段长什么样。这七行分别是llm、session、agent-loop、tools、system-prompt、sandbox-policy、agent-presets。它们覆盖了基础设施、编排、凭据、持久化、安全、模型面工具和委派这几类职责。练习 4 只动llm一行但你必须知道这一行在整棵树的哪个位置——它属于宿主组合平面而不是 agent preset 平面。把凭据写到 preset 里去是另一个常见翻车点。还要记住一个约束base.yml和full.yml都是可加载的 YAML不是给人看的日志。你可以把full.yml存成文件再用--patch叠加它验证自己对层级的理解是否正确。这个动作看起来多余实际上是把我以为我懂了变成我能证明我懂了。3. 把 TaoToken 凭据写进家目录级 patch练习 4 的核心动作是在$DSH_HOME/cordis.patch.yml里加一条所有 profile 都生效的补丁并验证它压过 profile 级补丁。$DSH_HOME默认是~/.dsh。先拿到 Key。在 TaoToken 控制台 创建一条 API Key然后回到本地写补丁。Base URL 是固定的https://taotoken.net/api接下来是补丁内容。注意patch是整行替换语义不是深合并——这一点在练习 2 里已经强调过练习 4 同样适用。所以你不能只写一个baseUrl就完事必须把llm行原有的其余字段一并重述。# $DSH_HOME/cordis.patch.yml # 家目录级补丁对所有 profile 生效优先级高于 profile 级补丁 - name: llm config: provider: openai-compatible baseUrl: https://taotoken.net/api apiKey: YOUR_API_KEY model: gpt-4.1 timeoutMs: 60000几个要点第一文件不能为空。只有注释、没有列表项的 YAML 会被解析成空文档加载器按解析为空不是列表直接判失败这一层补丁整体不生效。哪怕你只想占位也要确保文件里至少有一条合法的列表项。第二apiKey这里是明文占位符YOUR_API_KEY。如果你所在的环境更倾向用环境变量注入可以保留原来的!!js写法但要注意!!js是在挂载时求值而不是每次读取时求值。挂载那一刻环境变量里没有值后面再export也没用。第三如果llm行在 bundle 里带了inject字段你在补丁里也要一并重述。整行替换会把inject一起换掉漏写就等于把依赖声明删了插件会一直停在 waiting 状态——这正好是练习 3 里强调过的那个卡点ctx.serviceName只有在inject激活之后才能引用。如果你不确定llm行原本有哪些字段最稳的做法是从full.yml里把整行抠出来改# 用 python 从 dump 结果里截取 llm 行便于原样重述 python3 - PY import re, pathlib text pathlib.Path(/tmp/full.yml).read_text() blocks re.split(r\n(?- name:), text) for b in blocks: if b.startswith(- name: llm): print(b) PY拿到原行之后只改baseUrl和apiKey两个值其余字段原样保留。这是最小改动原则也是避免顺手漏字段的唯一办法。4. 用 dump-config 与 diff 验证「家目录级 profile 级」补丁写完之后不要凭感觉判断优先级。用两步取证。第一步在 profile 级补丁里故意写一个不同的值制造冲突# $DSH_HOME/profiles/web/cordis.patch.yml - name: llm config: provider: openai-compatible baseUrl: https://profile-level.invalid/v1 apiKey: YOUR_API_KEY model: gpt-4.1 timeoutMs: 60000第二步重新 dump 并检查最终值pnpm dsh --profile web --dump-config /tmp/full-after.yml grep -A5 name: llm /tmp/full-after.yml如果优先级正确输出里的baseUrl应该是https://taotoken.net/api而不是https://profile-level.invalid/v1。家目录级覆盖了 profile 级符合练习 4 的验收要求。再补一个命令行层的验证pnpm dsh --profile web \ --patch {- name: llm} \ --dump-config 2/dev/null | head -n 5命令行--patch位于链条最末端优先级最高。你可以用它做一次性试验但不要把它当成持久化方案——重启就没了。完整的一行配置推导链条是这样的bundle 默认 → profile 补丁 → 家目录补丁 → --patch命令行越靠右越晚应用、越晚赢。这也是为什么家目录级补丁优先于 profile 级不是一条拍脑袋的规定而是层级顺序的自然结果家目录补丁是全局默认profile 补丁是局部覆盖全局默认反而要在局部之后应用才能保证跨 profile 行为一致。如果你还想验证 HMR可以直接改家目录补丁文件、不重启然后观察下一次--dump-config或 Web UI 是否反映新值。用户补丁层有热更新改文件即生效这也是调试期最省事的路径。5. patch 是整行替换persona 覆盖时必须重述字段练习 2 用--patch覆盖system-prompt的 persona练习 4 把这套覆盖持久化。两处都撞在同一个语义上patch 按行替换不做深合并。假设原本system-prompt行是这样- name: system-prompt config: persona: default-assistant mode: balanced maxTokens: 4096 language: zh-CN你只想把 persona 换成自定义值写- name: system-prompt config: persona: dsh-maintainer结果mode、maxTokens、language三个字段全部丢失因为整行被替换成了只有 persona 的新行。正确写法是把其余字段一并重述- name: system-prompt config: persona: dsh-maintainer mode: balanced maxTokens: 4096 language: zh-CN这也是 base 注释里那句mode-specific values live in mode bundles的原因——模式相关的值有它们自己的归属层不应该靠 patch 隐式继承。patch 层只负责显式覆盖不负责帮你保留没写出来的东西。同理删字段也不要用不写来实现。你想让某个字段回到默认值正确做法是显式写出默认值而不是省略该字段。省略等于替换掉默认值不会自己长回来。6. disabled 而非删除删行会让上层补丁静默失效练习 4 的卡点里有一条很容易被忽略禁用某行要用disabled: true不要删行。原因在于补丁是按行匹配、按层叠加的。如果你在某一层把行删了上层针对该行的补丁就失去了匹配目标匹配不到就静默跳过你不会收到任何报错。等到排查问题时你会看到补丁写了但没生效却查不出原因。正确做法- name: some-optional-plugin disabled: true行还在位置还占着上层补丁依然能匹配到它只是加载器跳过它的激活。这就是 web-app 层禁用而非删除的理由也是配置树可维护性的基本要求。反过来说当你发现某条家目录级补丁看起来没生效第一件事就是确认目标行在 profile 层或 bundle 层是否被删掉了。如果被删了先把行恢复出来再谈补丁优先级。7. 加载顺序不等于激活顺序inject 决定谁是 waiting配置树里行的先后顺序没有语义写在上面的行不会先加载写在下面的行也不会后加载。真正决定一个插件能不能起来的是它声明了什么依赖- name: llm-client inject: [llm, http-client] config: baseUrl: https://taotoken.net/api只要llm或http-client这两个服务键还没出现这行就进入 waiting 状态一直等下去。等依赖齐了才激活。这也是为什么把新行插在文件哪个位置不重要、它 inject 了什么才重要。调试激活问题时看的应该是服务键而不是行序。命令大致是pnpm dsh --profile web --dump-config | grep -n inject:把每一行的inject列出来再对照最终输出里哪些服务键实际存在缺失的那一环就是 waiting 的根因。这一点和练习 3 的!!js卡点是同一个道理!!js在挂载时求值不是每次读取ctx.serviceName只有在inject激活之后才可引用。挂载时刻依赖没就绪表达式里引用服务名就会拿到 undefined而不是等到运行时再补上。8. 同一套凭据思路落到 Claude Code / Codex / CC SwitchDSH 里配好的这套凭据思路可以平移到其他 AI 编码工具上。关键只有两个Base URL 和 Key 的来源。Base URL 统一用https://taotoken.net/apiKey 从 TaoToken 官网 取。Claude Code 走settings.json用的是ANTHROPIC_*系列变量{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: claude-sonnet-4-5 } }Codex 走config.toml用的是 TOML 结构不要和ANTHROPIC_*混用model gpt-5-codex model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api responsesCC Switch 三件套可以理解为Claude Code 的settings.json、Codex 的config.toml加上一份集中管理 API Key 的环境变量文件。三者的 Base URL 保持一致Key 指向同一个来源切换供应商时只改这一处不会出现Claude Code 通了、Codex 还在 401的分裂状态。这里最容易犯的错是把ANTHROPIC_*套到 Codex 上。Codex 不读这些变量配了也不生效只会让你误以为配置写对了。记住一条Claude Code 看ANTHROPIC_*Codex 看config.toml两者不通用。9. 三步排查法与自查清单每一行配置出问题时按这个顺序走--dump-config看最终值。确认现象是否真实存在。定位来源层。用diff对比base.yml和full.yml看这一行的值是从哪一层来的。修改对应层验证。改完再 dump确认最终值变了。落到练习 4 的具体自查清单$DSH_HOME/cordis.patch.yml是否存在且不是空文件或纯注释。llm行是否把原字段全部重述包括inject。profile 级补丁里是否也写了llm行用来验证优先级。baseUrl的最终值是不是https://taotoken.net/api。apiKey占位符是否已被替换成真实 Key且不在版本库里。禁用行是否用disabled: true而不是删除。依赖没就绪的行是否因为inject缺失而停在 waiting。另外练习 1 到练习 4 的产物——/tmp/full.yml加上一份你自己写的家目录级补丁——建议保留下来它们是后续所有练习的对照基线。没有基线任何一次我改了但好像没变都会变成无意义的猜测。10. 文末把练习产物固化下来如果你跟着走完这一遍手上应该有三样东西一份能解释层级关系的/tmp/full.yml、一份写在$DSH_HOME/cordis.patch.yml里对所有 profile 生效的llm凭据补丁以及一条被dump-config验证过的优先级推导链。想继续把其他工具也接上可以按这个顺序走先在 模型对话 里确认 Key 能正常调通再按需看 Coding Plan决定是走按量还是走套餐然后去 创建 API Key把YOUR_API_KEY替换掉最后对照 Claude Code 文档 把settings.json补全把同一套 Base URL 复用到 Codex 的config.toml上。配置树读得越透改动就越小改动越小验证就越快。练习 4 真正训练的不是写一条补丁而是每改一行都知道它在哪一层、被谁覆盖、最终值是什么——这个习惯一旦建立后面所有 profile 和工具的接入都会顺很多。
返回列表