Skip to content

Latest commit

 

History

History
124 lines (94 loc) · 19.2 KB

official-plugin-dev-skill.md

File metadata and controls

124 lines (94 loc) · 19.2 KB

官方自带插件开发技能(references/official-plugin-dev-skill.md)

这是什么:DeepSeek Harness 自带的 preset(packages/preset/agent-preset/)里有一整套官方插件开发技能,是 0.2 世代最重要的新增资料——它描述的正是"在当前 Harness 里、由 agent 自己写并安装插件"的官方流程。 本文件性质:要点摘录 + 原文定位(不是逐字全文;逐字全文随官方文档副本一起在 references/official-docs/ 之外的仓库路径,需 checkout 才能读)。凡写"官方明确"的地方都能在上游文件里逐字查到。 基线:d743267388641bc76f17c45ce8b4c231aed1d32c(dsh-v0.2.1-alpha.2,2026-10-09)。升级宿主后若与上游冲突,以上游为准。 上游路径(在 checkout 里,如 D:\deepseek-harness):packages/preset/agent-preset/skills/

1. 四个技能与各自职责

技能 职责
cordis-plugin-development/SKILL.md 主流程:设计/评审/启用/安装/配置/调试插件、bundle、页面、面板、工具、MCP 连接
cordis-plugin-development/references/{host-plugin,ui-plugin,mcp-bundle,practices,user-actions,verification}.md 六份分面参考(见 §3)
cordis-plugin-development/templates/{decoration,mcp}/** 可直接复制的 UI 插件模板与 MCP bundle 模板(含 locale 与 icon)
cordis-composition-reference/SKILL.md Loader patch 方言 + 可安装插件包清单
editing-cordis-compositions/SKILL.md agent preset 组合的编辑流程
agent-experience/SKILL.md 面向"模型体验"的写作/设计约束

2. 主流程(cordis-plugin-development/SKILL.md 的硬性规定)

安装方式(官方唯一推荐):在工作区用普通文件写 bundle,然后用 plugin_manager 工具的 install_bundle,target 传绝对包目录装进当前 profile。改动对该 profile 的所有会话生效并跨重启保留。

官方明确禁止(原文禁止项,逐条):

  • 不写 profile 的 package.json;
  • 不写 profile 的 cordis.patch.yml;
  • 不在 profile 目录里跑 pnpm;
  • 不在 $DSH_HOME 下创建包。

理由:install_bundle 会做这些步骤,而"profile 之外的每一次手写都需要单独审批"。另外 plugin_manager 的每个动作(包括 list_plugins / list_bundles)在没有 Full access 时也需要审批,所以只在结果决定下一步时才调。

"先交付能跑的插件"五步:

  1. 先定结果与落点。未指定的可视落点=当前 Harness Web UI;独立图片文件或 HTML 不算完成。先实现一个小的第一版并先装上,再做视觉打磨。
  2. 只查这一版需要的 API:先 cordis_inspect_list,再有针对性地 cordis_inspect_query。UI 类要查 Client 的 Slots.listSubTree 与目标 slot 的注册选项/props;超出静态装饰(工具策略、agent 上下文、会话派生状态、Chat 行)要先读 references/practices.md;涉及用户动作再读 references/user-actions.md。选定 slot 与注册 API 之后才开始写代码。
  3. 复制匹配的模板文件到一个工作区目录,或直接在工作区写包/patch/必需的 Host·Client 文件。装之前完成 references/host-plugin.md 的展示清单(本地化标题/描述、原创或有许可的图标、按需导出资源与分发校验)——Host-only 与纯配置 bundle 同样适用。然后检查 JS 语法与清单,安装。不要先做预览 HTML、mock 壳、design 变体、截图脚本或栅格化工具。
  4. 读安装结果:application 与 warnings 决定改动是否生效——不是日志、进程列表,也不是页面的 boot payload。用免审批的 cordis_inspect_query 确认新行,而不是 list_plugins。application: applied 之后,实际用一下能力,或检查实时 Client 注册。
  5. 在同一插件里修掉观察到的缺陷;请求的结果能用就收尾,报告位置、验证状态与任何展示资源例外的理由。不要继续做投机变体、可选功能或 mock 预览。

知识来源的优先级(官方顺序):

  1. inspection——cordis_inspect_query 回答精确的 Service 方法、Event 模式(Service/Event)、已挂插件的 Config JSON Schema(Config.listConfigs:先按 name 过滤分页目录,再查 entry id)、本 Agent 可调用的 Tool(Tool)、实时 Client Slots 与主题 token(Slots/Theme)。
  2. 包文档——Config.listConfigs 带 name 找到该包的 entry,查一个 entry 会返回 packageDir;读 <packageDir>/README.md。bundle 包从 dsh 安装位置解析、profile 安装的 bundle 从 profile 解析,不要从 $DSH_PROFILE_DIR 猜路径。
  3. 源码——已安装包只带构建后的 lib/index.js 与 lib/types/**/*.d.ts(含 JSDoc),没有 src/;源码 checkout 才有 packages/<group>/<name>/src。先看 inspection 与 README,再从具体的安装/运行失败出发做源码级诊断。

环境变量:profile 启动的 Harness 在每次 shell 调用里都设置 DSH_PROFILE(profile 名)与 DSH_PROFILE_DIR(其目录,node_modules 只含 profile 安装的 bundle);不以 profile 启动时不设置。Bash 读 $DSH_PROFILE,Windows preset 用的 PowerShell 读 $env:DSH_PROFILE。dsh --profile "$DSH_PROFILE" --dump-config 打印组合后的 profile。

Desktop 下的读取限制(官方警告):Desktop 里该技能目录位于 app.asar 内,只有 Host 进程自己的文件读取能打开它;shell 命令(ls/cat/cp/cmp)、glob 与搜索工具(走原生 ripgrep 进程)、node、pnpm 全部失败。永远不要就地安装或语法检查模板——先把模板内容复制到工作区。

启用随包发布但默认关闭的插件:行 id 与原因写在随包 patch(源码 checkout 的 packages/bundle/*/cordis.patch.yml)里。写一个工作区 bundle,用 patch 覆盖该行为 disabled: false,并把它依赖的 Host 行一起插入;源码 checkout 的 apps/cli/config/examples/<feature>/cordis.yml 会列出这些行。随 dsh 发布的包从 dsh 安装位置解析,所以这种 bundle 不声明对它们的依赖。

3. 六份分面参考的要点

3.1 host-plugin.md — bundle 与 Host 插件

  • 最小 Host-only bundle 不需要依赖、安装脚本或构建步骤:
    { "name": "@local/my-plugin", "version": "1.0.0", "private": true, "type": "module",
      "exports": { ".": "./index.js" }, "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }
  • 展示元数据与图标清单(Plugin Manager / Settings 在不激活插件的前提下读取):locale/en.json 里写 meta.title/meta.description,并按用户语言补 locale/zh.json 等;图标须原创或有再分发许可、保留署名;导出 ./locale/*.json 与 ./icon,并把 patch、locale、图标与每个运行时文件都放进 files。本地目录安装链接的是 checkout,files 不过滤链接目录——要直接检查文件。图标支持 SVG/PNG/JPEG/WebP,≤ 256 KiB,且 realpath 之后必须仍在包内(绝对路径、URL、指向包外的 symlink 都被拒)。文件名合规不等于字节能渲染。
  • 包根插件也可以在 package.json 顶层写 icon(优先于 ./icon,路径相对清单目录)。子路径插件(如 my-plugins/search)不是包,永远不读 package.json——它导出 ./search/locale/*.json 与 ./search/icon。
  • Host 插件导出形态(不要混用):export function apply(ctx, config) {} + 可选 export const inject = ['tools'] + export const Config;或者一个 service class 作为 default export。所有资源都在 apply 里用 ctx.effect/ctx.on 注册并返回清理函数。声明了 Config 的插件会在激活时校验该行的 config——写 config 之前先查 Config.listConfigs 拿 schema,并跟进返回文档里的 $defs 引用。
  • 安装/启用/观察:install_bundle 负责包安装与 bundle 选择,不要用 shell 复现;只有用户明确批准了报告出来的 pending build 脚本后才传 approvedBuilds。list_plugins/list_bundles 给出精确标识符,set_plugin/set_bundle 开关,remove_bundle 移除。分开看"保存状态"与"激活结果":failed 要诊断,overridden 表示更高优先层胜出,restart-required 表示未生效。新装 bundle 可以经 HMR 激活;替换已安装包需要重启才能载入新的 JS 模块代。

3.2 ui-plugin.md — Web 页里的 UI 插件

  • 读 templates/decoration/ 并把文件写进你的 bundle 目录:它的 package.json 在 bundle patch 之外加 dsh.client 段(platform/immediately/inject)与 ./client 导出。index.js 导出 export function apply() {};patch 插入一行,行名即包名。
  • 简单绘图优先选已分配空间的 slot,如 conversation.composer.dock;第一版就留在这个 slot 的流内,不要围绕宿主控件规划运动轨迹。只有需要覆盖层且落点已知时才用 shell.overlay。
  • 浏览器产物注册一个懒工厂,其 id 等于包名。React 来自浏览器模块表——不需要第二份 React、CDN script 或 UMD 查找。编译型源码用部署自带的 Client 构建工具产出该格式;非基线运行时 import 在 dsh.client.external 里声明。
  • templates/decoration/client.js 通过 ctx.slots.inject + ctx.slots.register 把懒工厂注册进 conversation.composer.dock;换 slot 时按 Slots.listSubTree 给出的 props/options 来写。
  • 工厂保持无副作用:样式、定时器、监听器等资源在 apply 里用 ctx.effect/ctx.on 注册并返回清理函数;组件局部样式渲染成 React 元素,卸载即移除。容器与控件继承宿主主题;只有 artwork 可以用自己的配色。可见文案走 Client locale 服务。不要替换 app root,也不要往 document.body 追加第二个应用。不要读别的插件的 DOM、样式表或组件源码去猜位置——选一个已经分配好空间的 slot。

3.3 mcp-bundle.md — 接一个 MCP server

  • 纯配置 bundle:清单只要有唯一 name、version 与 dsh.bundle.patch,不需要 Host/Client 入口文件。patch 插入已安装的 @deepseek-ai/dsh-mcp-client,配 serverName、transport: streamable-http、url、failOnStartupError: true。
  • 换掉 endpoint、经 plugin_manager 装好,然后调 mcp__demo__ping 之类新出现的 mcp__<serverName>__<tool> 验证。
  • stdio 用 transport: stdio + command + 可选 args/env/cwd;Config.listConfigs 查已装行会返回完整 client schema。环境凭据会被清洗:用 Loader 的 !!js 引用已有凭据,不要把密钥粘进对话文本。失败时修同一个 bundle,不要造重复的。

3.4 practices.md — 插件实践总纲(最值得先读的一份)

六条原则

  1. 会话日志是唯一真源:模型看到的一切都必须能从已提交的会话事件重建;fork/resume/replay 都从日志派生。插件内存只是派生缓存。
  2. 注册是被 ctx 拥有的 effect:插件卸载、agent 释放、slot 折叠、profile patch 都会撤销对应 ctx 上注册的东西。要选对拥有者 ctx;注册在别的 ctx 上(如 agent.ctx)的注册有两个拥有者——必须把它的 disposer 也留在自己插件的 effect 里,这样任一侧 teardown 都能移除。
  3. 框架负责驱动,插件只负责计算:session projection、Conversation 组装、slot 渲染已经替你订阅、缓存、发布。自己去订阅/重扫/写 DOM 就是绕过这套增量机制。
  4. 扩展点是共享的:用最弱的够用机制。由弱到强:ctx.tools.restrict()(只能移除工具)→ ctx.tools.guard()(只能拒绝)→ waterfall 监听器(可改写、依赖注册顺序)→ system-prompt/assemble(替换整个组装结果)。机制越强,你要替其他插件保留的东西越多。
  5. 别的插件、别的 Harness 版本会读你写的数据:未知事件类型、更老的读方、更老的缓存状态都会遇到你写的东西——按 Harness 提供的信封字段与版本声明兼容性。
  6. 插件 UI 就是 Harness UI 的一部分:用户看到的是一个应用,所以插件要用宿主的主题 token、locale 与布局模式,并匹配宿主组件的外观与行为。能不能做到由"在哪渲染"决定——先选渲染面再写视图;后面在错误的面里做样式救不回来。

稳定性(逐条可执行)

  • 不拥有该决策的 waterfall 监听器(agent/pre-step、agent/request、llm/stream、tools/pre-execute、tools/execute、tools/post-execute)必须返回 next()。改写 agent/pre-step 决策时要展开它({ ...decision, messages }),否则 startsRequestSeries 之类字段会丢。
  • 必须与顺序无关地成立的拒绝用 ctx.tools.guard()(同步);需要 await(比如问用户)的决策从 tools/pre-execute 返回 ask。只对某个 agent 隐藏工具=在那个 agent 的 ctx 上 ctx.tools.restrict()(它让 schema 展示、查找、执行保持一致)。最终结果只看 tools/result;只有要变换结果才用 tools/post-execute。不要监听 system-prompt/assemble 来增删工具或文本。
  • 加提示词文本用 ctx.systemPrompt.section()。加 per-agent 上下文用 agent.inject()——它在调用时记为 agent/inbox/spliced,进入下一次获准的 step。agent/request 监听器不能改请求消息。
  • per-agent 行为注册在 agent.ctx 上(在 agent/created 监听器里拿到),这样该 agent 释放时一起移除。把它包在一个 agent.ctx.effect() 里,并且把这个 disposer 按 agent 记在自己插件的 effect 里——卸载插件本身不会释放 agent.ctx 的注册。
  • 可选服务放进 inject 或 ctx.inject([...], ...),这样在没有它们的 profile 里插件保持不激活,而不是抛错。
  • 绝不要用新的 type 追加会话事件:读方只在事件带信封 ignorable: true 时接受未知类型,而运行时 Session.append() 写不了这个标记,于是会话会拒绝重新打开。插件状态从既有事件推导,或放进 inspection 找到的 storage 服务。
  • 可调值放进插件的 Config,让用户在 cordis.patch.yml 里改——用户的 patch 层能跨升级保留。

性能

  • per-session 状态放进 ctx.sessionProjections 单元,不要订阅 session/event 再重扫 session.events。apply(state, event) 是纯同步的,对它忽略的事件返回同一个引用,所以未变状态在下游零成本;读取用 stateOf()。view() 在值未变时返回同一引用,从而抑制发布。
  • projection 状态保持纯 JSON,字段或 fold 语义变化时 bump stateVersion:投影缓存会 checkpoint 它,冷读只回放尾巴,过期 checkpoint 被丢弃。
  • 等持久事件(turn/end、assistant/message、tool/result),实时 token 从 agent/assistant-stream 渲染。不要轮询 agent/status。 whenIdle() 不代表"一次 followup 结束了"——多个输入可以共享同一个运行区间。
  • 会启动工作的定时器调用 agent.followup()(它唤醒 agent);agent.inject() 不唤醒,所以注入的上下文可能在 inbox 里等到别的输入才被采纳。定时器要在拥有它的 effect 里清除。

UI

  • 插件页面渲染成 slot 里的 React 组件。不要从 Host 提供一个 HTML 页面再 iframe 嵌进去:iframe 文档拿不到宿主主题 token、明暗切换和 ctx.locale。
  • 用 cordis_inspect_query 的 Theme 列出的主题 token(--dsw-alias-*)做样式;字面颜色只用于 artwork。token 是最低风险的匹配方式:token 改名只会让外观退化,永远不会弄坏渲染。间距/字号/行模式照抄同类宿主页面(管理类列表的参照是 Plugin Manager 页)。
  • 不要 require('@deepseek-ai/dsh-client-ui-primitives') 或把任何 Harness Client 包当模块加载;dsh.client.inject 只做激活排序、保持允许即可。它们会不打招呼地变化,纯 JS 插件没有类型检查,而抛错的组件会把你的 slot 条目整块搞白(console 里是 slot entry crashed in '<slot>')。自己写控件并对齐宿主:从 primitive 里把标记、CSS 与行为抄进插件(DSH 源码 checkout 的 src/*.tsx 与 *.module.css,或已安装包的 lib/index.js 与 lib/**/*.css),或在连着的页面里观察渲染出来的宿主控件。抄来的 class 要改到你自己插件的前缀下,只保留 --dsw-alias-* token 引用,并保留用户依赖的行为(Modal 焦点与 Escape、role="switch" + aria-checked、Tooltip 落位)。这样 token 就是唯一的共享样式依赖。
  • 只通过 slot 贡献:ctx.slots.inject(ownerKey, () => ctx.slots.register(...))——回调里的注册在拥有它的声明折叠时释放、恢复时重新安装。会话数据经 slot props 的 selector hook 读取,订阅最小切片。不要在组件外写 DOM,也不要往 document.body 追加。
  • 加 Chat 行:用 ctx.uiConversation.events.register() 注册事件定义,并在 conversation.chat.node slot 下按该定义的 kind(即渲染键)注册视图;分页、Turn/Step 落位与增量组装由 Conversation 层负责。
  • Client 需要会话派生值时,在 Host projection 上声明 wire.view——值算好了再送到 Client,Client 不自己 fold 会话事件。

3.5 user-actions.md — 用户动作与 agent 工具

  • 一个操作,两个调用方:① 实现成一次 Host 服务方法(返回结果或显式状态,失败带原因);② UI 动作经 inspection 找到的 Client 可调 Host 入口(如 ctx.remote.commands.execute() 跑的会话命令)调用它并显示失败;③ 用参数与语义一致的 agent 工具暴露同一操作(相关操作可用一个 action 参数共用工具),工具调同一个方法并把结果作为工具结果返回。
  • 授权/确认类动作仅限用户(批准工具调用、回答 agent 的提问、放宽策略)。不要在 UI 与工具两条路径里各维护一份操作逻辑。
  • 验证:双调用方的操作只用工具跑一遍,比较状态变化、返回值与失败是否与 UI 动作一致;用户专属动作要验证 agent 无法执行或授权。

3.6 verification.md

无浏览器控制时的页面/面板设计核查流程(配合 §2 第 4-5 步使用)。

4. 与本知识库的关系

  • 本文件是索引与摘录;机制细节(精确签名、事件 @mode、Config schema)永远以 inspection 与 references/official-docs/docs/** 为准。
  • 官方这份技能覆盖"在会话里让 agent 写并安装插件";本知识库另外覆盖对外发布(npm/tarball/git 通道、prepare + allowBuilds、五语 README、质量门禁)与版本迁移(migration-0.2.md)。
  • 上游路径随宿主版本变化(本文件基线 d743267);preset/agent-presets → preset/agent-preset 的改名发生在 d1e22a7e24。