SNA

권한 흐름

런타임별 분기 없이 승인 UI를 연결합니다.

사용자가 보는 승인 흐름은 런타임이 달라도 같습니다. 앱이 권한 이벤트를 구독하고, 다이얼로그를 띄우고, 결정을 다시 POST합니다. SNA는 Claude의 PreToolUse 훅과 Codex의 JSON-RPC 승인을 하나의 이벤트로 맞춥니다.

서버 측

에이전트 시작 시 권한 모드 선택:

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 문법을 따릅니다. 같은 문자열 리스트가 런타임별로 정규화되므로 앱 설정은 런타임에 따라 쪼개지 않아도 됩니다.

Renderer 패턴 (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)}
        />
      )}
    </>
  );
}

목차