Clarify runtime module responsibilities

This commit is contained in:
murashit 2026-06-08 09:33:13 +09:00
parent 09545939cc
commit 8c775dcd99
47 changed files with 211 additions and 209 deletions

View file

@ -22,7 +22,7 @@ The source tree is organized by responsibility rather than by the original singl
- `src/features/chat/` owns the main Codex chat surface: Obsidian `ItemView` classes, panel orchestration, request/app-server controllers, composer behavior, display models, chat-only UI renderers, approvals, user input, thread actions, and panel-specific state.
- `src/features/threads-view/` owns the dedicated Obsidian thread list view, including app-server thread list rendering and live open-panel snapshot aggregation.
- `src/features/selection-rewrite/` owns the Markdown editor selection rewrite command, popover, prompt/output handling, and ephemeral rewrite thread runner.
- `src/runtime/` owns Codex runtime configuration projection, model metadata, collaboration mode, and compact labels used by views.
- `src/runtime/` owns Codex runtime config projection, model metadata, effective runtime setting resolution, override-command parsing/labels, and status summaries shared by views.
- `src/workspace/` owns Obsidian workspace coordination across Codex surfaces, including panel discovery/opening, open-panel snapshots, and thread surface broadcasts.
- `src/shared/` contains feature-neutral helpers, including reusable DOM pieces and unified diff display helpers shared by chat and selection rewrite.
- `src/settings/` contains Obsidian settings models, settings-tab rendering, and app-server-backed dynamic settings data.

View file

@ -1,4 +1,4 @@
import type { AppServerDiagnostics } from "../app-server/compatibility";
import type { AppServerDiagnostics } from "./compatibility";
import type { ConfigReadResponse } from "../generated/app-server/v2/ConfigReadResponse";
import type { Model } from "../generated/app-server/v2/Model";
import type { RateLimitSnapshot } from "../generated/app-server/v2/RateLimitSnapshot";

View file

@ -9,7 +9,7 @@ import {
createSharedAppServerState,
type SharedAppServerMetadata,
type SharedAppServerState,
} from "./shared-app-server-state";
} from "./shared-cache-state";
type ThreadListRefreshLifecycleState = { kind: "idle" } | { kind: "refreshing"; promise: Promise<readonly Thread[]> };

View file

@ -11,7 +11,7 @@ import type { ThreadStartResponse } from "../generated/app-server/v2/ThreadStart
import type { Turn } from "../generated/app-server/v2/Turn";
import type { TurnStartResponse } from "../generated/app-server/v2/TurnStartResponse";
import { namingPrompt, titleFromNamingTurn, type ThreadNamingContext } from "../domain/threads/naming";
import { runtimeOverride, validatedRuntimeOverride } from "../runtime/model";
import { runtimeOverride, validatedRuntimeOverride } from "../runtime/models";
import {
createStructuredTurnRunLifecycle,
structuredTurnRunMatches,

View file

@ -6,7 +6,7 @@ import {
type CapabilityProbeMethod,
} from "../../../app-server/compatibility";
import type { McpServerStatus } from "../../../generated/app-server/v2/McpServerStatus";
import type { SharedAppServerMetadata } from "../../../runtime/shared-app-server-state";
import type { SharedAppServerMetadata } from "../../../app-server/shared-cache-state";
import { mcpStatusLines as buildMcpStatusLines } from "../mcp-status";
import { cloneAppServerDiagnostics, type ChatAppServerBaseHost } from "./shared";

View file

@ -2,7 +2,7 @@ import { type AppServerDiagnostics, capabilityProbeError, capabilityProbeOk } fr
import type { Model } from "../../../generated/app-server/v2/Model";
import type { RateLimitSnapshot } from "../../../generated/app-server/v2/RateLimitSnapshot";
import type { SkillMetadata } from "../../../generated/app-server/v2/SkillMetadata";
import type { SharedAppServerMetadata } from "../../../runtime/shared-app-server-state";
import type { SharedAppServerMetadata } from "../../../app-server/shared-cache-state";
import { cloneAppServerDiagnostics, type ChatAppServerBaseHost } from "./shared";
export interface ChatAppServerMetadataActionsHost extends ChatAppServerBaseHost {

View file

@ -1,7 +1,7 @@
import type { AppServerClient } from "../../../app-server/client";
import { upsertThread } from "../../../domain/threads/model";
import type { Thread } from "../../../generated/app-server/v2/Thread";
import { requestedOrConfiguredServiceTier, type RuntimeSnapshot } from "../../../runtime/state";
import { requestedOrConfiguredServiceTier, type RuntimeSnapshot } from "../../../runtime/effective-settings";
import { resumedThreadAction } from "../thread-resume";
import type { ChatAppServerBaseHost } from "./shared";

View file

@ -1,5 +1,5 @@
import type { Thread } from "../../generated/app-server/v2/Thread";
import type { SharedAppServerMetadata } from "../../runtime/shared-app-server-state";
import type { SharedAppServerMetadata } from "../../app-server/shared-cache-state";
import type { CodexPanelSettings } from "../../settings/model";
import type { ChatTurnDiffViewState } from "./ui/turn-diff";

View file

@ -20,7 +20,7 @@ import {
setPendingRuntimeSetting,
unchangedRuntimeSetting,
type PendingRuntimeSetting,
} from "../../runtime/state";
} from "../../runtime/effective-settings";
import type { PendingApproval } from "./requests/approvals/model";
import type { ComposerSuggestion } from "./composer/suggestions";
import { upsertDisplayItem } from "./display/stream-updates";

View file

@ -8,7 +8,7 @@ import {
REASONING_EFFORTS,
sortedAvailableModels,
supportedEffortsForModel,
} from "../../../runtime/model";
} from "../../../runtime/models";
import { SLASH_COMMANDS, slashCommandSubcommands, type SlashCommandName } from "./slash-commands";
import { getThreadTitle } from "../../../domain/threads/model";
import { shortThreadId } from "../../../utils";

View file

@ -2,7 +2,7 @@ import type { App, Component, EventRef, WorkspaceLeaf } from "obsidian";
import type { AppServerClient } from "../../../app-server/client";
import type { ArchiveExportAdapter } from "../../../domain/threads/export";
import type { RuntimeSnapshot } from "../../../runtime/state";
import type { RuntimeSnapshot } from "../../../runtime/effective-settings";
import type { ChatState, ChatStateStore } from "../chat-state";
import type { CodexChatHost } from "../chat-host";
import type { ChatMessageScrollIntentController } from "../panel/message-scroll-intent-controller";

View file

@ -1,7 +1,7 @@
import { readRuntimeConfig } from "../../../../runtime/config";
import { autoReviewActive, currentModel, currentReasoningEffort, fastModeActive } from "../../../../runtime/state";
import { compactReasoningEffortLabel } from "../../../../runtime/settings";
import { contextSummary } from "../../../../runtime/view";
import { autoReviewActive, currentModel, currentReasoningEffort, fastModeActive } from "../../../../runtime/effective-settings";
import { compactReasoningEffortLabel } from "../../../../runtime/override-commands";
import { contextSummary } from "../../../../runtime/status-summary";
import type { ChatState } from "../../chat-state";
import type { ComposerContextMeterCellViewModel, ComposerContextMeterViewModel, ComposerMetaViewModel } from "./types";
import type { runtimeSnapshotForChatState } from "./runtime";

View file

@ -5,9 +5,9 @@ import {
pendingRuntimeSettingLabel,
serviceTierLabel,
supportedReasoningEfforts,
} from "../../../../runtime/state";
import { sortedAvailableModels } from "../../../../runtime/model";
import { contextSummary, rateLimitSummary } from "../../../../runtime/view";
} from "../../../../runtime/effective-settings";
import { sortedAvailableModels } from "../../../../runtime/models";
import { contextSummary, rateLimitSummary } from "../../../../runtime/status-summary";
import type { ChatState } from "../../chat-state";
import { statusValue, usageLimitStatusLines } from "../../status-lines";
import type { RuntimeChoice, RuntimeComposerChoicesInput, RuntimeSnapshotInput } from "./types";

View file

@ -1,6 +1,6 @@
import type { Thread } from "../../../../generated/app-server/v2/Thread";
import { getThreadTitle } from "../../../../domain/threads/model";
import { effectiveConfigSections, rateLimitSummary } from "../../../../runtime/view";
import { effectiveConfigSections, rateLimitSummary } from "../../../../runtime/status-summary";
import type { ToolbarThreadRow, ToolbarViewModel } from "../../toolbar-model";
import { connectionDiagnosticsModel } from "./diagnostics";
import type { ToolbarViewModelInput } from "./types";

View file

@ -1,5 +1,5 @@
import type { ReasoningEffort } from "../../../../generated/app-server/ReasoningEffort";
import type { RuntimeSnapshot } from "../../../../runtime/state";
import type { RuntimeSnapshot } from "../../../../runtime/effective-settings";
import type { ChatState } from "../../chat-state";
import type { ToolbarThreadRow } from "../../toolbar-model";

View file

@ -1,5 +1,5 @@
import type { ReasoningEffort } from "../../../../generated/app-server/ReasoningEffort";
import type { RuntimeSnapshot } from "../../../../runtime/state";
import type { RuntimeSnapshot } from "../../../../runtime/effective-settings";
import type { SendShortcut } from "../../../../shared/ui/keyboard";
import type { ChatState } from "../../chat-state";
import type { ToolbarThreadRow } from "../../toolbar-model";

View file

@ -1,8 +1,8 @@
import type { Model } from "../../../generated/app-server/v2/Model";
import type { Thread } from "../../../generated/app-server/v2/Thread";
import type { OpenCodexPanelSnapshot } from "../../../runtime/open-panel-snapshot";
import type { OpenCodexPanelSnapshot } from "../../../workspace/open-panel-snapshot";
import { readRuntimeConfig } from "../../../runtime/config";
import { currentModel } from "../../../runtime/state";
import { currentModel } from "../../../runtime/effective-settings";
import { activeTurnId, chatTurnBusy, type ChatState } from "../chat-state";
import type { DisplayItem } from "../display/types";
import type { RestoredThreadState } from "./lifecycle";

View file

@ -4,7 +4,7 @@ import type { ModeKind } from "../../../generated/app-server/ModeKind";
import type { ReasoningEffort } from "../../../generated/app-server/ReasoningEffort";
import type { ApprovalsReviewer } from "../../../generated/app-server/v2/ApprovalsReviewer";
import type { ThreadSettingsUpdateParams } from "../../../generated/app-server/v2/ThreadSettingsUpdateParams";
import { collaborationModeToggleMessage, nextCollaborationMode } from "../../../runtime/collaboration-mode";
import { collaborationModeToggleMessage, nextCollaborationMode } from "../../../runtime/override-commands";
import { readRuntimeConfig } from "../../../runtime/config";
import {
autoReviewActive,
@ -13,8 +13,8 @@ import {
pendingRuntimeSettingPayload,
requestedTurnRuntimeSettings,
type RuntimeSnapshot,
} from "../../../runtime/state";
import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../../runtime/settings";
} from "../../../runtime/effective-settings";
import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../../runtime/override-commands";
import type { ChatAction, ChatState, ChatStateStore } from "../chat-state";
type ThreadSettingsUpdate = Omit<ThreadSettingsUpdateParams, "threadId">;

View file

@ -19,7 +19,7 @@ import {
parseModelOverride,
parseReasoningEffortOverride,
reasoningEffortOverrideMessage,
} from "../../runtime/settings";
} from "../../runtime/override-commands";
export interface SlashCommandExecutionContext {
activeThreadId: string | null;

View file

@ -1,4 +1,4 @@
import type { RateLimitSummary } from "../../runtime/view";
import type { RateLimitSummary } from "../../runtime/status-summary";
export function statusValue(value: unknown, fallback: string): string {
if (typeof value === "string") return value;

View file

@ -1,4 +1,4 @@
import type { EffectiveConfigSection, RateLimitSummary } from "../../runtime/view";
import type { EffectiveConfigSection, RateLimitSummary } from "../../runtime/status-summary";
type ToolbarPanelKind = "history" | "chat-actions" | "status";

View file

@ -13,7 +13,7 @@ import type { ChatThreadGoalActions } from "../threads/thread-goal-actions";
import type { ThreadHistoryController } from "../threads/thread-history-controller";
import type { ThreadRenameController } from "../threads/thread-rename-controller";
import type { ChatInboundController } from "../inbound/controller";
import { currentModel } from "../../../runtime/state";
import { currentModel } from "../../../runtime/effective-settings";
import { ChatMessageRenderer } from "../ui/message-stream";
import type { ChatPanelContext } from "../panel/context";

View file

@ -1,7 +1,7 @@
import type { ButtonHTMLAttributes, ComponentChild as UiNode, TargetedKeyboardEvent } from "preact";
import { useLayoutEffect, useRef } from "preact/hooks";
import type { EffectiveConfigSection, RateLimitSummary } from "../../../runtime/view";
import type { EffectiveConfigSection, RateLimitSummary } from "../../../runtime/status-summary";
import { IconButton } from "../../../shared/ui/components";
import { renderUiRoot } from "../../../shared/ui/ui-root";
import type { ToolbarDiagnosticSection, ToolbarThreadRow, ToolbarViewModel } from "../toolbar-model";

View file

@ -6,11 +6,11 @@ import type { DisplayDetailSection, DisplayItem } from "./display/types";
import type { ReasoningEffort } from "../../generated/app-server/ReasoningEffort";
import type { Model } from "../../generated/app-server/v2/Model";
import type { Thread } from "../../generated/app-server/v2/Thread";
import { collaborationModeLabel as formatCollaborationModeLabel } from "../../runtime/collaboration-mode";
import type { RuntimeSnapshot } from "../../runtime/state";
import { collaborationModeLabel as formatCollaborationModeLabel } from "../../runtime/override-commands";
import type { RuntimeSnapshot } from "../../runtime/effective-settings";
import { chatTurnBusy, createChatStateStore, type ChatState, type ChatAction } from "./chat-state";
import type { OpenCodexPanelSnapshot } from "../../runtime/open-panel-snapshot";
import type { SharedAppServerMetadata } from "../../runtime/shared-app-server-state";
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
import type { SharedAppServerMetadata } from "../../app-server/shared-cache-state";
import type { CodexChatHost } from "./chat-host";
import { createSystemItem } from "./display/system";
import {

View file

@ -15,7 +15,7 @@ import type { ThreadItem } from "../../generated/app-server/v2/ThreadItem";
import type { ThreadStartResponse } from "../../generated/app-server/v2/ThreadStartResponse";
import type { Turn } from "../../generated/app-server/v2/Turn";
import type { TurnStartResponse } from "../../generated/app-server/v2/TurnStartResponse";
import { runtimeOverride, validatedRuntimeOverride } from "../../runtime/model";
import { runtimeOverride, validatedRuntimeOverride } from "../../runtime/models";
import type { SelectionRewriteRuntimeSettings } from "./model";
import { SELECTION_REWRITE_DEVELOPER_INSTRUCTIONS, SELECTION_REWRITE_SERVICE_NAME } from "./prompt";
import { SelectionRewriteOutputError, selectionRewriteOutputParseResultFromTurn, type SelectionRewriteOutput } from "./output";

View file

@ -1,4 +1,4 @@
import type { OpenCodexPanelSnapshot } from "../../runtime/open-panel-snapshot";
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
import type { Thread } from "../../generated/app-server/v2/Thread";
import { getThreadTitle } from "../../domain/threads/model";

View file

@ -6,7 +6,7 @@ import { VIEW_TYPE_CODEX_THREADS } from "../../constants";
import type { Thread } from "../../generated/app-server/v2/Thread";
import type { CodexPanelSettings } from "../../settings/model";
import { exportArchivedThreadMarkdown } from "../../domain/threads/export";
import type { OpenCodexPanelSnapshot } from "../../runtime/open-panel-snapshot";
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
import { findThreadNamingContext, THREAD_NAMING_CONTEXT_UNAVAILABLE_MESSAGE } from "../../domain/threads/naming";
import { generateThreadTitleWithCodex } from "../../app-server/thread-naming";
import { renderThreadsView, unmountThreadsView } from "./renderer";

View file

@ -7,7 +7,7 @@ import type { CodexChatHost } from "./features/chat/chat-host";
import { CodexChatTurnDiffView } from "./features/chat/chat-turn-diff-view";
import { openThreadPicker, type ThreadPickerHost } from "./features/thread-picker/modal";
import { CodexThreadsView, type CodexThreadsHost } from "./features/threads-view/view";
import { SharedAppServerCache } from "./runtime/shared-app-server-cache";
import { SharedAppServerCache } from "./app-server/shared-cache";
import { DEFAULT_SETTINGS, getVaultPath, normalizeSettings, settingsMatchNormalizedData, type CodexPanelSettings } from "./settings/model";
import { CodexPanelSettingTab, type CodexPanelSettingTabHost } from "./settings/tab";
import { persistedChatTurnDiffViewState, type ChatTurnDiffViewState } from "./features/chat/ui/turn-diff";

View file

@ -1,37 +0,0 @@
import type { CollaborationMode } from "../generated/app-server/CollaborationMode";
import type { ModeKind } from "../generated/app-server/ModeKind";
import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort";
export function nextCollaborationMode(mode: ModeKind): ModeKind {
return mode === "plan" ? "default" : "plan";
}
export function collaborationModeLabel(mode: ModeKind): string {
return mode === "plan" ? "Plan" : "Default";
}
export function collaborationModeToggleMessage(mode: ModeKind): string {
return mode === "plan" ? "Plan mode on for subsequent turns." : "Plan mode off for subsequent turns.";
}
export function planCollaborationMode(model: string, reasoningEffort: ReasoningEffort | null): CollaborationMode {
return {
mode: "plan",
settings: {
model,
reasoning_effort: reasoningEffort,
developer_instructions: null,
},
};
}
export function defaultCollaborationMode(model: string, reasoningEffort: ReasoningEffort | null): CollaborationMode {
return {
mode: "default",
settings: {
model,
reasoning_effort: reasoningEffort,
developer_instructions: null,
},
};
}

View file

@ -11,7 +11,7 @@ import type { SandboxMode } from "../generated/app-server/v2/SandboxMode";
import type { SandboxWorkspaceWrite } from "../generated/app-server/v2/SandboxWorkspaceWrite";
import type { ToolsV2 } from "../generated/app-server/v2/ToolsV2";
import { parseServiceTier, type ServiceTier } from "../app-server/service-tier";
import { isReasoningEffort } from "./model";
import { isReasoningEffort } from "./models";
export interface RuntimeConfigProjection {
profile: string | null;

View file

@ -16,8 +16,7 @@ import {
type ServiceTier,
type ServiceTierRequest,
} from "../app-server/service-tier";
import { defaultCollaborationMode, planCollaborationMode } from "./collaboration-mode";
import { findModelByIdOrName, supportedEffortsForModel } from "./model";
import { findModelByIdOrName, supportedEffortsForModel } from "./models";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
export type PendingRuntimeSetting<T> = { kind: "unchanged" } | { kind: "set"; value: T } | { kind: "resetToConfig" };
@ -116,11 +115,7 @@ export function autoReviewActive(
export function requestedTurnRuntimeSettings(snapshot: RuntimeSnapshot): TurnRuntimeSettings {
const model = currentModel(snapshot);
const effort = currentReasoningEffort(snapshot);
const collaborationMode = model
? snapshot.selectedCollaborationMode === "plan"
? planCollaborationMode(model, effort)
: defaultCollaborationMode(model, effort)
: null;
const collaborationMode = model ? turnCollaborationMode(snapshot.selectedCollaborationMode, model, effort) : null;
return {
collaborationMode,
warning: model ? null : "No effective model is available. Sending without a mode override.",
@ -191,6 +186,17 @@ function isAutoReviewReviewer(value: ApprovalsReviewer | null): boolean {
return value === "auto_review" || value === "guardian_subagent";
}
function turnCollaborationMode(mode: ModeKind, model: string, reasoningEffort: ReasoningEffort | null): CollaborationMode {
return {
mode,
settings: {
model,
reasoning_effort: reasoningEffort,
developer_instructions: null,
},
};
}
function currentModelServiceTiers(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),

View file

@ -1,5 +1,6 @@
import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort";
import { isReasoningEffort } from "./model";
import type { ModeKind } from "../generated/app-server/ModeKind";
import { isReasoningEffort } from "./models";
const DEFAULT_ALIASES = new Set(["default", "reset", "clear", "off"]);
@ -38,3 +39,15 @@ export function compactReasoningEffortLabel(effort: ReasoningEffort | null): str
if (effort === "minimal") return "min";
return effort;
}
export function nextCollaborationMode(mode: ModeKind): ModeKind {
return mode === "plan" ? "default" : "plan";
}
export function collaborationModeLabel(mode: ModeKind): string {
return mode === "plan" ? "Plan" : "Default";
}
export function collaborationModeToggleMessage(mode: ModeKind): string {
return mode === "plan" ? "Plan mode on for subsequent turns." : "Plan mode off for subsequent turns.";
}

View file

@ -2,7 +2,7 @@ import type { RateLimitWindow } from "../generated/app-server/v2/RateLimitWindow
import type { SpendControlLimitSnapshot } from "../generated/app-server/v2/SpendControlLimitSnapshot";
import type { ThreadTokenUsage } from "../generated/app-server/v2/ThreadTokenUsage";
import { jsonPreview } from "../utils";
import { sortedAvailableModels } from "./model";
import { sortedAvailableModels } from "./models";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
import {
currentApprovalsReviewer,
@ -14,7 +14,7 @@ import {
pendingRuntimeSettingLabel,
serviceTierLabel,
type RuntimeSnapshot,
} from "./state";
} from "./effective-settings";
export interface ContextSummary {
label: string;

View file

@ -2,7 +2,7 @@ import { FileSystemAdapter, type App } from "obsidian";
import { DEFAULT_CODEX_PATH } from "../constants";
import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort";
import { normalizeReasoningEffort } from "../runtime/model";
import { normalizeReasoningEffort } from "../runtime/models";
import type { SendShortcut } from "../shared/ui/keyboard";
export interface CodexPanelSettings {

View file

@ -7,7 +7,7 @@ import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort";
import type { HookMetadata } from "../generated/app-server/v2/HookMetadata";
import type { Model } from "../generated/app-server/v2/Model";
import type { Thread } from "../generated/app-server/v2/Thread";
import { findModelByIdOrName, REASONING_EFFORTS, sortedAvailableModels, supportedEffortsForModel } from "../runtime/model";
import { findModelByIdOrName, REASONING_EFFORTS, sortedAvailableModels, supportedEffortsForModel } from "../runtime/models";
import { archivedThreadDisplayTitle } from "../domain/threads/model";
import { errorMessage } from "../utils";
import {

View file

@ -2,7 +2,7 @@ import type { App, WorkspaceLeaf } from "obsidian";
import { VIEW_TYPE_CODEX_PANEL } from "../constants";
import { CodexChatView } from "../features/chat/view";
import type { OpenCodexPanelSnapshot } from "../runtime/open-panel-snapshot";
import type { OpenCodexPanelSnapshot } from "./open-panel-snapshot";
const BOOT_RESTORED_PANEL_LOAD_DELAY_MS = 1_000;
const BOOT_RESTORED_PANEL_LOAD_STAGGER_MS = 250;

View file

@ -4,7 +4,7 @@ import { VIEW_TYPE_CODEX_THREADS } from "../constants";
import { CodexThreadsView } from "../features/threads-view/view";
import type { Model } from "../generated/app-server/v2/Model";
import type { Thread } from "../generated/app-server/v2/Thread";
import type { SharedAppServerMetadata } from "../runtime/shared-app-server-state";
import type { SharedAppServerMetadata } from "../app-server/shared-cache-state";
import type { WorkspacePanelCoordinator } from "./panel-coordinator";
export interface ThreadSurfaceCoordinatorOptions {

View file

@ -0,0 +1,93 @@
import { describe, expect, it } from "vitest";
import { createAppServerDiagnostics } from "../../src/app-server/compatibility";
import {
applySharedAppServerMetadata,
applySharedModels,
applySharedThreadList,
cachedSharedAppServerMetadata,
cachedSharedThreadList,
createSharedAppServerState,
} from "../../src/app-server/shared-cache-state";
import type { Model } from "../../src/generated/app-server/v2/Model";
import type { Thread } from "../../src/generated/app-server/v2/Thread";
describe("shared app-server cache state", () => {
it("keeps snapshots detached from caller-owned arrays", () => {
const sourceThreads = [threadFixture("thread-1")];
const threadState = applySharedThreadList(createSharedAppServerState(), sourceThreads);
sourceThreads.push(threadFixture("thread-2"));
const cachedThreads = cachedSharedThreadList(threadState);
expect(cachedThreads?.map((thread) => thread.id)).toEqual(["thread-1"]);
const mutableCachedThreads = cachedThreads as Thread[];
mutableCachedThreads.push(threadFixture("thread-3"));
expect(cachedSharedThreadList(threadState)?.map((thread) => thread.id)).toEqual(["thread-1"]);
const sourceModels = [modelFixture("gpt-5.5")];
const modelState = applySharedModels(createSharedAppServerState(), sourceModels);
sourceModels.push(modelFixture("gpt-5.6"));
expect(modelState.availableModels.map((model) => model.model)).toEqual(["gpt-5.5"]);
const metadataState = applySharedAppServerMetadata(createSharedAppServerState(), {
effectiveConfig: null,
availableModels: sourceModels,
availableSkills: [{ name: "skill", description: "", path: "/tmp/skill", scope: "repo", enabled: true }],
rateLimit: null,
appServerDiagnostics: {
...createAppServerDiagnostics(),
mcpServers: [{ name: "server", startupStatus: "ready", authStatus: null, toolCount: 1, message: null }],
},
});
sourceModels.push(modelFixture("gpt-5.7"));
const cachedMetadata = cachedSharedAppServerMetadata(metadataState);
expect(cachedMetadata?.availableModels.map((model) => model.model)).toEqual(["gpt-5.5", "gpt-5.6"]);
});
});
function threadFixture(id: string): Thread {
return {
id,
sessionId: "session",
forkedFromId: null,
parentThreadId: null,
preview: "",
ephemeral: false,
modelProvider: "openai",
createdAt: 1,
updatedAt: 1,
status: { type: "idle" },
path: null,
cwd: "/vault",
cliVersion: "0.0.0",
source: "appServer",
threadSource: null,
agentNickname: null,
agentRole: null,
gitInfo: null,
name: null,
turns: [],
};
}
function modelFixture(model: string): Model {
return {
id: model,
model,
upgrade: null,
upgradeInfo: null,
availabilityNux: null,
displayName: model,
description: "",
hidden: false,
supportedReasoningEfforts: [],
defaultReasoningEffort: "medium",
inputModalities: [],
supportsPersonality: false,
additionalSpeedTiers: [],
serviceTiers: [],
defaultServiceTier: null,
isDefault: false,
};
}

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { statusValue, usageLimitStatusLines } from "../../../src/features/chat/status-lines";
import type { RateLimitSummary } from "../../../src/runtime/view";
import type { RateLimitSummary } from "../../../src/runtime/status-summary";
describe("status line helpers", () => {
it("formats primitive and structured diagnostic values", () => {

View file

@ -10,7 +10,7 @@ import {
type ThreadsRowModel,
} from "../../../src/features/threads-view/state";
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
import type { OpenCodexPanelSnapshot } from "../../../src/runtime/open-panel-snapshot";
import type { OpenCodexPanelSnapshot } from "../../../src/workspace/open-panel-snapshot";
import { changeInputValue, installObsidianDomShims } from "../../support/dom";
installObsidianDomShims();

View file

@ -8,7 +8,7 @@ import { DEFAULT_SETTINGS } from "../src/settings/model";
import type CodexPanelPlugin from "../src/main";
import type { CodexChatView } from "../src/features/chat/view";
import type { Thread } from "../src/generated/app-server/v2/Thread";
import type { SharedAppServerCache } from "../src/runtime/shared-app-server-cache";
import type { SharedAppServerCache } from "../src/app-server/shared-cache";
import type { WorkspacePanelCoordinator } from "../src/workspace/panel-coordinator";
import type { ThreadSurfaceCoordinator } from "../src/workspace/thread-surface-coordinator";
import { installObsidianDomShims } from "./support/dom";

View file

@ -1,45 +0,0 @@
import { describe, expect, it } from "vitest";
import {
collaborationModeLabel,
collaborationModeToggleMessage,
defaultCollaborationMode,
nextCollaborationMode,
planCollaborationMode,
} from "../../src/runtime/collaboration-mode";
describe("collaboration mode", () => {
it("toggles between Default and Plan mode", () => {
expect(nextCollaborationMode("default")).toBe("plan");
expect(nextCollaborationMode("plan")).toBe("default");
expect(collaborationModeLabel("default")).toBe("Default");
expect(collaborationModeLabel("plan")).toBe("Plan");
});
it("formats slash command status messages", () => {
expect(collaborationModeToggleMessage("plan")).toBe("Plan mode on for subsequent turns.");
expect(collaborationModeToggleMessage("default")).toBe("Plan mode off for subsequent turns.");
});
it("builds the Plan collaboration mode payload", () => {
expect(planCollaborationMode("gpt-5.5", "high")).toEqual({
mode: "plan",
settings: {
model: "gpt-5.5",
reasoning_effort: "high",
developer_instructions: null,
},
});
});
it("builds the Default collaboration mode payload", () => {
expect(defaultCollaborationMode("gpt-5.5", "high")).toEqual({
mode: "default",
settings: {
model: "gpt-5.5",
reasoning_effort: "high",
developer_instructions: null,
},
});
});
});

View file

@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import { collaborationModeLabel, collaborationModeToggleMessage, nextCollaborationMode } from "../../src/runtime/override-commands";
describe("runtime override commands", () => {
it("toggles between Default and Plan mode", () => {
expect(nextCollaborationMode("default")).toBe("plan");
expect(nextCollaborationMode("plan")).toBe("default");
expect(collaborationModeLabel("default")).toBe("Default");
expect(collaborationModeLabel("plan")).toBe("Plan");
});
it("formats slash command status messages", () => {
expect(collaborationModeToggleMessage("plan")).toBe("Plan mode on for subsequent turns.");
expect(collaborationModeToggleMessage("default")).toBe("Plan mode off for subsequent turns.");
});
});

View file

@ -2,8 +2,6 @@ import { describe, expect, it } from "vitest";
import type { ConfigReadResponse } from "../../src/generated/app-server/v2/ConfigReadResponse";
import type { Model } from "../../src/generated/app-server/v2/Model";
import type { Thread } from "../../src/generated/app-server/v2/Thread";
import { createAppServerDiagnostics } from "../../src/app-server/compatibility";
import {
compactModelLabel,
compactReasoningEffortLabel,
@ -11,7 +9,7 @@ import {
parseModelOverride,
parseReasoningEffortOverride,
reasoningEffortOverrideMessage,
} from "../../src/runtime/settings";
} from "../../src/runtime/override-commands";
import {
autoReviewActive,
currentApprovalsReviewer,
@ -28,17 +26,9 @@ import {
setPendingRuntimeSetting,
serviceTierLabel,
type RuntimeSnapshot,
} from "../../src/runtime/state";
} from "../../src/runtime/effective-settings";
import { readRuntimeConfig } from "../../src/runtime/config";
import { contextSummary, effectiveConfigSections, rateLimitSummary } from "../../src/runtime/view";
import {
applySharedAppServerMetadata,
applySharedModels,
applySharedThreadList,
cachedSharedAppServerMetadata,
cachedSharedThreadList,
createSharedAppServerState,
} from "../../src/runtime/shared-app-server-state";
import { contextSummary, effectiveConfigSections, rateLimitSummary } from "../../src/runtime/status-summary";
describe("runtime settings", () => {
it("parses model overrides", () => {
@ -47,38 +37,6 @@ describe("runtime settings", () => {
expect(parseModelOverride("")).toBeUndefined();
});
it("keeps shared app-server cache snapshots detached from caller-owned arrays", () => {
const sourceThreads = [threadFixture("thread-1")];
const threadState = applySharedThreadList(createSharedAppServerState(), sourceThreads);
sourceThreads.push(threadFixture("thread-2"));
const cachedThreads = cachedSharedThreadList(threadState);
expect(cachedThreads?.map((thread) => thread.id)).toEqual(["thread-1"]);
const mutableCachedThreads = cachedThreads as Thread[];
mutableCachedThreads.push(threadFixture("thread-3"));
expect(cachedSharedThreadList(threadState)?.map((thread) => thread.id)).toEqual(["thread-1"]);
const sourceModels = [modelFixture("gpt-5.5")];
const modelState = applySharedModels(createSharedAppServerState(), sourceModels);
sourceModels.push(modelFixture("gpt-5.6"));
expect(modelState.availableModels.map((model) => model.model)).toEqual(["gpt-5.5"]);
const metadataState = applySharedAppServerMetadata(createSharedAppServerState(), {
effectiveConfig: null,
availableModels: sourceModels,
availableSkills: [{ name: "skill", description: "", path: "/tmp/skill", scope: "repo", enabled: true }],
rateLimit: null,
appServerDiagnostics: {
...createAppServerDiagnostics(),
mcpServers: [{ name: "server", startupStatus: "ready", authStatus: null, toolCount: 1, message: null }],
},
});
sourceModels.push(modelFixture("gpt-5.7"));
const cachedMetadata = cachedSharedAppServerMetadata(metadataState);
expect(cachedMetadata?.availableModels.map((model) => model.model)).toEqual(["gpt-5.5", "gpt-5.6"]);
});
it("parses reasoning effort overrides", () => {
expect(parseReasoningEffortOverride("high")).toBe("high");
expect(parseReasoningEffortOverride("default")).toBeNull();
@ -131,6 +89,28 @@ describe("runtime settings", () => {
expect(pendingRuntimeSettingPayload(snapshot.requestedReasoningEffort)).toBe("low");
});
it("builds the Plan collaboration mode payload from selected runtime settings", () => {
expect(
requestedTurnRuntimeSettings(
runtimeSnapshot({
selectedCollaborationMode: "plan",
requestedModel: setPendingRuntimeSetting("gpt-5.5"),
requestedReasoningEffort: setPendingRuntimeSetting("high"),
}),
),
).toEqual({
collaborationMode: {
mode: "plan",
settings: {
model: "gpt-5.5",
reasoning_effort: "high",
developer_instructions: null,
},
},
warning: null,
});
});
it("resolves approval reviewer from requested, active, then effective config", () => {
expect(
currentApprovalsReviewer(
@ -701,31 +681,6 @@ function configLayer(config: Record<string, unknown>, profile: string | null): N
};
}
function threadFixture(id: string): Thread {
return {
id,
sessionId: "session",
forkedFromId: null,
parentThreadId: null,
preview: "",
ephemeral: false,
modelProvider: "openai",
createdAt: 1,
updatedAt: 1,
status: { type: "idle" },
path: null,
cwd: "/vault",
cliVersion: "0.0.0",
source: "appServer",
threadSource: null,
agentNickname: null,
agentRole: null,
gitInfo: null,
name: null,
turns: [],
};
}
function modelFixture(model: string): Model {
return {
id: model,

View file

@ -6,7 +6,7 @@ import type { Thread } from "../../src/generated/app-server/v2/Thread";
import type { HookMetadata } from "../../src/generated/app-server/v2/HookMetadata";
import type { Model } from "../../src/generated/app-server/v2/Model";
import type { ReasoningEffort } from "../../src/generated/app-server/ReasoningEffort";
import { findModelByIdOrName, sortedAvailableModels, supportedEffortsForModel } from "../../src/runtime/model";
import { findModelByIdOrName, sortedAvailableModels, supportedEffortsForModel } from "../../src/runtime/models";
import { CodexPanelSettingTab } from "../../src/settings/tab";
import { archivedThreadDisplayTitle } from "../../src/domain/threads/model";
import { notices } from "../mocks/obsidian";