跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

使用 YourBuddy

先完成一个实际任务,再根据逐渐清楚的需求增加专业能力。

开始使用

已有能力

按需扩展

工作时可以通过侧栏的“帮助与指南”打开这些说明。产品理念见为什么做 YourBuddy,后续方向见路线图

1 - 为什么做 YourBuddy

Your models. Your tools. Your way.

YourBuddy 将 DeepSeek Harness 与社区插件装配为桌面工作台。模型负责理解和生成;Harness 提供完成工作所需的工具、上下文、权限、会话与反馈。

先用起来,再按需扩展

默认组合提供模型接入、工作区任务和结果检查。遇到专业需求时,再选择扩展方式:复用插件、编写 Skill、调整配置或开发 DSH 插件。小团队可以共享项目和工作说明,每位成员使用自己的账号与工作区。

专业工作台,补上两样东西

DSH 本身就是一个带 WebUI 的 AI 运行时。会话创建、模型与工具的执行循环、历史记录,以及插件机制和 MCP 工具接入,都已有现成能力。Y8 在这个基础上提供开箱即用的默认组合;面向具体领域的专业定制,重点是补齐两部分:

  • 领域分析引擎:按领域的数据和规则完成分析,给出可以检查的结果。
  • 趁手的操作面板:让用户选择对象、发起任务、调整参数,并查看进度和结果。

这两部分都可以通过 DSH 插件实现。插件可以提供分析能力,也可以扩展工作台界面,让领域逻辑和实际操作围绕同一个任务组织起来。Harbor 的开发案例展示了这种做法。

选择适合自己的组合

按能力、可用性和成本选择模型。工具有助于任务时再加入。给工作台换上自己的名称和 Logo,同时直接使用已经装配好的应用。

检查实际发生了什么

让对话与文件、命令输出、Git 差异相互连接。能够检查实际结果时,助手的完成说明才有依据。

用证据改进

Context Doctor 帮助理解上下文负担;Harbor Evolution 帮助分析已完成的任务、比较受控修改。分数需要解释,一次运行成功也不自动意味着效果提升。

知道工作去了哪里

项目文件保存在你选择的工作区,会话与设置在本地管理。云端模型、搜索服务和评测器会接收完成相应操作所需的输入。本地存储不代表所有计算都离线进行。

首批桌面目标为 macOS Apple Silicon。支持范围与限制见下载插件归属路线图

2 - 完成第一个任务

在工作区创建一个文件,再亲自检查它。

开始前

准备一台受支持的 Mac、可用模型账号或 API 凭据,以及一个练习文件夹。先确认下载状态。桌面包携带默认插件与托管运行时,普通用户无需从源码构建。Docker 只用于确实需要它的评测流程。

接入模型并选择工作区

  1. 启动 YourBuddy,等待运行时准备完成。
  2. 打开设置,完成一条模型配置路径
  3. 在工作区选择器中添加并选中练习目录;选中后输入框即可使用。

请求一个成果

在当前目录创建 hello-yourbuddy.md,写出本周的三件事和各自的验收条件。完成后告诉我保存位置。

界面请求审批时按提示处理。完成后,通过 Better Sidebar 打开文件并检查内容;如果是 Git 仓库,也查看差异。助手的完成说明本身不能证明文件已经存在。

卡住时从哪里继续

卡点对应说明
不知道如何登录或选择模型模型配置
输入框不可用或文件位置不对工作区
插件已经预装但不能使用默认插件及前提

侧栏“帮助与指南”可以随时打开本页和故障排查。先完成这个文件任务,再按需要学习扩展 Y8

继续使用

继续当前会话,探索文件和终端面板,或调整工作台设置。某一步失败时先查看故障排查,无需重新安装所有插件。

3 - 接入模型

选择适合任务的账号或模型提供方。

GPT Auth

打开设置 → GPT Auth,查看登录状态并按引导登录,然后在模型选择器中选择可用 Codex 模型。Codex Auth 是使用非官方账号接口的社区集成,受账号可用性和额度限制,并复用原生 Codex 登录存储。

API 提供方

打开设置 → 模型,选择添加提供方,或在 DeepSeek 卡片中保存密钥。选中提供方和模型,发送一条简单文本请求。其他 DSH 环境中配置的凭据不会自动成为 YourBuddy 的凭据。

自定义接口

公司网关或兼容的本地服务可使用添加自定义提供方。填写提供方 ID、基础地址、协议、凭据与模型。声明的图片和工具能力必须与实际接口匹配。详细模型参考介绍模态声明与协议配置。

检查当前选择

API 模型变更会在下一次请求生效。比较结果前检查当前会话的选择。Codex Agent Preset 用来启用委派,与主会话选择哪个模型是两件事。

不要在对话或截图中放入密钥。请求失败时,检查账号、当前模型、额度与网络设置

4 - 处理文件与检查成果

把对话放在实际工作成果旁边。

选择正确目录

开始前添加并选中工作区。Agent 文件操作与 Harbor 工具请求使用当前会话的工作目录。陌生任务先使用练习文件夹,并核对工具返回的实际路径。

检查产物

Better Sidebar 提供文件树、编辑器、预览、终端、Git 和后台任务面板。打开生成的 Markdown 或代码,对照需求,检查命令结果。Git 操作与真实终端会影响实际工作区。

委派一个明确任务

默认 Codex 预设可以把自包含任务委派给 Codex Subagent。提供目标、相关文件和完成条件。它共享工作目录,但不会继承完整父会话;登录和权限遵循原生 Codex 配置。加载提供方并不会启动子进程。

有依据地管理上下文

Skill 提供可复用的任务指令;插件增加运行时能力或界面。额外指令和工具会占用上下文。精简前使用 Context Doctor,修改后再检查一个代表性任务。

可选控件见 Better Sidebar,反复任务的质量检查见评测

5 - 工作台设置

在一个地方调整界面并管理桌面应用。

工作台身份

打开设置 → 通用设置 → 我的工作台。预览并保存侧边栏名称、图片、新会话页标题与可选标题标记,也可以恢复 YourBuddy 默认值。文案字段留空时使用当前语言的标题和标记默认值。身份按 Profile 保存,重新打开后仍保留。它不会修改桌面图标、应用名称或完整主题。

网络

打开设置 → 通用设置 → 网络代理,选择直连、跟随系统或自定义固定代理。自定义模式需要不含认证信息的 HTTP 和 HTTPS 地址;不支持 PAC 与自动代理发现。企业网络可选用可读的 PEM 证书文件补充系统信任,证书校验始终开启。

分别测试桌面草稿链路和正在运行的 Host 链路。修改策略后保存并重启,再次测试确认生效。重启前,现有进程仍使用原策略。

更新与重启

通过设置 → 通用设置 → 应用生命周期检查更新或重启。新装插件后重启,让客户端插件被发现。内置插件随 YourBuddy 发行更新,上游版本提示不会自动安装任何内容。

这些控件来自 YourBuddy 自有的 Personal Workbench 插件。原生重启、代理与更新操作需要桌面壳,在独立 DSH Web 中不可用。

发行可用性见下载,失败处理见故障排查

帮助与指南

侧栏底部的“帮助与指南”提供快速开始、默认插件、扩展 Y8、故障排查与反馈入口;侧栏收起时使用问号图标。指南在外部浏览器打开,当前会话保留。打开失败时可复制显示的地址;网页正文需要网络连接。

6 - 评测与改进

从真实任务的证据开始,再建立稳定的比较。

四个实用概念

概念回答的问题
评测集 Dataset测什么?
生成器 Generator谁来产生答案或成果?
评测器与标准 Evaluator怎样算好,由谁检查?
优化器 Optimizer谁提出下一次受控修改?

随附的 Harbor Skill 会引导配置。在对话中调用 evolve-agent-with-harbor 并说明任务。桌面已包含插件、Skill 与配套 Python Adapter,无需重复独立包的安装命令。Candidate 执行仍需相应 Docker 环境和可用模型服务,开始前运行 Doctor。

还没有数据集

请求诊断当前工作目录中近期已完成的会话。先预览记录范围、评测器、预计请求与证据保存说明,再确认执行。在 Harbor 工作台检查覆盖率、证据和未评分原因。

历史诊断不重新运行 Candidate,也不能作为晋级基线。已完成但未评分的记录不等于零分。没有符合条件的历史时,先完成任务或明确提供 Query/Dataset。

已有可重复任务

确认四个概念,固定 Candidate 模型并建立有效基线。每次改变一个因素,在可比任务上回归,同时检查改善与退化。模型或评测器身份变化时可能需要新基线。

单 Query 快速诊断检查执行链路,不等于按草拟标准完成了质量评测。正式晋级需要已接受的策略,评测器可靠性需要独立 Ground Truth 元评测。部署仍是独立操作。

成本、凭据与限制见 Harbor Evolution

7 - 故障排查

从失败的具体操作开始,并保留诊断信息。
现象下一步检查
输入框不可用先选择工作区再开始会话
模型请求失败检查当前模型、登录或密钥、额度与网络状态
找不到生成的文件检查会话目录与真实工具结果,不只看助手概述
没有 Context Doctor先建立已有会话;尚无标识的新会话不显示会话级审计控件
插件安装后没有界面确认目标是 YourBuddy Web Profile,再通过应用生命周期重启
插件市场无法安装npm 包可能缺少明确的 Bundle 元数据,GitHub Topic 本身不足以证明可安装
代理测试成功但请求失败同时查看桌面与 Host 结果;修改后保存、重启并重新测试
Harbor 找不到历史使用有已完成业务会话的精确目录,或提供明确任务
评测结果未评分检查证据和覆盖率,信息不足时可以正常弃权
帮助页面打不开复制帮助菜单显示的地址,在浏览器中打开;检查网络,Help 不包含离线正文
开发教程找不到插件文件使用教程中的绝对路径,并从已准备好的 DSH 源码仓库运行命令

启动与系统提示

运行时准备失败时保留启动诊断,不要首先删除应用数据目录。Apple 签名、公证与 Tauri 更新签名是独立机制,请按实际发行说明处理系统打开提示。

提供有用的问题信息

附上应用版本、系统与架构、失败操作和可见错误。去掉凭据、私有任务内容与敏感文件路径。macOS 应用根目录为 ~/Library/Application Support/YourBuddy,项目产物保存在选定工作区。

详细前提见模型配置设置或对应插件页反馈问题

8 - 扩展 Y8

从一个想改进的具体任务开始。Y8 使用 DSH 的插件机制,你可以采用已有扩展,也可以开发自己的插件,沿用同一种插件格式。

专业插件可以从两个问题开始:领域分析引擎需要处理什么数据、返回什么结果?操作面板需要让用户发起什么操作、检查哪些结果?按需求扩展其中一部分或两部分,复用 DSH 已有的运行能力。背后的分工见产品理念

选择合适的方式

你的需求从这里开始
Y8 已经包含的能力阅读默认插件及其使用前提
已有其他人维护的能力插件市场查找,检查 README 和兼容要求
固定流程、提示词或输出标准编写工作区 Skill;仅有操作说明时无需新建运行时插件
调整选项或组合现有能力先看设置,再看插件配置和 Bundle 说明
接入一个已有外部服务先检查该服务是否提供可用的 MCP 集成
增加操作、服务、设置卡片或界面进入下面的插件开发路径

定义一个小需求

把这份提纲复制到对话或项目 README。先填写已知信息,其余可以让助手检查现有项目;提供样例数据,不要填写凭据。

  • 要解决的任务:
  • 输入内容及来源:
  • 期望输出,以及在哪里检查:
  • 允许修改的范围,以及禁止执行的操作:
  • 需要的账号、服务和配置项:
  • 一份样例输入及预期结果:

例如:只读查询团队构建服务的失败任务,返回失败原因和链接,不触发重跑。提供一份脱敏响应作为验收样例。这是扩展需求示例,不表示 Y8 已经接入该服务。

开发并检查结果

  1. 借助 Creator 开发,查询可用接口,试验一个范围明确的需求。
  2. 第一个插件加载可编辑源码。本教程需要准备好的 DSH 源码 checkout,仅有桌面安装包还不够。
  3. 增加一个工具配置项,每次修改一种行为并检查可见结果。
  4. 打包与安装把源码交付给其他人。临时动态试验不等于已安装的软件包。

专业扩展示例见 Harbor 如何开发。框架、事件和服务细节见框架指南;尚未在本站发布的深入参考会打开 GitHub。

在团队内复用

共享项目或确切的软件包版本、支持的 DSH 版本、安装步骤、配置项名称和验收样例。同事使用自己的账号与目录,再重复执行样例。共享文件不携带私人会话或凭据。本地 checkout 链接只在作者机器上有效,交给他人时使用打包指南。

文档中的步骤失败时,通过故障排查提供具体步骤、版本与错误,帮助区分说明缺失和需要完善的开发能力。

参与 Y8 开发

构建桌面应用和维护站点属于贡献者工作。相关 GitHub 参考为桌面源码指南站点维护指南产品声明

8.1 - 借助 Creator 开发

Creator 帮助你检查正在运行的 DSH 组合,并试验一个小插件。任务需要新操作或界面时使用它;如果只是调整配置或固定流程,先看扩展方式选择

准备环境

本指南使用 DSH 源码 Web 应用。先完成 GitHub 上的源码环境准备,再在仓库根目录运行:

pnpm dsh web --no-open --port 0

打开命令打印的启动地址,包括其中的认证片段。系统会分配可用端口。选择工作目录并配置模型后再发送请求,在会话的 Agent Preset 选择器中选择“创造模式”(Creator,预设 ID 为 cordis)。如果当前安装的 Y8 未提供该预设,请使用这条源码路径;仅安装 Y8 不代表已具备独立插件开发环境。

描述改动

使用需求提纲,请助手加载 Creator 预设中的插件开发 Skill;涉及组合配置时,再使用该预设中的组合编辑 Skill。

首次试验可以要求一个临时只读工具,返回某项任务的简短检查清单,并要求助手先查询当前工具注册接口。写明预期结果,要求验证后停止试验。模型请求使用你配置的账号。

先查询再实现

Creator 可以列出检查提供方、查询实际接口,并读取已有试验的源码和诊断。请它将操作与服务放在 Host,将界面放在 Client,确有需要时再组合两者。缺少能力时应明确报告,不能猜测 API。

激活前先看源码预览和预期效果。动态代码是普通 JavaScript 函数体,不是 TypeScript 或 JSX 模块。定义试验不等于执行它。按提示处理审批,并检查最终运行状态;等待中或启动中都不代表成功。

检查并停止

调用新增工具或操作改动后的界面,与样例中的预期结果比较。失败时让 Creator 检查该试验的诊断和源码。完成后停止它,确认对应工具或界面贡献消失。停止会移除注册,不会撤销已经写出的文件或外部操作。

保留有用的成果

动态定义保存在进程内,Host 重启后会消失。会话中记录过源码,不代表会自动重新加载。重启前把有用源码和需求保存到项目,再通过第一个插件教程打包指南建立并验证普通插件。这条路径没有动态代码自动转包功能。

修改已有插件时,继续维护原项目及其身份和配置。自定义预设使用用户自己的副本,不要直接修改随应用提供的 Creator 预设,因为升级会替换它。确切运行规则和诊断说明见 GitHub 上的动态工具参考

8.2 - 第一个插件

本教程会创建一个最小的 Harness 插件,并将其加载到 Web UI 中。请从已完成从源码运行路径的仓库检出开始。

创建本地项目

在仓库根目录创建本教程使用的临时项目:

mkdir -p scratch-plugin/src

插件是什么

在 Harness 中,插件是一个导出 apply 函数的 TypeScript 模块。框架在加载时调用 apply,传入一个 ctx(上下文对象),你通过 ctx 注册能力:

import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-plugin'

export function apply(ctx: Context) {
  // Register capabilities here.
}

这就是完整配置。

创建插件文件

创建 scratch-plugin/src/my-plugin.ts

import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  // Required dependencies are ready before apply runs.
  console.log('[hello-plugin] plugin loaded!')
}

注册到 cordis.yml

在仓库根目录运行 pwd,然后创建 scratch-plugin/cordis.yml,作为插入本地插件的 Web 覆盖层。请将下文的 /absolute/path/to/deepseek-harness 替换为命令打印的路径:

- insert:
    - id: hello
      name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'

插件路径必须是绝对路径。patch 文件只贡献配置,不会改变 loader 解析模块路径时使用的 profile 目录。

使用该覆盖层启动 Web UI:

pnpm dsh web --patch ./scratch-plugin/cordis.yml --no-open --port 0

打开终端打印的启动地址,包括其中的认证片段。启动期间,终端会打印 [hello-plugin] plugin loaded!

修改与停止

修改插件中的问候语,使用 Ctrl+C 停止开发命令,再运行同一条命令。检查终端是否打印新的问候语。每次重启使用新打印的启动地址;端口 0 表示由系统分配可用端口。完成后停止开发命令。

自动清理

通过 ctx 注册的任何东西——事件监听、工具、定时器——在插件卸载时都会被自动清理。你不需要手动 removeListener 或 clearInterval。

如果你有需要手动清理的资源(比如一个网络连接),用 ctx.effect() 告诉框架怎么清理:

import type { Context } from '@deepseek-ai/cordis'

export function apply(ctx: Context) {
  ctx.effect(() => {
    const timer = setInterval(() => {
      console.log('heartbeat')
    }, 5000)

    // The returned function runs when the plugin unloads.
    return () => clearInterval(timer)
  })
}

声明依赖

如果你的插件需要使用其他服务(如 toolsllm),需要声明 inject

import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-tool-plugin'
export const inject = ['tools']

export function apply(ctx: Context) {
  // ctx.tools is ready here.
  ctx.tools.register(/* ... */)
}

框架会确保依赖的服务就绪后才加载你的插件。

插件的三种形态

除了函数形式,插件还支持对象形式和类形式:

对象形式

import type { Context } from '@deepseek-ai/cordis'

export default {
  name: 'my-plugin',
  inject: ['tools'],
  apply(ctx: Context) {
    // ...
  },
}

类形式

import { Service, type Context } from '@deepseek-ai/cordis'

export default class MyService extends Service {
  static inject = ['tools']

  constructor(ctx: Context) {
    super(ctx, 'myService')
    // Perform synchronous initialization in the constructor.
  }
}

大多数情况下,函数形式足够了。当插件需要向其他插件提供服务时,可使用类形式(见 服务与依赖)。

下一步

8.3 - 开发一个工具

本教程会在 Web UI 中添加一个 greet 工具。请先完成第一个插件,并保留其中的 scratch-plugin 目录。

创建工具插件

scratch-plugin/src/my-plugin.ts 替换为:

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: { type: 'string', required: true, description: 'The name to greet' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

inject 让 Cordis 等待工具注册表就绪。defineTool 根据 parameters 推导并校验 argsexecute 返回 output.schema 声明的规范值,output.render 再将该值转换为面向模型的内容。

运行并调用工具

如果开发命令未在运行,请重新启动:

pnpm dsh web --patch ./scratch-plugin/cordis.yml --no-open --port 0

打开终端打印的启动地址,包括其中的认证片段,然后输入:Use the greet tool to greet Ada. 模型可以调用 greet,并收到 Hello, Ada! 这一工具结果。

下一步

  • 插件配置 — 让问候语可配置。
  • 工具编写参考 — 查阅嵌套 schema、规范值、后台工作、策略钩子、PTC mode 和 UI 卡片。
  • 能力分层 — 将可替换能力拆分为 Service Definition、Service Provider 和 Consumer 三类包。

8.4 - 插件配置

让你的插件接受用户在 cordis.yml 中传入的配置。

先完成第一个插件。和该教程一样,将下方绝对路径替换为自己的仓库路径;patch 不会改变插件模块的解析目录。

定义 Config 类型

在插件中导出一个 Config 类型和同名的 Schemastery schema;默认值直接写在 schema 中:

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'

export const name = 'my-plugin'

export interface Config {
  greeting: string
  maxRetries: number
  verbose?: boolean
}

export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  maxRetries: Schema.number().default(3),
  verbose: Schema.boolean().default(false),
})

export function apply(ctx: Context, config: Config) {
  console.log(config.greeting)  // User value or schema default.
}

scratch-plugin/cordis.yml 新插入的本地插件行中添加配置:

- insert:
    - id: hello
      name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
      config:
        greeting: 'Hi there'
        maxRetries: 5

插件加载时,Cordis 会通过导出的 schema 校验配置,并填充未提供字段的默认值。不要导出普通对象作为 Config,因为它不满足 Cordis 要求的 Standard Schema 接口。

Schema 校验

对于需要严格校验的场景,使用 Schemastery 定义 schema:

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'

export const name = 'validated-plugin'

export interface Config {
  apiKey: string
  timeout: number
  mode: 'fast' | 'accurate'
}

export const Config = Schema.object({
  apiKey: Schema.string().required(),
  timeout: Schema.number().default(30000),
  mode: Schema.union(['fast', 'accurate']).default('fast'),
})

export function apply(ctx: Context, config: Config) {
  // config is validated and type-safe.
}

Schema 在插件加载时执行校验。如果配置不合法,插件会加载失败并给出明确错误信息。

设计原则

无硬编码可调参数

Harness 的约定:凡是不同部署可能需要采用不同值的参数,都必须定义为配置字段

// Wrong: hardcoded timeout.
const TIMEOUT = 30000

// Correct: configurable.
export interface Config {
  timeoutMs: number  // Defaults to 30000.
}

检验标准:能否在 cordis.yml 中改变这个值,而不需要修改代码?

配置错误要响亮

在 schema 中表达自身完备的约束,使无效配置在插件加载时失败。对服务或已注册资源的引用需要依赖注入;服务教程 会介绍这项约定。

应用并验证

修改源码或 patch 后,停止并重启教程命令。使用上面的配置时,终端会打印 Hi there。把 maxRetries: 5 改为 maxRetries: wrong-type,重启后检查插件加载时的校验错误;继续前恢复有效值。这条路径不依赖源码或 overlay 自动重载。

下一步

8.5 - 打包与安装插件

前几篇教程通过 --patch overlay 加载本地插件。本教程把它打包成可安装的组合包(bundle),用 dsh plugin add 安装进一个 profile,并解释决定组合后配置的层顺序。本文假设 dsh CLI 已安装。请先完成插件配置

如果改用全新的源码 checkout,请先按照从源码运行章节完成准备,将本教程的 hello-plugin 目录放在仓库根目录,并从该目录把下文的 dsh ... 命令改为 pnpm dsh ...。构建与启动器行为见源码执行

两个概念,两种 manifest

安装机制建立在两个概念之上。二者都由一份 package.json 描述,但它们在 dsh 键下携带的 manifest(元数据清单)种类不同,回答的问题也不同:

  • 组合包是附带一个配置层的 npm 包。它的 manifest 声明 dsh.bundle,回答的是"这个包贡献什么?":一个插入或覆盖插件行的 patch 文件。
  • profile 是位于 $DSH_HOME/profiles/<name> 下、描述一份可启动组合的目录。它的 manifest 声明 dsh.profile,回答的是"这套配置由哪些组合包按什么顺序组成?"。

组合包是你编写并分发的东西;profile 是用户用 dsh --profile <name> 启动的东西。没有东西同时是两者。

组合包 manifest

创建包目录:

mkdir -p hello-plugin
hello-plugin/
├── package.json       # declares dsh.bundle
├── cordis.patch.yml   # the layer applied when a profile lists this bundle
└── index.js           # plugin modules the patch rows reference

创建 hello-plugin/package.json

{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}

创建 hello-plugin/index.js,写入插件入口:

export const name = 'hello-plugin'

export function apply() {
  console.log('[hello-plugin] plugin loaded!')
}

创建 hello-plugin/cordis.patch.yml。这个 patch 与你写过的 --patch overlay 一样,是一个 patch 条目的 YAML 数组;区别是插件行按包名而不是相对源码路径引用这个包,这样 Node 的模块解析才能找到已安装的代码:

- insert:
    - id: hello
      name: dsh-hello-plugin

没有 dsh.bundle 声明的包仍然可以安装,但只作为普通依赖:dsh plugin 会打印警告,且不激活任何层。如果一个库供插件包 import,而不是供用户启用,就使用这种包格式。

profile manifest

profile 目录包含两个文件:

  • package.json — profile 的树外插件依赖(由 pnpm 管理),加上 dsh.profile manifest 及其有序的 bundles 列表。
  • cordis.patch.yml — 用户自己的 patch 层,在每个组合包层之后应用。

profile manifest 从不需要手写:dsh --profile <name> --from-default-profile <template> 可以从随附应用模板创建 profile,dsh plugin 则创建一个以 base 为基础的 profile,并维护其中已安装的 bundle 列表。创建规则以 CLI(命令行界面)行为参考为准;下一节展示插件路径。

安装进 profile

dsh plugin --profile <name> <args...> 在 profile 目录内转发给 pnpm,因此所有 pnpm 子命令都可用。在包含 hello-plugin 的目录中安装该包的 checkout:

dsh plugin --profile demo add ./hello-plugin

首次使用会初始化 profile(@deepseek-ai/dsh-base 作为它的第一个组合包),pnpm 链接该 checkout,而 dsh 因为这个包声明了 dsh.bundle,把它追加进 dsh.profile.bundles

{
  "name": "dsh-profile-demo",
  "private": true,
  "dependencies": {
    "dsh-hello-plugin": "link:/path/to/hello-plugin"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "dsh-hello-plugin"
      ]
    }
  }
}

先不启动、只验证该层,再启动:

dsh --profile demo --dump-config   # shows a "# == dsh-hello-plugin" layer
dsh --profile demo

dsh plugin --profile demo remove dsh-hello-plugin 会同时移除依赖和对应的层。

加载顺序

生效配置在空根之上按以下顺序逐层组合:

  1. profile 的 dsh.profile.bundles 列表所列的各个组合包 patch,按列表顺序——先是 @deepseek-ai/dsh-base,然后是每个已安装组合包,按其加入顺序。
  2. profile 自己的 cordis.patch.yml
  3. home 级的 $DSH_HOME/cordis.patch.yml——各 profile 共享的机器本地偏好。
  4. 每个 --patch <path> overlay,按 argv 顺序。

应用参数不是另一层 patch。表层组合包可以通过下文所述的普通应用自有服务解析它们。

后应用的层按行胜出,且 patch 会替换目标行的整个 config 值,而不是深度合并各键。这给组合包作者带来两个推论:

  • 你的 patch 可以按 id 覆盖前面各层的行——就像 dsh-web-app 组合包覆盖 dsh-base 的行那样——但必须重述该行需要的每一个键,而不是只写改动的那个。
  • 用户可以在自己 profile 的 cordis.patch.yml 中覆盖你的行,无需改动你的包,所以优先给出用户大概率会保留的配置默认值,其余交给 schema 承担。

内置组合包名称始终从 dsh 安装目录本身解析;pnpm 只管理树外的包,所以你的组合包可以放心依赖 @deepseek-ai/dsh-base 存在且与安装保持一致。

让表层组合包持有自己的命令行

定义了可运行应用的组合包挂载一个普通提供方插件:

- id: hello-startup
  name: 'dsh-hello-plugin/startup'

该插件导出 inject = ['cmdlineArgs'],使用自己的 commander program 调用 @deepseek-ai/dsh-cmdline 中的 parseCmdline,再在 program 自己的 action 中把应用自有服务提供出去。启动器把自身 flag 之后的同一份不可变参数交给每个插件,因此添加应用专属 flag 无需修改启动器,多个插件也可以解析该快照。Loader 行不需要启动器标记或特殊类型。

受这些参数配置的行会注入提供方服务,并在自己的 !!js 选项中读取它,同时把部署取值写在旁边作为回退:

- id: my-app
  name: '@example/my-app'
  inject: [myAppStartup]
  config:
    port: !!js ctx.myAppStartup.port ?? 8080

遇到 --help 时,提供方不会发布该服务,所以这些行不会激活。Loader 只挂载一次组合,等待每一行的普通注入,再基于其已注入的上下文求值该行的 !!js 配置。

从 GitHub 安装:构建脚本这道坎

发布到注册表不是必须的——用户可以直接从 git 托管安装:

dsh plugin --profile demo add github:you/hello-plugin

但 git 安装拉取的是源码,不是构建产物:没有任何环节运行你的 build 脚本,因此 TypeScript 包到手时没有 lib/ 输出,加载会失败。必须两边各做一件事:

  • 作者提供一个 prepare 脚本——pnpm 在 git 安装后运行它——从源码构建出发布入口,且必须自包含:不能假设仅开发环境才有的上下文,例如旁边有一份 monorepo checkout。turtle-ui 是一个可用的例子:它的 prepare 运行一份专用的 tsdown 配置,直接转译 src/,不用项目引用,也不做类型检查。

  • 用户为构建授权。pnpm ≥10 在得到显式允许之前拒绝运行 git 依赖的 prepare 脚本,所以第一次 add 会失败;dsh 会指出修法——把 pnpm 打印的确切包键复制进该 profile 的 pnpm-workspace.yaml

    allowBuilds:
      dsh-hello-plugin: true

    然后重新执行 add

请把这项授权视为允许该包的代码在安装时于你的机器上执行,且不在 agent 运行的任何沙箱之内。只对源码可信的包授权,并锁定 commit(github:you/hello-plugin#<sha>),让后续推送无法悄悄改变实际运行的内容。

如果不想让用户做这项授权,就改为分发构建产物——以下两种形式都不需要任何构建权限:

  • 发布到 npm,在 pnpm publish 时构建好 lib/dsh plugin add your-package 安装的就是预构建代码。
  • 交付 tarball:用 pnpm pack 打包;用户执行 dsh plugin add ./hello-plugin-0.1.0.tgz

下一步