12 第10章 插件系统
12.1 插件是什么
Herdr 的核心是一个终端工作空间管理器。但 Herdr 的真正威力来自于它的插件系统——一个让你(和社区)封装、分享和复用工作流的能力。
简单来说,插件 = 可分享的可执行工作流包。
你写了一个工作流:“当 Agent 完成任务时,自动发送 Telegram 通知 + 运行测试 + 提交代码”。把这个工作流打包成插件,分享到 GitHub。其他人一条命令就能安装使用。
12.1.1 插件能做什么
| 能力 | 示例 |
|---|---|
| 响应事件 | Agent 完成时发送通知 |
| 执行动作 | 一键启动完整开发环境 |
| 管理 Pane | 自动创建日志面板并附加 |
| 处理链接 | 点击 GitHub URL 自动 clone 并打开 |
| 运行启动脚本 | 进入项目时自动加载环境变量 |
| 自定义命令 | 添加 herdr my-command |
12.1.2 插件 vs Skill
| 维度 | 插件 | Skill |
|---|---|---|
| 运行位置 | Herdr 进程内 | Agent 的上下文中 |
| 执行方式 | 代码执行 | 作为 prompt 注入 |
| 语言 | JavaScript/TypeScript | Markdown |
| 能操作什么 | Herdr 的所有功能 | 只能通过 Agent 间接操作 |
| 谁安装 | 开发者 | Agent |
| 面向对象 | Herdr 工具 | AI Agent |
一句话总结:Skill 教 Agent 怎么做,插件教 Herdr 怎么做。
12.2 插件 Manifest 结构
每个插件的核心是一个 herdr-plugin.toml 文件——它是插件的”身份证”和”说明书”。
12.2.1 完整结构
# herdr-plugin.toml
[metadata]
name = "my-awesome-plugin"
version = "1.0.0"
description = "一个超棒的 Herdr 插件"
author = "your-name"
license = "MIT"
homepage = "https://github.com/your-name/my-awesome-plugin"
min_herdr_version = "1.0.0"
# ─── 构建命令 ───
[build]
commands = [
"npm install",
"npm run build"
]
# ─── 启动钩子 ───
[[startup]]
event = "workspace.load"
command = "node index.js --init"
[[startup]]
event = "session.attach"
command = "echo 'Welcome back!'"
# ─── 动作 ───
[[actions]]
name = "start-dev"
description = "启动开发环境"
command = "node index.js --action start-dev"
shortcut = "ctrl+d s"
[[actions]]
name = "run-tests"
description = "运行所有测试"
command = "node index.js --action run-tests"
shortcut = "ctrl+d t"
# ─── 事件钩子 ───
[[events]]
trigger = "agent.status_change"
condition = { from = "working", to = "idle" }
command = "node index.js --event agent-done --agent-id ${AGENT_ID}"
[[events]]
trigger = "agent.status_change"
condition = { to = "blocked" }
command = "node index.js --event agent-blocked --agent-id ${AGENT_ID}"
# ─── Pane 定义 ───
[[panes]]
name = "notifications"
title = "🔔 Notifications"
size = 0.15
command = "node index.js --pane notifications"
# ─── 链接处理器 ───
[[link_handlers]]
pattern = "https://github.com/(.+)/(.+)"
command = "node index.js --handle-github --url ${URL}"12.2.2 字段详解
12.2.2.1 [metadata] — 元数据
[metadata]
name = "my-plugin" # 唯一标识符(npm 风格)
version = "1.0.0" # 语义化版本
description = "简短描述" # 显示在插件列表中
author = "your-name"
license = "MIT"
homepage = "https://..." # 文档/源码地址
min_herdr_version = "1.0.0" # 最低兼容版本12.2.2.2 [build] — 构建命令
安装插件时自动执行的命令:
[build]
commands = [
"npm install", # 安装依赖
"npm run build", # 编译 TypeScript
"chmod +x scripts/*.sh" # 设置权限
]12.2.2.3 [[startup]] — 启动钩子
当特定事件发生时自动执行的命令:
[[startup]]
event = "workspace.load" # 工作空间加载时
command = "node index.js --init"
[[startup]]
event = "session.attach" # 会话重连时
command = "echo 'Welcome back!'"
[[startup]]
event = "session.detach" # 会话分离时
command = "node index.js --save-state"12.2.2.4 [[actions]] — 用户可触发的动作
[[actions]]
name = "deploy" # 动作名称(herdr plugin run deploy)
description = "部署到生产环境"
command = "node index.js --action deploy"
shortcut = "ctrl+d d" # 可选快捷键
confirm = true # 执行前需要确认(危险操作)12.2.2.5 [[events]] — 事件钩子
[[events]]
trigger = "agent.status_change" # 触发条件
condition = { to = "blocked" } # 过滤条件
command = "node index.js --on-blocked --agent-id ${AGENT_ID}"可用的事件触发器:
| 触发器 | 条件字段 | 说明 |
|---|---|---|
agent.status_change |
from, to |
Agent 状态变化 |
agent.start |
model, cwd |
Agent 启动 |
agent.exit |
exit_code |
Agent 退出 |
pane.created |
tab, name |
新 pane 创建 |
tab.switched |
from, to |
标签页切换 |
workspace.focus |
— | 工作空间获得焦点 |
12.2.2.6 [[panes]] — Pane 定义
插件可以定义自己的 pane,自动在工作空间中创建:
[[panes]]
name = "notifications"
title = "🔔 Notifications" # 标题栏显示
size = 0.15 # 占比(15%)
command = "node index.js --pane notifications"
position = "bottom" # bottom | top | left | right12.2.2.7 [[link_handlers]] — 链接处理器
当用户在终端中点击(或通过命令打开)匹配的 URL 时触发:
[[link_handlers]]
pattern = "https://github.com/(.+)/(.+)"
command = "node index.js --handle-github --url ${URL} --owner ${1} --repo ${2}"捕获组 ${1}、${2} 对应正则的分组。
12.3 插件管理命令
12.3.1 安装插件
# 从 GitHub 安装
herdr plugin install owner/repo
# 从 GitHub 子目录安装
herdr plugin install owner/repo/subdir
# 从本地路径安装
herdr plugin install ./my-plugin
# 从 Git URL 安装
herdr plugin install https://github.com/owner/repo.git
# 指定版本
herdr plugin install owner/repo --version 1.2.0
# 全局安装(所有工作空间可用)
herdr plugin install owner/repo --global12.3.2 链接本地插件(开发模式)
开发插件时,每次修改都重新安装太麻烦。用 link 模式:
# 链接本地插件(修改即时生效)
herdr plugin link /path/to/my-plugin
# 取消链接
herdr plugin unlink my-plugin# 1. 创建插件项目
mkdir my-plugin && cd my-plugin
# 2. 编写 herdr-plugin.toml 和 index.js
# 3. 链接到 Herdr
herdr plugin link .
# 4. 修改代码,在 Herdr 中立即测试
# 5. 满意后发布到 GitHub
git init && git remote add origin ...
git push -u origin main
# 6. 其他人安装
herdr plugin install your-name/my-plugin12.3.3 其他管理命令
# 列出已安装的插件
herdr plugin list
# my-plugin | 1.0.0 | linked | ~/projects/my-plugin
# notifications | 0.3.2 | installed| ~/.herdr/plugins/notifications
# auto-deploy | 2.1.0 | installed| ~/.herdr/plugins/auto-deploy
# 查看插件详情
herdr plugin info my-plugin
# 更新插件
herdr plugin update my-plugin
herdr plugin update --all # 更新所有
# 禁用/启用插件
herdr plugin disable my-plugin # 暂时禁用,不卸载
herdr plugin enable my-plugin # 重新启用
# 卸载插件
herdr plugin uninstall my-plugin
# 运行插件的动作
herdr plugin run my-plugin start-dev
# 查看插件日志
herdr plugin logs my-plugin12.4 编写第一个插件:从零开始
让我们从零开始编写一个完整的 Herdr 插件。
12.4.1 目标
创建一个名为 dev-setup 的插件,功能包括:
- 注册一个
start动作——一键启动开发环境 - Agent 完成时自动运行测试
- Agent 阻塞时发出提醒
- 添加一个状态面板
12.4.2 步骤一:创建项目结构
mkdir herdr-plugin-dev-setup && cd herdr-plugin-dev-setup
# 项目结构
.
├── herdr-plugin.toml
├── package.json
├── index.js
└── README.md12.4.3 步骤二:编写 herdr-plugin.toml
# herdr-plugin.toml
[metadata]
name = "dev-setup"
version = "1.0.0"
description = "一键开发环境 + Agent 自动测试"
author = "wuzhiguo"
license = "MIT"
min_herdr_version = "1.0.0"
[build]
commands = ["npm install"]
# ─── 动作 ───
[[actions]]
name = "start"
description = "🚀 启动开发环境(服务器 + Agent + 日志)"
command = "node index.js --action start"
[[actions]]
name = "test"
description = "🧪 运行测试"
command = "node index.js --action test"
shortcut = "ctrl+d t"
[[actions]]
name = "deploy"
description = "📦 部署到 staging"
command = "node index.js --action deploy"
confirm = true
# ─── 事件钩子 ───
[[events]]
trigger = "agent.status_change"
condition = { from = "working", to = "idle" }
command = "node index.js --event agent-done --agent-id ${AGENT_ID}"
[[events]]
trigger = "agent.status_change"
condition = { to = "blocked" }
command = "node index.js --event agent-blocked --agent-id ${AGENT_ID}"
# ─── 启动钩子 ───
[[startup]]
event = "workspace.load"
command = "node index.js --init"
# ─── Pane ───
[[panes]]
name = "status"
title = "📊 Status"
size = 0.12
position = "bottom"
command = "node index.js --pane status"
# ─── 链接处理器 ───
[[link_handlers]]
pattern = "https://github.com/(.+)/(.+)/issues/(\\d+)"
command = "node index.js --handle-issue --url ${URL} --owner ${1} --repo ${2} --issue ${3}"12.4.4 步骤三:编写 package.json
{
"name": "herdr-plugin-dev-setup",
"version": "1.0.0",
"description": "Herdr 插件:一键开发环境 + Agent 自动测试",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"child_process": "^1.0.0"
}
}12.4.5 步骤四:编写 index.js
#!/usr/bin/env node
const { execSync, spawn } = require("child_process");
const fs = require("fs");
const path = require("path");
// ─── 环境变量 ───
// Herdr 提供的环境变量
const HERDR_BIN_PATH = process.env.HERDR_BIN_PATH || "herdr";
const HERDR_WORKSPACE = process.env.HERDR_WORKSPACE || "";
const HERDR_SESSION = process.env.HERDR_SESSION || "";
const HERDR_TAB = process.env.HERDR_TAB || "";
const HERDR_PANE_ID = process.env.HERDR_PANE_ID || "";
// 解析命令行参数
const args = process.argv.slice(2);
function getArg(name) {
const idx = args.indexOf(`--${name}`);
return idx >= 0 ? args[idx + 1] : null;
}
// ─── Herdr CLI 封装 ───
function herdr(...cmdArgs) {
const cmd = `${HERDR_BIN_PATH} ${cmdArgs.join(" ")}`;
try {
return execSync(cmd, { encoding: "utf-8", timeout: 10000 });
} catch (e) {
console.error(`Herdr command failed: ${cmd}`, e.message);
return null;
}
}
// ─── 初始化 ───
function init() {
console.log("🎯 dev-setup 插件已加载");
console.log(` 工作空间: ${HERDR_WORKSPACE}`);
console.log(` 会话: ${HERDR_SESSION}`);
}
// ─── 启动开发环境 ───
function startDev() {
console.log("🚀 启动开发环境...\n");
// 创建标签页
herdr("tab", "new", "server");
herdr("tab", "new", "agent");
herdr("tab", "new", "logs");
// 在 server 标签页启动开发服务器
const serverPane = herdr("tab", "goto", "server");
herdr("pane", "run", "current", "npm run dev");
// 在 agent 标签页启动 Claude Code
herdr("tab", "goto", "agent");
herdr("pane", "run", "current", "claude");
// 在 logs 标签页启动日志
herdr("tab", "goto", "logs");
herdr("pane", "run", "current", "tail -f logs/*.log");
// 回到 main 标签页
herdr("tab", "goto", "main");
console.log("✅ 开发环境已启动!");
console.log(" - server: npm run dev");
console.log(" - agent: claude");
console.log(" - logs: tail -f");
}
// ─── 运行测试 ───
function runTests() {
console.log("🧪 运行测试...\n");
const output = execSync("npm test 2>&1", { encoding: "utf-8" });
console.log(output);
const passed = output.includes("passing");
if (passed) {
console.log("✅ 测试通过!");
} else {
console.log("❌ 测试失败!");
process.exit(1);
}
}
// ─── 部署 ───
function deploy() {
console.log("📦 部署到 staging...\n");
// 先运行测试
try {
execSync("npm test", { encoding: "utf-8", stdio: "inherit" });
} catch (e) {
console.error("❌ 测试未通过,取消部署");
process.exit(1);
}
// 构建并部署
console.log("🔨 构建中...");
execSync("npm run build", { stdio: "inherit" });
console.log("📤 部署中...");
execSync("npx vercel --prod --yes", { stdio: "inherit" });
console.log("✅ 部署完成!");
}
// ─── Agent 事件处理 ───
function onAgentDone(agentId) {
console.log(`🎉 Agent ${agentId} 完成了任务!`);
// 自动运行测试
console.log("🧪 自动运行测试...");
const result = execSync("npm test 2>&1", { encoding: "utf-8" });
if (result.includes("passing")) {
console.log("✅ 测试通过!");
// 发送系统通知
if (process.platform === "darwin") {
execSync(
`osascript -e 'display notification "Agent ${agentId} 完成,测试通过" with title "Herdr" sound name "Glass"'`
);
}
} else {
console.log("❌ 测试失败!");
console.log(result);
// 反馈给 Agent
herdr("agent", "prompt", agentId, "测试失败了,请检查并修复");
}
}
function onAgentBlocked(agentId) {
console.log(`⚠️ Agent ${agentId} 被阻塞了!`);
// 读取 Agent 的输出,看看它需要什么
const output = herdr("agent", "read", agentId);
console.log("Agent 输出:", output);
// 发送系统通知
if (process.platform === "darwin") {
execSync(
`osascript -e 'display notification "Agent ${agentId} 需要你的注意" with title "Herdr" sound name "Basso"'`
);
}
}
// ─── 状态面板 ───
function statusPane() {
// 清屏并显示状态
setInterval(() => {
console.log("\x1b[2J\x1b[H"); // 清屏
const time = new Date().toLocaleTimeString("zh-CN");
// 获取 Agent 列表
const agents = herdr("agent", "list");
const tabs = herdr("tab", "list");
console.log("┌────────────────────────────────────┐");
console.log("│ 📊 Herdr 状态面板 │");
console.log("├────────────────────────────────────┤");
console.log(`│ 时间: ${time} │`);
console.log(`│ 标签页: ${tabs ? tabs.trim() : "N/A"} │`);
console.log(`│ Agents: │`);
console.log(`│ ${agents ? agents.trim() : "无"} │`);
console.log("└────────────────────────────────────┘");
}, 2000);
}
// ─── GitHub Issue 链接处理 ───
function handleGitHubIssue(url, owner, repo, issue) {
console.log(`📌 GitHub Issue: ${owner}/${repo}#${issue}`);
console.log(` URL: ${url}`);
// 自动获取 issue 详情
const issueData = execSync(
`gh issue view ${issue} --repo ${owner}/${repo} --json title,body,labels`,
{ encoding: "utf-8" }
).trim();
const issueObj = JSON.parse(issueData);
console.log(` 标题: ${issueObj.title}`);
console.log(` 标签: ${issueObj.labels.map((l) => l.name).join(", ")}`);
// 询问是否创建 worktree 并用 Agent 处理
console.log("\n是否要用 Agent 处理这个 issue?");
}
// ─── 主入口 ───
function main() {
if (args.includes("--init")) {
init();
return;
}
if (args.includes("--pane") && args.includes("status")) {
statusPane();
return;
}
const action = getArg("action");
if (action === "start") {
startDev();
} else if (action === "test") {
runTests();
} else if (action === "deploy") {
deploy();
}
const event = getArg("event");
if (event === "agent-done") {
onAgentDone(getArg("agent-id"));
} else if (event === "agent-blocked") {
onAgentBlocked(getArg("agent-id"));
}
if (args.includes("--handle-issue")) {
handleGitHubIssue(
getArg("url"),
getArg("owner"),
getArg("repo"),
getArg("issue")
);
}
}
main();12.4.6 步骤五:测试插件
# 链接到 Herdr
herdr plugin link .
# 验证安装
herdr plugin list
# dev-setup | 1.0.0 | linked | ~/projects/herdr-plugin-dev-setup
# 测试动作
herdr plugin run dev-setup start
herdr plugin run dev-setup test
# 查看日志
herdr plugin logs dev-setup12.4.7 步骤六:发布
# 创建 Git 仓库
git init
git add -A
git commit -m "feat: initial release of dev-setup plugin"
git remote add origin https://github.com/your-name/herdr-plugin-dev-setup.git
git push -u origin main
# 其他人安装
herdr plugin install your-name/herdr-plugin-dev-setup12.5 信任与安全模型
12.5.1 信任问题
插件是可执行代码——它能做任何 Node.js 程序能做的事:读写文件、发网络请求、执行系统命令。所以你安装的插件必须是可信的。
12.5.2 Herdr 的安全措施
| 措施 | 说明 |
|---|---|
| 来源验证 | GitHub 安装时显示仓库信息,让你确认 |
| 权限声明 | 插件需要在 manifest 中声明所需权限 |
| 沙箱执行 | 插件在受限的环境中运行(计划中) |
| 确认机制 | 危险操作需要 confirm = true |
| 日志记录 | 所有插件操作记录在 ~/.herdr/logs/plugins/ |
12.5.3 权限声明(计划中)
未来的版本中,插件需要在 manifest 中声明权限:
[permissions]
filesystem = ["read:project", "write:project"] # 只能读写项目目录
network = ["localhost", "api.github.com"] # 只能访问这些域名
process = ["execute"] # 可以执行子进程
environment = ["HERDR_*", "PATH"] # 可访问的环境变量12.5.4 安全最佳实践
# 1. 安装前审查源码
herdr plugin inspect owner/repo
# 显示 manifest + 主要源文件
# 2. 使用 --dry-run 测试
herdr plugin install owner/repo --dry-run
# 模拟安装,不实际执行
# 3. 限制权限(未来)
herdr plugin install owner/repo --no-filesystem --no-network
# 4. 在隔离环境中测试
# 使用 Docker 容器或虚拟机测试不信任的插件插件可以执行任意代码。在安装社区插件前: 1. 查看作者的 GitHub profile 2. 阅读 manifest 和主要源文件 3. 检查是否有已知安全问题 4. 先在测试环境中运行
12.6 插件运行时环境变量
Herdr 在执行插件命令时,会注入以下环境变量:
| 变量 | 说明 | 示例值 |
|---|---|---|
HERDR_BIN_PATH |
Herdr CLI 的路径 | /usr/local/bin/herdr |
HERDR_VERSION |
Herdr 版本号 | 1.5.0 |
HERDR_WORKSPACE |
当前工作空间路径 | ~/projects/my-app |
HERDR_SESSION |
当前会话名 | main |
HERDR_SESSION_ID |
会话唯一 ID | sess-a1b2c3d4 |
HERDR_TAB |
当前标签页名 | agent |
HERDR_TAB_ID |
标签页唯一 ID | tab-e5f6g7h8 |
HERDR_PANE_ID |
当前 pane ID | pane-i9j0k1l2 |
HERDR_PANE_CWD |
当前 pane 工作目录 | ~/projects/my-app/src |
HERDR_PLUGIN_DIR |
插件安装目录 | ~/.herdr/plugins/dev-setup |
HERDR_PLUGIN_NAME |
插件名称 | dev-setup |
HERDR_PLUGIN_VERSION |
插件版本 | 1.0.0 |
HERDR_AGENT_ID |
触发事件的 Agent ID(仅事件钩子中) | agent-m3n4o5p6 |
HERDR_AGENT_MODEL |
Agent 使用的模型 | claude-sonnet-4.5 |
HERDR_EVENT_TYPE |
触发的事件类型 | agent.status_change |
12.6.1 在代码中使用
// 调用 Herdr CLI
const herdrPath = process.env.HERDR_BIN_PATH;
const result = execSync(`${herdrPath} agent list`, { encoding: "utf-8" });
// 获取当前工作空间
const workspace = process.env.HERDR_WORKSPACE;
// 识别事件上下文
const eventType = process.env.HERDR_EVENT_TYPE;
const agentId = process.env.HERDR_AGENT_ID;
if (eventType === "agent.status_change") {
console.log(`Agent ${agentId} 状态变化`);
}12.7 Marketplace(未来)
目前插件通过 GitHub 直接安装。未来 Herdr 计划推出官方 Marketplace:
# 浏览插件市场
herdr plugin search "notification"
herdr plugin search "auto-deploy"
# 查看热门插件
herdr plugin trending
# 查看分类
herdr plugin categories
# > 通知类 (12)
# > 部署类 (8)
# > 开发环境 (23)
# > 测试类 (15)
# > 监控类 (7)12.7.1 示例插件 Cookbook
社区已有一些优秀插件可供参考:
# 安装示例插件合集
herdr plugin install ogulcancelik/herdr-plugin-examples| 插件 | 功能 | 复杂度 |
|---|---|---|
auto-notify |
Agent 完成时系统通知 | ⭐ 简单 |
git-flow |
Git 工作流自动化 | ⭐⭐ 中等 |
test-runner |
自动运行测试并报告 | ⭐⭐ 中等 |
deploy-bot |
CI/CD 部署集成 | ⭐⭐⭐ 复杂 |
metrics-dashboard |
Agent 性能指标面板 | ⭐⭐⭐ 复杂 |
team-sync |
团队协作通知 | ⭐⭐⭐ 复杂 |
12.8 实战案例:Telegram 通知插件
让我们编写一个实用的插件——当 Agent 完成任务时发送 Telegram 通知。这样你就可以离开电脑,在手机上收到通知。
12.8.1 项目结构
mkdir herdr-plugin-telegram && cd herdr-plugin-telegramherdr-plugin-telegram/
├── herdr-plugin.toml
├── package.json
├── index.js
└── README.md
12.8.2 herdr-plugin.toml
[metadata]
name = "telegram-notify"
version = "1.0.0"
description = "Agent 状态变化时发送 Telegram 通知"
author = "wuzhiguo"
license = "MIT"
min_herdr_version = "1.0.0"
[build]
commands = ["npm install"]
# ─── 配置 ───
[config]
bot_token = { type = "string", required = true, description = "Telegram Bot Token" }
chat_id = { type = "string", required = true, description = "Telegram Chat ID" }
notify_on = { type = "array", default = ["idle", "blocked", "error"], description = "哪些状态触发通知" }
# ─── 事件钩子 ───
[[events]]
trigger = "agent.status_change"
condition = { to = "idle" }
command = "node index.js --event done --agent-id ${AGENT_ID}"
[[events]]
trigger = "agent.status_change"
condition = { to = "blocked" }
command = "node index.js --event blocked --agent-id ${AGENT_ID}"
[[events]]
trigger = "agent.status_change"
condition = { to = "error" }
command = "node index.js --event error --agent-id ${AGENT_ID}"
# ─── 动作 ───
[[actions]]
name = "test"
description = "发送测试通知"
command = "node index.js --action test"
[[actions]]
name = "config"
description = "配置 Bot Token 和 Chat ID"
command = "node index.js --action config"12.8.3 index.js
#!/usr/bin/env node
const https = require("https");
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const HERDR_BIN_PATH = process.env.HERDR_BIN_PATH || "herdr";
const PLUGIN_DIR = process.env.HERDR_PLUGIN_DIR || __dirname;
// ─── 配置管理 ───
function loadConfig() {
const configPath = path.join(PLUGIN_DIR, ".config.json");
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
}
return null;
}
function saveConfig(config) {
const configPath = path.join(PLUGIN_DIR, ".config.json");
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
}
// ─── Telegram 发送 ───
function sendTelegram(message) {
const config = loadConfig();
if (!config) {
console.error("❌ 未配置 Telegram Bot Token");
console.log("运行: herdr plugin run telegram-notify config");
return;
}
const data = JSON.stringify({
chat_id: config.chat_id,
text: message,
parse_mode: "Markdown",
});
const options = {
hostname: "api.telegram.org",
path: `/bot${config.bot_token}/sendMessage`,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": data.length,
},
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
if (res.statusCode === 200) {
resolve(JSON.parse(body));
} else {
reject(new Error(`Telegram API error: ${res.statusCode}`));
}
});
});
req.on("error", reject);
req.write(data);
req.end();
});
}
// ─── 获取 Agent 信息 ───
function getAgentInfo(agentId) {
try {
const output = execSync(`${HERDR_BIN_PATH} agent status ${agentId}`, {
encoding: "utf-8",
});
return JSON.parse(output);
} catch {
return { id: agentId, model: "unknown", duration: "unknown" };
}
}
function getAgentOutput(agentId, lines = 20) {
try {
return execSync(
`${HERDR_BIN_PATH} agent read ${agentId} --lines ${lines}`,
{ encoding: "utf-8" }
).trim();
} catch {
return "(无法读取输出)";
}
}
// ─── 事件处理 ───
async function onAgentDone(agentId) {
const info = getAgentInfo(agentId);
const output = getAgentOutput(agentId, 10);
const message = [
`✅ *Agent 完成了任务*`,
``,
`🤖 模型: \`${info.model}\``,
`⏱ 耗时: ${info.duration || "未知"}`,
`📁 修改文件: ${info.filesChanged || "未知"}`,
``,
`*最后输出:*`,
"```",
output.split("\n").slice(-10).join("\n"),
"```",
].join("\n");
try {
await sendTelegram(message);
console.log("✅ Telegram 通知已发送");
} catch (e) {
console.error("❌ 发送失败:", e.message);
}
}
async function onAgentBlocked(agentId) {
const info = getAgentInfo(agentId);
const output = getAgentOutput(agentId, 15);
const message = [
`⚠️ *Agent 需要你的注意*`,
``,
`🤖 模型: \`${info.model}\``,
`🔴 状态: blocked`,
``,
`*Agent 的消息:*`,
"```",
output.split("\n").slice(-15).join("\n"),
"```",
``,
`请回到 Herdr 查看详情`,
].join("\n");
try {
await sendTelegram(message);
console.log("✅ Telegram 通知已发送");
} catch (e) {
console.error("❌ 发送失败:", e.message);
}
}
async function onAgentError(agentId) {
const message = [
`❌ *Agent 发生错误*`,
``,
`Agent ID: \`${agentId}\``,
`时间: ${new Date().toLocaleString("zh-CN")}`,
``,
`请检查 Herdr 日志获取详情`,
].join("\n");
try {
await sendTelegram(message);
} catch (e) {
console.error("❌ 发送失败:", e.message);
}
}
// ─── 配置命令 ───
function interactiveConfig() {
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log("🔧 配置 Telegram 通知\n");
rl.question("Bot Token: ", (botToken) => {
rl.question("Chat ID: ", (chatId) => {
saveConfig({ bot_token: botToken.trim(), chat_id: chatId.trim() });
console.log("\n✅ 配置已保存!");
rl.close();
});
});
}
// ─── 测试命令 ───
async function testNotification() {
console.log("📤 发送测试通知...");
try {
await sendTelegram(
"🧪 *Herdr Telegram 插件测试*\n\n如果你看到这条消息,说明配置正确!"
);
console.log("✅ 测试通知已发送!");
} catch (e) {
console.error("❌ 发送失败:", e.message);
console.log("\n请检查配置: herdr plugin run telegram-notify config");
}
}
// ─── 主入口 ───
function main() {
const args = process.argv.slice(2);
const event = args[args.indexOf("--event") + 1];
const action = args[args.indexOf("--action") + 1];
const agentId = args[args.indexOf("--agent-id") + 1];
if (action === "config") {
interactiveConfig();
} else if (action === "test") {
testNotification();
} else if (event === "done") {
onAgentDone(agentId);
} else if (event === "blocked") {
onAgentBlocked(agentId);
} else if (event === "error") {
onAgentError(agentId);
}
}
main();12.8.4 安装和配置
# 1. 安装插件
herdr plugin install your-name/herdr-plugin-telegram
# 2. 配置(交互式)
herdr plugin run telegram-notify config
# Bot Token: 123456:ABC-DEF...
# Chat ID: 987654321
# 3. 测试
herdr plugin run telegram-notify test
# ✅ 测试通知已发送!12.8.5 获取 Telegram Bot Token
- 在 Telegram 中搜索
@BotFather - 发送
/newbot - 按提示创建 bot,获取 token
- 获取 Chat ID:给你的 bot 发一条消息,然后访问:
https://api.telegram.org/bot<TOKEN>/getUpdates - 在返回的 JSON 中找到
chat.id
12.8.6 使用效果
当你让 Agent 跑一个长任务,然后去喝咖啡:
📱 手机收到 Telegram 消息:
✅ Agent 完成了任务
🤖 模型: claude-sonnet-4.5
⏱ 耗时: 12m 34s
📁 修改文件: 8
最后输出:
> ✅ 所有功能已实现
> 创建的文件:
> - src/models/User.js
> - src/controllers/authController.js
> ...
如果 Agent 被阻塞:
📱 手机收到 Telegram 消息:
⚠️ Agent 需要你的注意
🤖 模型: claude-sonnet-4.5
🔴 状态: blocked
Agent 的消息:
> 我需要确认:数据库使用 PostgreSQL 还是 MySQL?
> 请回复你的选择
请回到 Herdr 查看详情
12.9 小结
Herdr 的插件系统是它最具扩展性的能力。本章核心:
- 插件 = 可分享的工作流包——封装你的最佳实践
- Manifest 驱动——一个
herdr-plugin.toml定义一切 - 六大能力——actions、events、startup hooks、panes、link handlers、build
- 安全第一——只安装信任的插件,审查源码
- 环境变量——通过
HERDR_*环境变量与 Herdr 交互 - 开发流程——link 模式实时测试,GitHub 发布,一条命令安装
到这里,我们完成了第三部分「实战工作流篇」的全部内容。你已经掌握了从单 Agent 到多 Agent、从本地到远程、从内置功能到插件扩展的完整 Herdr 工作流。
下一部分「自动化与 API 篇」,我们将深入 Herdr 的自动化能力和编程接口。