diff --git a/assets/docs/en/ARCHITECTURE.md b/assets/docs/en/ARCHITECTURE.md index c14b6a4..f1dce3a 100644 --- a/assets/docs/en/ARCHITECTURE.md +++ b/assets/docs/en/ARCHITECTURE.md @@ -259,13 +259,13 @@ VM built-ins: `json.encode` / `json.decode` / `log` / `http_get` / `http_post`. | Method | Registration Mechanism | Compilation | Usage | |--------|----------------------|-------------|-------| | Built-in | `init()` → `RegisterFactory` | `internal/plugins/` compiled into kernel | webui/cli/timer/mcp etc. | -| External `.so` | C ABI dynamic loading | `-buildmode=c-shared` + bridge | qq/files/web/memo etc. | -| Lua script plugin | Parse `main.lua` to register tools | No compilation, hot-reload | luaplugintest/testlua etc. | +| External `.so` | C ABI dynamic loading | `-buildmode=c-shared` + bridge | qq/browser/files etc. | +| Lua script plugin | Execute `main.lua` to register tools | No compilation, takes effect after restart/reload | luademo etc. | | SKILL plugin | Parse `SKILL.md` | Markdown definition | Loaded via clawhubadapter | Built-in plugin registration: `internal/plugins/all.go` blank imports → each plugin `init()` → `Registry.Load()` scans directory to match factory. External plugin loading: `internal/plugin/dynamic.go` → copy to SHA256 temp path (bypass `plugin.Open` path cache) → `Open` + `Lookup("NewPlugin")`. -Lua script plugin loading: `internal/lua/` → parse `main.lua` via Lua VM, call `start()` to register tools. +Lua script plugin loading: `internal/plugin/` → the gopher-lua interpreter executes `main.lua` (at load time `sdk.register_*` only buffers handlers), then `Start()` swaps in the real SDK implementation and registers them in batch. The script is read only once at load time; runtime execution happens via callbacks. ### Built-in vs External Plugins diff --git a/assets/docs/en/OVERVIEW.md b/assets/docs/en/OVERVIEW.md index 6907de6..14aa1e2 100644 --- a/assets/docs/en/OVERVIEW.md +++ b/assets/docs/en/OVERVIEW.md @@ -79,5 +79,5 @@ Code is in the project root, implemented in Go. Core functionality is operational. Plugin system and SDK are ready for independent external plugin development. - Built-in plugins: webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / clawhubadapter / files / cfgmgr -- External plugin examples ([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo `example/`, both Go and Lua types): qq / files / web / memo / bili / editdoc / a2a / ocr / sanitizer / luaplugintest / testlua +- External plugin examples ([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo `example/`, both Go and Lua types): qq / files / a2a / ai_image / bili / browser / calendar / editdoc / memo / music / ocr / rss / sanitizer / weather / luademo - Distribution: `.hmap` plugin package format, installable via WebUI diff --git a/assets/docs/en/PLUGIN_DEV.md b/assets/docs/en/PLUGIN_DEV.md index 68895c7..3536d81 100644 --- a/assets/docs/en/PLUGIN_DEV.md +++ b/assets/docs/en/PLUGIN_DEV.md @@ -64,6 +64,14 @@ plugindev sdk path # show current SDK path SDK is stored at `~/.homeagent/plugindev/sdk//`; `plugindev init` reads the current SDK version for `go.mod`. +### Source Debugging + +`plugindev debug` interprets plugin source and prints a call trace, no compilation environment needed: + +```bash +plugindev debug [dir] # dir defaults to the current directory +``` + ### Creating a Go Plugin ```bash @@ -71,8 +79,11 @@ plugindev init myplugin cd myplugin # Edit plugin code vim plugin.go -# Build and package +# Build and package (default is a multi-platform bundle, see below) plugindev build +# Output: dist/myplugin_bundle.hmap +# Single-platform build: +plugindev build --no-bundle # Output: dist/myplugin_linux_amd64.hmap (or windows_amd64) ``` @@ -121,14 +132,19 @@ myluaplugin/ ```bash cd myplugin -plugindev build +plugindev build # default bundle mode (multi-platform) +plugindev build --no-bundle # single-target build (per plg.json targets) +plugindev build --target linux/amd64 # append a target on top of plg.json targets +plugindev build --outdir dist # output directory (default: dist) +plugindev build --sdk-path # SDK path override (go.mod replace) +plugindev build --replace # append a go.mod replace directive (repeatable) ``` Execution process: -1. Reads `plg.json` `targets` field to determine target platforms -2. Auto-generates C ABI bridge code (`z_bridge_gen.go` + `z_entry.c`) +1. Reads `plg.json` `targets`/`bundle` fields to determine build targets (bundle takes priority, see below) +2. Auto-generates C ABI bridge code (`z_bridge_gen.go` + `z_entry.c`; Windows only `z_bridge_gen.go`) 3. **Go plugin**: Runs `go build -buildmode=c-shared` (produces `.so` / `.dylib` / `.dll`) -4. **Lua plugin**: Packages source code directly, no compilation needed +4. **Lua plugin**: Packages source code directly, no compilation needed (contents: `plugin.json` + `main.lua`, plus optional `README.md`, `LICENSE`, `thirdpart/*.lua`) 5. Generates `plugin.json` output manifest 6. Packages as `.hmap` distribution (zip format, containing `plugin.json` + binary) @@ -136,7 +152,7 @@ Execution process: | File | Purpose | Key fields | |------|---------|------------| -| `plg.json` | Project metadata, maintained by developer | `targets` — build targets (e.g. `"linux/amd64,windows/amd64"`) | +| `plg.json` | Project metadata, maintained by developer | `targets` — single-target build list (e.g. `"linux/amd64,windows/amd64"`); `bundle` — multi-platform bundle switch (default `true`) | | `plugin.json` | Build artifact manifest, auto-generated | `entry` — entry filename; `platforms` — declared platforms | Each target produces a separate `.hmap`; binary name by platform: @@ -147,23 +163,28 @@ Each target produces a separate `.hmap`; binary name by platform: | macOS | `plugin.dylib` | | Windows | `plugin.dll` | -### Multi-platform bundle: --bundle +### Build Targets & Multi-platform Bundle + +**`plugindev build` defaults to bundle mode** (unless `plg.json` explicitly sets `"bundle": false`): it builds linux/amd64 + darwin/amd64 + windows/amd64 in one pass, producing a single `.hmap` with all platform binaries. The output manifest includes a `platforms` field. The kernel auto-selects the correct binary during installation. ```bash -plugindev build --bundle +plugindev build # default bundle, outputs dist/myplugin_bundle.hmap +plugindev build --bundle # explicitly enable bundle (same as above) +plugindev build --no-bundle # disable bundle, build per plg.json targets ``` -Builds linux/amd64 + darwin/amd64 + windows/amd64 in one pass, producing a single `.hmap` -with all platform binaries. The output manifest includes a `platforms` field. -The kernel auto-selects the correct binary during installation. +Notes: +- In bundle mode the `plg.json` `targets` field is ignored; the three platforms above are always built +- Cross-compilation needs the corresponding toolchains (e.g. building darwin on Linux requires clang/macOS SDK); if a toolchain is missing the build fails — use `--no-bundle` to build only the current platform +- Single-target output naming: `{name}_{os}_{arch}.hmap`, e.g. `myplugin_linux_amd64.hmap` Output in `dist/` directory: ``` dist/ -├── myplugin_linux_amd64.hmap # Single platform: Linux -├── myplugin_windows_amd64.hmap # Single platform: Windows -├── myplugin_darwin_amd64.hmap # Single platform: macOS -├── myplugin_bundle.hmap # Multi-platform bundle +├── myplugin_bundle.hmap # default bundle: multi-platform +├── myplugin_linux_amd64.hmap # after --no-bundle: Linux +├── myplugin_windows_amd64.hmap # after --no-bundle: Windows +├── myplugin_darwin_amd64.hmap # after --no-bundle: macOS └── myplugin_lua.hmap # Lua plugin ``` @@ -172,12 +193,12 @@ dist/ Install via PluginMgr HTTP API (three methods): ```bash -# 1. Install from URL (auto-cleanup) +# 1. Install from URL (http/https only, streamed, no local temp file) curl -X POST http://127.0.0.1:9876/plugins \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/myplugin.hmap"}' -# 2. Install from local path (keeps source file) +# 2. Install from local path (reads the given file, source file untouched) curl -X POST http://127.0.0.1:9876/plugins \ -H "Content-Type: application/json" \ -d '{"path": "/path/to/myplugin.hmap"}' @@ -187,9 +208,18 @@ curl -X POST http://127.0.0.1:9876/plugins \ --data-binary @dist/myplugin.hmap ``` +`9876` is the pluginmgr local port (defaults to listening on 127.0.0.1 only, no auth). + Reload plugins via `/api/v1/plugins/reload` or restart the kernel to activate. -Or upload via WebUI plugin management page. +Or upload via the WebUI plugin management page, or through the WebUI HTTP API (default port 8080, requires the `api_key` bearer token; it proxies to pluginmgr): + +```bash +curl -X POST http://127.0.0.1:8080/api/v1/plugins \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"path": "/path/to/myplugin.hmap"}' +``` --- @@ -491,6 +521,14 @@ outputCh := s.OutputChan() Lua plugins are suitable for lightweight rapid prototyping, requiring no Go compilation environment. Changes take effect after kernel restart. +### Execution Model + +Lua plugins run inside the kernel process on a gopher-lua interpreter (single Lua state guarded by a mutex). This differs fundamentally from Go plugins: + +- **Passive callback model**: `main.lua` executes only once at load time. Afterward, tools, stage hooks, output/input channels, and registered APIs are all invoked by the kernel via callbacks into Lua functions. Plugins cannot start background tasks on their own. +- **No concurrency / no long-running services**: Lua has no goroutines, coroutine scheduling, `os`/`io` libraries, or socket listening. The only outbound capability is `sdk.http.get/post` (synchronous). Any blocking loop will stall every call of that plugin while holding the lock. +- **For long-running services (listening on a port, background polling, timers) use a Go plugin** (`.so`/`.dll` built with the toolchain, which may spawn goroutines — see the webui/cli plugins). The Lua equivalent is event-driven: register tools/stage hooks/channels to be called back by the kernel, or interact with external processes via `sdk.http`. + ### Plugin Structure ```lua diff --git a/assets/docs/zh/ARCHITECTURE.md b/assets/docs/zh/ARCHITECTURE.md index 1eaf66f..3ab8dfc 100644 --- a/assets/docs/zh/ARCHITECTURE.md +++ b/assets/docs/zh/ARCHITECTURE.md @@ -257,13 +257,13 @@ VM 内置 `json.encode` / `json.decode` / `log` / `http_get` / `http_post`。 | 方式 | 注册机制 | 编译 | 用途 | |------|----------|------|------| | 内置插件 | `init()` → `RegisterFactory` | `internal/plugins/` 编译进内核 | webui/cli/timer/mcp 等 | -| 外部 `.so` | C ABI 动态加载 | `-buildmode=c-shared` + bridge | qq/files/web/memo 等 | -| Lua 脚本插件 | 解析 `main.lua` 注册工具 | 无需编译,热加载 | luaplugintest/testlua 等 | +| 外部 `.so` | C ABI 动态加载 | `-buildmode=c-shared` + bridge | qq/browser/files 等 | +| Lua 脚本插件 | 执行 `main.lua` 注册工具 | 无需编译,重启/重载生效 | luademo 等 | | SKILL 插件 | 解析 `SKILL.md` | Markdown 定义 | clawhubadapter 兼容加载 | 内置插件注册:`internal/plugins/all.go` 空白导入 → 各插件 `init()` → `Registry.Load()` 扫描目录匹配工厂。 外部插件加载:`internal/plugin/dynamic.go` → 复制到 SHA256 临时路径(绕过 `plugin.Open` 路径缓存)→ `Open` + `Lookup("NewPlugin")`。 -Lua 脚本插件加载:`internal/lua/` → 通过 Lua VM 解析 `main.lua`,调用 `start()` 注册工具。 +Lua 脚本插件加载:`internal/plugin/` → gopher-lua 解释器执行 `main.lua`(加载期 `sdk.register_*` 仅暂存 handler),`Start()` 时替换为真实 SDK 实现并批量注册。脚本只在加载时读取一次,运行期通过回调执行。 ### 内置插件 vs 外部插件 diff --git a/assets/docs/zh/OVERVIEW.md b/assets/docs/zh/OVERVIEW.md index 082f998..852451c 100644 --- a/assets/docs/zh/OVERVIEW.md +++ b/assets/docs/zh/OVERVIEW.md @@ -79,5 +79,5 @@ HomeAgent 是一个持续运行的个人智能 Agent 框架。 核心功能已可运行。插件系统和 SDK 已就绪,可独立开发外部插件。 - 内置插件:webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / clawhubadapter / files / cfgmgr -- 外部插件示例([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库 `example/`,含 Go 和 Lua 两种类型):qq / files / web / memo / bili / editdoc / a2a / ocr / sanitizer / luaplugintest / testlua +- 外部插件示例([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库 `example/`,含 Go 和 Lua 两种类型):qq / files / a2a / ai_image / bili / browser / calendar / editdoc / memo / music / ocr / rss / sanitizer / weather / luademo - 打包分发:`.hmap` 插件包格式,通过 WebUI 安装 diff --git a/assets/docs/zh/PLUGIN_DEV.md b/assets/docs/zh/PLUGIN_DEV.md index 2508273..b9dea75 100644 --- a/assets/docs/zh/PLUGIN_DEV.md +++ b/assets/docs/zh/PLUGIN_DEV.md @@ -65,6 +65,14 @@ plugindev sdk path # 显示当前 SDK 路径 SDK 存储在 `~/.homeagent/plugindev/sdk//`,`plugindev init` 自动读取当前 SDK 版本填充 `go.mod`。 +### 源码调试 + +`plugindev debug` 直接用解释器执行插件源码并输出调用轨迹,无需编译环境: + +```bash +plugindev debug [dir] # dir 默认当前目录 +``` + ### 创建 Go 插件 ```bash @@ -73,7 +81,10 @@ cd myplugin # 编辑插件代码 vim plugin.go # 编译打包 -plugindev build +plugindev build # 默认多平台 bundle(见下节) +# 输出: dist/myplugin_bundle.hmap +# 单平台构建: +plugindev build --no-bundle # 输出: dist/myplugin_linux_amd64.hmap (或 windows_amd64) ``` @@ -122,14 +133,19 @@ myluaplugin/ ```bash cd myplugin -plugindev build +plugindev build # 默认 bundle 模式(多平台合集) +plugindev build --no-bundle # 单平台构建(仅当前 plg.json targets) +plugindev build --target linux/amd64 # 在 targets 基础上追加一个目标 +plugindev build --outdir dist # 指定输出目录(默认 dist) +plugindev build --sdk-path # 指定 SDK 路径(覆盖 go.mod replace) +plugindev build --replace # 追加 go.mod replace 指令(可多次) ``` 执行过程: -1. 读取 `plg.json` 的 `targets` 字段确定目标平台 -2. 自动生成 C ABI bridge 代码(`z_bridge_gen.go` + `z_entry.c`) +1. 读取 `plg.json` 的 `targets`/`bundle` 字段确定构建目标(bundle 模式优先,见下节) +2. 自动生成 C ABI bridge 代码(`z_bridge_gen.go` + `z_entry.c`,Windows 仅 `z_bridge_gen.go`) 3. **Go 插件**:执行 `go build -buildmode=c-shared`(生成 `.so` / `.dylib` / `.dll`) -4. **Lua 插件**:直接打包源码,无需编译 +4. **Lua 插件**:直接打包源码,无需编译(打包内容:`plugin.json` + `main.lua`,以及可选的 `README.md`、`LICENSE`、`thirdpart/*.lua`) 5. 生成 `plugin.json` 输出清单 6. 打包为 `.hmap` 分发包(zip 格式,内含 `plugin.json` + 二进制) @@ -137,7 +153,7 @@ plugindev build | 文件 | 用途 | 关键字段 | |------|------|---------| -| `plg.json` | 项目元信息,由开发者维护 | `targets` — 构建目标(如 `"linux/amd64,windows/amd64"`)| +| `plg.json` | 项目元信息,由开发者维护 | `targets` — 单平台构建目标(如 `"linux/amd64,windows/amd64"`);`bundle` — 多平台合集开关(默认 `true`)| | `plugin.json` | 构建产物清单,`plugindev build` 自动生成 | `entry` — 入口文件名;`platforms` — 声明的支持平台 | 每个目标生成单独的 `.hmap`,二进制文件名由平台决定: @@ -148,22 +164,28 @@ plugindev build | macOS | `plugin.dylib` | | Windows | `plugin.dll` | -### 多平台打包:--bundle +### 构建目标与多平台打包(bundle) + +**`plugindev build` 默认就是 bundle 模式**(`plg.json` 未显式写 `"bundle": false` 时):一次编译 linux/amd64 + darwin/amd64 + windows/amd64,生成包含所有平台二进制的单 `.hmap`,输出清单自动添加 `platforms` 字段。安装时核心自动选择当前平台的二进制,跳过其他平台。 ```bash -plugindev build --bundle +plugindev build # 默认 bundle,输出 dist/myplugin_bundle.hmap +plugindev build --bundle # 显式开启 bundle(同上) +plugindev build --no-bundle # 关闭 bundle,按 plg.json 的 targets 逐平台构建 ``` -一次编译 linux/amd64 + darwin/amd64 + windows/amd64,生成包含所有平台二进制的单 `.hmap`, -输出清单自动添加 `platforms` 字段。安装时核心自动选择当前平台的二进制,跳过其他平台。 +注意: +- bundle 模式下 `plg.json` 的 `targets` 字段被忽略,固定构建上述三个平台 +- 跨平台交叉编译需要对应工具链(如 Linux 上构建 darwin 需 clang/macOS SDK);缺少工具链时编译会失败,此时使用 `--no-bundle` 只构建当前平台 +- 单平台输出文件名:`{name}_{os}_{arch}.hmap`,如 `myplugin_linux_amd64.hmap` 输出在 `dist/` 目录: ``` dist/ -├── myplugin_linux_amd64.hmap # 单平台:Linux 版 -├── myplugin_windows_amd64.hmap # 单平台:Windows 版 -├── myplugin_darwin_amd64.hmap # 单平台:macOS 版 -├── myplugin_bundle.hmap # 多平台合集 +├── myplugin_bundle.hmap # 默认 bundle:多平台合集 +├── myplugin_linux_amd64.hmap # --no-bundle 后:Linux 版 +├── myplugin_windows_amd64.hmap # --no-bundle 后:Windows 版 +├── myplugin_darwin_amd64.hmap # --no-bundle 后:macOS 版 └── myplugin_lua.hmap # Lua 插件 ``` @@ -172,12 +194,12 @@ dist/ 通过 PluginMgr HTTP API 安装,支持三种方式: ```bash -# 1. 从 URL 安装(自动清理安装包) +# 1. 从 URL 安装(仅支持 http/https,流式下载不落盘) curl -X POST http://127.0.0.1:9876/plugins \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/myplugin.hmap"}' -# 2. 从本地路径安装(保留安装包) +# 2. 从本地路径安装(读取指定文件,不移动原文件) curl -X POST http://127.0.0.1:9876/plugins \ -H "Content-Type: application/json" \ -d '{"path": "/path/to/myplugin.hmap"}' @@ -187,9 +209,18 @@ curl -X POST http://127.0.0.1:9876/plugins \ --data-binary @dist/myplugin.hmap ``` +`9876` 为 pluginmgr 本地监听端口(默认仅监听 127.0.0.1,无鉴权)。 + 安装后需调用 `/api/v1/plugins/reload` 或重启内核生效。 -也可通过 WebUI 插件管理页面上传安装。 +也可通过 WebUI 插件管理页面上传安装,或走 WebUI 的 HTTP API(端口默认 8080,需 `api_key` 鉴权,内部代理到 pluginmgr): + +```bash +curl -X POST http://127.0.0.1:8080/api/v1/plugins \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"path": "/path/to/myplugin.hmap"}' +``` --- @@ -489,6 +520,14 @@ outputCh := s.OutputChan() Lua 插件适合轻量级快速原型,无需 Go 编译环境,修改后直接重启内核即可生效。 +### 执行模型 + +Lua 插件运行在内核进程内的 gopher-lua 解释器中(单 Lua 状态 + 互斥锁),与 Go 插件的执行模型有本质区别: + +- **被动回调模型**:`main.lua` 仅在加载时执行一次,此后插件的工具、阶段钩子、输出/输入通道、注册 API 全部由内核事件驱动回调 Lua 函数;插件不能自己启动后台任务。 +- **无并发/无常驻服务能力**:Lua 侧没有 goroutine、协程调度、`os`/`io` 库和 socket 监听能力,唯一主动出站通道是 `sdk.http.get/post`(同步请求)。任何阻塞循环都会持锁卡死该插件的所有调用。 +- **常驻服务(如监听端口、后台轮询、定时任务)请使用 Go 插件**(工具链编译的 `.so`/`.dll`,可自行启动 goroutine,参见 webui/cli 插件)。Lua 插件的等价做法是事件驱动:注册工具/阶段钩子/通道由内核回调,或经 `sdk.http` 与外部进程交互。 + ### 插件结构 ```lua