diff --git a/docs/development.md b/docs/development.md index f6c9f694..49e2848a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -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. diff --git a/src/runtime/shared-app-server-state.ts b/src/app-server/shared-cache-state.ts similarity index 97% rename from src/runtime/shared-app-server-state.ts rename to src/app-server/shared-cache-state.ts index 239fecbc..619005b7 100644 --- a/src/runtime/shared-app-server-state.ts +++ b/src/app-server/shared-cache-state.ts @@ -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"; diff --git a/src/runtime/shared-app-server-cache.ts b/src/app-server/shared-cache.ts similarity index 98% rename from src/runtime/shared-app-server-cache.ts rename to src/app-server/shared-cache.ts index 3c18a604..69a9b187 100644 --- a/src/runtime/shared-app-server-cache.ts +++ b/src/app-server/shared-cache.ts @@ -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 }; diff --git a/src/app-server/thread-naming.ts b/src/app-server/thread-naming.ts index 048e9b7f..efdd829b 100644 --- a/src/app-server/thread-naming.ts +++ b/src/app-server/thread-naming.ts @@ -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, diff --git a/src/features/chat/app-server/diagnostics-actions.ts b/src/features/chat/app-server/diagnostics-actions.ts index ec50e7d4..8daf9a02 100644 --- a/src/features/chat/app-server/diagnostics-actions.ts +++ b/src/features/chat/app-server/diagnostics-actions.ts @@ -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"; diff --git a/src/features/chat/app-server/metadata-actions.ts b/src/features/chat/app-server/metadata-actions.ts index 33b45152..b98265c0 100644 --- a/src/features/chat/app-server/metadata-actions.ts +++ b/src/features/chat/app-server/metadata-actions.ts @@ -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 { diff --git a/src/features/chat/app-server/thread-actions.ts b/src/features/chat/app-server/thread-actions.ts index 66764b12..1e99b4a0 100644 --- a/src/features/chat/app-server/thread-actions.ts +++ b/src/features/chat/app-server/thread-actions.ts @@ -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"; diff --git a/src/features/chat/chat-host.ts b/src/features/chat/chat-host.ts index 74eac7bf..aa4f842e 100644 --- a/src/features/chat/chat-host.ts +++ b/src/features/chat/chat-host.ts @@ -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"; diff --git a/src/features/chat/chat-state.ts b/src/features/chat/chat-state.ts index 29501ec2..faba266b 100644 --- a/src/features/chat/chat-state.ts +++ b/src/features/chat/chat-state.ts @@ -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"; diff --git a/src/features/chat/composer/suggestions.ts b/src/features/chat/composer/suggestions.ts index 35344729..a6824c63 100644 --- a/src/features/chat/composer/suggestions.ts +++ b/src/features/chat/composer/suggestions.ts @@ -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"; diff --git a/src/features/chat/panel/context.ts b/src/features/chat/panel/context.ts index 55f8bcf0..26a32e0a 100644 --- a/src/features/chat/panel/context.ts +++ b/src/features/chat/panel/context.ts @@ -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"; diff --git a/src/features/chat/panel/model/composer.ts b/src/features/chat/panel/model/composer.ts index 2370b269..acd5269d 100644 --- a/src/features/chat/panel/model/composer.ts +++ b/src/features/chat/panel/model/composer.ts @@ -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"; diff --git a/src/features/chat/panel/model/runtime.ts b/src/features/chat/panel/model/runtime.ts index 3f84d084..df7c188d 100644 --- a/src/features/chat/panel/model/runtime.ts +++ b/src/features/chat/panel/model/runtime.ts @@ -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"; diff --git a/src/features/chat/panel/model/toolbar.ts b/src/features/chat/panel/model/toolbar.ts index 85b11c04..a5ccbe26 100644 --- a/src/features/chat/panel/model/toolbar.ts +++ b/src/features/chat/panel/model/toolbar.ts @@ -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"; diff --git a/src/features/chat/panel/model/types.ts b/src/features/chat/panel/model/types.ts index 3e549375..e2eaab40 100644 --- a/src/features/chat/panel/model/types.ts +++ b/src/features/chat/panel/model/types.ts @@ -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"; diff --git a/src/features/chat/panel/slots/types.ts b/src/features/chat/panel/slots/types.ts index 12bbd14f..107b2cfc 100644 --- a/src/features/chat/panel/slots/types.ts +++ b/src/features/chat/panel/slots/types.ts @@ -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"; diff --git a/src/features/chat/panel/snapshot.ts b/src/features/chat/panel/snapshot.ts index 31cf0a1f..1b14d699 100644 --- a/src/features/chat/panel/snapshot.ts +++ b/src/features/chat/panel/snapshot.ts @@ -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"; diff --git a/src/features/chat/runtime/runtime-settings-actions.ts b/src/features/chat/runtime/runtime-settings-actions.ts index 88d62c95..fd124ae4 100644 --- a/src/features/chat/runtime/runtime-settings-actions.ts +++ b/src/features/chat/runtime/runtime-settings-actions.ts @@ -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; diff --git a/src/features/chat/slash-command-execution.ts b/src/features/chat/slash-command-execution.ts index 7eb8d9ed..c001df19 100644 --- a/src/features/chat/slash-command-execution.ts +++ b/src/features/chat/slash-command-execution.ts @@ -19,7 +19,7 @@ import { parseModelOverride, parseReasoningEffortOverride, reasoningEffortOverrideMessage, -} from "../../runtime/settings"; +} from "../../runtime/override-commands"; export interface SlashCommandExecutionContext { activeThreadId: string | null; diff --git a/src/features/chat/status-lines.ts b/src/features/chat/status-lines.ts index 2aa3a9e0..c777b63f 100644 --- a/src/features/chat/status-lines.ts +++ b/src/features/chat/status-lines.ts @@ -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; diff --git a/src/features/chat/toolbar-model.ts b/src/features/chat/toolbar-model.ts index 46c5ac97..9cb7abcc 100644 --- a/src/features/chat/toolbar-model.ts +++ b/src/features/chat/toolbar-model.ts @@ -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"; diff --git a/src/features/chat/turns/composition.ts b/src/features/chat/turns/composition.ts index 5f6c0c1f..cac16baa 100644 --- a/src/features/chat/turns/composition.ts +++ b/src/features/chat/turns/composition.ts @@ -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"; diff --git a/src/features/chat/ui/toolbar.tsx b/src/features/chat/ui/toolbar.tsx index ad338ff7..91db8ffd 100644 --- a/src/features/chat/ui/toolbar.tsx +++ b/src/features/chat/ui/toolbar.tsx @@ -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"; diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index ffff4634..58402b31 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -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 { diff --git a/src/features/selection-rewrite/runner.ts b/src/features/selection-rewrite/runner.ts index a599af3a..091464b3 100644 --- a/src/features/selection-rewrite/runner.ts +++ b/src/features/selection-rewrite/runner.ts @@ -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"; diff --git a/src/features/threads-view/state.ts b/src/features/threads-view/state.ts index 9ab3fb6e..86ccea47 100644 --- a/src/features/threads-view/state.ts +++ b/src/features/threads-view/state.ts @@ -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"; diff --git a/src/features/threads-view/view.ts b/src/features/threads-view/view.ts index d9216199..cd5ffee8 100644 --- a/src/features/threads-view/view.ts +++ b/src/features/threads-view/view.ts @@ -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"; diff --git a/src/main.ts b/src/main.ts index fdd335bb..cf485927 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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"; diff --git a/src/runtime/collaboration-mode.ts b/src/runtime/collaboration-mode.ts deleted file mode 100644 index 73c060fb..00000000 --- a/src/runtime/collaboration-mode.ts +++ /dev/null @@ -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, - }, - }; -} diff --git a/src/runtime/config.ts b/src/runtime/config.ts index 6e20d5a5..78e20927 100644 --- a/src/runtime/config.ts +++ b/src/runtime/config.ts @@ -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; diff --git a/src/runtime/state.ts b/src/runtime/effective-settings.ts similarity index 95% rename from src/runtime/state.ts rename to src/runtime/effective-settings.ts index 12046550..aec749e2 100644 --- a/src/runtime/state.ts +++ b/src/runtime/effective-settings.ts @@ -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 = { 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), diff --git a/src/runtime/model.ts b/src/runtime/models.ts similarity index 100% rename from src/runtime/model.ts rename to src/runtime/models.ts diff --git a/src/runtime/settings.ts b/src/runtime/override-commands.ts similarity index 73% rename from src/runtime/settings.ts rename to src/runtime/override-commands.ts index d92c322f..84fc8271 100644 --- a/src/runtime/settings.ts +++ b/src/runtime/override-commands.ts @@ -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."; +} diff --git a/src/runtime/view.ts b/src/runtime/status-summary.ts similarity index 99% rename from src/runtime/view.ts rename to src/runtime/status-summary.ts index cc588f69..f892c4c3 100644 --- a/src/runtime/view.ts +++ b/src/runtime/status-summary.ts @@ -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; diff --git a/src/settings/model.ts b/src/settings/model.ts index ad05a452..28e45893 100644 --- a/src/settings/model.ts +++ b/src/settings/model.ts @@ -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 { diff --git a/src/settings/tab.ts b/src/settings/tab.ts index 54e9dab2..9b90e58b 100644 --- a/src/settings/tab.ts +++ b/src/settings/tab.ts @@ -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 { diff --git a/src/runtime/open-panel-snapshot.ts b/src/workspace/open-panel-snapshot.ts similarity index 100% rename from src/runtime/open-panel-snapshot.ts rename to src/workspace/open-panel-snapshot.ts diff --git a/src/workspace/panel-coordinator.ts b/src/workspace/panel-coordinator.ts index c4d84b95..411c421a 100644 --- a/src/workspace/panel-coordinator.ts +++ b/src/workspace/panel-coordinator.ts @@ -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; diff --git a/src/workspace/thread-surface-coordinator.ts b/src/workspace/thread-surface-coordinator.ts index b92a5a4a..09990c59 100644 --- a/src/workspace/thread-surface-coordinator.ts +++ b/src/workspace/thread-surface-coordinator.ts @@ -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 { diff --git a/tests/app-server/shared-cache-state.test.ts b/tests/app-server/shared-cache-state.test.ts new file mode 100644 index 00000000..55f1fdda --- /dev/null +++ b/tests/app-server/shared-cache-state.test.ts @@ -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, + }; +} diff --git a/tests/features/chat/status-lines.test.ts b/tests/features/chat/status-lines.test.ts index f849a07f..ac64fede 100644 --- a/tests/features/chat/status-lines.test.ts +++ b/tests/features/chat/status-lines.test.ts @@ -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", () => { diff --git a/tests/features/threads-view/renderer.test.ts b/tests/features/threads-view/renderer.test.ts index 612c49d0..c894d0db 100644 --- a/tests/features/threads-view/renderer.test.ts +++ b/tests/features/threads-view/renderer.test.ts @@ -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(); diff --git a/tests/main.test.ts b/tests/main.test.ts index e30b14f7..df3df1fc 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -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"; diff --git a/tests/runtime/collaboration-mode.test.ts b/tests/runtime/collaboration-mode.test.ts deleted file mode 100644 index 2d55f055..00000000 --- a/tests/runtime/collaboration-mode.test.ts +++ /dev/null @@ -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, - }, - }); - }); -}); diff --git a/tests/runtime/override-commands.test.ts b/tests/runtime/override-commands.test.ts new file mode 100644 index 00000000..0ad704ac --- /dev/null +++ b/tests/runtime/override-commands.test.ts @@ -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."); + }); +}); diff --git a/tests/runtime/runtime-settings.test.ts b/tests/runtime/runtime-settings.test.ts index 7f249f41..e651c88e 100644 --- a/tests/runtime/runtime-settings.test.ts +++ b/tests/runtime/runtime-settings.test.ts @@ -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, 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, diff --git a/tests/settings/settings-tab.test.ts b/tests/settings/settings-tab.test.ts index a5b63a1c..1ce3b63d 100644 --- a/tests/settings/settings-tab.test.ts +++ b/tests/settings/settings-tab.test.ts @@ -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";