From 76fd86175d413a735715e336da67f318e96dfa1e Mon Sep 17 00:00:00 2001 From: murashit Date: Mon, 1 Jun 2026 11:07:59 +0900 Subject: [PATCH] Unify runtime pending setting state --- src/features/chat/chat-state.ts | 55 +++++++++++------- src/features/chat/plan-implementation.ts | 9 +-- .../chat/runtime-settings-controller.ts | 25 ++++---- src/features/chat/view-model.ts | 10 ++-- src/features/chat/view-snapshot.ts | 4 +- src/features/chat/view.ts | 2 +- src/runtime/state.ts | 57 ++++++++++--------- src/runtime/view.ts | 16 +++--- .../chat/chat-app-server-controller.test.ts | 4 +- .../features/chat/chat-state-reducer.test.ts | 12 ++-- .../plan-implementation-controller.test.ts | 2 +- .../chat/runtime-settings-controller.test.ts | 8 +-- .../blocks-and-messages.test.tsx | 4 +- tests/runtime/runtime-settings.test.ts | 52 ++++++++--------- 14 files changed, 138 insertions(+), 122 deletions(-) diff --git a/src/features/chat/chat-state.ts b/src/features/chat/chat-state.ts index 87a58205..60163507 100644 --- a/src/features/chat/chat-state.ts +++ b/src/features/chat/chat-state.ts @@ -19,7 +19,12 @@ import type { DisplayItem } from "./display/types"; import { upsertDisplayItem } from "./display/stream-updates"; import type { PendingUserInput } from "./user-input/model"; import { parseServiceTier, type RequestedServiceTier, type ServiceTier } from "../../app-server/service-tier"; -import { defaultRuntimeOverride, resetRuntimeOverride, setRuntimeOverride, type RuntimeOverride } from "../../runtime/state"; +import { + resetRuntimeSettingToConfig, + setPendingRuntimeSetting, + unchangedRuntimeSetting, + type PendingRuntimeSetting, +} from "../../runtime/state"; export interface PendingTurnStart { anchorItemId: string; @@ -56,11 +61,11 @@ export interface ChatState { activePermissionProfile: ActivePermissionProfile | null; activeThreadCreationCliVersion: string | null; appServerDiagnostics: AppServerDiagnostics; - requestedModel: RuntimeOverride; - requestedReasoningEffort: RuntimeOverride; - requestedApprovalsReviewer: ApprovalsReviewer | null; - requestedCollaborationMode: ModeKind; - requestedServiceTier: RequestedServiceTier | null; + requestedModel: PendingRuntimeSetting; + requestedReasoningEffort: PendingRuntimeSetting; + requestedApprovalsReviewer: PendingRuntimeSetting; + selectedCollaborationMode: ModeKind; + requestedServiceTier: PendingRuntimeSetting; tokenUsage: ThreadTokenUsage | null; rateLimit: RateLimitSnapshot | null; displayItems: readonly DisplayItem[]; @@ -214,11 +219,11 @@ export function createChatState(): ChatState { activePermissionProfile: null, activeThreadCreationCliVersion: null, appServerDiagnostics: createAppServerDiagnostics(), - requestedModel: defaultRuntimeOverride(), - requestedReasoningEffort: defaultRuntimeOverride(), - requestedApprovalsReviewer: null, - requestedCollaborationMode: "default", - requestedServiceTier: null, + requestedModel: unchangedRuntimeSetting(), + requestedReasoningEffort: unchangedRuntimeSetting(), + requestedApprovalsReviewer: unchangedRuntimeSetting(), + selectedCollaborationMode: "default", + requestedServiceTier: unchangedRuntimeSetting(), tokenUsage: null, rateLimit: null, displayItems: [], @@ -362,7 +367,7 @@ function reduceThreadState(state: ChatState, action: ThreadAction): ChatState { activeModel: action.model, activeReasoningEffort: action.reasoningEffort, activeCollaborationMode: action.collaborationMode, - requestedCollaborationMode: action.collaborationMode, + selectedCollaborationMode: action.collaborationMode, activeServiceTier: action.serviceTier, activeApprovalPolicy: action.approvalPolicy, activeApprovalsReviewer: action.approvalsReviewer, @@ -526,21 +531,24 @@ function reduceUiState(state: ChatState, action: UiAction): ChatState { function reduceRuntimeState(state: ChatState, action: RuntimeAction): ChatState { switch (action.type) { case "runtime/requested-model-set": - return patchChatState(state, { requestedModel: action.model === null ? resetRuntimeOverride() : setRuntimeOverride(action.model) }); + return patchChatState(state, { + requestedModel: action.model === null ? resetRuntimeSettingToConfig() : setPendingRuntimeSetting(action.model), + }); case "runtime/requested-effort-set": return patchChatState(state, { - requestedReasoningEffort: action.effort === null ? resetRuntimeOverride() : setRuntimeOverride(action.effort), + requestedReasoningEffort: action.effort === null ? resetRuntimeSettingToConfig() : setPendingRuntimeSetting(action.effort), }); case "runtime/requested-service-tier-set": return patchChatState(state, { - requestedServiceTier: action.serviceTier, + requestedServiceTier: action.serviceTier === null ? unchangedRuntimeSetting() : setPendingRuntimeSetting(action.serviceTier), }); case "runtime/requested-approvals-reviewer-set": return patchChatState(state, { - requestedApprovalsReviewer: action.approvalsReviewer, + requestedApprovalsReviewer: + action.approvalsReviewer === null ? unchangedRuntimeSetting() : setPendingRuntimeSetting(action.approvalsReviewer), }); case "runtime/requested-collaboration-mode-set": - return patchChatState(state, { requestedCollaborationMode: action.collaborationMode }); + return patchChatState(state, { selectedCollaborationMode: action.collaborationMode }); case "runtime/pending-thread-settings-committed": return commitPendingThreadSettings(state, action.update); } @@ -713,13 +721,18 @@ function setUserInputDraftState(state: ChatState, key: string, value: string): C function commitPendingThreadSettings(state: ChatState, update: Omit): ChatState { return patchChatState(state, { - ...("model" in update ? { activeModel: update.model ?? null, requestedModel: defaultRuntimeOverride() } : {}), + ...("model" in update ? { activeModel: update.model ?? null, requestedModel: unchangedRuntimeSetting() } : {}), ...("effort" in update - ? { activeReasoningEffort: update.effort ?? null, requestedReasoningEffort: defaultRuntimeOverride() } + ? { activeReasoningEffort: update.effort ?? null, requestedReasoningEffort: unchangedRuntimeSetting() } + : {}), + ...("serviceTier" in update + ? { activeServiceTier: parseServiceTier(update.serviceTier), requestedServiceTier: unchangedRuntimeSetting() } : {}), - ...("serviceTier" in update ? { activeServiceTier: parseServiceTier(update.serviceTier), requestedServiceTier: null } : {}), ...("approvalsReviewer" in update - ? { activeApprovalsReviewer: update.approvalsReviewer ?? null, requestedApprovalsReviewer: null } + ? { + activeApprovalsReviewer: update.approvalsReviewer ?? null, + requestedApprovalsReviewer: unchangedRuntimeSetting(), + } : {}), ...(update.collaborationMode ? { activeCollaborationMode: update.collaborationMode.mode } : {}), }); diff --git a/src/features/chat/plan-implementation.ts b/src/features/chat/plan-implementation.ts index bd82ca30..a45c235b 100644 --- a/src/features/chat/plan-implementation.ts +++ b/src/features/chat/plan-implementation.ts @@ -2,14 +2,9 @@ import { chatTurnBusy, type ChatState } from "./chat-state"; import type { DisplayItem } from "./display/types"; export function implementPlanCandidateFromState( - state: Pick, + state: Pick, ): DisplayItem | null { - if ( - !state.activeThreadId || - chatTurnBusy(state) || - state.composerDraft.trim().length > 0 || - state.requestedCollaborationMode !== "plan" - ) { + if (!state.activeThreadId || chatTurnBusy(state) || state.composerDraft.trim().length > 0 || state.selectedCollaborationMode !== "plan") { return null; } return ( diff --git a/src/features/chat/runtime-settings-controller.ts b/src/features/chat/runtime-settings-controller.ts index 37aa9d3d..22fe3924 100644 --- a/src/features/chat/runtime-settings-controller.ts +++ b/src/features/chat/runtime-settings-controller.ts @@ -10,8 +10,8 @@ import { autoReviewActive, fastModeActive, fastServiceTierRequestValue, + pendingRuntimeSettingPayload, requestedTurnRuntimeSettings, - runtimeOverridePayload, type RuntimeSnapshot, } from "../../runtime/state"; import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../runtime/settings"; @@ -89,7 +89,7 @@ export class ChatRuntimeSettingsController { } async toggleCollaborationMode(): Promise { - const next = nextCollaborationMode(this.state.requestedCollaborationMode); + const next = nextCollaborationMode(this.state.selectedCollaborationMode); await this.setCollaborationMode(next); } @@ -117,25 +117,28 @@ export class ChatRuntimeSettingsController { const snapshot = this.host.runtimeSnapshot(); const turnSettings = requestedTurnRuntimeSettings(snapshot); - if (state.requestedModel.kind !== "default") { - const model = runtimeOverridePayload(state.requestedModel); + if (state.requestedModel.kind !== "unchanged") { + const model = pendingRuntimeSettingPayload(state.requestedModel); if (model !== undefined) update.model = model; } - if (state.requestedReasoningEffort.kind !== "default") { - const effort = runtimeOverridePayload(state.requestedReasoningEffort); + if (state.requestedReasoningEffort.kind !== "unchanged") { + const effort = pendingRuntimeSettingPayload(state.requestedReasoningEffort); if (effort !== undefined) update.effort = effort; } - if (state.requestedServiceTier !== null) { + if (state.requestedServiceTier.kind === "set") { const serviceTier = requestedServiceTierRequestValue( - state.requestedServiceTier, + state.requestedServiceTier.value, fastServiceTierRequestValue(snapshot, readRuntimeConfig(state.effectiveConfig)), ); if (serviceTier !== undefined) update.serviceTier = serviceTier; + } else if (state.requestedServiceTier.kind === "resetToConfig") { + update.serviceTier = null; } - if (state.requestedApprovalsReviewer !== null) { - update.approvalsReviewer = state.requestedApprovalsReviewer; + if (state.requestedApprovalsReviewer.kind !== "unchanged") { + const approvalsReviewer = pendingRuntimeSettingPayload(state.requestedApprovalsReviewer); + if (approvalsReviewer !== undefined) update.approvalsReviewer = approvalsReviewer; } - if (state.requestedCollaborationMode !== state.activeCollaborationMode) { + if (state.selectedCollaborationMode !== state.activeCollaborationMode) { if (turnSettings.warning) { this.host.addSystemMessage(`${this.host.collaborationModeLabel()} mode is selected, but ${turnSettings.warning}`); } else if (turnSettings.collaborationMode) { diff --git a/src/features/chat/view-model.ts b/src/features/chat/view-model.ts index 94d3bce1..739ee441 100644 --- a/src/features/chat/view-model.ts +++ b/src/features/chat/view-model.ts @@ -6,7 +6,7 @@ import { currentModel, currentReasoningEffort, fastModeActive, - runtimeOverrideLabel, + pendingRuntimeSettingLabel, runtimeSummaryLabel, serviceTierLabel, supportedReasoningEfforts, @@ -81,7 +81,7 @@ export function runtimeSnapshotForChatState({ state }: RuntimeSnapshotInput): Ru requestedModel: state.requestedModel, requestedReasoningEffort: state.requestedReasoningEffort, requestedApprovalsReviewer: state.requestedApprovalsReviewer, - requestedCollaborationMode: state.requestedCollaborationMode, + selectedCollaborationMode: state.selectedCollaborationMode, requestedServiceTier: state.requestedServiceTier, tokenUsage: state.tokenUsage, rateLimit: state.rateLimit, @@ -168,7 +168,7 @@ export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel historyOpen, statusPanelOpen, runtimeOpen, - planActive: state.requestedCollaborationMode === "plan", + planActive: state.selectedCollaborationMode === "plan", autoReviewActive: autoReviewActive(snapshot, config), fastActive: fastModeActive(snapshot, config), runtimeSummary: runtimeSummaryLabel(model, effort), @@ -230,7 +230,7 @@ export function modelStatusLines(state: ChatState, snapshot: RuntimeSnapshot, co const config = readRuntimeConfig(state.effectiveConfig); return [ `Model: ${currentModel(snapshot, config) ?? "(Codex default)"}`, - `Override: ${runtimeOverrideLabel(state.requestedModel)}`, + `Override: ${pendingRuntimeSettingLabel(state.requestedModel)}`, `Provider: ${statusValue(config.modelProvider, "(Codex default)")}`, `Effort: ${currentReasoningEffort(snapshot, config) ?? "(Codex default)"}`, `Mode: ${collaborationModeLabel}`, @@ -242,7 +242,7 @@ export function effortStatusLines(state: ChatState, snapshot: RuntimeSnapshot): const config = readRuntimeConfig(state.effectiveConfig); return [ `Effort: ${currentReasoningEffort(snapshot, config) ?? "(Codex default)"}`, - `Override: ${runtimeOverrideLabel(state.requestedReasoningEffort)}`, + `Override: ${pendingRuntimeSettingLabel(state.requestedReasoningEffort)}`, `Supported: ${supportedReasoningEfforts(snapshot).join(", ")}`, ]; } diff --git a/src/features/chat/view-snapshot.ts b/src/features/chat/view-snapshot.ts index 0def8499..492daf6c 100644 --- a/src/features/chat/view-snapshot.ts +++ b/src/features/chat/view-snapshot.ts @@ -32,7 +32,7 @@ export function toolbarSlotSnapshot(state: ChatState, connected: boolean): ChatP state.activeApprovalPolicy, state.activeApprovalsReviewer, state.activePermissionProfile, - state.requestedCollaborationMode, + state.selectedCollaborationMode, state.requestedServiceTier, state.requestedApprovalsReviewer, state.requestedModel, @@ -60,7 +60,7 @@ export function messagesSlotSnapshot(state: ChatState, pendingRequestsSignature: chatTurnBusy(state), state.messagesPinnedToBottom, state.composerDraft, - state.requestedCollaborationMode, + state.selectedCollaborationMode, displayItemsSignature(state.displayItems), turnDiffsSignature(state.turnDiffs), openDetailsSignature(state.openDetails), diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index 15593486..cb30fc32 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -691,7 +691,7 @@ export class CodexChatView extends ItemView { } private collaborationModeLabel(): string { - return formatCollaborationModeLabel(this.state.requestedCollaborationMode); + return formatCollaborationModeLabel(this.state.selectedCollaborationMode); } private runtimeSnapshot(): RuntimeSnapshot { diff --git a/src/runtime/state.ts b/src/runtime/state.ts index 9dad0594..5ab784a8 100644 --- a/src/runtime/state.ts +++ b/src/runtime/state.ts @@ -21,7 +21,7 @@ import { findModelByIdOrName, supportedEffortsForModel } from "./model"; import { compactModelLabel, compactReasoningEffortLabel } from "./settings"; import { readRuntimeConfig, type RuntimeConfigProjection } from "./config"; -export type RuntimeOverride = { kind: "default" } | { kind: "set"; value: T } | { kind: "resetPending" }; +export type PendingRuntimeSetting = { kind: "unchanged" } | { kind: "set"; value: T } | { kind: "resetToConfig" }; export interface RuntimeSnapshot { effectiveConfig: ConfigReadResponse | null; @@ -33,11 +33,11 @@ export interface RuntimeSnapshot { activeApprovalPolicy: AskForApproval | null; activeApprovalsReviewer: ApprovalsReviewer | null; activePermissionProfile: ActivePermissionProfile | null; - requestedModel: RuntimeOverride; - requestedReasoningEffort: RuntimeOverride; - requestedApprovalsReviewer: ApprovalsReviewer | null; - requestedCollaborationMode: ModeKind; - requestedServiceTier: RequestedServiceTier | null; + requestedModel: PendingRuntimeSetting; + requestedReasoningEffort: PendingRuntimeSetting; + requestedApprovalsReviewer: PendingRuntimeSetting; + selectedCollaborationMode: ModeKind; + requestedServiceTier: PendingRuntimeSetting; tokenUsage: ThreadTokenUsage | null; rateLimit: RateLimitSnapshot | null; hasThreadTurns: boolean; @@ -53,8 +53,9 @@ export function currentServiceTier( snapshot: RuntimeSnapshot, config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig), ): string | null { - if (snapshot.requestedServiceTier === "fast") return "fast"; - if (snapshot.requestedServiceTier === "off") return null; + if (snapshot.requestedServiceTier.kind === "set" && snapshot.requestedServiceTier.value === "fast") return "fast"; + if (snapshot.requestedServiceTier.kind === "set" && snapshot.requestedServiceTier.value === "off") return null; + if (snapshot.requestedServiceTier.kind === "resetToConfig") return config.serviceTier; return snapshot.activeServiceTier ?? config.serviceTier; } @@ -62,8 +63,11 @@ export function requestedOrConfiguredServiceTier( snapshot: RuntimeSnapshot, config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig), ): ServiceTierRequest { - if (snapshot.requestedServiceTier !== null) { - return requestedServiceTierRequestValue(snapshot.requestedServiceTier, fastServiceTierRequestValue(snapshot, config)); + if (snapshot.requestedServiceTier.kind === "set") { + return requestedServiceTierRequestValue(snapshot.requestedServiceTier.value, fastServiceTierRequestValue(snapshot, config)); + } + if (snapshot.requestedServiceTier.kind === "resetToConfig") { + return null; } return configuredServiceTierRequestValue(config.serviceTier); } @@ -74,7 +78,7 @@ export function currentModel( ): string | null { const configModel = config.model; if (snapshot.requestedModel.kind === "set") return snapshot.requestedModel.value; - if (snapshot.requestedModel.kind === "resetPending" && configModel) return configModel; + if (snapshot.requestedModel.kind === "resetToConfig" && configModel) return configModel; return snapshot.activeModel ?? configModel; } @@ -83,7 +87,7 @@ export function currentReasoningEffort( config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig), ): ReasoningEffort | null { if (snapshot.requestedReasoningEffort.kind === "set") return snapshot.requestedReasoningEffort.value; - if (snapshot.requestedReasoningEffort.kind === "resetPending") return config.reasoningEffort; + if (snapshot.requestedReasoningEffort.kind === "resetToConfig") return config.reasoningEffort; return snapshot.activeReasoningEffort ?? config.reasoningEffort; } @@ -91,7 +95,8 @@ export function currentApprovalsReviewer( snapshot: RuntimeSnapshot, config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig), ): ApprovalsReviewer | null { - if (snapshot.requestedApprovalsReviewer !== null) return snapshot.requestedApprovalsReviewer; + if (snapshot.requestedApprovalsReviewer.kind === "set") return snapshot.requestedApprovalsReviewer.value; + if (snapshot.requestedApprovalsReviewer.kind === "resetToConfig") return config.approvalsReviewer; return snapshot.activeApprovalsReviewer ?? config.approvalsReviewer; } @@ -113,7 +118,7 @@ export function requestedTurnRuntimeSettings(snapshot: RuntimeSnapshot): TurnRun const model = currentModel(snapshot); const effort = currentReasoningEffort(snapshot); const collaborationMode = model - ? snapshot.requestedCollaborationMode === "plan" + ? snapshot.selectedCollaborationMode === "plan" ? planCollaborationMode(model, effort) : defaultCollaborationMode(model, effort) : null; @@ -152,7 +157,7 @@ export function fastModeLabel( snapshot: RuntimeSnapshot, config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig), ): string { - if (snapshot.requestedServiceTier === "off") return "off"; + if (snapshot.requestedServiceTier.kind === "set" && snapshot.requestedServiceTier.value === "off") return "off"; if (fastModeActive(snapshot, config)) return "on"; const serviceTier = currentServiceTier(snapshot, config); return serviceTier ? "off" : "Codex default"; @@ -165,27 +170,27 @@ export function fastServiceTierRequestValue( return currentModelServiceTiers(snapshot, config).find((tier) => tier.name.trim().toLowerCase() === "fast")?.id ?? "fast"; } -export function defaultRuntimeOverride(): RuntimeOverride { - return { kind: "default" }; +export function unchangedRuntimeSetting(): PendingRuntimeSetting { + return { kind: "unchanged" }; } -export function setRuntimeOverride(value: T): RuntimeOverride { +export function setPendingRuntimeSetting(value: T): PendingRuntimeSetting { return { kind: "set", value }; } -export function resetRuntimeOverride(): RuntimeOverride { - return { kind: "resetPending" }; +export function resetRuntimeSettingToConfig(): PendingRuntimeSetting { + return { kind: "resetToConfig" }; } -export function runtimeOverridePayload(override: RuntimeOverride): T | null | undefined { - if (override.kind === "set") return override.value; - if (override.kind === "resetPending") return null; +export function pendingRuntimeSettingPayload(setting: PendingRuntimeSetting): T | null | undefined { + if (setting.kind === "set") return setting.value; + if (setting.kind === "resetToConfig") return null; return undefined; } -export function runtimeOverrideLabel(override: RuntimeOverride): string { - if (override.kind === "set") return String(override.value); - if (override.kind === "resetPending") return "(reset to config)"; +export function pendingRuntimeSettingLabel(setting: PendingRuntimeSetting): string { + if (setting.kind === "set") return String(setting.value); + if (setting.kind === "resetToConfig") return "(reset to config)"; return "(none)"; } diff --git a/src/runtime/view.ts b/src/runtime/view.ts index c37dabd5..3b1b45c3 100644 --- a/src/runtime/view.ts +++ b/src/runtime/view.ts @@ -10,7 +10,7 @@ import { currentReasoningEffort, autoReviewActive, fastModeLabel, - runtimeOverrideLabel, + pendingRuntimeSettingLabel, serviceTierLabel, type RuntimeSnapshot, } from "./state"; @@ -122,25 +122,25 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st { key: "effective model", value: currentModel(snapshot, config) ?? CODEX_DEFAULT_LABEL }, { key: "configured model", value: configuredModel(snapshot, config) ?? NOT_REPORTED_LABEL }, { key: "thread model", value: activeRuntimeValueLabel(snapshot.activeModel) }, - { key: "requested model", value: runtimeOverrideLabel(snapshot.requestedModel) }, + { key: "requested model", value: pendingRuntimeSettingLabel(snapshot.requestedModel) }, { key: "effective effort", value: currentReasoningEffort(snapshot, config) ?? CODEX_DEFAULT_LABEL }, { key: "configured effort", value: configuredReasoningEffort(snapshot, config) ?? NOT_REPORTED_LABEL }, { key: "thread effort", value: activeRuntimeValueLabel(snapshot.activeReasoningEffort) }, - { key: "requested effort", value: runtimeOverrideLabel(snapshot.requestedReasoningEffort) }, + { key: "requested effort", value: pendingRuntimeSettingLabel(snapshot.requestedReasoningEffort) }, { key: "reasoning summary", value: stringValue(config.reasoningSummary, CODEX_DEFAULT_LABEL) }, { key: "verbosity", value: stringValue(config.verbosity, CODEX_DEFAULT_LABEL) }, { key: "effective mode", value: snapshot.activeCollaborationMode === "plan" ? "Plan" : "Default" }, { key: "requested mode", value: - snapshot.requestedCollaborationMode === snapshot.activeCollaborationMode + snapshot.selectedCollaborationMode === snapshot.activeCollaborationMode ? "(none)" - : modeLabel(snapshot.requestedCollaborationMode), + : modeLabel(snapshot.selectedCollaborationMode), }, { key: "effective service tier", value: serviceTierLabel(snapshot, config) }, { key: "configured service tier", value: config.serviceTier ?? CODEX_DEFAULT_LABEL }, { key: "thread service tier", value: activeRuntimeValueLabel(snapshot.activeServiceTier) }, - { key: "requested service tier", value: snapshot.requestedServiceTier ?? "(none)" }, + { key: "requested service tier", value: pendingRuntimeSettingLabel(snapshot.requestedServiceTier) }, { key: "fast mode", value: fastModeLabel(snapshot, config) }, { key: "context window", value: tokenLimitLabel(config.modelContextWindow) }, { key: "auto compact limit", value: tokenLimitLabel(config.autoCompactTokenLimit) }, @@ -157,7 +157,7 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st { key: "auto-review", value: autoReviewActive(snapshot, config) ? "on" : "off" }, { key: "configured reviewer", value: config.approvalsReviewer ?? CODEX_DEFAULT_LABEL }, { key: "thread reviewer", value: activeRuntimeValueLabel(snapshot.activeApprovalsReviewer) }, - { key: "requested reviewer", value: snapshot.requestedApprovalsReviewer ?? "(none)" }, + { key: "requested reviewer", value: pendingRuntimeSettingLabel(snapshot.requestedApprovalsReviewer) }, { key: "sandbox", value: stringValue(config.sandboxMode, CODEX_DEFAULT_LABEL) }, { key: "workspace network", value: stringValue(config.workspaceNetworkAccess, CODEX_DEFAULT_LABEL) }, { key: "writable roots", value: writableRootsLabel(config.writableRoots) }, @@ -191,7 +191,7 @@ function configuredReasoningEffort(snapshot: RuntimeSnapshot, config: RuntimeCon ); } -function modeLabel(mode: RuntimeSnapshot["requestedCollaborationMode"]): string { +function modeLabel(mode: RuntimeSnapshot["selectedCollaborationMode"]): string { return mode === "plan" ? "Plan" : "Default"; } diff --git a/tests/features/chat/chat-app-server-controller.test.ts b/tests/features/chat/chat-app-server-controller.test.ts index 9d52ded7..447c6980 100644 --- a/tests/features/chat/chat-app-server-controller.test.ts +++ b/tests/features/chat/chat-app-server-controller.test.ts @@ -35,7 +35,7 @@ describe("ChatAppServerController", () => { stateStore, vaultPath: "/vault", currentClient: () => client, - runtimeSnapshot: () => ({ requestedServiceTier: null, effectiveConfig: null }) as never, + runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, effectiveConfig: null }) as never, forceMessagesToBottom: () => undefined, publishThreadList, publishAppServerMetadata: () => undefined, @@ -68,7 +68,7 @@ describe("ChatAppServerController", () => { stateStore, vaultPath: "/vault", currentClient: () => client, - runtimeSnapshot: () => ({ requestedServiceTier: null, effectiveConfig: null }) as never, + runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, effectiveConfig: null }) as never, forceMessagesToBottom: () => undefined, publishThreadList, publishAppServerMetadata: () => undefined, diff --git a/tests/features/chat/chat-state-reducer.test.ts b/tests/features/chat/chat-state-reducer.test.ts index 9e42f452..421aab82 100644 --- a/tests/features/chat/chat-state-reducer.test.ts +++ b/tests/features/chat/chat-state-reducer.test.ts @@ -219,13 +219,13 @@ describe("chatReducer", () => { }); expect(next.activeModel).toBe("gpt-5.1"); - expect(next.requestedModel).toEqual({ kind: "default" }); + expect(next.requestedModel).toEqual({ kind: "unchanged" }); expect(next.activeReasoningEffort).toBe("high"); - expect(next.requestedReasoningEffort).toEqual({ kind: "default" }); + expect(next.requestedReasoningEffort).toEqual({ kind: "unchanged" }); expect(next.activeServiceTier).toBe("fast"); - expect(next.requestedServiceTier).toBeNull(); + expect(next.requestedServiceTier).toEqual({ kind: "unchanged" }); expect(next.activeApprovalsReviewer).toBe("auto_review"); - expect(next.requestedApprovalsReviewer).toBeNull(); + expect(next.requestedApprovalsReviewer).toEqual({ kind: "unchanged" }); expect(next.activeCollaborationMode).toBe("plan"); }); @@ -237,9 +237,9 @@ describe("chatReducer", () => { state = chatReducer(state, { type: "runtime/requested-service-tier-set", serviceTier: "fast" }); state = chatReducer(state, { type: "runtime/requested-approvals-reviewer-set", approvalsReviewer: "auto_review" }); - expect(state.requestedServiceTier).toBe("fast"); + expect(state.requestedServiceTier).toEqual({ kind: "set", value: "fast" }); expect(state.activeServiceTier).toBe("flex"); - expect(state.requestedApprovalsReviewer).toBe("auto_review"); + expect(state.requestedApprovalsReviewer).toEqual({ kind: "set", value: "auto_review" }); expect(state.activeApprovalsReviewer).toBe("user"); }); diff --git a/tests/features/chat/controllers/submission/plan-implementation-controller.test.ts b/tests/features/chat/controllers/submission/plan-implementation-controller.test.ts index 5d5a31df..1ede5828 100644 --- a/tests/features/chat/controllers/submission/plan-implementation-controller.test.ts +++ b/tests/features/chat/controllers/submission/plan-implementation-controller.test.ts @@ -68,7 +68,7 @@ describe("PlanImplementationController", () => { await controller.implement(plan); expect(host.ensureConnected).toHaveBeenCalledOnce(); - expect(stateStore.getState().requestedCollaborationMode).toBe("default"); + expect(stateStore.getState().selectedCollaborationMode).toBe("default"); expect(stateStore.getState().runtimePicker).toBeNull(); expect(host.sendTurnText).toHaveBeenCalledWith("Please implement this plan."); }); diff --git a/tests/features/chat/runtime-settings-controller.test.ts b/tests/features/chat/runtime-settings-controller.test.ts index 9442e67c..cbb338cc 100644 --- a/tests/features/chat/runtime-settings-controller.test.ts +++ b/tests/features/chat/runtime-settings-controller.test.ts @@ -28,7 +28,7 @@ describe("ChatRuntimeSettingsController", () => { requestedModel: store.getState().requestedModel, requestedReasoningEffort: store.getState().requestedReasoningEffort, requestedApprovalsReviewer: store.getState().requestedApprovalsReviewer, - requestedCollaborationMode: store.getState().requestedCollaborationMode, + selectedCollaborationMode: store.getState().selectedCollaborationMode, requestedServiceTier: store.getState().requestedServiceTier, tokenUsage: store.getState().tokenUsage, rateLimit: store.getState().rateLimit, @@ -42,7 +42,7 @@ describe("ChatRuntimeSettingsController", () => { await expect(controller.setRequestedModel("gpt-5.5")).resolves.toBe(true); expect(client.updateThreadSettings).toHaveBeenCalledWith("thread", { model: "gpt-5.5" }); - expect(store.getState().requestedModel).toEqual({ kind: "default" }); + expect(store.getState().requestedModel).toEqual({ kind: "unchanged" }); expect(store.getState().activeModel).toBe("gpt-5.5"); expect(messages).toEqual([]); }); @@ -58,7 +58,7 @@ describe("ChatRuntimeSettingsController", () => { await controller.toggleFastMode(); expect(client.updateThreadSettings).toHaveBeenCalledWith("thread", { serviceTier: "fast" }); - expect(store.getState().requestedServiceTier).toBeNull(); + expect(store.getState().requestedServiceTier).toEqual({ kind: "unchanged" }); expect(store.getState().activeServiceTier).toBe("fast"); expect(messages).toEqual(["Fast mode on for subsequent turns."]); }); @@ -126,7 +126,7 @@ function runtimeControllerFixture( requestedModel: state.requestedModel, requestedReasoningEffort: state.requestedReasoningEffort, requestedApprovalsReviewer: state.requestedApprovalsReviewer, - requestedCollaborationMode: state.requestedCollaborationMode, + selectedCollaborationMode: state.selectedCollaborationMode, requestedServiceTier: state.requestedServiceTier, tokenUsage: state.tokenUsage, rateLimit: state.rateLimit, diff --git a/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx b/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx index e3cf45aa..60f52bd6 100644 --- a/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx +++ b/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx @@ -793,12 +793,12 @@ describe("message stream block identity and message actions", () => { activeThreadId: "thread", turnLifecycle: { kind: "idle" as const }, composerDraft: "", - requestedCollaborationMode: "plan" as const, + selectedCollaborationMode: "plan" as const, displayItems: [firstPlan, { id: "a1", kind: "message", role: "assistant", text: "answer" } as const, secondPlan], }; expect(implementPlanCandidateFromState(baseState)).toBe(secondPlan); - expect(implementPlanCandidateFromState({ ...baseState, requestedCollaborationMode: "default" })).toBeNull(); + expect(implementPlanCandidateFromState({ ...baseState, selectedCollaborationMode: "default" })).toBeNull(); expect(implementPlanCandidateFromState({ ...baseState, composerDraft: "edit first" })).toBeNull(); expect(implementPlanCandidateFromState({ ...baseState, turnLifecycle: { kind: "running", turnId: "turn-2" } })).toBeNull(); }); diff --git a/tests/runtime/runtime-settings.test.ts b/tests/runtime/runtime-settings.test.ts index e5a3be02..3a8d6341 100644 --- a/tests/runtime/runtime-settings.test.ts +++ b/tests/runtime/runtime-settings.test.ts @@ -24,9 +24,9 @@ import { fastModeLabel, requestedOrConfiguredServiceTier, requestedTurnRuntimeSettings, - resetRuntimeOverride, - runtimeOverridePayload, - setRuntimeOverride, + resetRuntimeSettingToConfig, + pendingRuntimeSettingPayload, + setPendingRuntimeSetting, serviceTierLabel, type RuntimeSnapshot, } from "../../src/runtime/state"; @@ -111,8 +111,8 @@ describe("runtime settings", () => { it("keeps runtime defaults, resets, and collaboration mode semantics distinct", () => { const snapshot = runtimeSnapshot({ - requestedModel: resetRuntimeOverride(), - requestedReasoningEffort: resetRuntimeOverride(), + requestedModel: resetRuntimeSettingToConfig(), + requestedReasoningEffort: resetRuntimeSettingToConfig(), }); expect(currentModel(snapshot)).toBe("gpt-5.5"); @@ -123,27 +123,27 @@ describe("runtime settings", () => { settings: { model: "gpt-5.5", reasoning_effort: "high" }, }, }); - expect(runtimeOverridePayload(snapshot.requestedModel)).toBeNull(); - expect(runtimeOverridePayload(snapshot.requestedReasoningEffort)).toBeNull(); + expect(pendingRuntimeSettingPayload(snapshot.requestedModel)).toBeNull(); + expect(pendingRuntimeSettingPayload(snapshot.requestedReasoningEffort)).toBeNull(); }); it("uses explicit runtime overrides as current values and settings payload values", () => { const snapshot = runtimeSnapshot({ - requestedModel: setRuntimeOverride("gpt-5.4"), - requestedReasoningEffort: setRuntimeOverride("low"), + requestedModel: setPendingRuntimeSetting("gpt-5.4"), + requestedReasoningEffort: setPendingRuntimeSetting("low"), }); expect(currentModel(snapshot)).toBe("gpt-5.4"); expect(currentReasoningEffort(snapshot)).toBe("low"); - expect(runtimeOverridePayload(snapshot.requestedModel)).toBe("gpt-5.4"); - expect(runtimeOverridePayload(snapshot.requestedReasoningEffort)).toBe("low"); + expect(pendingRuntimeSettingPayload(snapshot.requestedModel)).toBe("gpt-5.4"); + expect(pendingRuntimeSettingPayload(snapshot.requestedReasoningEffort)).toBe("low"); }); it("resolves approval reviewer from requested, active, then effective config", () => { expect( currentApprovalsReviewer( runtimeSnapshot({ - requestedApprovalsReviewer: "user", + requestedApprovalsReviewer: setPendingRuntimeSetting("user"), activeApprovalsReviewer: "auto_review", effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "guardian_subagent" }), }), @@ -187,7 +187,7 @@ describe("runtime settings", () => { it("uses requested reviewer above active and configured reviewers", () => { const snapshot = runtimeSnapshot({ - requestedApprovalsReviewer: "user", + requestedApprovalsReviewer: setPendingRuntimeSetting("user"), activeApprovalsReviewer: "user", effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "auto_review" }), }); @@ -279,7 +279,7 @@ describe("runtime settings", () => { it("uses requested service tier above active and configured service tiers", () => { const snapshot = runtimeSnapshot({ - requestedServiceTier: "off", + requestedServiceTier: setPendingRuntimeSetting("off"), activeServiceTier: "flex", effectiveConfig: effectiveConfigFixture({ service_tier: "fast" }), }); @@ -289,7 +289,7 @@ describe("runtime settings", () => { }); it("resolves requested approval reviewer without adding it to turn runtime settings", () => { - const snapshot = runtimeSnapshot({ requestedApprovalsReviewer: "auto_review" }); + const snapshot = runtimeSnapshot({ requestedApprovalsReviewer: setPendingRuntimeSetting("auto_review") }); expect(autoReviewActive(snapshot)).toBe(true); expect(currentApprovalsReviewer(snapshot)).toBe("auto_review"); @@ -321,8 +321,8 @@ describe("runtime settings", () => { activeModel: "gpt-5-active", activeReasoningEffort: "low", activeCollaborationMode: "plan", - requestedCollaborationMode: "plan", - requestedModel: setRuntimeOverride("gpt-5-pending"), + selectedCollaborationMode: "plan", + requestedModel: setPendingRuntimeSetting("gpt-5-pending"), }), "/vault", ); @@ -348,7 +348,7 @@ describe("runtime settings", () => { const resetSections = effectiveConfigSections( runtimeSnapshot({ - requestedReasoningEffort: resetRuntimeOverride(), + requestedReasoningEffort: resetRuntimeSettingToConfig(), }), "/vault", ); @@ -482,7 +482,7 @@ describe("runtime settings", () => { }); it("summarizes service tier and context meter state from one runtime snapshot", () => { - const snapshot = runtimeSnapshot({ requestedServiceTier: "fast", activeThreadId: "thread" }); + const snapshot = runtimeSnapshot({ requestedServiceTier: setPendingRuntimeSetting("fast"), activeThreadId: "thread" }); expect(serviceTierLabel(snapshot)).toBe("fast"); expect(fastModeLabel(snapshot)).toBe("on"); @@ -522,7 +522,7 @@ describe("runtime settings", () => { it("serializes explicit fast off as a null service tier request", () => { const snapshot = runtimeSnapshot({ effectiveConfig: effectiveConfigFixture({ service_tier: "fast" }), - requestedServiceTier: "off", + requestedServiceTier: setPendingRuntimeSetting("off"), }); expect(serviceTierLabel(snapshot)).toBe("(Codex default)"); @@ -534,7 +534,7 @@ describe("runtime settings", () => { const model = modelFixture("gpt-5.5"); model.serviceTiers = [{ id: "priority", name: "Fast", description: "1.5x speed, increased usage" }]; const snapshot = runtimeSnapshot({ - requestedServiceTier: "fast", + requestedServiceTier: setPendingRuntimeSetting("fast"), effectiveConfig: effectiveConfigFixture({ model: "gpt-5.5" }), availableModels: [model], }); @@ -654,11 +654,11 @@ function runtimeSnapshot(overrides: Partial = {}): RuntimeSnaps activeApprovalPolicy: null, activeApprovalsReviewer: null, activePermissionProfile: null, - requestedModel: { kind: "default" }, - requestedReasoningEffort: { kind: "default" }, - requestedApprovalsReviewer: null, - requestedCollaborationMode: "default", - requestedServiceTier: null, + requestedModel: { kind: "unchanged" }, + requestedReasoningEffort: { kind: "unchanged" }, + requestedApprovalsReviewer: { kind: "unchanged" }, + selectedCollaborationMode: "default", + requestedServiceTier: { kind: "unchanged" }, tokenUsage: null, rateLimit: null, hasThreadTurns: false,