Robutler

host.commands

host.commands is how an app exposes a command surface that agents can drive. You register a named handler with host.commands.handle(name, fn); an agent (or another part of the platform) then invokes it. This is the mechanism behind agent-driven editing: the same code paths the app's own UI uses, reachable by name.

The command surface has two halves. host.commands is the runtime half (registering handlers in the app). The declaration half lives in your manifest's interface.commands, which is what agents read to discover what they can call. Keep the two in sync. See the agent command interface for the full picture and how agents invoke via workspace_widgets_invoke.

API

interface CommandsNamespace {
  handle<A = unknown, R = unknown>(
    name: string,
    fn: (args: A, ctx: { sourceItemId: string | null; name: string }) => R | Promise<R>,
  ): () => void;          // returns an unregister function
  list(): string[];       // declared command names
}

Register a handler

await host.ready();

const off = host.commands.handle('goTo', ({ slug }: { slug: string }, ctx) => {
  navigate(slug);
  return { ok: true, slug };
});

// later, on teardown:
off();

The handler receives:

  • args: whatever the caller passed (shape it to match your manifest declaration).
  • ctx.sourceItemId: the workspace item id of the caller, or null.
  • ctx.name: the command name, handy when one function fields several commands.

The return value (or resolved promise) is sent back to the caller as the command result. Throwing rejects the call with an error frame.

Streaming commands

For long-running work, return a runId quickly and emit progress events through host.emit. Declare the event names under the command's streams in your manifest so the calling agent subscribes ahead of invoking:

host.commands.handle('render', async ({ scene }) => {
  const runId = crypto.randomUUID();
  (async () => {
    for await (const frame of renderScene(scene)) {
      await host.emit({ type: 'render.progress', runId, frame });
    }
    await host.emit({ type: 'render.done', runId });
  })();
  return { runId };
});

Discoverability

host.commands.list() returns the command names currently registered. For an agent to discover the surface, declare each command (with description, args, returns, optional streams) in your widget.json interface.commands. Agents read that declaration through workspace_widgets_list, then call workspace_widgets_invoke(itemId, name, args).

Built-in commands

Every app answers a set of built-in commands for free, without registering anything: __describe, __getState, __screenshot, __record, and __capture. See the agent command interface for what each does.

On this page