Plugin SDK reference
Package: @zana-ai/zcc-plugin-sdk (PLUGIN_SDK_VERSION / engines.zccPluginSdk).
Server — `ZccPluginApi`#
Handed to export default function plugin(zcc).
| Surface | Purpose |
|---|---|
pluginId |
Derived id |
log |
debug/info/warn/error |
settings.define |
Declarative settings the host/CLI can render (string / boolean / number / select / project) |
storage.kv |
Per-plugin KV |
rpc.method |
Renderer/host RPC |
realtime.publish |
Events |
background.service / schedule |
Long-running work |
agents.contributeInstructions / contributeSkills / registerTool / configure |
Agent capabilities. registerTool takes Zod or JSON Schema parameters (parsed on invoke). configure sees thread / project / environment / host / provider / origin / frozen pluginMetadata |
ui.requestInput |
Host prompt |
status.needsConfiguration |
Degraded-until-configured |
sdk.threads / sdk.files / sdk.environments / sdk.providers |
Product SDK: attributed spawn with host, environment and service tier; thread metadata; confined host file read/write; environment PR lookup; provider models discovered on the selected host |
sdk.system.defaultHost() |
Primary enrolled host ID, or null when none is configured |
http.route |
Authenticated plugin HTTP routes; request headers and bounded rawBody preserve uploaded bytes; response body accepts a string or Uint8Array |
sdk.hosts.list({ signal? }) |
Read enrolled machine IDs and names; credentials and connection metadata are excluded. Use an exact ID when machine names are ambiguous. |
services.provide / services.use / services.has |
Experimental plugin-to-plugin SDK (live proxy; has after provide; service_unavailable until provided) |
onDispose |
LIFO teardown |
Plugins do not get ctx.exec permission tokens. They are full-trust in the
server process and must not be given host-daemon tokens.
sdk.threads.spawn accepts hostId, serviceTier: "default" | "fast" and
environment: { kind: "unmanaged" | "personal" | "worktree" | "reuse", ... }.
The server validates environment choices and authorizes host/project access.
Omit the environment to use the project's default. Provider model discovery
accepts hostId and returns modelLoadError when discovery fails.
sdk.files.read and sdk.files.write resolve an omitted hostId to the
primary host and retain the host file service's path confinement.
sdk.environments.pullRequest({ environmentId }) returns pullRequest or
unavailableReason. Thread summaries include titles and update timestamps.
Plugin HTTP bodies are capped at 25 MiB; JSON routes also receive parsed
body, while attachment routes can consume the exact rawBody bytes.
Plugin services (experimental)#
zcc.services.provide(impl) is keyed by this plugin's id (another plugin
cannot impersonate it). zcc.services.use(id) returns a live proxy that
always dispatches to the current impl, so a provider reload does not drop
consumers. zcc.services.has(id) is true after that plugin has called
provide. Missing / disposed providers throw PluginServiceUnavailableError
(code: 'service_unavailable'). Stay experimental until a second in-tree
consumer exists — see packages/plugin-sdk/docs/api_to_audit.md.
The Salesforce plugin publishes @zcc-ext/salesforce/sdk (SalesforceSdk) as
a types-only contract. Consumers import type and use('salesforce');
they must not import ConnectionManager / createSalesforceSdk or receive
org credentials. Reuse + extraction map: plugins/salesforce/SDK.md.
App — `definePluginApp`#
Slots are grouped the same way as the in-app Plugin Guide (Plugins hub).
When you add or rename a surface, update plugins/plugin-guide/src/surfaces.ts,
this page, and run node website/scripts/sync-plugin-guide.mjs (copies the map
into website/lib/plugin-guide/; do not hand-edit that folder).
App shell#
navPanel— sidebar entry + full view.placement: "extensions"lists under Plugins;placement: "unlisted"is a full/plugins/<id>/<path>page with no rail row (open viatoPluginPanel)experimental_projectMenuAction— project-row overflow, or the Projects Organize menu (placement: "workspace"means no project is selected;projectIdisnull).toProjectopens aprojectTabexperimental_createProjectAction— Add project (+) menu (openDialogmounts an optional create wizard;addProjectregisters the folder)sidebarFooterAction— host-rendered footer icon (openSettings/toPluginPanel)
Project shell#
projectTab— per-project rail tab (global: falsehides the sidebar entry). Useheader: 'custom'when the component supplies its own toolbar; render itsheaderActionsprop there to retain the host split-pane controls without an extra title row.experimental_agentsBoardAction— toolbar control on the Agents board (projectIdisnullon the cross-project Agents nav)experimental_agentCardAction— right-click item on an Agents board cardprojectStatusbarItem— project statusbar chip (alignleft/right;runmaytoProject/toPluginPanel/openDialog/openMenu)
Home#
homepageSection— card on the Home compose surfaceexperimental_newThreadPanelAction— CTA under New Chat compose (can open a compose-time side panel)
Composer#
composer—composer.customize: actions, banners, plus-menu items, meta chips, advanced fields, and rich-text effects. Scope tothread,new-thread,cli-agent,queued-message, orside-chat.useComposer().experimental_setLaunchPatchoverlays spawn extraArgs / profile / routing
Thread#
threadPanelAction— thread side-panel tabs; optionalscopesinclude"agent-session"for the CLI-agent inspector. Both this slot andexperimental_newThreadPanelActionaccept an optionalcategorylabel for the New Tab launcher. Omit it to group under the installed plugin’s display name. Categories are collapsible; search matches tool titles and category names across all groups. The optionaliconis shown beside the tool.pendingInteraction— custom in-thread prompt UI (idmust matchzcc.ui.requestInputrendererId)experimental_threadHeaderAction— action in the thread detail headerexperimental_threadList— replace the Agents list pane (exclusive; last registered wins Appearance pin)experimental_timelineRenderer— custom body for a timeline row kindmessageDirective— render::name{attr}leaves in markdownmessageAction— per-message menu item on the timelinefileOpener— open a previewed file by extension
Command palette#
commandPaletteAction— a row in ⌘P Extensions (toPluginPanel/toProject/openPanel)
Configure#
settingsSection— React settings UI on the plugin’s Plugins hub detail (Configure)
Platform#
Headless (no pixels), plus picker chrome:
skills—zcc.skills/contributeSkillscli—zcc.cli.registermcp—zcc.mcpServerssettings-define—zcc.settings.definebackground—zcc.background.service/scheduledesktop-browsers—zcc.sdk.experimental_desktopBrowsers(experimental; first-party Browser Automation)contentScripts—contentScripts.registerexperimental_providerIcon— picker glyph for a provider id
Registrations replace wholesale per plugin id. Each carries a generation used
as the React remount key. Wrap UI in PluginSlotBoundary. Live-reload with
zcc plugin dev.
Manifest#
package.json zcc block, parsed by @zana-ai/zcc-domain readPluginManifest.
Id from derivePluginId(package.name). Reserved sentinel __builtin__ is
host chrome, not a plugin.
| Field | Meaning |
|---|---|
skills |
Directory roots (BB). Default ["skills"]; [] opts out. Each child dir with a regular SKILL.md is a skill named after the folder. |
mcpServers |
Map of Claude CLI MCP servers for PTY / CLI Agent (--mcp-config). stdio command is basename-only; relative args are rewritten to contained paths. Conversation threads do not read this map — they get zcc.agents.registerTool via bb-bridge. |
extra |
Opaque JSON object (≤32 keys, ≤8 KiB). Displayed on install; never synced as skills/MCP. |
requires |
Other plugin ids this plugin consumes via zcc.services.use. Host topo-sorts load order. A missing required plugin marks the consumer needs-configuration. |
Durable skills belong in zcc.skills. agents.contributeSkills is a runtime extra. There is no registerMcpServer. registerTool (parameters: Zod or JSON Schema) is the conversation-thread tool path; zcc.mcpServers is the Claude CLI / PTY path.
Do not put secrets in extra. Env values on mcpServers are written to .mcp.json only — the hub sees envKeys.
Legacy extension.json is shimmed for one release via shimLegacyExtensionManifest.