Extract chat view controller assembly

This commit is contained in:
murashit 2026-05-29 07:57:49 +09:00
parent 4f1d2331ab
commit 2f37b91837
3 changed files with 636 additions and 428 deletions

View file

@ -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<unknown>;
openThreadInAvailableView(threadId: string): Promise<void>;
focusThreadInOpenView(threadId: string): Promise<boolean>;
openTurnDiff(state: ChatTurnDiffViewState): Promise<void>;
notifyThreadArchived(threadId: string): void;
notifyThreadRenamed(threadId: string, name: string | null): void;
refreshThreadsViewLiveState(): void;
refreshSharedThreadListFromOpenSurface(): void;
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
cachedThreadList(): readonly Thread[] | null;
publishAppServerMetadata(metadata: SharedAppServerMetadata): void;
cachedAppServerMetadata(): SharedAppServerMetadata | null;
}

View file

@ -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<string[]>;
modelStatusLines: () => string[];
effortStatusLines: () => string[];
statusSummaryLines: () => string[];
ensureRestoredThreadLoaded: () => Promise<boolean>;
startNewThread: () => Promise<void>;
selectThread: (threadId: string) => Promise<void>;
resumeThread: (threadId: string) => Promise<void>;
refreshThreads: () => Promise<void>;
refreshSkills: (forceReload?: boolean) => Promise<void>;
publishAppServerMetadataSnapshot: () => void;
loadSharedThreadList: () => Promise<void>;
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);
}

View file

@ -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<unknown>;
openThreadInAvailableView(threadId: string): Promise<void>;
focusThreadInOpenView(threadId: string): Promise<boolean>;
openTurnDiff(state: ChatTurnDiffViewState): Promise<void>;
notifyThreadArchived(threadId: string): void;
notifyThreadRenamed(threadId: string, name: string | null): void;
refreshThreadsViewLiveState(): void;
refreshSharedThreadListFromOpenSurface(): void;
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
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();
}