04 · 从 CLI 到插件树:profile、bundle、preset 与自己的 Agent¶
本章源码固定 da00f7f;可安装实验固定 0.2.0-rc.2。应用怎样被组装,决定了后面每个模型、工具、权限和界面功能是否存在。DSH 的扩展入口首先是组合配置,然后才是业务代码。
4.1 两种选择解决两个不同问题¶
Profile 选择一个进程的应用树。它回答“启动浏览器应用、一次性命令、SDK 服务还是 ACP 服务;装哪些基础服务和第三方包”。Preset 选择这个进程内一批 Agent 的能力组合。它回答“这次会话用哪些工具、persona、指令、压缩和协作插件”。同一 Web profile 可以同时承载 standard 与 minimal 会话,它们共享主机服务,但不会自动获得彼此的 scoped tool。
$DSH_HOME/profiles/<name>/package.json 保存 dsh.profile.bundles 和 profile 自己安装的依赖;cordis.patch.yml 保存用户配置层;cordis.yml 是 launcher 每次写回的空根,不是用户配置入口。根之所以存在,是给 Loader 一个真实 include/baseUrl 以解析相对模块。把最终 dump 写入这个空根会造成下一次 bundle insert 重复。源码:profile-boot.ts · export function prepareProfile、profile.ts · export function initProfile。
| shipped profile | 初始 bundles | 运行目的 |
|---|---|---|
web |
base → web-app | 浏览器 Host 与 Client |
headless |
base → headless | 单任务,一次性进程 |
sdk |
base → sdk-app | JSON-RPC SDK 服务 |
sdk-minimal |
sdk-minimal | 独立明确最小 SDK 树,不叠 base |
acp |
base → acp-app | automation-only ACP |
这些是源码 PROFILE_TEMPLATES 的事实,不是从名字推出来的默认行为。Desktop 还拥有保留的 desktop profile 和打包运行时,由 Desktop 首次初始化;不能简单当成普通 npm CLI 的第六个模板。源码:profile.ts · export const PROFILE_TEMPLATES。
4.2 patch 是替换整份 config,不能当成深合并¶
组合从空 EntryOptions 列表开始。每个 bundle 的 manifest 声明 dsh.bundle.patch,可以是一个文件或有序文件数组;bundle 本身也是普通 npm distribution。每个 profile bundle 按 manifest 顺序贡献 patch,再上叠 profile 层、home 层、调用层。home 层晚于 profile 层,因此会覆盖 profile 设置;只检查项目文件不足以知道机器实际运行的配置。最后还有 launcher flag 产生的配置,例如 telemetry switch。源码:profile-boot.ts · async function composeProfile、profile.ts · export function bundlePatchFiles。
# 针对 sdk-minimal 的演示覆盖:整份替换该 row 的 config。
- id: system-prompt
config:
includeHarnessIdentity: false
includeRuntimeContext: false
personaPrefix: >-
你是我的研究助手。所有结论给出可核查证据。
不确定的信息标注为待验证,不虚构已经执行的操作。
- id: sandbox-policy
config:
mode: read-only
workspaceRoot: !!js process.cwd()
这份 patch 明确重述了自己需要的字段。若只写 personaPrefix,其他字段不会继承旧 config:它们会回到插件 schema 默认,或因缺必需项失败。覆盖嵌套 preset.config.plugins 更要把完整列表写出来,不能只写新增一行希望旧工具自动留下。DSL 的 insert 添加 rows;id 定位已有 row;disabled 是显式关闭。普通 YAML metadata 不求值,!!js 只用于插件 config 和 disabled 等明确支持的位置;不要把 !js 写成另一种等价语法。Web bundle 自己就在注释中重述整个 config 的原因。源码:cordis.patch.yml · A patch replaces。
dump 是配置观察手段,启动成功是另一个证据。 --dump-config 能让你审查树、id 和生效 config;它不说明模型认证成功、native backend 实际可用,也不说明 external server 已连接。
# 在自己安装的固定版本 dsh 中执行。
dsh --profile sdk-minimal --dump-config
dsh --profile sdk-minimal --patch ./research.patch.yml --dump-config
4.3 boot 的异步生命周期与模块解析¶
CLI 将 inner args 冻结后提供成 cmdlineArgs service。像 web-startup、headless-startup 的插件读取应用参数,launcher 不替它们理解每个工具的参数。appReady 在 boot、Host setup 成功后才提交;应用 consumer 通过 service 等待,不应以“某个 module 已 import”代替就绪。源码:profile-boot.ts · function createAppReady。
bundle 名称先从 dsh installation 解析,再从 profile 目录解析;profile 的 pnpm-owned dependencies 获得显式优先级。runtime resolution 维护 installation 与 profile 两类包表,遍历 dependency/peerDependency 图,帮助外部插件拿到共享 Cordis 实例。pnpm-workspace.yaml 用 hoisted linker、autoInstallPeers: false,目的之一就是避免插件自己安装另一份服务定义/Cordis。这解释了为什么第三方插件的 peer range 必须与当前 DSH 版本一致。源码:profile.ts · export async function createRuntimeResolution。
两个快照都已记录指向 profile 外部目录的 linkedRoots,保留真实祖先的 Node module lookup;LinkedRoot 与 linked-root 解析不是 main 新增功能。main 新增的是携带来源输入的 ProfileRuntimeResolution 与 computeLatestResolution():重读 profile manifest、已装依赖和 bundle selection,配合 PluginPackages.refresh() 与 HMR 协调新一代解析。rc.2 的 resolution 是冻结数据对象,不能按 main 的新方法调用。源码:profile.ts · export interface LinkedRoot、profile.ts · computeLatestResolution():。
loadProfileDirectory 可以把解析/兼容失败的 bundle 记录到 skippedBundles,launcher 会报告;不能笼统说“一行坏配置必定中止所有应用”或“缺包默默忽略”。最终启用树还接受 startup audit、fail-loud 和 profile compatibility policy。查看报错必须区分 bundle 跳过、row 禁用、row 缺依赖等待、import/activation 失败四种状态。
4.4 preset 的隔离不是新建整个进程¶
agent-preset-registry 建立常驻的 preset revision tree。选择该 revision 的多个 Agent 继承其 scoped registrations;当定义变化,已有会话持有旧 revision,不能把每次会话创建想象成从 YAML 重装一遍所有包。scope 控制注册可见性,Cordis isolate 控制 service implementation 使用哪个 realm;两者解决不同问题。
mountPreset() 必须已有 scope,它先创建只在内存写入的 PresetTree,对 rows 做 profile 兼容处理,等待并审计,检查有没有服务泄露到 root realm。preset 内新增 planMode、compaction、workflowEngine、terminals 这类 service provider,通常必须包在具有相应 isolate 的 group 中。否则该 service 会影响整个进程,mount 会报告 Preset services require isolate realms。源码:mount.ts · export async function mountPreset、mount.ts · export function leakedServices。
# 一段 preset 内部结构,不是独立启动配置。
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
toolResultPruner: true
config:
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
auditRows() 分开返回 failed 与 pending。disabled row 有意不激活;缺 Host service 的 row 可以保持 mounted,等 Host settles 后再审计,不必在第一瞬间误判整个 preset 已不可用。插件初始化失败、service leak、未最终满足依赖都不是“工具暂时为空”的同义词。源码:mount.ts · export async function auditRows。
4.5 Standard、Minimal、Code/PTC、Creator 到底分别是什么¶
源码稳定 id 为 standard、minimal、ptc、cordis。Creator 是显示名,创建/恢复会话用 cordis;Code/PTC 能力对应 ptc,不要发送不存在的 creator 或 code id。 当前 UI 字典显示 PTC mode,而部分教程/介绍以 code mode 描述编程调用工具;读实际 display.ts 与 locale,而不是从网页标题猜 API。源码:display.ts · const BUILT_IN_PRESET_KEYS。
| preset id | 主要组合 | 适用场景与注意事项 |
|---|---|---|
standard |
persona、工作区指令、bash/pwsh、fs/search、jobs、skills、goal、planning、compaction、delegation、web、interaction/todo、present | 完整 coding agent;常规工具调用,可装 workflow/subagent |
minimal |
complete persona + isolated terminal service + persistent bash/pwsh | 快速简单执行;没有自动继承 standard 的 skills/compaction/delegation |
ptc |
大体类似 standard,编程编排能力由 Tools mode/runtime 提供,仍包含自己声明的 scoped consumers | 批处理、程序化聚合、减少模型来回;不能只靠这个 id断言运行时 mode 已切换 |
cordis |
coding 能力 + tool-cordis + bundled Cordis 作者 skills + plugin-manager tools |
作者 Agent:让 Agent理解/改造插件组合;权限、HMR 与包安装会影响主机,需自行限定 |
standard 与 ptc 的声明文件不是全部 runtime:global base services、profile tools mode 和 scoped限制共同决定最终模型看到什么。Web 有临时 DSH_TOOLS_MODE 环境 seam 写入 tools config;省略时是 native。实际应 dump 并检查 tools.mode,不能把 preset 名称等同于 native|ptc|both。tool-ralph 在 standard/cordis声明中默认 disabled;部分 external subagent tools 也 disabled;“文件里出现”与“默认可用”须分开。main 的 standard/ptc/cordis 增加 time-context/schedule consumers,而 service由Web提供;rc.2 的同文件缺少这些新 rows。源码:cordis.patch.yml · id: tools、standard.patch.yml · id: preset-standard、minimal.patch.yml · id: preset-minimal、cordis.patch.yml · id: tool-cordis。
4.6 以发布版做一份属于自己的完整 profile¶
最稳妥的起点是拷贝 shipped template 的 bundle 列表,再覆盖少量配置;--from-default-profile 不读取另一已存在 profile 的私有 patch、依赖或会话,更不是持续继承。名字必须是新的、非保留 shipped name,目录存在时拒绝复用。初始化失败会清理本次新建目录。源码:profile-boot.ts · export function initializeProfileFromDefault。
# Node ^22.19 或 >=24,安装完整匹配的 rc.2 CLI。
# 若下载实验目录已安装依赖,优先使用实验的 node_modules/.bin/dsh。
npm install --save-exact @deepseek-ai/[email protected]
# 自己的 Harness home;不要覆盖系统 HOME。
export DSH_HOME="$PWD/.dsh-course-home"
./node_modules/.bin/dsh --profile my-research \
--from-default-profile sdk-minimal --dump-config
# 之后不再传 --from-default-profile,profile 已存在。
./node_modules/.bin/dsh --profile my-research \
--patch ./research.patch.yml --dump-config
对科研助手,先保留 minimal SDK 的 LLM/session/kernel,关闭 persistent-bash/persistent-pwsh rows,再装一个只提供白名单资料工具的自有 plugin bundle。只改 persona 无法实现工具权限;只写“只读”提示无法阻止 shell。需要 shell 时,明确选 read-only 或 workspace-write,并验证系统沙箱可用。发布版 sdk-minimal 默认 danger-full-access,这是该 bundle 的显式选择,不是 SandboxPolicy schema 默认。
第三方 bundle 通常包含三个文件:ESM index.js、cordis.patch.yml、声明 dsh.bundle.patch 的 package.json。它通过 dsh plugin --profile my-research add ./your-plugin 安装到 profile;安装依赖与启用 bundle 是不同状态。检查 dsh.profile.bundles,启用只添加你审核过的 bundle 名,再 dump。包安装 CLI 接受 pnpm 参数,manifest必须声明真正使用的 peer ranges,不要 npm link 出第二份 Cordis。课程可运行实验把这套流程做成可重复目录,见 插件实现、构建自己的 Agent 与 离线 SDK 实验。源码:plugin.ts · export async function runPlugin。
4.7 你自己的架构选择¶
做一个领域 Agent,通常选择 sdk 或受限 sdk-minimal profile 作为应用载体,领域 tool/plugin 作为能力,preset 作为会话角色。多个业务角色共用同一 Host 时,用 preset scope 管控工具与可替换 service;要隔开凭证、进程权限或不同租户,就用独立 DSH_HOME、独立进程/部署,scope 本身不承担操作系统安全隔离。
这个设计与 Pi 最核心的区别是组合所有权:Pi 常从 SDK 建立会话再注册 extension;DSH 要把 loop、model adapter、tools、storage、surface也当作可替换插件,在 profile中先构造完整应用,业务使用 SDK 驱动它。真正可运行的 Agent 是一份能审查、能 dump、依赖闭合、权限明确的组合,加上可测试的业务插件。