Skip to content
Closed
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
14 changes: 12 additions & 2 deletions apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,15 @@ export function NewTaskDraftScreen(props: {
? "Approve actions"
: flow.runtimeMode === "auto-accept-edits"
? "Auto-accept edits"
: "Full access",
: flow.runtimeMode === "auto"
? "Auto"
: "Full access",
subactions: [
{ id: "options:runtime:approval-required", title: "Approve actions" },
{ id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" },
...(flow.selectedModelOption?.supportsAutoRuntimeMode
? [{ id: "options:runtime:auto", title: "Auto" }]
: []),
{ id: "options:runtime:full-access", title: "Full access" },
].map((option) => {
const value = option.id.replace("options:runtime:", "");
Expand Down Expand Up @@ -229,7 +234,12 @@ export function NewTaskDraftScreen(props: {
}),
},
],
[flow.interactionMode, flow.runtimeMode, providerOptionDescriptors],
[
flow.interactionMode,
flow.runtimeMode,
flow.selectedModelOption?.supportsAutoRuntimeMode,
providerOptionDescriptors,

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.

🟡 Medium threads/NewTaskDraftScreen.tsx:241

When the selected model does not support the "auto" runtime mode (flow.selectedModelOption?.supportsAutoRuntimeMode is false), the menu hides the "auto" option but flow.runtimeMode is never normalized back to a supported value. If the user previously selected "auto" with a supporting model and then switches to an unsupported model, the options menu subtitle still labels the runtime as "Auto", and handleStart() submits runtimeMode: "auto" to createProjectThread for a provider that does not expose that mode. Consider resetting flow.runtimeMode whenever supportsAutoRuntimeMode transitions to false so the submitted runtime mode stays within the selected provider's supported set.

Also found in 1 other location(s)

apps/mobile/src/features/threads/ThreadComposer.tsx:555

The runtime menu can still present the current mode as "Auto" after the user switches away from a provider that supports auto mode. handleModelMenuAction only changes modelSelection, so currentRuntimeMode stays "auto"; then the subtitle logic at lines 550-557 still renders "Auto" even when currentModelOption?.supportsAutoRuntimeMode is false and the Auto menu item has been removed. Trigger: pick Claude Auto, switch to another provider, and reopen the composer. The UI now claims the active provider is in Auto mode even though that option is supposed to be hidden for non-Claude providers.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/NewTaskDraftScreen.tsx around line 241:

When the selected model does not support the `"auto"` runtime mode (`flow.selectedModelOption?.supportsAutoRuntimeMode` is false), the menu hides the `"auto"` option but `flow.runtimeMode` is never normalized back to a supported value. If the user previously selected `"auto"` with a supporting model and then switches to an unsupported model, the options menu subtitle still labels the runtime as `"Auto"`, and `handleStart()` submits `runtimeMode: "auto"` to `createProjectThread` for a provider that does not expose that mode. Consider resetting `flow.runtimeMode` whenever `supportsAutoRuntimeMode` transitions to false so the submitted runtime mode stays within the selected provider's supported set.

Also found in 1 other location(s):
- apps/mobile/src/features/threads/ThreadComposer.tsx:555 -- The runtime menu can still present the current mode as `"Auto"` after the user switches away from a provider that supports auto mode. `handleModelMenuAction` only changes `modelSelection`, so `currentRuntimeMode` stays `"auto"`; then the subtitle logic at lines 550-557 still renders `"Auto"` even when `currentModelOption?.supportsAutoRuntimeMode` is false and the `Auto` menu item has been removed. Trigger: pick Claude `Auto`, switch to another provider, and reopen the composer. The UI now claims the active provider is in `Auto` mode even though that option is supposed to be hidden for non-Claude providers.

],
);

const workspaceMenuActions = useMemo(() => {
Expand Down
14 changes: 12 additions & 2 deletions apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,15 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
? "Approve actions"
: currentRuntimeMode === "auto-accept-edits"
? "Auto-accept edits"
: "Full access",
: currentRuntimeMode === "auto"
? "Auto"
: "Full access",
subactions: [
{ id: "options:runtime:approval-required", title: "Approve actions" },
{ id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" },
...(currentModelOption?.supportsAutoRuntimeMode
? [{ id: "options:runtime:auto", title: "Auto" }]
: []),
{ id: "options:runtime:full-access", title: "Full access" },
].map((option) => {
const value = option.id.replace("options:runtime:", "");
Expand Down Expand Up @@ -583,7 +588,12 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
}),
},
],
[currentInteractionMode, currentRuntimeMode, providerOptionDescriptors],
[
currentInteractionMode,
currentModelOption?.supportsAutoRuntimeMode,
currentRuntimeMode,
providerOptionDescriptors,
],
);

// ── Menu handlers ────────────────────────────────────────
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/lib/modelOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ModelOption = {
readonly providerKey: string;
readonly providerLabel: string;
readonly providerDriver: string;
readonly supportsAutoRuntimeMode: boolean;
readonly capabilities: ModelCapabilities | null;
readonly selection: ModelSelection;
};
Expand Down Expand Up @@ -78,6 +79,7 @@ export function buildModelOptions(
providerKey: provider.instanceId,
providerLabel,
providerDriver: provider.driver,
supportsAutoRuntimeMode: provider.supportsAutoRuntimeMode ?? false,
capabilities: model.capabilities,
selection: normalizeSelectionOptions(
{
Expand Down Expand Up @@ -107,6 +109,7 @@ export function buildModelOptions(
providerKey: fallbackModelSelection.instanceId,
providerLabel,
providerDriver: fallbackModelSelection.instanceId,
supportsAutoRuntimeMode: false,

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.

Fallback options hide Auto mode

Medium Severity

Synthetic ModelOption rows from buildModelOptions always set supportsAutoRuntimeMode to false, even when the matching provider is still present in config.providers (disabled, unauthenticated, or missing from the model list). The runtime menus now gate the Auto entry on that flag instead of providerDriver, so Claude selections in those states lose Auto while drafts can still use runtimeMode "auto".

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 45eddfd. Configure here.

capabilities: null,
selection: fallbackModelSelection,
});
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3431,6 +3431,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
const effectiveEffort = getEffectiveClaudeAgentEffort(effort, modelSelection?.model);
const runtimeModeToPermission: Record<string, PermissionMode> = {
"auto-accept-edits": "acceptEdits",
auto: "auto",
"full-access": "bypassPermissions",
};
const permissionMode = runtimeModeToPermission[input.runtimeMode];
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/Layers/ClaudeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const PROVIDER = ProviderDriverKind.make("claudeAgent");
const CLAUDE_PRESENTATION = {
displayName: "Claude",
showInteractionModeToggle: true,
supportsAutoRuntimeMode: true,
} as const;
const MINIMUM_CLAUDE_FABLE_5_VERSION = "2.1.169";
const MINIMUM_CLAUDE_OPUS_4_8_VERSION = "2.1.154";
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/provider/Layers/CodexSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ function runtimeModeToThreadConfig(input: RuntimeMode): {
sandbox: "read-only",
};
case "auto-accept-edits":
case "auto":
return {
approvalPolicy: "on-request",
sandbox: "workspace-write",
Expand Down Expand Up @@ -311,6 +312,7 @@ function runtimeModeToTurnSandboxPolicy(
type: "readOnly",
};
case "auto-accept-edits":
case "auto":
return {
type: "workspaceWrite",
};
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/provider/providerSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface ServerProviderPresentation {
readonly badgeLabel?: string;
readonly showInteractionModeToggle?: boolean;
readonly requiresNewThreadForModelChange?: boolean;
readonly supportsAutoRuntimeMode?: boolean;
}

export type ServerProviderDraft = Omit<ServerProvider, "instanceId" | "driver">;
Expand Down Expand Up @@ -234,6 +235,9 @@ export function buildServerProvider(input: {
...(typeof input.presentation.requiresNewThreadForModelChange === "boolean"
? { requiresNewThreadForModelChange: input.presentation.requiresNewThreadForModelChange }
: {}),
...(typeof input.presentation.supportsAutoRuntimeMode === "boolean"
? { supportsAutoRuntimeMode: input.presentation.supportsAutoRuntimeMode }
: {}),
enabled: input.enabled,
installed: input.probe.installed,
version: input.probe.version,
Expand Down
25 changes: 23 additions & 2 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,15 @@ import {
LockIcon,
LockOpenIcon,
PenLineIcon,
SparklesIcon,
XIcon,
} from "lucide-react";
import { proposedPlanTitle } from "../../proposedPlan";
import { getProviderDisplayName, getProviderInteractionModeToggle } from "../../providerModels";
import {
getProviderAutoRuntimeModeSupport,
getProviderDisplayName,
getProviderInteractionModeToggle,
} from "../../providerModels";
import {
applyProviderInstanceSettings,
deriveProviderInstanceEntries,
Expand Down Expand Up @@ -142,6 +147,11 @@ const runtimeModeConfig: Record<
description: "Auto-approve edits, ask before other actions.",
icon: PenLineIcon,
},
auto: {
label: "Auto",
description: "Run without prompts; a safety classifier reviews actions in the background.",
icon: SparklesIcon,
},
"full-access": {
label: "Full access",
description: "Allow commands and edits without prompts.",
Expand Down Expand Up @@ -194,6 +204,7 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop
showInteractionModeToggle: boolean;
interactionMode: ProviderInteractionMode;
runtimeMode: RuntimeMode;
runtimeModeOptions: ReadonlyArray<RuntimeMode>;
showPlanToggle: boolean;
planSidebarLabel: string;
planSidebarOpen: boolean;
Expand Down Expand Up @@ -269,7 +280,7 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop
<SelectValue>{runtimeModeOption.label}</SelectValue>
</TooltipTrigger>
<SelectPopup alignItemWithTrigger={false}>
{runtimeModeOptions.map((mode) => {
{props.runtimeModeOptions.map((mode) => {
const option = runtimeModeConfig[mode];
const OptionIcon = option.icon;
return (
Expand Down Expand Up @@ -817,9 +828,17 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
providerStatuses,
selectedProvider,
),
supportsAutoRuntimeMode: getProviderAutoRuntimeModeSupport(providerStatuses, selectedProvider),
}),
[providerStatuses, selectedProvider],
);
const runtimeModeOptionsForProvider = useMemo(
() =>
composerProviderControls.supportsAutoRuntimeMode
? runtimeModeOptions
: runtimeModeOptions.filter((mode) => mode !== "auto"),
[composerProviderControls.supportsAutoRuntimeMode],
);
Comment thread
cursor[bot] marked this conversation as resolved.
const selectedModelSelection = useMemo<ModelSelection>(
() => createModelSelection(selectedInstanceId, selectedModel, selectedModelOptionsForDispatch),
[selectedInstanceId, selectedModel, selectedModelOptionsForDispatch],
Expand Down Expand Up @@ -2500,6 +2519,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
planSidebarLabel={planSidebarLabel}
planSidebarOpen={planSidebarOpen}
runtimeMode={runtimeMode}
runtimeModeOptions={runtimeModeOptionsForProvider}
showInteractionModeToggle={composerProviderControls.showInteractionModeToggle}
traitsMenuContent={providerTraitsMenuContent}
onToggleInteractionMode={toggleInteractionMode}
Expand All @@ -2518,6 +2538,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
showInteractionModeToggle={composerProviderControls.showInteractionModeToggle}
interactionMode={interactionMode}
runtimeMode={runtimeMode}
runtimeModeOptions={runtimeModeOptionsForProvider}
showPlanToggle={showPlanSidebarToggle}
planSidebarLabel={planSidebarLabel}
planSidebarOpen={planSidebarOpen}
Expand Down
16 changes: 13 additions & 3 deletions apps/web/src/components/chat/CompactComposerControlsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ import {
MenuTrigger,
} from "../ui/menu";

const RUNTIME_MODE_LABELS: Record<RuntimeMode, string> = {
"approval-required": "Supervised",
"auto-accept-edits": "Auto-accept edits",
auto: "Auto",
"full-access": "Full access",
};

export const CompactComposerControlsMenu = memo(function CompactComposerControlsMenu(props: {
activePlan: boolean;
interactionMode: ProviderInteractionMode;
planSidebarLabel: string;
planSidebarOpen: boolean;
runtimeMode: RuntimeMode;
runtimeModeOptions: ReadonlyArray<RuntimeMode>;
showInteractionModeToggle: boolean;
traitsMenuContent?: ReactNode;
onToggleInteractionMode: () => void;
Expand Down Expand Up @@ -69,9 +77,11 @@ export const CompactComposerControlsMenu = memo(function CompactComposerControls
props.onRuntimeModeChange(value as RuntimeMode);
}}
>
<MenuRadioItem value="approval-required">Supervised</MenuRadioItem>
<MenuRadioItem value="auto-accept-edits">Auto-accept edits</MenuRadioItem>
<MenuRadioItem value="full-access">Full access</MenuRadioItem>
{props.runtimeModeOptions.map((mode) => (
<MenuRadioItem key={mode} value={mode}>
{RUNTIME_MODE_LABELS[mode]}
</MenuRadioItem>
))}
</MenuRadioGroup>
{props.activePlan ? (
<>
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/providerModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export function getProviderInteractionModeToggle(
return getProviderSnapshot(providers, provider)?.showInteractionModeToggle ?? true;
}

export function getProviderAutoRuntimeModeSupport(
providers: ReadonlyArray<ServerProvider>,
provider: ProviderDriverKind,
): boolean {
return getProviderSnapshot(providers, provider)?.supportsAutoRuntimeMode ?? false;
}

export function isProviderEnabled(
providers: ReadonlyArray<ServerProvider>,
provider: ProviderDriverKind,
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export type ModelSelection = typeof ModelSelection.Type;
export const RuntimeMode = Schema.Literals([

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.

🟡 Medium src/orchestration.ts:117

Adding "auto" to the global RuntimeMode schema makes that value valid in every persisted thread and command, not just Claude. A thread created with runtimeMode: "auto" can later have its modelSelection changed via ThreadMetaUpdateCommand to a non-Claude provider while keeping runtimeMode: "auto". Adapters that don't recognize "auto" silently misresolve it — for example CursorAdapter.resolveRequestedModeId maps any non-"approval-required" value to its implement mode, and buildOpenCodePermissionRules falls back to generic non-full-access rules. The thread then runs with wrong permission behavior instead of a normalization or validation failure. If "auto" is Claude-specific, consider narrowing it to a Claude-only field or adding adapter-level validation that rejects unrecognized modes.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/contracts/src/orchestration.ts around line 117:

Adding `"auto"` to the global `RuntimeMode` schema makes that value valid in every persisted thread and command, not just Claude. A thread created with `runtimeMode: "auto"` can later have its `modelSelection` changed via `ThreadMetaUpdateCommand` to a non-Claude provider while keeping `runtimeMode: "auto"`. Adapters that don't recognize `"auto"` silently misresolve it — for example `CursorAdapter.resolveRequestedModeId` maps any non-`"approval-required"` value to its implement mode, and `buildOpenCodePermissionRules` falls back to generic non-full-access rules. The thread then runs with wrong permission behavior instead of a normalization or validation failure. If `"auto"` is Claude-specific, consider narrowing it to a Claude-only field or adding adapter-level validation that rejects unrecognized modes.

"approval-required",
"auto-accept-edits",
"auto",
"full-access",
]);
export type RuntimeMode = typeof RuntimeMode.Type;
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export const ServerProvider = Schema.Struct({
continuation: Schema.optional(ServerProviderContinuation),
showInteractionModeToggle: Schema.optional(Schema.Boolean),
requiresNewThreadForModelChange: Schema.optional(Schema.Boolean),
supportsAutoRuntimeMode: Schema.optional(Schema.Boolean),
enabled: Schema.Boolean,
installed: Schema.Boolean,
version: Schema.NullOr(TrimmedNonEmptyString),
Expand Down
Loading