Building plugins
Tool plugins
Tool plugins add agent-callable tools to OmeniaClaw without adding a channel,
model provider, hook, service, or setup backend. Use defineToolPlugin when the
plugin owns a fixed list of tools and you want OmeniaClaw to generate the manifest
metadata that keeps those tools discoverable without loading runtime code.
The recommended flow is:
- Scaffold a package with
OmeniaClaw plugins init. - Write tools with
defineToolPlugin. - Build JavaScript.
- Generate
OmeniaClaw.plugin.jsonandpackage.jsonmetadata withOmeniaClaw plugins build. - Validate the generated metadata before publishing or installing.
For provider, channel, hook, service, or mixed-capability plugins, start with Building plugins, Channel Plugins, or Provider Plugins instead.
Requirements
- Node >= 22.
- TypeScript ESM package output.
typeboxfor config and tool parameter schemas.OmeniaClaw >=2026.5.17, the first OmeniaClaw version that exportsOmeniaClaw/plugin-sdk/tool-plugin.- A package root that can ship
dist/,OmeniaClaw.plugin.json, andpackage.json.
The generated plugin imports typebox at runtime, so keep typebox in
dependencies, not only devDependencies.
Quickstart
Create a new plugin package:
OmeniaClaw plugins init stock-quotes --name "Stock Quotes"cd stock-quotesnpm installnpm run plugin:buildnpm run plugin:validatenpm testThe scaffold creates:
src/index.ts: adefineToolPluginentry with anechotool.src/index.test.ts: a small metadata test.tsconfig.json: NodeNext TypeScript output todist/.package.json: scripts, runtime dependencies, andOmeniaClaw.extensions: ["./dist/index.js"].OmeniaClaw.plugin.json: generated manifest metadata for the initial tool.
Expected validation output:
Plugin stock-quotes is valid.Write a tool
defineToolPlugin takes plugin identity, an optional config schema, and a
static list of tools. Parameter and config types are inferred from TypeBox
schemas.
export default defineToolPlugin({ id: "stock-quotes", name: "Stock Quotes", description: "Fetch stock quote snapshots.", configSchema: Type.Object({ apiKey: Type.Optional(Type.String({ description: "Quote API key." })), baseUrl: Type.Optional(Type.String({ description: "Quote API base URL." })), }), tools: (tool) => [ tool({ name: "stock_quote", label: "Stock Quote", description: "Fetch a stock quote snapshot.", parameters: Type.Object({ symbol: Type.String({ description: "Ticker symbol, for example OPEN." }), }), async execute({ symbol }, config, context) { context.signal?.throwIfAborted(); return { symbol: symbol.toUpperCase(), configured: Boolean(config.apiKey), baseUrl: config.baseUrl ?? "https://api.example.com", }; }, }), ],});Tool names are the stable API. Pick names that are unique, lowercase, and specific enough to avoid collisions with core tools or other plugins.
Optional and factory tools
Set optional: true when users should explicitly allowlist the tool before it
is sent to a model:
tool({ name: "workflow_run", description: "Run an external workflow.", parameters: Type.Object({ goal: Type.String() }), optional: true, execute: ({ goal }) => ({ queued: true, goal }),});OmeniaClaw plugins build writes the matching toolMetadata.<tool>.optional
manifest entry, so OmeniaClaw can discover the tool without loading plugin
runtime code.
Use factory when a tool needs the runtime tool context before it can be
created. The factory keeps metadata static while letting the tool opt out for a
specific run, inspect sandbox state, or bind runtime helpers.
tool({ name: "local_workflow", description: "Run a local workflow outside sandboxed sessions.", parameters: Type.Object({ goal: Type.String() }), optional: true, factory({ api, toolContext }) { if (toolContext.sandboxed) { return null; } return createLocalWorkflowTool(api); },});Factories are still for fixed tool names. Use definePluginEntry directly when
the plugin computes tool names dynamically or combines tools with hooks,
services, providers, commands, or other runtime surfaces.
Return values
defineToolPlugin wraps plain return values into the OmeniaClaw tool-result
format:
- Return a string when the model should see that exact text.
- Return a JSON-compatible value when you want the model to see formatted JSON
and OmeniaClaw to keep the original value in
details.
tool({ name: "echo_text", description: "Echo input text.", parameters: Type.Object({ input: Type.String(), }), execute: ({ input }) => input,});tool({ name: "echo_json", description: "Echo input as structured JSON.", parameters: Type.Object({ input: Type.String(), }), execute: ({ input }) => ({ input, length: input.length }),});Use a factory tool when you need to return a custom AgentToolResult or reuse
an existing api.registerTool implementation. Use definePluginEntry instead
of defineToolPlugin when you need fully dynamic tools or mixed plugin
capabilities.
Configuration
configSchema is optional. If you omit it, OmeniaClaw uses a strict empty object
schema and the generated manifest still includes configSchema.
export default defineToolPlugin({ id: "no-config-tools", name: "No Config Tools", description: "Adds tools that do not need configuration.", tools: () => [],});When you include configSchema, the second execute argument is typed from the
schema:
const configSchema = Type.Object({ apiKey: Type.String(),}); export default defineToolPlugin({ id: "configured-tools", name: "Configured Tools", description: "Adds configured tools.", configSchema, tools: (tool) => [ tool({ name: "configured_ping", description: "Check whether configuration is available.", parameters: Type.Object({}), execute: (_params, config) => ({ hasKey: config.apiKey.length > 0 }), }), ],});OmeniaClaw reads plugin config from the plugin entry in the Gateway config. Do not hard-code secrets in source or in docs examples. Use config, environment variables, or SecretRefs according to the plugin's security model.
Generated metadata
OmeniaClaw discovers installed plugins from cold metadata. It must be able to read
the plugin manifest before importing plugin runtime code. defineToolPlugin
therefore exposes static metadata, and OmeniaClaw plugins build writes that
metadata into the package.
Run the generator after changing plugin id, name, description, config schema, activation, or tool names:
npm run buildOmeniaClaw plugins build --entry ./dist/index.jsFor a one-tool plugin, the generated manifest looks like this:
{ "id": "stock-quotes", "name": "Stock Quotes", "description": "Fetch stock quote snapshots.", "version": "0.1.0", "configSchema": { "type": "object", "additionalProperties": false, "properties": {} }, "activation": { "onStartup": true }, "contracts": { "tools": ["stock_quote"] }}contracts.tools is the important discovery contract. It tells OmeniaClaw which
plugin owns each tool without loading every installed plugin runtime. If the
manifest is stale, the tool may be missing from discovery or the wrong plugin
may be blamed for a registration error.
Package metadata
For the simple tool-plugin workflow, OmeniaClaw plugins build aligns
package.json to the selected single runtime entry:
{ "type": "module", "files": ["dist", "OmeniaClaw.plugin.json", "README.md"], "dependencies": { "typebox": "^1.1.38" }, "peerDependencies": { "OmeniaClaw": ">=2026.5.17" }, "OmeniaClaw": { "extensions": ["./dist/index.js"] }}Use built JavaScript such as ./dist/index.js for installed packages. Source
entries are useful in workspace development, but published packages should not
depend on TypeScript runtime loading.
Validate in CI
Use plugins build --check to fail CI when generated metadata is stale without
rewriting files:
npm run buildOmeniaClaw plugins build --entry ./dist/index.js --checkOmeniaClaw plugins validate --entry ./dist/index.jsnpm testplugins validate checks that:
OmeniaClaw.plugin.jsonexists and passes the normal manifest loader.- The current entry exports
defineToolPluginmetadata. - Generated manifest fields match the entry metadata.
contracts.toolsmatches the declared tool names.package.jsonpointsOmeniaClaw.extensionsat the selected runtime entry.
Install and inspect locally
From a separate OmeniaClaw checkout or installed CLI, install the package path:
OmeniaClaw plugins install ./stock-quotesOmeniaClaw plugins inspect stock-quotes --runtimeFor a packaged smoke, pack first and install the tarball:
npm packOmeniaClaw plugins install npm-pack:./OmeniaClaw-plugin-stock-quotes-0.1.0.tgzOmeniaClaw plugins inspect stock-quotes --runtime --jsonAfter installation, start or restart the Gateway and ask the agent to use the tool. If you are debugging tool visibility, inspect the plugin runtime and the effective tool catalog before changing the code.
Publish
Publish through ClawHub when the package is ready:
clawhub package publish your-org/stock-quotes --dry-runclawhub package publish your-org/stock-quotesInstall with an explicit ClawHub locator:
OmeniaClaw plugins install clawhub:your-org/stock-quotesBare npm package specs remain supported during the launch cutover, but ClawHub is the preferred discovery and distribution surface for OmeniaClaw plugins.
Troubleshooting
plugin entry not found: ./dist/index.js
The selected entry file does not exist. Run npm run build, then rerun
OmeniaClaw plugins build --entry ./dist/index.js or
OmeniaClaw plugins validate --entry ./dist/index.js.
plugin entry does not expose defineToolPlugin metadata
The entry did not export a value created by defineToolPlugin. Check that the
module default export is the defineToolPlugin(...) result, or pass the correct
entry with --entry.
OmeniaClaw.plugin.json generated metadata is stale
The manifest no longer matches the entry metadata. Run:
npm run buildOmeniaClaw plugins build --entry ./dist/index.jsCommit both OmeniaClaw.plugin.json and package.json changes.
package.json OmeniaClaw.extensions must include ./dist/index.js
The package metadata points at a different runtime entry. Run
OmeniaClaw plugins build --entry ./dist/index.js so the generator aligns the
package metadata with the entry you intend to ship.
Cannot find package 'typebox'
The built plugin imports typebox at runtime. Keep typebox in
dependencies, reinstall package dependencies, rebuild, and rerun validation.
Tool does not appear after install
Check these in order:
OmeniaClaw plugins inspect <plugin-id> --runtimeOmeniaClaw plugins validate --root <plugin-root> --entry ./dist/index.jsOmeniaClaw.plugin.jsonhascontracts.toolswith the expected tool names.package.jsonhasOmeniaClaw.extensions: ["./dist/index.js"].- The Gateway was restarted or reloaded after installing the plugin.