Agent Zero 插件开发实战:从 plugin.yaml 到 Plugin Hub 的全栈插件创建指南
【免费下载链接】agent-zeroAgent Zero AI framework项目地址: https://gitcode.com/GitHub_Trending/ag/agent-zero
导读
本文以 Agent Zero 内置技能 a0-create-plugin/AGENTS.md 及其核心载体 SKILL.md 为主干,系统讲解在 Agent Zero 中创建、扩展、修改插件的完整工作流。读完本文,你将掌握插件清单plugin.yaml的字段语义、usr/plugins/目录布局规范、前端 Store Gate 与 A0 通知体系、后端 AgentContext 与插件配置读写、execute.py与hooks.py的生命周期用法,以及社区插件从 GitHub 仓库到 Plugin Index 提交的完整发布流程。文中所有结论均可在当前仓库的 插件契约、插件运行时 与内置插件实例中逐一验证。
一、插件体系概览:谁是插件,插件放在哪里
Agent Zero 把"可扩展能力"统一收敛为**插件(Plugin)**这一形态:UI 钩子、API 处理器、生命周期扩展、设置面板、工具、提示词模板、模型提供商覆盖,全部通过插件承载。围绕插件的开发、审查、贡献、管理,仓库内建了四个配套技能:
- a0-create-plugin:创建、扩展、修改插件(本文主线);
- a0-review-plugin:审查插件;
- a0-contribute-plugin:社区插件全流程贡献(含 git 操作);
- a0-manage-plugin:插件管理。
1.1 两个插件根目录,一条硬性红线
仓库中存在两个插件根目录,用途严格分离:
| 目录 | 用途 | 说明 |
|---|---|---|
plugins/ | 核心系统插件 | 仅存放随框架发布的内置插件,目录名与plugin.yaml的name均以下划线_开头(如_chat_naming、_a0_connector),避免与社区插件冲突 |
usr/plugins/ | 用户自定义插件 | 所有新建的自定义插件必须放在这里 |
这是 a0-create-plugin 技能的第一条硬性约束:新插件一律创建于usr/plugins/<plugin_name>/。从源码看,插件的发现顺序也是"用户优先":get_plugin_roots 按usr/plugins→plugins的顺序返回根路径,find_plugin_dir 在 ID 冲突时同样优先解析usr/plugins下的版本,即用户插件可以覆盖同名内置插件。
1.2 一条铁律:不要留下"永生"的副作用
技能文档明确要求:插件被删除时,不应残留未托管的服务、符号链接或插件自有路径之外的文件。除非用户显式要求且插件文档写明了清理方式。这与 插件契约 中"插件删除或禁用不得遗留未管理副作用"的约定完全一致。同样被禁止的还有:硬编码密钥、绕过认证、产生持久化的非托管副作用。
二、第一步先问清:本地插件还是社区插件?
动手前必须向用户确认一个关键问题:
这个插件是仅本地使用(留在你的 Agent Zero 安装内),还是社区插件(发布到 Plugin Index 供他人安装)?
- 本地插件:直接在
usr/plugins/<plugin_name>/创建,无需 Git 仓库,直接进入清单编写环节; - 社区插件:插件必须托管在独立的 GitHub 仓库(运行时清单
plugin.yaml位于仓库根目录),随后还要向 Plugin Index 提交一个单独的索引 PR,需要分两步引导用户完成。
这一步决定了后续的目录结构、清单字段与发布流程,务必先确认再动手。
三、插件清单 plugin.yaml:没有它就不会被发现
每个插件目录必须包含plugin.yaml,否则运行时无法发现该插件。基础模板如下:
name: my_plugin # 社区插件必填;必须与目录名一致(^[a-z0-9_]+$) title: My Plugin description: What this plugin does. version: 1.0.0 settings_sections: - agent per_project_config: false per_agent_config: false各字段语义与取值约束:
name:仅允许小写字母、数字、下划线(^[a-z0-9_]+$)。提交 Plugin Index 时 CI 会强制校验,必须与索引目录名完全一致;title/description/version:插件的展示名、描述与版本号;settings_sections:控制"设置"界面中哪些标签页显示本插件的子区块。合法值为agent、external、mcp、developer、backup;设为[]表示不显示子区块;per_project_config/per_agent_config:置为true可启用按项目/按 Agent 作用域的精细化配置切换;always_enabled:仅供框架内置插件使用,锁定插件永久开启并禁用 UI 开关。
运行时的PluginMetadata模型在 helpers/plugins.py 中与上述字段一一对应;开关状态由.toggle-1/.toggle-0标记文件驱动(见 determined_toggle_from_paths)。插件的激活默认是 ON——只要没有禁用标记就处于启用状态,这一点与 plugins/AGENTS.md 契约一致。
仓库内的真实示例可对照两个内置插件:_chat_naming/plugin.yaml(settings_sections: [agent]、per_project_config: true、per_agent_config: true、always_enabled: true)与 _a0_connector/plugin.yaml(settings_sections: [external, developer])。
3.1 默认配置与解析顺序
插件默认值放在default_config.yaml;运行时用户设置则落在usr/下。根据插件契约,配置解析顺序为:项目/Agent 档案(project/profile)→ 项目(project)→ 用户/档案(user/profile)→ 用户插件配置 → 打包的default_config.yaml。这与 get_plugin_config 的查找链一致:先按作用域找config.json,找不到再回落default_config.yaml,且支持通过get_plugin_config钩子二次加工。
四、目录布局:插件该长成什么样
usr/plugins/<name>/的推荐布局如下:
usr/plugins/<name>/ plugin.yaml # 必需:清单 execute.py # 可选:用户手动触发的安装/维护脚本 hooks.py # 可选:框架运行时钩子函数 default_config.yaml # 可选:默认设置回退 README.md # 本地可选;社区插件强烈建议 LICENSE # 本地可选(存在时显示在插件列表中);Plugin Index 提交时仓库根目录必须 agents/ <profile>/agent.yaml # 可选:随插件分发的 Agent 档案 api/ # API 处理器(ApiHandler 基类) tools/ # 工具子类 helpers/ # 共享 Python 逻辑 prompts/ # 提示词模板 conf/ model_providers.yaml # 可选:新增或覆盖模型提供商 extensions/ python/<extension_point>/ # 具名 Python 生命周期扩展 python/_functions/<module>/<qualname>/<start|end>/ # 隐式 @extensible 钩子 webui/<point>/ # HTML/JS 钩子扩展 webui/ config.html # 可选:插件设置 UI my-modal.html # 完整插件页面 my-store.js # Alpine 状态 store4.1 扩展点目录的两代布局(重要)
- 具名扩展点:
extensions/python/<extension_point>/,目录内放继承Extension基类的 Python 类; - 隐式扩展点:
extensions/python/_functions/<module>/<qualname>/<start|end>/,这是@extensible装饰器自动生成的扩展点路径。装饰器按func.__module__按.拆段、func.__qualname__按嵌套层级拆段(剔除<locals>),组合出 start/end 两个目录,见 helpers/extension.py。
严禁使用已废弃的拍平式旧布局extensions/python/<module>_<qualname>_<start|end>/——当前运行时只解析深层的_functions/<module>/<qualname>/<start|end>形式。
4.2 插件本地 Python 导入规则
插件内部的 Python 模块必须使用全限定路径usr.plugins.<plugin_name>...导入:
# 正确 from usr.plugins.my_plugin.helpers.runtime import do_work import usr.plugins.my_plugin.helpers.state as state# 避免 sys.path.insert(0, ...) from helpers.runtime import do_work from plugins.my_plugin.helpers.runtime import do_work这样做的好处是插件可以保留常规的helpers/目录名而无需改名,同时规避sys.path污染和符号链接安装。契约规定:内置插件(来自plugins/树)可用plugins.<plugin_name>...导入,用户插件(usr/plugins/)必须走usr.plugins.<plugin_name>...。
五、强制前端模式:Store Gate、独立 Store 与 A0 通知
5.1 Store Gate 模板(每个组件必用)
为避免竞态条件和 undefined 报错,每个组件必须使用如下包装:
<div x-data> <template x-if="$store.myPluginStore"> <div x-init="$store.myPluginStore.onOpen()" x-destroy="$store.myPluginStore.cleanup()"> <!-- Content goes here --> </div> </template> </div>x-init在进入时调用onOpen(),x-destroy在离开时调用cleanup(),确保状态生命周期可控。
5.2 独立 Store 模块
Store 逻辑必须放在独立的.js文件中,禁止在 HTML 内使用alpine:init监听器:
// webui/my-store.js import { createStore } from "/js/AlpineStore.js"; export const store = createStore("myPluginStore", { status: 'idle', init() { ... }, onOpen() { ... }, cleanup() { ... } });随后在 HTML 的<head>中以模块方式引入:
<head> <script type="module" src="/plugins/<plugin_name>/webui/my-store.js"></script> </head>5.3 用户反馈:只用 A0 通知系统
不要用内联提示框(比如把红色<div>绑定到store.error)展示错误或成功信息,必须走项目通知系统,让 toast 与历史保持一致:
- 错误:
toastFrontendError(message, "My Plugin")(或$store.notificationStore.frontendError(...)); - 成功:
toastFrontendSuccess(message, "My Plugin"); - 警告/信息:
toastFrontendWarning、toastFrontendInfo,来自/components/notifications/notification-store.js。
在 Store 中 import 并调用即可,不要在模板中渲染专门的错误/成功区块。完整 API 见 通知开发文档。这也与 插件契约 中"插件 UI 必须使用 A0 通知系统而非内联成功/错误框"的要求呼应。
六、插件设置:webui/config.html 与作用域持久化
如果插件需要用户可配置项,添加webui/config.html即可——系统会自动检测该文件,并按plugin.yaml的settings_sections在对应标签页显示设置按钮。
6.1 设置弹窗契约
弹窗提供 Project + Agent 档案上下文选择器;插件设置包装器通过$store.pluginSettingsPrototype实例化局部弹窗上下文。在config.html中,插件字段绑定到config.*,弹窗级状态与动作使用context.*:
<html> <head> <title>My Plugin Settings</title> <script type="module"> import { store } from "/components/plugins/plugin-settings-store.js"; </script> </head> <body> <div x-data> <input x-model="config.my_key" /> <input type="checkbox" x-model="config.feature_enabled" /> </div> </body> </html>点击弹窗的 Save 按钮后,config会被持久化为config.json,写入正确的作用域(项目/Agent/全局)。这正是 save_plugin_config 依据project_name、agent_profile计算写入路径的落盘逻辑。
6.2 侧边栏入口点
如果要在侧边栏放置入口按钮:
- 扩展点:
sidebar-quick-actions-main-start - 样式类:
class="config-button" - 位置:
x-move-after=".config-button#dashboard" - 动作:
@click="openModal('/plugins/<plugin_name>/webui/my-modal.html')"
6.3 后端读取与写入插件设置
from helpers.plugins import get_plugin_config, save_plugin_config # 运行时读取(有运行中的 agent,自动从上下文解析项目/档案) settings = get_plugin_config("my-plugin", agent=agent) or {} # 显式写入目标(project/profile 作用域) save_plugin_config( "my-plugin", project_name="my-project", agent_profile="default", settings=settings, )七、后端 API 与上下文:主动发消息的正确姿势
7.1 导入路径
- 正确:
from agent import AgentContext, AgentContextType - 正确:
from initialize import initialize_agent - 正确(插件本地模块):
from usr.plugins.<name>.helpers.module import ... - 避免:
sys.path黑魔法、依赖符号链接的from plugins.<name>...导入
AgentContext定义在 agent.py:它是运行中 Agent 会话的运行时上下文,通过AgentContext.use(context_id)取用、AgentContext.current()获取当前上下文,AgentContextType枚举区分user/task/background三种会话类型(agent.py)。
7.2 主动发送消息
from agent import AgentContext from helpers.messages import UserMessage context = AgentContext.use(context_id) task = context.communicate(UserMessage("Message text")) response = await task.result()communicate()返回异步任务,await task.result()等待处理结果——这是插件主动向 Agent 投递消息的标准调用链。
八、execute.py:用户手动触发的操作脚本
如果插件需要用户触发的安装、维护、修复类操作,在插件根目录添加execute.py。典型用途:
- 安装依赖、下载模型/资源;
- 插件拷贝就位后的后置安装步骤;
- 重建缓存、索引或生成文件;
- 执行迁移、修复步骤或同步任务;
- 仅在用户显式要求时执行的周期性维护。
关键原则:execute.py只做用户发起的工作;框架内部逻辑或应随插件生命周期自动发生的行为,应放入hooks.py或生命周期扩展。它是一次可重复运行的手动操作——成功返回0,失败返回非零,并打印进度让用户理解发生了什么;尽量做到可安全重入,若不可重入则检测状态并给出明确提示。
import subprocess import sys def main(): print("Installing plugin dependencies...") result = subprocess.run( [sys.executable, "-m", "pip", "install", "requests==2.31.0"], text=True, ) if result.returncode != 0: print("ERROR: Installation failed") return result.returncode print("Refreshing plugin resources...") # Add post-install, repair, migration, or maintenance logic here. print("Done.") return 0 if __name__ == "__main__": sys.exit(main())用户在插件 UI 中触发该脚本。运行时在 get_enhanced_plugins_list 中通过has_execute_script检测其存在并在列表中展示。
九、hooks.py:框架运行时钩子
需要框架内部钩子点时,在插件根目录添加hooks.py。框架通过helpers.plugins.call_plugin_hook(...)按函数名调用导出的钩子(见 call_plugin_hook,支持同步与异步函数)。
重要:hooks.py运行在Agent Zero 框架运行时内,而非独立的 Agent 执行环境。适合的场景:安装钩子、更新前钩子、插件注册、缓存建立、文件准备等框架内部操作。
当前内置调用点:
- 插件安装器将插件放入
usr/plugins/后调用install(); - 插件更新器在拉取新代码前调用
pre_update(); - 插件卸载器在删除插件目录前调用
uninstall()——用于清理install()创建的依赖与状态。
钩子应可逆、可安全清理,优先使用框架托管状态与插件自有路径,而不是永久修改系统。
9.1 环境定位规则
hooks.py中执行sys.executable -m pip install ...会安装到运行 Agent Zero 的同一 Python 环境——这对框架运行时内需要的依赖是正确的。但如果依赖是给独立 Agent 运行时或 OS 级工具用的,不要假设当前环境正确,应在子进程中显式切换目标:
- 调用目标运行时对应的确切 Python 解释器;
- 在子进程中先激活目标虚拟环境再执行
pip; - 从针对目标环境配置的子进程运行 OS 包管理器。
在 Docker 中,hooks.py通常作用于/opt/venv-a0,除非你明确指定/opt/venv或其他环境。
十、社区插件:GitHub 仓库 + Plugin Index 提交
用户选择社区插件时,本地构建测试完成后还有额外步骤。
10.1 仓库结构:内容必须在仓库根目录
your-plugin-repo/ ← GitHub 仓库根目录 ├── plugin.yaml ← 运行时清单(必须包含 name 字段!) ├── default_config.yaml ├── README.md ├── LICENSE ← Plugin Index 提交前仓库根目录必须存在 ├── api/ ├── tools/ ├── extensions/ └── webui/根目录的运行时plugin.yaml必须包含name字段,且与索引目录名完全一致:
name: my_plugin # REQUIRED - must match index folder name exactly title: My Plugin description: What this plugin does. version: 1.0.010.2 索引清单 index.yaml:与运行时清单不同
Plugin Index 使用独立的index.yaml,只描述可发现性,与运行时plugin.yaml是两套 schema:
title: My Plugin description: What this plugin does. github: https://github.com/yourname/your-plugin-repo tags: - tools - example screenshots: # 可选,最多 5 个完整图片 URL - https://raw.githubusercontent.com/yourname/your-plugin-repo/main/docs/screen1.png必填字段:title、description、github;可选:tags(最多 5 个)、screenshots(最多 5 个 URL)。注意:CI 还会校验远端plugin.yaml中的name字段与索引目录名精确匹配。
10.3 提交流程
- Fork Plugin Index 仓库;
- 在 fork 中创建
plugins/<your_plugin_name>/目录:- 目录名仅限小写字母、数字、下划线(
^[a-z0-9_]+$),不含连字符; - 必须与远端
plugin.yaml的name完全一致;
- 目录名仅限小写字母、数字、下划线(
- 在目录内添加
index.yaml(可选附带 ≤ 20 KB 的方形缩略图thumbnail.png/thumbnail.jpg/thumbnail.webp); - 打开 PR,且一个 PR只能新增一个插件目录;
- CI 自动校验,维护者审查后合并。
提交约束:目录名唯一且稳定、^[a-z0-9_]+$;_开头目录预留给内部使用;title最长 50 字符、description最长 500 字符、index.yaml总长不超过 2000 字符。完整的 git 操作引导见 a0-contribute-plugin 技能。
十一、Plugin Index 与 Plugin Hub
Plugin Index是社区插件中心。Agent Zero 通过内置的Plugin Hub向用户暴露索引插件:用户从Plugins对话框的Browse标签页或Install按钮打开,即可查看插件详情并直接从 UI 安装。插件的安装、更新、扫描、校验分别由内置插件_plugin_installer、_plugin_scan、_plugin_validator承担,详见 插件契约 的子插件索引表。
插件静态资源通过GET /plugins/<name>/<path>提供,API 处理器走POST /api/plugins/<name>/<handler>,管理动作走POST /api/plugins——这些路由契约同样定义在 插件契约。
十二、全流程清单与验证要点
一个完整插件从零到上线的检查清单:
- 确认类型:本地插件(
usr/plugins/)还是社区插件(独立仓库 + 索引 PR); - 清单:
plugin.yaml字段合法,name匹配目录名(^[a-z0-9_]+$); - 布局:api / tools / helpers / prompts / webui / extensions 按需创建,扩展点使用
extensions/python/<point>/与_functions/<module>/<qualname>/<start|end>/深层布局; - 前端:Store Gate 包装每个组件、Store 独立成 js、反馈一律走 A0 通知;
- 设置:需要配置项则提供
webui/config.html,字段绑定config.*; - 后端:
usr.plugins.<name>...导入、AgentContext发消息、get_plugin_config/save_plugin_config读写设置; - 生命周期:用户触发的操作进
execute.py,框架内部逻辑进hooks.py,并遵循环境定位规则; - 无残留:删除插件不留下非托管服务、符号链接或越界文件;
- 发布(社区插件):仓库根目录含
plugin.yaml(带name)+LICENSE,提交index.yamlPR,通过 CI 与维护者审查。
技能文档本身还给出了两条元规则,开发者在改造框架后应同步更新技能:a0-create-plugin/AGENTS.md 要求——当插件加载器、Plugin Hub、设置弹窗、扩展或 WebUI 模式变化时更新本技能;并定期人工通读 SKILL.md 检查过时路径与相关插件技能间的衔接,保证文档与运行时始终一致。
延伸阅读
- 插件开发文档(生命周期与发布)
- 通知开发文档(toast 全量 API)
- 插件运行时实现(发现、开关、配置、钩子)
- 扩展机制实现(@extensible 与扩展点解析)
- WebUI 组件规范、WebUI JS 规范、WebUI CSS 规范
- 内置插件实例:_chat_naming(设置分区 + 分作用域配置 + always_enabled)、_a0_connector(external/developer 分区)
【免费下载链接】agent-zeroAgent Zero AI framework项目地址: https://gitcode.com/GitHub_Trending/ag/agent-zero
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考