Agent plugin quickstart
Start with one TypeScript file and a Wuu CLI. Node.js 22.13 or later with --experimental-transform-types support is required for this path. Wuu supplies its matching official TypeScript SDK; no checkout, npm install, manifest, or build command is needed.
Run one file
Section titled “Run one file”Save this as hello.ts:
import type { RuntimePlugin } from "@wuu/plugin-sdk";
export default { initialize() { return { protocol_version: 3, tools: [{ id: "greet", description: "Return a friendly greeting", input_schema: { type: "object", properties: {}, additionalProperties: false }, activity: { read_only: true, concurrency_safe: true, risk: "low" }, }], }; }, executeTool() { return { result: { content: [{ type: "text", text: "Hello from my plugin!" }] } }; },} satisfies RuntimePlugin;Run:
wuu plugin dev ./hello.tsThis authorizes that exact file and watches it. A single .ts file can default-export a RuntimePlugin, export activate(api) / activatePresentation(api) for Wuu Desktop UI, or export both to define a unified full-stack plugin in one file. You can also export an optional export const manifest = { version, description, permissions, contributes, interface } object when you want to declare desktop contributions or metadata inline without a plugin.json file. Wuu snapshots the file, checks syntax, imports, and exports without calling initialize, and publishes an isolated generation. Edit the greeting and save to reload. Syntax, import, or export failures retain the last published generation; fixing the file and saving retries automatically, including after an initial failure. Publication can proceed while a turn is running; that turn and its outstanding workers or managed processes keep their existing generation. The conversation can adopt the update at an idle turn boundary after that work settles and the host finishes preparation and activation. The actual host collects the candidate’s declarations in initialize, then calls prepare to validate state through the selected services. Preparation failure keeps the old runtime; native activation follows the runtime change’s commit. If activation fails after that point, the new generation remains published with failure diagnostics rather than rolling back. Publication does not prove activation in every open conversation; check the plugin’s actual status and the generation contract.
You can also inspect, install, or update a single .ts file directly without creating a directory or .zip archive:
wuu plugin inspect ./hello.tswuu plugin install ./hello.tsInstalling a local .ts file grants trust to that canonical file path and enables it immediately; editing ./hello.ts and re-running wuu plugin install ./hello.ts (or wuu plugin update <id> ./hello.ts) performs a same-source update without an extra approval step.
Use a regular .ts file, not a symlink. Only Node builtins and @wuu/plugin-sdk imports are supported in this single-file path. Other local files, npm dependencies, JSX, and custom tsconfig transforms need the package workflow below. TypeScript execution does not type-check your code. Keep stdout for the protocol; use console.error for diagnostics. Importing a module runs its top-level code, even during validation. Development authorization grants trust to this source; it does not sandbox code or approve a distributed package.
Source changes are debounced and reconciled every two seconds, including after filesystem-watch errors. --poll 500ms changes that positive interval; --watch=false publishes once and exits. Ctrl+C stops watching, leaving the published development generation available. Disable it through Skills & Plugins or wuu plugin disable <id> using the ID printed by the command.
Recover an older development generation
Section titled “Recover an older development generation”Development receipts created before storage-owner bindings can still load with a
plugin:dev:<id> storage identity. If the installed plugin already received an
ownership transfer, that older generation can fail with plugin storage transfer conflicts with existing ownership metadata; restarting alone does not update its
receipt.
When the existing transfer belongs to the installed plugin and the development
identity has no separate state, republish the same authorized source with the
current Wuu CLI and the same WUU_HOME:
wuu plugin dev --watch=false /path/to/authorized/sourceNormal publication validates the candidate and writes a signed storage-owner binding. It does not delete ownership metadata or replay completed transfers over updated or forgotten records. Verify the published plugin’s activation before retrying its UI. If the development identity already has separate data, or the conflict names another owner, resolve that ownership explicitly rather than using this procedure to combine namespaces. Never delete storage files, fences, or receipts to suppress the error.
Let the agent add a tool
Section titled “Let the agent add a tool”In a Wuu-engine conversation, the deferred plugin_manager tool provides a scoped development path:
- Use
write_fileto savehello.tsinside the conversation’s workspace. - Discover
plugin_managerand call it with{"action":"apply","path":"./hello.ts"}. A plugin directory is also accepted. Apply publishes once, likewuu plugin dev --watch=false; it does not start a watcher. - Check the result, then finish the current turn. Its tool definitions stay pinned to the old generation.
- Once the host has activated the published generation, discover and call the new greeting tool in an idle turn. Use
{"action":"status"}or status with an exactidto compare published state with the current conversation’s generation. Check the actual tool result before calling it usable.
For authoring through the local bash tool (Git Bash on Windows), use
"$WUU_CLI_EXECUTABLE" plugin create <name> or
"$WUU_CLI_EXECUTABLE" plugin describe --types-dir <new-directory>.
Wuu supplies the absolute path of its running CLI, so an older wuu on PATH
or another installed app cannot silently select the wrong SDK. The describe
command inspects a new CLI composition, not the conversation’s pinned generation.
This hint does not change PATH or grant additional permissions. It is set by
the execution host, not user configuration: a remote Wuu CLI worker supplies its
own executable, while library hosts without an authoring CLI omit it rather than
forwarding a host-only path.
A new optional plugin may still be preparing while unrelated turns continue. Publication alone does not make its tools available; app-server clients can await the exact fingerprint in that server’s committed inventory as described in the generation contract. Outstanding conversation workers and managed processes must settle before this conversation can adopt the update. Do not leave a wuu plugin dev watcher running as work owned by this turn; use the one-shot apply path above.
Apply can execute module top-level code and package build scripts on the host. Before validation or import, it requires an unconfined session, or standard mode with an enabled, configured permission reviewer returning an allow decision. Read-only mode rejects mutations. Workspace write permission alone does not authorize host code execution. The host chooses the executable and plugin storage; this tool does not grant general filesystem access to $WUU_HOME. Remote-environment source paths are not supported.
{"action":"disable","id":"<plugin-id>"} and {"action":"enable","id":"<plugin-id>"} change an existing plugin’s enabled state; enabling does not supply missing trust. A committed disable revokes the plugin across retained generations without waiting for the next idle turn. Applying an update does not automatically start another turn. Published or installed state, callable tools in this conversation, and rendered desktop UI are separate checks. For desktop contributions, inspect the actual view as well as runtime status.
Grow into a package
Section titled “Grow into a package”For multiple source files, desktop contributions, or distribution, use a package. This example adds a greet tool to the built-in Wuu engine using the matching SDK embedded in your CLI.
Prepare the SDK and package
Section titled “Prepare the SDK and package”Run these commands from the directory where you want the plugin project. No Wuu checkout or npm release of @wuu/plugin-sdk is required:
wuu plugin create hello-plugincd hello-pluginnpm installnpm run buildThe generator creates plugin.json, package.json, tsconfig.json, src/index.ts, and a local sdk/ source package. @wuu/plugin-sdk resolves to that snapshot, while npm installs the ordinary TypeScript, type-definition, and bundler dependencies. Its manifest starts node dist/index.js using the wuu-plugin-v1 runtime transport. The generated build type-checks your source and bundles the SDK into the runtime file: Wuu’s package preparation excludes node_modules, so installed plugins do not depend on the development directory. --type desktop and --type full also generate browser-targeted desktop bundles.
Add the tool
Section titled “Add the tool”Replace src/index.ts with:
import { runJSONLRuntime, type RuntimePlugin } from "@wuu/plugin-sdk";
const plugin: RuntimePlugin = { initialize() { return { protocol_version: 3, tools: [{ id: "greet", description: "Return a greeting for a name", input_schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false, }, activity: { read_only: true, concurrency_safe: true, risk: "low" }, }], }; }, executeTool(params) { const name = (params.arguments as { name?: unknown })?.name; if (params.tool_id !== "greet" || typeof name !== "string" || !name.trim()) { return { result: { is_error: true, content: [{ type: "text", text: "A non-empty name is required." }], }, }; } return { result: { content: [{ type: "text", text: `Hello, ${name}!` }] } }; },};
runJSONLRuntime(plugin, { input: process.stdin, output: process.stdout }).catch(error => { console.error(error); process.exitCode = 1;});initialize declares the model-visible description, input schema, and scheduling metadata. The host namespaces the tool ID. executeTool still validates its input and returns is_error for a tool failure. Keep stdout for the JSONL protocol; write diagnostics to stderr.
Build and try it
Section titled “Build and try it”npm run buildwuu plugin validate .wuu plugin test .wuu plugin dev .validate checks the package. test starts its runtime and checks initialization, protocol negotiation, capability descriptors, and tool registration; it does not exercise greet or prove behavior in a conversation.
dev authorizes this directory, builds a candidate, and publishes a development generation. Keep it running while editing. Saves trigger another build; build or package-validation failure leaves the previously published generation in place and continues watching for a repair. Running work retains its generation while the update is published for subsequent idle turns. The command’s successful publication is not proof that every desktop or runtime contribution activated successfully—inspect the plugin’s actual status too.
Open a conversation using the Wuu engine and ask it to use the greeting tool for a name. Confirm the tool result says hello. This verifies the behavior that the contract test does not cover.
Develop a bundled plugin
Section titled “Develop a bundled plugin”From a source checkout, build the host once and prepare the bundled plugin workspace:
npm ci --prefix pluginsnpm --prefix plugins run buildmake build-goWUU_HOME="$PWD/.wuu-dev" ./bin/wuu plugin dev plugins/todoKeep that last command running and use the development app with the same
WUU_HOME. Edit plugins/todo/src/index.ts and save. The ordinary package watcher
rebuilds and publishes the candidate. This explicitly authorized development
package replaces the bundled todo candidate without acquiring bundled trust or
provenance. Plugin-only TypeScript edits do not require another Go build. Changes
to the Go host or its public services still do.
All eight bundled plugins use this workflow. plugin.json and dist/ under each
source directory are generated development files; edit the source or the canonical
manifest under internal/plugin/bundled/<id>/ instead. To build only one runtime,
run node scripts/build-bundled-plugins.mjs --plugin=todo. To verify that committed
bundled runtimes match their source, run npm --prefix plugins run build:check.
A current turn and its outstanding workers or managed processes keep their original implementation and files. The same conversation adopts the replacement at its next idle boundary after that work settles. A preparation or pre-commit failure leaves the previous generation available; activation failure after commit leaves a failed new generation. Verify an actual tool result after adoption; a successful build is not proof of activation.
Run credential-free process acceptance from the checkout:
node scripts/bundled-plugins-e2e.mjsWUU_SELF_EXTENSION_BINARY="$PWD/bin/wuu" \ WUU_SELF_EXTENSION_EVIDENCE="$PWD/.tmp/e2e/plugin-generation" \ go test -count=1 -run '^TestAgentSelfExtensionProcess$' ./cmd/wuuThe first command drives the shipped plugin executables over JSONL with disposable
host services. It covers tools, persisted state, background work, cancellation,
and shutdown, saving protocol transcripts, runtime hashes, and results under
.tmp/e2e/bundled-plugins/. The second runs the real Go app-server against a local
synthetic model endpoint. It verifies live-turn publication, adoption after same-server readiness,
last-good recovery, and disable cleanup without a model account. Neither command
checks rendered desktop contributions.
Distribute the package
Section titled “Distribute the package”wuu plugin pack .wuu plugin install ./hello-plugin-0.1.0.zipInstalling a local package confirms trust and enables code execution. Updates from the same recorded source retain trust; changing the source requires confirmation. Development-directory authorization is separate and is not distributed in the zip. Plugins run with your user authority; package validation is not a security audit. See plugin management for disabling and removing packages.
For persistent state, services, and lifecycle callbacks, continue with the authoring reference. To add UI instead of a model tool, use the desktop quickstart.
Their package metadata also watches the shared SDK, build script, and canonical author assets. Prompt and desktop-asset edits follow the same reload path; generated runtime output is excluded to avoid rebuild loops.