/** * @file sessionDefaults.ts * @description The account's own starting choices for new work, picked in the * first-run setup (docs/plans/onboarding) or Settings → AI tools → Session * defaults, and synced per account like every other preference * (`lib/userPrefs.ts`): the AI a new workspace starts on, the permission mode * a new Claude session starts in, and a new Codex session's sandbox, approval * and bypass. Bypass (Claude's `bypassPermissions`, Codex's bypass switch) may * be a default only because the user turned it on in Settings behind a * confirm — the first-run setup never offers it, and the pet chat never * starts in it (`readDefaultMode({ safe: true })`). * @author Nguyễn Ngọc Trí Vĩ */ import type { CodexApproval, CodexSandbox, PermissionMode } from "./api"; const TOOL_KEY = "agenthub.defaultTool"; const MODE_KEY = "agenthub.permissionMode"; const CODEX_SANDBOX_KEY = "agenthub.codex.sandbox"; const CODEX_APPROVAL_KEY = "agenthub.codex.approval"; const CODEX_BYPASS_KEY = "agenthub.codex.bypass"; /** The permission modes a default may be. */ export const DEFAULT_MODES = ["auto", "acceptEdits", "default", "plan", "bypassPermissions"] as const; export type DefaultMode = (typeof DEFAULT_MODES)[number]; /** The ones the first-run setup offers: never bypass. */ export const SAFE_MODES = DEFAULT_MODES.filter((m) => m !== "bypassPermissions"); export const CODEX_SANDBOXES: readonly CodexSandbox[] = [ "read-only", "workspace-write", "danger-full-access", ]; export const CODEX_APPROVALS: readonly CodexApproval[] = ["on-request", "never"]; function read(key: string): string | null { try { return localStorage.getItem(key); } catch { return null; } } function write(key: string, value: string): void { try { localStorage.setItem(key, value); } catch { /* storage blocked */ } } /** The account's own default AI tool id, or null (the install's default applies). */ export const readDefaultTool = (): string | null => read(TOOL_KEY); export const writeDefaultTool = (id: string): void => write(TOOL_KEY, id); /** The permission mode a new Claude session starts in ("auto" when none was * picked); `safe` turns a bypass default into "auto". */ export function readDefaultMode({ safe = false }: { safe?: boolean } = {}): PermissionMode { const v = read(MODE_KEY); if (safe && v === "bypassPermissions") return "auto"; return (DEFAULT_MODES as readonly string[]).includes(v ?? "") ? (v as DefaultMode) : "auto"; } export function writeDefaultMode(mode: DefaultMode): void { if ((DEFAULT_MODES as readonly string[]).includes(mode)) write(MODE_KEY, mode); } export interface CodexDefaults { sandbox: CodexSandbox; approval: CodexApproval; bypass: boolean; } /** A new Codex session's sandbox, approval and bypass (Codex's own defaults when none was picked). */ export function readCodexDefaults(): CodexDefaults { const sandbox = read(CODEX_SANDBOX_KEY) as CodexSandbox | null; const approval = read(CODEX_APPROVAL_KEY) as CodexApproval | null; return { sandbox: sandbox && CODEX_SANDBOXES.includes(sandbox) ? sandbox : "workspace-write", approval: approval && CODEX_APPROVALS.includes(approval) ? approval : "on-request", bypass: read(CODEX_BYPASS_KEY) === "true", }; } export function writeCodexDefaults(patch: Partial): void { if (patch.sandbox && CODEX_SANDBOXES.includes(patch.sandbox)) write(CODEX_SANDBOX_KEY, patch.sandbox); if (patch.approval && CODEX_APPROVALS.includes(patch.approval)) write(CODEX_APPROVAL_KEY, patch.approval); if (patch.bypass !== undefined) write(CODEX_BYPASS_KEY, String(patch.bypass === true)); }