fix(settings): highlight changes that apply next turn (#2201)
Permission and thinking changes made during an active turn do not affect that turn. Use setting-specific warnings so the timing is harder to miss.
This commit is contained in:
@@ -6,7 +6,7 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||||
import { isWeb } from "@/constants/platform";
|
import { isWeb } from "@/constants/platform";
|
||||||
import { AlertTriangle, CheckCircle2 } from "lucide-react-native";
|
import { AlertTriangle, CheckCircle2, Info } from "lucide-react-native";
|
||||||
import { getOverlayRoot, OVERLAY_Z } from "@/lib/overlay-root";
|
import { getOverlayRoot, OVERLAY_Z } from "@/lib/overlay-root";
|
||||||
import {
|
import {
|
||||||
HEADER_INNER_HEIGHT,
|
HEADER_INNER_HEIGHT,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
HEADER_TOP_PADDING_MOBILE,
|
HEADER_TOP_PADDING_MOBILE,
|
||||||
} from "@/constants/layout";
|
} from "@/constants/layout";
|
||||||
|
|
||||||
export type ToastVariant = "default" | "success" | "error";
|
export type ToastVariant = "default" | "info" | "success" | "warning" | "error";
|
||||||
|
|
||||||
export interface ToastShowOptions {
|
export interface ToastShowOptions {
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
@@ -230,7 +230,9 @@ export function ToastViewport({
|
|||||||
const toastAnimatedStyle = useMemo(
|
const toastAnimatedStyle = useMemo(
|
||||||
() => [
|
() => [
|
||||||
styles.toast,
|
styles.toast,
|
||||||
|
toastVariant === "info" ? styles.toastInfo : null,
|
||||||
toastVariant === "success" ? styles.toastSuccess : null,
|
toastVariant === "success" ? styles.toastSuccess : null,
|
||||||
|
toastVariant === "warning" ? styles.toastWarning : null,
|
||||||
toastVariant === "error" ? styles.toastError : null,
|
toastVariant === "error" ? styles.toastError : null,
|
||||||
{
|
{
|
||||||
marginTop: topOffset,
|
marginTop: topOffset,
|
||||||
@@ -250,8 +252,12 @@ export function ToastViewport({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let defaultIcon: ReactNode = null;
|
let defaultIcon: ReactNode = null;
|
||||||
if (toast.variant === "success") {
|
if (toast.variant === "info") {
|
||||||
|
defaultIcon = <Info size={18} color={theme.colors.palette.blue[300]} />;
|
||||||
|
} else if (toast.variant === "success") {
|
||||||
defaultIcon = <CheckCircle2 size={18} color={theme.colors.primary} />;
|
defaultIcon = <CheckCircle2 size={18} color={theme.colors.primary} />;
|
||||||
|
} else if (toast.variant === "warning") {
|
||||||
|
defaultIcon = <AlertTriangle size={18} color={theme.colors.palette.amber[500]} />;
|
||||||
} else if (toast.variant === "error") {
|
} else if (toast.variant === "error") {
|
||||||
defaultIcon = <AlertTriangle size={18} color={theme.colors.destructive} />;
|
defaultIcon = <AlertTriangle size={18} color={theme.colors.destructive} />;
|
||||||
}
|
}
|
||||||
@@ -313,6 +319,12 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
toastSuccess: {
|
toastSuccess: {
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
},
|
},
|
||||||
|
toastInfo: {
|
||||||
|
borderColor: theme.colors.palette.blue[300],
|
||||||
|
},
|
||||||
|
toastWarning: {
|
||||||
|
borderColor: theme.colors.palette.amber[500],
|
||||||
|
},
|
||||||
toastError: {
|
toastError: {
|
||||||
borderColor: theme.colors.destructive,
|
borderColor: theme.colors.destructive,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function showProviderNoticeToast(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.show(notice.message, {
|
toast.show(notice.message, {
|
||||||
variant: "default",
|
variant: notice.type,
|
||||||
durationMs: notice.type === "warning" ? 3200 : undefined,
|
durationMs: notice.type === "warning" ? 5000 : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import type { AgentProviderNotice } from "./agent-sdk-types.js";
|
import type { AgentProviderNotice } from "./agent-sdk-types.js";
|
||||||
|
|
||||||
export const SETTING_APPLIES_NEXT_TURN_NOTICE: AgentProviderNotice = {
|
export const MODE_APPLIES_NEXT_TURN_NOTICE: AgentProviderNotice = {
|
||||||
type: "info",
|
type: "warning",
|
||||||
message: "This change applies next turn.",
|
message: "Permission mode applies next turn",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const THINKING_APPLIES_NEXT_TURN_NOTICE: AgentProviderNotice = {
|
||||||
|
type: "warning",
|
||||||
|
message: "Thinking level applies next turn",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -704,8 +704,8 @@ describe("ClaudeAgentSession features", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await expect(session.setThinkingOption?.("ultracode")).resolves.toEqual({
|
await expect(session.setThinkingOption?.("ultracode")).resolves.toEqual({
|
||||||
type: "info",
|
type: "warning",
|
||||||
message: "This change applies next turn.",
|
message: "Thinking level applies next turn",
|
||||||
});
|
});
|
||||||
|
|
||||||
await session.close();
|
await session.close();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import { claudeQuery, type ClaudeOptions, type ClaudeQueryFactory } from "./quer
|
|||||||
import { realClaudeRewindSdk, revertClaudeConversation, revertClaudeFiles } from "./rewind.js";
|
import { realClaudeRewindSdk, revertClaudeConversation, revertClaudeFiles } from "./rewind.js";
|
||||||
import { normalizeProviderReplayTimestamp } from "../../provider-history-timestamps.js";
|
import { normalizeProviderReplayTimestamp } from "../../provider-history-timestamps.js";
|
||||||
import { claudeProjectDirSync } from "./project-dir.js";
|
import { claudeProjectDirSync } from "./project-dir.js";
|
||||||
import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
|
import { THINKING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
|
||||||
import {
|
import {
|
||||||
isProviderImageMarkdown,
|
isProviderImageMarkdown,
|
||||||
materializeProviderImage,
|
materializeProviderImage,
|
||||||
@@ -2209,7 +2209,7 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
}
|
}
|
||||||
this.queryRestartNeeded = true;
|
this.queryRestartNeeded = true;
|
||||||
if (this.activeForegroundTurnId || this.autonomousTurn) {
|
if (this.activeForegroundTurnId || this.autonomousTurn) {
|
||||||
return SETTING_APPLIES_NEXT_TURN_NOTICE;
|
return THINKING_APPLIES_NEXT_TURN_NOTICE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -347,12 +347,12 @@ describe("Codex app-server provider", () => {
|
|||||||
const session = createSession({ modeId: "auto", thinkingOptionId: "medium" });
|
const session = createSession({ modeId: "auto", thinkingOptionId: "medium" });
|
||||||
|
|
||||||
await expect(session.setMode("full-access")).resolves.toEqual({
|
await expect(session.setMode("full-access")).resolves.toEqual({
|
||||||
type: "info",
|
type: "warning",
|
||||||
message: "This change applies next turn.",
|
message: "Permission mode applies next turn",
|
||||||
});
|
});
|
||||||
await expect(session.setThinkingOption?.("high")).resolves.toEqual({
|
await expect(session.setThinkingOption?.("high")).resolves.toEqual({
|
||||||
type: "info",
|
type: "warning",
|
||||||
message: "This change applies next turn.",
|
message: "Thinking level applies next turn",
|
||||||
});
|
});
|
||||||
|
|
||||||
session.activeForegroundTurnId = null;
|
session.activeForegroundTurnId = null;
|
||||||
|
|||||||
@@ -92,7 +92,10 @@ import {
|
|||||||
resolveBinaryVersion,
|
resolveBinaryVersion,
|
||||||
} from "./diagnostic-utils.js";
|
} from "./diagnostic-utils.js";
|
||||||
import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "./provider-runner.js";
|
import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "./provider-runner.js";
|
||||||
import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../provider-notices.js";
|
import {
|
||||||
|
MODE_APPLIES_NEXT_TURN_NOTICE,
|
||||||
|
THINKING_APPLIES_NEXT_TURN_NOTICE,
|
||||||
|
} from "../provider-notices.js";
|
||||||
import type { WorkspaceGitService } from "../../workspace-git-service.js";
|
import type { WorkspaceGitService } from "../../workspace-git-service.js";
|
||||||
|
|
||||||
function assertChildWithPipes(
|
function assertChildWithPipes(
|
||||||
@@ -3943,7 +3946,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
|||||||
this.currentMode = modeId;
|
this.currentMode = modeId;
|
||||||
this.cachedRuntimeInfo = null;
|
this.cachedRuntimeInfo = null;
|
||||||
if (this.activeForegroundTurnId) {
|
if (this.activeForegroundTurnId) {
|
||||||
return SETTING_APPLIES_NEXT_TURN_NOTICE;
|
return MODE_APPLIES_NEXT_TURN_NOTICE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3961,7 +3964,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
|||||||
this.refreshResolvedCollaborationMode();
|
this.refreshResolvedCollaborationMode();
|
||||||
this.cachedRuntimeInfo = null;
|
this.cachedRuntimeInfo = null;
|
||||||
if (this.activeForegroundTurnId) {
|
if (this.activeForegroundTurnId) {
|
||||||
return SETTING_APPLIES_NEXT_TURN_NOTICE;
|
return THINKING_APPLIES_NEXT_TURN_NOTICE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,10 @@ describe("OMP agent client and session", () => {
|
|||||||
expect.objectContaining({ name: "review", kind: "skill" }),
|
expect.objectContaining({ name: "review", kind: "skill" }),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
await expect(omp.setMode("ask")).resolves.toEqual(expect.objectContaining({ type: "info" }));
|
await expect(omp.setMode("ask")).resolves.toEqual({
|
||||||
|
type: "warning",
|
||||||
|
message: "Start a new OMP session to change approval mode",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rewinds natively, interrupts, and shuts down", async () => {
|
test("rewinds natively, interrupts, and shuts down", async () => {
|
||||||
|
|||||||
@@ -1084,9 +1084,8 @@ export class OmpAgentSession implements AgentSession {
|
|||||||
throw new Error(`Invalid OMP mode '${modeId}'`);
|
throw new Error(`Invalid OMP mode '${modeId}'`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "info",
|
type: "warning",
|
||||||
message:
|
message: "Start a new OMP session to change approval mode",
|
||||||
"OMP approval mode is set when the agent launches. Start a new OMP session to use a different mode.",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user