Skip to content

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.

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:

Terminal window
wuu plugin dev ./hello.ts

This 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:

Terminal window
wuu plugin inspect ./hello.ts
wuu plugin install ./hello.ts

Installing 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.

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:

Terminal window
wuu plugin dev --watch=false /path/to/authorized/source

Normal 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.

In a Wuu-engine conversation, the deferred plugin_manager tool provides a scoped development path:

  1. Use write_file to save hello.ts inside the conversation’s workspace.
  2. Discover plugin_manager and call it with {"action":"apply","path":"./hello.ts"}. A plugin directory is also accepted. Apply publishes once, like wuu plugin dev --watch=false; it does not start a watcher.
  3. Check the result, then finish the current turn. Its tool definitions stay pinned to the old generation.
  4. 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 exact id to 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.

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.

Run these commands from the directory where you want the plugin project. No Wuu checkout or npm release of @wuu/plugin-sdk is required:

Terminal window
wuu plugin create hello-plugin
cd hello-plugin
npm install
npm run build

The 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.

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.

Terminal window
npm run build
wuu 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.

From a source checkout, build the host once and prepare the bundled plugin workspace:

Terminal window
npm ci --prefix plugins
npm --prefix plugins run build
make build-go
WUU_HOME="$PWD/.wuu-dev" ./bin/wuu plugin dev plugins/todo

Keep 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:

Terminal window
node scripts/bundled-plugins-e2e.mjs
WUU_SELF_EXTENSION_BINARY="$PWD/bin/wuu" \
WUU_SELF_EXTENSION_EVIDENCE="$PWD/.tmp/e2e/plugin-generation" \
go test -count=1 -run '^TestAgentSelfExtensionProcess$' ./cmd/wuu

The 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.

Terminal window
wuu plugin pack .
wuu plugin install ./hello-plugin-0.1.0.zip

Installing 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.