Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
cycleModeOption,
getCurrentModeFromConfigOptions,
} from "@features/sessions/stores/sessionStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { TaskInputEditor } from "@features/task-detail/components/TaskInputEditor";
import { WorkspaceModeSelect } from "@features/task-detail/components/WorkspaceModeSelect";
import { usePreviewConfig } from "@features/task-detail/hooks/usePreviewConfig";
Expand Down Expand Up @@ -59,7 +58,6 @@ interface TutorialStepProps {
}

export function TutorialStep({ onComplete, onBack }: TutorialStepProps) {
const { allowBypassPermissions } = useSettingsStore();
const completeOnboarding = useOnboardingStore(
(state) => state.completeOnboarding,
);
Expand Down Expand Up @@ -204,11 +202,11 @@ export function TutorialStep({ onComplete, onBack }: TutorialStepProps) {

// Shift+tab mode cycling (only active during explain-mode step)
const handleCycleMode = useCallback(() => {
const nextValue = cycleModeOption(modeOption, allowBypassPermissions);
const nextValue = cycleModeOption(modeOption);
if (nextValue && modeOption) {
setConfigOption(modeOption.id, nextValue);
}
}, [modeOption, allowBypassPermissions, setConfigOption]);
}, [modeOption, setConfigOption]);

useHotkeys(
"shift+tab",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
usePendingPermissionsForTask,
} from "@features/sessions/stores/sessionStore";
import type { Plan } from "@features/sessions/types";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping";
import { Pause, Spinner, Warning } from "@phosphor-icons/react";
import { Box, Button, ContextMenu, Flex, Text } from "@radix-ui/themes";
Expand Down Expand Up @@ -92,33 +91,18 @@ export function SessionView({
const { setShowRawLogs } = useSessionViewActions();
const pendingPermissions = usePendingPermissionsForTask(taskId);
const modeOption = useModeConfigOptionForTask(taskId);
const { allowBypassPermissions } = useSettingsStore();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removing use effects, that's what it's all about

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

always has been

image

const currentModeId = modeOption?.currentValue;

useEffect(() => {
if (allowBypassPermissions) return;
const isBypass =
currentModeId === "bypassPermissions" || currentModeId === "full-access";
if (isBypass && taskId) {
getSessionService().setSessionConfigOptionByCategory(
taskId,
"mode",
"default",
);
}
}, [allowBypassPermissions, currentModeId, taskId]);

const handleModeChange = useCallback(() => {
if (!taskId) return;
const nextMode = cycleModeOption(modeOption, allowBypassPermissions);
const nextMode = cycleModeOption(modeOption);
if (nextMode) {
getSessionService().setSessionConfigOptionByCategory(
taskId,
"mode",
nextMode,
);
}
}, [taskId, allowBypassPermissions, modeOption]);
}, [taskId, modeOption]);

const sessionId = taskId ?? "default";
const setContext = useDraftStore((s) => s.actions.setContext);
Expand Down Expand Up @@ -147,7 +131,7 @@ export function SessionView({
(e) => {
e.preventDefault();
if (!taskId) return;
const nextMode = cycleModeOption(modeOption, allowBypassPermissions);
const nextMode = cycleModeOption(modeOption);
if (nextMode) {
getSessionService().setSessionConfigOptionByCategory(
taskId,
Expand All @@ -161,14 +145,7 @@ export function SessionView({
enableOnContentEditable: true,
enabled: isRunning && !!modeOption && isActiveSession,
},
[
taskId,
currentModeId,
isRunning,
modeOption,
allowBypassPermissions,
isActiveSession,
],
[taskId, isRunning, modeOption, isActiveSession],
);

const latestPlan = useMemo((): Plan | null => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
import { describe, expect, it } from "vitest";
import { cycleModeOption } from "./sessionStore";

function createModeOption(
currentValue: string,
values: string[],
): SessionConfigOption {
return {
id: "mode",
name: "Approval Preset",
type: "select",
category: "mode",
currentValue,
options: values.map((value) => ({
value,
name: value,
})),
} as SessionConfigOption;
}

describe("cycleModeOption", () => {
it("cycles through auto-accept permissions for claude", () => {
const option = createModeOption("plan", [
"default",
"acceptEdits",
"plan",
"bypassPermissions",
]);

expect(cycleModeOption(option)).toBe("bypassPermissions");
});

it("cycles through full access for codex", () => {
const option = createModeOption("auto", [
"read-only",
"auto",
"full-access",
]);

expect(cycleModeOption(option)).toBe("full-access");
});
});
18 changes: 5 additions & 13 deletions apps/code/src/renderer/features/sessions/stores/sessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,27 +146,19 @@ export function getConfigOptionByCategory(
*/
export function cycleModeOption(
modeOption: SessionConfigOption | undefined,
allowBypassPermissions: boolean,
): string | undefined {
if (!modeOption || modeOption.type !== "select") return undefined;

const allOptions = flattenSelectOptions(modeOption.options);
const filteredOptions = allowBypassPermissions
? allOptions
: allOptions.filter(
(opt) =>
opt.value !== "bypassPermissions" && opt.value !== "full-access",
);
if (allOptions.length === 0) return undefined;

if (filteredOptions.length === 0) return allOptions[0]?.value;

const currentIndex = filteredOptions.findIndex(
const currentIndex = allOptions.findIndex(
(opt) => opt.value === modeOption.currentValue,
);
if (currentIndex === -1) return filteredOptions[0]?.value;
if (currentIndex === -1) return allOptions[0]?.value;

const nextIndex = (currentIndex + 1) % filteredOptions.length;
return filteredOptions[nextIndex]?.value;
const nextIndex = (currentIndex + 1) % allOptions.length;
return allOptions[nextIndex]?.value;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@ export function ClaudeCodeSettings() {
(checked: boolean) => {
if (checked) {
setShowBypassWarning(true);
} else {
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
setting_name: "allow_bypass_permissions",
new_value: false,
old_value: true,
});
setAllowBypassPermissions(false);
return;
}

track(ANALYTICS_EVENTS.SETTING_CHANGED, {
setting_name: "allow_bypass_permissions",
new_value: false,
old_value: true,
});
setAllowBypassPermissions(false);
},
[setAllowBypassPermissions],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe("feature settingsStore cloud selections", () => {
removeItem.mockResolvedValue(undefined);

useSettingsStore.setState({
allowBypassPermissions: false,
lastUsedCloudRepository: null,
});
});
Expand Down Expand Up @@ -65,4 +66,19 @@ describe("feature settingsStore cloud selections", () => {
"posthog/posthog",
);
});

it("rehydrates the unsafe mode toggle", async () => {
getItem.mockResolvedValue(
JSON.stringify({
state: {
allowBypassPermissions: true,
},
version: 0,
}),
);

await useSettingsStore.persist.rehydrate();

expect(useSettingsStore.getState().allowBypassPermissions).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export function TaskInput({
setLastUsedAdapter,
lastUsedCloudRepository,
setLastUsedCloudRepository,
allowBypassPermissions,
setLastUsedEnvironment,
getLastUsedEnvironment,
defaultInitialTaskMode,
Expand Down Expand Up @@ -285,11 +284,11 @@ export function TaskInput({
});

const handleCycleMode = useCallback(() => {
const nextValue = cycleModeOption(modeOption, allowBypassPermissions);
const nextValue = cycleModeOption(modeOption);
if (nextValue && modeOption) {
setConfigOption(modeOption.id, nextValue);
}
}, [modeOption, allowBypassPermissions, setConfigOption]);
}, [modeOption, setConfigOption]);

// Global shift+tab to cycle mode regardless of focus
useHotkeys(
Expand Down
Loading