核心摘要

DeepSeek Harness 插件是通过 apply(ctx)、依赖声明、服务、事件和可逆副作用扩展 Cordis 的模块。在本文核验的 dsh-v0.2.1-alpha.1 源码中,可部署扩展还应属于一个 Bundle,而可运行安装则属于由有序 Patch Layer 组成的 Profile。因此,正确插件必须具备生命周期测试、明确兼容目标、可检查的有效插件树和回滚路径。

目录

最小可用插件

DeepSeek Harness 插件是由 Cordis 挂载的 TypeScript 模块。官方入门教程使用导出的 name 与 apply(ctx) 函数。这个形态只能证明 Loader 能导入并挂载模块,不代表已经提供工具、授权、持久化、分发元数据或清理策略。

应把本地开发与持久安装拆成四个阶段:

阶段 制品 能证明什么
临时开发 TypeScript 模块与 --patch Overlay 模块可在一次性检出中挂载
分发 声明 dsh.bundle.patch 的 npm Package Package 提供具名配置层
安装 包含 Bundle 列表与 Profile Patch 的具名 Profile 目标运行时可解析并组合 Package
运行验证 有效插件树、激活状态与契约测试 目标能力已激活且行为符合预期

官方教程通过绝对路径的 cordis.yml Overlay 加载本地插件:

yaml
- insert:
    - id: workspace-notice
      name: "/absolute/path/to/deepseek-harness/scratch-plugin/src/workspace-notice.ts"

使用 Overlay 启动 Web Profile,并检查组合后的有效插件树:

sh
pnpm dsh --profile web --patch ./scratch-plugin/cordis.yml
pnpm dsh --profile web --patch ./scratch-plugin/cordis.yml --dump-config

先只在一次性工作区使用这套流程。Patch 能改变激活的插件树,而且模块直接在 Host Process 中执行;文件 Sandbox Mode 不会把不可信插件代码自动变成受隔离依赖。

服务、依赖与生命周期

Cordis 服务在 Context 上提供具名能力。例如,需要 ctx.tools 的插件应在 inject 中声明 tools,Cordis 会让插件保持 Pending,直到 Provider 可用。如果所需服务随后消失,Cordis 会卸载依赖插件,只有服务恢复后才重新挂载。因此,依赖注入是一份实时生命周期契约,而不只是启动时检查。

该模式有三个关键边界:

关注点 正确契约 不安全的捷径
依赖 为消费的服务声明 inject 访问可选服务并假定它必定存在
生命周期 通过 ctx.on() 或 ctx.effect() 注册 保留未受管理的定时器、Socket 或监听器
能力 依赖 ctx.tools 等接口 导入并修改某个具体内部实现

Cordis 注册是可逆副作用。受支持的事件监听器和注册项会在插件卸载时移除;定时器、网络 Client、Watcher 等非托管资源必须在 ctx.effect() 内创建,并由 Disposer 释放。如果清理存在顺序依赖,应把相关步骤放在同一个 Effect 中,因为多个异步 Disposer 可能并发执行。

不要把定时器当作持久化调度或恢复机制。进程重启后它会消失;需要定时执行的任务必须拥有持久化 Job 契约和明确恢复模型。

服务、事件和副作用如何选择

DSH 架构区分直接能力和生命周期接缝。插件需要直接调用能力时使用服务;需要观察、转换或治理运行时中的工作流动时使用事件。

flowchart TD A["需要新行为"] --> B{"是否直接调用能力?"} B -->|是| C["消费文档化服务"] B -->|否| D{"是否属于生命周期策略或观测?"} D -->|是| E["使用文档化事件接缝"] D -->|否| F["定义边界明确的服务接口"] C --> G["注册可逆副作用"] E --> G F --> G

当前 Extension Cookbook 与 Tool Subsystem 给出的执行顺序更严格:

  • tools/pre-execute:派发前执行可扩展的允许、拒绝或审批策略。
  • ctx.tools.guard():执行后续 Listener 不能撤销的单调拒绝不变量。
  • tools/execute:包裹派发生命周期,实现取消、超时、重试或计量。
  • tools/post-execute:在最终物化前检查、阻止或替换结果。
  • tools/result:只观察冻结后的权威结果,不能再修改。
  • session/event:将日志事件投影到界面、遥测或外部协议。

这些接缝不能互换。可重排的 Pre-execute Listener 不是最终安全边界;Monotonic Guard 不能强制允许其他 Guard 已拒绝的调用;替换展示文本不会自动隐藏程序化 Value;tools/result Observer 也不可能阻止已经发生的副作用。

把策略放在正确的工具接缝

应根据需要执行的不变量选择接缝。追加协作式审批策略的插件属于 tools/pre-execute,不拥有最终决定时必须通过 next() 委托。不能受 Listener 重排影响的租户边界属于 Monotonic Guard;超时包装属于 tools/execute;最终指标则属于 tools/result。

这些框架接缝都不能自行证明写入安全。生产策略必须把已认证用户与租户绑定到精确资源、参数、Schema Digest、时间窗口、策略版本和审批制品,下游服务也必须独立执行授权。更广泛的设计可参阅 人机协同 和 审批门禁。

Profile、Bundle、Patch 与安装

当前 DSH 把开发者分发的 Package 与用户实际运行的应用组合明确分开:

概念 当前职责 运维含义
Bundle 声明一个或多个有序 Patch 文件的 npm Package 按可执行供应链输入进行版本锁定与审查
Profile Harness Home 下的一份可运行组合 把用户和环境选择留在 Bundle 之外
Profile Patch 在 Bundle Layer 之后应用的用户层 后应用的 Row 胜出,且 config 整体替换而非深度合并
Home Patch 多个 Profile 共享的机器级偏好 评审其对每个 Profile 的影响
--patch Overlay 优先级最高的单次启动层 用于受控实验,不能形成未记录的生产漂移

持久安装使用 dsh plugin --profile <name> add <spec>。Manager 会先检查 Bundle Metadata 与运行时 Peer Compatibility;依赖构建脚本需要单独批准,因为脚本以 Host User 权限执行。版本豁免只对精确 Plugin/Runtime 组合生效,升级不会继承,应把它视为一次明确接受崩溃或数据丢失风险的决定。

发布前应执行:

  1. 锁定 DSH Revision 与所有外部插件依赖。
  2. 检查源码、依赖脚本、Host Capability 与 Bundle Patch。
  3. 将审核后的 Patch 和预期有效插件树纳入版本控制。
  4. 安装到一次性 Profile,确认激活状态是 applied、restart-required 还是失败。
  5. 执行允许、拒绝、取消、卸载、重启和数据兼容测试。
  6. 使用最小能力集、作用域凭证和经过验证的移除路径逐步发布。

不要根据 Package 名称承诺兼容性。DSH 公共 API 仍处于 Pre-stable 状态,而 Session 数据承担比普通运行时接口更严格的兼容义务。

插件契约测试

应测试插件的可观察契约,而不是只测试成功路径。下表是实用的最小集:

测试 断言
挂载 声明的服务依赖在 apply() 运行前已经可用
卸载 监听器、定时器和客户端被释放
允许操作 被允许的工具进入执行并产生结果
拒绝操作 被禁止的工具不会进入执行
审批过期 过期审批不能授权已变化的参数
取消 取消不会留下未受管理的后台工作
有效插件树 导出配置与审核通过的 Bundle、Profile Layer 一致
持久化 旧 Fixture 要么忠实迁移,要么失败关闭
升级 相同测试集可通过目标锁定的 DSH 版本

对于工具插件,应使用伪造或沙箱化的下游系统做集成测试。不能把共享仓库或真实客户数据当作写入策略的测试环境。Agent Harness 评测指南 中的故障注入和发布门禁可以补充框架层测试。

下面的 Go 门禁在插件发布前校验一次已捕获的 Rollout Manifest。它不会加载插件,而是避免运维人员把“已经安装”误判成“兼容且已激活”。

go
package main

import (
	"errors"
	"fmt"
)

type Manifest struct {
	RuntimeVersion   string
	PluginVersion    string
	PeerCompatible   bool
	BundleDeclared   bool
	ApplicationState string
	TestsPassed      bool
	RollbackTested   bool
}

func validate(manifest Manifest) error {
	if manifest.RuntimeVersion == "" || manifest.PluginVersion == "" {
		return errors.New("runtime and plugin versions must be pinned")
	}
	if !manifest.PeerCompatible {
		return errors.New("plugin peer range rejects this runtime")
	}
	if !manifest.BundleDeclared {
		return errors.New("package has no dsh bundle patch")
	}
	if manifest.ApplicationState != "applied" {
		return fmt.Errorf("plugin is not active: %s", manifest.ApplicationState)
	}
	if !manifest.TestsPassed || !manifest.RollbackTested {
		return errors.New("contract tests and rollback rehearsal are required")
	}
	return nil
}

func main() {
	ready := Manifest{
		RuntimeVersion:   "dsh-v0.2.1-alpha.1",
		PluginVersion:    "plugin-7",
		PeerCompatible:   true,
		BundleDeclared:   true,
		ApplicationState: "applied",
		TestsPassed:      true,
		RollbackTested:   true,
	}
	restartRequired := ready
	restartRequired.ApplicationState = "restart-required"

	fmt.Printf("ready=%t\n", validate(ready) == nil)
	fmt.Printf("restart_required_rejected=%t\n", validate(restartRequired) != nil)
}

常见问题 (FAQ)

每个 DeepSeek Harness 功能都应该拆成插件吗?

不应该。只有具备独立生命周期、配置面、依赖边界或替换需求的能力才适合成为插件。把每个小辅助函数都拆成插件会掩盖控制流并增加兼容测试成本。

可以直接使用 DSH 仓库的私有 API 吗?

从源码检出中导入内部实现技术上可行,但不应把它视为稳定扩展契约。优先使用生成的 Subsystem Reference 和文档化 Service/Event 接缝;若确实无法避免,必须锁定源码版本并增加升级测试。

插件状态应如何保存?

按状态语义选择。仅运行时缓存可以保存在内存;模型可见或与回放相关的事实需要持久化会话事件;外部副作用则需要应用拥有的操作记录与对账。

插件可以添加 MCP Server 吗?

DSH 的扩展映射将 MCP 描述为每个 Server 一个插件:发现工具后注册到 ctx.tools。发现不等于授权,仍要验证 Server、工具元数据、租户 Scope、参数、结果上限和下游副作用。

插件需要锁定哪些版本?

至少锁定插件包或提交、DSH Revision、Profile Template、Bundle 顺序、配置 Patch、Service 假设、模型路由、策略版本、测试集、Session Fixture,以及所有会改变行为的外部协议或 Tool Schema。

相关资源