尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
DeepSeek Harness (dsh) 插件开发实战:从零写一个自动调用 Codex 的插件
1. 从零理解 dsh 插件为什么“一切皆插件”值得你动手DeepSeek Harness下称 dsh是一个把“一切皆插件”落到实处的智能体运行时。它构建在开源插件框架 Cordis 之上内核极小工具、LLM 适配器、沙箱、终端、UI、命令、skill、权限策略全部以插件形式存在通过配置组合成完整能力。换句话说你想给 dsh 加任何能力路径只有一条写一个插件然后在配置里点亮它。这篇教程面向想让 dsh 自动触发 Codex 完成代码任务的开发者。我会带你从初始化 TypeScript 工程开始定义插件清单与生命周期钩子封装 Codex 调用并处理流式返回最后在本地加载插件、触发一次真实调用、核对返回结果。整个过程基于 dsh 0.1.0-rc.5 源码实测示例是一个真实可用的插件用户一提到 “codex”dsh 就自动把任务交给 WSL 里的 Codex CLI 完成。先明确一个概念dsh 插件不是一次性的扩展点而是 Citizen一等公民和官方内置插件共享同一套机制。一个插件就是一个导出apply(ctx)函数的 TypeScript 模块框架加载时调用apply把上下文对象ctx交给你你通过它注册一切能力。没有框架启动代码插件只描述自己的贡献组合方式是配置的事。插件有三种形态函数形态最常用导出apply(ctx)对象形态导出default { name, apply }类形态用于对外提供服务继承Service。所有通过ctx做的注册——事件监听、工具注册、定时器——在插件卸载时都会被自动清理你不需要手动removeListener或clearInterval。需要依赖其他服务时声明inject框架会等依赖就绪后再加载你的插件。理解这些之后你会发现 dsh 插件开发的门槛比想象中低。接下来我们从工程初始化开始一步步把插件跑起来。2. 初始化 TypeScript 工程与插件清单配置2.1 创建工程与安装依赖先建目录并初始化mkdir wsl-codex cd wsl-codex pnpm init pnpm add -D typescript types/node pnpm add deepseek-ai/cordis deepseek-ai/schemastery deepseek-ai/dsh-tools deepseek-ai/dsh-llm这里的关键依赖有四个deepseek-ai/cordis提供Context与Service类型deepseek-ai/schemastery用于定义配置 schemadeepseek-ai/dsh-tools提供defineTooldeepseek-ai/dsh-llm提供createUserMessage用于自动触发时注入上下文。2.2 可复制的 tsconfig.jsondsh 插件运行在 Node ESM 环境tsconfig 需要开启module: NodeNext并确保strict打开因为 schema 推导依赖严格类型{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, strict: true, esModuleInterop: true, skipLibCheck: true, declaration: true, outDir: dist, rootDir: src, types: [node] }, include: [src/**/*.ts] }moduleResolution: NodeNext是必须的否则deepseek-ai/*的子路径导出会解析失败。skipLibCheck建议打开避免第三方类型定义拖慢编译。2.3 插件入口与 manifest 配置插件入口src/index.ts的最小骨架如下包含名称、配置 schema、依赖声明和applyimport type { Context } from deepseek-ai/cordis import Schema from deepseek-ai/schemastery export const name wsl-codex export interface Config { distro: string keyword: string codexBin: string timeoutMs: number skipGitRepoCheck: boolean } export const Config Schema.object({ distro: Schema.string().default(Ubuntu-22.04), keyword: Schema.string().default(codex), codexBin: Schema.string().default(codex), timeoutMs: Schema.number().default(600_000), skipGitRepoCheck: Schema.boolean().default(true), }) export const inject [tools, subprocess, agents] export function apply(ctx: Context, config: Config) { // 工具注册与自动触发逻辑在下一节展开 }凡是“不同部署可能需要不同取值”的参数都必须定义成配置字段这是 dsh 的硬性约定。默认值直接写在 schema 里框架会在加载时校验并填充默认值。inject声明了插件依赖的服务框架会等ctx.tools、ctx.subprocess、ctx.agents就绪后再调用apply。如果你要打包分发给别人还需要一个带dsh.bundlemanifest 的package.json{ name: dsh-wsl-codex, version: 0.1.0, type: module, main: dist/index.js, files: [dist, cordis.patch.yml], dsh: { bundle: { patch: ./cordis.patch.yml } } }cordis.patch.yml是这个包贡献的配置层用户安装后dsh plugin --profile web add ./wsl-codex会把它追加到 profile 的dsh.profile.bundles列表。3. 封装 Codex 调用工具注册与流式返回处理3.1 用 defineTool 注册工具工具是模型能调用的函数。用defineTool定义schema 自动推导出类型化参数并做运行时校验import { defineTool } from deepseek-ai/dsh-tools ctx.tools.register(defineTool({ name: wsl_codex, description: Run OpenAI Codex inside WSL on a self-contained task and return its final answer. Use this whenever the user mentions Codex or asks to delegate work to Codex., parameters: { task: { type: string, required: true, description: The task to hand to Codex. }, cwd: { type: string, description: Working dir (WSL path or Windows path, auto-mapped). }, }, output: { schema: { type: string }, render: (_args, value) [{ type: text, text: value }], }, async execute(args, exec) { // 调用逻辑见下 }, }))几个要点execute(args)返回的是规范 JSON 值人类可读的文案放在output.render里不要混在一起遵守exec.signal及时取消长任务走run_in_backgroundctx.jobs部署策略允许/拒绝/审批不要写进工具用tools/pre-execute等钩子实现。3.2 路径映射与 HOME 重设Windows 路径要转成 WSL 路径否则cd会失败function winToWslPath(winPath: string): string { const normalized winPath.replaceAll(\\, /) const drive /^([a-zA-Z]):\//.exec(normalized) if (drive) return /mnt/${drive[1].toLowerCase()}/${normalized.slice(3)} return normalized.startsWith(/) ? normalized : normalized }关键坑wsl.exe会把 Windows 的HOME传进 WSL导致 codex 读到 Windows 侧配置。必须在 bash 里重设 HOMEconst wslCwd winToWslPath(args.cwd ?? exec.agent?.session.header.cwd ?? process.cwd()) const script [ export HOME$(getent passwd $(id -un) | cut -d: -f6), cd ${wslCwd}, ${config.codexBin} exec${config.skipGitRepoCheck ? --skip-git-repo-check : }, ].join( )3.3 流式返回与超时处理用ctx.subprocess.spawn启动进程stdin 写入任务stdout 收集流式输出const controller new AbortController() exec.signal.addEventListener(abort, () controller.abort(exec.signal.reason), { once: true }) const timer setTimeout(() controller.abort(new Error(timeout ${config.timeoutMs}ms)), config.timeoutMs) try { const handle ctx.subprocess.spawn({ argv: [wsl.exe, -d, config.distro, --cd, wslCwd, --, bash, -lc, script], cwd: process.cwd(), stdio: { stdin: { data: args.task }, stdout: { maxBytes: 200_000 }, stderr: { maxBytes: 50_000 }, }, graceMs: 5_000, signal: controller.signal, }) const outcome await handle.done const stdout handle.collected.stdout?.readFrom(0).text ?? if (outcome.exitCode ! 0) throw new Error(codex exited ${outcome.exitCode}) return stdout.trim() } finally { clearTimeout(timer) exec.signal.removeEventListener(abort, () controller.abort(exec.signal.reason)) }maxBytes限制防止内存爆掉graceMs给进程优雅退出时间signal让取消能传导到子进程。3.4 自动触发钩子用户消息命中关键词时注入上下文让模型调用wsl_codexconst injectedFor new Setstring() ctx.on(session/event, (session, event) { if (event.type ! user/message) return if (event.data.source.kind ! user) return const text event.data.content .map(b (b.type text ? b.text : )) .join(\n) if (!text.toLowerCase().includes(config.keyword.toLowerCase())) return const agent ctx.agents.get(session.id) if (!agent || injectedFor.has(session.id)) return injectedFor.add(session.id) agent.inject(createUserMessage({ content: [{ type: text, text: The user mentioned Codex. Use the wsl_codex tool to delegate the work. }], source: { kind: plugin, plugin: name }, })) })每个会话只提醒一次避免重复注入。4. 本地加载插件与验证 Codex 调用结果4.1 在 profile 中点亮插件插件写好后不会自动加载必须显式声明在配置层里。在 profile 的cordis.patch.yml里插入一行- insert: - id: wsl-codex name: file:///F:/my-project/wsl-codex/src/index.ts config: distro: Ubuntu-22.04 keyword: codex timeoutMs: 600000注意 Windows 上路径必须是file:///URL写F:/...会被当成f:协议抛ERR_UNSUPPORTED_ESM_URL_SCHEME。4.2 用 --dump-config 核对配置来源重启 dsh web 前先用--dump-config看每一行来自哪个文件、被谁修改过pnpm dsh --profile web --dump-config输出里应该能看到wsl-codex这一行以及它的config对象。如果没出现说明 patch 文件路径或层级不对。4.3 触发一次真实调用重启 dsh web 后在会话里输入包含 “codex” 的消息比如用 codex 帮我把 src/utils.ts 里的日期格式化函数改成支持时区参数预期行为dsh 自动注入提醒模型调用wsl_codex工具WSL 里的 Codex CLI 执行任务最终答案返回会话。终端里能看到[wsl-codex] plugin loaded!说明插件已生效。4.4 核对返回结果检查三点一是工具调用记录里wsl_codex的task参数是否是你输入的任务二是stdout是否包含 Codex 的最终答案三是exitCode是否为 0。如果exitCode非 0看stderr里的错误信息。5. 常见报错排查401、local proxy failed 与 OAuth5.1 401 Unauthorized如果 Codex CLI 返回 401通常是认证信息没读到。检查 WSL 里的~/.codex配置是否存在以及 HOME 是否被正确重设。可以在 WSL 里手动跑一次codex exec --skip-git-repo-check确认认证正常。5.2 local proxy failed这个报错通常出现在网络请求层。检查 WSL 的网络配置是否能访问 Codex 服务以及codexBin路径是否正确。如果 Codex 需要走特定端点确认环境变量已传入。5.3 reading choices 报错reading choices一般出现在流式返回解析阶段说明 stdout 格式不符合预期。检查maxBytes是否太小导致输出被截断或者 Codex 版本不兼容。可以先把maxBytes调大再试。5.4 OAuth 相关错误如果 Codex 使用 OAuth 认证WSL 里的浏览器回调可能无法完成。建议在 WSL 里先手动完成一次 OAuth 登录确认 token 已缓存再让插件调用。5.5 配置层按行整替patch 覆盖一行是替换整个config对象不是深合并。覆盖内置插件时要重述它需要的全部键否则会丢配置。5.6 插件路径协议错误Windows 上写F:/...会抛ERR_UNSUPPORTED_ESM_URL_SCHEME必须写成file:///F:/...。6. 接入 TaoToken 与长期编码方案如果你希望 Codex 调用走更稳定的模型接入层可以把 Codex 的 Base URL 指向 TaoToken 的 API 端点。TaoToken 提供兼容 OpenAI 的接口适合作为长期编码与 Agent 场景的模型入口。配置时三件套要写全Base URL 填https://taotoken.net/apiKey 在控制台创建Model ID 按你使用的模型填写。在 Codex 的配置里对应设置# ~/.codex/config.toml model your-model-id model_provider taotoken [model_providers.taotoken] base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY然后在 WSL 里导出环境变量export TAOTOKEN_API_KEYsk-你的key验证请求是否成功可以直接用 curl 测一次curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:your-model-id,messages:[{role:user,content:ping}]}返回choices字段说明接入正常。如果要在 dsh 里长期跑编码任务建议用 Coding Plan 管理额度与模型切换需要调试模型行为时用模型对话页面快速验证创建和管理 Key 在 API Keys 页面接入细节看接入文档。插件给了 dsh 无限的可能性。把它写成一个目录、一行配置、一个工具剩下的交给组合。
RELATED

相关推荐

claude-mem 实战:为对话式 AI 构建持久化记忆系统

claude-mem 实战:为对话式 AI 构建持久化记忆系统

1. 从“聊完就忘”说起:claude-mem 到底想解决什么如果你用 Claude 这类对话式 AI 做过稍微长期一点的事情,大概率遇到过这种尴尬:昨天花了半小时跟它对齐的项目背景、代码规范、命名习惯,今天开个新会话,它全忘了&…

📅 2026/10/8 17:29:06
用PI控制器思路调校AI编程助手:从上下文爆炸到稳定输出

用PI控制器思路调校AI编程助手:从上下文爆炸到稳定输出

1. 给编程助手取名Pi:从"对话打字员"到"自治开发小组"如果你和我一样,每天要在编辑器里和AI编码助手来回拉扯十几个回合,你一定体会过那种感觉:它能写,但忘得快。项目刚开始时很爽,到第…

📅 2026/10/8 17:29:06
单片机C库运行时:链接布局、可重入与中断安全实战

单片机C库运行时:链接布局、可重入与中断安全实战

1. 从一次HardFault说起&#xff1a;为什么C库运行时值得单独拎出来讲很多人写单片机代码&#xff0c;注意力几乎全放在外设寄存器、中断向量、时钟树上&#xff0c;觉得C库就是#include <string.h>之后随便调调memcpy、strlen的事。我早年也是这么想的&#xff0c;直到有…

📅 2026/10/8 17:29:06
MORE NEWS

更多资讯

📰

电子保险丝与单片机协同实现工业电源路径保护

做嵌入式和工业控制器&#xff0c;电源路径保护这四个字&#xff0c;很多人觉得是保险丝该干的事。但我自己在电源入口吃过亏&#xff1a;保险管没跳变&#xff0c;后级DC-DC已经热击穿&#xff1b;换过自恢复保险丝&#xff0c;结果动作时间太慢&#xff0c;板子还是挂了。后来…

📰

运动耳机哪个牌子好?2026年运动耳机品牌排行榜前十名实测对比

不少运动爱好者挑选耳机时都很纠结&#xff0c;市面上运动耳机品类繁多&#xff0c;骨传导、开放式挂耳等款式让人眼花缭乱&#xff0c;各类宣传卖点也真假难辨。很多耳机看着参数好看&#xff0c;实际跑步容易滑落、出汗容易故障&#xff0c;或是风噪、漏音问题突出&#xff0…

📰

智能体工程化转型:行为审计、评测基准与成本优化实战

1. 从本周趋势榜看智能体赛道的真实转向过去大半年&#xff0c;只要聊到智能体&#xff0c;圈子里最常见的讨论还是"哪个框架更顺手""提示词怎么写更稳""怎么让模型别乱调工具"。但这一周的 GitHub Trending 中文区给了我一个很明确的信号&#…

📰

FDE实战:用Agent Harness、RAG与MCP串起AI应用全流程

从去年开始&#xff0c;我身边越来越多同事的title里出现了FDE三个字母。有人以为这是前端工程师&#xff08;Front-End Developer&#xff09;的缩写&#xff0c;也有人觉得是某个新职级。其实在我做的这条业务线里&#xff0c;FDE指的是Feature/Full-cycle Development Engin…

📰

DeepSeek Harness桌面端详解:内网部署、插件配置与避坑指南

前段时间 DeepSeek Harness 悄悄更新了桌面端 Release&#xff0c;我看到消息的时候先愣了一下——这项目不是一直在终端里跑的吗&#xff1f;等到自己下载下来用了两天&#xff0c;又把安装目录、配置文件、插件机制从头到尾翻了一遍&#xff0c;才意识到这个"桌面端&quo…

📰

命令行参数:main函数的参数

345 命令行参数:main函数的参数 你有没有注意过,Linux命令后面总能跟各种参数?ls -la、gcc -o main main.c、python script.py arg1 arg2。这些参数是怎么传递给程序的呢?答案就藏在main函数的参数里。 一、main函数的完整形式 int main(int argc, char *argv[])参数 类…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬