2025-12-07 19:32:53 +02:00
|
|
|
import { create } from "zustand";
|
2026-07-12 15:03:16 +03:00
|
|
|
import { persist } from "zustand/middleware";
|
2026-03-20 01:01:03 +02:00
|
|
|
import type { Session } from "@opencode-ai/sdk/v2/client";
|
2026-07-12 15:03:16 +03:00
|
|
|
import { autoRespondsPermission, type PermissionAutoAcceptMap } from "./utils/permissionAutoAccept";
|
2026-07-13 23:18:12 +03:00
|
|
|
import { getAllSyncSessionMap } from "@/sync/sync-refs";
|
2026-07-12 15:03:16 +03:00
|
|
|
import { runtimeFetch } from "@/lib/runtime-fetch";
|
|
|
|
|
import { isVSCodeRuntime } from "@/lib/desktop";
|
2026-06-30 04:47:52 -04:00
|
|
|
import { createDeferredSafeJSONStorage } from "./utils/safeStorage";
|
2026-03-31 18:47:00 +03:00
|
|
|
import { useSessionUIStore } from "@/sync/session-ui-store";
|
2026-07-12 15:03:16 +03:00
|
|
|
import { opencodeClient } from "@/lib/opencode/client";
|
2025-12-07 19:32:53 +02:00
|
|
|
|
2026-07-12 15:03:16 +03:00
|
|
|
type PermissionPolicySnapshot = {
|
|
|
|
|
sessions: PermissionAutoAcceptMap;
|
|
|
|
|
};
|
2025-12-07 19:32:53 +02:00
|
|
|
|
2026-07-12 15:03:16 +03:00
|
|
|
interface PermissionStore {
|
|
|
|
|
autoAccept: PermissionAutoAcceptMap;
|
|
|
|
|
loaded: boolean;
|
|
|
|
|
saving: boolean;
|
|
|
|
|
hydrate: () => Promise<void>;
|
|
|
|
|
applySnapshot: (snapshot: PermissionPolicySnapshot) => void;
|
|
|
|
|
reset: () => void;
|
2026-03-20 01:01:03 +02:00
|
|
|
isSessionAutoAccepting: (sessionId: string) => boolean;
|
|
|
|
|
setSessionAutoAccept: (sessionId: string, enabled: boolean) => Promise<void>;
|
2025-12-07 19:32:53 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 15:03:16 +03:00
|
|
|
const readSnapshot = async (response: Response): Promise<PermissionPolicySnapshot> => {
|
|
|
|
|
if (!response.ok) throw new Error(`Permission auto-accept request failed (${response.status})`);
|
|
|
|
|
const payload = await response.json() as Partial<PermissionPolicySnapshot>;
|
|
|
|
|
if (!payload.sessions || typeof payload.sessions !== "object") {
|
|
|
|
|
throw new Error("Invalid permission auto-accept response");
|
2026-04-16 19:14:13 +03:00
|
|
|
}
|
2026-07-12 15:03:16 +03:00
|
|
|
const sessions: PermissionAutoAcceptMap = {};
|
|
|
|
|
for (const [sessionId, enabled] of Object.entries(payload.sessions)) {
|
|
|
|
|
if (sessionId && typeof enabled === "boolean") sessions[sessionId] = enabled;
|
2026-04-16 19:14:13 +03:00
|
|
|
}
|
2026-07-12 15:03:16 +03:00
|
|
|
return { sessions };
|
2026-04-16 19:14:13 +03:00
|
|
|
};
|
|
|
|
|
|
2026-07-12 15:03:16 +03:00
|
|
|
const requestSnapshot = async (path: string, init?: RequestInit) => readSnapshot(await runtimeFetch(path, init));
|
|
|
|
|
|
2026-07-13 23:18:12 +03:00
|
|
|
const isAutoAccepting = (
|
|
|
|
|
autoAccept: PermissionAutoAcceptMap,
|
|
|
|
|
sessionById: ReadonlyMap<string, Session>,
|
|
|
|
|
sessionId: string,
|
|
|
|
|
) => autoRespondsPermission({ autoAccept, sessions: [], sessionById, sessionID: sessionId });
|
2026-07-12 15:03:16 +03:00
|
|
|
|
|
|
|
|
export const usePermissionStore = create<PermissionStore>()(persist((set, get) => ({
|
|
|
|
|
autoAccept: {},
|
|
|
|
|
loaded: false,
|
|
|
|
|
saving: false,
|
|
|
|
|
|
|
|
|
|
hydrate: async () => {
|
|
|
|
|
let snapshot = await requestSnapshot("/api/permission-auto-accept");
|
|
|
|
|
const legacyEntries = Object.entries(get().autoAccept)
|
|
|
|
|
.filter(([sessionId, enabled]) => !sessionId.includes("/") && typeof enabled === "boolean");
|
|
|
|
|
if (Object.keys(snapshot.sessions).length === 0 && legacyEntries.length > 0) {
|
|
|
|
|
for (const [sessionId, enabled] of legacyEntries) {
|
|
|
|
|
if (!sessionId || typeof enabled !== "boolean") continue;
|
|
|
|
|
snapshot = await requestSnapshot(
|
|
|
|
|
`/api/permission-auto-accept/sessions/${encodeURIComponent(sessionId)}`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ enabled }),
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-04-16 19:14:13 +03:00
|
|
|
}
|
|
|
|
|
}
|
2026-07-12 15:03:16 +03:00
|
|
|
set({ autoAccept: snapshot.sessions, loaded: true });
|
|
|
|
|
},
|
2026-04-16 19:14:13 +03:00
|
|
|
|
2026-07-12 15:03:16 +03:00
|
|
|
reset: () => set({ autoAccept: {}, loaded: false, saving: false }),
|
2026-03-20 01:01:03 +02:00
|
|
|
|
2026-07-12 15:03:16 +03:00
|
|
|
applySnapshot: (snapshot) => {
|
|
|
|
|
const sessions: PermissionAutoAcceptMap = {};
|
|
|
|
|
for (const [sessionId, enabled] of Object.entries(snapshot.sessions ?? {})) {
|
|
|
|
|
if (sessionId && typeof enabled === "boolean") sessions[sessionId] = enabled;
|
2026-04-16 19:14:13 +03:00
|
|
|
}
|
2026-07-12 15:03:16 +03:00
|
|
|
set({ autoAccept: sessions, loaded: true });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
isSessionAutoAccepting: (sessionId) => {
|
|
|
|
|
if (!sessionId) return false;
|
2026-07-13 23:18:12 +03:00
|
|
|
const autoAccept = get().autoAccept;
|
|
|
|
|
if (Object.keys(autoAccept).length === 0) return false;
|
|
|
|
|
return isAutoAccepting(autoAccept, getAllSyncSessionMap(), sessionId);
|
2026-07-12 15:03:16 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setSessionAutoAccept: async (sessionId, enabled) => {
|
|
|
|
|
if (!sessionId) return;
|
|
|
|
|
if (isVSCodeRuntime()) {
|
|
|
|
|
const response = await runtimeFetch("/api/notifications/auto-accept", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ sessionId, enabled }),
|
|
|
|
|
});
|
|
|
|
|
if (!response.ok) throw new Error(`Permission auto-accept request failed (${response.status})`);
|
|
|
|
|
set((state) => ({ autoAccept: { ...state.autoAccept, [sessionId]: enabled }, loaded: true }));
|
|
|
|
|
return;
|
2026-04-16 19:14:13 +03:00
|
|
|
}
|
2026-07-12 15:03:16 +03:00
|
|
|
set({ saving: true });
|
2026-05-14 15:30:10 +03:00
|
|
|
try {
|
2026-07-12 15:03:16 +03:00
|
|
|
const directory = useSessionUIStore.getState().getDirectoryForSession(sessionId)
|
|
|
|
|
?? opencodeClient.getDirectory()
|
|
|
|
|
?? undefined;
|
|
|
|
|
const snapshot = await requestSnapshot(
|
|
|
|
|
`/api/permission-auto-accept/sessions/${encodeURIComponent(sessionId)}`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ enabled, directory }),
|
2026-03-20 01:01:03 +02:00
|
|
|
},
|
2026-07-12 15:03:16 +03:00
|
|
|
);
|
|
|
|
|
set({ autoAccept: snapshot.sessions, loaded: true });
|
|
|
|
|
} finally {
|
|
|
|
|
set({ saving: false });
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-03-31 18:47:00 +03:00
|
|
|
|
2026-07-12 15:03:16 +03:00
|
|
|
}), {
|
|
|
|
|
name: "permission-store",
|
|
|
|
|
storage: createDeferredSafeJSONStorage(),
|
|
|
|
|
partialize: (state) => ({ autoAccept: state.autoAccept }),
|
|
|
|
|
}));
|