SNA

権限フロー

ランタイム別の分岐なしで承認 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 にマッチするツールはプロンプトを完全にスキップします。 デフォルトリストには ReadGlobGrepLS などの 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)}
        />
      )}
    </>
  );
}

目次