尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DeepSeek Harness插件开发:将重复操作固化为面板按钮与Agent工具

DeepSeek Harness插件开发:将重复操作固化为面板按钮与Agent工具 1. 从一个烦人的重复操作说起项目里总有那么几件事一天要跑八遍。比如改完代码跑一遍 lint 加单测比如每次提交前要同步一下某个目录比如调试某个服务时得先起三个依赖进程。这些操作本身不复杂但架不住频率高而且每次都要切终端、敲命令、等结果、看输出一套流程下来注意力被打断好几次。我一开始的解法很朴素写 shell 脚本扔到scripts/目录里需要的时候bash scripts/xxx.sh。这招管用但有几个问题一直没解决。第一脚本散落在项目各处新人进来根本不知道有哪些可用操作得靠口口相传或者翻 README。第二脚本和 IDE 是割裂的我人在编辑器里却要切到终端去触发上下文切换成本高。第三脚本只能我自己用团队里其他人想复用得先理解脚本参数、环境依赖门槛不低。后来我把目光投向了 DeepSeek Harness 的插件机制。Harness 本身是一个面向 Agent 工作流的运行框架它允许你通过插件的方式扩展能力把外部工具、脚本、服务包装成 Agent 可以调用的工具同时也能在面板上暴露入口。这就意味着我可以把那些反复跑的操作一次性固化成两个东西一个是面板上的按钮点一下就跑另一个是 Agent 工具让 Agent 在需要的时候自己调用。这个思路的核心价值在于把隐性的操作知识显性化把个人的肌肉记忆变成团队可复用的资产。你不再需要记住“那个同步脚本叫什么名字”也不需要在新人入职时花半小时讲“我们项目有这几个常用命令”。面板上列得清清楚楚Agent 也能在对话里直接帮你执行。这篇文章我会完整拆解这个插件的设计思路、核心实现、踩过的坑以及怎么把它适配到你自己的项目里。不管你是刚接触 Harness 插件开发还是已经在用 Agent 工作流想进一步提效应该都能从中拿到可以直接抄的作业。2. 插件整体设计与核心思路拆解2.1 为什么选 Harness 插件而不是 VS Code Tasks说到“把常用操作固化成入口”很多人第一反应是 VS Code Tasks。确实VS Code 的tasks.json能定义任务也能绑定快捷键甚至可以通过dependsOn串联多个步骤。我一开始也试过这条路但很快发现几个不匹配的地方。VS Code Tasks 的本质是“编辑器内的任务运行器”它的触发入口在编辑器里输出在终端面板。而 Harness 插件的定位是“Agent 工作流的能力扩展”它的触发入口既可以在面板上也可以被 Agent 调用。这两者的区别在于Tasks 是给人用的插件是给人和 Agent 共用的。举个具体场景。我在调试一个接口时需要先启动 mock 服务再跑一个数据初始化脚本最后打开日志窗口。如果用 Tasks我得手动触发一个复合任务然后盯着终端看有没有报错。但如果做成 Harness 插件我可以直接在对话里说“帮我把调试环境起起来”Agent 会依次调用 mock 服务启动工具、数据初始化工具然后把日志路径返回给我。整个过程不需要我记住任何命令。另一个考虑是跨编辑器复用。VS Code Tasks 绑定在 VS Code 上如果团队里有人用 JetBrains 系列这套配置就用不了。而 Harness 插件是独立于编辑器的只要 Harness 能跑插件就能用。这对于技术栈不统一的团队来说省去了很多“你那边怎么配”的沟通成本。当然VS Code Tasks 也有它的优势比如配置简单、和编辑器深度集成、调试体验好。所以我的选择是编辑器内的轻量任务继续用 Tasks跨编辑器、需要 Agent 参与的复杂操作做成 Harness 插件。两者不是替代关系而是分工关系。2.2 插件的两个核心能力面板入口与 Agent 工具这个插件的设计目标很明确把项目里的重复操作同时暴露为面板入口和 Agent 工具。这两条路径共享同一套底层执行逻辑但面向不同的使用场景。面板入口面向的是“我知道我要做什么我只想快速触发”。比如我改完代码想跑一遍检查我直接点面板上的“运行检查”按钮结果输出在面板里展示。这条路径的关键是低认知负担按钮名字要直白参数要尽量少默认值要合理最好一键完成。Agent 工具面向的是“我不确定要做什么或者我想让 Agent 帮我判断”。比如我在对话里说“这个改动会影响哪些测试”Agent 会先分析代码变更然后调用“运行相关测试”工具把结果整理后返回给我。这条路径的关键是可组合性工具要有清晰的输入输出定义要能被 Agent 编排进更大的工作流里。这两条路径共享的核心是“操作定义”。我用一个actions.json文件来描述每个操作它叫什么名字、接受什么参数、执行什么命令、输出怎么解析。面板入口和 Agent 工具都是从这个定义文件生成的。这样做的好处是新增一个操作只需要改一处配置两个入口自动同步。2.3 actions.json 的结构设计与字段含义actions.json是整个插件的数据核心。它的结构设计直接决定了插件的易用性和扩展性。我参考了 VS Code Tasks 的字段命名习惯同时结合 Harness 工具定义的要求最终定下来这么一套结构。{ actions: [ { id: run-lint, name: 运行 Lint 检查, description: 对当前项目执行 ESLint 检查输出问题列表, category: 代码质量, command: npm run lint, cwd: ${workspaceFolder}, args: [], env: {}, timeout: 60000, outputFormat: text, agentTool: true, panelEntry: true } ] }几个关键字段值得展开说。id是操作的唯一标识Agent 调用工具时用的就是它。命名建议用短横线分隔的小写英文比如run-lint、sync-assets、start-debug-env。不要用中文也不要用空格否则在 Agent 工具注册时容易出问题。command是实际执行的命令。这里有个设计取舍是直接写完整命令还是拆成commandargs我最终选择了完整命令字符串因为很多项目的命令本身就带参数拆开反而增加配置负担。但如果你需要动态拼接参数可以在args里定义参数模板执行时替换。cwd是工作目录。支持变量替换比如${workspaceFolder}表示项目根目录。这个字段很重要因为很多脚本对执行目录敏感配错了就会出现“手动跑没问题插件跑就报错”的情况。timeout是超时时间单位毫秒。默认给 60 秒对于大多数 lint、测试、构建操作够用。如果是启动服务这类长驻进程建议单独处理不要走这个超时逻辑。outputFormat决定输出怎么解析。支持text、json、lines三种。text就是原样展示json会尝试解析成结构化数据lines会按行拆分。Agent 工具模式下json格式最友好因为 Agent 可以直接读取字段。agentTool和panelEntry是两个开关控制这个操作是否暴露为 Agent 工具、是否显示在面板上。有些操作只适合人点比如“打开日志目录”有些操作只适合 Agent 调比如“获取当前分支信息”。分开控制更灵活。2.4 与 VS Code Tasks 的字段对照如果你之前用过 VS Code Tasks下面这张对照表可以帮你快速迁移配置。VS Code Tasks 字段actions.json 对应字段说明labelname显示名称type无插件统一用 shell 执行commandcommand执行命令argsargs参数列表options.cwdcwd工作目录options.envenv环境变量dependsOn无插件暂不支持任务依赖建议用脚本串联problemMatcheroutputFormat输出解析方式groupcategory分类这张表里最值得注意的是dependsOn。VS Code Tasks 支持任务依赖可以自动串联多个任务。我的插件目前没做这个能力原因是 Agent 工具模式下串联逻辑应该由 Agent 来编排而不是硬编码在配置里。如果你确实需要串联建议写一个 shell 脚本把多个步骤包进去然后插件只调用这个脚本。3. 核心细节解析与实操要点3.1 插件目录结构与文件职责一个 Harness 插件的最小结构并不复杂但要把面板入口和 Agent 工具都跑通需要几个关键文件各司其职。下面是我实际使用的目录结构。deepseek-harness-actions/ ├── package.json ├── actions.json ├── src/ │ ├── index.ts │ ├── executor.ts │ ├── panel.ts │ └── agentTool.ts ├── dist/ │ └── index.js └── README.mdpackage.json是插件的元信息文件声明插件名称、版本、入口文件、依赖等。Harness 在加载插件时会读取这个文件所以main字段必须指向编译后的入口。actions.json是操作定义文件前面已经详细讲过。它放在插件根目录插件启动时读取并解析。src/index.ts是插件入口负责注册面板入口和 Agent 工具。它会在 Harness 启动时被调用完成初始化。src/executor.ts是执行器负责实际运行命令、处理超时、捕获输出、解析结果。面板和 Agent 工具都调用它保证行为一致。src/panel.ts是面板入口的实现负责渲染按钮、收集参数、展示输出。src/agentTool.ts是 Agent 工具的实现负责定义工具 schema、处理 Agent 调用、返回结构化结果。dist/index.js是编译产物。Harness 加载的是这个文件所以每次改完代码要重新编译。这个结构的好处是职责清晰。执行逻辑集中在executor.ts面板和 Agent 工具只是不同的“壳”。如果以后要加新的入口类型比如 CLI 命令只需要再写一个壳复用执行器即可。3.2 操作定义的参数化与变量替换硬编码命令只能解决固定场景真正好用需要支持参数化。比如“运行指定测试文件”这个操作测试文件路径应该是动态的。我在actions.json里设计了参数模板机制。{ id: run-test, name: 运行指定测试, command: npm test -- ${testFile}, args: [ { name: testFile, type: string, description: 测试文件路径, required: true, default: } ] }执行时插件会把${testFile}替换成实际传入的值。面板入口会弹出一个输入框让用户填写Agent 工具会把参数定义成 JSON Schema让 Agent 自己填。变量替换还支持内置变量比如${workspaceFolder}、${file}、${selectedText}。这些变量在面板触发时会自动填充在 Agent 触发时由 Agent 根据上下文提供。内置变量的完整列表可以参考 Harness 的文档我这里只列几个最常用的。变量名含义面板触发Agent 触发${workspaceFolder}项目根目录自动填充Agent 提供${file}当前文件路径自动填充Agent 提供${selectedText}当前选中文本自动填充Agent 提供${env:XXX}环境变量自动读取自动读取这里有个坑要注意变量替换是在命令拼接阶段做的如果变量值里包含空格或特殊字符需要做转义。我在executor.ts里对参数值做了 shell 转义处理避免命令注入和解析错误。具体做法是用单引号包裹参数值然后把值里的单引号替换成\。这个技巧在 shell 脚本里很常见但容易被忽略。3.3 输出解析与 Agent 友好格式面板入口对输出格式要求不高原样展示就行。但 Agent 工具模式下输出格式直接决定了 Agent 能不能正确理解结果。我设计了三种输出格式分别对应不同的使用场景。text格式最简单原样返回字符串。适合 lint 输出、日志这类人类可读但结构不固定的内容。Agent 拿到后需要自己解析适合 Agent 有较强理解能力的场景。json格式要求命令输出合法 JSON。插件会尝试解析解析成功返回对象失败返回错误信息。适合测试报告、构建统计这类结构化数据。Agent 拿到后可以直接读取字段不需要额外解析。lines格式把输出按行拆分返回字符串数组。适合文件列表、变更列表这类一行一条的内容。Agent 可以遍历数组也可以统计数量。下面是一个json格式的实际例子。假设有个操作是“获取当前分支信息”命令输出是{branch: main, commit: abc123}。Agent 工具返回的结果就是{ success: true, actionId: get-branch-info, data: { branch: main, commit: abc123 }, duration: 120 }这个结构里success表示执行是否成功actionId方便 Agent 追溯是哪个操作data是解析后的数据duration是耗时。Agent 可以根据这些字段做判断比如如果success为 false就提示用户检查环境。注意如果命令输出包含日志前缀或额外信息json解析会失败。建议在命令里加--silent或2/dev/null过滤无关输出确保 stdout 只有 JSON。3.4 面板入口的交互设计细节面板入口看起来简单就是几个按钮但交互细节决定了它好不好用。我踩过的坑主要集中在三个方面按钮分组、执行状态、输出展示。按钮分组方面我一开始把所有操作平铺展示结果面板上十几个按钮找起来很费劲。后来加了category字段按分类折叠展示常用分类默认展开不常用的折叠。这样面板清爽很多。执行状态方面命令执行需要时间如果点了按钮没反应用户会以为没生效然后重复点击。我加了一个执行中的状态提示按钮变成禁用状态旁边显示一个进度指示。执行完成后恢复并展示结果。输出展示方面长输出直接铺在面板上会撑爆布局。我的做法是默认只展示摘要比如“检查完成发现 3 个问题”点击后展开完整输出。对于json格式的输出还会做一个简单的表格化展示比原始 JSON 更易读。还有一个细节是错误处理。命令执行失败时不能只显示“执行失败”要展示 stderr 内容让用户知道具体哪里错了。我在executor.ts里把 stdout 和 stderr 分开捕获失败时优先展示 stderr。4. 实操过程与核心环节实现4.1 环境准备与插件初始化开始之前你需要确认几件事。第一Harness 已经安装并能正常运行。第二你有 Node.js 环境因为插件是用 TypeScript 写的需要编译。第三你有一个想要固化的操作最好先从最简单的开始比如跑 lint。初始化插件项目我习惯用下面的步骤。先建目录然后初始化package.json再安装依赖。mkdir deepseek-harness-actions cd deepseek-harness-actions npm init -y npm install --save-dev typescript types/node npm install --save deepseek/harness-sdkdeepseek/harness-sdk是 Harness 提供的插件开发 SDK里面包含了注册面板入口和 Agent 工具的 API。版本号建议用最新的稳定版避免 API 不兼容。然后创建tsconfig.json配置编译选项。关键是把target设为ES2020module设为CommonJSoutDir设为dist。这些配置和 Harness 的加载机制匹配。{ compilerOptions: { target: ES2020, module: CommonJS, outDir: dist, rootDir: src, strict: true, esModuleInterop: true, skipLibCheck: true }, include: [src/**/*] }package.json里需要补充main字段和scripts。main指向dist/index.jsscripts里加一个build命令方便编译。{ name: deepseek-harness-actions, version: 1.0.0, main: dist/index.js, scripts: { build: tsc } }这些准备工作做完就可以开始写代码了。我建议先写一个最小的可运行版本只包含一个操作跑通面板入口和 Agent 工具两条路径然后再逐步扩展。4.2 编写 actions.json 定义第一个操作第一个操作我选了“运行 Lint 检查”因为它足够简单输出也直观。在插件根目录创建actions.json写入下面的内容。{ actions: [ { id: run-lint, name: 运行 Lint 检查, description: 对当前项目执行 ESLint 检查输出问题列表, category: 代码质量, command: npm run lint, cwd: ${workspaceFolder}, timeout: 60000, outputFormat: text, agentTool: true, panelEntry: true } ] }这个定义里command是npm run lint前提是你的package.json里有这个 script。如果没有改成你项目实际使用的 lint 命令比如npx eslint .或ruff check .。cwd用${workspaceFolder}保证在项目根目录执行。timeout给 60 秒一般 lint 够用。outputFormat用text因为 lint 输出是给人看的Agent 也能理解。agentTool和panelEntry都设为true两条路径都暴露。这样我既可以在面板上点按钮也可以在对话里让 Agent 帮我跑。4.3 实现执行器 executor.ts执行器是核心负责把actions.json里的定义变成实际执行的命令。下面是我简化后的实现保留了关键逻辑。import { exec } from child_process; import { promisify } from util; const execAsync promisify(exec); export interface ActionDefinition { id: string; name: string; command: string; cwd?: string; timeout?: number; outputFormat?: text | json | lines; args?: Array{ name: string; type: string; required?: boolean; default?: string; }; } export interface ExecutionResult { success: boolean; actionId: string; data: any; error?: string; duration: number; } export async function executeAction( action: ActionDefinition, params: Recordstring, string {} ): PromiseExecutionResult { const startTime Date.now(); const command buildCommand(action, params); const cwd resolveCwd(action.cwd); try { const { stdout, stderr } await execAsync(command, { cwd, timeout: action.timeout || 60000, maxBuffer: 10 * 1024 * 1024, }); const data parseOutput(stdout, action.outputFormat || text); return { success: true, actionId: action.id, data, duration: Date.now() - startTime, }; } catch (error: any) { return { success: false, actionId: action.id, data: null, error: error.stderr || error.message, duration: Date.now() - startTime, }; } } function buildCommand( action: ActionDefinition, params: Recordstring, string ): string { let command action.command; for (const [key, value] of Object.entries(params)) { command command.replace( new RegExp(\\$\\{${key}\\}, g), escapeShellArg(value) ); } return command; } function escapeShellArg(arg: string): string { return ${arg.replace(//g, \\)}; } function resolveCwd(cwd?: string): string { if (!cwd) return process.cwd(); return cwd.replace(${workspaceFolder}, process.cwd()); } function parseOutput( output: string, format: text | json | lines ): any { const trimmed output.trim(); if (format json) { try { return JSON.parse(trimmed); } catch { return { raw: trimmed, parseError: true }; } } if (format lines) { return trimmed.split(\n).filter(Boolean); } return trimmed; }这段代码有几个关键点。buildCommand负责变量替换escapeShellArg做 shell 转义防止参数里的特殊字符破坏命令结构。resolveCwd处理工作目录支持${workspaceFolder}变量。parseOutput根据格式解析输出json解析失败时返回原始文本并标记parseError方便排查。maxBuffer设成 10MB因为有些命令输出很大默认的 1MB 容易溢出。这个值可以根据项目情况调整但不要设太大避免内存问题。4.4 注册面板入口与 Agent 工具入口文件index.ts负责把执行器、面板、Agent 工具串起来。下面是一个简化版的实现。import * as fs from fs; import * as path from path; import { executeAction, ActionDefinition } from ./executor; import { registerPanelEntry } from ./panel; import { registerAgentTool } from ./agentTool; export function activate(context: any) { const actionsPath path.join(__dirname, .., actions.json); const actionsConfig JSON.parse(fs.readFileSync(actionsPath, utf-8)); const actions: ActionDefinition[] actionsConfig.actions; for (const action of actions) { if (action.panelEntry) { registerPanelEntry(context, action, executeAction); } if (action.agentTool) { registerAgentTool(context, action, executeAction); } } } export function deactivate() { // 清理资源 }activate是 Harness 加载插件时调用的入口。它读取actions.json遍历每个操作根据开关注册面板入口和 Agent 工具。deactivate在插件卸载时调用用来清理资源比如关闭长驻进程、释放文件句柄。registerPanelEntry和registerAgentTool的具体实现分别在panel.ts和agentTool.ts里。面板入口的注册逻辑主要是创建按钮、绑定点击事件、展示输出。Agent 工具的注册逻辑主要是定义工具 schema、绑定调用处理函数。这里有个细节要注意actions.json的路径是相对于编译后的dist目录的。因为index.js在dist里__dirname指向dist所以要用..回到插件根目录。如果你把actions.json放在别的位置记得调整路径。4.5 编译与加载插件代码写完后运行npm run build编译。编译成功会在dist目录生成index.js。然后需要在 Harness 的配置里注册这个插件。Harness 的插件配置方式取决于你的安装方式。如果是桌面版通常在设置里有一个“插件目录”配置项把插件根目录路径填进去。如果是命令行版可能在配置文件里加一行插件路径。加载成功后面板上应该能看到“运行 Lint 检查”按钮。点击按钮命令执行输出展示在面板上。同时在对话里Agent 应该能识别到run-lint这个工具你可以说“帮我跑一下 lint”Agent 会调用它。如果面板没出现按钮或者 Agent 不认识这个工具先检查actions.json的路径是否正确再检查package.json的main字段是否指向dist/index.js。这两个地方最容易出错。5. 常见问题与排查技巧实录5.1 插件加载失败与路径问题插件加载失败是最常见的问题表现是面板上没有按钮Agent 也不认识工具。排查思路按下面的顺序来。先看 Harness 的日志。大多数加载失败会在日志里留下错误信息比如“找不到入口文件”“actions.json 解析失败”“插件依赖缺失”。日志位置取决于 Harness 的安装方式桌面版一般在用户目录下的日志文件夹命令行版直接输出到终端。如果日志里没有明显错误检查package.json的main字段。这个字段必须指向编译后的入口文件通常是dist/index.js。如果指向src/index.tsHarness 加载时会报错因为它不认识 TypeScript。再检查actions.json的路径。index.ts里读取actions.json用的是相对路径相对于dist目录。如果你把actions.json放在插件根目录路径应该是path.join(__dirname, .., actions.json)。如果放错位置读取会失败。还有一个容易忽略的点是文件权限。如果插件目录在 Linux 或 macOS 上确保 Harness 进程有读取权限。Windows 上一般不会有这个问题但如果插件目录在网络驱动器上可能会有权限限制。5.2 命令执行报错与工作目录陷阱命令执行报错但手动在终端跑同样的命令却没问题这种情况十有八九是工作目录不对。cwd字段如果没配默认是 Harness 进程的工作目录而不是项目根目录。很多脚本依赖相对路径工作目录错了就会找不到文件。我的做法是每个操作都显式配cwd用${workspaceFolder}变量。这样不管 Harness 从哪里启动命令都在项目根目录执行。另一个常见原因是环境变量缺失。手动跑命令时shell 会加载.bashrc或.zshrc里的环境变量但插件执行时不会加载这些文件。如果命令依赖某个环境变量需要在actions.json的env字段里显式声明或者在命令里用source加载配置文件。还有一种情况是命令本身有交互式提示比如npm init会问问题。插件执行时没有终端交互命令会卡住直到超时。这类命令不适合做成插件操作建议加--yes或-y参数跳过交互。5.3 Agent 工具调用参数不匹配Agent 调用工具时参数格式必须和actions.json里定义的 schema 匹配。如果 Agent 传的参数名不对或者类型不对执行会失败。排查方法是看 Agent 返回的错误信息。如果提示“缺少必填参数”检查args里的required字段是否设成了true以及 Agent 是否真的传了这个参数。如果提示“参数类型错误”检查type字段是否和 Agent 传的值匹配。有时候 Agent 会自作主张传一些额外参数比如_reason或_confidence。这些参数不在 schema 里会被忽略不影响执行。但如果你的命令拼接逻辑对未知参数敏感可能会出问题。我的做法是在buildCommand里只替换 schema 里定义的参数忽略其他。还有一个坑是参数值包含特殊字符。比如文件路径里有空格Agent 传过来是my file.txt如果不做转义命令会解析成两个参数。我在escapeShellArg里做了处理但如果你自己实现执行器记得加上这一步。5.4 输出解析失败与编码问题json格式的输出解析失败最常见的原因是命令输出里混入了日志。比如某个工具在 stdout 里先打印一行“Starting...”再输出 JSON。这种情况下JSON.parse会失败。解决办法是在命令里过滤无关输出。比如用2/dev/null把 stderr 丢掉或者用| tail -n 1只取最后一行。如果工具支持--silent或--quiet参数加上这些参数最省事。编码问题在 Windows 上比较常见。如果命令输出是 GBK 编码而插件按 UTF-8 解析中文会乱码。解决办法是在命令里设置输出编码比如chcp 65001切换到 UTF-8或者在执行器里做编码转换。还有一个隐蔽的问题是输出被截断。如果命令输出超过maxBufferexec会报错。我设的是 10MB对于大多数场景够用。如果你的操作输出特别大比如全量测试报告建议把结果写到文件然后读取文件内容而不是直接捕获 stdout。5.5 常见问题速查表问题现象可能原因排查方法解决方案面板无按钮插件未加载查看 Harness 日志检查 main 字段和插件路径Agent 不认识工具agentTool 为 false检查 actions.json设为 true 并重新加载命令找不到工作目录错误打印 cwd 确认配置 ${workspaceFolder}命令卡住交互式提示手动跑命令观察加 --yes 或 -y 参数参数缺失required 未设或 Agent 未传查看错误信息检查 args 定义JSON 解析失败输出混入日志查看原始输出过滤无关输出中文乱码编码不匹配检查系统编码设置 UTF-8 输出输出截断超过 maxBuffer查看错误信息增大 maxBuffer 或写文件这张表里的问题我都实际遇到过解决方案也是验证过的。如果你遇到表里没有的问题建议先看 Harness 日志再看命令的原始输出大多数问题都能定位到。6. 进阶玩法与扩展思路6.1 把操作串联成工作流单个操作解决单点问题但实际工作中往往需要串联多个操作。比如“提交前检查”可能包含 lint、单测、类型检查三步。我的做法是写一个 shell 脚本把三步串起来然后在actions.json里定义一个操作调用这个脚本。#!/bin/bash set -e npm run lint npm test npm run typecheck echo 所有检查通过set -e让脚本在任一步失败时立即退出避免继续执行无意义的步骤。然后在actions.json里定义{ id: pre-commit-check, name: 提交前检查, command: bash scripts/pre-commit-check.sh, cwd: ${workspaceFolder}, timeout: 300000, outputFormat: text }这样面板上就多了一个“提交前检查”按钮Agent 也能调用它。超时给 5 分钟因为单测可能比较慢。为什么不直接在插件里做串联因为 shell 脚本更灵活改起来不用重新编译插件。而且 shell 脚本可以独立运行不依赖 Harness调试起来更方便。6.2 让 Agent 根据上下文选择操作Agent 工具的价值在于可组合性。你可以定义多个细粒度的操作让 Agent 根据上下文自己选择调用哪个。比如定义“获取变更文件列表”“运行指定测试”“获取测试覆盖率”三个工具Agent 在回答“这个改动影响哪些测试”时会先调第一个获取变更文件再调第二个运行相关测试最后调第三个获取覆盖率。这种玩法的关键是工具描述要清晰。description字段要写清楚这个工具做什么、返回什么、什么时候用。Agent 会根据描述判断是否调用。描述写得太模糊Agent 可能不用写得太啰嗦又会浪费 token。我的经验是描述控制在两句话以内第一句说做什么第二句说返回什么。比如“获取当前 Git 仓库的变更文件列表返回文件路径数组”。这样 Agent 一看就懂。6.3 适配不同项目的配置策略这个插件最初是为我自己的项目写的后来想推广到团队其他项目发现每个项目的命令不一样。如果每个项目都复制一份插件维护成本太高。我的解法是把actions.json做成可配置的。插件启动时先读插件自带的默认配置再读项目根目录下的.harness-actions.json如果有就合并覆盖。这样每个项目只需要维护自己的差异部分公共操作放在默认配置里。合并逻辑是按键覆盖。如果项目配置里定义了同id的操作就覆盖默认配置如果定义了新id就追加。这样项目可以覆盖默认命令也可以添加项目特有操作。这个策略的代价是配置来源变多排查问题时需要确认当前生效的是哪份配置。我在面板上加了一个“查看当前配置”的入口点击后展示合并后的完整配置方便排查。6.4 安全边界与权限控制插件能执行任意命令这本身就是一把双刃剑。用得好是效率工具用不好是安全隐患。我在设计时加了几道防线。第一actions.json里的命令是预定义的Agent 不能动态生成命令。Agent 只能选择调用哪个已定义的操作不能自己拼命令。这避免了 Agent 被诱导执行危险命令。第二参数值做了 shell 转义防止命令注入。即使 Agent 传了恶意参数也会被当成普通字符串处理不会破坏命令结构。第三敏感操作可以设agentTool: false只允许人工触发。比如“部署到生产环境”这种操作不应该让 Agent 自己决定执行必须人工确认。第四执行日志完整记录。每次执行都会记录操作 ID、参数、执行时间、结果。出问题时可以追溯也方便审计。这几道防线不是万无一失但能挡住大多数常见风险。如果你要在团队里推广建议再加一层审批机制敏感操作需要二次确认。6.5 后续可以扩展的方向这个插件目前只做了最基础的能力还有不少可以扩展的方向。比如支持操作依赖让多个操作按顺序自动执行支持定时触发比如每天早上自动跑一遍检查支持结果通知执行完成后发消息到团队频道。还有一个有意思的方向是让 Agent 根据执行历史优化操作定义。比如某个操作经常因为超时失败Agent 可以建议调大超时时间某个操作很少用Agent 可以建议从面板上移除。这种自适应优化能进一步提升插件的实用性。不过这些扩展都要在安全边界内做。任何自动修改配置的行为都应该经过人工确认不能完全交给 Agent 决定。7. 一些实操心得这个插件我从有这个想法到跑通大概花了两个周末。中间踩的坑不少但收获也很大。最大的体会是把重复操作固化成工具收益不只是省时间更是把隐性知识显性化。以前这些操作只在我脑子里现在面板上列得清清楚楚新人进来一看就知道项目有哪些常用操作不用再问“那个脚本叫什么”。另一个体会是 Agent 工具的设计和传统工具设计不太一样。传统工具面向人可以容忍一定的复杂度Agent 工具面向 Agent描述要精准输入输出要结构化否则 Agent 理解不了。我一开始把description写得很详细结果 Agent 反而不用后来精简到两句话调用率明显提升。还有一点是关于超时设置。我一开始给所有操作都设 60 秒结果启动服务的操作总是超时。后来把长驻进程单独处理不走超时逻辑问题才解决。所以配置不能一刀切要根据操作特性调整。最后分享一个小技巧如果你不确定某个操作适不适合做成插件先问自己三个问题。这个操作我一周跑几次跑的时候需要记住多少细节别人能不能独立完成如果频率高、细节多、别人做不了那就值得固化。反之偶尔跑一次的操作写个脚本就够了没必要做成插件。
返回列表