Actions and app commands

Handle local actions, control which URLs may open, and let the agent drive the app with opt-in server commands.

Local actions

Kletso.instance.registerAction('open_product', (args, ctx) async {
  final sku = args['sku'] as String? ?? '';
  Kletso.instance.close();
  await Navigator.of(ctx.context).pushNamed('/product/$sku');
});
  • args is the action’s declared args merged with whatever the widget passed to executeAction.
  • ctx is a KletsoActionContext: context (a BuildContext), origin (surface, server, notification), surface and node (null for server commands), action, client.
  • After the handler runs, the SDK sends reportLocalAction(handled: true|false) so the conversation log shows what the app did. Disable with reportLocalActions: false.

Unregistered names are ignored and reported as unhandled; nothing crashes.

Firing actions from your widgets

Inside a component builder, ctx.executeAction('add') looks up the action with that id on the node and dispatches it by kind: local to your handler, agent/submit/confirm/workflow to the runtime, url to onOpenUrl. The “both” pattern (change app state and tell the agent) is simply doing both in the callback.

URLs

Kletso.instance.onOpenUrl = (uri) => launchUrl(uri, mode: LaunchMode.externalApplication);

Without onOpenUrl, links are shown but do nothing. Every URL passes KletsoUrlPolicy first: https only, host must be on the agent’s URL allowlist (delivered in the session as allowedUrlHosts; *.example.com matches subdomains). javascript:, data:, file: and http: are always blocked.

Server commands (app.command)

The agent has a built-in app_command tool. When it calls it, the SDK receives app.command { name, args, closeChat } and runs the local action with origin: server, no tap involved. This is off by default.

KletsoConfig(allowServerCommands: true)
Kletso.instance.ui.contextProvider = () => navigatorKey.currentContext;

Outcomes are published on client.ui.commandResults as ran, disabled, noHandler or noContext. Trigger rules and notification taps use the same registry, so one open_checkout handler serves taps, notifications and commands.

Confirmations

You do not write code for confirmations. When a tool that requires confirmation is called, the runtime renders a confirm block with approve and decline confirm actions; the built-in block handles the tap and the turn resumes. If you replace the confirm block with your own widget, call ctx.executeAction('yes') / ('no') (the ids on the node) or ctx.execute(action).

Last updated 2026-09-28 · Report an issue with this page