49f87c4da3
Whole tree from parallel sessions, plus the version bump: - Clone: a queue with live progress (Add workspace and extra folders), clones run under nice/ionice so the machine stays responsive. - Settings → AI tools → Session defaults: a new session's default permission mode / bypass per account. - Chat lists the background subagents that have not reported back yet. - A file pasted or dropped into a claude pane goes in as an @ mention. - Changes panel: say why amend/undo is refused on a pushed commit. - openapi.yaml regenerated (Artifacts, Host, resumeById).
155 lines
5.2 KiB
TypeScript
155 lines
5.2 KiB
TypeScript
/**
|
|
* @file SessionDefaultsSettings — Settings → AI tools → Session defaults: the
|
|
* account's own starting choices for a new Claude Code session (permission
|
|
* mode, Bypass included) and a new Codex session (sandbox, approval, bypass),
|
|
* which the workspace home's prompt box starts from (`lib/sessionDefaults.ts`,
|
|
* synced per account). Turning bypass on as a default asks first; a pick in
|
|
* the prompt box still changes only that one session.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { ShieldAlert } from "lucide-react";
|
|
import type { CodexApproval, CodexSandbox } from "../../lib/api";
|
|
import { useAiTools } from "../../hooks/useAiTools";
|
|
import { confirmDialog } from "../../lib/confirm";
|
|
import {
|
|
CODEX_APPROVALS,
|
|
CODEX_SANDBOXES,
|
|
DEFAULT_MODES,
|
|
readCodexDefaults,
|
|
readDefaultMode,
|
|
writeCodexDefaults,
|
|
writeDefaultMode,
|
|
type DefaultMode,
|
|
} from "../../lib/sessionDefaults";
|
|
import { SettingsRow, SettingsSection, SettingsSegmented, SettingsSwitchRow } from "./SettingsRow";
|
|
|
|
const MODE_KEYS: Record<DefaultMode, string> = {
|
|
auto: "Auto",
|
|
acceptEdits: "AcceptEdits",
|
|
default: "Default",
|
|
plan: "Plan",
|
|
bypassPermissions: "Bypass",
|
|
};
|
|
const SANDBOX_KEYS: Record<CodexSandbox, string> = {
|
|
"read-only": "sandboxReadOnly",
|
|
"workspace-write": "sandboxWorkspaceWrite",
|
|
"danger-full-access": "sandboxFullAccess",
|
|
};
|
|
const APPROVAL_KEYS: Record<CodexApproval, string> = {
|
|
"on-request": "approvalOnRequest",
|
|
never: "approvalNever",
|
|
};
|
|
|
|
function Danger({ children }: { children: string }) {
|
|
return (
|
|
<p className="flex items-start gap-2 py-2 text-xs text-status-danger">
|
|
<ShieldAlert className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
|
{children}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
export function SessionDefaultsSettings() {
|
|
const { t } = useTranslation("settings");
|
|
const aiTools = useAiTools();
|
|
const shown = (id: string) => aiTools.tools.some((x) => x.id === id && x.enabled);
|
|
const [mode, setMode] = useState<DefaultMode>(() => readDefaultMode() as DefaultMode);
|
|
const [codex, setCodex] = useState(readCodexDefaults);
|
|
|
|
const askBypass = (tool: string) =>
|
|
confirmDialog({
|
|
title:
|
|
tool === "Codex"
|
|
? t("sessionDefaults.codexConfirmTitle")
|
|
: t("sessionDefaults.confirmTitle", { tool }),
|
|
message: t("sessionDefaults.confirmBody", { tool }),
|
|
confirmLabel: t("sessionDefaults.confirmLabel"),
|
|
});
|
|
|
|
const pickMode = async (m: DefaultMode) => {
|
|
if (m === "bypassPermissions" && !(await askBypass("Claude Code"))) return;
|
|
writeDefaultMode(m);
|
|
setMode(m);
|
|
};
|
|
const patchCodex = (patch: Partial<typeof codex>) => {
|
|
writeCodexDefaults(patch);
|
|
setCodex((c) => ({ ...c, ...patch }));
|
|
};
|
|
const pickCodexBypass = async (on: boolean) => {
|
|
if (on && !(await askBypass("Codex"))) return;
|
|
patchCodex({ bypass: on });
|
|
};
|
|
|
|
if (!shown("claude") && !shown("codex")) return null;
|
|
return (
|
|
<SettingsSection
|
|
id="session-defaults"
|
|
title={t("sessionDefaults.title")}
|
|
description={t("sessionDefaults.description")}
|
|
>
|
|
{shown("claude") && (
|
|
<>
|
|
<SettingsRow
|
|
label={t("sessionDefaults.claudeMode")}
|
|
description={t("sessionDefaults.claudeModeDesc")}
|
|
control={
|
|
<SettingsSegmented
|
|
ariaLabel={t("sessionDefaults.claudeMode")}
|
|
value={mode}
|
|
onChange={(m) => void pickMode(m)}
|
|
options={DEFAULT_MODES.map((m) => ({
|
|
value: m,
|
|
label: t(`lanes:home.permission${MODE_KEYS[m]}`),
|
|
}))}
|
|
/>
|
|
}
|
|
/>
|
|
{mode === "bypassPermissions" && <Danger>{t("run:hint.permissionWarning")}</Danger>}
|
|
</>
|
|
)}
|
|
{shown("codex") && (
|
|
<>
|
|
<SettingsRow
|
|
label={t("sessionDefaults.codexSandbox")}
|
|
control={
|
|
<SettingsSegmented
|
|
ariaLabel={t("sessionDefaults.codexSandbox")}
|
|
value={codex.sandbox}
|
|
onChange={(sandbox) => patchCodex({ sandbox })}
|
|
options={CODEX_SANDBOXES.map((v) => ({
|
|
value: v,
|
|
label: t(`run:fields.${SANDBOX_KEYS[v]}`),
|
|
}))}
|
|
/>
|
|
}
|
|
/>
|
|
<SettingsRow
|
|
label={t("sessionDefaults.codexApproval")}
|
|
control={
|
|
<SettingsSegmented
|
|
ariaLabel={t("sessionDefaults.codexApproval")}
|
|
value={codex.approval}
|
|
onChange={(approval) => patchCodex({ approval })}
|
|
options={CODEX_APPROVALS.map((v) => ({
|
|
value: v,
|
|
label: t(`run:fields.${APPROVAL_KEYS[v]}`),
|
|
}))}
|
|
/>
|
|
}
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={t("sessionDefaults.codexBypass")}
|
|
description={t("sessionDefaults.codexBypassDesc")}
|
|
checked={codex.bypass}
|
|
onChange={(on) => void pickCodexBypass(on)}
|
|
/>
|
|
{codex.bypass && <Danger>{t("run:hint.bypassWarning", { tool: "Codex" })}</Danger>}
|
|
</>
|
|
)}
|
|
</SettingsSection>
|
|
);
|
|
}
|