mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Resolve profile runtime config for toolbar
This commit is contained in:
parent
c05364addf
commit
d26f9f58a8
10 changed files with 379 additions and 28 deletions
|
|
@ -170,7 +170,7 @@ export class AppServerClient {
|
|||
}
|
||||
|
||||
readEffectiveConfig(cwd: string): Promise<ConfigReadResponse> {
|
||||
return this.request("config/read", { cwd, includeLayers: false });
|
||||
return this.request("config/read", { cwd, includeLayers: true });
|
||||
}
|
||||
|
||||
listHooks(cwd: string): Promise<HooksListResponse> {
|
||||
|
|
|
|||
|
|
@ -427,8 +427,13 @@ export class PanelController {
|
|||
this.state.activeReasoningEffort = settings.effort ?? null;
|
||||
this.state.activeCollaborationMode = settings.collaborationMode.mode;
|
||||
this.state.requestedCollaborationMode = settings.collaborationMode.mode;
|
||||
this.state.activeServiceTier = settings.serviceTier ?? null;
|
||||
this.state.activeApprovalsReviewer = settings.approvalsReviewer ?? null;
|
||||
const serviceTierOverride = this.state.activeServiceTierOverride || this.state.requestedServiceTier !== null;
|
||||
const approvalsReviewerOverride = this.state.activeApprovalsReviewerOverride || this.state.requestedApprovalsReviewer !== null;
|
||||
this.state.activeServiceTier = settings.serviceTier ?? (serviceTierOverride ? this.state.activeServiceTier : null);
|
||||
this.state.activeServiceTierOverride = serviceTierOverride;
|
||||
this.state.activeApprovalsReviewer =
|
||||
settings.approvalsReviewer ?? (approvalsReviewerOverride ? this.state.activeApprovalsReviewer : null);
|
||||
this.state.activeApprovalsReviewerOverride = approvalsReviewerOverride;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,9 @@ export class PanelSessionController {
|
|||
this.host.state.activeModel = response.model ?? null;
|
||||
this.host.state.activeReasoningEffort = response.reasoningEffort ?? null;
|
||||
this.host.state.activeServiceTier = response.serviceTier ?? null;
|
||||
this.host.state.activeServiceTierOverride = false;
|
||||
this.host.state.activeApprovalsReviewer = response.approvalsReviewer ?? null;
|
||||
this.host.state.activeApprovalsReviewerOverride = false;
|
||||
this.host.state.activeThreadCliVersion = response.thread.cliVersion ?? null;
|
||||
this.host.state.tokenUsage = null;
|
||||
this.host.state.historyCursor = null;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ export interface PanelState {
|
|||
activeReasoningEffort: ReasoningEffort | null;
|
||||
activeCollaborationMode: ModeKind;
|
||||
activeServiceTier: string | null;
|
||||
activeServiceTierOverride: boolean;
|
||||
activeApprovalsReviewer: ApprovalsReviewer | null;
|
||||
activeApprovalsReviewerOverride: boolean;
|
||||
activeThreadCliVersion: string | null;
|
||||
appServerDiagnostics: AppServerDiagnostics;
|
||||
requestedModel: RuntimeOverride<string>;
|
||||
|
|
@ -78,7 +80,9 @@ export function createPanelState(): PanelState {
|
|||
activeReasoningEffort: null,
|
||||
activeCollaborationMode: "default",
|
||||
activeServiceTier: null,
|
||||
activeServiceTierOverride: false,
|
||||
activeApprovalsReviewer: null,
|
||||
activeApprovalsReviewerOverride: false,
|
||||
activeThreadCliVersion: null,
|
||||
appServerDiagnostics: createAppServerDiagnostics(),
|
||||
requestedModel: defaultRuntimeOverride(),
|
||||
|
|
@ -128,7 +132,9 @@ export function clearActiveThreadState(state: PanelState): void {
|
|||
state.activeReasoningEffort = null;
|
||||
state.activeCollaborationMode = "default";
|
||||
state.activeServiceTier = null;
|
||||
state.activeServiceTierOverride = false;
|
||||
state.activeApprovalsReviewer = null;
|
||||
state.activeApprovalsReviewerOverride = false;
|
||||
state.activeThreadCliVersion = null;
|
||||
state.tokenUsage = null;
|
||||
state.historyCursor = null;
|
||||
|
|
@ -145,7 +151,9 @@ export function clearConnectionScopedState(state: PanelState): void {
|
|||
state.activeReasoningEffort = null;
|
||||
state.activeCollaborationMode = "default";
|
||||
state.activeServiceTier = null;
|
||||
state.activeServiceTierOverride = false;
|
||||
state.activeApprovalsReviewer = null;
|
||||
state.activeApprovalsReviewerOverride = false;
|
||||
state.activeThreadCliVersion = null;
|
||||
state.rateLimit = null;
|
||||
state.listedThreads = [];
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
currentServiceTier,
|
||||
defaultRuntimeOverride,
|
||||
requestedTurnRuntimeSettings,
|
||||
resolvedConfigValue,
|
||||
resetRuntimeOverride,
|
||||
runtimeOverridePayload,
|
||||
runtimeOverrideLabel,
|
||||
|
|
@ -329,7 +330,9 @@ export class CodexPanelView extends ItemView {
|
|||
this.state.activeModel = response.model ?? null;
|
||||
this.state.activeReasoningEffort = response.reasoningEffort ?? null;
|
||||
this.state.activeServiceTier = response.serviceTier ?? null;
|
||||
this.state.activeServiceTierOverride = false;
|
||||
this.state.activeApprovalsReviewer = response.approvalsReviewer ?? null;
|
||||
this.state.activeApprovalsReviewerOverride = false;
|
||||
this.state.activeThreadCliVersion = response.thread.cliVersion ?? null;
|
||||
this.state.tokenUsage = null;
|
||||
this.state.displayItems = [];
|
||||
|
|
@ -631,10 +634,12 @@ export class CodexPanelView extends ItemView {
|
|||
}
|
||||
if ("serviceTier" in update) {
|
||||
this.state.activeServiceTier = this.state.requestedServiceTier === "standard" ? "standard" : (update.serviceTier ?? null);
|
||||
this.state.activeServiceTierOverride = true;
|
||||
this.state.requestedServiceTier = null;
|
||||
}
|
||||
if ("approvalsReviewer" in update) {
|
||||
this.state.activeApprovalsReviewer = update.approvalsReviewer ?? null;
|
||||
this.state.activeApprovalsReviewerOverride = true;
|
||||
this.state.requestedApprovalsReviewer = null;
|
||||
}
|
||||
if (update.collaborationMode) {
|
||||
|
|
@ -647,6 +652,7 @@ export class CodexPanelView extends ItemView {
|
|||
const next: ServiceTier = current === "fast" ? "standard" : "fast";
|
||||
this.state.requestedServiceTier = next;
|
||||
this.state.activeServiceTier = next;
|
||||
this.state.activeServiceTierOverride = true;
|
||||
this.state.runtimePicker = null;
|
||||
if (!(await this.applyPendingThreadSettings())) return;
|
||||
this.addSystemMessage(next === "fast" ? "Fast mode on for subsequent turns." : "Fast mode off for subsequent turns.");
|
||||
|
|
@ -666,6 +672,7 @@ export class CodexPanelView extends ItemView {
|
|||
: "auto_review";
|
||||
this.state.requestedApprovalsReviewer = next;
|
||||
this.state.activeApprovalsReviewer = next;
|
||||
this.state.activeApprovalsReviewerOverride = true;
|
||||
this.state.runtimePicker = null;
|
||||
if (!(await this.applyPendingThreadSettings())) return;
|
||||
this.addSystemMessage(next === "auto_review" ? "Auto-review on for subsequent turns." : "Auto-review off for subsequent turns.");
|
||||
|
|
@ -1009,7 +1016,7 @@ export class CodexPanelView extends ItemView {
|
|||
return [
|
||||
`Model: ${currentModel(snapshot, config) ?? "(from default)"}`,
|
||||
`Override: ${runtimeOverrideLabel(this.state.requestedModel)}`,
|
||||
`Provider: ${statusValue(config.model_provider, "(from default)")}`,
|
||||
`Provider: ${statusValue(resolvedConfigValue(config, "model_provider"), "(from default)")}`,
|
||||
`Effort: ${currentReasoningEffort(snapshot, config) ?? "(from default)"}`,
|
||||
`Mode: ${this.collaborationModeLabel()}`,
|
||||
`Service tier: ${serviceTierLabel(snapshot, config)}`,
|
||||
|
|
@ -1067,7 +1074,9 @@ export class CodexPanelView extends ItemView {
|
|||
activeReasoningEffort: this.state.activeReasoningEffort,
|
||||
activeCollaborationMode: this.state.activeCollaborationMode,
|
||||
activeServiceTier: this.state.activeServiceTier,
|
||||
activeServiceTierOverride: this.state.activeServiceTierOverride,
|
||||
activeApprovalsReviewer: this.state.activeApprovalsReviewer,
|
||||
activeApprovalsReviewerOverride: this.state.activeApprovalsReviewerOverride,
|
||||
requestedModel: this.state.requestedModel,
|
||||
requestedReasoningEffort: this.state.requestedReasoningEffort,
|
||||
requestedApprovalsReviewer: this.state.requestedApprovalsReviewer,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ export interface RuntimeSnapshot {
|
|||
activeReasoningEffort: ReasoningEffort | null;
|
||||
activeCollaborationMode: ModeKind;
|
||||
activeServiceTier: string | null;
|
||||
activeServiceTierOverride: boolean;
|
||||
activeApprovalsReviewer: ApprovalsReviewer | null;
|
||||
activeApprovalsReviewerOverride: boolean;
|
||||
requestedModel: RuntimeOverride<string>;
|
||||
requestedReasoningEffort: RuntimeOverride<ReasoningEffort>;
|
||||
requestedApprovalsReviewer: ApprovalsReviewer | null;
|
||||
|
|
@ -39,16 +41,26 @@ export interface TurnRuntimeSettings {
|
|||
}
|
||||
|
||||
export function configRecord(effectiveConfig: ConfigReadResponse | null): Record<string, unknown> {
|
||||
return asRecord(effectiveConfig?.config);
|
||||
return fillMissingConfigValues(asRecord(effectiveConfig?.config), effectiveConfig?.layers ?? null);
|
||||
}
|
||||
|
||||
export function selectedProfileConfig(config: Record<string, unknown>): Record<string, unknown> {
|
||||
const profileName = typeof config.profile === "string" && config.profile.length > 0 ? config.profile : null;
|
||||
return profileName ? asRecord(asRecord(config.profiles)[profileName]) : {};
|
||||
}
|
||||
|
||||
export function resolvedConfigValue(config: Record<string, unknown>, key: string): unknown {
|
||||
const profileValue = selectedProfileConfig(config)[key];
|
||||
return profileValue === undefined || profileValue === null ? config[key] : profileValue;
|
||||
}
|
||||
|
||||
export function currentServiceTier(snapshot: RuntimeSnapshot, config = configRecord(snapshot.effectiveConfig)): string | null {
|
||||
return (
|
||||
snapshot.requestedServiceTier ??
|
||||
parseServiceTier(snapshot.activeServiceTier) ??
|
||||
snapshot.activeServiceTier ??
|
||||
configuredServiceTier(config)
|
||||
);
|
||||
const active = parseServiceTier(snapshot.activeServiceTier) ?? snapshot.activeServiceTier;
|
||||
const configured = configuredServiceTier(config);
|
||||
if (snapshot.requestedServiceTier !== null) return snapshot.requestedServiceTier;
|
||||
if (snapshot.activeServiceTierOverride) return active ?? configured;
|
||||
if (configured && (active === null || active === "standard")) return configured;
|
||||
return active ?? configured;
|
||||
}
|
||||
|
||||
export function requestedOrConfiguredServiceTier(
|
||||
|
|
@ -59,7 +71,7 @@ export function requestedOrConfiguredServiceTier(
|
|||
}
|
||||
|
||||
export function currentModel(snapshot: RuntimeSnapshot, config = configRecord(snapshot.effectiveConfig)): string | null {
|
||||
const model = config.model;
|
||||
const model = resolvedConfigValue(config, "model");
|
||||
const configModel = typeof model === "string" && model.length > 0 ? model : null;
|
||||
if (snapshot.requestedModel.kind === "set") return snapshot.requestedModel.value;
|
||||
if (snapshot.requestedModel.kind === "resetPending" && configModel) return configModel;
|
||||
|
|
@ -68,7 +80,7 @@ export function currentModel(snapshot: RuntimeSnapshot, config = configRecord(sn
|
|||
|
||||
export function currentReasoningEffort(snapshot: RuntimeSnapshot, config = configRecord(snapshot.effectiveConfig)): ReasoningEffort | null {
|
||||
if (snapshot.requestedReasoningEffort.kind === "set") return snapshot.requestedReasoningEffort.value;
|
||||
const effort = config.model_reasoning_effort;
|
||||
const effort = resolvedConfigValue(config, "model_reasoning_effort");
|
||||
if (snapshot.requestedReasoningEffort.kind === "resetPending") return isReasoningEffort(effort) ? effort : null;
|
||||
return snapshot.activeReasoningEffort ?? (isReasoningEffort(effort) ? effort : null);
|
||||
}
|
||||
|
|
@ -77,12 +89,20 @@ export function currentApprovalsReviewer(
|
|||
snapshot: RuntimeSnapshot,
|
||||
config = configRecord(snapshot.effectiveConfig),
|
||||
): ApprovalsReviewer | null {
|
||||
const configured = config.approvals_reviewer;
|
||||
return snapshot.requestedApprovalsReviewer ?? snapshot.activeApprovalsReviewer ?? (isApprovalsReviewer(configured) ? configured : null);
|
||||
const configured = configuredApprovalsReviewer(config);
|
||||
if (snapshot.requestedApprovalsReviewer !== null) return snapshot.requestedApprovalsReviewer;
|
||||
if (snapshot.activeApprovalsReviewerOverride) return snapshot.activeApprovalsReviewer ?? configured;
|
||||
if (configured && (!snapshot.activeApprovalsReviewer || snapshot.activeApprovalsReviewer === "user")) return configured;
|
||||
return snapshot.activeApprovalsReviewer ?? configured;
|
||||
}
|
||||
|
||||
export function autoReviewActive(snapshot: RuntimeSnapshot, config = configRecord(snapshot.effectiveConfig)): boolean {
|
||||
return currentApprovalsReviewer(snapshot, config) === "auto_review";
|
||||
return isAutoReviewReviewer(currentApprovalsReviewer(snapshot, config));
|
||||
}
|
||||
|
||||
export function configuredApprovalsReviewer(config: Record<string, unknown>): ApprovalsReviewer | null {
|
||||
const value = resolvedConfigValue(config, "approvals_reviewer");
|
||||
return isApprovalsReviewer(value) ? value : null;
|
||||
}
|
||||
|
||||
export function requestedTurnRuntimeSettings(snapshot: RuntimeSnapshot): TurnRuntimeSettings {
|
||||
|
|
@ -146,14 +166,43 @@ export function runtimeOverrideLabel<T>(override: RuntimeOverride<T>): string {
|
|||
return "(none)";
|
||||
}
|
||||
|
||||
function configuredServiceTier(config: Record<string, unknown>): ServiceTier | null {
|
||||
return parseServiceTier(config.service_tier);
|
||||
export function configuredServiceTier(config: Record<string, unknown>): ServiceTier | null {
|
||||
return parseServiceTier(resolvedConfigValue(config, "service_tier"));
|
||||
}
|
||||
|
||||
function isApprovalsReviewer(value: unknown): value is ApprovalsReviewer {
|
||||
return value === "user" || value === "auto_review" || value === "guardian_subagent";
|
||||
}
|
||||
|
||||
function isAutoReviewReviewer(value: ApprovalsReviewer | null): boolean {
|
||||
return value === "auto_review" || value === "guardian_subagent";
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
function fillMissingConfigValues(config: Record<string, unknown>, layers: ConfigReadResponse["layers"]): Record<string, unknown> {
|
||||
if (!layers || layers.length === 0) return config;
|
||||
const fallback = rawLayerConfigRecord(layers);
|
||||
if (Object.keys(fallback).length === 0) return config;
|
||||
|
||||
const merged = { ...config };
|
||||
for (const [key, value] of Object.entries(fallback)) {
|
||||
if (merged[key] === undefined || merged[key] === null) {
|
||||
merged[key] = value;
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
function rawLayerConfigRecord(layers: NonNullable<ConfigReadResponse["layers"]>): Record<string, unknown> {
|
||||
const merged: Record<string, unknown> = {};
|
||||
for (const layer of layers) {
|
||||
const config = asRecord(layer.config);
|
||||
for (const [key, value] of Object.entries(config)) {
|
||||
if (value !== undefined && value !== null) merged[key] = value;
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,14 @@ import { jsonPreview } from "../utils";
|
|||
import { sortedAvailableModels } from "./model";
|
||||
import {
|
||||
configRecord,
|
||||
configuredApprovalsReviewer,
|
||||
configuredServiceTier,
|
||||
currentApprovalsReviewer,
|
||||
currentModel,
|
||||
currentReasoningEffort,
|
||||
autoReviewActive,
|
||||
fastModeLabel,
|
||||
resolvedConfigValue,
|
||||
runtimeOverrideLabel,
|
||||
serviceTierLabel,
|
||||
type RuntimeSnapshot,
|
||||
|
|
@ -102,7 +107,7 @@ export function rateLimitSummary(snapshot: RuntimeSnapshot, nowMs = Date.now()):
|
|||
export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: string): EffectiveConfigSection[] {
|
||||
const config = configRecord(snapshot.effectiveConfig);
|
||||
const features = asRecord(config.features);
|
||||
const tools = asRecord(config.tools);
|
||||
const tools = asRecord(resolvedConfigValue(config, "tools"));
|
||||
const workspaceWrite = asRecord(config.sandbox_workspace_write);
|
||||
return [
|
||||
{
|
||||
|
|
@ -110,7 +115,7 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st
|
|||
rows: [
|
||||
{ key: "cwd", value: vaultPath },
|
||||
{ key: "profile", value: stringValue(config.profile, "(default)") },
|
||||
{ key: "model provider", value: stringValue(config.model_provider, "(from default)") },
|
||||
{ key: "model provider", value: stringValue(resolvedConfigValue(config, "model_provider"), "(from default)") },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -122,8 +127,8 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st
|
|||
{ key: "effort", value: currentReasoningEffort(snapshot, config) ?? "(from default)" },
|
||||
{ key: "config effort", value: configuredReasoningEffort(snapshot, config) ?? "(not reported)" },
|
||||
{ key: "effort change", value: runtimeOverrideLabel(snapshot.requestedReasoningEffort) },
|
||||
{ key: "reasoning summary", value: stringValue(config.model_reasoning_summary, "(from default)") },
|
||||
{ key: "verbosity", value: stringValue(config.model_verbosity, "(from default)") },
|
||||
{ key: "reasoning summary", value: stringValue(resolvedConfigValue(config, "model_reasoning_summary"), "(from default)") },
|
||||
{ key: "verbosity", value: stringValue(resolvedConfigValue(config, "model_verbosity"), "(from default)") },
|
||||
{ key: "mode", value: snapshot.activeCollaborationMode === "plan" ? "Plan" : "Default" },
|
||||
{
|
||||
key: "mode change",
|
||||
|
|
@ -133,6 +138,8 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st
|
|||
: modeLabel(snapshot.requestedCollaborationMode),
|
||||
},
|
||||
{ key: "service tier", value: serviceTierLabel(snapshot, config) },
|
||||
{ key: "config service tier", value: configuredServiceTier(config) ?? "(not reported)" },
|
||||
{ key: "active service tier", value: activeRuntimeValueLabel(snapshot.activeServiceTier, snapshot.activeServiceTierOverride) },
|
||||
{ key: "fast mode", value: fastModeLabel(snapshot, config) },
|
||||
{ key: "context window", value: tokenLimitLabel(config.model_context_window) },
|
||||
{ key: "auto compact limit", value: tokenLimitLabel(config.model_auto_compact_token_limit) },
|
||||
|
|
@ -141,12 +148,18 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st
|
|||
{
|
||||
title: "Policy",
|
||||
rows: [
|
||||
{ key: "approval", value: stringValue(config.approval_policy, "(from default)") },
|
||||
{ key: "reviewer", value: stringValue(config.approvals_reviewer, "(from default)") },
|
||||
{ key: "approval", value: stringValue(resolvedConfigValue(config, "approval_policy"), "(from default)") },
|
||||
{ key: "reviewer", value: currentApprovalsReviewer(snapshot, config) ?? "(not reported)" },
|
||||
{ key: "auto-review", value: autoReviewActive(snapshot, config) ? "on" : "off" },
|
||||
{ key: "config reviewer", value: configuredApprovalsReviewer(config) ?? "(from default)" },
|
||||
{
|
||||
key: "active reviewer",
|
||||
value: activeRuntimeValueLabel(snapshot.activeApprovalsReviewer, snapshot.activeApprovalsReviewerOverride),
|
||||
},
|
||||
{ key: "sandbox", value: stringValue(config.sandbox_mode, "(from default)") },
|
||||
{ key: "workspace network", value: stringValue(workspaceWrite.network_access, "(from default)") },
|
||||
{ key: "writable roots", value: writableRootsLabel(workspaceWrite.writable_roots) },
|
||||
{ key: "web search", value: stringValue(config.web_search, "(from default)") },
|
||||
{ key: "web search", value: stringValue(resolvedConfigValue(config, "web_search"), "(from default)") },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -166,13 +179,15 @@ function contextUsageTokens(usage: ThreadTokenUsage): number {
|
|||
}
|
||||
|
||||
function configuredModel(snapshot: RuntimeSnapshot, config: Record<string, unknown>): string | null {
|
||||
if (typeof config.model === "string" && config.model.length > 0) return config.model;
|
||||
const model = resolvedConfigValue(config, "model");
|
||||
if (typeof model === "string" && model.length > 0) return model;
|
||||
return sortedAvailableModels(snapshot.availableModels).find((model) => model.isDefault)?.model ?? null;
|
||||
}
|
||||
|
||||
function configuredReasoningEffort(snapshot: RuntimeSnapshot, config: Record<string, unknown>): string | null {
|
||||
if (typeof config.model_reasoning_effort === "string" && config.model_reasoning_effort.length > 0) {
|
||||
return config.model_reasoning_effort;
|
||||
const effort = resolvedConfigValue(config, "model_reasoning_effort");
|
||||
if (typeof effort === "string" && effort.length > 0) {
|
||||
return effort;
|
||||
}
|
||||
const model = configuredModel(snapshot, config);
|
||||
return (
|
||||
|
|
@ -184,6 +199,10 @@ function modeLabel(mode: RuntimeSnapshot["requestedCollaborationMode"]): string
|
|||
return mode === "plan" ? "Plan" : "Default";
|
||||
}
|
||||
|
||||
function activeRuntimeValueLabel(value: string | null, override: boolean): string {
|
||||
return `${value ?? "(not reported)"}${override ? " (override)" : ""}`;
|
||||
}
|
||||
|
||||
function rateLimitWindowSummary(
|
||||
fallbackLabel: string,
|
||||
window: RateLimitWindow | null,
|
||||
|
|
|
|||
|
|
@ -377,6 +377,19 @@ describe("AppServerClient", () => {
|
|||
await update;
|
||||
});
|
||||
|
||||
it("requests effective config with raw layers for display fallbacks", async () => {
|
||||
const { client, transport } = await connectedClient();
|
||||
|
||||
const reading = client.readEffectiveConfig("/vault");
|
||||
expect(transport.sent[2]).toMatchObject({
|
||||
id: 2,
|
||||
method: "config/read",
|
||||
params: { cwd: "/vault", includeLayers: true },
|
||||
});
|
||||
transport.emitLine({ id: 2, result: { config: {}, origins: {}, layers: [] } });
|
||||
await reading;
|
||||
});
|
||||
|
||||
it("sends model list request payloads", async () => {
|
||||
const { client, transport } = await connectedClient();
|
||||
|
||||
|
|
|
|||
|
|
@ -1029,6 +1029,41 @@ describe("PanelController", () => {
|
|||
expect(state.displayItems).toEqual([]);
|
||||
});
|
||||
|
||||
it("preserves explicit standard service tier when settings notifications report null", () => {
|
||||
const state = createPanelState();
|
||||
state.activeThreadId = "thread-active";
|
||||
state.activeServiceTier = "standard";
|
||||
state.activeServiceTierOverride = true;
|
||||
const controller = controllerForState(state);
|
||||
|
||||
controller.handleNotification({
|
||||
method: "thread/settings/updated",
|
||||
params: {
|
||||
threadId: "thread-active",
|
||||
threadSettings: {
|
||||
cwd: "/workspace/active",
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "user",
|
||||
sandboxPolicy: { type: "readOnly", networkAccess: false },
|
||||
activePermissionProfile: null,
|
||||
model: "gpt-5.5",
|
||||
modelProvider: "openai",
|
||||
serviceTier: null,
|
||||
effort: "high",
|
||||
summary: null,
|
||||
collaborationMode: {
|
||||
mode: "default",
|
||||
settings: { model: "gpt-5.5", reasoning_effort: "high", developer_instructions: null },
|
||||
},
|
||||
personality: null,
|
||||
},
|
||||
},
|
||||
} satisfies Extract<ServerNotification, { method: "thread/settings/updated" }>);
|
||||
|
||||
expect(state.activeServiceTier).toBe("standard");
|
||||
expect(state.activeServiceTierOverride).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps goal notifications out of the message stream", () => {
|
||||
const state = createPanelState();
|
||||
state.activeThreadId = "thread-active";
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "../../src/runtime/settings";
|
||||
import {
|
||||
autoReviewActive,
|
||||
configRecord,
|
||||
currentApprovalsReviewer,
|
||||
currentModel,
|
||||
currentReasoningEffort,
|
||||
|
|
@ -119,6 +120,128 @@ describe("runtime settings", () => {
|
|||
).toBe("guardian_subagent");
|
||||
});
|
||||
|
||||
it("lets config auto-review beat an implicit initial user reviewer", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
activeApprovalsReviewer: "user",
|
||||
activeApprovalsReviewerOverride: false,
|
||||
effectiveConfig: { config: { approvals_reviewer: "auto_review" } } as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
});
|
||||
|
||||
expect(currentApprovalsReviewer(snapshot)).toBe("auto_review");
|
||||
expect(autoReviewActive(snapshot)).toBe(true);
|
||||
});
|
||||
|
||||
it("uses raw config layers when typed config omits approval reviewer", () => {
|
||||
const config = configRecord({
|
||||
config: {
|
||||
approvals_reviewer: null,
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
name: { type: "user", file: "/home/me/.codex/config.toml", profile: null },
|
||||
version: "1",
|
||||
config: { approvals_reviewer: "auto_review" },
|
||||
disabledReason: null,
|
||||
},
|
||||
],
|
||||
} as unknown as RuntimeSnapshot["effectiveConfig"]);
|
||||
const snapshot = runtimeSnapshot({
|
||||
activeApprovalsReviewer: "user",
|
||||
activeApprovalsReviewerOverride: false,
|
||||
effectiveConfig: { config } as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
});
|
||||
|
||||
expect(config.approvals_reviewer).toBe("auto_review");
|
||||
expect(autoReviewActive(snapshot, config)).toBe(true);
|
||||
});
|
||||
|
||||
it("treats guardian subagent reviewer as active auto-review", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
activeApprovalsReviewer: "guardian_subagent",
|
||||
activeApprovalsReviewerOverride: false,
|
||||
});
|
||||
|
||||
expect(currentApprovalsReviewer(snapshot)).toBe("guardian_subagent");
|
||||
expect(autoReviewActive(snapshot)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps an explicit user reviewer override above config auto-review", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
activeApprovalsReviewer: "user",
|
||||
activeApprovalsReviewerOverride: true,
|
||||
effectiveConfig: { config: { approvals_reviewer: "auto_review" } } as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
});
|
||||
|
||||
expect(currentApprovalsReviewer(snapshot)).toBe("user");
|
||||
expect(autoReviewActive(snapshot)).toBe(false);
|
||||
});
|
||||
|
||||
it("resolves approval reviewer from the selected config profile", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
effectiveConfig: {
|
||||
config: {
|
||||
approvals_reviewer: "user",
|
||||
profile: "auto",
|
||||
profiles: {
|
||||
auto: { approvals_reviewer: "auto_review" },
|
||||
},
|
||||
},
|
||||
} as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
});
|
||||
|
||||
expect(currentApprovalsReviewer(snapshot)).toBe("auto_review");
|
||||
expect(autoReviewActive(snapshot)).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves model, effort, and fast mode from the selected config profile", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
effectiveConfig: {
|
||||
config: {
|
||||
model: "gpt-root",
|
||||
model_reasoning_effort: "low",
|
||||
service_tier: "standard",
|
||||
profile: "fast-profile",
|
||||
profiles: {
|
||||
"fast-profile": {
|
||||
model: "gpt-profile",
|
||||
model_reasoning_effort: "high",
|
||||
service_tier: "fast",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
});
|
||||
|
||||
expect(currentModel(snapshot)).toBe("gpt-profile");
|
||||
expect(currentReasoningEffort(snapshot)).toBe("high");
|
||||
expect(currentServiceTier(snapshot)).toBe("fast");
|
||||
expect(serviceTierLabel(snapshot)).toBe("fast");
|
||||
expect(fastModeLabel(snapshot)).toBe("on");
|
||||
expect(requestedOrConfiguredServiceTier(snapshot)).toBe("fast");
|
||||
});
|
||||
|
||||
it("lets config fast mode beat an implicit initial standard service tier", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
activeServiceTier: "standard",
|
||||
activeServiceTierOverride: false,
|
||||
effectiveConfig: { config: { service_tier: "fast" } } as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
});
|
||||
|
||||
expect(currentServiceTier(snapshot)).toBe("fast");
|
||||
expect(fastModeLabel(snapshot)).toBe("on");
|
||||
});
|
||||
|
||||
it("keeps an explicit standard service tier override above config fast mode", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
activeServiceTier: "standard",
|
||||
activeServiceTierOverride: true,
|
||||
effectiveConfig: { config: { service_tier: "fast" } } as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
});
|
||||
|
||||
expect(currentServiceTier(snapshot)).toBe("standard");
|
||||
expect(fastModeLabel(snapshot)).toBe("off");
|
||||
});
|
||||
|
||||
it("resolves requested approval reviewer without adding it to turn runtime settings", () => {
|
||||
const snapshot = runtimeSnapshot({ requestedApprovalsReviewer: "auto_review" });
|
||||
|
||||
|
|
@ -170,6 +293,8 @@ describe("runtime settings", () => {
|
|||
"effort change": "(none)",
|
||||
mode: "Plan",
|
||||
"mode change": "(none)",
|
||||
"config service tier": "standard",
|
||||
"active service tier": "(not reported)",
|
||||
});
|
||||
|
||||
const resetSections = effectiveConfigSections(
|
||||
|
|
@ -184,6 +309,90 @@ describe("runtime settings", () => {
|
|||
expect(resetRuntimeRows["effort change"]).toBe("(reset to config)");
|
||||
});
|
||||
|
||||
it("shows selected profile runtime and policy values in status details", () => {
|
||||
const sections = effectiveConfigSections(
|
||||
runtimeSnapshot({
|
||||
effectiveConfig: {
|
||||
config: {
|
||||
model: "gpt-root",
|
||||
model_provider: "root-provider",
|
||||
model_reasoning_effort: "low",
|
||||
model_reasoning_summary: "auto",
|
||||
model_verbosity: "low",
|
||||
approval_policy: "on-request",
|
||||
web_search: "disabled",
|
||||
service_tier: "standard",
|
||||
profile: "auto",
|
||||
profiles: {
|
||||
auto: {
|
||||
model: "gpt-profile",
|
||||
model_provider: "profile-provider",
|
||||
model_reasoning_effort: "high",
|
||||
model_reasoning_summary: "detailed",
|
||||
model_verbosity: "high",
|
||||
approval_policy: "never",
|
||||
approvals_reviewer: "auto_review",
|
||||
web_search: "live",
|
||||
service_tier: "fast",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
}),
|
||||
"/vault",
|
||||
);
|
||||
const scopeRows = Object.fromEntries(
|
||||
sections.find((section) => section.title === "Scope")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
const runtimeRows = Object.fromEntries(
|
||||
sections.find((section) => section.title === "Runtime")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
const policyRows = Object.fromEntries(
|
||||
sections.find((section) => section.title === "Policy")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
|
||||
expect(scopeRows["model provider"]).toBe("profile-provider");
|
||||
expect(runtimeRows).toMatchObject({
|
||||
model: "gpt-profile",
|
||||
"config model": "gpt-profile",
|
||||
effort: "high",
|
||||
"config effort": "high",
|
||||
"reasoning summary": "detailed",
|
||||
verbosity: "high",
|
||||
"service tier": "fast",
|
||||
"config service tier": "fast",
|
||||
"active service tier": "(not reported)",
|
||||
"fast mode": "on",
|
||||
});
|
||||
expect(policyRows.approval).toBe("never");
|
||||
expect(policyRows.reviewer).toBe("auto_review");
|
||||
expect(policyRows["auto-review"]).toBe("on");
|
||||
expect(policyRows["config reviewer"]).toBe("auto_review");
|
||||
expect(policyRows["active reviewer"]).toBe("(not reported)");
|
||||
expect(policyRows["web search"]).toBe("live");
|
||||
});
|
||||
|
||||
it("shows active and configured reviewer inputs in status details", () => {
|
||||
const sections = effectiveConfigSections(
|
||||
runtimeSnapshot({
|
||||
activeApprovalsReviewer: "guardian_subagent",
|
||||
activeApprovalsReviewerOverride: false,
|
||||
effectiveConfig: { config: { approvals_reviewer: "auto_review" } } as unknown as RuntimeSnapshot["effectiveConfig"],
|
||||
}),
|
||||
"/vault",
|
||||
);
|
||||
const policyRows = Object.fromEntries(
|
||||
sections.find((section) => section.title === "Policy")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
|
||||
expect(policyRows).toMatchObject({
|
||||
reviewer: "guardian_subagent",
|
||||
"auto-review": "on",
|
||||
"config reviewer": "auto_review",
|
||||
"active reviewer": "guardian_subagent",
|
||||
});
|
||||
});
|
||||
|
||||
it("summarizes service tier and context meter state from one runtime snapshot", () => {
|
||||
const snapshot = runtimeSnapshot({ requestedServiceTier: "fast", activeThreadId: "thread" });
|
||||
|
||||
|
|
@ -324,7 +533,9 @@ function runtimeSnapshot(overrides: Partial<RuntimeSnapshot> = {}): RuntimeSnaps
|
|||
activeReasoningEffort: null,
|
||||
activeCollaborationMode: "default",
|
||||
activeServiceTier: null,
|
||||
activeServiceTierOverride: false,
|
||||
activeApprovalsReviewer: null,
|
||||
activeApprovalsReviewerOverride: false,
|
||||
requestedModel: { kind: "default" },
|
||||
requestedReasoningEffort: { kind: "default" },
|
||||
requestedApprovalsReviewer: null,
|
||||
|
|
|
|||
Loading…
Reference in a new issue