SNA

Permission flows

Wire an approval UI without diverging per runtime.

The user-facing pattern is the same across runtimes: the consumer app subscribes to permission events, shows a dialog, posts back the verdict. SNA hides Claude's PreToolUse hook vs Codex's JSON-RPC approval behind one event.

Server side

Pick a permission mode when starting the agent:

await client.agent.start(sessionId, {
  provider: "claude-code",
  permissionMode: "default", // prompts; alternatives: "acceptEdits" | "bypassPermissions"
});

Subscribe + respond

await client.agent.subscribePermissions();

client.agent.onPermissionRequest(async ({ session, request }) => {
  const approved = await showApprovalDialog(request);
  await client.agent.respondPermission(session, approved);
});

The same callback fires for Claude PreToolUse hooks and Codex approval/request notifications.

Safe-tool allowlist

Tools that match the configured allowlist bypass the prompt entirely. The default list includes Read, Glob, Grep, LS and other read-only operations. To extend:

await client.agent.start(sessionId, {
  provider: "claude-code",
  allowedTools: ["Read", "Glob", "Grep", "Bash(ls:*)", "Bash(git status:*)"],
});

Patterns use Claude Code's tool-pattern syntax. The same string list is normalized per-runtime, so the consumer config doesn't need to fork by runtime.

Renderer pattern (Electron / React)

The bundled <SnaChatUI> ships a permission dialog out of the box. Wire <SnaProvider> around your app and the prompt flows automatically. For custom UI:

function PermissionGuard({ children }: React.PropsWithChildren) {
  const [pending, setPending] = useState(null);
  const client = getSnaClient();

  useEffect(() => {
    client.agent.subscribePermissions().catch(() => {});
    return client.agent.onPermissionRequest(setPending);
  }, [client]);

  return (
    <>
      {children}
      {pending && (
        <Dialog
          request={pending.request}
          onApprove={() => client.agent.respondPermission(pending.session, true)}
          onDeny={() => client.agent.respondPermission(pending.session, false)}
        />
      )}
    </>
  );
}

On this page