Unify runtime pending setting state

This commit is contained in:
murashit 2026-06-01 11:07:59 +09:00
parent be7f504871
commit 76fd86175d
14 changed files with 138 additions and 122 deletions

View file

@ -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<string>;
requestedReasoningEffort: RuntimeOverride<ReasoningEffort>;
requestedApprovalsReviewer: ApprovalsReviewer | null;
requestedCollaborationMode: ModeKind;
requestedServiceTier: RequestedServiceTier | null;
requestedModel: PendingRuntimeSetting<string>;
requestedReasoningEffort: PendingRuntimeSetting<ReasoningEffort>;
requestedApprovalsReviewer: PendingRuntimeSetting<ApprovalsReviewer>;
selectedCollaborationMode: ModeKind;
requestedServiceTier: PendingRuntimeSetting<RequestedServiceTier>;
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<ThreadSettingsUpdateParams, "threadId">): ChatState {
return patchChatState(state, {
...("model" in update ? { activeModel: update.model ?? null, requestedModel: defaultRuntimeOverride<string>() } : {}),
...("model" in update ? { activeModel: update.model ?? null, requestedModel: unchangedRuntimeSetting<string>() } : {}),
...("effort" in update
? { activeReasoningEffort: update.effort ?? null, requestedReasoningEffort: defaultRuntimeOverride<ReasoningEffort>() }
? { activeReasoningEffort: update.effort ?? null, requestedReasoningEffort: unchangedRuntimeSetting<ReasoningEffort>() }
: {}),
...("serviceTier" in update
? { activeServiceTier: parseServiceTier(update.serviceTier), requestedServiceTier: unchangedRuntimeSetting<RequestedServiceTier>() }
: {}),
...("serviceTier" in update ? { activeServiceTier: parseServiceTier(update.serviceTier), requestedServiceTier: null } : {}),
...("approvalsReviewer" in update
? { activeApprovalsReviewer: update.approvalsReviewer ?? null, requestedApprovalsReviewer: null }
? {
activeApprovalsReviewer: update.approvalsReviewer ?? null,
requestedApprovalsReviewer: unchangedRuntimeSetting<ApprovalsReviewer>(),
}
: {}),
...(update.collaborationMode ? { activeCollaborationMode: update.collaborationMode.mode } : {}),
});

View file

@ -2,14 +2,9 @@ import { chatTurnBusy, type ChatState } from "./chat-state";
import type { DisplayItem } from "./display/types";
export function implementPlanCandidateFromState(
state: Pick<ChatState, "activeThreadId" | "turnLifecycle" | "composerDraft" | "requestedCollaborationMode" | "displayItems">,
state: Pick<ChatState, "activeThreadId" | "turnLifecycle" | "composerDraft" | "selectedCollaborationMode" | "displayItems">,
): 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 (

View file

@ -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<void> {
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) {

View file

@ -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(", ")}`,
];
}

View file

@ -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),

View file

@ -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 {

View file

@ -21,7 +21,7 @@ import { findModelByIdOrName, supportedEffortsForModel } from "./model";
import { compactModelLabel, compactReasoningEffortLabel } from "./settings";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
export type RuntimeOverride<T> = { kind: "default" } | { kind: "set"; value: T } | { kind: "resetPending" };
export type PendingRuntimeSetting<T> = { 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<string>;
requestedReasoningEffort: RuntimeOverride<ReasoningEffort>;
requestedApprovalsReviewer: ApprovalsReviewer | null;
requestedCollaborationMode: ModeKind;
requestedServiceTier: RequestedServiceTier | null;
requestedModel: PendingRuntimeSetting<string>;
requestedReasoningEffort: PendingRuntimeSetting<ReasoningEffort>;
requestedApprovalsReviewer: PendingRuntimeSetting<ApprovalsReviewer>;
selectedCollaborationMode: ModeKind;
requestedServiceTier: PendingRuntimeSetting<RequestedServiceTier>;
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<T>(): RuntimeOverride<T> {
return { kind: "default" };
export function unchangedRuntimeSetting<T>(): PendingRuntimeSetting<T> {
return { kind: "unchanged" };
}
export function setRuntimeOverride<T>(value: T): RuntimeOverride<T> {
export function setPendingRuntimeSetting<T>(value: T): PendingRuntimeSetting<T> {
return { kind: "set", value };
}
export function resetRuntimeOverride<T>(): RuntimeOverride<T> {
return { kind: "resetPending" };
export function resetRuntimeSettingToConfig<T>(): PendingRuntimeSetting<T> {
return { kind: "resetToConfig" };
}
export function runtimeOverridePayload<T>(override: RuntimeOverride<T>): T | null | undefined {
if (override.kind === "set") return override.value;
if (override.kind === "resetPending") return null;
export function pendingRuntimeSettingPayload<T>(setting: PendingRuntimeSetting<T>): T | null | undefined {
if (setting.kind === "set") return setting.value;
if (setting.kind === "resetToConfig") return null;
return undefined;
}
export function runtimeOverrideLabel<T>(override: RuntimeOverride<T>): string {
if (override.kind === "set") return String(override.value);
if (override.kind === "resetPending") return "(reset to config)";
export function pendingRuntimeSettingLabel<T>(setting: PendingRuntimeSetting<T>): string {
if (setting.kind === "set") return String(setting.value);
if (setting.kind === "resetToConfig") return "(reset to config)";
return "(none)";
}

View file

@ -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";
}

View file

@ -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,

View file

@ -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");
});

View file

@ -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.");
});

View file

@ -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,

View file

@ -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();
});

View file

@ -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<RuntimeSnapshot> = {}): 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,