mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Simplify toolbar runtime debug details
This commit is contained in:
parent
87cac9ee85
commit
bf4d3cc9e1
16 changed files with 92 additions and 517 deletions
|
|
@ -83,7 +83,7 @@ By default, `Enter` sends and `Shift+Enter` inserts a newline. You can switch se
|
|||
- Inspect context usage from the composer status row.
|
||||
- Toggle Plan mode, fast mode, and approval auto-review for subsequent turns.
|
||||
- Set the model and reasoning effort for subsequent turns when supported by Codex.
|
||||
- Inspect usage limits, connection diagnostics, MCP servers, enabled skills, effective Codex config, and discovered hooks from the toolbar, settings, or slash commands.
|
||||
- Inspect usage limits, connection diagnostics, MCP servers, enabled skills, runtime debug details, and discovered hooks from the toolbar, settings, or slash commands.
|
||||
|
||||
### Obsidian-native workflows
|
||||
|
||||
|
|
|
|||
|
|
@ -58,10 +58,6 @@ export function supportedEffortsForModelMetadata(model: ModelMetadata | null): R
|
|||
);
|
||||
}
|
||||
|
||||
export function defaultEffortForModelMetadata(model: ModelMetadata | null): ReasoningEffort | null {
|
||||
return normalizeReasoningEffort(model?.defaultReasoningEffort);
|
||||
}
|
||||
|
||||
export function sortedModelMetadata(models: readonly ModelMetadata[]): ModelMetadata[] {
|
||||
return [...models]
|
||||
.filter((model) => !model.hidden)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { cloneRuntimeConfigSnapshot, emptyRuntimeConfigSnapshot, type RuntimeConfigSnapshot } from "../../../../domain/runtime/config";
|
||||
import { findModelMetadataByIdOrName, type ModelMetadata } from "../../../../domain/catalog/metadata";
|
||||
import type { ApprovalPolicy, ApprovalsReviewer } from "../../../../domain/runtime/policy";
|
||||
import type { ApprovalsReviewer } from "../../../../domain/runtime/policy";
|
||||
import { supportedEffortsForModelMetadata, type ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import type { RuntimeSnapshot } from "./snapshot";
|
||||
|
||||
|
|
@ -34,10 +34,6 @@ export function currentApprovalsReviewer(snapshot: RuntimeSnapshot, config: Runt
|
|||
return snapshot.activeApprovalsReviewer ?? config.approvalsReviewer;
|
||||
}
|
||||
|
||||
export function currentApprovalPolicy(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): ApprovalPolicy | null {
|
||||
return snapshot.activeApprovalPolicy ?? config.approvalPolicy;
|
||||
}
|
||||
|
||||
export function autoReviewActive(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): boolean {
|
||||
return isAutoReviewReviewer(currentApprovalsReviewer(snapshot, config));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { h } from "preact";
|
|||
|
||||
import type { Thread } from "../../../../domain/threads/model";
|
||||
import { getThreadTitle } from "../../../../domain/threads/model";
|
||||
import { runtimeConfigSections, rateLimitSummary } from "../../presentation/runtime/status";
|
||||
import { rateLimitSummary } from "../../presentation/runtime/status";
|
||||
import { connectionDiagnosticSections } from "../../application/connection/diagnostics-display";
|
||||
import type { RuntimeSnapshot } from "../../application/runtime/snapshot";
|
||||
import { chatTurnBusy, type ChatState } from "../../application/state/root-reducer";
|
||||
|
|
@ -12,7 +12,7 @@ import { Toolbar, type ToolbarActions, type ToolbarThreadRow, type ToolbarViewMo
|
|||
import type { ChatPanelToolbarSurface } from "./model";
|
||||
import { runtimeSnapshotForToolbarShellState } from "./runtime-snapshot";
|
||||
|
||||
type ToolbarState = Pick<ChatState, "connection" | "threadList" | "activeThread" | "ui">;
|
||||
type ToolbarState = Pick<ChatState, "connection" | "threadList" | "activeThread" | "runtime" | "ui">;
|
||||
|
||||
interface ToolbarViewModelInput {
|
||||
state: ToolbarState;
|
||||
|
|
@ -68,7 +68,7 @@ function chatPanelToolbarProjection(input: ToolbarViewModelInput): ToolbarViewMo
|
|||
historyOpen: projection.historyOpen,
|
||||
statusPanelOpen: projection.statusPanelOpen,
|
||||
rateLimit: limit,
|
||||
configSections: runtimeConfigSections(snapshot, input.vaultPath),
|
||||
debugDetails: runtimeDebugDetails(input),
|
||||
openPanel: projection.openPanel,
|
||||
threads: projection.threads,
|
||||
connectLabel: input.connected ? "Reconnect" : "Connect",
|
||||
|
|
@ -80,6 +80,20 @@ function chatPanelToolbarProjection(input: ToolbarViewModelInput): ToolbarViewMo
|
|||
};
|
||||
}
|
||||
|
||||
function runtimeDebugDetails(input: ToolbarViewModelInput): string {
|
||||
return JSON.stringify(
|
||||
{
|
||||
vaultPath: input.vaultPath,
|
||||
configuredCommand: input.configuredCommand,
|
||||
runtimeConfig: input.state.connection.runtimeConfig,
|
||||
runtime: input.state.runtime,
|
||||
availableModels: input.state.connection.availableModels,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
function toolbarStateProjection(input: Pick<ToolbarViewModelInput, "state" | "turnBusy" | "archiveExportEnabled">): ToolbarStateProjection {
|
||||
const historyOpen = input.state.ui.toolbarPanel === "history";
|
||||
const chatActionsOpen = input.state.ui.toolbarPanel === "chat-actions";
|
||||
|
|
|
|||
|
|
@ -22,9 +22,3 @@ export function pendingRuntimeSettingLabel(
|
|||
export function serviceTierLabel(value: string | null): string {
|
||||
return value ?? "(Codex default)";
|
||||
}
|
||||
|
||||
export function fastModeLabel(input: { requestedOff: boolean; active: boolean; serviceTier: string | null }): string {
|
||||
if (input.requestedOff) return "off";
|
||||
if (input.active) return "on";
|
||||
return input.serviceTier ? "off" : "Codex default";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,15 @@
|
|||
import type { RuntimeConfigSnapshot } from "../../../../domain/runtime/config";
|
||||
import type { RateLimitWindow, SpendControlLimitSnapshot, ThreadTokenUsage } from "../../../../domain/runtime/metrics";
|
||||
import { jsonPreview } from "../../../../utils";
|
||||
import { sortedModelMetadata } from "../../../../domain/catalog/metadata";
|
||||
import { defaultEffortForModelMetadata } from "../../../../domain/catalog/metadata";
|
||||
import {
|
||||
currentApprovalsReviewer,
|
||||
currentApprovalPolicy,
|
||||
currentServiceTier,
|
||||
currentModel,
|
||||
currentReasoningEffort,
|
||||
autoReviewActive,
|
||||
fastModeActive,
|
||||
runtimeConfigOrDefault,
|
||||
supportedReasoningEfforts,
|
||||
} from "../../domain/runtime/effective";
|
||||
import type { RuntimeSnapshot } from "../../domain/runtime/snapshot";
|
||||
import { effectiveCollaborationMode } from "../../domain/runtime/pending-settings";
|
||||
import {
|
||||
collaborationModeLabel,
|
||||
fastModeLabel as formatFastModeLabel,
|
||||
pendingRuntimeSettingLabel,
|
||||
serviceTierLabel as formatServiceTierLabel,
|
||||
} from "./messages";
|
||||
import { pendingRuntimeSettingLabel, serviceTierLabel as formatServiceTierLabel } from "./messages";
|
||||
|
||||
export interface ContextSummary {
|
||||
label: string;
|
||||
|
|
@ -46,11 +34,6 @@ interface RateLimitSummaryRow {
|
|||
level: "ok" | "warn" | "danger";
|
||||
}
|
||||
|
||||
export interface RuntimeConfigSection {
|
||||
title: string;
|
||||
rows: { key: string; value: string }[];
|
||||
}
|
||||
|
||||
export interface StatusSummaryLinesInput {
|
||||
activeThreadId: RuntimeSnapshot["activeThreadId"];
|
||||
snapshot: RuntimeSnapshot;
|
||||
|
|
@ -71,20 +54,11 @@ export interface EffortStatusLinesInput {
|
|||
}
|
||||
|
||||
const CODEX_DEFAULT_LABEL = "(Codex default)";
|
||||
const NOT_REPORTED_LABEL = "(not reported)";
|
||||
|
||||
function serviceTierLabel(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): string {
|
||||
return formatServiceTierLabel(currentServiceTier(snapshot, config));
|
||||
}
|
||||
|
||||
function fastModeLabel(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): string {
|
||||
return formatFastModeLabel({
|
||||
requestedOff: snapshot.requestedServiceTier.kind === "set" && snapshot.requestedServiceTier.value === "off",
|
||||
active: fastModeActive(snapshot, config),
|
||||
serviceTier: currentServiceTier(snapshot, config),
|
||||
});
|
||||
}
|
||||
|
||||
export function contextSummary(snapshot: RuntimeSnapshot): ContextSummary | null {
|
||||
const usage = snapshot.tokenUsage;
|
||||
const config = runtimeConfigOrDefault(snapshot.runtimeConfig);
|
||||
|
|
@ -146,75 +120,6 @@ export function rateLimitSummary(snapshot: RuntimeSnapshot, nowMs: number): Rate
|
|||
};
|
||||
}
|
||||
|
||||
export function runtimeConfigSections(snapshot: RuntimeSnapshot, vaultPath: string): RuntimeConfigSection[] {
|
||||
const config = runtimeConfigOrDefault(snapshot.runtimeConfig);
|
||||
return [
|
||||
{
|
||||
title: "Scope",
|
||||
rows: [
|
||||
{ key: "cwd", value: vaultPath },
|
||||
{ key: "profile", value: config.profile ?? "(default)" },
|
||||
{ key: "model provider", value: stringValue(config.modelProvider, CODEX_DEFAULT_LABEL) },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Runtime",
|
||||
rows: [
|
||||
{ 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: 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: 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: activeCollaborationModeLabel(snapshot.activeCollaborationMode) },
|
||||
{
|
||||
key: "requested mode",
|
||||
value:
|
||||
snapshot.selectedCollaborationMode === effectiveCollaborationMode(snapshot.activeCollaborationMode)
|
||||
? "(none)"
|
||||
: collaborationModeLabel(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: 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) },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Policy",
|
||||
rows: [
|
||||
{ key: "effective approval", value: stringValue(currentApprovalPolicy(snapshot, config), CODEX_DEFAULT_LABEL) },
|
||||
{ key: "configured approval", value: stringValue(config.approvalPolicy, CODEX_DEFAULT_LABEL) },
|
||||
{ key: "thread approval", value: stringValue(snapshot.activeApprovalPolicy, NOT_REPORTED_LABEL) },
|
||||
{ key: "active permissions", value: activePermissionProfileLabel(snapshot.activePermissionProfile) },
|
||||
{ key: "effective reviewer", value: currentApprovalsReviewer(snapshot, config) ?? NOT_REPORTED_LABEL },
|
||||
{ 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: 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) },
|
||||
{ key: "web search", value: stringValue(config.webSearch, CODEX_DEFAULT_LABEL) },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Features",
|
||||
rows: [
|
||||
{ key: "tool web search", value: stringValue(config.rawToolWebSearch, CODEX_DEFAULT_LABEL) },
|
||||
{ key: "apps", value: enabledAppsLabel(config.rawApps) },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function statusSummaryLines(input: StatusSummaryLinesInput): string[] {
|
||||
const context = contextSummary(input.snapshot);
|
||||
const limit = rateLimitSummary(input.snapshot, input.nowMs);
|
||||
|
|
@ -251,32 +156,6 @@ function contextUsageTokens(usage: ThreadTokenUsage): number {
|
|||
return usage.last.inputTokens > 0 ? usage.last.inputTokens : usage.last.totalTokens;
|
||||
}
|
||||
|
||||
function configuredModel(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): string | null {
|
||||
if (config.model) return config.model;
|
||||
return sortedModelMetadata(snapshot.availableModels).find((model) => model.isDefault)?.model ?? null;
|
||||
}
|
||||
|
||||
function configuredReasoningEffort(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): string | null {
|
||||
if (config.rawReasoningEffort) return config.rawReasoningEffort;
|
||||
const model = configuredModel(snapshot, config);
|
||||
return defaultEffortForModelMetadata(
|
||||
sortedModelMetadata(snapshot.availableModels).find((availableModel) => availableModel.model === model) ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
function activeRuntimeValueLabel(value: string | null): string {
|
||||
return value ?? NOT_REPORTED_LABEL;
|
||||
}
|
||||
|
||||
function activeCollaborationModeLabel(value: RuntimeSnapshot["activeCollaborationMode"]): string {
|
||||
return value ? collaborationModeLabel(value) : NOT_REPORTED_LABEL;
|
||||
}
|
||||
|
||||
function activePermissionProfileLabel(profile: RuntimeSnapshot["activePermissionProfile"]): string {
|
||||
if (!profile) return NOT_REPORTED_LABEL;
|
||||
return profile.extends ? `${profile.id} extends ${profile.extends}` : profile.id;
|
||||
}
|
||||
|
||||
function usageLimitStatusLines(limit: RateLimitSummary): string[] {
|
||||
return ["Usage limits", ...limit.rows.map((row) => `${row.label}: ${row.value}${row.resetLabel ? ` (${row.resetLabel})` : ""}`)];
|
||||
}
|
||||
|
|
@ -365,33 +244,6 @@ function capitalize(value: string): string {
|
|||
return value.length === 0 ? value : value.charAt(0).toUpperCase() + value.slice(1);
|
||||
}
|
||||
|
||||
function tokenLimitLabel(value: unknown): string {
|
||||
const tokens = toNumber(value);
|
||||
return tokens === null ? CODEX_DEFAULT_LABEL : formatTokenCount(tokens);
|
||||
}
|
||||
|
||||
function writableRootsLabel(value: unknown): string {
|
||||
if (!Array.isArray(value)) return CODEX_DEFAULT_LABEL;
|
||||
if (value.length === 0) return "none";
|
||||
if (value.length === 1) return String(value[0]);
|
||||
return `${String(value.length)} roots`;
|
||||
}
|
||||
|
||||
function enabledAppsLabel(apps: unknown): string {
|
||||
const appConfig = asRecord(apps);
|
||||
const enabled = Object.entries(appConfig)
|
||||
.filter(([key, app]) => key !== "_default" && asRecord(app)["enabled"] === true)
|
||||
.map(([key]) => key)
|
||||
.sort();
|
||||
if (enabled.length > 0) return enabled.join(", ");
|
||||
const defaultConfig = asRecord(appConfig["_default"]);
|
||||
return stringValue(defaultConfig["enabled"], CODEX_DEFAULT_LABEL);
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
function stringValue(value: unknown, fallback = ""): string {
|
||||
if (typeof value === "string") return value;
|
||||
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
||||
|
|
@ -399,12 +251,6 @@ function stringValue(value: unknown, fallback = ""): string {
|
|||
return jsonPreview(value);
|
||||
}
|
||||
|
||||
function toNumber(value: unknown): number | null {
|
||||
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
||||
if (typeof value === "bigint") return Number(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatTokenCount(value: number): string {
|
||||
return new Intl.NumberFormat("en-US").format(Math.round(value));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ButtonHTMLAttributes, ComponentChild as UiNode, TargetedKeyboardEvent } from "preact";
|
||||
import { useLayoutEffect, useRef } from "preact/hooks";
|
||||
|
||||
import type { RuntimeConfigSection, RateLimitSummary } from "../presentation/runtime/status";
|
||||
import type { RateLimitSummary } from "../presentation/runtime/status";
|
||||
import { IconButton } from "../../../shared/ui/components";
|
||||
|
||||
type ButtonProps = ButtonHTMLAttributes & {
|
||||
|
|
@ -38,7 +38,7 @@ export interface ToolbarViewModel {
|
|||
historyOpen: boolean;
|
||||
statusPanelOpen: boolean;
|
||||
rateLimit: RateLimitSummary | null;
|
||||
configSections: RuntimeConfigSection[];
|
||||
debugDetails: string;
|
||||
openPanel: "history" | "chat-actions" | "status" | null;
|
||||
threads: ToolbarThreadRow[];
|
||||
connectLabel: string;
|
||||
|
|
@ -116,7 +116,7 @@ function StatusButton({ model, actions }: { model: ToolbarViewModel; actions: To
|
|||
return (
|
||||
<ToolbarIconButton
|
||||
icon="waypoints"
|
||||
label={model.statusPanelOpen ? "Hide Codex status and settings" : "Show Codex status and settings"}
|
||||
label={model.statusPanelOpen ? "Hide status" : "Show status"}
|
||||
className={["codex-panel__status-menu-toggle", model.statusPanelOpen ? "is-active" : ""].filter(Boolean).join(" ")}
|
||||
aria-expanded={model.statusPanelOpen ? "true" : "false"}
|
||||
onClick={actions.toggleStatusPanel}
|
||||
|
|
@ -170,7 +170,7 @@ function StatusPanel({ model, actions }: { model: ToolbarViewModel; actions: Too
|
|||
</div>
|
||||
<RateLimitPanel rateLimit={model.rateLimit} />
|
||||
<ConnectionDiagnostics sections={model.diagnostics} />
|
||||
<RuntimeConfigPanel sections={model.configSections} />
|
||||
<DebugDetails details={model.debugDetails} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -243,30 +243,12 @@ function DiagnosticSection({ section }: { section: ToolbarDiagnosticSection }):
|
|||
);
|
||||
}
|
||||
|
||||
function RuntimeConfigPanel({ sections }: { sections: RuntimeConfigSection[] }): UiNode {
|
||||
function DebugDetails({ details }: { details: string }): UiNode {
|
||||
return (
|
||||
<div className="codex-panel__config">
|
||||
<div className="codex-panel__config-title">Effective Codex config</div>
|
||||
<dl className="codex-panel__config-list">
|
||||
{sections.map((section) => (
|
||||
<FragmentedConfigSection key={section.title} section={section} />
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FragmentedConfigSection({ section }: { section: RuntimeConfigSection }): UiNode {
|
||||
return (
|
||||
<>
|
||||
<div className="codex-panel__config-section">{section.title}</div>
|
||||
{section.rows.map((row) => (
|
||||
<div key={`${row.key}\n${row.value}`} className="codex-panel__config-row">
|
||||
<dt>{row.key}</dt>
|
||||
<dd>{row.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
<details className="codex-panel__debug-details">
|
||||
<summary className="codex-panel__debug-details-summary">Debug details</summary>
|
||||
<pre className="codex-panel__debug-details-content">{details}</pre>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export interface ThreadPickerHost {
|
|||
openThreadInAvailableView(threadId: string): Promise<void>;
|
||||
}
|
||||
|
||||
export type ThreadPickerCatalog = Pick<SharedThreadCatalog, "cachedThreads" | "refreshThreads">;
|
||||
type ThreadPickerCatalog = Pick<SharedThreadCatalog, "cachedThreads" | "refreshThreads">;
|
||||
|
||||
interface ThreadSuggestion {
|
||||
thread: Thread;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export interface CodexThreadsHost {
|
|||
getOpenPanelSnapshots(): OpenCodexPanelSnapshot[];
|
||||
}
|
||||
|
||||
export type ThreadsThreadCatalog = Pick<
|
||||
type ThreadsThreadCatalog = Pick<
|
||||
SharedThreadCatalog,
|
||||
"archiveThreadInCatalog" | "renameThreadInCatalog" | "refreshFromOpenSurface" | "refreshThreads" | "cachedThreads"
|
||||
>;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export interface SettingsDynamicDataHost {
|
|||
threadCatalog: SettingsThreadCatalog;
|
||||
}
|
||||
|
||||
export type SettingsThreadCatalog = Pick<SharedThreadCatalog, "refreshFromOpenSurface" | "cachedModels" | "publishModels">;
|
||||
type SettingsThreadCatalog = Pick<SharedThreadCatalog, "refreshFromOpenSurface" | "cachedModels" | "publishModels">;
|
||||
|
||||
interface SettingsDynamicDataControllerCallbacks {
|
||||
display(): void;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
.codex-panel__limit-panel {
|
||||
margin-top: var(--codex-panel-panel-gap);
|
||||
padding-top: var(--codex-panel-item-gap);
|
||||
border-top: var(--codex-panel-divider-muted);
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-title {
|
||||
|
|
@ -148,7 +147,6 @@
|
|||
.codex-panel__connection-diagnostics {
|
||||
margin-top: var(--codex-panel-panel-gap);
|
||||
padding-top: var(--codex-panel-item-gap);
|
||||
border-top: var(--codex-panel-divider-muted);
|
||||
}
|
||||
|
||||
.codex-panel__connection-diagnostics-title {
|
||||
|
|
|
|||
|
|
@ -28,60 +28,36 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel .codex-panel__config {
|
||||
.codex-panel__debug-details {
|
||||
margin-top: var(--codex-panel-panel-gap);
|
||||
padding: var(--codex-panel-item-gap) 0 0;
|
||||
border-top: var(--codex-panel-divider-muted);
|
||||
color: var(--codex-panel-text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel .codex-panel__config-title {
|
||||
.codex-panel__debug-details-summary {
|
||||
padding: var(--codex-panel-section-label-padding);
|
||||
color: var(--codex-panel-section-label-color);
|
||||
font-size: var(--codex-panel-section-label-size);
|
||||
font-weight: var(--codex-panel-section-label-weight);
|
||||
line-height: var(--codex-panel-section-label-line-height);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel .codex-panel__config-list {
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
.codex-panel__debug-details-summary:hover {
|
||||
color: var(--nav-item-color-hover, var(--text-normal));
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel .codex-panel__config-section {
|
||||
padding: var(--codex-panel-section-label-block-padding);
|
||||
border-top: 0;
|
||||
color: var(--codex-panel-section-label-color);
|
||||
font-size: var(--codex-panel-section-label-size);
|
||||
font-weight: var(--codex-panel-section-label-weight);
|
||||
line-height: var(--codex-panel-section-label-line-height);
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel .codex-panel__config-section:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel .codex-panel__config-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(96px, 36%) minmax(0, 1fr);
|
||||
gap: var(--codex-panel-section-gap);
|
||||
padding: var(--codex-panel-control-gap) var(--codex-panel-section-gap);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel .codex-panel__config-row dt,
|
||||
.codex-panel__toolbar-panel .codex-panel__config-row dd {
|
||||
.codex-panel__debug-details-content {
|
||||
margin: 0 var(--codex-panel-section-gap) var(--codex-panel-control-gap);
|
||||
overflow: auto;
|
||||
color: var(--codex-panel-text-muted);
|
||||
font-size: var(--font-ui-smaller);
|
||||
line-height: var(--codex-panel-compact-line-height);
|
||||
line-height: var(--line-height-tight);
|
||||
white-space: pre;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.codex-panel__config-title,
|
||||
.codex-panel__approval-details summary,
|
||||
.codex-panel__output summary,
|
||||
.codex-panel-diff-file summary,
|
||||
|
|
@ -102,56 +78,6 @@
|
|||
color: var(--nav-item-color-hover, var(--text-normal));
|
||||
}
|
||||
|
||||
.codex-panel__config-list {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.codex-panel__config-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(82px, 34%) minmax(0, 1fr);
|
||||
gap: var(--codex-panel-section-gap);
|
||||
align-items: start;
|
||||
padding: var(--codex-panel-control-gap) var(--codex-panel-section-gap);
|
||||
border-top: var(--codex-panel-divider-muted);
|
||||
}
|
||||
|
||||
.codex-panel__config-row:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.codex-panel__config-section {
|
||||
padding: var(--codex-panel-section-label-block-padding);
|
||||
border-top: var(--codex-panel-divider-muted);
|
||||
color: var(--codex-panel-section-label-color);
|
||||
font-size: var(--codex-panel-section-label-size);
|
||||
font-weight: var(--codex-panel-section-label-weight);
|
||||
line-height: var(--codex-panel-section-label-line-height);
|
||||
}
|
||||
|
||||
.codex-panel__config-section:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.codex-panel__config-row dt,
|
||||
.codex-panel__config-row dd {
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
font-size: var(--font-ui-smaller);
|
||||
line-height: var(--codex-panel-compact-line-height);
|
||||
}
|
||||
|
||||
.codex-panel__config-row dt {
|
||||
color: var(--codex-panel-text-faint);
|
||||
}
|
||||
|
||||
.codex-panel__config-row dd {
|
||||
color: var(--codex-panel-text-muted);
|
||||
overflow-wrap: anywhere;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.codex-panel__meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,27 @@ describe("chat panel surface projections", () => {
|
|||
unmountUiRoot(parent);
|
||||
});
|
||||
|
||||
it("renders raw runtime debug details from toolbar state sources", () => {
|
||||
const state = createChatState();
|
||||
state.ui.toolbarPanel = "status-panel";
|
||||
state.connection.runtimeConfig = runtimeConfigFixture({ model: "gpt-debug", approval_policy: "on-request" });
|
||||
state.connection.availableModels = [modelFixture("gpt-debug")];
|
||||
state.runtime.requestedModel = { kind: "set", value: "gpt-debug" };
|
||||
|
||||
const parent = renderWithShellState(state, h(ChatPanelToolbar, { surface: toolbarSurfaceFixture(), actions: toolbarActionsFixture() }));
|
||||
|
||||
const debugContent = parent.querySelector(".codex-panel__debug-details-content")?.textContent;
|
||||
if (!debugContent) throw new Error("Expected toolbar debug details");
|
||||
const debugDetails = JSON.parse(debugContent) as Record<string, unknown>;
|
||||
|
||||
expect(debugDetails["vaultPath"]).toBe("/vault");
|
||||
expect(debugDetails["configuredCommand"]).toBe("codex");
|
||||
expect(debugDetails["runtimeConfig"]).toMatchObject({ model: "gpt-debug", approvalPolicy: "on-request" });
|
||||
expect(debugDetails["runtime"]).toMatchObject({ requestedModel: { kind: "set", value: "gpt-debug" } });
|
||||
expect(debugDetails["availableModels"]).toMatchObject([{ model: "gpt-debug" }]);
|
||||
unmountUiRoot(parent);
|
||||
});
|
||||
|
||||
it("builds composer meta from context and runtime state", () => {
|
||||
const state = createChatState();
|
||||
state.activeThread.id = "thread-1";
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ describe("Toolbar decisions", () => {
|
|||
expect([...expectPresent(navButtons).children].map((button) => button.getAttribute("aria-label"))).toEqual([
|
||||
"Show thread list",
|
||||
"Show chat actions",
|
||||
"Show Codex status and settings",
|
||||
"Show status",
|
||||
]);
|
||||
expect(parent.querySelector(".codex-panel__plan-toggle")).toBeNull();
|
||||
expect(parent.querySelector(".codex-panel__auto-review-toggle")).toBeNull();
|
||||
|
|
@ -53,7 +53,7 @@ describe("Toolbar decisions", () => {
|
|||
const statusButton = parent.querySelector(".codex-panel__status-menu-toggle");
|
||||
expect(statusButton?.tagName).toBe("BUTTON");
|
||||
expect(statusButton?.getAttribute("role")).toBeNull();
|
||||
expect(statusButton?.getAttribute("aria-label")).toBe("Show Codex status and settings");
|
||||
expect(statusButton?.getAttribute("aria-label")).toBe("Show status");
|
||||
expect((statusButton as HTMLElement | null)?.dataset["icon"]).toBe("waypoints");
|
||||
expect(statusButton?.classList.contains("nav-action-button")).toBe(true);
|
||||
expect(statusButton?.classList.contains("clickable-icon")).toBe(true);
|
||||
|
|
@ -75,7 +75,7 @@ describe("Toolbar decisions", () => {
|
|||
expect(parent.querySelector(".codex-panel__history-toggle")?.classList.contains("is-active")).toBe(true);
|
||||
expect(parent.querySelector(".codex-panel__new-chat")?.getAttribute("aria-label")).toBe("Hide chat actions");
|
||||
expect(parent.querySelector(".codex-panel__new-chat")?.classList.contains("is-active")).toBe(true);
|
||||
expect(parent.querySelector(".codex-panel__status-menu-toggle")?.getAttribute("aria-label")).toBe("Hide Codex status and settings");
|
||||
expect(parent.querySelector(".codex-panel__status-menu-toggle")?.getAttribute("aria-label")).toBe("Hide status");
|
||||
expect(parent.querySelector(".codex-panel__status-menu-toggle")?.classList.contains("is-active")).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ describe("Toolbar decisions", () => {
|
|||
expect(parent.querySelector(".codex-panel__connection-diagnostics-title")?.textContent).toBe("Connection");
|
||||
expect(parent.textContent).toContain("Process");
|
||||
expect(parent.textContent).toContain("App Server Checks");
|
||||
expect(parent.textContent).toContain("Effective Codex config");
|
||||
expect(parent.textContent).toContain("Debug details");
|
||||
expect(parent.textContent).toContain("Refresh status");
|
||||
expect(parent.textContent).not.toContain("Refresh diagnostics");
|
||||
expect(parent.textContent).not.toContain("Refresh thread list");
|
||||
|
|
@ -188,7 +188,7 @@ describe("Toolbar decisions", () => {
|
|||
expect(refreshStatus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders effective config inside the status menu without a separate toggle", () => {
|
||||
it("renders raw debug details inside a collapsed status menu section", () => {
|
||||
const parent = document.createElement("div");
|
||||
|
||||
mountToolbar(
|
||||
|
|
@ -196,16 +196,20 @@ describe("Toolbar decisions", () => {
|
|||
toolbarModel({
|
||||
statusPanelOpen: true,
|
||||
openPanel: "status",
|
||||
configSections: [{ title: "Runtime", rows: [{ key: "model", value: "gpt-5.5" }] }],
|
||||
debugDetails: JSON.stringify({ runtimeConfig: { model: "gpt-5.5" } }, null, 2),
|
||||
}),
|
||||
toolbarActions(),
|
||||
);
|
||||
|
||||
expect(parent.querySelector(".codex-panel__region--config")).toBeNull();
|
||||
expect(parent.querySelector(".codex-panel__toolbar-panel .codex-panel__config")?.textContent).toContain("Effective Codex config");
|
||||
const details = expectPresent(parent.querySelector<HTMLDetailsElement>(".codex-panel__debug-details"));
|
||||
expect(details.open).toBe(false);
|
||||
expect(details.querySelector("summary")?.textContent).toBe("Debug details");
|
||||
expect(details.querySelector("pre")?.textContent).toContain('"model": "gpt-5.5"');
|
||||
expect(parent.querySelector(".codex-panel__toolbar-panel .codex-panel__config")).toBeNull();
|
||||
expect(parent.textContent).not.toContain("Effective Codex config");
|
||||
expect(parent.textContent).not.toContain("Show effective config");
|
||||
expect(parent.textContent).not.toContain("Hide effective config");
|
||||
expect(parent.textContent).toContain("gpt-5.5");
|
||||
});
|
||||
|
||||
it("renders thread list rename actions and an inline rename editor", () => {
|
||||
|
|
@ -366,7 +370,7 @@ function toolbarModel(overrides: Partial<ToolbarViewModel> = {}): ToolbarViewMod
|
|||
historyOpen: false,
|
||||
statusPanelOpen: false,
|
||||
rateLimit: null,
|
||||
configSections: [],
|
||||
debugDetails: "{}",
|
||||
openPanel: null,
|
||||
threads: [{ title: "Thread", threadId: "thread", selected: true, disabled: false, canArchive: true, rename: null }],
|
||||
connectLabel: "Reconnect",
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
await waitForAsyncWork(() => {
|
||||
expect(view.containerEl.textContent).toContain("Restored thread");
|
||||
});
|
||||
requiredButton(view.containerEl, '[aria-label="Show Codex status and settings"]').click();
|
||||
requiredButton(view.containerEl, '[aria-label="Show status"]').click();
|
||||
await waitForAsyncWork(() => {
|
||||
expect(view.containerEl.textContent).toContain("gpt-cached");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,15 +25,7 @@ import {
|
|||
pendingRuntimeSettingsPatch,
|
||||
serviceTierRequestForThreadStart,
|
||||
} from "../../src/features/chat/application/runtime/thread-settings-update";
|
||||
import { contextSummary, runtimeConfigSections, rateLimitSummary } from "../../src/features/chat/presentation/runtime/status";
|
||||
|
||||
function runtimeRows(snapshot: RuntimeSnapshot): Record<string, string> {
|
||||
return Object.fromEntries(
|
||||
runtimeConfigSections(snapshot, "/vault")
|
||||
.find((section) => section.title === "Runtime")
|
||||
?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
}
|
||||
import { contextSummary, rateLimitSummary } from "../../src/features/chat/presentation/runtime/status";
|
||||
|
||||
describe("runtime settings", () => {
|
||||
it("formats runtime override messages", () => {
|
||||
|
|
@ -86,13 +78,7 @@ describe("runtime settings", () => {
|
|||
collaborationModeWarning: null,
|
||||
});
|
||||
|
||||
const runtimeRows = Object.fromEntries(
|
||||
runtimeConfigSections(snapshot, "/vault")
|
||||
.find((section) => section.title === "Runtime")
|
||||
?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
expect(runtimeRows["effective mode"]).toBe("(not reported)");
|
||||
expect(runtimeRows["requested mode"]).toBe("(none)");
|
||||
expect(snapshot.selectedCollaborationMode).toBe("default");
|
||||
});
|
||||
|
||||
it("keeps model reset tied to config when active thread model differs", () => {
|
||||
|
|
@ -280,8 +266,7 @@ describe("runtime settings", () => {
|
|||
expect(currentModel(snapshot, snapshotConfig(snapshot))).toBe("gpt-profile");
|
||||
expect(currentReasoningEffort(snapshot, snapshotConfig(snapshot))).toBe("high");
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBe("fast");
|
||||
expect(runtimeRows(snapshot)["effective service tier"]).toBe("fast");
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("on");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(true);
|
||||
expect(serviceTierRequestForThreadStart(snapshot, snapshotConfig(snapshot))).toBe("fast");
|
||||
});
|
||||
|
||||
|
|
@ -292,7 +277,7 @@ describe("runtime settings", () => {
|
|||
});
|
||||
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBe("flex");
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("off");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(false);
|
||||
});
|
||||
|
||||
it("treats the catalog Fast service tier id as fast mode while preserving the id", () => {
|
||||
|
|
@ -307,9 +292,7 @@ describe("runtime settings", () => {
|
|||
});
|
||||
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBe("priority");
|
||||
expect(runtimeRows(snapshot)["effective service tier"]).toBe("priority");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(true);
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("on");
|
||||
expect(fastRuntimeServiceTierRequestValue(snapshot, snapshotConfig(snapshot))).toBe("priority");
|
||||
});
|
||||
|
||||
|
|
@ -324,9 +307,7 @@ describe("runtime settings", () => {
|
|||
});
|
||||
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBe("default");
|
||||
expect(runtimeRows(snapshot)["effective service tier"]).toBe("default");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(false);
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("off");
|
||||
});
|
||||
|
||||
it("uses requested service tier above active and configured service tiers", () => {
|
||||
|
|
@ -337,7 +318,7 @@ describe("runtime settings", () => {
|
|||
});
|
||||
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBeNull();
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("off");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(false);
|
||||
});
|
||||
|
||||
it("resolves requested approval reviewer without adding it to turn runtime settings", () => {
|
||||
|
|
@ -363,105 +344,6 @@ describe("runtime settings", () => {
|
|||
expect(pendingRuntimeSettingsPatch(snapshot, snapshotConfig(snapshot)).update).not.toHaveProperty("effort");
|
||||
});
|
||||
|
||||
it("separates effective runtime, config defaults, and pending changes in status details", () => {
|
||||
const sections = runtimeConfigSections(
|
||||
runtimeSnapshot({
|
||||
activeModel: "gpt-5-active",
|
||||
activeReasoningEffort: "low",
|
||||
activeCollaborationMode: "plan",
|
||||
selectedCollaborationMode: "plan",
|
||||
requestedModel: setPendingRuntimeSetting("gpt-5-pending"),
|
||||
}),
|
||||
"/vault",
|
||||
);
|
||||
const runtimeRows = Object.fromEntries(
|
||||
sections.find((section) => section.title === "Runtime")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
|
||||
expect(runtimeRows).toMatchObject({
|
||||
"effective model": "gpt-5-pending",
|
||||
"configured model": "gpt-5.5",
|
||||
"thread model": "gpt-5-active",
|
||||
"requested model": "gpt-5-pending",
|
||||
"effective effort": "low",
|
||||
"configured effort": "high",
|
||||
"thread effort": "low",
|
||||
"requested effort": "(none)",
|
||||
"effective mode": "Plan",
|
||||
"requested mode": "(none)",
|
||||
"configured service tier": "flex",
|
||||
"thread service tier": "(not reported)",
|
||||
"requested service tier": "(none)",
|
||||
});
|
||||
|
||||
const resetSections = runtimeConfigSections(
|
||||
runtimeSnapshot({
|
||||
requestedReasoningEffort: resetRuntimeSettingToConfig(),
|
||||
}),
|
||||
"/vault",
|
||||
);
|
||||
const resetRuntimeRows = Object.fromEntries(
|
||||
resetSections.find((section) => section.title === "Runtime")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
expect(resetRuntimeRows["requested effort"]).toBe("(reset to config)");
|
||||
});
|
||||
|
||||
it("labels unset config values as Codex defaults when no concrete value is reported", () => {
|
||||
const sections = runtimeConfigSections(
|
||||
runtimeSnapshot({
|
||||
runtimeConfig: runtimeConfigFixture({}),
|
||||
}),
|
||||
"/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("(Codex default)");
|
||||
expect(runtimeRows).toMatchObject({
|
||||
"effective model": "(Codex default)",
|
||||
"configured model": "(not reported)",
|
||||
"thread model": "(not reported)",
|
||||
"requested model": "(none)",
|
||||
"configured service tier": "(Codex default)",
|
||||
"thread service tier": "(not reported)",
|
||||
"requested service tier": "(none)",
|
||||
"fast mode": "Codex default",
|
||||
});
|
||||
expect(policyRows).toMatchObject({
|
||||
"effective approval": "(Codex default)",
|
||||
"configured approval": "(Codex default)",
|
||||
"thread approval": "(not reported)",
|
||||
"active permissions": "(not reported)",
|
||||
"configured reviewer": "(Codex default)",
|
||||
sandbox: "(Codex default)",
|
||||
"web search": "(Codex default)",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses catalog default reasoning efforts from model metadata", () => {
|
||||
const model = { ...modelFixture("gpt-catalog-default"), isDefault: true, defaultReasoningEffort: "extreme" };
|
||||
const sections = runtimeConfigSections(
|
||||
runtimeSnapshot({
|
||||
runtimeConfig: runtimeConfigFixture({}),
|
||||
availableModels: [model],
|
||||
}),
|
||||
"/vault",
|
||||
);
|
||||
const runtimeRows = Object.fromEntries(
|
||||
sections.find((section) => section.title === "Runtime")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
|
||||
expect(runtimeRows["configured model"]).toBe("gpt-catalog-default");
|
||||
expect(runtimeRows["configured effort"]).toBe("extreme");
|
||||
});
|
||||
|
||||
it("uses the explicit config when finding supported reasoning efforts", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
requestedModel: resetRuntimeSettingToConfig(),
|
||||
|
|
@ -476,95 +358,11 @@ describe("runtime settings", () => {
|
|||
expect(supportedReasoningEfforts(snapshot, explicitConfig)).toEqual(["high"]);
|
||||
});
|
||||
|
||||
it("shows effective profile runtime and policy values in status details", () => {
|
||||
const profileConfig = {
|
||||
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",
|
||||
};
|
||||
const sections = runtimeConfigSections(
|
||||
runtimeSnapshot({
|
||||
runtimeConfig: runtimeConfigFixture(profileConfig, [configLayer({}, null), configLayer(profileConfig, "auto")]),
|
||||
}),
|
||||
"/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["profile"]).toBe("auto");
|
||||
expect(scopeRows["model provider"]).toBe("profile-provider");
|
||||
expect(runtimeRows).toMatchObject({
|
||||
"effective model": "gpt-profile",
|
||||
"configured model": "gpt-profile",
|
||||
"thread model": "(not reported)",
|
||||
"requested model": "(none)",
|
||||
"effective effort": "high",
|
||||
"configured effort": "high",
|
||||
"thread effort": "(not reported)",
|
||||
"requested effort": "(none)",
|
||||
"reasoning summary": "detailed",
|
||||
verbosity: "high",
|
||||
"effective service tier": "fast",
|
||||
"configured service tier": "fast",
|
||||
"thread service tier": "(not reported)",
|
||||
"requested service tier": "(none)",
|
||||
"fast mode": "on",
|
||||
});
|
||||
expect(policyRows["effective approval"]).toBe("never");
|
||||
expect(policyRows["configured approval"]).toBe("never");
|
||||
expect(policyRows["thread approval"]).toBe("(not reported)");
|
||||
expect(policyRows["active permissions"]).toBe("(not reported)");
|
||||
expect(policyRows["effective reviewer"]).toBe("auto_review");
|
||||
expect(policyRows["auto-review"]).toBe("on");
|
||||
expect(policyRows["configured reviewer"]).toBe("auto_review");
|
||||
expect(policyRows["thread reviewer"]).toBe("(not reported)");
|
||||
expect(policyRows["requested reviewer"]).toBe("(none)");
|
||||
expect(policyRows["web search"]).toBe("live");
|
||||
});
|
||||
|
||||
it("shows active and configured reviewer inputs in status details", () => {
|
||||
const sections = runtimeConfigSections(
|
||||
runtimeSnapshot({
|
||||
activeApprovalsReviewer: "guardian_subagent",
|
||||
activeApprovalPolicy: "never",
|
||||
activePermissionProfile: { id: ":workspace", extends: ":default" },
|
||||
runtimeConfig: runtimeConfigFixture({ approvals_reviewer: "auto_review" }),
|
||||
}),
|
||||
"/vault",
|
||||
);
|
||||
const policyRows = Object.fromEntries(
|
||||
sections.find((section) => section.title === "Policy")?.rows.map((row) => [row.key, row.value]) ?? [],
|
||||
);
|
||||
|
||||
expect(policyRows).toMatchObject({
|
||||
"effective approval": "never",
|
||||
"thread approval": "never",
|
||||
"active permissions": ":workspace extends :default",
|
||||
"effective reviewer": "guardian_subagent",
|
||||
"auto-review": "on",
|
||||
"configured reviewer": "auto_review",
|
||||
"thread reviewer": "guardian_subagent",
|
||||
});
|
||||
});
|
||||
|
||||
it("summarizes service tier and context meter state from one runtime snapshot", () => {
|
||||
const snapshot = runtimeSnapshot({ requestedServiceTier: setPendingRuntimeSetting("fast"), activeThreadId: "thread" });
|
||||
|
||||
expect(runtimeRows(snapshot)["effective service tier"]).toBe("fast");
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("on");
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBe("fast");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(true);
|
||||
expect(contextSummary(snapshot)).toMatchObject({
|
||||
label: "Context 0%",
|
||||
title: "Context: 0 / 100,000 (0%). No turns in this thread yet.",
|
||||
|
|
@ -604,8 +402,8 @@ describe("runtime settings", () => {
|
|||
requestedServiceTier: setPendingRuntimeSetting("off"),
|
||||
});
|
||||
|
||||
expect(runtimeRows(snapshot)["effective service tier"]).toBe("(Codex default)");
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("off");
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBeNull();
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(false);
|
||||
expect(serviceTierRequestForThreadStart(snapshot, snapshotConfig(snapshot))).toBeNull();
|
||||
});
|
||||
|
||||
|
|
@ -615,8 +413,8 @@ describe("runtime settings", () => {
|
|||
requestedServiceTier: resetRuntimeSettingToConfig(),
|
||||
});
|
||||
|
||||
expect(runtimeRows(snapshot)["effective service tier"]).toBe("fast");
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("on");
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBe("fast");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(true);
|
||||
expect(serviceTierRequestForThreadStart(snapshot, snapshotConfig(snapshot))).toBeNull();
|
||||
});
|
||||
|
||||
|
|
@ -629,7 +427,7 @@ describe("runtime settings", () => {
|
|||
availableModels: [model],
|
||||
});
|
||||
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("on");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(true);
|
||||
expect(serviceTierRequestForThreadStart(snapshot, snapshotConfig(snapshot))).toBe("priority");
|
||||
});
|
||||
|
||||
|
|
@ -642,8 +440,8 @@ describe("runtime settings", () => {
|
|||
it("passes through configured non-fast service tier ids", () => {
|
||||
const snapshot = runtimeSnapshot({ runtimeConfig: runtimeConfigFixture({ service_tier: "flex" }) });
|
||||
|
||||
expect(runtimeRows(snapshot)["effective service tier"]).toBe("flex");
|
||||
expect(runtimeRows(snapshot)["fast mode"]).toBe("off");
|
||||
expect(currentServiceTier(snapshot, snapshotConfig(snapshot))).toBe("flex");
|
||||
expect(fastModeActive(snapshot, snapshotConfig(snapshot))).toBe(false);
|
||||
expect(serviceTierRequestForThreadStart(snapshot, snapshotConfig(snapshot))).toBe("flex");
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue