権限フロー
ランタイム別の分岐なしで承認 UI を接続します。
ユーザーに見える承認フローは、ランタイムが変わっても同じです。アプリが 権限イベントを購読し、ダイアログを出し、決定を POST します。SNA は Claude の PreToolUse フックと Codex の JSON-RPC 承認を 1 つのイベントに そろえます。
サーバ側
エージェント起動時に権限モードを選びます。
await client.agent.start(sessionId, {
provider: "claude-code",
permissionMode: "default", // プロンプト表示; 代替: "acceptEdits" | "bypassPermissions"
});購読と応答
await client.agent.subscribePermissions();
client.agent.onPermissionRequest(async ({ session, request }) => {
const approved = await showApprovalDialog(request);
await client.agent.respondPermission(session, approved);
});同じコールバックが Claude PreToolUse フックと Codex approval/request
通知の両方で呼ばれます。
Safe-tool allowlist
設定された allowlist にマッチするツールはプロンプトを完全にスキップします。
デフォルトリストには Read、Glob、Grep、LS などの read-only
操作が含まれます。拡張するには次のように指定します。
await client.agent.start(sessionId, {
provider: "claude-code",
allowedTools: ["Read", "Glob", "Grep", "Bash(ls:*)", "Bash(git status:*)"],
});パターンは Claude Code の tool-pattern 構文に従います。同じ文字列リスト がランタイム別に正規化されるので、アプリ設定をランタイムで 分岐させる必要はありません。
レンダラパターン (Electron / React)
バンドル済みの <SnaChatUI> は権限ダイアログを標準で提供します。
アプリの周りに <SnaProvider> を巻けばプロンプトが自動的に届きます。
カスタム 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)}
/>
)}
</>
);
}