<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>DSH on Trevor's Blog</title><link>http://mings.in/tags/dsh/</link><description>Recent content in DSH on Trevor's Blog</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Tue, 03 Nov 2026 19:40:00 +0800</lastBuildDate><atom:link href="http://mings.in/tags/dsh/index.xml" rel="self" type="application/rss+xml"/><item><title>让 dsh 自己给自己写插件，它花了九分钟</title><link>http://mings.in/posts/dsh-creator/</link><pubDate>Tue, 03 Nov 2026 19:40:00 +0800</pubDate><guid>http://mings.in/posts/dsh-creator/</guid><description>我让 dsh 的创造模式写一个插件，它查运行时、读源码、写包、申请安装，九分钟后工具真的出现在插件管理页里，新会话十秒调通。</description><content:encoded><![CDATA[<p>今天热点里那条「DeepSeek 上线创作者模式」，英文媒体写得很玄，说用聊天就能造插件。</p>
<p>我本机天天在用 dsh，跑的是 2026 年 10 月 28 日发的 0.2.2-alpha.2，看到这条的第一反应不是转述，是试一遍。</p>
<p>于是我在自己的 web profile 里开了一个 Creator mode 的会话。给它的需求只有一句话：写一个最小的插件，注册一个叫 profile_ping 的工具，返回当前 profile 名、dsh 版本号、以及这个 profile 里装了哪些插件。</p>
<p>九分钟之后，这个工具躺在插件管理页里，我新开一个会话调它，十秒拿到了结果。</p>
<h2 id="dsh-一共有四种模式">dsh 一共有四种模式</h2>
<p>打开 web 界面，输入框上面有个模式选择器，点开是四条：</p>
<ul>
<li>Standard，写代码、查资料、跑命令，适合大多数任务</li>
<li>PTC，包住 Standard 全部能力，额外适合「批量调工具再筛、去重、统计」这类活</li>
<li>Minimal，只给终端和读图，用来测基础能力</li>
<li>Creator，官方原话是「通过对话定制 dsh，让 agent 写插件加功能或界面，或者把工具和提示词组合成你自己的模式」</li>
</ul>
<p>我这次点的是第四条。</p>
<p><img alt="四种模式，第四种是让它改自己" loading="lazy" src="/images/dsh-creator/fig1.png"></p>
<h2 id="它没有一上来就写代码">它没有一上来就写代码</h2>
<p>这点是我最意外的。</p>
<p>接到需求之后，它先载入了一个叫 cordis-plugin-development 的技能包，然后开始查运行时。</p>
<p>界面右侧的 Trajectory 里能看到它一连串动作。先 inspect providers 看有哪些服务，再 query runtime 问 Service.listService 的方法签名，一遍遍查 pluginManager、profileContext、defineTool 的确切契约。</p>
<p>它甚至去翻了 @deepseek-ai/dsh-app-boot 的源码，来判断「插件里能不能 import 这个包」。它要拿 dsh 版本号，而版本号只能从安装目录的 package.json 里读。</p>
<p>这一步的判断过程我觉得比结果更值钱：它先确认「profile 里装的插件解析不到安装目录的包」，然后改用运行时已经提供的 profileContext，从 installAnchor 那个路径自己去读。</p>
<h2 id="写出来的东西是这个样子">写出来的东西是这个样子</h2>
<p>生成物在会话的工作区里，一个标准的 bundle 包：</p>
<p>一个标准 bundle 包：清单、补丁、入口、两份语言文件、一枚图标，外加一份 README。</p>
<p>package.json 里那句 <code>&quot;dsh&quot;: { &quot;bundle&quot;: { &quot;patch&quot;: &quot;./cordis.patch.yml&quot; } }</code>，就是它跟 dsh 打招呼的方式。补丁文件只有三行，插入一条 id 叫 profile-ping 的行。</p>
<p>index.js 六点九 KB，自己注册工具、定义参数和输出 schema、写 render。中文名字「Profile 探针」是它自己起的。</p>
<p><img alt="它生成的不是一个文件，是一个能装进 profile 的包" loading="lazy" src="/images/dsh-creator/fig3.png"></p>
<h2 id="中间我拒了它一次">中间我拒了它一次</h2>
<p>安装那一步它调的是 plugin_manager 的 install_bundle，界面上弹出一个授权框，写明「这会把改动写进 profile，装进去的 Host 代码在沙箱外面跑」。</p>
<p>我点了 Allow once。</p>
<p>后来它想把打包好的 tgz 从 /tmp 复制到 Documents 下做长期保存，又申请一次授权。这次我点了 Reject，因为这是我准备做完实验就清理的临时东西。</p>
<p>它的反应值得记一笔：它停下了，并且明确说「用户拒绝了这次提权，所以我不该绕开，解释清楚就行」。</p>
<p>这条规矩写在它的技能包里，不是模型的即兴发挥。原文是「不要手写 profile 的 package.json，不要自己在 profile 目录里跑 pnpm，这些事由 install_bundle 做」。</p>
<p><img alt="它自己走完的八十三个步骤" loading="lazy" src="/images/dsh-creator/fig2.png"></p>
<h2 id="跑通了">跑通了</h2>
<p>新开一个会话，我说「直接调用 profile_ping，把返回原文贴出来」。</p>
<p>十秒钟，它贴出来：</p>
<p>返回就三行开头：<code>Profile: web</code>、<code>dsh version: 0.2.2-alpha.2</code>、<code>Plugins (209):</code>。后面跟着两百零九个包名，排在末尾的那个，正是它自己写的。</p>
<p>两百零九个插件。那个名单的末尾，就是它自己写的插件。</p>
<p>插件管理页的「已安装」里也多了一行：Profile ping，带图标、带描述、开关是开的。</p>
<p><img alt="左边是调用结果，右边是它出现在插件管理页" loading="lazy" src="/images/dsh-creator/fig4.png"></p>
<h2 id="它的技能包里把规矩写死了">它的技能包里把规矩写死了</h2>
<p>跑完我才去读它载入的那份技能文档，读完明白它为什么这么走。文档里写道，这套流程分五步，第一步就是「先交付能用的版本」。</p>
<p>文档开头就写：先交一个能跑的版本，再谈好不好看。视觉需求如果没说落在哪，默认就是当前这个 Web 界面；不要先做预览页、别做几套设计稿、别写截图脚本。</p>
<p>然后是三条禁止：不要手写 profile 的 package.json，不要在 profile 目录里跑 pnpm，不要在 $DSH_HOME 下面造包。这些活统一交给插件管理器的 install_bundle，手工写一次就要一次审批。</p>
<p>还有一条验证规矩：装完看返回里的 application 字段和 warnings，别看日志、别看进程列表、别看页面是不是起了。</p>
<p>我这次全程没干预它的技术判断，唯一做的就是两次授权点击，一次同意一次拒绝。它在这套规矩里自己走完了。</p>
<h2 id="三个坑我得说清楚">三个坑，我得说清楚</h2>
<p>第一个，它创建的工作区目录名带了反斜杠。dsh 在 macOS 上把路径拼成了 <code>deepseek-harness\creator-lab</code>，那个反斜杠是真的字符，不是转义。结果 ESM 加载这种路径直接报错，插件根本 import 不进来。它自己发现的，改用打包成 tgz 安装绕过去了。</p>
<p>第二个，装完当回合调不到工具。工具列表是这个回合开始时就定死的，新注册的工具下一轮才出现。它在 Trajectory 里连着打转了好几次，自己在思考里写「我可能只能发出静态列表里的工具」，我另开一个会话，才验证成功。</p>
<p>第三个，安装记录里写的是 <code>file:/tmp/dsh-plugin-build/xxx.tgz</code>。也就是说，清了临时目录，这个插件下次就装不上。它想修的正是这一点，被我拒了。真要长期用，得把包放到工作区里，用 install_bundle 指目录安装。</p>
<h2 id="它现在能造什么造不出什么">它现在能造什么，造不出什么</h2>
<p>能造的：工具类插件，往 agent 的工具箱里加一个能力，我这次的 profile_ping 就是；配置类插件，改宿主某个服务的行为；界面类插件，往 Web 界面的插槽里挂一块。技能包里这三类各有模板和参考文档。</p>
<p>造不出的：需要新宿主服务的插件，它自己文档里写着这个插件不是内核，不加服务、不加会话事件类型、不加沙箱后端；还有跨 profile 的改动，一次只能动当前这个 profile。</p>
<p>一个更实际的限制是 token。这次九分钟的活，烧掉 890 万 token，缓存命中 99% 才压住了成本。你要让它干大活，得先想清楚值不值。</p>
<h2 id="我的判断">我的判断</h2>
<p>「用聊天造插件」这个说法容易让人以为它是个生成器：你描述，它吐代码。</p>
<p>不是。它更像一个懂这套框架的工程师：先查运行时有哪些能力，再读源码确认约束，然后照技能包里的流程施工，装的时候申请授权，装完自己验一遍。</p>
<p>真正稀缺的是那套流程本身。技能包里把「先交一个能跑的版本，再谈好看」「视觉需求默认落在当前界面」「安装结果看 application 字段而不是日志」这些规矩都写死了。模型会换，这套规矩不换。</p>
<p>一个能力边界放这儿：它九分钟里烧了 890 万 token，缓存命中 99%。这不是零成本的魔法，是一次真刀真枪的施工。</p>
<p>你手上有哪个工具，是每次都要手动翻一遍配置才知道状态的？那种东西，值得让它给你写一个插件。</p>
]]></content:encoded></item></channel></rss>