ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

VS Code 插件实战:用 actions.json 统一面板与 Agent 工具调用

2026/10/7 6:10:09 拓冰建站 浏览量
VS Code 插件实战:用 actions.json 统一面板与 Agent 工具调用 1. 从一个重复操作说起为什么我要做这个插件项目里总有那么几条命令一天要跑几十遍。比如拉完代码先跑一遍格式化改完配置要重新生成类型定义提交前要跑一次本地校验部署前要同步一遍静态资源。这些操作本身不复杂但每次都要切到终端、翻历史命令、确认参数、等它跑完一天下来光在这些动作上耗掉的时间就不少。我用的编辑器是 VS Code团队里也有人用 JetBrains 系列。一开始我的做法很土就是把这些命令写进package.json的 scripts 里或者干脆记在便签上。后来发现 VS Code 自带的 Tasks 功能其实挺好用tasks.json里定义好任务CtrlShiftB就能跑。但问题也很明显Tasks 是编辑器级别的换台机器、换个同事、换到别的 IDE这套配置就带不过去。而且 Tasks 只能跑命令没法跟项目里的 Agent 对话也没法把执行结果回传给模型做后续判断。再后来我开始用 DeepSeek Harness 这类工具它把大模型能力接进了开发流程能读文件、能改代码、能执行命令。但用着用着又发现一个新问题每次想让 Agent 帮我跑某个固定操作都得在对话里重新描述一遍需求。比如“帮我跑一下 lint 然后修复格式问题”这句话我一天要说好几遍。模型每次都要重新理解意图、重新规划步骤既浪费 token又不稳定。于是我就想能不能把这两件事合起来把项目里反复跑的操作既做成一个可以点的面板入口又做成一个 Agent 可以直接调用的工具。面板入口给人用工具给模型用两边共享同一份配置。这就是我写这个 DeepSeek Harness 插件的出发点。这个插件解决的核心问题就一个把项目里高频、固定、有明确输入输出的操作从“每次重新描述”变成“一次定义、处处调用”。它适合那些已经在项目里用上 Agent 能力、并且有一批重复操作想固化下来的开发者。不管你是刚接触 Agent 开发还是已经在用 Harness 做自动化只要你有“这件事我做过很多遍了”的感觉这个思路就值得参考。2. 整体设计思路一份配置两个出口2.1 为什么选 actions.json 作为配置载体设计这个插件时我第一个要决定的就是配置放哪、用什么格式。候选方案有几个写死在插件代码里、放 VS Code 的 settings、用独立的 YAML 文件、或者用 JSON。写死在代码里肯定不行项目之间的操作不一样插件得通用。放 VS Code settings 也不合适因为这份配置本质上是项目级的应该跟着仓库走而不是跟着某台机器的编辑器走。YAML 可读性好但解析要额外依赖而且和项目里已有的 JSON 生态不太统一。最后我选了actions.json放在项目根目录。理由有三条。第一JSON 是项目里最常见的配置格式package.json、tsconfig.json、.eslintrc.json到处都是开发者对它没有认知负担。第二JSON 的解析在 Node 和浏览器环境里都是原生的插件不需要引入额外依赖启动快。第三这份文件可以进版本控制团队里谁改了哪个操作git diff 里看得清清楚楚。actions.json的结构我设计得很简单顶层是一个数组每个元素就是一个 action。一个 action 包含几个关键字段id是唯一标识Agent 调用时用它来定位name是显示名称面板上展示给人看description是给模型看的说明模型靠它判断什么时候该调用这个工具command是要执行的命令args是参数定义说明这个操作需要哪些输入。{ actions: [ { id: lint-and-fix, name: Lint 并自动修复, description: 对指定目录执行代码检查并自动修复可修复的问题, command: npm run lint -- --fix ${target}, args: [ { name: target, description: 要检查的目录或文件路径, default: src } ] } ] }这个结构的好处是人和模型看的是同一份东西。人看name和args模型看description和args。不需要维护两套配置也就不会出现“面板上能跑但 Agent 调不动”或者反过来“Agent 能调但面板上没有”的割裂情况。2.2 面板入口和 Agent 工具的双出口设计配置只有一份但出口有两个。第一个出口是面板入口也就是在编辑器侧边栏或者命令面板里把这些 action 列出来点一下就能跑。第二个出口是 Agent 工具也就是把这些 action 注册成模型可以调用的 function模型在对话里决定要跑哪个操作时直接调用对应的工具。这两个出口共享同一套执行逻辑。面板点击时插件读取 action 定义收集参数拼出最终命令丢给终端执行。Agent 调用时模型返回一个工具调用请求插件解析出 action id 和参数走的是完全一样的执行路径。这样做的好处是行为一致面板上跑出来是什么结果Agent 调出来就是什么结果不会因为入口不同而产生差异。为什么要有面板入口因为不是所有操作都适合让模型来决定。有些操作是开发者自己想跑的比如“我想看看当前分支和主分支的差异”这种时候直接点面板比跟模型说一句话再等它规划要快得多。面板入口是给人用的快捷方式。为什么要有 Agent 工具因为有些操作是模型在完成任务过程中需要自己触发的。比如模型在帮你重构代码改完之后它需要跑一遍 lint 确认没引入新问题。这时候如果还要人去点面板就打断自动化流程了。Agent 工具是给模型用的能力扩展。两个出口的存在本质上是在回答一个问题这个操作到底该由谁发起。人发起的走面板模型发起的走工具但底层是同一套东西。2.3 和 VS Code Tasks 的关系与取舍有人可能会问VS Code 已经有 Tasks 了为什么还要自己搞一套。我用下来的体会是Tasks 和这个插件解决的不是同一个问题。Tasks 的核心是“在编辑器里跑命令”它的配置是tasks.json绑定在.vscode目录下。它的优势是原生、稳定、和编辑器的构建系统深度集成。但它的局限也很明显Tasks 是编辑器概念不是项目概念。你换到 JetBrainsTasks 就没了你在 CI 里想复用这套定义也很别扭。而且 Tasks 没有“给模型调用”这个维度它不知道什么是 Agent也不知道什么是工具描述。这个插件的定位是“项目级的操作定义”它不绑定具体编辑器。actions.json放在项目根目录任何能读这个文件的工具都能用。VS Code 插件只是它的一个消费方理论上你写个命令行工具读同一份配置也能跑。这种解耦带来的好处是操作定义的生命周期和项目绑定而不是和某个编辑器绑定。当然Tasks 有它的价值。如果你的操作只在 VS Code 里跑而且不需要给模型调用那用 Tasks 就够了没必要引入额外的东西。这个插件更适合的场景是操作需要在多个入口复用或者需要暴露给 Agent 使用。3. 核心细节解析从配置到执行的完整链路3.1 action 定义的字段设计与参数传递一个 action 定义得好不好直接决定了它好不好用。我在设计字段时反复调整过几轮最后定下来的字段都有明确的用途。id是机器标识要求全局唯一用短横线连接的小写字母。Agent 调用时靠它定位所以不能重复。name是给人看的可以写中文可以带空格怎么清楚怎么来。description是给模型看的这里有个经验描述要写“什么时候用”而不是“这是什么”。比如“对指定目录执行代码检查并自动修复可修复的问题”就比“lint 工具”要好因为模型需要的是判断依据不是名词解释。command是命令模板里面用${参数名}占位。参数替换发生在执行前插件会把用户输入或模型传入的值填进去。这里要注意转义问题如果参数值里包含空格或特殊字符需要做 shell 转义否则命令会拼错。我在实现时对参数值做了基本的引号包裹避免大部分注入问题。args是参数定义数组每个参数有name、description、default三个字段。default很重要它让参数变成可选的。如果模型没传某个参数就用默认值如果面板上用户没填也用默认值。这样设计的好处是常用操作可以零参数直接跑需要定制时再传参。参数传递的链路是这样的面板入口收集表单值Agent 入口从工具调用请求里取参数两者都汇成一个键值对对象然后拿这个对象去替换command里的占位符。替换完成后得到一个最终命令字符串交给执行器。3.2 命令执行与输出回传的处理命令执行看起来简单其实有不少细节。我用的是 Node 的child_process来跑命令但直接exec有几个问题输出可能很大、执行时间可能很长、需要能中断。输出大的问题我用的是流式读取。命令的 stdout 和 stderr 都监听data事件边跑边把输出追加到一个缓冲区。面板上实时显示Agent 调用时则在命令结束后把完整输出回传。这里有个坑如果输出特别大比如跑一个全量构建缓冲区可能撑爆内存。我的处理是设一个上限超过就截断并在末尾标注“输出已截断”。执行时间长的问题我加了超时机制。默认超时是 120 秒可以在 action 定义里覆盖。超时后杀掉进程返回超时错误。这个默认值是根据常见操作估的lint、格式化、类型检查一般都在这个范围内。如果是构建或测试可能需要调大。中断的问题面板上跑的命令可以点停止按钮Agent 调用的命令目前不支持中途取消因为模型调用是同步等待结果的。这是个已知限制后续可以考虑加异步任务机制。输出回传给 Agent 时我做了一层处理把 ANSI 颜色码去掉把过长的行折行把退出码附在末尾。模型不需要看颜色它需要的是干净的文本和明确的成功失败标识。退出码为 0 表示成功非 0 表示失败这个约定让模型能判断操作结果。3.3 工具描述如何影响 Agent 的调用准确率这一块是我踩坑最多的地方。一开始我以为只要把 action 注册成工具模型自然就会用。实际跑下来发现模型经常该调用的时候不调用不该调用的时候乱调用。问题出在工具描述上。模型的工具调用决策很大程度上依赖description字段。如果描述写得太泛比如“执行命令”模型不知道什么时候该用如果写得太窄比如“执行 eslint 检查 src 目录”模型又不知道参数可以变。我后来总结出一个写法描述里要包含触发场景、输入说明、输出说明三部分。触发场景告诉模型“什么时候用我”输入说明告诉模型“我需要什么”输出说明告诉模型“我会返回什么”。比如当需要检查代码风格并自动修复格式问题时使用。输入是要检查的目录路径默认为 src。返回检查结果和修复后的文件列表。这样写之后模型调用准确率明显提升。另外参数描述也要写清楚模型是靠参数描述来决定传什么值的。如果参数描述是空的模型可能就不传参数直接用默认值这在需要定制时就会出问题。还有一个经验是工具数量不要太多。我一开始把二十多个操作全注册成工具结果模型选择困难经常调错。后来我做了分组把高频操作注册成工具低频操作只保留面板入口。工具数量控制在十个以内模型的选择准确率会好很多。4. 实操过程从零把这个插件跑起来4.1 环境准备与插件安装这个插件是基于 VS Code 扩展机制开发的所以你需要有 Node 环境和 VS Code。Node 版本建议 18 以上因为用到了较新的 API。VS Code 版本建议 1.80 以上确保扩展 API 的兼容性。安装方式有两种。一种是从插件市场装搜索对应的插件名即可。另一种是本地开发模式把源码 clone 下来npm install之后按 F5 启动调试窗口。如果你要改代码用第二种如果只是用第一种就够了。装完之后插件会在项目根目录找actions.json。如果找不到面板上会提示你创建一个。你可以手动创建也可以用插件提供的初始化命令生成一个模板。模板里会带几个示例 action你可以照着改。这里有个细节actions.json的查找是从当前打开的文件夹根目录开始的。如果你打开的是 monorepo 的子包可能需要在子包目录下也放一份或者配置一个指向根目录的路径。我目前的处理是只找根目录monorepo 场景后续再优化。4.2 编写第一个 action 并验证假设你的项目里有一个常用的操作跑测试并生成覆盖率报告。命令是npm test -- --coverage。我们把它写成一个 action。{ actions: [ { id: test-coverage, name: 跑测试并生成覆盖率, description: 当需要运行单元测试并查看覆盖率时使用。无需输入参数直接执行。返回测试结果和覆盖率摘要。, command: npm test -- --coverage, args: [] } ] }保存文件后面板上应该会刷新出这个 action。点一下终端里就会跑起来。跑完之后输出会显示在面板的输出区域。验证 Agent 调用时你需要在 Harness 的对话里触发一个需要跑测试的场景。比如你说“帮我确认一下当前代码的测试覆盖率”模型应该会调用test-coverage这个工具。如果它没调用检查一下description是不是写得不够明确。我建议第一个 action 先用最简单的、无参数的、执行快的命令来验证链路。等链路通了再逐步加复杂的。4.3 带参数 action 的完整配置示例带参数的操作更能体现这个插件的价值。比如“对指定文件跑格式化”这个操作文件路径是变化的就需要参数。{ actions: [ { id: format-file, name: 格式化指定文件, description: 当需要格式化单个文件时使用。输入是文件路径必填。返回格式化结果。, command: npx prettier --write ${filePath}, args: [ { name: filePath, description: 要格式化的文件路径相对于项目根目录, default: } ] } ] }这里default是空字符串意味着这个参数实际上是必填的。如果用户或模型不传命令会变成npx prettier --writeprettier 会报错。更好的做法是在执行前校验必填参数如果为空就返回一个明确的错误提示而不是让命令去报错。我在插件里加了这个校验参数为空且没有默认值时直接返回“缺少必填参数”的错误。参数替换时要注意路径问题。如果参数是相对路径命令执行的工作目录是项目根目录所以相对路径是相对于根目录的。如果用户传的是绝对路径也能正常工作。但如果路径里有空格需要引号包裹。我在替换时统一加了引号避免空格导致的参数断裂。4.4 在 Agent 对话中调用工具的实测记录实测下来模型调用工具的流程是这样的用户在对话里提出需求模型判断需要调用某个工具返回一个工具调用请求插件执行对应 action把结果回传给模型模型基于结果继续对话。我拿“格式化指定文件”这个 action 测了几轮。第一轮我说“帮我把 src/utils/date.ts 格式化一下”模型正确识别出要调用format-file并传了filePath为src/utils/date.ts。执行成功返回了 prettier 的输出。模型看到输出后回复“文件已格式化”。第二轮我说“格式化一下这个文件”但没有指明是哪个文件。模型没有调用工具而是反问我“请问要格式化哪个文件”。这个行为是对的因为filePath是必填的模型知道缺参数。第三轮我说“把整个 src 目录格式化一下”。模型调用了format-file但传的filePath是src。prettier 对目录也能处理所以执行成功了。但这里其实有个语义问题action 的名字叫“格式化指定文件”但实际传目录也能跑。这说明description和name要一致否则模型的理解会有偏差。后来我把描述改成了“格式化指定文件或目录”就更准确了。这几轮测下来我的体会是工具描述的质量直接决定调用质量。描述写得清楚模型就调得准描述含糊模型就瞎调。这个投入是值得的。5. 常见问题与排查技巧实录5.1 插件装了但面板不显示 action这是最常见的问题通常有三个原因。第一actions.json不在项目根目录。插件只找根目录子目录里的不认。第二JSON 格式有错。少个逗号、多个括号解析就会失败。插件在解析失败时会在输出通道里打日志你可以打开输出面板看具体错误。第三文件保存后没触发刷新。插件监听文件变化但有些编辑器保存时不会触发文件系统事件手动重启一下窗口就好。排查顺序建议是先确认文件位置再确认 JSON 合法性最后重启窗口。我遇到过好几次都是 JSON 里多了个尾逗号肉眼很难发现用编辑器的 JSON 校验功能能快速定位。5.2 Agent 不调用工具或调用错误工具模型不调用工具八成是description没写好。模型判断要不要调用靠的就是描述里的触发场景。如果描述里没写“什么时候用”模型就不知道什么时候该用。我的经验是描述里一定要有“当……时使用”这样的句式。调用错误工具通常是工具之间描述太像。比如有两个 action一个叫“跑测试”一个叫“跑构建”描述都写得很泛模型就容易混。解决办法是把描述写具体把区别点出来。比如“跑测试”的描述里强调“验证代码逻辑正确性”“跑构建”的描述里强调“生成可部署产物”。还有一个可能是工具数量太多。前面说过控制在十个以内。如果确实有很多操作可以考虑做二级分类或者只把最高频的注册成工具。5.3 命令执行失败但看不出原因命令执行失败时插件会把 stderr 和退出码回传。但有时候 stderr 是空的退出码非 0这就很难排查。这种情况通常是命令本身的问题比如命令不存在、权限不够、工作目录不对。我一般会先在终端里手动跑一遍同样的命令确认命令本身没问题。如果手动能跑通插件跑不通那就是环境差异。常见的是 PATH 问题插件执行命令时的环境变量和终端不一样导致找不到命令。解决办法是在 action 里用绝对路径或者在命令前加上环境初始化。还有一个坑是工作目录。插件默认在项目根目录执行命令但如果你的命令依赖某个子目录就需要在命令里cd过去。我建议命令里显式写清楚工作目录不要依赖默认值。5.4 输出乱码或颜色码干扰有些命令的输出带 ANSI 颜色码在终端里看是彩色的但回传给模型就是一堆[32m之类的乱码。我在插件里做了颜色码剥离但有些命令用的转义序列比较特殊可能剥不干净。如果你遇到这个问题可以在 action 里给命令加上禁用颜色的参数。比如很多工具支持--no-color或NO_COLOR1环境变量。在命令前加上NO_COLOR1通常能解决大部分问题。乱码的另一个来源是编码。Windows 上有些命令默认用 GBK 编码输出而插件按 UTF-8 解析就会乱码。解决办法是在命令里指定编码或者用chcp 65001切换代码页。这个在跨平台项目里比较常见。5.5 常见问题速查表问题现象可能原因排查方法解决方式面板不显示 action文件位置不对确认 actions.json 在根目录移动到根目录面板不显示 actionJSON 格式错误看输出通道日志修复 JSON 语法Agent 不调用工具描述缺少触发场景检查 description 字段补充“当……时使用”Agent 调错工具工具描述太相似对比各工具描述写清区别点命令执行失败环境变量差异终端手动跑一遍用绝对路径或初始化环境输出乱码颜色码或编码问题看原始输出加 --no-color 或指定编码参数没替换占位符拼写错误检查 command 里的 ${}修正占位符名称执行超时命令耗时过长看超时设置调大 timeout 值6. 一些实操心得和后续可以扩展的方向这个插件我从有想法到跑通大概花了一个周末。中间踩的坑主要集中在工具描述和参数传递上。工具描述这块我改了好几版才找到感觉核心就是站在模型的角度想它看到这段描述能不能判断出什么时候该用、怎么用。参数传递这块主要是转义和校验宁可多写几行校验代码也不要让命令带着错误参数去跑。有一个小技巧我一直在用给每个 action 加一个dryRun参数默认 false。当dryRun为 true 时命令不真正执行只打印出最终拼出来的命令。这个在调试参数替换时特别有用能一眼看出命令拼得对不对。后续可以扩展的方向有几个。一个是支持 action 之间的依赖比如跑完 lint 再跑测试可以定义一个组合 action。另一个是支持条件执行比如只在某个文件变化时才跑。还有一个是把执行历史记录下来面板上能看到最近跑过哪些操作方便重复执行。如果你也在用 Agent 做开发并且有一批重复操作想固化下来我建议先从最简单的无参数 action 开始跑通链路之后再逐步加复杂度。不要一上来就搞很复杂的配置那样出了问题很难定位。先把一个 action 跑顺后面的就是复制粘贴改改的事。