Notes

How it works

The SDK natively handles agent_approval_start and step_await (with approval_required) SSE events. It creates an approval bubble inline in the chat. When the user clicks Approve or Deny, the SDK calls the approval API and pipes the response stream back through the chat.

Programmatic control

Use controller.resolveApproval(approvalId, decision) to resolve approvals programmatically from your application code.

Controller events

Listen for approval:requested and approval:resolved events on the controller to react to the approval lifecycle in your app.

Customization

Style the approval bubble via config.approval (colors, labels), override rendering with the renderApproval plugin hook, or disable entirely with approval: false.

Plugin renderer (Pixel Guardian)

Switch the Renderer to Plugin to replace the built-in card with a fully custom renderApproval plugin: a pixel-art "Gatekeeper" who blocks the path with a speech bubble — Halt! The assistant wants to use "{tool}". Shall I let it pass? — and his own ✦ Allow, Always let this through, and Deny buttons. He idles with a bob and a flickering torch, then opens the gate on approve or raises his shield on deny.

A custom renderer resolves the approval via the approve / deny callbacks passed into the hook, and branches on approval.status to render the resolved state. An approval is a single binary gate, so "Always let this through" is just approve({ remember: true }): it resolves the current approval the same as a plain allow, and the remember flag is forwarded to onDecision so your app can persist a don't-ask-again policy for future approvals (watch the Event log for "(remember)"). The whole character is self-contained — an inline SVG sprite, CSS keyframes, and the widget's plugin-kit for shadow-DOM-safe styles. See src/plugins/pixel-guardian-plugin.js.

Config snippet

// Approval config { approval: { title: "Approval Required", approveLabel: "Approve", denyLabel: "Deny", approveButtonColor: "#16a34a", // Custom handler (optional) onDecision: async (data, decision) => { // Return Response or ReadableStream } } }