内容提要
本文介绍从零实现GeekAgent第16天:插件框架。主程序扫描plugins目录自动加载插件,插件通过SDK声明命令、工具和生命周期钩子,支持失败回滚。实现了echo插件(命令/echo和工具)及web插件(HTTP服务提供浏览器对话界面),复用主程序对话链路,通过SSE流式返回结果。
延伸解读
插件框架的定位与边界
本文的插件框架并非安全沙箱,而是生命周期隔离。插件与主程序共享进程权限,模块顶层副作用无法回滚,因此不适合加载不可信代码。它主要解决的是主程序入口稳定、失败隔离和生命周期统一的问题,与技能、MCP、RAG 等能力互补,而非替代。
依赖注入与回滚机制
插件通过 PluginContext 获得主程序注入的依赖,避免直接 import 主程序,降低耦合。框架在 onStart 失败时回滚该插件注册的命令和工具,但动态 import 已执行的顶层副作用无法回滚,所以隔离有限。这种设计适合本机 demo,但生产环境需考虑更严格的边界。
服务模式复用对话链路
web 插件通过 headless TUI 和 SSE 复用主程序的 reply 与 handleCommand,使浏览器端能获得与 TUI 一致的对话体验。但服务模式简化了确认操作(confirm 直接返回 true),且同一时间只处理一个对话请求,存在并发限制,这些是 demo 的取舍,并非最终方案。
Q&A
GeekAgent的插件框架是如何实现自动加载插件的?
主程序在启动时扫描plugins/目录,对每个子目录动态import其中的plugin.ts文件,如果插件导出合法的Plugin对象(包含name和description),则将其加入插件列表。加载失败的插件会被跳过并记录错误,不影响其他插件。
GeekAgent插件可以注册哪些类型的能力?
插件可以注册命令(如/echo)、工具(通过onStart钩子调用ctx.registerTool)以及启动服务(如web插件在onStart中创建HTTP服务器)。这些能力通过Plugin接口中的commands、onStart和onExit钩子实现。
GeekAgent插件框架如何处理插件启动失败?
如果某个插件的onStart钩子抛出错误,框架会回滚该插件注册的命令和工具(通过记录的命令和工具列表删除),并将该插件从列表中移除,其他插件继续正常启动。
GeekAgent的web插件是如何实现浏览器对话界面的?
web插件在web模式下启动一个HTTP服务器(端口8787),提供静态页面和/api/chat接口。当收到聊天请求时,它通过SSE(Server-Sent Events)将主程序的回复流式返回给浏览器。浏览器端使用fetch和ReadableStream读取SSE流,并动态更新聊天界面。
GeekAgent插件框架中,插件如何与主程序交互?
插件通过PluginContext对象与主程序交互,该对象由主程序在调用onStart时注入。PluginContext提供了tui(输出接口)、reply(对话处理)、handleCommand(命令处理)、registerTool(注册工具)等方法。插件只依赖plugin-sdk导出的类型,不直接import主程序内部模块。
GeekAgent插件框架中,内置命令和插件命令的优先级是如何处理的?
内置命令的switch case优先匹配,如果未匹配到,则委托给插件命令表执行。这样确保内置命令不能被插件覆盖。
GeekAgent插件框架支持哪些运行模式?
支持两种模式:TUI模式(默认)和web模式。通过命令行参数指定,例如`npm run dev -- day16/index.ts web`。在web模式下,主程序使用headless TUI实现,并启动web插件提供浏览器界面。
GeekAgent插件框架中,插件如何清理资源?
插件通过onExit钩子清理资源,例如web插件在onExit中关闭HTTP服务器。主程序在退出前会调用所有插件的onExit钩子,即使某个插件的onExit失败,也不会阻塞其他插件的清理。