권한 흐름
런타임별 분기 없이 승인 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)}
/>
)}
</>
);
}