Files
homeagent-sdk/docs/guide/packaging.md
JianFeeeee 0a6e2b7dc4 docs: 插件 SDK 文档站(API 参考从源码生成 + 自建检索)
为插件作者建一个文档站,重点是**能按描述搜到 API**,以及**明确能力边界**。

## 为什么 API 参考要生成而不是手写

公开 API 面有 115 个符号、11 个接口。手抄必然与代码漂移——这是文档站最常见的
死法(本仓 README 里已经有过几处「文档说一套、代码是另一套」)。

所以 `tools/apidoc` 直接从 `sdk/*.go` 提取签名、文档注释与代码块示例,渲染成
`docs/api/*.md`。发现文档不对时改的是**源码注释**,不是生成物。生成页首行带
「勿手改」标记,防止有人改了下次构建白改。

- `extract.go`:go/ast + go/doc 提取(只用标准库,离线可跑,不引入依赖)
- `gensite/`:渲染 Markdown + 检索索引
- `gensite/usages.go`:从 `example/` 21 个示例插件里反查**真实调用点**,
  贴在每个 API 下(源码注释里几乎没有可运行示例,但示例插件都是能编译跑的真代码)

## 能力边界:写这个站时查出的三处文档错误

这是本次最有价值的部分。原以为「公开 SDK 里有的 API 外部插件都能用」,
实测对照桥接模板后发现三处不符,站内已更正:

1. **`PluginMgr()` 被写成「仅内置可用」——错的。** 桥接模板第 692 行显式
   `base.SetPluginMgrAPI(procPluginMgr{})`,公开 `PluginMgrAPI` 注释也写「外部插件可调用」。
   真正的区别是**方法数**:公开面 3 个(ReloadOne/ListLoadedPlugins/IsPluginDisabled),
   内部面 9 个。容易混淆是因为两个包里有同名但不同的接口。
2. **`Events()` 外部插件恒为 nil。** `SetEventSubscriber` 全仓只有定义、无调用点,
   故 subscriber 从未被注入。外部插件的事件订阅实际由生成的运行时走
   `events.subscribe` RPC 完成——旧文档把它当成可用入口,会让人写出必然失效的代码。
3. **`UnregisterOutputChannel` 是静默无效,不是报错。** 桥接只注入 registrar、
   不注入 unregistrar,于是 `regOutputUnreg == nil`,函数命中 else 分支**直接返回 nil**
   (sdk/plugin.go:539-549)——不报错、通道也没注销。

每条裁定的依据写进 `tools/apidoc/tiers.json`(文件:行号 或 grep 结论),
站上以告警框呈现,读者可自行核对。判断依据三源:桥接模板的 `base.Set*` 注入点、
`internal/sdk` 完整面、`internal/plugin/proc/protocol.go` 的 RPC 表。

## 检索(用户的核心诉求)

两套互补:

- **MkDocs 内置搜索**:全文,中文走 jieba 分词。
- **自建 API 检索**(`docs/javascripts/api-search.js` + `assets/api-index.json`):
  支持四类查询——按名称、**按功能描述**(「注册工具」→ RegisterTool、
  「崩溃」→ SetAutoRestart)、按 `限定符.方法`(`memory.recall` → MemoryAPI.Recall)、
  按签名片段(`(string) error`)。并标出「仅内置」,避免外部插件作者踩空。

自建的理由:Material 内置搜索按整页文本索引,搜 `InjectText` 会列出所有提到它的
页面,但分不清哪条是它的定义;而且它要等 mkdocs build 才更新。

## 文档结构

- `docs/guide/`:快速开始、Go/Lua 首个插件、能力边界、打包发布、多平台、受限 SDK 与安全
- `docs/api/`:10 个按「你想做什么」划分的章节(工具/阶段/记忆/通道/配置/生命周期/
  事件/LLM/常量/桥接)+ 仅内置汇总页
- `docs/versions.md`:SDK 版本语义(跟随内核中版本、patch 恒为 .0)、
  1.0.0 是唯一破坏性变更、RPC 协议版本

## 验证

- `mkdocs build --strict` 零告警
- 23 个页面的全部站内链接与锚点可达(自动校验)
- 1440 / 768 / 390px 三视口:无横向溢出、无控制台错误
- 四种检索模式实测有结果且跳转锚点正确
- 构建产物 `site_build/` 已 gitignore

用法:`tools/apidoc/build.sh`(生成+构建)、`tools/apidoc/build.sh serve`(预览)。
2026-09-24 12:08:37 +08:00

115 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 打包与发布
`hmapdev build` 一次完成编译与打包,产出 `.hmap` 分发包(zip 格式,内含
`plugin.json` 清单 + 二进制)。
## 命令
```bash
hmapdev build # 默认 bundle(多平台合集)
hmapdev build --no-bundle # 只构建 plg.json targets 里的平台
hmapdev build --target linux/arm64 # 在 targets 基础上追加目标
hmapdev build --outdir out # 指定输出目录(默认 dist)
hmapdev build --sdk-path <path> # 覆盖 go.mod 的 replace 指向的 SDK
hmapdev build --replace <mod@path> # 追加 go.mod replace(可多次)
```
执行流程:
1. 读 `plg.json` 的 `targets` / `bundle` 决定构建目标
2. 生成子进程运行时代码(`z_proc_gen.go`、`z_proc_shm_*.go`)
3. **Go 插件**:`go build`(普通可执行文件,`CGO_ENABLED=0`)
**Lua 插件**:直接打包源码,不编译
4. 生成 `plugin.json` 输出清单
5. 打成 `.hmap`
## 两个 JSON 的区别
这一点经常混淆:
| 文件 | 谁维护 | 作用 | 关键字段 |
|---|---|---|---|
| `plg.json` | **你** | 项目元信息,构建输入 | `targets`、`bundle` |
| `plugin.json` | `hmapdev` 自动生成 | 构建产物清单 | `entry`、`platforms` |
`plg.json` 里的 `sdk` 字段声明**本插件针对的 SDK 版本**;未命中本地 SDK 存储
会明确报错(见[环境与工具链](getting-started.md))。
## 多平台(bundle)
`build` 默认就是 bundle 模式:一次编译 linux/amd64、darwin/amd64、windows/amd64,
产出一个含全部平台二进制的 `.hmap`;安装时内核挑当前平台那份。
```bash
hmapdev build # → dist/myplugin_bundle.hmap
hmapdev build --no-bundle # → dist/myplugin_linux_amd64.hmap 等
```
!!! note "bundle 模式会忽略 `plg.json` 的 `targets`"
固定构建上述三个平台。交叉编译需要对应工具链(如 Linux 上构建 darwin 需要
clang / macOS SDK),缺工具链时会失败 —— 此时用 `--no-bundle` 只构建当前平台。
bundle 包内按 `plugin.bin.<goos>.<goarch>` 区分,安装时重命名为 `plugin.bin`。
## 产物形态
子进程插件是**普通可执行文件**,不分平台后缀:
| 平台 | 二进制 |
|---|---|
| Linux / macOS / Windows | `plugin.bin` |
!!! warning "v1.0.0 破坏性变更:不再加载 `.so` / `.dll`"
外部插件从 C ABI 动态库改为**子进程 + 共享内存**。
- `plugin.so` / `plugin.dylib` / `plugin.dll` **不再被加载**。
新内核遇到旧产物会跳过并报可操作错误,不崩溃。
- **业务代码不用改一行** —— 公开 SDK 接口零改动,用新版 `hmapdev`
(原 `plugindev`)重编即可。
- `plg.json` 的 `entry` 字段对 Go 插件**已无意义**(写 `plugin.so` 也无妨),
现在只用于区分 Lua 插件。
- 产物不再需要 cgo,交叉编译无需目标平台 C 工具链。
## 安装
三种方式(`9876` 是 pluginmgr 的本地端口,默认只监听 `127.0.0.1`、无鉴权):
```bash
# 从 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"}'
# 从本地路径安装(读取文件,不移动原文件)
curl -X POST http://127.0.0.1:9876/plugins \
-H "Content-Type: application/json" \
-d '{"path": "/path/to/myplugin.hmap"}'
# 直接上传二进制
curl -X POST http://127.0.0.1:9876/plugins \
--data-binary @dist/myplugin_bundle.hmap
```
安装后调用 `/api/v1/plugins/reload` 或重启内核生效。
走 WebUI 的 HTTP API(默认 `8080`,需 `api_key` 鉴权,内部代理到 pluginmgr):
```bash
curl -X POST http://127.0.0.1:8080/api/v1/plugins \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{"path": "/path/to/myplugin.hmap"}'
```
也可以在 WebUI 的插件管理页面上传。
## 发布前自查
- [ ] `plg.json` 的 `sdk` 版本与目标内核匹配
- [ ] `version` 已递增(内核按版本判断是否需要重装)
- [ ] 若插件有外部状态,`SetAutoRestart(false)` 或在 `Start` 里重建连接
(崩溃重启是**线性退避** 1s→2s→3s,5 分钟内第 4 次崩溃即停止,
见[生命周期](../api/lifecycle.md#pluginsdksetautorestart))
- [ ] `RegisterOnRemoveHandler` 里清理自己写下的数据文件
- [ ] 在 `-race` 下跑一遍:插件的 `Start` 与工具的并发访问是最常见的竞态来源