Technical reference
How plugins interact with taiku: the bridge, storage, permissions, and patterns that keep panels reliable in live sessions.
This page explains how taiku treats plugins in a live session—the bridge, where data lives, how permissions are enforced, and practical patterns for panels that stay fast and predictable while people work together.
Runtime architecture
The plugin system spans four layers, each with a distinct role. Understanding how they interact helps you choose the right tool for each job in your plugin.
The host bridge
The host bridge is the central coordination point. It runs in the main taiku
page (not in the iframe) and handles every request from every plugin. When a
plugin iframe sends a postMessage, the host bridge:
- Validates the
taiku:requestpayload and rejects traffic that is not from the plugin's live iframe. - Looks up the plugin's registered permissions.
- Checks whether the requested method is allowed by those permissions.
- Executes the method against the local session state (shells, users, workspace, etc.).
- Sends a
taiku:responseback to the iframe with the result or error.
The host bridge reads the same live session state the taiku UI uses—shells, workspace layout, who is connected, chat, and agent activity. That is why bridge calls are fast: they use in-memory session state instead of round-tripping everything through the server.
Permission enforcement happens here. If a plugin with only session.read tries
to call writeToShell, the bridge rejects the request before the method is even
invoked. The rejection comes back as an error on the same postMessage
channel—not as a separate HTTP error page.
The SDK client
The TaikuPlugin class in @taiku/plugin-sdk wraps the raw postMessage
protocol into typed async methods. It handles:
- Initialization: waits for the
taiku:initmessage, extractspluginIdandsessionId. - Request/response correlation: generates unique request IDs, tracks pending promises, resolves them when responses arrive.
- Timeouts: rejects pending requests after 10 seconds (configurable) if no response arrives.
- Event dispatching: routes
taiku:eventmessages to registered listeners, supports wildcard (*) subscriptions.
The SDK is intentionally thin. It does not cache data, retry failed requests, or manage UI state. Those decisions belong to you.
import { TaikuPlugin } from "@taiku/plugin-sdk";
const taiku = new TaikuPlugin({ timeout: 15_000 });
await taiku.ready();
// Every method returns a Promise that resolves when the host responds
const session = await taiku.getSession();
const shells = await taiku.getShells();SDK testing helpers
The MockHost class lets you test plugin logic without a running taiku session.
It simulates the host bridge by responding to postMessage requests with
configurable mock data. This is useful for:
- Automated tests for plugin logic where no browser session is running
- Developing UI components against predictable data
- Testing error handling (you can configure the mock to reject specific requests)
import { MockHost } from "@taiku/plugin-sdk/testing";
const mock = new MockHost({
pluginId: "my-org.my-plugin",
sessionId: "test-session-abc",
});
// The mock responds to getSession, getShells, etc. with default data.
// Your plugin code running in a test environment will receive these responses.Server routes
Some operations require server involvement. The taiku server exposes HTTP endpoints for:
- User KV: server-backed key-value storage scoped to user + plugin. Persists across sessions and devices.
- Plugin file storage: Server-side file storage for binary data and larger assets.
- Session-managed plugins: Server-coordinated plugin sets that stay synchronized across all clients in a session.
- Filesystem access: Proxied access to the session's working directory (requires the CLI to be running with filesystem capabilities).
These endpoints are authenticated and scoped. A plugin cannot access another
user's KV data or another plugin's file storage (unless it has kv.read
permission, which allows cross-plugin session KV reads).
Generated API documentation
The API Reference page is auto-generated from the SDK type definitions and server route schemas. It provides the canonical reference for every bridge method, HTTP endpoint, request parameter, and response shape. Use it alongside this page -- this page explains the why and when; the API page covers the exact what.
Persistence model in depth
Taiku provides four storage layers. Each has different characteristics that determine when it is the right choice.
Local secrets and settings
Scope: Current taiku client (browser tab or desktop app), current plugin.
Lifetime: Until the user clears taiku’s stored site data or removes the
plugin. Backed by: localStorage. Size limit: Same order of magnitude
as typical browser localStorage (a few MB per origin). Network: None --
never sent to the server.
Secrets and settings are declared in the plugin manifest and configured by the user through the plugin settings UI. The plugin reads them at runtime:
// Reading a secret (API key, token, etc.)
const apiKey = await taiku.getSecret("api_key");
if (!apiKey) {
await taiku.showToast(
"Please configure your API key in plugin settings",
"error",
);
return;
}
// Reading a setting with a manifest-defined default
const refreshInterval = await taiku.getSetting("refresh_interval");
// Returns the user's configured value, or the default from the manifest
// Reading all settings at once
const settings = await taiku.getSettings();
// { refresh_interval: 5000, theme: "dark", show_timestamps: true }
// Updating a setting programmatically
await taiku.setSetting("last_used_shell", 3);Secrets are stored separately from settings in localStorage under the key
taiku:plugin-secrets. They are keyed by plugin ID, so plugins cannot read each
other's secrets.
Use secrets for: API keys, access tokens, passwords -- anything sensitive that should never leave this client.
Use settings for: User preferences, display options, feature toggles -- anything the user might want to configure.
Session KV
Scope: Current session, current plugin (readable by other plugins with
kv.read). Lifetime: Until the session ends. Backed by: In-memory store
in the host bridge. Size limit: 64 KiB total per plugin per session.
Network: Synchronized across session participants via the WebSocket
connection.
Session KV is the primary mechanism for plugins to share state within a session. All participants see the same data. Writes from one client propagate to all others.
// Store collaborative state
await taiku.setPluginData("build_status", {
state: "running",
startedAt: Date.now(),
startedBy: "user-42",
});
// Read it from any client in the session
const status = await taiku.getPluginData("build_status");
// { state: "running", startedAt: 1710000000000, startedBy: "user-42" }
// Get all keys at once
const allData = await taiku.getAllPluginData();
// { build_status: { ... }, config: { ... } }
// Delete a key
await taiku.deletePluginData("build_status");
// Read another plugin's KV data (requires kv.read permission)
const otherData = await taiku.getOtherPluginData(
"taiku.music-player",
"now_playing",
);Use session KV for: Shared state that all participants need (playback position, build status, task state), lightweight plugin data that does not need to survive the session.
User KV
Scope: Current authenticated user, current plugin. Lifetime: Indefinite -- persists across sessions and devices. Backed by: persistent storage on the taiku server. Size limit: Reasonable per-key sizes (no enforced hard cap, but not designed for large blobs). Network: HTTP round-trip to the server on each read/write.
User KV is the right choice when state should follow the user, not the session. If a user logs in from a different machine, their user KV data is available.
// Save user preferences
await taiku.setUserData("preferred_layout", "vertical-split");
await taiku.setUserData("favorite_shells", [1, 3, 7]);
// Read them back -- works in any session, on any device
const layout = await taiku.getUserData("preferred_layout");
// "vertical-split"
// List all stored keys and values
const allUserData = await taiku.listUserData();
// { preferred_layout: "vertical-split", favorite_shells: [1, 3, 7] }
// Delete a key
await taiku.deleteUserData("favorite_shells");Use user KV for: Personal preferences, saved configurations, workspace layouts, any state that should follow the user across sessions.
Plugin file storage
Scope: Current authenticated user, current plugin. Lifetime: Indefinite -- persists on the server. Backed by: Server-side file storage. Size limit: Per-file and total limits (check the API reference for current values). Network: HTTP round-trip to the server on each read/write.
Plugin file storage is designed for binary data and larger assets that do not fit well in a key-value model. The Music Player plugin uses it to store uploaded audio files. A reporting plugin might use it to store generated PDF reports.
// Write a text file
await taiku.writePluginFile(
"config.json",
JSON.stringify({
version: 2,
rules: [{ pattern: "error", color: "red" }],
}),
);
// Write a binary file (ArrayBuffer)
const audioData = await fetchAudioBuffer();
await taiku.writePluginFile("notification.wav", audioData);
// List all stored files
const files = await taiku.listPluginFiles();
// [{ name: "config.json", size: 128 }, { name: "notification.wav", size: 44100 }]
// Read a file back as ArrayBuffer
const buffer = await taiku.readPluginFile("config.json");
const text = new TextDecoder().decode(buffer);
const config = JSON.parse(text);
// Delete a file
await taiku.deletePluginFile("notification.wav");
// Check storage usage
const usage = await taiku.getStorageUsage();
// { totalBytes: 44228, fileCount: 1, files: [...] }Use plugin file storage for: Audio files, images, generated reports, cached data, any binary or large content.
Storage decision matrix
| Question | Answer | Use this |
|---|---|---|
| Should other session participants see it? | Yes | Session KV |
| Should it persist after the session ends? | No | Session KV |
| Should it persist after the session ends? | Yes, for this user | User KV |
| Is it sensitive (API key, token)? | Yes | Local secrets |
| Is it a user preference? | Yes | Local settings or User KV |
| Is it binary or larger than a few KB? | Yes | Plugin file storage |
| Does it need to be fast (sub-millisecond)? | Yes | Session KV or local storage |
Session-managed plugins
By default, plugins are a per-client concern -- each user enables and configures their own plugins independently. But taiku also supports session-managed plugins, where the server coordinates a shared plugin set for the session. When session-managed plugins are active:
- The server broadcasts the plugin manifest set to all connected clients.
- Clients automatically load and enable the session's plugins.
- Plugin enablement is synchronized -- if an admin enables a plugin for the session, all participants get it.
- The
serverManagedflag on theRegisteredPluginobject indicates whether a plugin was loaded this way.
This matters when you ship a plugin because:
- Your plugin might be loaded without user action. In a session-managed context, the user did not explicitly choose to enable your plugin -- the session admin did. Design your plugin to work gracefully in this mode (do not assume the user has configured secrets or settings).
- Multiple instances run simultaneously. Every connected client creates its own iframe instance of your plugin. If your plugin writes to session KV, be aware that multiple instances may write concurrently.
- Lifecycle is tied to the session. When the session ends, session-managed plugins are unloaded. Do not rely on teardown hooks running reliably -- persist important state proactively.
Design patterns for good plugins
Minimal permissions
Request only the permissions your plugin actually uses. Every additional
permission is a trust decision the user has to make. A plugin that requests
terminal.write and fs.write but only uses them in an obscure edge case
creates unnecessary risk perception.
// Good: this monitoring plugin only reads
{
"permissions": ["session.read", "events.subscribe", "ui.panel"]
}
// Bad: this monitoring plugin requests write access it does not need
{
"permissions": [
"session.read", "session.write",
"terminal.read", "terminal.write",
"events.subscribe", "ui.panel"
]
}Bridge-first architecture
Default to using bridge calls for everything interactive. Only reach for the HTTP API when you genuinely need persistence beyond the session.
// Good: use session KV for ephemeral collaborative state
await taiku.setPluginData("active_panel", { view: "logs", shellId: 3 });
// Bad: use user KV for ephemeral state that does not need to persist
await taiku.setUserData("preferred_panel", { view: "logs", shellId: 3 });
// This makes a network round-trip for data that will be stale in secondsGraceful degradation
Handle missing permissions, missing data, and network errors gracefully. Your plugin should never show a blank panel because a single API call failed.
try {
const output = await taiku.getShellOutput(shellId, 10);
renderOutput(output);
} catch (err) {
renderError("Could not read shell output. The shell may have closed.");
}Event-driven updates
Subscribe to events instead of polling. Taiku's event system delivers real-time updates for shell data, user joins/leaves, chat messages, agent activity, and custom plugin events.
// Good: react to events
taiku.on("shell:data", (event) => {
refreshShell(event.data.shellId);
});
// Bad: poll on a timer
setInterval(async () => {
const shells = await taiku.getShells();
for (const shell of shells) {
const output = await taiku.getShellOutput(shell.id, 5);
renderShell(shell.id, output);
}
}, 1000);Dark theme by default
Taiku's UI is dark-themed. Your plugin panels should match. Use #18181b for
backgrounds, #a1a1aa for text, and #27272a for borders. Consult the built-in
taiku's built-in plugins for color reference.
Related documentation
- Plugins overview — install flow, permissions, and how plugin-owned state works
- Built-in Plugins — what ships with taiku and when to use each one
- Building Plugins — step-by-step path from manifest to a working panel
- API Reference — every bridge method and HTTP endpoint your code can call