大家对"本地 Agent"这词儿应该不陌生, 用过 Claude Code、Codex或者其他类似的工具。DSH (DeepSeek Harness) 是同一个赛道的新玩家,但它的野心很直白:Everything is a Plugin,一切皆插件。工具、模型、界面、权限,几乎全挂在插件体系上------意思是你不用跪求官方加功能,自己就能给它"接条胳膊"。
这篇文章聊三件事:它到底是个啥、怎么在本地调起来、怎么写出你的第一个自定义插件。
01 先搞清楚:DSH 到底是什么玩意儿
DSH 是 DeepSeek 开源的本地 AI Agent 运行框架,官方仓库 https://github.com/deepseek-ai/deepseek-harness,npm 包名 @deepseek-ai/dsh,MIT 协议开源(连许可证都这么大方)。理解它最省事儿的公式是:
Agent = 大模型 + Harness
模型负责"想",Harness 负责"干"。
说人话:大模型只负责往外蹦字儿,它没长手。Harness 就是那双手:让 AI 读写本地文件、执行系统命令、调工具、按流程把活儿干完。DSH 把这俩焊一块儿,再把整个过程可视化、可控制------你终于能从"它说它改了"变成"我看到它改了"。
和网页版 AI 聊天工具比,差别不是功能多寡,是"问它"和"让它干"的本质区别:
| 对比项 | 网页版 AI | DSH |
|---|---|---|
| 文件访问 | 只能上传附件(传一次烦一次) | 直接读写工作区目录,项目文件说改就改 |
| 命令执行 | 想都别想 | Shell 命令、跑测试、装依赖,全给你安排 |
| 结果落点 | 烂在聊天记录里 | 直接写进本地项目,落地即用 |
| 过程透明度 | 中间过程是黑盒,出问题全靠猜 | 工具调用树全程可见、可追溯 |
| 安全机制 | 本地审批?不存在的 | 沙箱 + 敏感操作审批 |
| 扩展能力 | 功能焊死 | 插件随便长 |
DSH 底层基于 Cordis 插件框架(cordis 4.0.2 那版)。所谓"一切皆插件",就是说读文件、跑命令、搜网页、换模型、改界面,在 DSH 里全是可插拔模块。
官方目前给它贴了 Developer Preview(开发者预览版)的标签,插件 API 还可能给你来个不兼容更新------但反过来说:现在正是偷师它插件机制的好时候。
02 十分钟跑起来:一条命令就行
DSH 基于 TypeScript,跑在 Node.js 上。环境要求:Node.js 20.19+ 或 22+ (官方 quick-start 原话是 ^20.19 || >=22,所以 20.19 其实就能跑,别被"Node 22 起"吓得去重装一遍环境). 一条命令启动:
bash
npx @deepseek-ai/dsh web
首次运行会自动下载依赖,看到下面这行,说明你赢在了起跑线:
text
DeepSeek Harness
http://127.0.0.1:3080/
浏览器会自动弹开 http://127.0.0.1:3080/ 。打算长期用,就全局装:
bash
npm install -g @deepseek-ai/dsh
dsh web
启动后还差两步配置,DSH 才肯干活 ------ 它比你家猫还挑。
第一步,喂模型。 DSH 自己不产模型,得配大模型 API 密钥。界面右上角"设置 → 模型"里,把 DeepSeek 开放平台申请的 API 密钥(sk- 开头那一串)粘进去;它也兼容所有 OpenAI 协议接口,智谱、通义千问、本地 Ollama 等 20+ 家厂商都能接,路子野得很。
第二步,选工作区。 工作区就是你让 AI 动土的项目目录,也是所有文件操作的边界。没选之前,输入框是锁死的------它不是在摆谱,是在保护你。点"选择工作区",把启动 DSH 时所在的项目目录加进来选中即可。
然后就能开始第一次对话了。
03 三个核心概念
下面仨是 DSH 的设计核心,搞懂它们才算"会用"而不是"会点"。
工作区:AI 的"自留地"
工作区划定了 AI 能碰哪些文件。添加工作区只是记个目录路径,不会复制或移动你文件,删工作区也不会删本地文件(松口气)。最佳实践:选项目根目录,别拿 C 盘、用户目录这种"全宇宙范围"当工作区------范围越大越慢,翻车概率也越高。
会话:独立的对话上下文
每个会话是独立对话,上下文互不串门;会话归属对应工作区,方便按项目翻旧账。它还支持分叉、归档、搜索,适合多方案对比和事后复盘------毕竟谁还没改崩过几次呢。
工具调用树:可观测的执行过程
这是 DSH 跟"黑盒 AI"最大的区别:AI 每走一步,界面上就留一行,串起来就是完整执行链路。常见工具包括 Think(内部碎碎念)、Bash/Pwsh(跑命令)、Glob(按模式找文件)、Read(读文件)、Write/Edit(写文件)等,每一行都能点开看参数和结果------像极了你妈查你浏览器历史。
DSH 的安全机制也建立在这套可观测性上。权限分三档(官方沙箱术语是 read-only / workspace-write / danger-full-access):
- read-only(只读):只能看不能改,最安全。
- workspace-write:可读写工作区并执行命令,日常用这个最香。
- danger-full-access:整机可操作,手贱党慎用。
超出权限的操作会弹审批窗口,而且每次审批只对当前这一步有效------别想着"批一次管一辈子",DSH 不惯这毛病。
顺手提一嘴:社区有人扒源码发现,官方默认预设表里其实没真 ship 一个叫 read-only 的预设。但"只读 / 工作区写 / 全开"这三档的概念你得门儿清,装懂容易,真出事就尴尬了。
04 本地调试:把每一步都摊开给你看
本地调试是 DSH 比远程方案舒服太多的地方。你不用猜 AI 干了啥,所有执行细节都摊在桌面上,像美剧CSI现场调查。
看工具调用树与性能统计
每次任务结束,工具调用树就是你的"审计日志":每一轮思考、每一步工具调用、命令参数、返回结果,全都能点开。底部还有一行统计:轮数、步数、模型思考耗时、工具执行耗时、缓存命中率、Token 消耗------查性能和成本问题基本靠它,比你记手账靠谱。
用 --dump-config 检查实际加载的插件树
DSH 的运行配置是多个 Bundle Patch 叠出来的一棵插件树。要是你装了插件却没生效,先别急着砸键盘,用这条命令确认插件真进配置里了:
bash
dsh --profile web --dump-config
在输出里搜插件名,能搜到就说明 Bundle → Patch → Profile 组合成功了。
插件开发的本地调试:--patch
开发插件时,反复装到 Profile 太折腾。DSH 支持用 --patch 临时加载本地插件文件,改完代码刷一下就生效:
bash
pnpm dsh web --patch ./my-plugin/cordis.yml
注意:本地 Patch 加载插件时,插件路径必须写绝对路径------因为插件解析是基于 Profile 环境的。写相对路径?它不会报错,但会静默失败,留你一个人对着黑屏怀疑人生。
推荐工作流:写代码 → --patch 调试 → 构建 → plugin add 正式安装 → dump-config 验证 → 正式运行。别跳步,跳步必踩坑。
05 自定义插件:写出你的第一个 Tool
这是 DSH 最值得玩的部分。
先记住一句话:在 DSH 里,插件就是一个导出了 apply() 方法的 TypeScript 模块。
ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export function apply(ctx: Context) {
// 在这里注册插件能力
}
DSH 加载插件时会调 apply(ctx),把 Context 对象塞给你。真正要啃的只有四个概念:
| 概念 | 作用 |
|---|---|
| name | 插件名称 |
| inject | 声明插件依赖的服务(如 'tools'),Cordis 等依赖就绪后才执行插件 |
| apply | 插件生命周期入口,DSH 加载时调它 |
| ctx | 插件体系核心,通过它注册工具、监听事件、访问服务 |
最常用的场景是给 Agent 加工具。
下面这个 text_stats 工具,让 AI 能数一段文本的字符数、行数、单词数------没错,就是个"数数"工具,但它是你长出的第一根手指:
ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'text-stats-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(
defineTool({
name: 'text_stats',
description: '统计一段文本的字符数、行数和单词数',
parameters: {
text: { type: 'string', required: true, description: '要分析的文本' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args) {
const text = args.text
const characters = [...text].length
const lines = text.length === 0 ? 0 : text.split(/\r?\n/).length
const words = text.trim() ? text.trim().split(/\s+/).length : 0
return JSON.stringify({ characters, lines, words })
},
}),
)
}
defineTool 的结构很像 OpenAI 的 Function Calling:
- parameters 告诉模型"这工具要吃啥参数",
- execute 才是真干活的地点;
模型决定调用时,DSH 按你声明的 schema 校验参数再传进去。注册的 Tool 会自动并进 system prompt,插件卸载也自动注销------来去自如,不拖泥带水。
除了加工具,插件还能监听和拦截生命周期事件。比如给所有工具调用加道权限门,禁止执行 rm -rf /------毕竟 AI 一时上头,你可不想到时候对着空硬盘哭:
ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'permission-gate'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.on('tools/pre-execute', async (exec, next) => {
const cmd = exec.arguments?.command ?? ''
if (exec.name === 'bash' && cmd.includes('rm -rf /')) {
return { kind: 'deny', reason: '禁止 rm -rf /,别问,问就是不让' }
}
return next()
})
}
⚠️ 这里有个新手最爱写错的点:
tools/pre-execute的决策只有三种------allow(调next()放行)、deny(reason)(拒绝)、ask(reason?)(转一次性人工审批)。拒绝必须返回{ kind: 'deny', reason: '...' }。那些写成{ allowed: false, reason: '...' }的,框架根本不认,等于这道门形同虚设------你以为锁了门,其实只是把"请勿打扰"的牌子挂门把手上。
官方给的工具扩展点包括 tools/pre-execute、tools/execute、tools/post-execute、tools/result------权限控制、日志、重试、超时、审计,都能在这些环节插进去。
用脚手架快速创建插件项目
不想从零手写配置的话,社区有脚手架,一条命令生成插件项目(这玩意儿还真存在,不是我编的):
bash
npx create-dsh-plugin@latest dsh-text-stats -t tool
生成后进目录装依赖,改 src/index.ts 里的代码,构建:
bash
cd dsh-text-stats
pnpm install
pnpm run build
一个能发布的 DSH 插件包,三个关键文件:src/index.ts(插件逻辑)、package.json、cordis.patch.yml(把插件插进 DSH 插件树的声明)。
值得敲黑板的一点:package.json 里必须声明 dsh.bundle,否则即使装成功,插件也可能只是个普通依赖,不会自动变成 DSH 的组合层:
json
{
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
把插件安装到 Profile
Profile 可以理解成一套 DSH 运行环境,官方内置 web、headless 等。调试通过后,正式安装到 Web Profile:
bash
dsh plugin --profile web add ./dsh-text-stats
安装后建议先 dsh --profile web --dump-config 确认插件进了插件树,再启动。然后在对话里直接对 AI 说"用 text_stats 工具统计这段文本",验验工具是否生效。
插件开发完,可以发到 npm(包名建议带 dsh 前缀),并在 GitHub 仓库加个 dsh-plugin 的 Topic,方便社区捞到你。
06 新手最容易踩的 5 个坑
1. 忘记声明 inject。 用了 ctx.tools 却没写 export const inject = ['tools'],插件会一直卡在 PENDING 状态装死。Cordis 按依赖决定加载顺序,不按你配置文件里的顺序------它说了算,不是你。
2. package.json 缺 dsh.bundle。 插件"装上了"却永远不生效,大概率是没声明 bundle patch,包只是个普通 npm 依赖,躺在那儿当吉祥物。
3. 本地 --patch 用了相对路径。 本地 Patch 加载插件要求绝对路径,相对路径会静默解析失败------不报错,但啥也不加载,专治各种不服。
4. 装完插件不 dump-config。 养成习惯,装完先 dump-config 确认插件真进了插件树再启动,能省下大把抓瞎的时间。
5. 同一个插件加载两次。 已经用 plugin add 装过 Bundle,又在 cordis.patch.yml 里手动 insert 同一插件,会喜提 duplicate loader entry id。正式 Bundle 装完就别手贱再手动 insert 了。
另外提醒一句:DSH 目前还是 Developer Preview,插件 API 可能来个破坏性更新。自己写插件时依赖要锁版本,别长期用 latest;升级前先做兼容测试,别拿生产环境练手。
结尾:先跑起来,再决定要不要跟它过日子
回到开头的问题:DSH 值不值得上手?我的建议分两步判断。
第一步,先跑通"它是什么":装好 Node,一条命令启动,选个工作区,让它帮你做件真实的、低风险的小事------比如给项目写 README、理理目录结构。如果这流程你觉得顺、可控、看得懂,再进第二步。
第二步,再玩"它还能干啥":从给 AI 加一个工具开始,理解万物皆插件的机制。你不必成为插件专家,只要搞懂 name、inject、apply、ctx 这四个词,DSH 对你来说就不再是固定功能的工具,而是个能按你需求自己生长的平台。
一个朴素的判断标准:如果一个工具让你愿意为它多装个环境、多读几页文档,那它通常值得留;如果只是图个新鲜,它迟早回收藏夹吃灰。DSH 目前最勾人的地方,是它第一次把"AI 到底干了啥"这件事,摊开给你看。