Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

编写工具插件

这是本系列的入门指南:从空 crate 到模型在对话中调用的工具的完整实操路径。这里构建的工具是 redact,它会对文本中的电子邮件、已知凭据前缀以及由操作员提供的模式进行掩码处理。它刻意采用配置驱动,因为读取自己的受限配置部分是每个非平凡插件都需要做的事情,也是最容易出错的事情。

本页上的所有内容都根据契约源代码进行了核对:wit/v0/tool.wit 中的 tool-plugin world、crates/zeroclaw-plugins/src/runtime.rswasm_tool.rs 中的宿主侧调用路径,以及 host.rs 中的清单校验。源路径是指向 ZeroClaw 仓库的引用,用于验证;插件本身是你自己的仓库中的你自己的 crate。构建插件时,你完全不需要检出 ZeroClaw,只需要 wit/ 契约文件(在第 1 步获取)以及一个已安装、并已编译进插件宿主的 zeroclaw 二进制来运行它。

发布二进制文件并非该二进制文件。 安装程序附带的预构建二进制文件不包含插件宿主(zeroclaw plugin … 是无法识别的子命令),且 plugins-wasm 不在该 crate 的默认特性集中。请使用执行后端从源码构建宿主端;每个后端特性本身均携带 plugins-wasm 伞形特性,因此一个标志即可:

cargo build --release --features plugins-wasm-cranelift

协议页面 记录了后端选项。

工具调用的流程如何运作

在编写代码之前先了解运行时形状:

  1. 启动时,发现机制会找到插件目录,验证清单结构,执行签名策略,然后验证 config_schema。注册之前,宿主会将插件的操作器值具体化为带类型的 JSON,并对其进行验证。通过验证的插件会成为 WasmTool 实例。
  2. 注册时,主机会实例化该组件一次,以读取 namedescriptionparameters-schema。这些信息会被缓存,之后不会再次请求。如果此次探测失败,注册也会失败;主机绝不会为出问题的组件替换为合成元数据。
  3. 每次调用时,WasmTool::execute 都会从规范状态解析并验证配置,创建一个全新的存储(新的 WASI 上下文、新的 fuel 预算,不包含上一次调用的状态),并实例化组件。这个解析后的对象将服务于整个帧:主机仅会将其非机密值注入 __config,通过作用域限定的 secrets 导入提供架构标记的机密值,然后调用 execute

fresh-store-per-call 模型是最重要的设计约束:工具插件在设计上是无状态的。任何你想在调用之间持久保存的内容,都必须存在于插件之外(在你返回的文本中,或在 operator 配置中)。

1. Crate 设置

创建 crate 并添加 guest 端依赖项:

cargo new --lib my-plugin
cd my-plugin
cargo add wit-bindgen@0.46
cargo add serde --features derive
cargo add serde_json

然后对包清单进行两处手动编辑:

  1. 将库的 crate-type 设置为 ["cdylib", "rlib"]cdylib 是组件构建生成的内容;rlib 允许同一个 crate 的纯逻辑模块在主机上原生编译并进行单元测试。
  2. 在发布配置中,设置 opt-level = "s"lto = truestrip = true。组件大小就是下载和加载时间;没有理由在插件边界传递调试符号。

将 ZeroClaw 仓库中的 wit/v0/ 目录复制到 crate 根目录下的 wit/。你不需要完整检出;只需从与你的目标主机版本匹配的 tag 中获取该目录:

git clone --depth 1 --filter=blob:none --sparse \
  https://github.com/zeroclaw-labs/zeroclaw /tmp/zeroclaw-wit
git -C /tmp/zeroclaw-wit sparse-checkout set wit
cp -r /tmp/zeroclaw-wit/wit .

WIT 文件就是 ABI:宿主是根据这些精确的文件生成其绑定的,因此你的 guest 绑定也必须来自同一组文件。请锁定版本:WIT world 会随宿主演进,而基于比宿主绑定更高版本的 world 构建的组件将无法实例化。

2. 将逻辑与胶水代码分离

将实际行为放在一个没有 wit-bindgen 导入的普通 Rust 模块中,并保持组件胶水层尽量薄。原因是可测试性:组件目标不能原生运行 cargo test,因此被困在胶水层中的逻辑只能通过 wasm 主机进行端到端验证。胶水层应当薄到不太可能出错。

src/redact.rs 保存了一个配置结构体和一个纯函数:

#![allow(unused)]
fn main() {
pub const DEFAULT_REPLACEMENT: &str = "[REDACTED]";

/// 脱敏策略根据插件自身的配置段解析。
#[derive(Debug, serde::Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct RedactConfig {
    pub replacement: String,
    pub redact_emails: bool,
    pub patterns: Vec<String>,
}

impl Default for RedactConfig {
    fn default() -> Self {
        Self {
            replacement: DEFAULT_REPLACEMENT.to_string(),
            redact_emails: true,
            patterns: Vec::new(),
        }
    }
}

/// 对输入进行脱敏。返回输出以及被遮蔽片段的数量。
pub fn redact(input: &str, cfg: &RedactConfig) -> (String, usize) {
    // 当 cfg.redact_emails、凭据前缀
    //(sk-、ghp_、AKIA、xoxb-)以及 cfg.patterns 中的每个字面量匹配时进行掩码,
    // 用 cfg.replacement 替换每个命中项。
    // ...
}
}

guest 会接收由 schema 生成的公开 JSON 对象,因此只需将其反序列化一次,而不是重复进行字符串解析。此示例的 schema 将每个字段都设为可选,当主机提供 {} 时,其行为由 Default 负责。操作员尚未配置插件,或主机拒绝所请求的 config_read 授权时,空对象都是正常情况。如果插件没有某个值就无法运行,请在 config_schema 中将其标记为必需;这样,主机就会在 guest 代码启动前拒绝空对象。

3. 实现 world

wit/v0/tool.wit 定义了你必须导出的接口面。world 是:

world tool-plugin {
    import logging;
    import secrets;
    export plugin-info;
    export tool;
}

tool 接口包含四个函数:

record tool-result {
    success: bool,
    output: string,
    error: option<string>,
}

name: func() -> string;
description: func() -> string;
parameters-schema: func() -> json-string;
execute: func(args: json-string) -> result<tool-result, string>;

src/lib.rs 生成 guest 绑定并实现这两个导出:

#![allow(unused)]
fn main() {
pub mod redact;

#[cfg(target_family = "wasm")]
mod component {
    wit_bindgen::generate!({
        path: "wit/v0",
        world: "tool-plugin",
        features: ["plugins-wit-v0"],
    });

    use crate::redact::{redact, RedactConfig};
    use exports::zeroclaw::plugin::plugin_info::Guest as PluginInfo;
    use exports::zeroclaw::plugin::tool::{Guest as Tool, ToolResult};
    use zeroclaw::plugin::logging::{
        log_record, LogLevel, PluginAction, PluginEvent, PluginOutcome,
    };

    struct RedactPlugin;

    #[derive(serde::Deserialize)]
    struct ExecuteArgs {
        text: String,
        #[serde(rename = "__config", default)]
        config: RedactConfig,
    }

    impl PluginInfo for RedactPlugin {
        fn plugin_name() -> String {
            "my-redact-plugin".to_string()
        }
        fn plugin_version() -> String {
            "0.1.0".to_string()
        }
    }

    impl Tool for RedactPlugin {
        fn name() -> String {
            "redact".to_string()
        }

        fn description() -> String {
            "在文本到达日志、通道或模型之前,对其中的机密和 PII 进行编辑。屏蔽电子邮件、凭据前缀以及运维人员配置的字面模式。"
                .to_string()
        }

        fn parameters_schema() -> String {
            serde_json::json!({
                类型: 对象,
                属性: {
                    "文本": {
                        类型: "字符串",
                        "描述": “要遮盖的文本。”
                    }
                },
                “required”: ["文本"]
            })
            .to_string()
        }

        fn execute(args: String) -> Result<ToolResult, String> {
            let parsed: ExecuteArgs = match serde_json::from_str(&args) {
                Ok(a) => a,
                Err(e) => {
                    return Ok(ToolResult {
                        success: false,
                        output: String::new(),
                        error: Some(format!("无效参数:{e}")),
                    });
                }
            };

            let (output, count) = redact(&parsed.text, &parsed.config);

            log_record(
                LogLevel::Info,
                &PluginEvent {
                    function_name: "my_redact_plugin::tool::execute".into(),
                    action: PluginAction::Complete,
                    outcome: Some(PluginOutcome::Success),
                    duration_ms: None,
                    attrs: Some(format!("{{\"redactions\":{count}}}")),
                    message: "redacted input".into(),
                },
            );

            Ok(ToolResult { success: true, output, error: None })
        }
    }

    export!(RedactPlugin);
}
}

契约点,每个都锚定在宿主源中:

  • plugin-info 是每个 world 的必需导出。 它会报告组件自身的名称和版本。请使二者与清单保持同步。
  • 元数据只读取一次。 call_tool_metadataruntime.rs 中于注册时读取 namedescriptionparameters-schema,并将它们缓存。不要从任何动态内容计算它们;它们之后不会被重新观察到。
  • 模式是模型对你的工具的全部视图。 主机在加载时将其解析为 JSON(tool parameters-schema is not valid JSON 是硬性注册失败),并将其逐字转发给 LLM。描述每个属性。绝不要在其中声明 __config:该键由主机保留,主机会在注入前精确地剥离任何调用方提供的值,以免模型冒充你的操作员。
  • success: falseErr success: falseToolResult 会作为普通工具响应返回给模型,模型可以据此作出反应(使用修正后的参数重试、道歉、选择另一个工具)。Err(String) 会作为插件故障跨越边界:宿主会将其包装为 plugin execute returned error,并使调用失败。请将 Err 保留给真正损坏的状态,并通过 success: false 报告错误输入。
  • 始终通过导入的 logging 接口记录日志,绝不要使用 wasi:logging log-record 是即发即弃的;宿主会吸收所有错误,因此失败的日志写入绝不会让你的调用崩溃,而且事件会进入 zeroclaw_log 写入的每个目标,并携带你的调用所运行于其下的宿主 span 的 zeroclaw.* 归因agent_aliassession_key、provider、channel)。请注意 plugin-event 上的 attrs 字段不是归因:它是日志行的自由形式 attributes 载荷。归因与别名绑定,继承自宿主侧的环境 tracing span,插件发送的任何内容都不能设置或覆盖它。PluginActionPluginOutcome 是与宿主分类法对应的封闭枚举;刻意不提供自由形式变体。请选择最接近的项。

4. __config 沙箱

插件不会读取进程环境变量,也永远看不到全局配置。请求 config_read 的清单还必须声明 config_schema;没有该权限的架构同样无效。该架构采用 Draft 2020-12,其根必须是包含 properties 映射和 additionalProperties = false 的对象,并且每个顶层属性都必须显式解析为 stringbooleanintegernumberarrayobject

宿主会解析存储在根据此实例的包、tool 能力和绑定派生出的版本化配置条目键下的部分,根据包模式将其实例化,验证完整的类型化对象,然后才对其进行分区。只有非机密属性会在保留的 __config 键下合并到 execute 中:

  • 模型提供的参数中任何已存在的 __config 都会先被删除。伪造在结构上是不可能的。
  • Operator 的存储仍然是加密的字符串映射。直接存储字符串;将布尔值和数字编码为 JSON 标量("true""4""0.5"),将数组和对象编码为 JSON('["secret-a","secret-b"]')。来宾会收到真正的 JSON 布尔值、数字、数组和对象,而不是这些存储字符串。
  • 标记为 x-secret = true 的直接顶层字符串属性不会包含在 __config 中。使用生成的 zeroclaw::plugin::secrets::get 函数显式读取它。嵌套标记、值为 false 或非布尔值的标记,以及机密非字符串属性都会导致清单准入失败。
  • 主机仅在分派 execute 期间启用机密读取。来自组件初始化或元数据导出的调用会返回 unavailable,而不会解析配置。一次执行期间,公开的 __config 和机密读取使用相同的已解析配置视图。
  • 如果请求了 config_read,但未实际授予该权限,主机会将其解析为 {} 并进行验证。因此,此示例中的可选架构会使工具省略 __config,而 #[serde(default)] 会选择 RedactConfig::default。必需的架构会安全失败,而不会在没有凭据的情况下运行。
  • 未知键、无效的 JSON 编码、类型错误以及架构约束失败,会在插件代码运行前拒绝该插件。运维人员目前通过 TOML 或通用的 zeroclaw config set 路径,在安装时打印的实例键下设置值;这些值会使用配置的密钥进行静态加密。基于架构的 zerocode 和网关编辑器属于未来的 SDK/配置界面工作。

对于此工具,类型化部分有三个可选键:replacement 是字符串,redact_emails 是布尔值,patterns 是字符串数组。

5. 清单

manifest 是插件目录中名为 manifest.toml 的文件。其字段是 crates/zeroclaw-plugins/src/lib.rsPluginManifest 的 serde 表面,这是唯一的事实来源:

字段必填含义
name唯一的规范包 slug,也是每个派生实例配置键中的包组件。它本身不是操作符配置键。使用 1–128 个小写 ASCII 字符;以 [a-z0-9] 开头和结尾,中间只能包含 [a-z0-9._-]。发现过程会拒绝无效或重复的名称。
version版本字符串,例如 0.1.0
descriptionzeroclaw plugin list 显示的人类可读描述。
author作者姓名或组织。
wasm_path用于 WASM 能力组件文件名,相对于插件目录。除非唯一的 capability 是 skill,否则为必填。如果指定的文件不存在,发现过程会跳过该插件。
capabilities是,非空插件类型:toolchannelmemoryobserverskill 中的任意一个(PluginCapability,序列化为 snake_case)。
permissions代码可能访问的主机服务:http_clientconfig_readfile_readfile_writememory_readmemory_writePluginPermission)。目前仅前两项会被强制执行;其余项虽会被接受,但不起作用。声明 config_read 需要 config_schema,目前只有工具/通道适配器会提供它。
config_schema恰好使用 config_read为此插件的私有配置起草 2020-12 JSON Schema;它会包含在规范清单字节中,因此在清单签名时也会受到保护。根必须是一个包含 properties 映射且 additionalProperties = false 的对象。每个顶层属性都必须具有一个明确的受支持类型,可直接指定,也可通过本地 JSON Pointer 指定:stringbooleanintegernumberarrayobject。工具和通道使用者可以直接在顶层字符串属性上设置 x-secret = true,以便将其从公共配置中移除,并通过带作用域的 secrets.get 主机导入来公开。工具会在 execute 期间通过 __config 接收公共配置,并可以读取机密。通道会在 configure 和操作调用期间通过 config.get 读取当前公共对象,并通过 secrets.get 读取机密;这两个导入在实例化和静态元数据发现期间均不可用。嵌套、值为 false 或非布尔值的机密标记,以及非字符串的机密属性都会被拒绝。没有 config_read 的 schema,或没有 schema 的 config_read,都会被拒绝。
signature对规范化清单字节进行的 Base64url Ed25519 签名。用于发布签名时设置。
publisher_key签名者的十六进制编码 Ed25519 公钥。

只声明代码实际使用的权限。未声明的权限是组件无法触达的宿主表面;不必要声明的权限则是你主动增加的攻击面,也是审查你的插件的人需要承担的审核负担。

操作方提供的值在 plugins.entries 中仍保持为字符串,并会在持久化时加密,以一个由宿主拥有的包、能力和绑定标识派生的版本化 zpi1_… 字符串作为键(安装过程会打印并初始化默认工具绑定的完整实例密钥):字符串按原样存储,布尔值和数字使用 JSON 标量文本,数组和对象使用 JSON 文本。在任何来宾代码运行之前,宿主会将这些字符串物化为包模式规定的类型,并针对工具和频道适配器验证完整对象。非机密工具属性构成 __config;频道通过 config.get 获取非机密对象。标记为 x-secret = true 的属性会从两个公共接口中省略,仅可在授权的服务帧中通过 secrets.get("property") 获取。频道在一次调用中的公共和机密读取共享同一个规范修订版本,调用结束时宿主会丢弃该物化视图。符合要求的频道插件 必须 在每个使用点都解析这两类值,并且不得在来宾的热状态中保留配置或凭据值;将明文返回给来宾意味着宿主无法针对恶意代码强制实施不保留策略。如果请求了 config_read 但未实际获授予,宿主会验证一个空对象;因此,包含必需属性的模式会拒绝启动,而不会在缺少必需配置的情况下启动。如果空对象有效,工具会省略空的 __config,而频道的配置/机密导入会返回 access-denied;在授权帧之外的调用、解析失败以及宿主调用预算耗尽会返回 unavailable

对于此插件:nameversion 需与 plugin-info 报告的内容一致,wasm_path 命名你将与其一起发布的组件文件,capabilities 需恰好包含 toolpermissions 需恰好包含 config_read。仅当你的工具发起出站 HTTP 调用时,才添加 http_client。工具适配器实现了 wasi:http,但仅在该授权通过验证后才会进行链接;若缺少适配器支持和授权二者之一,则不存在 HTTP 接口。

类型化 RedactConfig 的匹配清单契约为:

name = "my-redact-plugin"
version = "0.1.0"
wasm_path = "my_redact_plugin.wasm"
capabilities = ["tool"]
permissions = ["config_read"]

[config_schema]
"$schema" = "https://json-schema.org/draft/2020-12/schema"
type = "object"
additionalProperties = false

[config_schema.properties.replacement]
type = "string"
minLength = 1

[config_schema.properties.redact_emails]
type = "boolean"

[config_schema.properties.patterns]
type = "array"
items = { type = "string" }

这些属性是可选的,与来宾的默认值一致。对于必须存在的凭据,将其名称添加到 [config_schema] 中的 required;授权被拒绝或值缺失将阻止组件启动。

调用网络的工具

可以说,现实世界中最常见的工具形态并不是像 redact 这样的纯转换,而是连接外部 API 的桥接器:在清单中声明 http_client,通过限定作用域的机密服务读取凭据,并发起出站请求。在已签名的架构中标记该凭据:

[config_schema]
required = ["api_key"]

[config_schema.properties.api_key]
type = "string"
minLength = 1
x-secret = true

相较于本指南,缺少的是一个可在组件内部工作的 HTTP 客户端:reqwest 及其同类库无法做到这一点,因为这里没有套接字接口,只有 wasi:http。已知可与此宿主配合工作的客户端是 waki,它是阻塞式的,因此可以直接适配 execute 的同步签名。将其添加并设置为仅在组件目标下启用,这样你的纯逻辑模块仍可在原生环境中测试:

cargo add waki --target 'cfg(target_family = "wasm")'

在解析公开的 __config 后,execute 内部调用的形式:

#![allow(unused)]
fn main() {
let api_key = zeroclaw::plugin::secrets::get("api_key")
    .map_err(|_| "api_key 不可用".to_string())?;
let resp = waki::Client::new()
    .get("https://api.example.com/search")
    .query([("q", term.as_str())])
    .header(授权, format!("Bearer {api_key}"))
    .connect_timeout(std::time::Duration::from_secs(5))
    .send()
    .map_err(|e| format!("请求失败:{e}"))?;
}

两个看起来像破坏性变更但实际上不是的版本事实:waki 随自身 vendored 的 wit-bindgen(0.34)一起提供,而你的 world bindings 使用的是 0.46;两者可以并存,各自生成自己的 bindings。并且 waki 发出的是 wasi:http@0.2.4 imports,而当前工具链基线是 @0.2.6;host 可以无问题地同时链接两者。两者都无需处理。

记住 概述 中的信任框架:http_client 要么全部允许,要么全部禁止。沙箱不会限制已授予的插件将数据发送到哪里,因此运行 strict 签名策略的操作者信任的是你的代码,而不是 URL 允许列表。

6. 原生测试逻辑

因为 redact.rs 没有 wasm 依赖,普通的 cargo test 会在主机上覆盖它:

#![allow(unused)]
fn main() {
#[test]
fn empty_config_falls_back_to_defaults() {
    let cfg: RedactConfig = serde_json::from_str("{}").unwrap();
    let (out, n) = redact("mail me at a@b.example", &cfg);
    assert_eq!(n, 1);
    assert!(out.contains("[REDACTED]"));
}
}

至少涵盖:jail 情况(空节)、已配置情况,以及没有任何需要屏蔽内容时文本的透明透传。粘合层转发的每一种行为都应能在这里得到证明,而且完全不需要看到 wasm 工具链。

7. 构建

先安装一次 WASI Preview 2 目标,然后构建组件:

rustup target add wasm32-wasip2
cargo build --release --target wasm32-wasip2

该组件位于 target/wasm32-wasip2/release/<crate_name>.wasm(crate 名称中的连字符会变成下划线)。在组装插件目录时,请将其重命名为你的清单中 wasm_path 声明的名称。

[!IMPORTANT] 编译后的 .wasm.cwasm 文件是二进制构件,通常每个都有数 MB。不要在没有 Git LFS 的情况下将它们提交到 git 源树中:每次重建都作为普通 blob 提交,会永久膨胀仓库历史,并且 git diff/审查工具会因此卡住。把它们当作其他构建输出一样处理:将 target/*.wasm/*.cwasm 添加到 .gitignore,并改为通过发布构件或插件注册表归档分发。如果某个构件确实必须留在树中,请在首次提交之前使用 LFS 跟踪该模式(git lfs track "*.wasm")。

如果目标主机是仅运行时构建(未编译 JIT 后端),它在加载时无法编译 .wasm;而是反序列化预编译的 .cwasm。请使用与主机版本匹配的 wasmtime CLI 进行预编译,并将 .cwasm 作为 wasm_path 构件分发。版本不匹配的构件会被 wasmtime 的反序列化检查拒绝,而不会被静默错误加载。

8. 安装并验证

这些命令需要一个编译了插件宿主的二进制文件。 安装程序附带的预构建发布二进制文件在构建时未启用 plugins-wasm 特性,因此 zeroclaw plugin ... 在该版本中是无法识别的子命令,已安装的插件也永远不会被发现。请从源码构建并选择一个插件执行后端,例如 cargo build --release --features plugins-wasm-cranelift

每个插件都位于 plugins 目录的各自子目录中(默认 ~/.zeroclaw/plugins/,通过 plugins.plugins_dir 解析),其中包含清单以及与清单的 wasm_path 匹配的组件:

~/.zeroclaw/plugins/
└── my-plugin/
    ├── manifest.toml
    └── my-plugin.wasm

从本地目录安装(这会在复制任何内容之前验证清单形状并运行签名策略):

zeroclaw plugin install ./my-plugin/

启用插件系统并确认发现:

zeroclaw config set plugins.enabled true
zeroclaw plugin list
zeroclaw plugin info my-plugin

zeroclaw plugin listzeroclaw plugin info 可确认软件包已安装且可被发现,但发现并不等于激活。plugins.enabled = true 会开启插件主机;只有同时将 plugins.auto_discover = true 设为 true 时,自动发现的工具和技能功能才会在运行时加载,而该标志默认为 false(故障关闭):

zeroclaw config set plugins.auto_discover true

因此,仅设置 plugins.enabled = true 时,会启用你在 [channels.plugin.<alias>] 下声明的通道,而不会启用任何插件工具或技能:工具或技能包可能会出现在 zeroclaw plugin list 中,但在运行时不会产生任何作用。显式通道绑定由操作员命名,而不是自动发现,因此不需要 auto_discover;该标志仅控制自动发现的工具和技能。

zeroclaw plugin list 中缺失的插件在发现时已被跳过:请检查启动日志中的跳过警告(清单格式错误、缺少 wasm_path 文件,或签名策略拒绝)。

9. 运行它

请让代理使用该工具:

> redact this before you log it: key sk-live-abc123, mail ops@example.com

模型会在其目录中看到带有你的 schema 的 redact,调用它后,宿主会在一个新的 store 中,在配置的 fuel 和内存限制下运行该组件。插件工具不在内置的只读自动批准集合中,因此在非完全自治模式下,这次调用会像其他特权工具一样弹出操作员批准提示;请在你的工具描述中预料到这一点,而不是对此感到意外。你的 log-record 事件会以宿主调用点的 span attribution 形式出现在结构化日志中。

来自 插件概览 的两个操作约束值得再次强调:

  • 工具名称不得与内置项冲突。 内置工具会先注册,调度按首个匹配项解析(运行时中的 find_tool),因此名字与内置工具相同的插件工具永远不会被选中。不会报错;只会静默失败。请选一个唯一名称。
  • 每个组件一个工具。 tool-plugin 世界导出一个单一的 tool 接口。一个工具箱由多个插件目录组成,每个组件一个。

故障排除

症状可能的原因
zeroclaw plugin list 中缺少插件插件系统已禁用;manifest 格式错误;wasm_path 文件缺失;签名策略拒绝了它。启动日志中包含具体的跳过警告。
出现在 zeroclaw plugin list 中,但工具始终不会加载plugins.auto_discoverfalse(默认值)。自动发现的工具和技能功能仅在 plugins.auto_discover = true 时加载;仅设置 plugins.enabled = true 只会激活显式声明的通道。运行 zeroclaw config set plugins.auto_discover true。
工具注册遭到拒绝配置验证或元数据探测失败。请查看日志以获取具体错误;探测失败通常意味着组件是针对不匹配的 WIT 构建的。
模型从未选择该工具名称与内置项冲突,或描述/schema 没有告知模型该工具何时适用。
__config 即使已配置该部分仍然缺失有效作用域拒绝了 config_read、条目未使用安装时打印的完整实例密钥、经过验证的对象为空,或所有经过验证的属性都标记为机密。相反,config_schema/权限不匹配会拒绝该插件。
secrets.get 返回 not-found该属性缺失,或者在已获准的架构中不是标记为 x-secret = true 的直接顶层字符串。
secrets.get 返回 unavailable该调用在 execute 之外运行、配置解析失败,或执行耗尽了其固定的主机调用预算。
调用失败或触发陷阱已达到燃料、挂钟时间或内存上限。请酌情提高 plugins.limits.call_fuelplugins.limits.call_timeout_msplugins.limits.max_memory_mb,或减少每次调用的工作量。
仅运行时主机上加载失败您将 .wasm 部署到了一个没有 JIT 的主机上;请改为部署与版本匹配的 .cwasm

下一个

  • 编写 channel 插件,用于 warm-store 生命周期、capability 标志以及由宿主提供的 inbound。
  • 分发插件 适用于此工具应该离开你的机器时。