Repository Wiki
deepseek-ai/deepseek-harness

编写插件、工具与模型适配器

本页说明 DeepSeek Harness CLI 如何把插件包管理、工具插件与模型相关插件组合到 profile 中,并通过显式版本兼容审批、文件锁和安装器上下文控制扩展生命周期。

Purpose and Scope

本页聚焦 apps/cli/src/plugin.ts 暴露的插件命令执行路径,以及 apps/cli/composition.md 所描述的基础插件组合关系。覆盖内容包括:profile 定位、插件命令分流、pnpm 操作委托、Desktop profile 的特殊处理、兼容版本豁免、构建脚本提示、输出与失败码传播,以及插件/工具/LLM 相关模块在 dsh-base bundle 中的组织方式。

本页不展开各个外部插件包的内部实现:这些包通过 @deepseek-ai/* 依赖注入,当前仓库中没有它们的源码实现。有关具体工具行为、模型 API 协议或单个插件的业务逻辑,应查看对应插件包或 sibling catalog 页面;本页只记录 Harness CLI 对它们的编排边界。

Overview

插件、工具和模型适配器在本项目中不是由 CLI 逐个实现,而是以 profile 为安装边界,由 runPlugin() 创建 profile 上下文并委托给 @deepseek-ai/dsh-plugin-manager。这种设计将两类职责分开:

  • CLI 负责用户可见的命令语法、profile 前置检查、锁定、输出转发和退出码。
  • dsh-plugin-manager 负责实际的包管理操作及不兼容版本检测;其内部算法未在当前仓库中提供。
  • composition.md 展示 dsh-base 通过 patch 配置聚合插件 ID 与 npm module,包括 tool-plugin-manager、plugin-manager、llm、agent、agent-default-model、tools、llm-deepseek 等。
  • Desktop profile 使用应用已初始化的 package tree;其他 profile 可以由 CLI 按模板初始化。

因此,适配器的扩展点是“可安装的 profile package/module”,而不是在 plugin.ts 中增加一个模型分支。CLI 只在版本豁免命令处解析 DSH 自有参数,其余参数原样留给 pnpm/插件管理器。

Architecture

Loading diagram...

Source: plugin.ts

Source: composition.md

架构中的实线关系对应 CLI 源码中的真实调用;bundle 内的插件关系表示 composition.md 中同一 dsh-base patch 对插件模块的聚合,而不是这些外部模块的内部调用关系。runPlugin() 依据 profile 是否为 desktop 在两个外部管理器操作之间选择:Desktop 走 runProfilePnpm 并加锁,其他 profile 走 runPluginCommand。

Profile 与插件安装边界

Profile 初始化

runPlugin() 首先对 Desktop 做快速检查:profile 目录必须已经存在 package.json。缺失时不会自动创建,而是返回失败并提示先启动并完全退出 Desktop 应用。这一限制避免 CLI 在应用专属目录中创建不完整的 package tree。

对于非 Desktop profile,版本命令进入锁区后会在缺少 package.json 时调用 initProfile(),并选择 PROFILE_TEMPLATES[profile]?.bundles,找不到模板时回退到 DEFAULT_PROFILE_BUNDLES。普通插件命令本身不在 CLI 中初始化 profile,而是把现有上下文交给 runPluginCommand;初始化策略因此只在版本豁免处理路径中明确出现,完整安装行为由外部管理器控制。

Desktop 与普通 profile 的差异

场景package.json 检查执行器文件锁
desktop必须预先存在runProfilePnpmpackage.json,等待 120000 ms
非 Desktop版本豁免路径可按模板初始化runPluginCommand由外部管理器承担普通命令的操作语义

源码还会将 profile、profile 目录、INSTALL_ANCHOR 和当前工作目录组成 context。这让安装器获得稳定的 profile 边界,同时保留调用方的 cwd,从而可以处理相对于调用目录的 pnpm 参数。

版本兼容审批流程

versionCommand() 只拦截三个 DSH 自有命令:allow-version、revoke-version 和 version-exemptions。任何其他命令返回 undefined,由 runPlugin() 继续普通插件操作;这避免 CLI 重新实现 pnpm 参数解析。

解析规则是严格的:

  1. 第一个非 option 参数作为 packageVersion。
  2. --dsh-version value 或 --dsh-version=value 设置精确的 DSH runtime 版本。
  3. allow-version 只有在出现 --accept-risk 后才允许确认风险。
  4. 未知参数、重复关键参数或缺少 package/runtime 版本会抛出 usage 错误。
  5. version-exemptions 不接受额外参数。

通过校验后,操作在 package.json 上执行 withFileLock(..., { waitMs: 120000 })。读取命令调用 readProfileCompatibility() 并把 warnings 写到 stderr,再把 exemptions 以格式化 JSON 写到 stdout;写入命令调用 setProfileVersionExemption(),随后输出 allowed 或 revoked 结果。整个函数捕获异常并返回 1,成功返回 0。

typescript
1const [command, ...rest] = args 2if (command !== 'allow-version' && command !== 'revoke-version' && command !== 'version-exemptions') return undefined 3try { 4 let packageVersion: string | undefined 5 let runtimeVersion: string | undefined 6 let acceptRisk = false 7 const argumentsIterator = rest.values() 8 for (const argument of argumentsIterator) { 9 if (argument === '--accept-risk' && command === 'allow-version' && !acceptRisk) acceptRisk = true 10 else if (argument === '--dsh-version' && runtimeVersion === undefined) runtimeVersion = argumentsIterator.next().value 11 else if (argument.startsWith('--dsh-version=') && runtimeVersion === undefined) runtimeVersion = argument.slice('--dsh-version='.length) 12 else if (!argument.startsWith('-') && packageVersion === undefined) packageVersion = argument 13 else throw new Error(`unexpected argument ${JSON.stringify(argument)}`) 14 }

Source: plugin.ts

这里的设计意图是把“兼容性风险接受”变成精确的 package version + exact DSH version 记录,而不是一个永久的全局开关。源码在 allow-version 前明确向 stderr 输出风险警告,并把 acceptRisk 传给外部 setProfileVersionExemption()。

Core Flow

Loading diagram...

Source: plugin.ts

普通命令的关键顺序是:先确认 Desktop profile 可用,随后尝试处理版本命令;若未命中,再解析 profile 目录和兼容性 warnings,创建操作选项,最后选择外部执行器。这个顺序保证版本审批不会误落入普通 pnpm 参数,同时普通命令仍能看到已有兼容性警告。

插件、工具与模型适配器的组合

composition.md 的 dsh-base patch 将插件 ID 映射到外部包。例如基础组合中同时存在:

  • 包管理基础设施:tool-plugin-manager 与 plugin-manager。
  • 模型/LLM:llm、llm-retry、llm-deepseek、agent-default-model。
  • 工具入口:tools、tool-bash、tool-pwsh、tool-fs、tool-web、tool-workflow。
  • Agent 与执行编排:agent、agent-loop、subagent、workflow-ptc。

这些条目证明工具与模型适配器采用 bundle 配置组合,而不是硬编码在 CLI 的安装函数中。当前仓库没有提供这些包的类、方法签名或运行时协议,因此无法从本仓库进一步推导它们如何调用模型服务、注册工具或处理请求;相关实现细节应以对应 package 源码为准。

Usage Examples

普通 profile 插件命令

下面的调用路径是仓库中导出的 CLI service function。args 不由该函数自行限制为某一套插件参数,未被 DSH 版本命令识别的参数会继续交给插件管理器。

typescript
1export async function runPlugin(profile: string, args: readonly string[], packageManager?: ProfileContext['packageManager']): Promise<number> { 2 if (profile === 'desktop') { 3 try { requireDesktopProfile(resolveProfileDir(profile)) } catch (error) { 4 process.stderr.write(`dsh: ${String(error)}\n`) 5 return 1 6 } 7 } 8 const versionResult = await versionCommand(profile, args) 9 if (versionResult !== undefined) return versionResult 10 const dir = resolveProfileDir(profile) 11 if (existsSync(join(dir, 'package.json'))) { 12 for (const warning of readProfileCompatibility(dir).warnings) process.stderr.write(`dsh: warning: ${warning}\n`) 13 }

Source: plugin.ts

通过 profile manager 执行安装

CLI 为外部管理器统一设置 CLI 执行模式、输出上限、锁等待和 registry lookup 超时,并把子进程输出转发到相同名称的 process stream。

typescript
1const options: PackageOperationOptions = { 2 ...packageManager, 3 execution: 'cli', 4 outputBytes: 16384, 5 lockWaitMs: 120000, 6 lookupTimeoutMs: 120000, 7 onOutput: (text, stream) => { process[stream].write(text) }, 8} 9const result = profile === 'desktop' 10 ? await withFileLock(join(dir, 'package.json'), async () => { 11 requireDesktopProfile(dir) 12 return runProfilePnpm(context, args, options) 13 }, { waitMs: 120000 }) 14 : await runPluginCommand(context, args, options)

Source: plugin.ts

dsh-base 中的工具与模型模块声明

以下摘录来自自动生成的组合文档,展示配置层如何把 bundle patch 连接到工具管理器、LLM 和模型适配器模块。

yaml
1# composition.md 中生成的关系(展示为配置图节点) 2packages/bundle/base/cordis.patch.yml -> @deepseek-ai/dsh-plugin-manager/tools 3packages/bundle/base/cordis.patch.yml -> @deepseek-ai/dsh-plugin-manager 4packages/bundle/base/cordis.patch.yml -> @deepseek-ai/dsh-llm 5packages/bundle/base/cordis.patch.yml -> @deepseek-ai/dsh-agent-default-model 6packages/bundle/base/cordis.patch.yml -> @deepseek-ai/dsh-tools 7packages/bundle/base/cordis.patch.yml -> @deepseek-ai/dsh-llm-deepseek-api-key

Source: composition.md

上例是文档生成器输出的关系图内容,不是可直接执行的配置文件;真实 patch 文件未在本次有限源代码读取范围内读取。

Configuration Options

选项/上下文类型默认/固定值说明
profilestring调用方提供决定 profile 目录、初始化模板和 Desktop 分支。
argsreadonly string[]调用方提供DSH 只解析三个版本命令;其他参数交给 pnpm/插件管理器。
packageManagerProfileContext['packageManager']可选覆盖或补充 PackageOperationOptions 中的安装器配置。
execution字面量'cli'CLI 固定使用的外部操作执行模式。
outputBytesnumber16384传给外部管理器的输出字节限制。
lockWaitMsnumber120000包操作上下文中的等待时长。
lookupTimeoutMsnumber120000包查找操作的超时。
文件锁等待number120000版本豁免与 Desktop pnpm 操作都以 120 秒等待 package.json 锁。

resolveProfileDir()、PROFILE_TEMPLATES、DEFAULT_PROFILE_BUNDLES 和 INSTALL_ANCHOR 来自外部包或另一个已读取但未展开的本地模块;它们的具体目录、模板内容和 anchor 值在当前证据中不可见,不能进一步假设。

API Reference

runPlugin(profile: string, args: readonly string[], packageManager?: ProfileContext['packageManager']): Promise<number>

公开导出的 CLI 插件执行入口。它返回外部插件管理器的 exitCode,或在 Desktop profile 前置检查失败时返回 1。

  • profile:profile 名称;desktop 触发预初始化检查和加锁的 runProfilePnpm 路径。
  • args:版本豁免命令或 pnpm 相对参数。
  • packageManager:可选的安装器上下文,展开到操作选项。
  • 返回值:0 或外部管理器返回的退出码表示成功/失败;Desktop profile 不存在时为 1。

versionCommand(profile: string, args: readonly string[]): Promise<number | undefined>

内部命令分流器。识别 DSH 自有版本命令时返回 0 或 1;对于其他参数返回 undefined,让调用方继续普通插件流程。源码没有将其导出,因此它是实现细节而非稳定 CLI API。

Failure Modes、边界与并发

  • Desktop 未初始化:requireDesktopProfile() 检查 package.json;不存在时抛错,runPlugin() 捕获并返回 1。
  • 参数非法:未知 option、缺少 package/runtime version、version-exemptions 带参数都会抛出错误;错误字符串写入 stderr。
  • pnpm 不存在:当结果码为 127 时,CLI 明确提示安装 pnpm 并确保它位于 PATH。
  • 不兼容插件版本:外部结果中的 incompatible 项会转换成可复制的 allow-version ... --accept-risk 命令,但 CLI 不会自动批准。
  • 非零执行结果:写出插件管理器提供的 logPath,便于定位诊断日志。
  • Git hosted plugin 构建限制:参数包含 git URL 形态时,失败后提示将精确的包名加入 profile 的 pnpm-workspace.yaml allowBuilds,再重试。
  • 并发写入:版本豁免和 Desktop pnpm 操作都锁定 profile 的 package.json,等待时间为 120 秒,避免并发修改同一 profile 元数据。
  • 输出流:外部管理器的 stdout/stderr 由 onOutput 直接写回对应 process stream;大输出受 outputBytes: 16384 约束。外部管理器如何截断或记录日志,当前仓库没有实现证据。

Performance、运维与扩展点

性能相关的明确参数只有两个 120 秒超时(锁等待与 package lookup)以及 16384 字节输出限制。CLI 不实现缓存、重试或并行安装;这些能力若存在,应在 dsh-plugin-manager 中查找。运维时应优先检查 stderr 中的兼容性 warning、pnpm was not found 提示和失败结果的 logPath。

扩展插件时,优先通过 profile bundle 增加 package/module 映射,并让 runPluginCommand 处理安装;不要在 runPlugin() 中为每一个工具或模型新增硬编码分支。若确实需要新的 DSH-owned 参数,应扩展 versionCommand() 的严格解析规则,同时维持未知参数交给 pnpm 的约定。版本豁免必须保持 package version 与 exact DSH version 成对记录,避免把兼容风险扩大为 profile 全局开关。

Tests 与证据边界

在本次限定读取范围内未发现与 runPlugin() 直接对应的测试文件,因此具体测试覆盖率和断言集合无法从已读取源码确认。已确认的行为只来自 plugin.ts 的实现和自动生成的 composition.md;外部插件包的 API、模型适配器协议、工具注册机制和 profile 模板内容属于当前仓库证据之外的实现细节。

Sources

(2 files)
apps/cli/src