diff --git a/src/features/chat/chat-host.ts b/src/features/chat/chat-host.ts new file mode 100644 index 00000000..c3828e28 --- /dev/null +++ b/src/features/chat/chat-host.ts @@ -0,0 +1,21 @@ +import type { Thread } from "../../generated/app-server/v2/Thread"; +import type { SharedAppServerMetadata } from "../../runtime/shared-app-server-state"; +import type { CodexPanelSettings } from "../../settings/model"; +import type { ChatTurnDiffViewState } from "./ui/turn-diff"; + +export interface CodexChatHost { + readonly settings: CodexPanelSettings; + readonly vaultPath: string; + openThreadInNewView(threadId: string): Promise; + openThreadInAvailableView(threadId: string): Promise; + focusThreadInOpenView(threadId: string): Promise; + openTurnDiff(state: ChatTurnDiffViewState): Promise; + notifyThreadArchived(threadId: string): void; + notifyThreadRenamed(threadId: string, name: string | null): void; + refreshThreadsViewLiveState(): void; + refreshSharedThreadListFromOpenSurface(): void; + refreshThreadList(fetchThreads: () => Promise): Promise; + cachedThreadList(): readonly Thread[] | null; + publishAppServerMetadata(metadata: SharedAppServerMetadata): void; + cachedAppServerMetadata(): SharedAppServerMetadata | null; +} diff --git a/src/features/chat/chat-view-controller-assembly.ts b/src/features/chat/chat-view-controller-assembly.ts new file mode 100644 index 00000000..f18aa99f --- /dev/null +++ b/src/features/chat/chat-view-controller-assembly.ts @@ -0,0 +1,524 @@ +import { Notice, type App, type Component, type EventRef, type WorkspaceLeaf } from "obsidian"; + +import type { AppServerClient } from "../../app-server/client"; +import { ConnectionManager } from "../../app-server/connection-manager"; +import type { ArchiveExportAdapter } from "../../domain/threads/export"; +import type { RuntimeSnapshot } from "../../runtime/state"; +import { currentModel } from "../../runtime/state"; +import type { ChatState, ChatStateStore } from "./chat-state"; +import type { DisplayDetailSection } from "./display/types"; +import { ChatAppServerController } from "./chat-app-server-controller"; +import { ChatComposerController } from "./chat-composer-controller"; +import { ChatController } from "./chat-controller"; +import type { CodexChatHost } from "./chat-host"; +import { ChatMessageRenderer } from "./chat-message-renderer"; +import type { ChatViewEffects } from "./view-effects"; +import type { ChatConnectionWorkTracker, ChatResumeWorkTracker, ChatViewDeferredTasks } from "./view-lifecycle"; +import { ChatThreadActionController } from "./thread-actions"; +import { ThreadHistoryLoader } from "./thread-history"; +import { ThreadRenameController } from "./thread-rename"; +import { ChatRuntimeSettingsController } from "./runtime-settings-controller"; +import { ToolbarPanelController } from "./toolbar-panel-controller"; +import { AppServerWarmupController } from "./controllers/connection/app-server-warmup-controller"; +import { ChatConnectionController } from "./controllers/connection/connection-controller"; +import { ChatReconnectController } from "./controllers/connection/reconnect-controller"; +import { PendingRequestController } from "./controllers/requests/pending-request-controller"; +import { ServerRequestResponder } from "./controllers/requests/server-request-responder"; +import { ComposerSubmissionController } from "./controllers/submission/composer-submission-controller"; +import { PlanImplementationController } from "./controllers/submission/plan-implementation-controller"; +import { SlashCommandController } from "./controllers/submission/slash-command-controller"; +import { TurnSubmissionController } from "./controllers/submission/turn-submission-controller"; +import { RestoredThreadController } from "./controllers/thread/restored-thread-controller"; +import { ThreadIdentityController } from "./controllers/thread/thread-identity-controller"; +import { ThreadResumeController } from "./controllers/thread/thread-resume-controller"; +import { ThreadSelectionController } from "./controllers/thread/thread-selection-controller"; +import type { ChatMessageScrollController } from "./controllers/view/message-scroll-controller"; +import { ChatViewOpenCloseController } from "./controllers/view/view-open-close-controller"; +import { ChatViewRenderController } from "./controllers/view/view-render-controller"; +import { ChatViewStateController } from "./controllers/view/view-state-controller"; + +export interface ChatViewControllerAssembly { + connection: ConnectionManager; + controller: ChatController; + appServer: ChatAppServerController; + connectionController: ChatConnectionController; + history: ThreadHistoryLoader; + threadResume: ThreadResumeController; + threadActions: ChatThreadActionController; + runtimeSettings: ChatRuntimeSettingsController; + restoredThread: RestoredThreadController; + threadIdentity: ThreadIdentityController; + threadRename: ThreadRenameController; + pendingRequests: PendingRequestController; + toolbarPanels: ToolbarPanelController; + reconnectActions: ChatReconnectController; + composerController: ChatComposerController; + messageRenderer: ChatMessageRenderer; + renderController: ChatViewRenderController; + openCloseController: ChatViewOpenCloseController; + viewStateController: ChatViewStateController; + appServerWarmup: AppServerWarmupController; + composerSubmission: ComposerSubmissionController; + threadSelection: ThreadSelectionController; +} + +export interface ChatViewControllerAssemblyHost { + app: App; + owner: Component; + plugin: CodexChatHost; + stateStore: ChatStateStore; + viewId: string; + deferredTasks: ChatViewDeferredTasks; + resumeWork: ChatResumeWorkTracker; + connectionWork: ChatConnectionWorkTracker; + messageScroll: ChatMessageScrollController; + effects: ChatViewEffects; + getState: () => ChatState; + getClient: () => AppServerClient | null; + setClient: (client: AppServerClient | null) => void; + getOpened: () => boolean; + setOpened: (opened: boolean) => void; + getClosing: () => boolean; + setClosing: (closing: boolean) => void; + panelRoot: () => HTMLElement | null; + renderToolbar: (toolbar: HTMLElement) => void; + renderMessages: (parent: HTMLElement) => void; + renderComposer: (parent: HTMLElement) => void; + pendingRequestsSignature: () => string; + activeComposerThreadName: () => string | null; + composerPlaceholder: () => string; + runtimeSnapshot: () => RuntimeSnapshot; + collaborationModeLabel: () => string; + connectionDiagnosticDetails: () => DisplayDetailSection[]; + mcpStatusLines: () => Promise; + modelStatusLines: () => string[]; + effortStatusLines: () => string[]; + statusSummaryLines: () => string[]; + ensureRestoredThreadLoaded: () => Promise; + startNewThread: () => Promise; + selectThread: (threadId: string) => Promise; + resumeThread: (threadId: string) => Promise; + refreshThreads: () => Promise; + refreshSkills: (forceReload?: boolean) => Promise; + publishAppServerMetadataSnapshot: () => void; + loadSharedThreadList: () => Promise; + closeToolbarPanelOnOutsidePointer: (event: PointerEvent) => void; + registerEvent: (eventRef: EventRef) => void; + registerPointerDown: (handler: (event: PointerEvent) => void) => void; + registerActiveLeafChange: (handler: (leaf: WorkspaceLeaf | null) => void) => void; + isOwnLeaf: (leaf: WorkspaceLeaf | null) => boolean; + archiveAdapter: () => ArchiveExportAdapter; +} + +export function createChatViewControllerAssembly(host: ChatViewControllerAssemblyHost): ChatViewControllerAssembly { + let controller!: ChatController; + let appServer!: ChatAppServerController; + let connectionController!: ChatConnectionController; + let history!: ThreadHistoryLoader; + let threadResume!: ThreadResumeController; + let threadActions!: ChatThreadActionController; + let runtimeSettings!: ChatRuntimeSettingsController; + let restoredThread!: RestoredThreadController; + let threadIdentity!: ThreadIdentityController; + let threadRename!: ThreadRenameController; + let pendingRequests!: PendingRequestController; + let toolbarPanels!: ToolbarPanelController; + let reconnectActions!: ChatReconnectController; + let composerController!: ChatComposerController; + let messageRenderer!: ChatMessageRenderer; + let renderController!: ChatViewRenderController; + let openCloseController!: ChatViewOpenCloseController; + let viewStateController!: ChatViewStateController; + let appServerWarmup!: AppServerWarmupController; + let turnSubmission!: TurnSubmissionController; + let slashCommands!: SlashCommandController; + let composerSubmission!: ComposerSubmissionController; + let planImplementation!: PlanImplementationController; + let threadSelection!: ThreadSelectionController; + let serverRequestResponder!: ServerRequestResponder; + + renderController = new ChatViewRenderController({ + stateStore: host.stateStore, + panelRoot: host.panelRoot, + connected: () => connection.isConnected(), + pendingRequestsSignature: host.pendingRequestsSignature, + activeComposerThreadName: host.activeComposerThreadName, + renderToolbar: host.renderToolbar, + renderMessages: host.renderMessages, + renderComposer: host.renderComposer, + clearScheduledRender: () => { + host.deferredTasks.clearRender(); + }, + }); + turnSubmission = new TurnSubmissionController({ + stateStore: host.stateStore, + vaultPath: host.plugin.vaultPath, + currentClient: host.getClient, + ensureRestoredThreadLoaded: host.ensureRestoredThreadLoaded, + startThread: () => appServer.startThread(), + notifyActiveThreadIdentityChanged: host.effects.thread.notifyIdentityChanged, + resetThreadTurnPresence: host.effects.thread.resetTurnPresence, + applyPendingThreadSettings: () => runtimeSettings.applyPendingThreadSettings(), + codexInput: (text) => composerController.codexInput(text), + setDraft: (text, options) => { + composerController.setDraft(text, options); + }, + forceMessagesToBottom: host.effects.scroll.forceBottom, + render: host.effects.render.now, + scheduleRender: host.effects.render.schedule, + setStatus: host.effects.status.set, + addSystemMessage: host.effects.status.addSystemMessage, + }); + slashCommands = new SlashCommandController({ + stateStore: host.stateStore, + currentClient: host.getClient, + codexInput: (text) => composerController.codexInput(text), + startNewThread: host.startNewThread, + resumeThread: host.selectThread, + forkThread: (threadId) => threadActions.forkThread(threadId), + rollbackThread: (threadId) => threadActions.rollbackThread(threadId), + archiveThread: (threadId) => threadActions.archiveThread(threadId), + toggleFastMode: () => runtimeSettings.toggleFastMode(), + toggleCollaborationMode: () => runtimeSettings.toggleCollaborationMode(), + toggleAutoReview: () => void runtimeSettings.toggleAutoReview(), + addSystemMessage: host.effects.status.addSystemMessage, + addStructuredSystemMessage: host.effects.status.addStructuredSystemMessage, + setStatus: host.effects.status.set, + setRequestedModel: (model) => runtimeSettings.setRequestedModel(model), + setRequestedReasoningEffort: (effort) => runtimeSettings.setRequestedReasoningEffort(effort), + statusSummaryLines: host.statusSummaryLines, + connectionDiagnosticDetails: host.connectionDiagnosticDetails, + mcpStatusLines: host.mcpStatusLines, + modelStatusLines: host.modelStatusLines, + effortStatusLines: host.effortStatusLines, + }); + messageRenderer = new ChatMessageRenderer({ + app: host.app, + owner: host.owner, + stateStore: host.stateStore, + vaultPath: host.plugin.vaultPath, + consumeScrollIntent: () => host.messageScroll.consumeIntent(), + loadOlderTurns: () => void history.loadOlder(), + rollbackThread: (threadId) => void threadActions.rollbackThread(threadId), + implementPlan: (item) => void planImplementation.implement(item), + openTurnDiff: (state) => void host.plugin.openTurnDiff(state), + pendingRequestsSignature: host.pendingRequestsSignature, + renderPendingRequests: () => pendingRequests.renderNode(), + }); + composerController = new ChatComposerController({ + app: host.app, + stateStore: host.stateStore, + viewId: host.viewId, + sendShortcut: () => host.plugin.settings.sendShortcut, + canInterrupt: () => + host.getState().turnLifecycle.kind !== "idle" && Boolean(host.getState().activeThreadId && activeTurnId(host.getState())), + composerPlaceholder: host.composerPlaceholder, + currentModelForSuggestions: () => currentModel(host.runtimeSnapshot()), + renderIfDetached: host.effects.render.now, + onDraftChange: host.effects.liveState.refresh, + onComposerResize: () => { + if (host.getState().messagesPinnedToBottom) host.effects.scroll.forceBottom(); + }, + onSubmit: () => void composerSubmission.submit(), + onNewThread: () => void host.startNewThread(), + }); + composerSubmission = new ComposerSubmissionController({ + stateStore: host.stateStore, + composer: composerController, + slashCommands, + turnSubmission, + currentClient: host.getClient, + ensureConnected: host.effects.client.ensureConnected, + setStatus: host.effects.status.set, + addSystemMessage: host.effects.status.addSystemMessage, + }); + serverRequestResponder = new ServerRequestResponder({ + currentClient: host.getClient, + }); + planImplementation = new PlanImplementationController({ + stateStore: host.stateStore, + currentClient: host.getClient, + ensureConnected: host.effects.client.ensureConnected, + sendTurnText: (text) => turnSubmission.sendTurnText(text), + }); + const connection = new ConnectionManager(() => host.plugin.settings.codexPath, host.plugin.vaultPath, { + onNotification: (notification) => { + controller.handleNotification(notification); + host.effects.liveState.refresh(); + host.effects.render.schedule(); + }, + onServerRequest: (request) => { + controller.handleServerRequest(request); + host.effects.liveState.refresh(); + host.effects.render.now(); + }, + onLog: (message) => { + controller.handleAppServerLog(message); + host.effects.render.now(); + }, + onExit: () => { + connectionController.handleExit(); + }, + }); + appServerWarmup = new AppServerWarmupController({ + deferredTasks: host.deferredTasks, + opened: host.getOpened, + closing: host.getClosing, + connected: () => connection.isConnected(), + ensureConnected: host.effects.client.ensureConnected, + }); + openCloseController = new ChatViewOpenCloseController({ + setOpened: host.setOpened, + setClosing: host.setClosing, + registerEvent: host.registerEvent, + registerComposerNoteIndexInvalidation: (register) => { + composerController.registerNoteIndexInvalidation(register); + }, + registerPointerDown: host.registerPointerDown, + registerActiveLeafChange: host.registerActiveLeafChange, + isOwnLeaf: host.isOwnLeaf, + scrollMessagesToBottomOnFocus: host.effects.scroll.bottomOnFocus, + applyCachedSharedAppServerState: () => { + applyCachedSharedAppServerState(host, appServer); + }, + render: host.effects.render.now, + scheduleDeferredAppServerWarmup: host.effects.lifecycle.scheduleDeferredAppServerWarmup, + scheduleDeferredRestoredThreadHydration: host.effects.lifecycle.scheduleDeferredRestoredThreadHydration, + closeToolbarPanelOnOutsidePointer: host.closeToolbarPanelOnOutsidePointer, + invalidateConnectionWork: host.effects.lifecycle.invalidateConnectionWork, + invalidateResumeWork: host.effects.lifecycle.invalidateResumeWork, + clearDeferredTasks: () => { + host.deferredTasks.clearAll(); + }, + panelRoot: host.panelRoot, + disposeMessages: () => { + messageRenderer.dispose(); + }, + disposeComposer: () => { + composerController.dispose(); + }, + disconnect: () => { + connection.disconnect(); + }, + clearClient: host.effects.client.clear, + refreshLiveState: host.effects.liveState.refresh, + deferRefreshLiveState: host.effects.liveState.deferRefresh, + }); + controller = new ChatController(host.stateStore, { + refreshThreads: () => { + void host.refreshThreads(); + }, + refreshSkills: (forceReload) => void host.refreshSkills(forceReload), + publishAppServerMetadata: host.publishAppServerMetadataSnapshot, + maybeNameThread: (threadId, turn) => { + threadRename.maybeAutoNameThread(threadId, turn); + }, + notifyThreadArchived: host.plugin.notifyThreadArchived.bind(host.plugin), + notifyThreadRenamed: host.plugin.notifyThreadRenamed.bind(host.plugin), + recordMcpStartupStatus: (name, status, message) => { + appServer.recordMcpStartupStatus(name, status, message); + host.effects.render.schedule(); + }, + respondToServerRequest: (requestId, result) => serverRequestResponder.respond(requestId, result), + rejectServerRequest: (requestId, code, message) => serverRequestResponder.reject(requestId, code, message), + }); + pendingRequests = new PendingRequestController({ + stateStore: host.stateStore, + controller, + composerHasFocus: () => composerController.hasFocus(), + refreshLiveState: host.effects.liveState.refresh, + render: host.effects.render.now, + }); + appServer = new ChatAppServerController({ + stateStore: host.stateStore, + vaultPath: host.plugin.vaultPath, + currentClient: () => connection.currentClient(), + runtimeSnapshot: host.runtimeSnapshot, + forceMessagesToBottom: host.effects.scroll.forceBottom, + publishAppServerMetadata: (metadata) => { + host.plugin.publishAppServerMetadata(metadata); + }, + }); + connectionController = new ChatConnectionController({ + stateStore: host.stateStore, + connection, + connectionWork: host.connectionWork, + appServer, + setClient: host.setClient, + invalidateResumeWork: host.effects.lifecycle.invalidateResumeWork, + loadSharedThreadList: host.loadSharedThreadList, + scheduleDeferredDiagnostics: host.effects.lifecycle.scheduleDeferredDiagnostics, + clearDeferredDiagnostics: host.effects.lifecycle.clearDeferredDiagnostics, + refreshTabHeader: host.effects.thread.refreshTabHeader, + resetThreadTurnPresence: host.effects.thread.resetTurnPresence, + setStatus: host.effects.status.set, + addSystemMessage: host.effects.status.addSystemMessage, + refreshLiveState: host.effects.liveState.refresh, + render: host.effects.render.now, + scheduleRender: host.effects.render.schedule, + notifyConnectionFailed: () => { + new Notice("Codex app-server connection failed."); + }, + }); + history = new ThreadHistoryLoader({ + stateStore: host.stateStore, + currentClient: host.getClient, + render: host.effects.render.now, + addSystemMessage: host.effects.status.addSystemMessage, + forceMessagesToBottom: host.effects.scroll.forceBottom, + keepCurrentScrollPosition: host.effects.scroll.preservePosition, + setThreadTurnPresence: host.effects.thread.resetTurnPresence, + }); + threadActions = new ChatThreadActionController({ + stateStore: host.stateStore, + vaultPath: host.plugin.vaultPath, + settings: () => host.plugin.settings, + archiveAdapter: host.archiveAdapter, + ensureConnected: host.effects.client.ensureConnected, + currentClient: host.getClient, + history, + addSystemMessage: host.effects.status.addSystemMessage, + setStatus: host.effects.status.set, + setComposerText: host.effects.composer.setText, + openThreadInNewView: (threadId) => host.plugin.openThreadInNewView(threadId), + notifyThreadArchived: host.plugin.notifyThreadArchived.bind(host.plugin), + notifyThreadRenamed: (threadId, name) => { + host.plugin.notifyThreadRenamed(threadId, name); + }, + notifyActiveThreadIdentityChanged: host.effects.thread.notifyIdentityChanged, + refreshThreads: host.refreshThreads, + refreshSharedThreadListFromOpenSurface: () => { + host.plugin.refreshSharedThreadListFromOpenSurface(); + }, + }); + toolbarPanels = new ToolbarPanelController({ + stateStore: host.stateStore, + threadActions, + scheduleRender: host.effects.render.schedule, + }); + threadSelection = new ThreadSelectionController({ + stateStore: host.stateStore, + closeForThreadSelection: () => { + toolbarPanels.closeForThreadSelection(); + }, + focusThreadInOpenView: (threadId) => host.plugin.focusThreadInOpenView(threadId), + resumeThread: host.resumeThread, + addSystemMessage: host.effects.status.addSystemMessage, + }); + reconnectActions = new ChatReconnectController({ + stateStore: host.stateStore, + activeThreadId: () => host.getState().activeThreadId, + invalidateConnectionWork: host.effects.lifecycle.invalidateConnectionWork, + invalidateResumeWork: host.effects.lifecycle.invalidateResumeWork, + clearDeferredDiagnostics: host.effects.lifecycle.clearDeferredDiagnostics, + reconnect: () => { + connection.reconnect(); + }, + clearClient: host.effects.client.clear, + setStatus: host.effects.status.set, + render: host.effects.render.now, + ensureConnected: host.effects.client.ensureConnected, + resumeThread: host.resumeThread, + addSystemMessage: host.effects.status.addSystemMessage, + }); + runtimeSettings = new ChatRuntimeSettingsController({ + stateStore: host.stateStore, + currentClient: host.getClient, + runtimeSnapshot: host.runtimeSnapshot, + collaborationModeLabel: host.collaborationModeLabel, + addSystemMessage: host.effects.status.addSystemMessage, + }); + restoredThread = new RestoredThreadController({ + deferredTasks: host.deferredTasks, + opened: host.getOpened, + resumeThread: host.resumeThread, + invalidateResumeWork: host.effects.lifecycle.invalidateResumeWork, + dispatch: host.effects.state.dispatch, + systemItem: host.effects.state.systemItem, + setStatus: host.effects.status.set, + refreshTabHeader: host.effects.thread.refreshTabHeader, + }); + viewStateController = new ChatViewStateController({ + invalidateResumeWork: host.effects.lifecycle.invalidateResumeWork, + clearRestoredThreadLifecycle: host.effects.thread.clearRestoredLifecycle, + clearDeferredRestoredThreadHydration: host.effects.lifecycle.clearDeferredRestoredThreadHydration, + scheduleDeferredAppServerWarmup: host.effects.lifecycle.scheduleDeferredAppServerWarmup, + restoreThreadPlaceholder: host.effects.thread.restorePlaceholder, + }); + threadResume = new ThreadResumeController({ + stateStore: host.stateStore, + vaultPath: host.plugin.vaultPath, + resumeWork: host.resumeWork, + history, + restoredThread, + currentClient: host.getClient, + ensureConnected: host.effects.client.ensureConnected, + closing: host.getClosing, + systemItem: host.effects.state.systemItem, + resetThreadTurnPresence: host.effects.thread.resetTurnPresence, + clearDeferredRestoredThreadHydration: host.effects.lifecycle.clearDeferredRestoredThreadHydration, + notifyActiveThreadIdentityChanged: host.effects.thread.notifyIdentityChanged, + addSystemMessage: host.effects.status.addSystemMessage, + forceMessagesToBottom: host.effects.scroll.forceBottom, + render: host.effects.render.now, + refreshLiveState: host.effects.liveState.refresh, + }); + threadIdentity = new ThreadIdentityController({ + stateStore: host.stateStore, + restoredThread, + invalidateResumeWork: host.effects.lifecycle.invalidateResumeWork, + clearDeferredRestoredThreadHydration: host.effects.lifecycle.clearDeferredRestoredThreadHydration, + resetThreadTurnPresence: host.effects.thread.resetTurnPresence, + notifyActiveThreadIdentityChanged: host.effects.thread.notifyIdentityChanged, + refreshTabHeader: host.effects.thread.refreshTabHeader, + refreshLiveState: host.effects.liveState.refresh, + render: host.effects.render.now, + }); + threadRename = new ThreadRenameController({ + stateStore: host.stateStore, + vaultPath: host.plugin.vaultPath, + settings: () => host.plugin.settings, + ensureConnected: host.effects.client.ensureConnected, + currentClient: () => connection.currentClient(), + refreshThreads: host.refreshThreads, + render: host.effects.render.shellSlots, + addSystemMessage: host.effects.status.addSystemMessage, + notifyThreadRenamed: host.plugin.notifyThreadRenamed.bind(host.plugin), + }); + + return { + connection, + controller, + appServer, + connectionController, + history, + threadResume, + threadActions, + runtimeSettings, + restoredThread, + threadIdentity, + threadRename, + pendingRequests, + toolbarPanels, + reconnectActions, + composerController, + messageRenderer, + renderController, + openCloseController, + viewStateController, + appServerWarmup, + composerSubmission, + threadSelection, + }; +} + +function activeTurnId(state: ChatState): string | null { + return state.turnLifecycle.kind === "running" ? state.turnLifecycle.turnId : null; +} + +function applyCachedSharedAppServerState(host: ChatViewControllerAssemblyHost, appServer: ChatAppServerController): void { + const threads = host.plugin.cachedThreadList(); + if (threads) appServer.applyThreadList(threads); + const metadata = host.plugin.cachedAppServerMetadata(); + if (metadata) appServer.applyAppServerMetadata(metadata); +} diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index ed855159..aea1872f 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -1,7 +1,6 @@ -import { ItemView, Notice, type ViewStateResult, type WorkspaceLeaf } from "obsidian"; +import { ItemView, type ViewStateResult, type WorkspaceLeaf } from "obsidian"; import type { AppServerClient } from "../../app-server/client"; -import { ConnectionManager } from "../../app-server/connection-manager"; import { VIEW_TYPE_CODEX_PANEL } from "../../constants"; import { createSystemItem } from "./display/system"; import type { DisplayDetailSection, DisplayItem } from "./display/types"; @@ -9,24 +8,15 @@ 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 { ChatController } from "./chat-controller"; -import { currentModel, type RuntimeSnapshot } from "../../runtime/state"; -import { ChatAppServerController } from "./chat-app-server-controller"; -import { ThreadHistoryLoader } from "./thread-history"; -import { ThreadRenameController } from "./thread-rename"; +import type { RuntimeSnapshot } from "../../runtime/state"; import { pendingRequestsSignature as requestStateSignature } from "./request-state"; -import type { CodexPanelSettings } from "../../settings/model"; -import { ChatComposerController } from "./chat-composer-controller"; import { activeTurnId, chatTurnBusy, createChatStateStore, type ChatState, type ChatAction } from "./chat-state"; import { renderToolbar } from "./ui/toolbar"; import type { ToolbarViewModel } from "./toolbar-model"; -import type { ChatTurnDiffViewState } from "./ui/turn-diff"; -import { ChatMessageRenderer } from "./chat-message-renderer"; import type { OpenCodexPanelSnapshot } from "../../runtime/open-panel-snapshot"; import type { SharedAppServerMetadata } from "../../runtime/shared-app-server-state"; -import { ChatThreadActionController } from "./thread-actions"; -import { ChatRuntimeSettingsController } from "./runtime-settings-controller"; -import { RestoredThreadController } from "./controllers/thread/restored-thread-controller"; +import type { RestoredThreadController } from "./controllers/thread/restored-thread-controller"; +import type { CodexChatHost } from "./chat-host"; import { activeComposerThreadName as buildActiveComposerThreadName, activeThreadTitle as buildActiveThreadTitle, @@ -47,75 +37,41 @@ import { ChatViewDeferredTasks, type ChatViewRenderScheduleOptions, } from "./view-lifecycle"; -import { ChatConnectionController } from "./controllers/connection/connection-controller"; -import { ChatReconnectController } from "./controllers/connection/reconnect-controller"; -import { AppServerWarmupController } from "./controllers/connection/app-server-warmup-controller"; -import { PendingRequestController } from "./controllers/requests/pending-request-controller"; -import { ServerRequestResponder } from "./controllers/requests/server-request-responder"; -import { ComposerSubmissionController } from "./controllers/submission/composer-submission-controller"; -import { PlanImplementationController } from "./controllers/submission/plan-implementation-controller"; -import { SlashCommandController } from "./controllers/submission/slash-command-controller"; -import { TurnSubmissionController } from "./controllers/submission/turn-submission-controller"; -import { ThreadIdentityController } from "./controllers/thread/thread-identity-controller"; -import { ThreadResumeController } from "./controllers/thread/thread-resume-controller"; -import { ThreadSelectionController } from "./controllers/thread/thread-selection-controller"; import { ChatMessageScrollController } from "./controllers/view/message-scroll-controller"; -import { ChatViewOpenCloseController } from "./controllers/view/view-open-close-controller"; -import { ChatViewRenderController } from "./controllers/view/view-render-controller"; -import { ChatViewStateController } from "./controllers/view/view-state-controller"; -import { ToolbarPanelController } from "./toolbar-panel-controller"; import { createChatViewEffects, type ChatViewEffects } from "./view-effects"; +import { createChatViewControllerAssembly, type ChatViewControllerAssembly } from "./chat-view-controller-assembly"; -export interface CodexChatHost { - readonly settings: CodexPanelSettings; - readonly vaultPath: string; - openThreadInNewView(threadId: string): Promise; - openThreadInAvailableView(threadId: string): Promise; - focusThreadInOpenView(threadId: string): Promise; - openTurnDiff(state: ChatTurnDiffViewState): Promise; - notifyThreadArchived(threadId: string): void; - notifyThreadRenamed(threadId: string, name: string | null): void; - refreshThreadsViewLiveState(): void; - refreshSharedThreadListFromOpenSurface(): void; - refreshThreadList(fetchThreads: () => Promise): Promise; - cachedThreadList(): readonly Thread[] | null; - publishAppServerMetadata(metadata: SharedAppServerMetadata): void; - cachedAppServerMetadata(): SharedAppServerMetadata | null; -} +export type { CodexChatHost } from "./chat-host"; export class CodexChatView extends ItemView { private client: AppServerClient | null = null; - private readonly connection: ConnectionManager; - private readonly controller: ChatController; - private readonly appServer: ChatAppServerController; - private readonly connectionController: ChatConnectionController; - private readonly history: ThreadHistoryLoader; - private readonly threadResume: ThreadResumeController; - private readonly threadActions: ChatThreadActionController; - private readonly runtimeSettings: ChatRuntimeSettingsController; - private readonly restoredThread: RestoredThreadController; - private readonly threadIdentity: ThreadIdentityController; - private readonly threadRename: ThreadRenameController; - private readonly pendingRequests: PendingRequestController; - private readonly toolbarPanels: ToolbarPanelController; - private readonly reconnectActions: ChatReconnectController; + private readonly connection: ChatViewControllerAssembly["connection"]; + private readonly controller: ChatViewControllerAssembly["controller"]; + private readonly appServer: ChatViewControllerAssembly["appServer"]; + private readonly connectionController: ChatViewControllerAssembly["connectionController"]; + private readonly history: ChatViewControllerAssembly["history"]; + private readonly threadResume: ChatViewControllerAssembly["threadResume"]; + private readonly threadActions: ChatViewControllerAssembly["threadActions"]; + private readonly runtimeSettings: ChatViewControllerAssembly["runtimeSettings"]; + private readonly restoredThread: ChatViewControllerAssembly["restoredThread"]; + private readonly threadIdentity: ChatViewControllerAssembly["threadIdentity"]; + private readonly threadRename: ChatViewControllerAssembly["threadRename"]; + private readonly pendingRequests: ChatViewControllerAssembly["pendingRequests"]; + private readonly toolbarPanels: ChatViewControllerAssembly["toolbarPanels"]; + private readonly reconnectActions: ChatViewControllerAssembly["reconnectActions"]; private readonly chatState = createChatStateStore(); private readonly viewId = `codex-panel-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; private readonly deferredTasks: ChatViewDeferredTasks; private readonly effects: ChatViewEffects; - private readonly composerController: ChatComposerController; - private readonly messageRenderer: ChatMessageRenderer; - private readonly renderController: ChatViewRenderController; - private readonly openCloseController: ChatViewOpenCloseController; - private readonly viewStateController: ChatViewStateController; - private readonly appServerWarmup: AppServerWarmupController; + private readonly composerController: ChatViewControllerAssembly["composerController"]; + private readonly messageRenderer: ChatViewControllerAssembly["messageRenderer"]; + private readonly renderController: ChatViewControllerAssembly["renderController"]; + private readonly openCloseController: ChatViewControllerAssembly["openCloseController"]; + private readonly viewStateController: ChatViewControllerAssembly["viewStateController"]; + private readonly appServerWarmup: ChatViewControllerAssembly["appServerWarmup"]; private readonly messageScroll: ChatMessageScrollController; - private readonly turnSubmission: TurnSubmissionController; - private readonly slashCommands: SlashCommandController; - private readonly composerSubmission: ComposerSubmissionController; - private readonly planImplementation: PlanImplementationController; - private readonly threadSelection: ThreadSelectionController; - private readonly serverRequestResponder: ServerRequestResponder; + private readonly composerSubmission: ChatViewControllerAssembly["composerSubmission"]; + private readonly threadSelection: ChatViewControllerAssembly["threadSelection"]; private readonly connectionWork = new ChatConnectionWorkTracker(); private readonly resumeWork: ChatResumeWorkTracker; private opened = false; @@ -137,12 +93,31 @@ export class CodexChatView extends ItemView { }, }); this.effects = this.createEffects(); - this.renderController = new ChatViewRenderController({ + const controllers = createChatViewControllerAssembly({ + app: this.app, + owner: this, + plugin: this.plugin, stateStore: this.chatState, + viewId: this.viewId, + deferredTasks: this.deferredTasks, + resumeWork: this.resumeWork, + connectionWork: this.connectionWork, + messageScroll: this.messageScroll, + effects: this.effects, + getState: () => this.state, + getClient: () => this.client, + setClient: (client) => { + this.client = client; + }, + getOpened: () => this.opened, + setOpened: (opened) => { + this.opened = opened; + }, + getClosing: () => this.closing, + setClosing: (closing) => { + this.closing = closing; + }, panelRoot: () => this.panelRoot(), - connected: () => this.connection.isConnected(), - pendingRequestsSignature: () => this.pendingRequestsSignature(), - activeComposerThreadName: () => this.activeComposerThreadName(), renderToolbar: (toolbar) => { this.renderToolbar(toolbar); }, @@ -152,368 +127,63 @@ export class CodexChatView extends ItemView { renderComposer: (parent) => { this.renderComposer(parent); }, - clearScheduledRender: () => { - this.deferredTasks.clearRender(); - }, - }); - this.turnSubmission = new TurnSubmissionController({ - stateStore: this.chatState, - vaultPath: this.plugin.vaultPath, - currentClient: () => this.client, - ensureRestoredThreadLoaded: () => this.ensureRestoredThreadLoaded(), - startThread: () => this.appServer.startThread(), - notifyActiveThreadIdentityChanged: this.effects.thread.notifyIdentityChanged, - resetThreadTurnPresence: this.effects.thread.resetTurnPresence, - applyPendingThreadSettings: () => this.runtimeSettings.applyPendingThreadSettings(), - codexInput: (text) => this.composerController.codexInput(text), - setDraft: (text, options) => { - this.composerController.setDraft(text, options); - }, - forceMessagesToBottom: this.effects.scroll.forceBottom, - render: this.effects.render.now, - scheduleRender: this.effects.render.schedule, - setStatus: this.effects.status.set, - addSystemMessage: this.effects.status.addSystemMessage, - }); - this.slashCommands = new SlashCommandController({ - stateStore: this.chatState, - currentClient: () => this.client, - codexInput: (text) => this.composerController.codexInput(text), - startNewThread: () => this.startNewThread(), - resumeThread: (threadId) => this.selectThread(threadId), - forkThread: (threadId) => this.threadActions.forkThread(threadId), - rollbackThread: (threadId) => this.threadActions.rollbackThread(threadId), - archiveThread: (threadId) => this.threadActions.archiveThread(threadId), - toggleFastMode: () => this.runtimeSettings.toggleFastMode(), - toggleCollaborationMode: () => this.runtimeSettings.toggleCollaborationMode(), - toggleAutoReview: () => void this.runtimeSettings.toggleAutoReview(), - addSystemMessage: this.effects.status.addSystemMessage, - addStructuredSystemMessage: this.effects.status.addStructuredSystemMessage, - setStatus: this.effects.status.set, - setRequestedModel: (model) => this.runtimeSettings.setRequestedModel(model), - setRequestedReasoningEffort: (effort) => this.runtimeSettings.setRequestedReasoningEffort(effort), - statusSummaryLines: () => this.statusSummaryLines(), + pendingRequestsSignature: () => this.pendingRequestsSignature(), + activeComposerThreadName: () => this.activeComposerThreadName(), + composerPlaceholder: () => this.composerPlaceholder(), + runtimeSnapshot: () => this.runtimeSnapshot(), + collaborationModeLabel: () => this.collaborationModeLabel(), connectionDiagnosticDetails: () => this.connectionDiagnosticDetails(), mcpStatusLines: () => this.mcpStatusLines(), modelStatusLines: () => this.modelStatusLines(), effortStatusLines: () => this.effortStatusLines(), - }); - this.messageRenderer = new ChatMessageRenderer({ - app: this.app, - owner: this, - stateStore: this.chatState, - vaultPath: this.plugin.vaultPath, - consumeScrollIntent: () => this.messageScroll.consumeIntent(), - loadOlderTurns: () => void this.history.loadOlder(), - rollbackThread: (threadId) => void this.threadActions.rollbackThread(threadId), - implementPlan: (item) => void this.planImplementation.implement(item), - openTurnDiff: (state) => void this.plugin.openTurnDiff(state), - pendingRequestsSignature: () => this.pendingRequestsSignature(), - renderPendingRequests: () => this.pendingRequests.renderNode(), - }); - this.composerController = new ChatComposerController({ - app: this.app, - stateStore: this.chatState, - viewId: this.viewId, - sendShortcut: () => this.plugin.settings.sendShortcut, - canInterrupt: () => this.turnBusy && Boolean(this.state.activeThreadId && this.activeTurnId), - composerPlaceholder: () => this.composerPlaceholder(), - currentModelForSuggestions: () => currentModel(this.runtimeSnapshot()), - renderIfDetached: this.effects.render.now, - onDraftChange: this.effects.liveState.refresh, - onComposerResize: () => { - if (this.state.messagesPinnedToBottom) this.effects.scroll.forceBottom(); + statusSummaryLines: () => this.statusSummaryLines(), + ensureRestoredThreadLoaded: () => this.ensureRestoredThreadLoaded(), + startNewThread: () => this.startNewThread(), + selectThread: (threadId) => this.selectThread(threadId), + resumeThread: (threadId) => this.resumeThread(threadId), + refreshThreads: () => this.refreshThreads(), + refreshSkills: (forceReload) => this.refreshSkills(forceReload), + publishAppServerMetadataSnapshot: () => { + this.publishAppServerMetadataSnapshot(); }, - onSubmit: () => void this.composerSubmission.submit(), - onNewThread: () => void this.startNewThread(), - }); - this.composerSubmission = new ComposerSubmissionController({ - stateStore: this.chatState, - composer: this.composerController, - slashCommands: this.slashCommands, - turnSubmission: this.turnSubmission, - currentClient: () => this.client, - ensureConnected: this.effects.client.ensureConnected, - setStatus: this.effects.status.set, - addSystemMessage: this.effects.status.addSystemMessage, - }); - this.serverRequestResponder = new ServerRequestResponder({ - currentClient: () => this.client, - }); - this.planImplementation = new PlanImplementationController({ - stateStore: this.chatState, - currentClient: () => this.client, - ensureConnected: this.effects.client.ensureConnected, - sendTurnText: (text) => this.turnSubmission.sendTurnText(text), - }); - this.connection = new ConnectionManager(() => this.plugin.settings.codexPath, this.plugin.vaultPath, { - onNotification: (notification) => { - this.controller.handleNotification(notification); - this.effects.liveState.refresh(); - this.effects.render.schedule(); - }, - onServerRequest: (request) => { - this.controller.handleServerRequest(request); - this.effects.liveState.refresh(); - this.effects.render.now(); - }, - onLog: (message) => { - this.controller.handleAppServerLog(message); - this.effects.render.now(); - }, - onExit: () => { - this.connectionController.handleExit(); - }, - }); - this.appServerWarmup = new AppServerWarmupController({ - deferredTasks: this.deferredTasks, - opened: () => this.opened, - closing: () => this.closing, - connected: () => this.connection.isConnected(), - ensureConnected: this.effects.client.ensureConnected, - }); - this.openCloseController = new ChatViewOpenCloseController({ - setOpened: (opened) => { - this.opened = opened; - }, - setClosing: (closing) => { - this.closing = closing; + loadSharedThreadList: () => this.loadSharedThreadList(), + closeToolbarPanelOnOutsidePointer: (event) => { + this.closeToolbarPanelOnOutsidePointer(event); }, registerEvent: (eventRef) => { this.registerEvent(eventRef); }, - registerComposerNoteIndexInvalidation: (register) => { - this.composerController.registerNoteIndexInvalidation(register); - }, registerPointerDown: (handler) => { this.registerDomEvent(this.containerEl.doc, "pointerdown", handler); }, registerActiveLeafChange: (handler) => { this.registerEvent(this.app.workspace.on("active-leaf-change", handler)); }, - isOwnLeaf: (leaf) => leaf === this.leaf, - scrollMessagesToBottomOnFocus: this.effects.scroll.bottomOnFocus, - applyCachedSharedAppServerState: () => { - this.applyCachedSharedAppServerState(); - }, - render: this.effects.render.now, - scheduleDeferredAppServerWarmup: this.effects.lifecycle.scheduleDeferredAppServerWarmup, - scheduleDeferredRestoredThreadHydration: this.effects.lifecycle.scheduleDeferredRestoredThreadHydration, - closeToolbarPanelOnOutsidePointer: (event) => { - this.closeToolbarPanelOnOutsidePointer(event); - }, - invalidateConnectionWork: this.effects.lifecycle.invalidateConnectionWork, - invalidateResumeWork: this.effects.lifecycle.invalidateResumeWork, - clearDeferredTasks: () => { - this.deferredTasks.clearAll(); - }, - panelRoot: () => this.panelRoot(), - disposeMessages: () => { - this.messageRenderer.dispose(); - }, - disposeComposer: () => { - this.composerController.dispose(); - }, - disconnect: () => { - this.connection.disconnect(); - }, - clearClient: this.effects.client.clear, - refreshLiveState: this.effects.liveState.refresh, - deferRefreshLiveState: this.effects.liveState.deferRefresh, - }); - this.controller = new ChatController(this.chatState, { - refreshThreads: () => { - void this.refreshThreads(); - }, - refreshSkills: (forceReload) => void this.refreshSkills(forceReload), - publishAppServerMetadata: () => { - this.publishAppServerMetadataSnapshot(); - }, - maybeNameThread: (threadId, turn) => { - this.threadRename.maybeAutoNameThread(threadId, turn); - }, - notifyThreadArchived: (threadId) => { - this.plugin.notifyThreadArchived(threadId); - }, - notifyThreadRenamed: (threadId, name) => { - this.plugin.notifyThreadRenamed(threadId, name); - }, - recordMcpStartupStatus: (name, status, message) => { - this.appServer.recordMcpStartupStatus(name, status, message); - this.effects.render.schedule(); - }, - respondToServerRequest: (requestId, result) => this.serverRequestResponder.respond(requestId, result), - rejectServerRequest: (requestId, code, message) => this.serverRequestResponder.reject(requestId, code, message), - }); - this.pendingRequests = new PendingRequestController({ - stateStore: this.chatState, - controller: this.controller, - composerHasFocus: () => this.composerController.hasFocus(), - refreshLiveState: this.effects.liveState.refresh, - render: this.effects.render.now, - }); - this.appServer = new ChatAppServerController({ - stateStore: this.chatState, - vaultPath: this.plugin.vaultPath, - currentClient: () => this.connection.currentClient(), - runtimeSnapshot: () => this.runtimeSnapshot(), - forceMessagesToBottom: this.effects.scroll.forceBottom, - publishAppServerMetadata: (metadata) => { - this.plugin.publishAppServerMetadata(metadata); - }, - }); - this.connectionController = new ChatConnectionController({ - stateStore: this.chatState, - connection: this.connection, - connectionWork: this.connectionWork, - appServer: this.appServer, - setClient: (client) => { - this.client = client; - }, - invalidateResumeWork: this.effects.lifecycle.invalidateResumeWork, - loadSharedThreadList: () => this.loadSharedThreadList(), - scheduleDeferredDiagnostics: this.effects.lifecycle.scheduleDeferredDiagnostics, - clearDeferredDiagnostics: this.effects.lifecycle.clearDeferredDiagnostics, - refreshTabHeader: this.effects.thread.refreshTabHeader, - resetThreadTurnPresence: this.effects.thread.resetTurnPresence, - setStatus: this.effects.status.set, - addSystemMessage: this.effects.status.addSystemMessage, - refreshLiveState: this.effects.liveState.refresh, - render: this.effects.render.now, - scheduleRender: this.effects.render.schedule, - notifyConnectionFailed: () => { - new Notice("Codex app-server connection failed."); - }, - }); - this.history = new ThreadHistoryLoader({ - stateStore: this.chatState, - currentClient: () => this.client, - render: this.effects.render.now, - addSystemMessage: this.effects.status.addSystemMessage, - forceMessagesToBottom: this.effects.scroll.forceBottom, - keepCurrentScrollPosition: this.effects.scroll.preservePosition, - setThreadTurnPresence: this.effects.thread.resetTurnPresence, - }); - this.threadActions = new ChatThreadActionController({ - stateStore: this.chatState, - vaultPath: this.plugin.vaultPath, - settings: () => this.plugin.settings, + isOwnLeaf: (candidateLeaf) => candidateLeaf === this.leaf, archiveAdapter: () => this.app.vault.adapter, - ensureConnected: () => this.ensureConnected(), - currentClient: () => this.client, - history: this.history, - addSystemMessage: this.effects.status.addSystemMessage, - setStatus: this.effects.status.set, - setComposerText: this.effects.composer.setText, - openThreadInNewView: (threadId) => this.plugin.openThreadInNewView(threadId), - notifyThreadArchived: (threadId) => { - this.plugin.notifyThreadArchived(threadId); - }, - notifyThreadRenamed: (threadId, name) => { - this.plugin.notifyThreadRenamed(threadId, name); - }, - notifyActiveThreadIdentityChanged: this.effects.thread.notifyIdentityChanged, - refreshThreads: () => this.refreshThreads(), - refreshSharedThreadListFromOpenSurface: () => { - this.plugin.refreshSharedThreadListFromOpenSurface(); - }, - }); - this.toolbarPanels = new ToolbarPanelController({ - stateStore: this.chatState, - threadActions: this.threadActions, - scheduleRender: this.effects.render.schedule, - }); - this.threadSelection = new ThreadSelectionController({ - stateStore: this.chatState, - closeForThreadSelection: () => { - this.toolbarPanels.closeForThreadSelection(); - }, - focusThreadInOpenView: (threadId) => this.plugin.focusThreadInOpenView(threadId), - resumeThread: (threadId) => this.resumeThread(threadId), - addSystemMessage: this.effects.status.addSystemMessage, - }); - this.reconnectActions = new ChatReconnectController({ - stateStore: this.chatState, - activeThreadId: () => this.state.activeThreadId, - invalidateConnectionWork: this.effects.lifecycle.invalidateConnectionWork, - invalidateResumeWork: this.effects.lifecycle.invalidateResumeWork, - clearDeferredDiagnostics: this.effects.lifecycle.clearDeferredDiagnostics, - reconnect: () => { - this.connection.reconnect(); - }, - clearClient: this.effects.client.clear, - setStatus: this.effects.status.set, - render: this.effects.render.now, - ensureConnected: this.effects.client.ensureConnected, - resumeThread: (threadId) => this.resumeThread(threadId), - addSystemMessage: this.effects.status.addSystemMessage, - }); - this.runtimeSettings = new ChatRuntimeSettingsController({ - stateStore: this.chatState, - currentClient: () => this.client, - runtimeSnapshot: () => this.runtimeSnapshot(), - collaborationModeLabel: () => this.collaborationModeLabel(), - addSystemMessage: this.effects.status.addSystemMessage, - }); - this.restoredThread = new RestoredThreadController({ - deferredTasks: this.deferredTasks, - opened: () => this.opened, - resumeThread: (threadId) => this.resumeThread(threadId), - invalidateResumeWork: this.effects.lifecycle.invalidateResumeWork, - dispatch: this.effects.state.dispatch, - systemItem: this.effects.state.systemItem, - setStatus: this.effects.status.set, - refreshTabHeader: this.effects.thread.refreshTabHeader, - }); - this.viewStateController = new ChatViewStateController({ - invalidateResumeWork: this.effects.lifecycle.invalidateResumeWork, - clearRestoredThreadLifecycle: this.effects.thread.clearRestoredLifecycle, - clearDeferredRestoredThreadHydration: this.effects.lifecycle.clearDeferredRestoredThreadHydration, - scheduleDeferredAppServerWarmup: this.effects.lifecycle.scheduleDeferredAppServerWarmup, - restoreThreadPlaceholder: this.effects.thread.restorePlaceholder, - }); - this.threadResume = new ThreadResumeController({ - stateStore: this.chatState, - vaultPath: this.plugin.vaultPath, - resumeWork: this.resumeWork, - history: this.history, - restoredThread: this.restoredThread, - currentClient: () => this.client, - ensureConnected: this.effects.client.ensureConnected, - closing: () => this.closing, - systemItem: this.effects.state.systemItem, - resetThreadTurnPresence: this.effects.thread.resetTurnPresence, - clearDeferredRestoredThreadHydration: this.effects.lifecycle.clearDeferredRestoredThreadHydration, - notifyActiveThreadIdentityChanged: this.effects.thread.notifyIdentityChanged, - addSystemMessage: this.effects.status.addSystemMessage, - forceMessagesToBottom: this.effects.scroll.forceBottom, - render: this.effects.render.now, - refreshLiveState: this.effects.liveState.refresh, - }); - this.threadIdentity = new ThreadIdentityController({ - stateStore: this.chatState, - restoredThread: this.restoredThread, - invalidateResumeWork: this.effects.lifecycle.invalidateResumeWork, - clearDeferredRestoredThreadHydration: this.effects.lifecycle.clearDeferredRestoredThreadHydration, - resetThreadTurnPresence: this.effects.thread.resetTurnPresence, - notifyActiveThreadIdentityChanged: this.effects.thread.notifyIdentityChanged, - refreshTabHeader: this.effects.thread.refreshTabHeader, - refreshLiveState: this.effects.liveState.refresh, - render: this.effects.render.now, - }); - this.threadRename = new ThreadRenameController({ - stateStore: this.chatState, - vaultPath: this.plugin.vaultPath, - settings: () => this.plugin.settings, - ensureConnected: () => this.ensureConnected(), - currentClient: () => this.connection.currentClient(), - refreshThreads: () => this.refreshThreads(), - render: this.effects.render.shellSlots, - addSystemMessage: this.effects.status.addSystemMessage, - notifyThreadRenamed: (threadId, name) => { - this.plugin.notifyThreadRenamed(threadId, name); - }, }); + this.connection = controllers.connection; + this.controller = controllers.controller; + this.appServer = controllers.appServer; + this.connectionController = controllers.connectionController; + this.history = controllers.history; + this.threadResume = controllers.threadResume; + this.threadActions = controllers.threadActions; + this.runtimeSettings = controllers.runtimeSettings; + this.restoredThread = controllers.restoredThread; + this.threadIdentity = controllers.threadIdentity; + this.threadRename = controllers.threadRename; + this.pendingRequests = controllers.pendingRequests; + this.toolbarPanels = controllers.toolbarPanels; + this.reconnectActions = controllers.reconnectActions; + this.composerController = controllers.composerController; + this.messageRenderer = controllers.messageRenderer; + this.renderController = controllers.renderController; + this.openCloseController = controllers.openCloseController; + this.viewStateController = controllers.viewStateController; + this.appServerWarmup = controllers.appServerWarmup; + this.composerSubmission = controllers.composerSubmission; + this.threadSelection = controllers.threadSelection; } private createEffects(): ChatViewEffects { @@ -790,13 +460,6 @@ export class CodexChatView extends ItemView { this.appServer.publishAppServerMetadataSnapshot(); } - private applyCachedSharedAppServerState(): void { - const threads = this.plugin.cachedThreadList(); - if (threads) this.appServer.applyThreadList(threads); - const metadata = this.plugin.cachedAppServerMetadata(); - if (metadata) this.appServer.applyAppServerMetadata(metadata); - } - private requestWorkspaceLayoutSave(): void { void this.app.workspace.requestSaveLayout(); }