mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
refactor(plugin)!: 重编提示与模板路径改用 hmapdev;文档全面对齐 1.2.0
工具链在 SDK 1.2.0 更名为 hmapdev(原 plugindev)。核心侧三处功能耦合同步:
1. 用户可见报错:旧 C ABI 产物 / 协议版本不匹配 / 共享段版本不匹配
三处「请用配套 plugindev 重编」→ hmapdev(对应两条测试断言同步)
2. e2e_template_test 的模板路径改为 tools/hmapdev/templates,
并保留旧路径回退(旧 SDK 检出仍能跑测试)
3. 注释与文档同步
文档更新(用户可见面):
- assets/docs/{zh,en}/PLUGIN_DEV.md:工具链章节整体改为 hmapdev,
补改名说明与 SDK 存储目录迁移;命令示例全部更新
- assets/docs/{zh,en}/ARCHITECTURE.md:**流程图与章节对齐 v1.2.0** ——
· 向量化章节改为三层降级:统一多模态空间(主)→ 词嵌入 → TF-IDF(回退),
写明「同指纹且同维度才参与融合」
· 媒体记忆章节重写:媒体是一等记忆块(无独立 GC / 无引用计数 / 无描述式索引 /
正文不再写 media marker),并写明 reembedStaleMedia 的跨空间迁移与写回
- README{,_EN}.md、docs/zh/plugin-interface-matrix.md:工具名与模板路径同步
(历史条目标注「当时名为 plugindev」)
验证:go test ./internal/plugin/ ./internal/plugin/proc/ ok,
含 4 条真实模板 E2E(模板路径切换后仍通过)。
This commit is contained in:
@ -190,7 +190,7 @@ internal/
|
|||||||
├── config/ SQLite 配置中心
|
├── config/ SQLite 配置中心
|
||||||
├── events/ 事件总线
|
├── events/ 事件总线
|
||||||
└── internal/lua/adapters/ 8 个 LLM 协议适配器脚本
|
└── internal/lua/adapters/ 8 个 LLM 协议适配器脚本
|
||||||
外部插件开发见 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库,使用 `plugindev` 工具链开发,参考 `example/` 目录下的 Go 和 Lua 示例
|
外部插件开发见 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库,使用 `hmapdev` 工具链开发,参考 `example/` 目录下的 Go 和 Lua 示例
|
||||||
```
|
```
|
||||||
|
|
||||||
## 项目状态
|
## 项目状态
|
||||||
@ -209,7 +209,7 @@ internal/
|
|||||||
是块的**迁移**,不是复制、也不靠引用保活。
|
是块的**迁移**,不是复制、也不靠引用保活。
|
||||||
- **数据面全部走共享内存**(工具调用帧 / Cleaner / 输入输出通道 / 媒体块 / 文档与知识正文),
|
- **数据面全部走共享内存**(工具调用帧 / Cleaner / 输入输出通道 / 媒体块 / 文档与知识正文),
|
||||||
RPC 只传偏移描述符;**RPC 协议升到 2**,fd3 布局改变,**不支持滚动升级**——
|
RPC 只传偏移描述符;**RPC 协议升到 2**,fd3 布局改变,**不支持滚动升级**——
|
||||||
内核与全部插件必须同批重建、同批安装,存量插件须用新版 `plugindev` 重编。
|
内核与全部插件必须同批重建、同批安装,存量插件须用新版 `hmapdev` 重编。
|
||||||
- 注入可声明 `InjectOptions{NoMemory, ContextPolicy}`(**默认仍记入记忆、默认不裁剪**);
|
- 注入可声明 `InjectOptions{NoMemory, ContextPolicy}`(**默认仍记入记忆、默认不裁剪**);
|
||||||
裁剪必须显式声明,且先经插件注册的 `Cleaner`。SDK 1.2.0 相对 1.1.0 **纯追加**。
|
裁剪必须显式声明,且先经插件注册的 `Cleaner`。SDK 1.2.0 相对 1.1.0 **纯追加**。
|
||||||
- **发行包默认启用** ONNX 向量空间,并把模型(754MB)与 ONNX Runtime(24MB)随
|
- **发行包默认启用** ONNX 向量空间,并把模型(754MB)与 ONNX Runtime(24MB)随
|
||||||
@ -226,7 +226,7 @@ internal/
|
|||||||
|
|
||||||
**v1.1.0** — 记忆系统支持**二进制多媒体节点**。内容寻址媒体存储(CAS + SQLite 元数据 + 磁盘 blob,`Get` always 重校 digest),贯通 L0(上下文事件)/L2(文档)/L3(图谱句子)三层,引用计数式 GC(有引用者绝不删)。视觉模型生成的描述文本是持久语义记忆,blob 只是可被容量 GC 淘汰的缓存。
|
**v1.1.0** — 记忆系统支持**二进制多媒体节点**。内容寻址媒体存储(CAS + SQLite 元数据 + 磁盘 blob,`Get` always 重校 digest),贯通 L0(上下文事件)/L2(文档)/L3(图谱句子)三层,引用计数式 GC(有引用者绝不删)。视觉模型生成的描述文本是持久语义记忆,blob 只是可被容量 GC 淘汰的缓存。
|
||||||
|
|
||||||
**v1.0.0** — 外部插件从 C ABI 动态库迁移到**子进程 + 共享内存**。首个不再加载 `.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件须用新版 `plugindev` 重编为 `plugin.bin`,**业务代码零改动**)。消除 6 类此前在生产造成故障的缺陷:热重载失效(`DF_1_NODELETE` 让 `dlclose` 成 no-op)、崩溃隔离缺失(插件 panic 带崩 homed)、stage lost update(副本模型丢失 35.8~36.8%)、cgo 超时不可中断(线程线性泄漏)、`output_send` 假成功(模型收到「已发送」而消息未送达)、Windows 能力断层(只见 3 个 stage 字段且无法写回)。三面通信:stdio JSON-RPC(控制)+ 共享内存段(数据)+ 事件环(通知);权限梯度显式化为三道闸。RPC 往返 p50 24.1µs,崩溃到恢复 <1s。
|
**v1.0.0** — 外部插件从 C ABI 动态库迁移到**子进程 + 共享内存**。首个不再加载 `.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件须用新版工具链重编;该工具链当时名为 `plugindev`,**现名 `hmapdev`**)。外部插件需重编为 `plugin.bin`,**业务代码零改动**)。消除 6 类此前在生产造成故障的缺陷:热重载失效(`DF_1_NODELETE` 让 `dlclose` 成 no-op)、崩溃隔离缺失(插件 panic 带崩 homed)、stage lost update(副本模型丢失 35.8~36.8%)、cgo 超时不可中断(线程线性泄漏)、`output_send` 假成功(模型收到「已发送」而消息未送达)、Windows 能力断层(只见 3 个 stage 字段且无法写回)。三面通信:stdio JSON-RPC(控制)+ 共享内存段(数据)+ 事件环(通知);权限梯度显式化为三道闸。RPC 往返 p50 24.1µs,崩溃到恢复 <1s。
|
||||||
|
|
||||||
**v0.9.0** — C ABI v2:外部插件 Stage 回调支持写回(`invoke_stage` 增加 result 输出,插件可在 OnInput/AfterToolcall/PostAction 修改 RawMessage/LLMText/ToolResults 等并同步回内核),ABI 版本随内核 minor 对齐(v0.9.x → ABIVersion=2,`version_min=1` 向后兼容旧插件)。同步修复工具循环 zen 兼容补位误伤首轮 system 上下文的问题。配套 SDK 提供增强版 sanitizer 示例(坏 UTF-8/U+FFFD/ANSI 转义全链路清洗)。**该 ABI 已随 v1.0.0 退场。**
|
**v0.9.0** — C ABI v2:外部插件 Stage 回调支持写回(`invoke_stage` 增加 result 输出,插件可在 OnInput/AfterToolcall/PostAction 修改 RawMessage/LLMText/ToolResults 等并同步回内核),ABI 版本随内核 minor 对齐(v0.9.x → ABIVersion=2,`version_min=1` 向后兼容旧插件)。同步修复工具循环 zen 兼容补位误伤首轮 system 上下文的问题。配套 SDK 提供增强版 sanitizer 示例(坏 UTF-8/U+FFFD/ANSI 转义全链路清洗)。**该 ABI 已随 v1.0.0 退场。**
|
||||||
|
|
||||||
|
|||||||
@ -179,7 +179,7 @@ internal/
|
|||||||
├── config/ SQLite config center
|
├── config/ SQLite config center
|
||||||
├── events/ Event bus
|
├── events/ Event bus
|
||||||
└── internal/lua/adapters/ 8 LLM protocol adapter scripts
|
└── internal/lua/adapters/ 8 LLM protocol adapter scripts
|
||||||
External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo, use `plugindev` toolchain, refer to Go and Lua examples in `example/`
|
External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo, use `hmapdev` toolchain, refer to Go and Lua examples in `example/`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
@ -240,7 +240,7 @@ by `-race`; in production this showed up as sporadic nil-dereference crashes dur
|
|||||||
items are never deleted). The description text produced by the vision model is the durable
|
items are never deleted). The description text produced by the vision model is the durable
|
||||||
semantic memory; the blob is only a cache that capacity GC may evict.
|
semantic memory; the blob is only a cache that capacity GC may evict.
|
||||||
|
|
||||||
**v1.0.0** — External plugins moved from C ABI shared libraries to **subprocess + shared memory**. The first release that no longer loads `.so`/`.dll`, and it is incompatible with 0.9.x (existing plugins must be rebuilt into `plugin.bin` with the new `plugindev`, though **business code needs zero changes**). Eliminates 6 classes of defects that had caused production incidents: hot-reload silently failing (`DF_1_NODELETE` making `dlclose` a no-op), no crash isolation (a plugin panic took down homed), stage lost updates (35.8~36.8% loss under the copy model), uncancellable cgo timeouts (linear OS-thread leaks), `output_send` reporting false success (the model was told "sent" while the message never went out), and Windows capability degradation (only 3 stage fields visible, no write-back). Three communication planes: stdio JSON-RPC (control) + shared memory segment (data) + event ring (notification); the privilege gradient is now enforced by three explicit gates. RPC round-trip p50 24.1µs; crash-to-recovery under 1s.
|
**v1.0.0** — External plugins moved from C ABI shared libraries to **subprocess + shared memory**. The first release that no longer loads `.so`/`.dll`, and it is incompatible with 0.9.x (existing plugins must be rebuilt into `plugin.bin` with the new toolchain — called `plugindev` back then, **now `hmapdev`** — though **business code needs zero changes**). Eliminates 6 classes of defects that had caused production incidents: hot-reload silently failing (`DF_1_NODELETE` making `dlclose` a no-op), no crash isolation (a plugin panic took down homed), stage lost updates (35.8~36.8% loss under the copy model), uncancellable cgo timeouts (linear OS-thread leaks), `output_send` reporting false success (the model was told "sent" while the message never went out), and Windows capability degradation (only 3 stage fields visible, no write-back). Three communication planes: stdio JSON-RPC (control) + shared memory segment (data) + event ring (notification); the privilege gradient is now enforced by three explicit gates. RPC round-trip p50 24.1µs; crash-to-recovery under 1s.
|
||||||
|
|
||||||
**v0.9.0** — C ABI v2: external plugin Stage callbacks can now write back (`invoke_stage` gained a result out-param; plugins may mutate RawMessage/LLMText/ToolResults etc. in OnInput/AfterToolcall/PostAction and have them synced to the core). ABI version now tracks core minor releases (v0.9.x → ABIVersion=2, `version_min=1` keeps old plugins loadable). Also fixes the tool-loop zen-compat placeholder that wrongly fired on first-turn system context tail. The SDK ships an enhanced sanitizer example (bad-UTF-8 / U+FFFD / ANSI-escape scrub across the whole pipeline). **This ABI retired with v1.0.0.**
|
**v0.9.0** — C ABI v2: external plugin Stage callbacks can now write back (`invoke_stage` gained a result out-param; plugins may mutate RawMessage/LLMText/ToolResults etc. in OnInput/AfterToolcall/PostAction and have them synced to the core). ABI version now tracks core minor releases (v0.9.x → ABIVersion=2, `version_min=1` keeps old plugins loadable). Also fixes the tool-loop zen-compat placeholder that wrongly fired on first-turn system context tail. The SDK ships an enhanced sanitizer example (bad-UTF-8 / U+FFFD / ANSI-escape scrub across the whole pipeline). **This ABI retired with v1.0.0.**
|
||||||
|
|
||||||
|
|||||||
@ -132,11 +132,22 @@ Setting `ctx.Response` at any stage jumps to `after_output`.
|
|||||||
→ triples → GraphDB.Commit
|
→ triples → GraphDB.Commit
|
||||||
```
|
```
|
||||||
|
|
||||||
### Vectorization: Pretrained Word Embedding + TF-IDF Fallback
|
### Vectorization: Unified Multimodal Space (primary) → Word Embedding → TF-IDF (fallback)
|
||||||
|
|
||||||
All vectorization unified under `StaticEmbedder` (`internal/memory/static_embedder.go`):
|
Vectorization degrades through three layers by availability; **each missing layer reports an explicit
|
||||||
|
error and never pretends to succeed**:
|
||||||
|
|
||||||
**Primary Strategy — Pretrained Word Embedding (aligned 300d)**
|
**① Unified multimodal space (primary path, since v1.2.0)**
|
||||||
|
Text and images share **one model, one dimension, one fingerprint** (default `chineseclip`: 512d,
|
||||||
|
Apache-2.0, Chinese-native; `qwen3vl` or an external `http` provider are alternatives).
|
||||||
|
Providers register through the public `pkg/embedding` SPI — **the kernel hardcodes no model**.
|
||||||
|
Vectors persist together with their fingerprint (`dense_fp` / `vec_model`); any mismatch with the
|
||||||
|
current fingerprint triggers recomputation, and only blocks with the **same fingerprint and the same
|
||||||
|
dimension** participate in fusion (mixing coordinate systems yields a direction resembling neither).
|
||||||
|
|
||||||
|
**② Word embedding (text fallback)** — `StaticEmbedder` (`internal/memory/static_embedder.go`):
|
||||||
|
|
||||||
|
**Model sources** (aligned 300d)
|
||||||
- Model sources: ConceptNet Numberbatch (77-language aligned) / fastText Chinese / fastText English
|
- Model sources: ConceptNet Numberbatch (77-language aligned) / fastText Chinese / fastText English
|
||||||
- Configured via `core.agent.embedding_model_path` (comma-separated multi-model)
|
- Configured via `core.agent.embedding_model_path` (comma-separated multi-model)
|
||||||
- Path containing `numberbatch` → auto-download ConceptNet; `cc.zh.` → fastText Chinese; `cc.en.` → fastText English
|
- Path containing `numberbatch` → auto-download ConceptNet; `cc.zh.` → fastText Chinese; `cc.en.` → fastText English
|
||||||
@ -196,28 +207,32 @@ single typo silently breaks reference binding with no error anywhere in the chai
|
|||||||
also gained `sentence_text`: media references hang off a sentence, so with no sentence there is
|
also gained `sentence_text`: media references hang off a sentence, so with no sentence there is
|
||||||
nowhere to attach them.
|
nowhere to attach them.
|
||||||
|
|
||||||
### Media Memory (since v1.1.0)
|
### Media Memory (since v1.2.0: first-class memory blocks)
|
||||||
|
|
||||||
`internal/memory/media/` — `Store`, content-addressed (CAS)
|
Media is not attached content but a **first-class memory node**: `internal/memory/media/` is a
|
||||||
|
content-addressed store (CAS), and graph `block` nodes carry its digest plus its own vector, while
|
||||||
|
structural edges (e.g. `sentence --contains--> block`) express ownership.
|
||||||
|
|
||||||
| Concern | Approach | Why |
|
| Concern | Approach | Why |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Addressing | sha256 digest; metadata in SQLite, blobs on disk | Identical bytes stored once; metadata must be queryable, blobs must not live in the database |
|
| Addressing | sha256 digest; metadata in SQLite, blobs on disk (`blobs/<first2>/<rest>`, two-level fanout) | Identical bytes stored once; metadata must be queryable, blobs must not live in the database |
|
||||||
| Integrity | Every `Get` re-verifies the digest | Silently returning corrupt data on disk damage is far worse than an error |
|
| Integrity | Every `Get` re-verifies the digest | Silently returning corrupt data on disk damage is far worse than an error |
|
||||||
| Write atomicity | `.tmp` + rename | A half-written file taken as complete content would permanently poison that digest |
|
| Write atomicity | `.tmp` + rename | A half-written file taken as complete content would permanently poison that digest |
|
||||||
| References | `owner_kind/owner_id/digest` composite primary key, `AddRef` idempotent | Three owner kinds: `context` (context events), `document`, `graph_sentence` |
|
| Retrieval | Blocks carry **their own multimodal vector and fingerprint** and are searched directly | No description text is needed as an intermediary |
|
||||||
| GC | Two-stage with `minAge`, **referenced items are never deleted** | Description text stays in the text layers while blobs may be evicted — semantic memory and byte cache are decoupled |
|
| Lifecycle | **No separate GC, no refcounts, no keep-set**; deleting the block deletes the content | Media is a memory node, not a cache that needs keeping alive |
|
||||||
|
|
||||||
**How media is represented in plain-text memory** is the marker `[<mime> <short digest>] <description>`:
|
**Description-based indexing is gone**: the old implementation embedded a
|
||||||
|
`[<mime> <short digest>] <description>` marker in the body and treated the description as the
|
||||||
|
semantic memory (retrieval used it). That path was removed wholesale in v1.2.0: a description is
|
||||||
|
second-hand model output, and retrieving "someone else's paraphrase of an image" is strictly worse
|
||||||
|
than retrieving the image's own vector. Images are now retrieved only by their own vector in the
|
||||||
|
unified space, and no media marker is written into the body.
|
||||||
|
|
||||||
```
|
**Cross-space vector migration**: media rows store their vector together with `vec_model` (the space
|
||||||
[image/png a1b2c3d4e5f6] a purple-blue-red three-band chart
|
fingerprint). At startup `reembedStaleMedia()` recomputes and **writes back** every row whose
|
||||||
```
|
`vec_model` is empty (never embedded) or differs from the current space (model/dimension switched).
|
||||||
|
Modalities outside the space return `ErrModalityUnsupported` — the kernel **never substitutes
|
||||||
Why it must ride on text: `Doc.Content`, `sentences.text` and text memory's `Input` are all strings
|
another model's vector**.
|
||||||
— there is no field to carry structured data. **The description text is the durable semantic
|
|
||||||
memory** (retrieval uses it); the digest is the key back to the bytes (reverse lookup uses it).
|
|
||||||
After capacity GC evicts a blob, the description remains in the L0/L2/L3 text.
|
|
||||||
|
|
||||||
The media store is **optional throughout**: with `core.memory.media.enabled=false` or no
|
The media store is **optional throughout**: with `core.memory.media.enabled=false` or no
|
||||||
configuration, the whole chain silently degrades to plain-text behaviour — no errors, no panics.
|
configuration, the whole chain silently degrades to plain-text behaviour — no errors, no panics.
|
||||||
@ -292,7 +307,7 @@ VM built-ins: `json.encode` / `json.decode` / `log` / `http_get` / `http_post`.
|
|||||||
| Method | Registration Mechanism | Compilation | Usage |
|
| Method | Registration Mechanism | Compilation | Usage |
|
||||||
|--------|----------------------|-------------|-------|
|
|--------|----------------------|-------------|-------|
|
||||||
| Built-in | `init()` → `RegisterFactory` | `internal/plugins/` compiled into kernel | webui/cli/timer/mcp etc. |
|
| Built-in | `init()` → `RegisterFactory` | `internal/plugins/` compiled into kernel | webui/cli/timer/mcp etc. |
|
||||||
| External subprocess plugin | Handshake + stdio JSON-RPC reverse registration | `plugindev build` → `plugin.bin` (ordinary Go binary) | qq/browser/files etc. |
|
| External subprocess plugin | Handshake + stdio JSON-RPC reverse registration | `hmapdev build` → `plugin.bin` (ordinary Go binary) | qq/browser/files etc. |
|
||||||
| Lua script plugin | Execute `main.lua` to register tools | No compilation, takes effect after restart/reload | luademo 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 |
|
| SKILL plugin | Parse `SKILL.md` | Markdown definition | Loaded via clawhubadapter |
|
||||||
|
|
||||||
@ -311,7 +326,7 @@ Lua script plugin loading: `internal/plugin/` → the gopher-lua interpreter exe
|
|||||||
| Dimension | Built-in Plugin | External Plugin |
|
| Dimension | Built-in Plugin | External Plugin |
|
||||||
|-----------|----------------|-----------------|
|
|-----------|----------------|-----------------|
|
||||||
| Registration | `init()` calls `plugin.RegisterFactory(name, factory)` | Implements `NewPluginFactory(name, config) (sdk.Plugin, error)` entry function |
|
| Registration | `init()` calls `plugin.RegisterFactory(name, factory)` | Implements `NewPluginFactory(name, config) (sdk.Plugin, error)` entry function |
|
||||||
| Compilation | Compiled into `homed` binary, no separate build | Compiled via `plugindev build` to `plugin.bin` (ordinary Go binary, zero cgo); the kernel spawns it as a subprocess |
|
| Compilation | Compiled into `homed` binary, no separate build | Compiled via `hmapdev build` to `plugin.bin` (ordinary Go binary, zero cgo); the kernel spawns it as a subprocess |
|
||||||
| Distribution | Bundled with kernel, not independently installable | `.hmap` package (ZIP archive), installed via WebUI or pluginmgr API |
|
| Distribution | Bundled with kernel, not independently installable | `.hmap` package (ZIP archive), installed via WebUI or pluginmgr API |
|
||||||
| Metadata | `plugin.RegisterPluginMeta()` for display name | `plugin.json` manifest file (name, version, entry, platforms, capabilities, etc.) |
|
| Metadata | `plugin.RegisterPluginMeta()` for display name | `plugin.json` manifest file (name, version, entry, platforms, capabilities, etc.) |
|
||||||
| Plugin directory | No separate directory, compiled into binary | `plugins/<name>/` independent directory with `plugin.json` + `plugin.bin` |
|
| Plugin directory | No separate directory, compiled into binary | `plugins/<name>/` independent directory with `plugin.json` + `plugin.bin` |
|
||||||
|
|||||||
@ -29,75 +29,80 @@ type Plugin interface {
|
|||||||
|
|
||||||
| Method | Use Case | Complexity |
|
| Method | Use Case | Complexity |
|
||||||
|--------|----------|------------|
|
|--------|----------|------------|
|
||||||
| **Subprocess plugin (recommended)** | Independently distributed third-party plugins | Medium, generated using `plugindev` toolchain |
|
| **Subprocess plugin (recommended)** | Independently distributed third-party plugins | Medium, generated using `hmapdev` toolchain |
|
||||||
| **Built-in plugin** | Released with HomeAgent | Simple, requires merging into main repo |
|
| **Built-in plugin** | Released with HomeAgent | Simple, requires merging into main repo |
|
||||||
| **Lua script plugin** | Lightweight rapid prototyping | Simple, generated using `plugindev init --lua` |
|
| **Lua script plugin** | Lightweight rapid prototyping | Simple, generated using `hmapdev init --lua` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<img src="../../assets/branding/mascot-xiaozhai.webp" width="20" style="border-radius:50%;vertical-align:middle"> :
|
<img src="../../assets/branding/mascot-xiaozhai.webp" width="20" style="border-radius:50%;vertical-align:middle"> :
|
||||||
|
|
||||||
## 1. Quick Start: Using the plugindev Toolchain
|
## 1. Quick Start: Using the hmapdev Toolchain
|
||||||
|
|
||||||
`plugindev` is the unified plugin development toolchain provided in the SDK repository, supporting both Go and Lua plugin types.
|
`hmapdev` is the unified plugin development toolchain provided in the SDK repository, supporting both Go and Lua
|
||||||
|
plugin types, and producing `.hmap` plugin bundles (the tool is named after that package format).
|
||||||
|
|
||||||
|
> Rename note: as of 1.2.0 the toolchain was renamed from `plugindev` to `hmapdev`; the SDK store moved from
|
||||||
|
> `~/.homeagent/plugindev/sdk` to `~/.homeagent/hmapdev/sdk` (the old directory keeps working automatically).
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd homeagent-sdk/tools/plugindev
|
cd homeagent-sdk/tools/hmapdev
|
||||||
go build -o plugindev
|
go build -o hmapdev
|
||||||
# Add plugindev to PATH or use directly
|
# Add hmapdev to PATH or use directly
|
||||||
|
# Prebuilt binaries also ship as release assets (hmapdev_linux_amd64, ...)
|
||||||
```
|
```
|
||||||
|
|
||||||
### SDK Version Management
|
### SDK Version Management
|
||||||
|
|
||||||
`plugindev sdk` manages local SDK versions:
|
`hmapdev sdk` manages local SDK versions:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev sdk list # list installed SDK versions
|
hmapdev sdk list # list installed SDK versions
|
||||||
plugindev sdk current # show current SDK version
|
hmapdev sdk current # show current SDK version
|
||||||
plugindev sdk latest # show latest available version
|
hmapdev sdk latest # show latest available version
|
||||||
plugindev sdk install v0.8.0 # install a specific version
|
hmapdev sdk install v1.2.0 # install a specific version
|
||||||
plugindev sdk use v0.8.0 # switch to a version
|
hmapdev sdk use v1.2.0 # switch to a version
|
||||||
plugindev sdk path # show current SDK path
|
hmapdev sdk path # show current SDK path
|
||||||
```
|
```
|
||||||
|
|
||||||
SDK is stored at `~/.homeagent/plugindev/sdk/<version>/`; `plugindev init` reads the current SDK version for `go.mod`.
|
SDK is stored at `~/.homeagent/hmapdev/sdk/<version>/`; `hmapdev init` reads the current SDK version for `go.mod`.
|
||||||
|
|
||||||
### Source Debugging
|
### Source Debugging
|
||||||
|
|
||||||
`plugindev debug` interprets plugin source and prints a call trace, no compilation environment needed:
|
`hmapdev debug` interprets plugin source and prints a call trace, no compilation environment needed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev debug [dir] # dir defaults to the current directory
|
hmapdev debug [dir] # dir defaults to the current directory
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating a Go Plugin
|
### Creating a Go Plugin
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev init myplugin
|
hmapdev init myplugin
|
||||||
cd myplugin
|
cd myplugin
|
||||||
# Edit plugin code
|
# Edit plugin code
|
||||||
vim plugin.go
|
vim plugin.go
|
||||||
# Build and package (default is a multi-platform bundle, see below)
|
# Build and package (default is a multi-platform bundle, see below)
|
||||||
plugindev build
|
hmapdev build
|
||||||
# Output: dist/myplugin_bundle.hmap
|
# Output: dist/myplugin_bundle.hmap
|
||||||
# Single-platform build:
|
# Single-platform build:
|
||||||
plugindev build --no-bundle
|
hmapdev build --no-bundle
|
||||||
# Output: dist/myplugin_linux_amd64.hmap (or windows_amd64)
|
# Output: dist/myplugin_linux_amd64.hmap (or windows_amd64)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating a Lua Plugin
|
### Creating a Lua Plugin
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev init myluaplugin --lua
|
hmapdev init myluaplugin --lua
|
||||||
cd myluaplugin
|
cd myluaplugin
|
||||||
# Edit plugin code
|
# Edit plugin code
|
||||||
vim main.lua
|
vim main.lua
|
||||||
# Local test
|
# Local test
|
||||||
lua main.lua
|
lua main.lua
|
||||||
# Build and package
|
# Build and package
|
||||||
plugindev build
|
hmapdev build
|
||||||
# Output: dist/myluaplugin_lua.hmap
|
# Output: dist/myluaplugin_lua.hmap
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -128,16 +133,16 @@ myluaplugin/
|
|||||||
|
|
||||||
### Build & Package
|
### Build & Package
|
||||||
|
|
||||||
`plugindev build` automatically handles compilation and packaging:
|
`hmapdev build` automatically handles compilation and packaging:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd myplugin
|
cd myplugin
|
||||||
plugindev build # default bundle mode (multi-platform)
|
hmapdev build # default bundle mode (multi-platform)
|
||||||
plugindev build --no-bundle # single-target build (per plg.json targets)
|
hmapdev build --no-bundle # single-target build (per plg.json targets)
|
||||||
plugindev build --target linux/amd64 # append a target on top of plg.json targets
|
hmapdev build --target linux/amd64 # append a target on top of plg.json targets
|
||||||
plugindev build --outdir dist # output directory (default: dist)
|
hmapdev build --outdir dist # output directory (default: dist)
|
||||||
plugindev build --sdk-path <path> # SDK path override (go.mod replace)
|
hmapdev build --sdk-path <path> # SDK path override (go.mod replace)
|
||||||
plugindev build --replace <mod@path> # append a go.mod replace directive (repeatable)
|
hmapdev build --replace <mod@path> # append a go.mod replace directive (repeatable)
|
||||||
```
|
```
|
||||||
|
|
||||||
Execution process:
|
Execution process:
|
||||||
@ -171,7 +176,7 @@ the kernel picks the one matching the current platform and renames it to `plugin
|
|||||||
> - `plugin.so` / `plugin.dylib` / `plugin.dll` are **no longer loaded**. The new kernel
|
> - `plugin.so` / `plugin.dylib` / `plugin.dll` are **no longer loaded**. The new kernel
|
||||||
> skips legacy artifacts with an actionable error instead of crashing.
|
> skips legacy artifacts with an actionable error instead of crashing.
|
||||||
> - **Business code needs no changes** — the public SDK interface is unchanged; just
|
> - **Business code needs no changes** — the public SDK interface is unchanged; just
|
||||||
> rebuild with the new `plugindev`.
|
> rebuild with the new `hmapdev` (formerly `plugindev`).
|
||||||
> - The `entry` field in `plg.json` is **meaningless for Go plugins** now (leaving
|
> - The `entry` field in `plg.json` is **meaningless for Go plugins** now (leaving
|
||||||
> `plugin.so` there is harmless); it only distinguishes Lua plugins.
|
> `plugin.so` there is harmless); it only distinguishes Lua plugins.
|
||||||
> - Artifacts no longer need cgo, so cross-compiling requires no target C toolchain.
|
> - Artifacts no longer need cgo, so cross-compiling requires no target C toolchain.
|
||||||
@ -180,12 +185,12 @@ the kernel picks the one matching the current platform and renames it to `plugin
|
|||||||
|
|
||||||
### Build Targets & Multi-platform 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.
|
**`hmapdev 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
|
```bash
|
||||||
plugindev build # default bundle, outputs dist/myplugin_bundle.hmap
|
hmapdev build # default bundle, outputs dist/myplugin_bundle.hmap
|
||||||
plugindev build --bundle # explicitly enable bundle (same as above)
|
hmapdev build --bundle # explicitly enable bundle (same as above)
|
||||||
plugindev build --no-bundle # disable bundle, build per plg.json targets
|
hmapdev build --no-bundle # disable bundle, build per plg.json targets
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
@ -275,7 +280,7 @@ func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, e
|
|||||||
|
|
||||||
### Entry Point
|
### Entry Point
|
||||||
|
|
||||||
`plugindev init` generates `plugin.go` with the `NewPlugin` export function directly,
|
`hmapdev init` generates `plugin.go` with the `NewPlugin` export function directly,
|
||||||
which is the entry point when the kernel loads the plugin:
|
which is the entry point when the kernel loads the plugin:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -284,7 +289,7 @@ func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
At build time, `plugindev build` auto-generates subprocess runtime code
|
At build time, `hmapdev build` auto-generates subprocess runtime code
|
||||||
(`z_proc_gen.go` for the platform-independent part, plus `z_proc_shm_unix.go` /
|
(`z_proc_gen.go` for the platform-independent part, plus `z_proc_shm_unix.go` /
|
||||||
`z_proc_shm_windows.go`). All three platforms share the same entry point and the same
|
`z_proc_shm_windows.go`). All three platforms share the same entry point and the same
|
||||||
RPC logic; only the cross-process resource-passing mechanism differs (inherited fds on
|
RPC logic; only the cross-process resource-passing mechanism differs (inherited fds on
|
||||||
|
|||||||
@ -132,11 +132,20 @@ eventLoop() → processTextInput()
|
|||||||
→ 三元组 → GraphDB.Commit
|
→ 三元组 → GraphDB.Commit
|
||||||
```
|
```
|
||||||
|
|
||||||
### 向量化:预训练词嵌入 + TF-IDF 回退
|
### 向量化:统一多模态空间(主)→ 词嵌入 → TF-IDF(回退)
|
||||||
|
|
||||||
所有向量化统一使用 `StaticEmbedder`(`internal/memory/static_embedder.go`):
|
向量化按可用性分三层降级,**每一层缺位都明确报错,不静默假装成功**:
|
||||||
|
|
||||||
**主策略 — 预训练词嵌入(词对齐 300 维)**
|
**① 统一多模态空间(主路径,v1.2.0 起)**
|
||||||
|
文本与图像共用**同一模型、同一维度、同一指纹**(默认 `chineseclip`:512 维、Apache-2.0、中文原生;
|
||||||
|
亦可选 `qwen3vl` 或外部 `http` provider)。provider 经 `pkg/embedding` 公共 SPI 注册,
|
||||||
|
**内核不硬编码任何模型**。向量与指纹一起持久化(`dense_fp` / `vec_model`),
|
||||||
|
与当前指纹不一致即触发重算;融合时只接受**同指纹且同维度**的块向量
|
||||||
|
(跨坐标系的向量混进去会算出两边都不像的方向)。
|
||||||
|
|
||||||
|
**② 词嵌入(文本兜底)** — `StaticEmbedder`(`internal/memory/static_embedder.go`):
|
||||||
|
|
||||||
|
**模型来源**(词对齐 300 维)
|
||||||
- 模型来源:ConceptNet Numberbatch(77 语对齐)/ fastText 中文 / fastText 英文
|
- 模型来源:ConceptNet Numberbatch(77 语对齐)/ fastText 中文 / fastText 英文
|
||||||
- 通过 `core.agent.embedding_model_path` 配置(逗号分隔多模型)
|
- 通过 `core.agent.embedding_model_path` 配置(逗号分隔多模型)
|
||||||
- 路径名含 `numberbatch` → 自动下载 ConceptNet,含 `cc.zh.` → fastText 中文,含 `cc.en.` → fastText 英文
|
- 路径名含 `numberbatch` → 自动下载 ConceptNet,含 `cc.zh.` → fastText 中文,含 `cc.en.` → fastText 英文
|
||||||
@ -194,30 +203,31 @@ eventLoop() → processTextInput()
|
|||||||
要求调用方知道格式,等于让一个拼写错误静默切断引用绑定而全链路无人报错。
|
要求调用方知道格式,等于让一个拼写错误静默切断引用绑定而全链路无人报错。
|
||||||
`memory_commit` 同时新增 `sentence_text`:媒体引用挂在句子上,没有句子就无处可挂。
|
`memory_commit` 同时新增 `sentence_text`:媒体引用挂在句子上,没有句子就无处可挂。
|
||||||
|
|
||||||
### 媒体记忆(v1.1.0 起)
|
### 媒体记忆(v1.2.0 起:一等记忆块)
|
||||||
|
|
||||||
`internal/memory/media/` — `Store`,内容寻址(CAS)
|
媒体不是外挂内容,而是**记忆的一等节点**:`internal/memory/media/` 是内容寻址仓储(CAS),
|
||||||
|
图数据库里的 block 节点携带它的 digest 与向量,结构边(如 `sentence --contains--> block`)表达归属。
|
||||||
|
|
||||||
| 关注点 | 做法 | 为何 |
|
| 关注点 | 做法 | 为何 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 寻址 | sha256 digest,元数据在 SQLite、blob 在磁盘 | 相同字节只存一份;元数据要可查询,blob 不该进数据库 |
|
| 寻址 | sha256 digest;元数据在 SQLite,blob 在磁盘(`blobs/<前2位>/<其余>` 两级分桶) | 相同字节只存一份;元数据要可查询,blob 不该进数据库 |
|
||||||
| 完整性 | 每次 `Get` 重校 digest | 磁盘损坏时静默返回脏数据比报错危险得多 |
|
| 完整性 | 每次 `Get` 重校 digest | 磁盘损坏时静默返回脏数据比报错危险得多 |
|
||||||
| 写入原子性 | `.tmp` + rename | 半个文件被当成完整内容会永久污染那个 digest |
|
| 写入原子性 | `.tmp` + rename | 半个文件被当成完整内容会永久污染那个 digest |
|
||||||
| 引用 | `owner_kind/owner_id/digest` 三元组主键,`AddRef` 幂等 | 三个 owner 类型:`context`(上下文事件)、`document`(文档)、`graph_sentence`(图谱句子) |
|
| 检索 | 块携带**自己的多模态向量与指纹**,直接参与向量检索 | 不需要描述文本做中介 |
|
||||||
| GC | 两阶段 + `minAge`,**有引用者绝不删** | 描述文本留在文本层,blob 可淘汰——语义记忆与字节缓存分离 |
|
| 生命周期 | **无独立 GC、无引用计数、无 keep-set**;删除块即删内容 | 媒体是记忆节点,不是需要保活的缓存 |
|
||||||
|
|
||||||
**媒体在纯文本记忆里的表示**是标记 `[<mime> <短digest>] <描述>`:
|
**不再有描述式索引**:旧实现在正文里写 `[<mime> <短digest>] <描述>` 标记,并把描述文本当作语义记忆
|
||||||
|
(检索靠描述)。该机制已在 v1.2.0 整体拆除:描述是模型生成的二手信息,
|
||||||
|
检索“别人转述的图片”不如检索图片自己的向量。现在图片只按自己的统一空间向量被检索,
|
||||||
|
正文里不再有 media marker。
|
||||||
|
|
||||||
```
|
**跨空间向量迁移**:媒体行的向量带 `vec_model`(空间指纹)。启动时
|
||||||
[image/png a1b2c3d4e5f6] 一张紫蓝红三色带图
|
`reembedStaleMedia()` 把 `vec_model` 为空(从未嵌入)或与当前空间不一致(换过模型/维度)的行
|
||||||
```
|
批量重算并**写回库**;模态不在本空间覆盖范围时返回 `ErrModalityUnsupported`,
|
||||||
|
**绝不拿别的模型的向量顶替**。
|
||||||
|
|
||||||
之所以必须借文本承载:`Doc.Content`、`sentences.text`、文本记忆的 `Input` 全是字符串,
|
媒体存储全程可选:`core.memory.media.enabled=false` 或未配置时,整条链路静默退化为纯文本行为,
|
||||||
没有字段能挂结构化数据。**描述文本才是持久的语义记忆**(检索靠它),digest 是回到字节的
|
不报错不 panic。
|
||||||
钥匙(反查靠它)。blob 被容量 GC 淘汰后,描述仍留在 L0/L2/L3 的文本里。
|
|
||||||
|
|
||||||
媒体存储**全程可选**:`core.memory.media.enabled=false` 或未配置时,整条链路静默退化为
|
|
||||||
纯文本行为,不报错不 panic。
|
|
||||||
|
|
||||||
### 其他记忆层
|
### 其他记忆层
|
||||||
|
|
||||||
@ -287,7 +297,7 @@ VM 内置 `json.encode` / `json.decode` / `log` / `http_get` / `http_post`。
|
|||||||
| 方式 | 注册机制 | 编译 | 用途 |
|
| 方式 | 注册机制 | 编译 | 用途 |
|
||||||
|------|----------|------|------|
|
|------|----------|------|------|
|
||||||
| 内置插件 | `init()` → `RegisterFactory` | `internal/plugins/` 编译进内核 | webui/cli/timer/mcp 等 |
|
| 内置插件 | `init()` → `RegisterFactory` | `internal/plugins/` 编译进内核 | webui/cli/timer/mcp 等 |
|
||||||
| 外部子进程插件 | 握手 + stdio JSON-RPC 反向注册 | `plugindev build` → `plugin.bin`(普通 Go 二进制) | qq/browser/files 等 |
|
| 外部子进程插件 | 握手 + stdio JSON-RPC 反向注册 | `hmapdev build` → `plugin.bin`(普通 Go 二进制) | qq/browser/files 等 |
|
||||||
| Lua 脚本插件 | 执行 `main.lua` 注册工具 | 无需编译,重启/重载生效 | luademo 等 |
|
| Lua 脚本插件 | 执行 `main.lua` 注册工具 | 无需编译,重启/重载生效 | luademo 等 |
|
||||||
| SKILL 插件 | 解析 `SKILL.md` | Markdown 定义 | clawhubadapter 兼容加载 |
|
| SKILL 插件 | 解析 `SKILL.md` | Markdown 定义 | clawhubadapter 兼容加载 |
|
||||||
|
|
||||||
@ -306,7 +316,7 @@ Lua 脚本插件加载:`internal/plugin/` → gopher-lua 解释器执行 `main
|
|||||||
| 维度 | 内置插件 | 外部插件 |
|
| 维度 | 内置插件 | 外部插件 |
|
||||||
|------|----------|----------|
|
|------|----------|----------|
|
||||||
| 注册方式 | `init()` 调用 `plugin.RegisterFactory(name, factory)` | 实现 `NewPluginFactory(name, config) (sdk.Plugin, error)` 入口函数 |
|
| 注册方式 | `init()` 调用 `plugin.RegisterFactory(name, factory)` | 实现 `NewPluginFactory(name, config) (sdk.Plugin, error)` 入口函数 |
|
||||||
| 编译方式 | 编译进 `homed` 二进制,无需独立编译 | 通过 `plugindev build` 编译为 `plugin.bin`(普通 Go 二进制,零 cgo),内核 spawn 为子进程 |
|
| 编译方式 | 编译进 `homed` 二进制,无需独立编译 | 通过 `hmapdev build` 编译为 `plugin.bin`(普通 Go 二进制,零 cgo),内核 spawn 为子进程 |
|
||||||
| 分发方式 | 随内核分发,不可独立安装/卸载 | `.hmap` 包(ZIP 归档),通过 WebUI 或 pluginmgr API 安装 |
|
| 分发方式 | 随内核分发,不可独立安装/卸载 | `.hmap` 包(ZIP 归档),通过 WebUI 或 pluginmgr API 安装 |
|
||||||
| 元数据 | 通过 `plugin.RegisterPluginMeta()` 注册显示名 | `plugin.json` manifest 文件(name, version, entry, platforms, capabilities 等) |
|
| 元数据 | 通过 `plugin.RegisterPluginMeta()` 注册显示名 | `plugin.json` manifest 文件(name, version, entry, platforms, capabilities 等) |
|
||||||
| 插件目录 | 无独立目录,编译进二进制 | `plugins/<name>/` 独立目录,包含 `plugin.json` + `plugin.bin` |
|
| 插件目录 | 无独立目录,编译进二进制 | `plugins/<name>/` 独立目录,包含 `plugin.json` + `plugin.bin` |
|
||||||
|
|||||||
@ -30,75 +30,80 @@ type Plugin interface {
|
|||||||
|
|
||||||
| 方式 | 适用场景 | 复杂度 |
|
| 方式 | 适用场景 | 复杂度 |
|
||||||
|------|---------|--------|
|
|------|---------|--------|
|
||||||
| **子进程插件(推荐)** | 独立分发的第三方插件 | 中等,使用 `plugindev` 工具链生成 |
|
| **子进程插件(推荐)** | 独立分发的第三方插件 | 中等,使用 `hmapdev` 工具链生成 |
|
||||||
| **内置插件** | 随 HomeAgent 一起发布 | 简单,需合入主仓库 |
|
| **内置插件** | 随 HomeAgent 一起发布 | 简单,需合入主仓库 |
|
||||||
| **Lua 脚本插件** | 轻量快速原型 | 简单,使用 `plugindev init --lua` 生成 |
|
| **Lua 脚本插件** | 轻量快速原型 | 简单,使用 `hmapdev init --lua` 生成 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<img src="../../assets/branding/mascot-xiaozhai.webp" width="20" style="border-radius:50%;vertical-align:middle"> :
|
<img src="../../assets/branding/mascot-xiaozhai.webp" width="20" style="border-radius:50%;vertical-align:middle"> :
|
||||||
|
|
||||||
## 一、快速开始:使用 plugindev 工具链
|
## 一、快速开始:使用 hmapdev 工具链
|
||||||
|
|
||||||
`plugindev` 是 SDK 仓库提供的统一插件开发工具链,支持 Go 和 Lua 两种插件类型。
|
`hmapdev` 是 SDK 仓库提供的统一插件开发工具链,支持 Go 和 Lua 两种插件类型,
|
||||||
|
最终产出 `.hmap` 插件包(工具名即来自这个包格式)。
|
||||||
|
|
||||||
|
> 改名说明:1.2.0 起工具链由 `plugindev` 更名为 `hmapdev`;SDK 存储目录同时由
|
||||||
|
> `~/.homeagent/plugindev/sdk` 迁到 `~/.homeagent/hmapdev/sdk`(旧目录会自动继续沿用)。
|
||||||
|
|
||||||
### 安装
|
### 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd homeagent-sdk/tools/plugindev
|
cd homeagent-sdk/tools/hmapdev
|
||||||
go build -o plugindev
|
go build -o hmapdev
|
||||||
# 将 plugindev 加入 PATH 或直接使用
|
# 将 hmapdev 加入 PATH 或直接使用
|
||||||
|
# 也可从 SDK 的 release 附件下载预编译二进制(hmapdev_linux_amd64 等)
|
||||||
```
|
```
|
||||||
|
|
||||||
### SDK 版本管理
|
### SDK 版本管理
|
||||||
|
|
||||||
`plugindev sdk` 子命令管理本地 SDK 版本:
|
`hmapdev sdk` 子命令管理本地 SDK 版本:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev sdk list # 列出已安装的 SDK 版本
|
hmapdev sdk list # 列出已安装的 SDK 版本
|
||||||
plugindev sdk current # 显示当前使用的 SDK 版本
|
hmapdev sdk current # 显示当前使用的 SDK 版本
|
||||||
plugindev sdk latest # 显示最新可用版本
|
hmapdev sdk latest # 显示最新可用版本
|
||||||
plugindev sdk install v0.8.0 # 安装指定版本
|
hmapdev sdk install v1.2.0 # 安装指定版本
|
||||||
plugindev sdk use v0.8.0 # 切换使用版本
|
hmapdev sdk use v1.2.0 # 切换使用版本
|
||||||
plugindev sdk path # 显示当前 SDK 路径
|
hmapdev sdk path # 显示当前 SDK 路径
|
||||||
```
|
```
|
||||||
|
|
||||||
SDK 存储在 `~/.homeagent/plugindev/sdk/<version>/`,`plugindev init` 自动读取当前 SDK 版本填充 `go.mod`。
|
SDK 存储在 `~/.homeagent/hmapdev/sdk/<version>/`,`hmapdev init` 自动读取当前 SDK 版本填充 `go.mod`。
|
||||||
|
|
||||||
### 源码调试
|
### 源码调试
|
||||||
|
|
||||||
`plugindev debug` 直接用解释器执行插件源码并输出调用轨迹,无需编译环境:
|
`hmapdev debug` 直接用解释器执行插件源码并输出调用轨迹,无需编译环境:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev debug [dir] # dir 默认当前目录
|
hmapdev debug [dir] # dir 默认当前目录
|
||||||
```
|
```
|
||||||
|
|
||||||
### 创建 Go 插件
|
### 创建 Go 插件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev init myplugin
|
hmapdev init myplugin
|
||||||
cd myplugin
|
cd myplugin
|
||||||
# 编辑插件代码
|
# 编辑插件代码
|
||||||
vim plugin.go
|
vim plugin.go
|
||||||
# 编译打包
|
# 编译打包
|
||||||
plugindev build # 默认多平台 bundle(见下节)
|
hmapdev build # 默认多平台 bundle(见下节)
|
||||||
# 输出: dist/myplugin_bundle.hmap
|
# 输出: dist/myplugin_bundle.hmap
|
||||||
# 单平台构建:
|
# 单平台构建:
|
||||||
plugindev build --no-bundle
|
hmapdev build --no-bundle
|
||||||
# 输出: dist/myplugin_linux_amd64.hmap (或 windows_amd64)
|
# 输出: dist/myplugin_linux_amd64.hmap (或 windows_amd64)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 创建 Lua 插件
|
### 创建 Lua 插件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev init myluaplugin --lua
|
hmapdev init myluaplugin --lua
|
||||||
cd myluaplugin
|
cd myluaplugin
|
||||||
# 编辑插件代码
|
# 编辑插件代码
|
||||||
vim main.lua
|
vim main.lua
|
||||||
# 本地测试
|
# 本地测试
|
||||||
lua main.lua
|
lua main.lua
|
||||||
# 编译打包
|
# 编译打包
|
||||||
plugindev build
|
hmapdev build
|
||||||
# 输出: dist/myluaplugin_lua.hmap
|
# 输出: dist/myluaplugin_lua.hmap
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -129,16 +134,16 @@ myluaplugin/
|
|||||||
|
|
||||||
### 编译打包
|
### 编译打包
|
||||||
|
|
||||||
`plugindev build` 会自动完成编译和打包:
|
`hmapdev build` 会自动完成编译和打包:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd myplugin
|
cd myplugin
|
||||||
plugindev build # 默认 bundle 模式(多平台合集)
|
hmapdev build # 默认 bundle 模式(多平台合集)
|
||||||
plugindev build --no-bundle # 单平台构建(仅当前 plg.json targets)
|
hmapdev build --no-bundle # 单平台构建(仅当前 plg.json targets)
|
||||||
plugindev build --target linux/amd64 # 在 targets 基础上追加一个目标
|
hmapdev build --target linux/amd64 # 在 targets 基础上追加一个目标
|
||||||
plugindev build --outdir dist # 指定输出目录(默认 dist)
|
hmapdev build --outdir dist # 指定输出目录(默认 dist)
|
||||||
plugindev build --sdk-path <path> # 指定 SDK 路径(覆盖 go.mod replace)
|
hmapdev build --sdk-path <path> # 指定 SDK 路径(覆盖 go.mod replace)
|
||||||
plugindev build --replace <mod@path> # 追加 go.mod replace 指令(可多次)
|
hmapdev build --replace <mod@path> # 追加 go.mod replace 指令(可多次)
|
||||||
```
|
```
|
||||||
|
|
||||||
执行过程:
|
执行过程:
|
||||||
@ -154,7 +159,7 @@ plugindev build --replace <mod@path> # 追加 go.mod replace 指令(可多次
|
|||||||
| 文件 | 用途 | 关键字段 |
|
| 文件 | 用途 | 关键字段 |
|
||||||
|------|------|---------|
|
|------|------|---------|
|
||||||
| `plg.json` | 项目元信息,由开发者维护 | `targets` — 单平台构建目标(如 `"linux/amd64,windows/amd64"`);`bundle` — 多平台合集开关(默认 `true`)|
|
| `plg.json` | 项目元信息,由开发者维护 | `targets` — 单平台构建目标(如 `"linux/amd64,windows/amd64"`);`bundle` — 多平台合集开关(默认 `true`)|
|
||||||
| `plugin.json` | 构建产物清单,`plugindev build` 自动生成 | `entry` — 入口文件名;`platforms` — 声明的支持平台 |
|
| `plugin.json` | 构建产物清单,`hmapdev build` 自动生成 | `entry` — 入口文件名;`platforms` — 声明的支持平台 |
|
||||||
|
|
||||||
每个目标生成单独的 `.hmap`。子进程插件是普通可执行文件,**不分平台后缀**:
|
每个目标生成单独的 `.hmap`。子进程插件是普通可执行文件,**不分平台后缀**:
|
||||||
|
|
||||||
@ -169,7 +174,7 @@ bundle 包内按 `plugin.bin.<goos>.<goarch>` 区分各平台,安装时内核
|
|||||||
>
|
>
|
||||||
> - `plugin.so` / `plugin.dylib` / `plugin.dll` **不再被加载**。新内核遇到旧产物
|
> - `plugin.so` / `plugin.dylib` / `plugin.dll` **不再被加载**。新内核遇到旧产物
|
||||||
> 会跳过并报可操作错误,不崩溃。
|
> 会跳过并报可操作错误,不崩溃。
|
||||||
> - **业务代码不需要改一行**——公开 SDK 接口零改动,只需用新版 `plugindev` 重编。
|
> - **业务代码不需要改一行**——公开 SDK 接口零改动,只需用新版 `hmapdev`(原 `plugindev`)重编。
|
||||||
> - `plg.json` 的 `entry` 字段对 Go 插件**已无意义**(写着 `plugin.so` 也无妨),
|
> - `plg.json` 的 `entry` 字段对 Go 插件**已无意义**(写着 `plugin.so` 也无妨),
|
||||||
> 它现在只用于区分 Lua 插件。
|
> 它现在只用于区分 Lua 插件。
|
||||||
> - 产物不再需要 cgo,交叉编译无需目标平台 C 工具链。
|
> - 产物不再需要 cgo,交叉编译无需目标平台 C 工具链。
|
||||||
@ -178,12 +183,12 @@ bundle 包内按 `plugin.bin.<goos>.<goarch>` 区分各平台,安装时内核
|
|||||||
|
|
||||||
### 构建目标与多平台打包(bundle)
|
### 构建目标与多平台打包(bundle)
|
||||||
|
|
||||||
**`plugindev build` 默认就是 bundle 模式**(`plg.json` 未显式写 `"bundle": false` 时):一次编译 linux/amd64 + darwin/amd64 + windows/amd64,生成包含所有平台二进制的单 `.hmap`,输出清单自动添加 `platforms` 字段。安装时核心自动选择当前平台的二进制,跳过其他平台。
|
**`hmapdev build` 默认就是 bundle 模式**(`plg.json` 未显式写 `"bundle": false` 时):一次编译 linux/amd64 + darwin/amd64 + windows/amd64,生成包含所有平台二进制的单 `.hmap`,输出清单自动添加 `platforms` 字段。安装时核心自动选择当前平台的二进制,跳过其他平台。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
plugindev build # 默认 bundle,输出 dist/myplugin_bundle.hmap
|
hmapdev build # 默认 bundle,输出 dist/myplugin_bundle.hmap
|
||||||
plugindev build --bundle # 显式开启 bundle(同上)
|
hmapdev build --bundle # 显式开启 bundle(同上)
|
||||||
plugindev build --no-bundle # 关闭 bundle,按 plg.json 的 targets 逐平台构建
|
hmapdev build --no-bundle # 关闭 bundle,按 plg.json 的 targets 逐平台构建
|
||||||
```
|
```
|
||||||
|
|
||||||
注意:
|
注意:
|
||||||
@ -273,7 +278,7 @@ func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, e
|
|||||||
|
|
||||||
### 入口点
|
### 入口点
|
||||||
|
|
||||||
`plugindev init` 生成的 `plugin.go` 中直接包含 `NewPlugin` 导出函数,它是内核加载插件时的入口:
|
`hmapdev init` 生成的 `plugin.go` 中直接包含 `NewPlugin` 导出函数,它是内核加载插件时的入口:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||||
@ -281,7 +286,7 @@ func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
编译时 `plugindev build` 自动生成子进程运行时代码(`z_proc_gen.go` 平台无关 + `z_proc_shm_unix.go` / `z_proc_shm_windows.go` 平台特定),无需手动编写。三平台共享同一入口与同一套 RPC 逻辑,仅跨进程资源传递机制不同(Unix 继承 fd,Windows 命名内核对象)。
|
编译时 `hmapdev build` 自动生成子进程运行时代码(`z_proc_gen.go` 平台无关 + `z_proc_shm_unix.go` / `z_proc_shm_windows.go` 平台特定),无需手动编写。三平台共享同一入口与同一套 RPC 逻辑,仅跨进程资源传递机制不同(Unix 继承 fd,Windows 命名内核对象)。
|
||||||
|
|
||||||
### PluginSDK 核心 API
|
### PluginSDK 核心 API
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import (
|
|||||||
// 所以选择:**原生 Windows 不提供 homed**。Windows 用户跑 WSL2——
|
// 所以选择:**原生 Windows 不提供 homed**。Windows 用户跑 WSL2——
|
||||||
// WSL2 里就是普通 linux/amd64,走与我们测试矩阵完全相同的那条路径。
|
// WSL2 里就是普通 linux/amd64,走与我们测试矩阵完全相同的那条路径。
|
||||||
//
|
//
|
||||||
// 注意范围:只有 homed 如此。plugindev 工具链仍可在 Windows 上运行
|
// 注意范围:只有 homed 如此。hmapdev 工具链仍可在 Windows 上运行
|
||||||
// (在 Windows 上开发、为 WSL 构建 linux 插件是合理工作流)。
|
// (在 Windows 上开发、为 WSL 构建 linux 插件是合理工作流)。
|
||||||
func requireSupportedPlatform() {
|
func requireSupportedPlatform() {
|
||||||
fmt.Fprintln(os.Stderr, "homed 不支持 Windows 原生运行。")
|
fmt.Fprintln(os.Stderr, "homed 不支持 Windows 原生运行。")
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> 状态:**完成 v3**(2026-09-06)——v2 的迁移已上生产(内核 v1.0.0);v3 记录 v1.1.1 的公开接口**扩展**。
|
> 状态:**完成 v3**(2026-09-06)——v2 的迁移已上生产(内核 v1.0.0);v3 记录 v1.1.1 的公开接口**扩展**。
|
||||||
> 目的:钉死「暴露给外部插件的接口不变」这一约束的**合同面**——迁移前、迁移后外部插件看到/调用的 SDK 接口完全一致;
|
> 目的:钉死「暴露给外部插件的接口不变」这一约束的**合同面**——迁移前、迁移后外部插件看到/调用的 SDK 接口完全一致;
|
||||||
> 所有改造落在**核心(homed 侧)+ 工具链(plugindev)**,外部插件业务代码零改动,只需用新 plugindev 重编。
|
> 所有改造落在**核心(homed 侧)+ 工具链(hmapdev,当时名为 plugindev)**,外部插件业务代码零改动,只需用新工具链重编。
|
||||||
>
|
>
|
||||||
> **结果(已验证)**:`git diff third_party/homeagent-sdk/sdk/` 全程为空;17 个 `example/*/plugin.go` 逐字节未改
|
> **结果(已验证)**:`git diff third_party/homeagent-sdk/sdk/` 全程为空;17 个 `example/*/plugin.go` 逐字节未改
|
||||||
> (`git status example/` 无输出);生产 17 插件全部经子进程通道运行。
|
> (`git status example/` 无输出);生产 17 插件全部经子进程通道运行。
|
||||||
@ -11,7 +11,7 @@
|
|||||||
> 它要保的是「换运行模型不动业务代码」。迁移完成后,SDK 需要能随功能演进而扩展,
|
> 它要保的是「换运行模型不动业务代码」。迁移完成后,SDK 需要能随功能演进而扩展,
|
||||||
> 否则多模态这类能力永远到不了插件手上。解除的边界见 §九:**只增不减,签名不改**。
|
> 否则多模态这类能力永远到不了插件手上。解除的边界见 §九:**只增不减,签名不改**。
|
||||||
>
|
>
|
||||||
> 维护规则:每次改动公开 SDK 接口面 `third_party/homeagent-sdk/sdk/` 或模板 `tools/plugindev/templates/` 后,
|
> 维护规则:每次改动公开 SDK 接口面 `third_party/homeagent-sdk/sdk/` 或模板 `tools/hmapdev/templates/` 后,
|
||||||
> 必须同步更新本矩阵。
|
> 必须同步更新本矩阵。
|
||||||
>
|
>
|
||||||
> 权威编号:plan.md 第 11 节(11.1~11.9)。本文档只做接口面盘点,不做实现。
|
> 权威编号:plan.md 第 11 节(11.1~11.9)。本文档只做接口面盘点,不做实现。
|
||||||
@ -21,9 +21,9 @@
|
|||||||
## 一、迁移的形状(一句话)
|
## 一、迁移的形状(一句话)
|
||||||
|
|
||||||
```
|
```
|
||||||
今天: 外部插件 = example/*/plugin.go(纯 Go) ──plugindev c-shared──> plugin.so
|
今天: 外部插件 = example/*/plugin.go(纯 Go) ──hmapdev c-shared──> plugin.so
|
||||||
homed ──dlopen──> plugin.so(C ABI bridge:51 个整数 method id)
|
homed ──dlopen──> plugin.so(C ABI bridge:51 个整数 method id)
|
||||||
之后: 外部插件 = example/*/plugin.go(纯 Go,一行不改) ──plugindev go build──> plugin.bin
|
之后: 外部插件 = example/*/plugin.go(纯 Go,一行不改) ──hmapdev go build──> plugin.bin
|
||||||
homed ──spawn──> plugin.bin(stdio JSON-RPC + shm + eventfd)
|
homed ──spawn──> plugin.bin(stdio JSON-RPC + shm + eventfd)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -33,8 +33,8 @@
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 公开 SDK `third_party/homeagent-sdk/sdk/*.go` | ❌ 纯 Go | **不动**(接口面 = 合同) |
|
| 公开 SDK `third_party/homeagent-sdk/sdk/*.go` | ❌ 纯 Go | **不动**(接口面 = 合同) |
|
||||||
| 外部插件业务代码 `example/*/plugin.go` | ❌ 纯 Go(只 import 公开 SDK) | **不动**(只重编) |
|
| 外部插件业务代码 `example/*/plugin.go` | ❌ 纯 Go(只 import 公开 SDK) | **不动**(只重编) |
|
||||||
| bridge 模板 `tools/plugindev/templates.go` 的 `tmplLinuxBridge`/`tmplBridge` | ✅ cgo | **删除/替换**为 `tmplProcMain` |
|
| bridge 模板 `tools/hmapdev/templates.go` 的 `tmplLinuxBridge`/`tmplBridge` | ✅ cgo | **删除/替换**为 `tmplProcMain` |
|
||||||
| `plugindev` 构建命令 | c-shared | 改普通 `go build` |
|
| `hmapdev` 构建命令 | c-shared | 改普通 `go build` |
|
||||||
| homed `internal/plugin/cabi/`(1096 行) | cgo | 删(已归入 plan 迁移收尾 5.2) |
|
| homed `internal/plugin/cabi/`(1096 行) | cgo | 删(已归入 plan 迁移收尾 5.2) |
|
||||||
| homed `internal/plugin/registry.go` 加载分派 | — | 改:按 `entry` 分派 `.so`/`.bin` |
|
| homed `internal/plugin/registry.go` 加载分派 | — | 改:按 `entry` 分派 `.so`/`.bin` |
|
||||||
|
|
||||||
@ -300,7 +300,7 @@ C 结构体不好传函数指针(那是运气,任何人给 dispatch 加个 c
|
|||||||
|
|
||||||
## 七、接口冻结检查点(全部已通过)
|
## 七、接口冻结检查点(全部已通过)
|
||||||
|
|
||||||
1. ✅ **阶段 2(子进程通道原型)**:`plugindev` 重编 weather → `plugin.bin` → 端到端跑通。
|
1. ✅ **阶段 2(子进程通道原型)**:`hmapdev` 重编 weather → `plugin.bin` → 端到端跑通。
|
||||||
验收:weather 业务代码逐字节未改(`git status example/` 无输出)。
|
验收:weather 业务代码逐字节未改(`git status example/` 无输出)。
|
||||||
2. ✅ **阶段 3(共享内存)**:子进程并发改写 StageContext 丢失率 = 0%
|
2. ✅ **阶段 3(共享内存)**:子进程并发改写 StageContext 丢失率 = 0%
|
||||||
(`TestPlugin_FiveProcessesConcurrentAppendNoLostUpdate` 与
|
(`TestPlugin_FiveProcessesConcurrentAppendNoLostUpdate` 与
|
||||||
@ -349,7 +349,7 @@ tool output_send__qq result: 已通过 [qq] 通道发送: map[status:sent]
|
|||||||
|
|
||||||
### 3. 生成模板必须同步接线,否则是**全体外部插件编译失败**
|
### 3. 生成模板必须同步接线,否则是**全体外部插件编译失败**
|
||||||
|
|
||||||
公开接口加方法时,`tools/plugindev/templates/proc_main.go.tmpl` 里的 `procIO` /
|
公开接口加方法时,`tools/hmapdev/templates/proc_main.go.tmpl` 里的 `procIO` /
|
||||||
`procDocMemory` 若不实现新方法,就不满足接口——**每个外部插件都编不过**,是硬失败
|
`procDocMemory` 若不实现新方法,就不满足接口——**每个外部插件都编不过**,是硬失败
|
||||||
不是软降级。v1.1.1 这一层是被 `go test` 抓出来的(`internal/plugin/proc` 的两个
|
不是软降级。v1.1.1 这一层是被 `go test` 抓出来的(`internal/plugin/proc` 的两个
|
||||||
E2E 用例编译失败),不是靠人工检查发现的。
|
E2E 用例编译失败),不是靠人工检查发现的。
|
||||||
@ -366,7 +366,7 @@ E2E 用例编译失败),不是靠人工检查发现的。
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 存量插件源码零改动 | `cd example/<n> && go vet ./...`(17 个) | ✅ 17/17 通过 |
|
| 存量插件源码零改动 | `cd example/<n> && go vet ./...`(17 个) | ✅ 17/17 通过 |
|
||||||
| 旧产物仍能建链 | 用 SDK 0.9.2 编的 `plugin.bin` 跑 `TestRealPlugin_*` | ✅ 4/4 通过(握手校验 `ProtocolVersion=1`,不是 SDK 版本) |
|
| 旧产物仍能建链 | 用 SDK 0.9.2 编的 `plugin.bin` 跑 `TestRealPlugin_*` | ✅ 4/4 通过(握手校验 `ProtocolVersion=1`,不是 SDK 版本) |
|
||||||
| 模板已接线 | `cd tools/plugindev && go test ./...` | ✅ `TestProcTemplate_CoversAllCoreMethods` 含新 method |
|
| 模板已接线 | `cd tools/hmapdev && go test ./...` | ✅ `TestProcTemplate_CoversAllCoreMethods` 含新 method |
|
||||||
| 并发安全 | `go test ./sdk/ -race -count=5` | ✅ 零 DATA RACE(13 例压测) |
|
| 并发安全 | `go test ./sdk/ -race -count=5` | ✅ 零 DATA RACE(13 例压测) |
|
||||||
|
|
||||||
### v1.2.x 的接口扩展(2026-09-12)
|
### v1.2.x 的接口扩展(2026-09-12)
|
||||||
@ -426,5 +426,5 @@ data URL 本身已是 base64 文本,包进二进制传输省不了空间,还
|
|||||||
- `internal/plugin/proc/protocol.go` — 合同面 B 的代码实现(`Method*` 常量,取代已删的 bridge 模板)
|
- `internal/plugin/proc/protocol.go` — 合同面 B 的代码实现(`Method*` 常量,取代已删的 bridge 模板)
|
||||||
- `internal/plugin/proc/shm.go` — 合同面 C 的代码实现(共享段布局与 18 字段枚举)
|
- `internal/plugin/proc/shm.go` — 合同面 C 的代码实现(共享段布局与 18 字段枚举)
|
||||||
- `internal/plugin/proc/capability.go` — 权限梯度(capability 组 + `withheldCapabilities`)
|
- `internal/plugin/proc/capability.go` — 权限梯度(capability 组 + `withheldCapabilities`)
|
||||||
- `third_party/homeagent-sdk/tools/plugindev/templates/` — 子进程运行时模板(三文件)
|
- `third_party/homeagent-sdk/tools/hmapdev/templates/` — 子进程运行时模板(三文件)
|
||||||
- `docs/zh/experiments/plugin-arch/` — 18 项可行性实验 + `19-migration-verify/` 迁移执行期工具
|
- `docs/zh/experiments/plugin-arch/` — 18 项可行性实验 + `19-migration-verify/` 迁移执行期工具
|
||||||
@ -9,7 +9,7 @@ var (
|
|||||||
//
|
//
|
||||||
// 1.0.0:外部插件从 C ABI 动态库迁到子进程 + 共享内存。
|
// 1.0.0:外部插件从 C ABI 动态库迁到子进程 + 共享内存。
|
||||||
// 这是首个不再加载 `.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件必须
|
// 这是首个不再加载 `.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件必须
|
||||||
// 用新版 plugindev 重编),故跃到主版本号。
|
// 用新版 hmapdev 重编),故跃到主版本号。
|
||||||
// 1.1.0:记忆系统支持二进制多媒体节点——CAS 媒体存储 + L0/L2/L3 贯通。
|
// 1.1.0:记忆系统支持二进制多媒体节点——CAS 媒体存储 + L0/L2/L3 贯通。
|
||||||
// 1.1.1:多模态贯通**插件边界**。内核实现公开 SDK 1.1.0 新增的媒体接口
|
// 1.1.1:多模态贯通**插件边界**。内核实现公开 SDK 1.1.0 新增的媒体接口
|
||||||
// (doc.insertWithMedia、io.injectMedia / injectMediaSync /
|
// (doc.insertWithMedia、io.injectMedia / injectMediaSync /
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const (
|
|||||||
//
|
//
|
||||||
// 保留这张表只为**给出明确错误**:插件目录里躺着 plugin.so 而内核不再认它时,
|
// 保留这张表只为**给出明确错误**:插件目录里躺着 plugin.so 而内核不再认它时,
|
||||||
// 静默跳过会让「目录在但插件没加载」看起来像配置问题,而实际原因是需要用
|
// 静默跳过会让「目录在但插件没加载」看起来像配置问题,而实际原因是需要用
|
||||||
// 新版 plugindev 重编。
|
// 新版 hmapdev 重编。
|
||||||
var legacyCABIEntries = []string{"plugin.so", "plugin.dll", "plugin.dylib"}
|
var legacyCABIEntries = []string{"plugin.so", "plugin.dll", "plugin.dylib"}
|
||||||
|
|
||||||
// entryKind 描述插件入口归属的加载通道。
|
// entryKind 描述插件入口归属的加载通道。
|
||||||
|
|||||||
@ -115,7 +115,7 @@ func TestHasLegacyCABIEntry(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 旧 .so 插件必须报「用新 plugindev 重编」而非静默跳过。
|
// 旧 .so 插件必须报「用新 hmapdev 重编」而非静默跳过。
|
||||||
func TestTryDynamic_LegacyCABIGivesActionableError(t *testing.T) {
|
func TestTryDynamic_LegacyCABIGivesActionableError(t *testing.T) {
|
||||||
r := NewRegistry()
|
r := NewRegistry()
|
||||||
defer r.closeProcHost()
|
defer r.closeProcHost()
|
||||||
@ -129,7 +129,7 @@ func TestTryDynamic_LegacyCABIGivesActionableError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// 错误消息须指向解决办法,且明确业务代码无需改
|
// 错误消息须指向解决办法,且明确业务代码无需改
|
||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
for _, want := range []string{"plugindev", "plugin.bin", "业务代码"} {
|
for _, want := range []string{"hmapdev", "plugin.bin", "业务代码"} {
|
||||||
if !strings.Contains(msg, want) {
|
if !strings.Contains(msg, want) {
|
||||||
t.Errorf("错误消息应含 %q,实际: %v", want, err)
|
t.Errorf("错误消息应含 %q,实际: %v", want, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,11 +10,11 @@ import (
|
|||||||
pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 端到端:用**真实 plugindev 模板**编译的插件,经内核 proc 通道加载运行。
|
// 端到端:用**真实 hmapdev 模板**编译的插件,经内核 proc 通道加载运行。
|
||||||
//
|
//
|
||||||
// 与 plugin_test.go 中 testdata/*.go 假插件的区别:
|
// 与 plugin_test.go 中 testdata/*.go 假插件的区别:
|
||||||
// 那些是手写的最简 RPC 实现,只验证内核侧逻辑;
|
// 那些是手写的最简 RPC 实现,只验证内核侧逻辑;
|
||||||
// 这里用的是 tools/plugindev/templates/proc_main.go.tmpl —— 外部插件作者
|
// 这里用的是 tools/hmapdev/templates/proc_main.go.tmpl —— 外部插件作者
|
||||||
// 真正会拿到的那份运行时。它验证的是「模板 ↔ 内核」两侧协议/布局真的对齐,
|
// 真正会拿到的那份运行时。它验证的是「模板 ↔ 内核」两侧协议/布局真的对齐,
|
||||||
// 而不只是内核自己跟自己对齐。
|
// 而不只是内核自己跟自己对齐。
|
||||||
//
|
//
|
||||||
@ -101,9 +101,9 @@ func (p *e2ePlugin) Start(s *sdk.PluginSDK) error {
|
|||||||
func (p *e2ePlugin) Stop() error { return nil }
|
func (p *e2ePlugin) Stop() error { return nil }
|
||||||
`
|
`
|
||||||
|
|
||||||
// procRuntimeTemplates 列出 plugindev 会生成到插件目录的运行时文件。
|
// procRuntimeTemplates 列出 hmapdev 会生成到插件目录的运行时文件。
|
||||||
//
|
//
|
||||||
// 必须与 SDK 仓 tools/plugindev/proc_runtime.go 的 procRuntimeFiles 一致:
|
// 必须与 SDK 仓 tools/hmapdev/proc_runtime.go 的 procRuntimeFiles 一致:
|
||||||
// 共享段与事件通知的传递机制按平台不同(Unix 继承 fd,Windows 命名
|
// 共享段与事件通知的传递机制按平台不同(Unix 继承 fd,Windows 命名
|
||||||
// 内核对象),故拆成带 build tag 的文件;只写主模板会编译失败。
|
// 内核对象),故拆成带 build tag 的文件;只写主模板会编译失败。
|
||||||
var procRuntimeTemplates = []struct {
|
var procRuntimeTemplates = []struct {
|
||||||
@ -115,15 +115,21 @@ var procRuntimeTemplates = []struct {
|
|||||||
{"proc_shm_windows.go.tmpl", "z_proc_shm_windows.go"},
|
{"proc_shm_windows.go.tmpl", "z_proc_shm_windows.go"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildPluginWithRealTemplate 用 plugindev 的真实模板编译一个插件二进制。
|
// buildPluginWithRealTemplate 用 hmapdev 的真实模板编译一个插件二进制。
|
||||||
func buildPluginWithRealTemplate(t *testing.T, businessCode string) string {
|
func buildPluginWithRealTemplate(t *testing.T, businessCode string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if _, err := exec.LookPath("go"); err != nil {
|
if _, err := exec.LookPath("go"); err != nil {
|
||||||
t.Skip("环境无 go 工具链,跳过端到端测试")
|
t.Skip("环境无 go 工具链,跳过端到端测试")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 工具链在 SDK 1.2.0 起改名 hmapdev(原 plugindev)。两个目录都接受:
|
||||||
|
// 旧检出(软链或旧版 SDK 仓)仍能跑本测试,新检出走新路径。
|
||||||
tmplDir := filepath.Join("..", "..", "..",
|
tmplDir := filepath.Join("..", "..", "..",
|
||||||
"third_party", "homeagent-sdk", "tools", "plugindev", "templates")
|
"third_party", "homeagent-sdk", "tools", "hmapdev", "templates")
|
||||||
|
if _, err := os.Stat(tmplDir); err != nil {
|
||||||
|
tmplDir = filepath.Join("..", "..", "..",
|
||||||
|
"third_party", "homeagent-sdk", "tools", "plugindev", "templates")
|
||||||
|
}
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
mustWriteFile(t, filepath.Join(dir, "plugin.go"), businessCode)
|
mustWriteFile(t, filepath.Join(dir, "plugin.go"), businessCode)
|
||||||
@ -131,7 +137,7 @@ func buildPluginWithRealTemplate(t *testing.T, businessCode string) string {
|
|||||||
for _, rt := range procRuntimeTemplates {
|
for _, rt := range procRuntimeTemplates {
|
||||||
data, err := os.ReadFile(filepath.Join(tmplDir, rt.tmpl))
|
data, err := os.ReadFile(filepath.Join(tmplDir, rt.tmpl))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skipf("plugindev 模板 %s 不可读(SDK 仓可能未就位): %v", rt.tmpl, err)
|
t.Skipf("hmapdev 模板 %s 不可读(SDK 仓可能未就位): %v", rt.tmpl, err)
|
||||||
}
|
}
|
||||||
mustWriteFile(t, filepath.Join(dir, rt.out), string(data))
|
mustWriteFile(t, filepath.Join(dir, rt.out), string(data))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -260,7 +260,7 @@ func (p *Process) handshake(timeout time.Duration) error {
|
|||||||
return fmt.Errorf("proc: %s 握手应答解析失败: %w", p.name, err)
|
return fmt.Errorf("proc: %s 握手应答解析失败: %w", p.name, err)
|
||||||
}
|
}
|
||||||
if res.Protocol != ProtocolVersion {
|
if res.Protocol != ProtocolVersion {
|
||||||
return fmt.Errorf("proc: %s 协议版本不匹配(插件 %d,内核 %d)——请用配套 plugindev 重编",
|
return fmt.Errorf("proc: %s 协议版本不匹配(插件 %d,内核 %d)——请用配套 hmapdev 重编",
|
||||||
p.name, res.Protocol, ProtocolVersion)
|
p.name, res.Protocol, ProtocolVersion)
|
||||||
}
|
}
|
||||||
log.Printf("[proc] %s 已建链(pid=%d protocol=%d sdk=%s)",
|
log.Printf("[proc] %s 已建链(pid=%d protocol=%d sdk=%s)",
|
||||||
|
|||||||
@ -331,7 +331,7 @@ func TestProcess_ProtocolMismatchRejected(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// 运维可读性:光报“不匹配”不能定位到行动。生产上碰到它的现场是
|
// 运维可读性:光报“不匹配”不能定位到行动。生产上碰到它的现场是
|
||||||
// “只更新了内核没重编插件”,所以错误里必须带出这条修复指令。
|
// “只更新了内核没重编插件”,所以错误里必须带出这条修复指令。
|
||||||
if !strings.Contains(err.Error(), "plugindev") || !strings.Contains(err.Error(), "重编") {
|
if !strings.Contains(err.Error(), "hmapdev") || !strings.Contains(err.Error(), "重编") {
|
||||||
t.Errorf("错误应给出重编插件的修复指令,实际: %v", err)
|
t.Errorf("错误应给出重编插件的修复指令,实际: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -143,7 +143,7 @@ func AttachSegment(data []byte) (*Segment, error) {
|
|||||||
return nil, fmt.Errorf("proc: 共享段魔数不匹配(0x%x,期望 0x%x)", got, shmMagic)
|
return nil, fmt.Errorf("proc: 共享段魔数不匹配(0x%x,期望 0x%x)", got, shmMagic)
|
||||||
}
|
}
|
||||||
if got := binary.LittleEndian.Uint32(data[offVersion:]); got != shmVersion {
|
if got := binary.LittleEndian.Uint32(data[offVersion:]); got != shmVersion {
|
||||||
return nil, fmt.Errorf("proc: 共享段版本不匹配(%d,本内核 %d)——插件需用配套 plugindev 重编",
|
return nil, fmt.Errorf("proc: 共享段版本不匹配(%d,本内核 %d)——插件需用配套 hmapdev 重编",
|
||||||
got, shmVersion)
|
got, shmVersion)
|
||||||
}
|
}
|
||||||
return &Segment{data: data}, nil
|
return &Segment{data: data}, nil
|
||||||
|
|||||||
2
internal/plugin/proc/testdata/stageplugin.go
vendored
2
internal/plugin/proc/testdata/stageplugin.go
vendored
@ -4,7 +4,7 @@
|
|||||||
// stage 处理经共享内存读改写(模拟 sanitizer 的清洗行为)。
|
// stage 处理经共享内存读改写(模拟 sanitizer 的清洗行为)。
|
||||||
//
|
//
|
||||||
// 它手写 RPC 与共享段访问,不依赖公开 SDK——因为 SDK 侧的 proc 支持
|
// 它手写 RPC 与共享段访问,不依赖公开 SDK——因为 SDK 侧的 proc 支持
|
||||||
// 属于 Part 3(plugindev 工具链)的内容。这里只验证内核侧机制。
|
// 属于 Part 3(hmapdev 工具链)的内容。这里只验证内核侧机制。
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1175,11 +1175,11 @@ func (r *Registry) tryDynamic(plgDir, name string, config map[string]interface{}
|
|||||||
|
|
||||||
// 旧 .so/.dll 插件给明确错误,不静默跳过。
|
// 旧 .so/.dll 插件给明确错误,不静默跳过。
|
||||||
// 静默跳过会让「插件目录在但没加载」看起来像配置问题,
|
// 静默跳过会让「插件目录在但没加载」看起来像配置问题,
|
||||||
// 而实际原因是需要用新 plugindev 重编。
|
// 而实际原因是需要用新 hmapdev 重编。
|
||||||
if hasLegacyCABIEntry(plgDir) {
|
if hasLegacyCABIEntry(plgDir) {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"plugin %s: 检测到旧 C ABI 产物(plugin.so/.dll/.dylib)。"+
|
"plugin %s: 检测到旧 C ABI 产物(plugin.so/.dll/.dylib)。"+
|
||||||
"外部插件已改为子进程模式,请用新版 plugindev 重编产出 %s"+
|
"外部插件已改为子进程模式,请用新版 hmapdev 重编产出 %s"+
|
||||||
"(业务代码无需修改)", name, binEntry)
|
"(业务代码无需修改)", name, binEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import (
|
|||||||
// 这里用 **example/ 里真实的 17 个插件产物**,验证「业务代码零改动 + 重编即可」
|
// 这里用 **example/ 里真实的 17 个插件产物**,验证「业务代码零改动 + 重编即可」
|
||||||
// 这一迁移承诺在完整内核装配下成立。
|
// 这一迁移承诺在完整内核装配下成立。
|
||||||
//
|
//
|
||||||
// 前置:插件需已用新版 plugindev 重编(scripts/rebuild-plugins.sh)。
|
// 前置:插件需已用新版 hmapdev 重编(scripts/rebuild-plugins.sh)。
|
||||||
// 未重编时测试 skip 而非 fail——CI 上不强制要求先跑重编脚本。
|
// 未重编时测试 skip 而非 fail——CI 上不强制要求先跑重编脚本。
|
||||||
|
|
||||||
// realPluginDir 返回某个 example 插件的 linux 产物路径。
|
// realPluginDir 返回某个 example 插件的 linux 产物路径。
|
||||||
|
|||||||
Reference in New Issue
Block a user