Skip to content

Example Plugins zh

Dev edited this page Sep 9, 2026 · 1 revision

English · 中文

示例插件

对应 v0.12.0(8dd8ae9)。下面每一个工具、命令、事件和 grant 都是从插件源码里读出来的,不是凭记忆写的。 源码在仓库 examples/ 下;若与本页不符,以仓库为准。


五个插件,以及你该从哪个开始

三个演示机制,两个是拿来装了真用的。

插件 作用 工具 命令 事件
wasm-plugin-minimal 拿来复制的骨架。 还能体现能力门的最小插件 —— 一个纯计算工具、一个受门控的,用一句标记注释把「样板」和「你的部分」分开。 greet、fetch_head — —
wasm-plugin 参考实现。 四个工具、斜杠命令,以及三种 command action(print / send_user_message / error)全都演示到。 wordcount、readfile、fetch、rot13 /todo、/explain —
wasm-plugin-events 事件订阅,以及「两份清单必须都同意」:插件自己的 list-events 和 配置里的 events 缺一不可,否则一个事件都不投递。另外它也带着断路器测试要用的 trap/spin fixture。 events_seen、busy、greet (无 —— 一个 [] 桩,阶梯的代价) tool_execution_start、turn_start、input
wasm-plugin-memory 持久化项目记忆。 每条记忆是 .nanopi/memory/ 下一个 markdown 文件,可读、可手改、可 diff、可提交。注入模型上下文的只有那份精简索引,全文由模型判断相关后调 recall 取。 remember、recall、forget /memory —
wasm-plugin-report 时间花在哪了。 跨会话记录每个工具的调用次数、耗时和失败。它能存在的理由是:tool_execution_end 带的那个 duration_ms 别处一概不持久化 —— 所以「刚才为什么慢」「什么老在失败」本来根本没有答案。 tool_stats /tools-report tool_execution_end、session_start

从 wasm-plugin-minimal 开始。 它就是为了被复制而写的:YOUR TOOLS 标记以上是逐字保留的样板,以下是你的。需要命令或第二个能力时再看 wasm-plugin。

想看能力被真正用起来而不是被演示,读 wasm-plugin-memory 和 wasm-plugin-report。 这两个是刻意互补的:加起来把整个 outbound surface 覆盖了(见 能力门)。

各自需要的 grant

插件拿不到任何没被授予的东西。这些写在它的 [[extensions]] 条目里;每个插件自己的 README 都给了完整的配置块。

插件 grants 实际调用的 host import
wasm-plugin-minimal allow_network + url_allowlist host-log、host-http-get
wasm-plugin allow_fs、allow_network + url_allowlist host-log、host-fs-read、host-http-get
wasm-plugin-events events = [...] host-log
wasm-plugin-memory allow_fs、allow_tools = ["write"]、allow_context host-log、host-fs-read、host-notify、host-set-context、host-call-tool
wasm-plugin-report allow_store、events = [...] host-log、host-notify、host-store-get、host-store-set

表里有两处特别容易搞错:

  • wasm-plugin-minimal 不需要 allow_fs,尽管它源码里声明了 host-fs-read。那句声明挂在 #[allow(dead_code)] 后面、没有任何地方调用,所以 LTO 会把这个 import 丢掉,构建出的 world 里根本没有它。要确认一个插件真正 import 了什么,对组件跑 wasm-tools component wit —— 源码可能声明得比实际用的多。
  • host-notify 不需要任何 grant。 它是输出而不是访问:既碰不到什么也读不到什么。约束它的是每轮的速率上限,而不是一道门;而且署名前缀由宿主加,所以一个插件没法冒充另一个。

world 与构建

WIT 的世界是线性阶梯 —— extension ⊂ extension-commands ⊂ extension-events —— 所以插件应该选能覆盖它导出内容的最低那一级。选得比需要的高,就要为用不到的部分补桩导出。

插件 world 构建
wasm-plugin-minimal extension 无 make target —— 手动,见其 README
wasm-plugin extension-commands make plugin
wasm-plugin-events extension-events make plugin-events
wasm-plugin-memory extension-commands make plugin-memory
wasm-plugin-report extension-events make plugin-report

一定要在仓库根目录构建。 wasm-tools component embed wit/ 是按工作目录去解析 wit/ 的,在示例目录里面构建会静默嵌进一个过时的模块。产物落在 dist/。

宿主需要 --features wasm;标准发布二进制没有 WASM 运行时,而且它会明说 —— 它会打印自己忽略了几条 [[extensions]],而不是默默不干活。

受限网络下的构建

在 GFW 后面且没配 cargo 镜像时,新插件项目解析 crates.io index 会超时(这五个示例都各自带了 Cargo.lock,能省掉一部分解析,但首次拉取依赖仍要出网)。一劳永逸的做法是配镜像:

# ~/.cargo/config.toml
[source.crates-io]
replace-with = 'mirror'

[source.mirror]
registry = "sparse+https://mirrors.tuna.tsinghua.edu.cn/crates.io-index/"

wasm-tools 用 cargo install wasm-tools --locked 安装,同样走上面的镜像。

wasm32-wasip1 目标如果 rustup target add 失败(部分镜像不带这个组件),先检查它是不是已经在 sysroot 里:

ls "$(rustc --print sysroot)/lib/rustlib/" | grep wasm32-wasip1

make 的 ensure-wasm-tools 检查的正是这个路径,而不是去信 rustup target list --installed。

shell hook 是另一半,不是次一等的选择

examples/hooks/ 里是给 [[hooks.*]] 用的脚本 —— check-rm-rf.sh、protected-paths.sh、redact-secrets.sh、dirty-repo-guard.sh、audit-log.sh、session-log.sh。见 生命周期 hooks。

有两件事只有 hook 能做,而且都是设计决定而非缺失:

  • 否决。 hook 能阻断或改写一次工具调用。插件只观察事件,拦不住。想让某件事不发生,那就是 hook 的活。
  • 绝不漏事件。 hook 是同步的。插件的事件投递是 drop-on-busy 且不排队 —— 插件正在另一个 guest 调用里时,下一个事件直接丢掉。所以任何必须完整的东西都属于 hook。wasm-plugin-report 在自己的输出里就是这么说的,并且在计数上打 ≥,因为一个会悄悄少算的统计比一个承认自己少算的更糟。

刻意不放在这里的

tests/fixtures/runaway-plugin/ 是一个 execute-tool 永不返回的插件,用来证明 epoch 断路器确实会触发。它自己的头部就写了为什么它不是示例:"Deliberately NOT in examples/ — nothing here is worth copying." 一个别的 README 都在说「复制我当起点」的目录,不该是一个会挂死的组件的家。

另见

  • 写插件 —— 从零写一个,含完整的 ABI 样板
  • 能力门 —— 插件能做什么,以及每道门背后的理由
  • 插件事件订阅 —— 十一个事件及其载荷
  • 调试插件 —— 插件加载不上、或调用被拒时怎么查

Clone this wiki locally