乐于分享
好东西不私藏

DSH 插件开发实战:从零写一个「定时提醒」插件

DSH 插件开发实战:从零写一个「定时提醒」插件

前言

DeepSeek Harness(DSH)最近在中文 AI 圈火得有点快。它的设计思路里有一条特别值得关注:把「一切能力」都做成插件。这意味着只要你会写 JavaScript,就能给一个 AI Agent 加新工具、新面板、新接口——不需要 fork 整个项目。
这篇文章就是顺着这条思路走一遍:从一个最朴素的「打招呼」插件开始,做到一个真正能用的「定时提醒」插件,并最终让它在 DSH 里跑起来。

DSH 插件是什么

用一句话讲:DSH 插件就是一个返回 Cordis Plugin 对象的 JavaScript 函数,由 Cordis 组合系统装到 DSH 进程里,可以订阅事件、调用服务、注册工具、注册 UI 面板。
它有几个关键属性:
  • 同一个插件可以发布多个版本,运行时可以热切换;
  • 插件既能跑在 Node 端(Host),也能跑在浏览器端(Client);
  • 插件的「权限」是显式授予的——它不会默认接管你的系统。

准备:环境与工具

你需要两样东西:
  • 一个能跑 Node.js 20+ 的环境
  • DSH 本体(npx @deepseek-ai/dsh web)
不需要 fork 仓库,不需要懂 Rust,写普通 JS 就行。我用的是 pnpm,但 npm / yarn 也都能用。

第一个插件:一个简单的「打招呼」

我们先写一个最朴素的 Host 插件,让它在 DSH 启动后向控制台打一声招呼。完整代码长这样:
return {
  apply(ctx) {
    ctx.effect(() => {
      console.log('[hello-dsh] plugin loaded');
      return () => console.log('[hello-dsh] plugin unloaded');
    });
  },
};
把这串代码塞到一个 hello-dsh.js 文件里,然后用 cordis_define 工具把它注册成 Package,再用 cordis_run 启动。打开 DSH 的终端,你应该能看到 [hello-dsh] plugin loaded。
如果没看到,别慌——先确认你的 DSH 是 dev 模式还是打包模式;打包模式下 console.log 会被吃掉,改成 ctx.logger.info 就行。

第二个插件:定时提醒(实战)

打招呼只是热身。下面这个插件才是正文。它会做三件事:
  • 暴露一个 schedule_add 工具给 Agent(添加定时任务);
  • 暴露一个 schedule_list 工具给 Agent(查看所有任务);
  • 在任务到期时,触发 DSH 的事件总线,让前端弹一条提醒。
核心代码:
return {
  inject: ['harness'],
  apply(ctx) {
    const tasks = [];
    ctx.harness.handle('schedule_add', (req) => {
      const id = crypto.randomUUID();
      tasks.push({ id, ...req });
      setTimeout(() => {
        ctx.scope.emit('schedule:fire', { id, ...req });
      }, req.delayMs ?? 0);
      return { ok: true, id };
    });
    ctx.harness.handle('schedule_list', () => tasks);
  },
};
把这段代码通过 cordis_define + cordis_run 加载之后,Agent 就能调 schedule_add 了。比如告诉它「5 分钟后提醒我喝水」,它会自己拼出 { delayMs: 5*60*1000, message: '喝水' } 这种参数传进来。
下一步是让前端能「看到」任务触发。我们再加一个 Client 插件:
return {
  apply(ctx) {
    ctx.host.on('schedule:fire', (payload) => {
      new Notification(payload.message ?? '提醒', {
        body: payload.message,
      }).show();
    });
  },
};

发布与加载

写完插件后你有几条路:
  • 本会话试用:cordis_define + cordis_run,改完用 cordis_run update 切换版本;
  • 团队共享:把 Package 提交到一个 Git 仓库,团队成员用 cordis_run + URL 引用;
  • 持久化:把插件放到 ${DSH_HOME}/.agent-presets/<id>/ 下,写成 preset,下次启动 DSH 自动加载。
我自己的惯例是先用本会话跑通,确认无坑后写成 preset——这样下次 DSH 升级不会丢。

踩坑经验

写 DSH 插件有几个最容易翻车的地方,我列一下:
  • ctx.serviceName 不要访问未声明的 Service。要么 inject: [...],要么用 ctx.get('serviceName') + 检查 undefined;
  • 副作用必须用 ctx.effect() / ctx.on()。直接 setInterval 会导致 stop / update 不掉;
  • 不要在 Client 代码里用 TypeScript / JSX / import。DSH 不做编译;
  • 数据不要用 JSON.stringify 序列化 Services / Events。它们是活对象,会丢失上下文。

写在最后

DSH 插件开发的核心体验是:把「我想要一个 AI 工具」这件事,缩短到一杯咖啡的时间。你写 30 行 JS,调一个 cordis_run,一个新的工具就出现在 Agent 的工具箱里了。
如果你也有想法但不知道怎么落地,先从你最近手动重复过的那个动作开始——它大概率就能变成一个插件。