dsh-suite/packages/create-dsh-plugin/templates/events

43Star8Fork27Issue0Watching

create-dsh-plugin 脚手架内的事件/生命周期模板示例:零运行时依赖,演示监听 session/event、tools/change、tools/pre-execute 与 ctx.effect 资源清理。

语言
HTML
License
MIT
分支
main
agent-frameworkawesome-listcordisdeepseekdeepseek-harnessdeveloper-toolsdshdsh-plugin

安装

$ dsh plugin --profile web add github:whyihaveyou/dsh-suite/packages/create-dsh-plugin/templates/events

在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程

一句话定位

create-dsh-plugin 脚手架内嵌的事件/生命周期示例模板:零运行时依赖、纯类型导入,演示如何订阅 session/eventtools/changetools/pre-execute 三个事件,并用 ctx.effect() 包装定时器证明资源清理可逆。可以作为最小可运行的"事件插件"参考,也可以直接 dsh plugin add 装进 profile 跑起来观察日志。

核心能力

  • 订阅会话事件总线:当 session 日志新增(turn/step 边界、用户/助手消息、工具结果等)时按 type + 计数打印前 5 条和每 25 条一次的节流日志。
  • 订阅工具注册表变更:当任何工具被注册或注销时即时打印计数(含同组合内其它兄弟插件的注册)。
  • 接入工具执行水线:在每次工具调用前打印工具名,并通过 next() 显式放行;不调 next() 会短路阻断工具调用。
  • ctx.effect() 包裹一个 30 秒心跳定时器,返回的 disposer 在插件卸载时打印 DISPOSED 并清掉定时器,演示宿主外资源如何正确回收。
  • 通过 cordis.patch.yml + dsh.bundle.patch 声明把自己注入宿主 loader,模板目录本身就是合法 bundle,可以直接 dsh plugin add 装入 profile。

技术实现

  • 语言: TypeScript(ESM,"type": "module" + tsc 输出纯 ESM 的 dist/index.jsverbatimModuleSyntax: true 保证 import type 在编译期被擦除)
  • 关键依赖: @deepseek-ai/cordis(peerDependency,运行时由宿主注入 ctx)、@deepseek-ai/dsh-tools(devDependency,仅取类型 ToolExecution / PreToolDecision)、@deepseek-ai/dsh-session(devDependency,仅取类型 Session / SessionEvent);模板自身 dependencies 为空
  • 架构模式: cordis bundle 事件/生命周期插件 —— host 暴露 ctx,插件 apply(ctx)ctx.on() 订阅三类事件 + ctx.effect() 包定时器;包级别声明 dsh.bundle.patch: ./cordis.patch.yml 让宿主 loader 在加载时插入 id: {{PLUGIN_ID}}, name: {{PKG_NAME}}
  • 入口文件: templates/events/src/index.ts(被 tsc 编译为 templates/events/dist/index.js,由 package.json 的 main / exports 指向)

适用场景

想搞清楚 DSH 插件如何"听"事件而不是"添"工具的人——比如做遥测埋点、审计日志、工具调用限流、或者把外部 webhook 桥接到会话流。模板代码本身已经是最小可跑的事件订阅样例,直接拷贝过来换监听器逻辑就能变成自己的事件插件;不想从零写时也可以用 create-dsh-plugin 脚手架 + -t events 把它当作脚手架起点。

前置依赖与兼容性

依赖最低版本说明
Node.js^22.19.0 || >=24.0.0来自 create-dsh-plugin 根 package.json 的 engines;老 Node 只会 EBADENGINE 警告但可能踩到运行时坑(脚手架 PITFALLS 第 1 条)
DSH>=0.1.0-rc.6@deepseek-ai/dsh-tools / @deepseek-ai/dsh-session 的 devDependencies next-tag 版本隐式锁定;离线生成时默认回退 0.1.0-rc.6
@deepseek-ai/cordis^{{CORDIS_VERSION}}(peerDependency,默认 4.0.1)运行时由宿主注入,模板代码只 import type 不在运行时 require
平台跨平台无原生模块,纯 ESM
原生模块模板 dependencies 为空

安装方式

dsh plugin --profile web add github:whyihaveyou/dsh-suite/packages/create-dsh-plugin/templates/events

配置项

本插件无需额外配置。模板不读 process.env / options / Schema,所有可调点都在源码里(监听哪些事件、定时器间隔 30_000 ms、节流阈值 5/25)。要修改行为直接改 src/index.ts 后重跑 pnpm run build

常见问题

Q: 模板里 import type 写法和普通 import 有什么区别?

A: import type { Context } from '@deepseek-ai/cordis' 告诉 tsc"只取类型,编译时擦除",最终 dist/index.js 里不会出现 require('@deepseek-ai/cordis')。这样模板可以把 cordis / dh-tools / dsh-session 全留作 devDependency 而不用塞进 runtime dependencies,宿主加载时直接把 ctx 喂给 apply(ctx)

Q: 我在 tools/pre-execute 里忘了调 next(),会有什么后果?

A: 工具调用会被短路、永远得不到执行。模板源码注释明确写了这是一个常见坑(templates/events/src/index.ts:38),next() 是水线下行的通行证。要"否决"调用应返回一个 PreToolDecision,而不是直接 return。

Q: 模板里的 30 秒心跳是必须保留的吗?

A: 不是。它存在的唯一目的是配合 ctx.effect() 演示"宿主不管理的资源如何在卸载时被清理"——卸载时日志会打印 DISPOSED — listeners removed, timer cleared。实际插件里如果不需要心跳,直接删掉那个 ctx.effect() 块即可。

Q: 想给这个模板加新的环境变量配置项,流程是什么?

A: 不推荐在模板里加。事件模板的设计意图是"零运行时依赖、最小可读";真要做可配置,应改用 tool 模板(带 defineTool + Schema),或复制 events 模板后自己加 ctx.effect(() => applyConfig(process.env.MY_FLAG))

Q: cordis.patch.ymlname: {{PKG_NAME}} 没被替换怎么办?

A: 说明你绕过了脚手架、直接复制了模板目录。{{PKG_NAME}} / {{PLUGIN_ID}} / {{CORDIS_VERSION}} 都是 token,必须经过 create-dsh-plugin 的 render 流程替换(packages/create-dsh-plugin/src/generate.js:64-65);smoke 测试会断言"不得残留 {{"(packages/create-dsh-plugin/test/smoke.test.mjs:47-50)。

上手难度

入门 — 文件只有一个 src/index.ts(62 行)、三类订阅 + 一个 effect 示例,没有运行时依赖,复制粘贴再改名就是自己的事件插件。

已知问题与限制

  • 必须先 tsc 才有 dist/:模板根目录里只有 src/,package.json 的 main 指向 dist/index.js;直接 dsh plugin add 后宿主会找不到入口,需要先在模板目录跑 pnpm install && pnpm run build
  • {{token}} 必须经脚手架替换:裸模板里的 {{PKG_NAME}} / {{PLUGIN_ID}} / {{CORDIS_VERSION}} 是占位符,不会被宿主解析;不走脚手架等于装了个"洞"。
  • @deepseek-ai/dsh-toolslatest dist-tag 是过期的 0.0.1-rc.1:脚手架强制把生成项目的 dsh-tools / dsh-session 锁到 next-tag(默认 0.1.0-rc.6),不要手动 npm i @deepseek-ai/dsh-tools 覆盖(脚手架 PITFALLS 第 2 条)。
  • 多 dsh 包必须锁同一版本线:所有 @deepseek-ai/dsh-* 包需统一 0.1.0-rc.x,否则 pnpm 会装两份模块导致类型不一致(脚手架 PITFALLS 第 3 条)。
  • dsh plugin add <dir> 的相对路径锚定调用目录:模板被作为 bundle 装入时路径解析走包名而不是相对路径,但本地调试若用 ./<dir>,必须从父目录而非模板内部运行(脚手架 PITFALLS 第 6 条)。
  • cordis.patch.ymlname 必须用包名:用相对路径会让宿主 loader 找不到入口,模板顶部注释专门把这个坑写出来防呆(templates/events/cordis.patch.yml:4-7)。