2026-06-15 02:32:45 +00:00
|
|
|
import type { App } from "obsidian";
|
2026-06-15 11:17:33 +00:00
|
|
|
import type { AppServerClient } from "./app-server/connection/client";
|
2026-06-21 12:16:57 +00:00
|
|
|
import type { AppServerClientAccess, AppServerClientAccessOptions } from "./app-server/connection/client-access";
|
2026-06-15 11:17:33 +00:00
|
|
|
import { withShortLivedAppServerClient } from "./app-server/connection/short-lived-client";
|
2026-07-16 12:44:28 +00:00
|
|
|
import {
|
|
|
|
|
type AppServerContextLease,
|
|
|
|
|
type AppServerQueryContext,
|
|
|
|
|
type AppServerQueryContextIdentity,
|
|
|
|
|
appServerQueryContextIdentityMatches,
|
|
|
|
|
appServerQueryContextIsComplete,
|
|
|
|
|
} from "./app-server/query/keys";
|
|
|
|
|
import { AppServerResourceStore, StaleAppServerResourceContextError } from "./app-server/query/resource-store";
|
2026-06-24 05:44:16 +00:00
|
|
|
import { VIEW_TYPE_CODEX_THREADS, VIEW_TYPE_CODEX_TURN_DIFF } from "./constants";
|
2026-06-27 11:40:42 +00:00
|
|
|
import { hasPendingRequests } from "./domain/pending-requests/aggregate";
|
2026-07-17 12:18:05 +00:00
|
|
|
import { createThreadGoalOperationCoordinator } from "./features/chat/application/threads/goal-actions";
|
2026-06-16 00:43:13 +00:00
|
|
|
import type {
|
|
|
|
|
ChatPanelClientSurface,
|
2026-06-27 11:55:38 +00:00
|
|
|
ChatPanelSettingsAccess,
|
2026-06-16 00:43:13 +00:00
|
|
|
ChatSharedThreadSurface,
|
|
|
|
|
ChatViewLifecycleSurface,
|
|
|
|
|
ChatWorkspacePanelSurface,
|
2026-06-27 01:32:29 +00:00
|
|
|
CodexChatHost,
|
|
|
|
|
} from "./features/chat/host/contracts";
|
2026-07-14 12:15:21 +00:00
|
|
|
import type { SelectionRewriteCommandController } from "./features/selection-rewrite/command.obsidian";
|
2026-06-25 02:18:01 +00:00
|
|
|
import { openThreadPicker, type ThreadPickerHost } from "./features/thread-picker/modal.obsidian";
|
2026-07-12 09:18:11 +00:00
|
|
|
import { createThreadOperationsTransport, createThreadTitleTransport } from "./features/threads/app-server/workflow-transports";
|
2026-06-30 03:09:14 +00:00
|
|
|
import { createThreadCatalog, type ThreadCatalog, type ThreadCatalogEvent } from "./features/threads/catalog/thread-catalog";
|
2026-07-13 11:56:18 +00:00
|
|
|
import { createThreadNameMutationCoordinator } from "./features/threads/workflows/thread-name-mutation-coordinator";
|
2026-06-26 11:23:37 +00:00
|
|
|
import type { ThreadsViewHost, ThreadsViewSettingsAccess } from "./features/threads-view/session";
|
2026-06-27 11:40:42 +00:00
|
|
|
import type { ThreadsViewPanelActivity } from "./features/threads-view/state";
|
2026-06-27 02:36:00 +00:00
|
|
|
import { CodexThreadsView } from "./features/threads-view/view.obsidian";
|
2026-06-27 12:49:26 +00:00
|
|
|
import { persistedTurnDiffViewState, type TurnDiffViewState } from "./features/turn-diff/model";
|
|
|
|
|
import { CodexTurnDiffView } from "./features/turn-diff/view.obsidian";
|
2026-06-30 01:32:23 +00:00
|
|
|
import { createSettingsAppServerDynamicData } from "./settings/app-server-dynamic-data";
|
2026-07-17 21:58:40 +00:00
|
|
|
import type { SettingsDynamicDataAccess } from "./settings/dynamic-data";
|
2026-06-17 01:09:50 +00:00
|
|
|
import type { CodexPanelSettingTabHost } from "./settings/host";
|
2026-06-27 11:55:38 +00:00
|
|
|
import type { CodexPanelSettings } from "./settings/model";
|
2026-07-17 12:18:05 +00:00
|
|
|
import { createKeyedOperationQueue } from "./shared/runtime/keyed-operation-queue";
|
2026-06-15 02:32:45 +00:00
|
|
|
import { WorkspacePanelCoordinator } from "./workspace/panel-coordinator";
|
|
|
|
|
|
2026-06-27 11:55:38 +00:00
|
|
|
interface CodexPanelRuntimeSettingsRef {
|
|
|
|
|
readonly settings: CodexPanelSettings;
|
|
|
|
|
readonly vaultPath: string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 02:32:45 +00:00
|
|
|
export interface CodexPanelRuntimeOptions {
|
|
|
|
|
app: App;
|
2026-06-27 11:55:38 +00:00
|
|
|
settingsRef: CodexPanelRuntimeSettingsRef;
|
2026-07-14 12:15:21 +00:00
|
|
|
saveSettings(settings: CodexPanelSettings): Promise<void>;
|
2026-06-15 02:32:45 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-17 03:55:54 +00:00
|
|
|
export class CodexPanelRuntime implements AppServerClientAccess {
|
2026-07-16 12:44:28 +00:00
|
|
|
private readonly appServerResourceStore = new AppServerResourceStore({
|
2026-06-15 11:17:33 +00:00
|
|
|
clientRunner: {
|
|
|
|
|
runWithClient: (context, operation, options) => this.runWithAppServerClient(context, operation, options),
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-06-15 12:11:05 +00:00
|
|
|
private readonly panels: WorkspacePanelCoordinator;
|
2026-06-20 03:10:43 +00:00
|
|
|
private readonly threadCatalog: ThreadCatalog;
|
2026-07-17 21:58:40 +00:00
|
|
|
private readonly settingsDynamicData: SettingsDynamicDataAccess;
|
2026-07-13 11:56:18 +00:00
|
|
|
private readonly threadNameMutations = createThreadNameMutationCoordinator();
|
2026-07-17 21:58:40 +00:00
|
|
|
private readonly threadGoalOperations = createThreadGoalOperationCoordinator();
|
|
|
|
|
private readonly runtimeSettingsCommitQueue = createKeyedOperationQueue<string>();
|
2026-07-14 12:15:21 +00:00
|
|
|
private selectionRewriteController: SelectionRewriteCommandController | null = null;
|
2026-06-15 02:32:45 +00:00
|
|
|
|
|
|
|
|
constructor(private readonly options: CodexPanelRuntimeOptions) {
|
|
|
|
|
this.panels = new WorkspacePanelCoordinator({
|
|
|
|
|
app: options.app,
|
|
|
|
|
refreshThreadsViewLiveState: () => {
|
2026-06-15 12:11:05 +00:00
|
|
|
this.refreshThreadsViewLiveState();
|
2026-06-15 02:32:45 +00:00
|
|
|
},
|
|
|
|
|
});
|
2026-06-20 03:10:43 +00:00
|
|
|
this.threadCatalog = createThreadCatalog({
|
2026-07-16 12:44:28 +00:00
|
|
|
store: this.appServerResourceStore,
|
2026-06-27 10:33:32 +00:00
|
|
|
onEventApplied: (event) => {
|
|
|
|
|
this.applyThreadCatalogSurfaceEvent(event);
|
2026-06-15 10:02:03 +00:00
|
|
|
},
|
2026-06-15 02:32:45 +00:00
|
|
|
});
|
2026-07-17 21:58:40 +00:00
|
|
|
this.settingsDynamicData = createSettingsAppServerDynamicData({
|
|
|
|
|
vaultPath: options.settingsRef.vaultPath,
|
|
|
|
|
clientAccess: this,
|
|
|
|
|
appServerQueries: this.appServerResourceStore,
|
|
|
|
|
threadCatalog: this.threadCatalog,
|
|
|
|
|
});
|
2026-06-15 02:32:45 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-16 12:44:28 +00:00
|
|
|
initialize(): void {
|
|
|
|
|
this.appServerResourceStore.initialize(this.configuredAppServerContext());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
appServerContextLease(): AppServerContextLease {
|
|
|
|
|
return this.appServerResourceStore.contextLease();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 02:32:45 +00:00
|
|
|
reset(): void {
|
2026-07-14 12:15:21 +00:00
|
|
|
this.selectionRewriteController?.closeAll();
|
|
|
|
|
this.selectionRewriteController = null;
|
2026-06-15 02:32:45 +00:00
|
|
|
this.panels.reset();
|
2026-07-16 12:44:28 +00:00
|
|
|
this.appServerResourceStore.reset();
|
2026-06-15 02:32:45 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-17 12:18:05 +00:00
|
|
|
activeWorkspaceLeafChanged(leaf: Parameters<WorkspacePanelCoordinator["activeLeafChanged"]>[0]): void {
|
|
|
|
|
this.panels.activeLeafChanged(leaf);
|
2026-06-15 02:32:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
activatePanel(): Promise<unknown> {
|
|
|
|
|
return this.panels.activateView();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
activateNewPanel(): Promise<unknown> {
|
|
|
|
|
return this.panels.activateNewView();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async startNewChat(): Promise<void> {
|
2026-07-17 12:18:05 +00:00
|
|
|
await this.panels.startNewChat();
|
2026-06-15 02:32:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
openThreadPicker(): void {
|
|
|
|
|
void openThreadPicker(this.threadPickerHost());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async activateThreadsView(): Promise<CodexThreadsView> {
|
|
|
|
|
const leaf = await this.options.app.workspace.ensureSideLeaf(VIEW_TYPE_CODEX_THREADS, "left", {
|
|
|
|
|
active: true,
|
|
|
|
|
reveal: true,
|
|
|
|
|
});
|
|
|
|
|
const view = leaf.view as CodexThreadsView;
|
|
|
|
|
await view.refresh();
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 05:50:56 +00:00
|
|
|
scheduleWorkspacePanelReconcile(): void {
|
|
|
|
|
this.panels.scheduleWorkspacePanelReconcile();
|
2026-06-15 02:32:45 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-22 05:50:56 +00:00
|
|
|
cancelWorkspacePanelReconcile(): void {
|
|
|
|
|
this.panels.cancelWorkspacePanelReconcile();
|
2026-06-15 02:32:45 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-14 12:15:21 +00:00
|
|
|
setSelectionRewriteController(controller: SelectionRewriteCommandController): void {
|
|
|
|
|
this.selectionRewriteController = controller;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 02:32:45 +00:00
|
|
|
chatHost(): CodexChatHost {
|
|
|
|
|
return {
|
2026-06-27 11:55:38 +00:00
|
|
|
settingsRef: {
|
|
|
|
|
settings: this.chatSettings(),
|
|
|
|
|
vaultPath: this.options.settingsRef.vaultPath,
|
|
|
|
|
},
|
2026-06-15 02:32:45 +00:00
|
|
|
workspace: {
|
|
|
|
|
openThreadInNewView: (threadId) => this.panels.openThreadInNewView(threadId),
|
|
|
|
|
focusThreadInOpenView: (threadId) => this.panels.focusThreadInOpenView(threadId),
|
|
|
|
|
openTurnDiff: (state) => this.openTurnDiff(state),
|
2026-07-11 09:38:33 +00:00
|
|
|
openSideChat: (sourceThreadId, sourceThreadTitle) => this.panels.openSideChat(sourceThreadId, sourceThreadTitle),
|
2026-06-16 12:14:42 +00:00
|
|
|
refreshThreadsViewLiveState: () => {
|
|
|
|
|
this.refreshThreadsViewLiveState();
|
|
|
|
|
},
|
2026-06-15 02:32:45 +00:00
|
|
|
},
|
2026-07-16 12:44:28 +00:00
|
|
|
appServerQueries: this.appServerResourceStore,
|
2026-06-15 06:00:14 +00:00
|
|
|
threadCatalog: this.threadCatalog,
|
2026-07-13 11:56:18 +00:00
|
|
|
threadNameMutations: this.threadNameMutations,
|
2026-07-17 12:18:05 +00:00
|
|
|
threadGoalOperations: this.threadGoalOperations,
|
|
|
|
|
runtimeSettingsCommitQueue: this.runtimeSettingsCommitQueue,
|
2026-06-15 02:32:45 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 11:55:38 +00:00
|
|
|
private chatSettings(): ChatPanelSettingsAccess {
|
|
|
|
|
return {
|
2026-07-05 02:24:47 +00:00
|
|
|
referenceActiveNoteOnSend: () => this.options.settingsRef.settings.referenceActiveNoteOnSend,
|
2026-06-28 11:20:01 +00:00
|
|
|
attachmentFolder: () => this.options.settingsRef.settings.attachmentFolder,
|
2026-06-27 11:55:38 +00:00
|
|
|
archiveExportEnabled: () => this.options.settingsRef.settings.archiveExportEnabled,
|
|
|
|
|
archiveExportSettings: () => ({
|
|
|
|
|
archiveExportFolderTemplate: this.options.settingsRef.settings.archiveExportFolderTemplate,
|
|
|
|
|
archiveExportFilenameTemplate: this.options.settingsRef.settings.archiveExportFilenameTemplate,
|
|
|
|
|
archiveExportTags: this.options.settingsRef.settings.archiveExportTags,
|
|
|
|
|
}),
|
|
|
|
|
codexPath: () => this.options.settingsRef.settings.codexPath,
|
|
|
|
|
scrollThreadFromComposerEdges: () => this.options.settingsRef.settings.scrollThreadFromComposerEdges,
|
|
|
|
|
sendShortcut: () => this.options.settingsRef.settings.sendShortcut,
|
|
|
|
|
showToolbar: () => this.options.settingsRef.settings.showToolbar,
|
|
|
|
|
threadNamingEffort: () => this.options.settingsRef.settings.threadNamingEffort,
|
|
|
|
|
threadNamingModel: () => this.options.settingsRef.settings.threadNamingModel,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-26 11:23:37 +00:00
|
|
|
threadsHost(): ThreadsViewHost {
|
2026-06-15 02:32:45 +00:00
|
|
|
return {
|
2026-06-17 04:46:16 +00:00
|
|
|
settings: this.threadsSettings(),
|
2026-06-15 02:32:45 +00:00
|
|
|
vaultPath: this.options.settingsRef.vaultPath,
|
2026-07-16 21:08:53 +00:00
|
|
|
appServerContextLease: () => this.appServerResourceStore.contextLease(),
|
2026-06-15 06:00:14 +00:00
|
|
|
threadCatalog: this.threadCatalog,
|
2026-07-13 11:56:18 +00:00
|
|
|
threadNameMutations: this.threadNameMutations,
|
2026-07-12 09:18:11 +00:00
|
|
|
threadOperationsTransport: createThreadOperationsTransport(this),
|
|
|
|
|
threadTitleTransport: createThreadTitleTransport({
|
|
|
|
|
clientAccess: this,
|
2026-07-16 12:44:28 +00:00
|
|
|
codexPath: () => this.appServerResourceStore.contextLease().context.codexPath,
|
|
|
|
|
vaultPath: this.appServerResourceStore.contextLease().context.vaultPath,
|
2026-07-12 09:18:11 +00:00
|
|
|
threadNamingModel: () => this.options.settingsRef.settings.threadNamingModel,
|
|
|
|
|
threadNamingEffort: () => this.options.settingsRef.settings.threadNamingEffort,
|
|
|
|
|
}),
|
2026-06-15 02:32:45 +00:00
|
|
|
openNewPanel: () => this.panels.openNewPanel(),
|
|
|
|
|
openThreadInAvailableView: (threadId) => this.panels.openThreadInAvailableView(threadId),
|
2026-06-27 11:40:42 +00:00
|
|
|
openPanelActivities: () => this.openPanelActivities(),
|
2026-06-30 03:09:14 +00:00
|
|
|
closeOpenPanelsForThread: (threadId) => {
|
|
|
|
|
this.closeOpenPanelsForThread(threadId);
|
|
|
|
|
},
|
2026-06-15 02:32:45 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-26 11:23:37 +00:00
|
|
|
private threadsSettings(): ThreadsViewSettingsAccess {
|
2026-06-17 04:46:16 +00:00
|
|
|
return {
|
|
|
|
|
archiveExportEnabled: () => this.options.settingsRef.settings.archiveExportEnabled,
|
|
|
|
|
codexPath: () => this.options.settingsRef.settings.codexPath,
|
|
|
|
|
threadNamingModel: () => this.options.settingsRef.settings.threadNamingModel,
|
|
|
|
|
threadNamingEffort: () => this.options.settingsRef.settings.threadNamingEffort,
|
2026-06-24 03:19:07 +00:00
|
|
|
archiveExportSettings: () => ({
|
|
|
|
|
archiveExportFolderTemplate: this.options.settingsRef.settings.archiveExportFolderTemplate,
|
|
|
|
|
archiveExportFilenameTemplate: this.options.settingsRef.settings.archiveExportFilenameTemplate,
|
|
|
|
|
archiveExportTags: this.options.settingsRef.settings.archiveExportTags,
|
|
|
|
|
}),
|
2026-06-17 04:46:16 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 02:32:45 +00:00
|
|
|
settingTabHost(): CodexPanelSettingTabHost {
|
|
|
|
|
return {
|
|
|
|
|
settings: this.options.settingsRef.settings,
|
2026-07-17 21:58:40 +00:00
|
|
|
dynamicData: this.settingsDynamicData,
|
2026-07-16 12:44:28 +00:00
|
|
|
publishSettings: (settings) => this.publishSettings(settings),
|
2026-06-15 02:32:45 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private threadPickerHost(): ThreadPickerHost {
|
|
|
|
|
return {
|
|
|
|
|
app: this.options.app,
|
2026-06-15 06:00:14 +00:00
|
|
|
threadCatalog: this.threadCatalog,
|
2026-06-15 02:32:45 +00:00
|
|
|
openThreadInCurrentView: (threadId) => this.panels.openThreadInCurrentView(threadId),
|
|
|
|
|
openThreadInAvailableView: (threadId) => this.panels.openThreadInAvailableView(threadId),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 12:16:57 +00:00
|
|
|
withClient<T>(operation: (client: AppServerClient) => Promise<T>, options: AppServerClientAccessOptions = {}): Promise<T> {
|
2026-07-16 12:44:28 +00:00
|
|
|
return this.runWithAppServerClient(this.appServerResourceStore.contextIdentity(), operation, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async publishSettings(settings: CodexPanelSettings): Promise<{ appServerContextReplaced: boolean }> {
|
|
|
|
|
const previousSettings = { ...this.options.settingsRef.settings };
|
|
|
|
|
await this.options.saveSettings(settings);
|
|
|
|
|
const appServerContextReplaced = previousSettings.codexPath !== settings.codexPath;
|
|
|
|
|
if (appServerContextReplaced) this.prepareAppServerContextChange();
|
|
|
|
|
Object.assign(this.options.settingsRef.settings, settings);
|
2026-07-17 21:58:40 +00:00
|
|
|
if (appServerContextReplaced) {
|
|
|
|
|
this.appServerResourceStore.replaceContext(this.configuredAppServerContext());
|
|
|
|
|
}
|
2026-07-16 12:44:28 +00:00
|
|
|
if (appServerContextReplaced || previousSettings.showToolbar !== settings.showToolbar) this.refreshOpenViews();
|
|
|
|
|
return { appServerContextReplaced };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private prepareAppServerContextChange(): void {
|
|
|
|
|
this.selectionRewriteController?.closeAll();
|
|
|
|
|
for (const view of this.panels.panelViews()) view.surface.prepareAppServerContextChange();
|
|
|
|
|
for (const view of this.threadsViews()) view.prepareAppServerContextChange();
|
2026-06-17 03:55:54 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-27 12:49:26 +00:00
|
|
|
private async openTurnDiff(state: TurnDiffViewState): Promise<void> {
|
2026-06-15 02:32:45 +00:00
|
|
|
const existing = this.options.app.workspace.getLeavesOfType(VIEW_TYPE_CODEX_TURN_DIFF).at(0);
|
|
|
|
|
const leaf = existing ?? this.options.app.workspace.getLeaf("tab");
|
2026-06-27 12:49:26 +00:00
|
|
|
await leaf.setViewState({ type: VIEW_TYPE_CODEX_TURN_DIFF, active: true, state: { ...persistedTurnDiffViewState(state) } });
|
2026-06-15 02:32:45 +00:00
|
|
|
await leaf.loadIfDeferred();
|
2026-06-27 12:49:26 +00:00
|
|
|
if (leaf.view instanceof CodexTurnDiffView) {
|
2026-06-15 02:32:45 +00:00
|
|
|
leaf.view.setDiffPayload(state);
|
|
|
|
|
}
|
|
|
|
|
await this.options.app.workspace.revealLeaf(leaf);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:02:03 +00:00
|
|
|
private refreshOpenViews(): void {
|
|
|
|
|
for (const view of this.panels.panelViews()) {
|
2026-06-16 00:43:13 +00:00
|
|
|
const surface: ChatViewLifecycleSurface = view.surface;
|
|
|
|
|
surface.refreshSettings();
|
2026-06-15 10:02:03 +00:00
|
|
|
}
|
2026-07-14 12:15:21 +00:00
|
|
|
for (const view of this.threadsViews()) view.refreshSettings();
|
2026-06-15 10:02:03 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-30 03:09:14 +00:00
|
|
|
private applyThreadArchived(threadId: string): void {
|
2026-06-15 10:02:03 +00:00
|
|
|
for (const view of this.panels.panelViews()) {
|
2026-06-16 00:43:13 +00:00
|
|
|
const surface: ChatSharedThreadSurface = view.surface;
|
|
|
|
|
surface.applyThreadArchived(threadId);
|
2026-06-15 10:02:03 +00:00
|
|
|
}
|
2026-06-30 03:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private closeOpenPanelsForThread(threadId: string): void {
|
|
|
|
|
const leavesToClose = this.panels.panelLeavesForThread(threadId);
|
2026-06-15 10:02:03 +00:00
|
|
|
for (const leaf of leavesToClose) {
|
|
|
|
|
leaf.detach();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private applyThreadRenamed(threadId: string, name: string | null): void {
|
|
|
|
|
for (const view of this.panels.panelViews()) {
|
2026-06-16 00:43:13 +00:00
|
|
|
const surface: ChatSharedThreadSurface = view.surface;
|
|
|
|
|
surface.applyThreadRenamed(threadId, name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 10:33:32 +00:00
|
|
|
private applyThreadCatalogSurfaceEvent(event: ThreadCatalogEvent): void {
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
case "thread-archived":
|
2026-06-30 03:09:14 +00:00
|
|
|
this.applyThreadArchived(event.threadId);
|
2026-06-27 10:33:32 +00:00
|
|
|
return;
|
|
|
|
|
case "thread-renamed":
|
|
|
|
|
this.applyThreadRenamed(event.threadId, event.name);
|
|
|
|
|
return;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:02:03 +00:00
|
|
|
private refreshThreadsViewLiveState(): void {
|
|
|
|
|
for (const view of this.threadsViews()) {
|
|
|
|
|
view.refreshLiveState();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 11:40:42 +00:00
|
|
|
private openPanelActivities(): readonly ThreadsViewPanelActivity[] {
|
|
|
|
|
return this.panels.getOpenPanelSnapshots().map((snapshot) => ({
|
|
|
|
|
threadId: snapshot.threadId,
|
|
|
|
|
selected: snapshot.lastFocused,
|
|
|
|
|
pending: hasPendingRequests(snapshot.pendingRequests),
|
|
|
|
|
running: snapshot.turnLifecycle.kind !== "idle",
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:02:03 +00:00
|
|
|
private threadsViews(): CodexThreadsView[] {
|
|
|
|
|
return this.options.app.workspace
|
|
|
|
|
.getLeavesOfType(VIEW_TYPE_CODEX_THREADS)
|
|
|
|
|
.flatMap((leaf) => (leaf.view instanceof CodexThreadsView ? [leaf.view] : []));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 11:17:33 +00:00
|
|
|
private async runWithAppServerClient<T>(
|
2026-07-16 12:44:28 +00:00
|
|
|
context: AppServerQueryContextIdentity,
|
2026-06-15 11:17:33 +00:00
|
|
|
operation: (client: AppServerClient) => Promise<T>,
|
2026-06-21 12:16:57 +00:00
|
|
|
options: AppServerClientAccessOptions = {},
|
2026-06-15 11:17:33 +00:00
|
|
|
): Promise<T> {
|
|
|
|
|
if (!appServerQueryContextIsComplete(context)) {
|
|
|
|
|
throw new Error("Codex app-server query context is incomplete.");
|
|
|
|
|
}
|
2026-07-17 11:57:28 +00:00
|
|
|
this.assertCurrentAppServerContext(context);
|
|
|
|
|
const result = await this.runWithContextClient(
|
|
|
|
|
context,
|
|
|
|
|
(client) => {
|
|
|
|
|
this.assertCurrentAppServerContext(context);
|
|
|
|
|
return operation(client);
|
|
|
|
|
},
|
|
|
|
|
options,
|
|
|
|
|
);
|
|
|
|
|
this.assertCurrentAppServerContext(context);
|
2026-07-14 12:15:21 +00:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-17 11:57:28 +00:00
|
|
|
private assertCurrentAppServerContext(context: AppServerQueryContextIdentity): void {
|
|
|
|
|
let current = false;
|
|
|
|
|
try {
|
|
|
|
|
current = appServerQueryContextIdentityMatches(this.appServerResourceStore.contextIdentity(), context);
|
|
|
|
|
} catch {
|
|
|
|
|
// A reset resource store has no current app-server context.
|
|
|
|
|
}
|
|
|
|
|
if (!current) throw new StaleAppServerResourceContextError();
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-14 12:15:21 +00:00
|
|
|
private runWithContextClient<T>(
|
2026-07-16 12:44:28 +00:00
|
|
|
context: AppServerQueryContextIdentity,
|
2026-07-14 12:15:21 +00:00
|
|
|
operation: (client: AppServerClient) => Promise<T>,
|
|
|
|
|
options: AppServerClientAccessOptions,
|
|
|
|
|
): Promise<T> {
|
2026-06-21 12:16:57 +00:00
|
|
|
if (options.serverRequests?.kind === "reject") {
|
2026-06-17 05:43:20 +00:00
|
|
|
return withShortLivedAppServerClient(context.codexPath, context.vaultPath, operation, options);
|
|
|
|
|
}
|
2026-06-22 07:34:51 +00:00
|
|
|
const chatSurface = this.connectedClientSurface(context);
|
2026-07-14 12:15:21 +00:00
|
|
|
return chatSurface
|
|
|
|
|
? chatSurface.runWithAppServerClient(operation)
|
|
|
|
|
: withShortLivedAppServerClient(context.codexPath, context.vaultPath, operation, options);
|
2026-06-15 11:17:33 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-16 12:44:28 +00:00
|
|
|
private connectedClientSurface(context: AppServerQueryContextIdentity): ChatPanelClientSurface | null {
|
2026-06-16 00:43:13 +00:00
|
|
|
for (const view of this.panels.panelViews()) {
|
|
|
|
|
const workspaceSurface: ChatWorkspacePanelSurface = view.surface;
|
|
|
|
|
if (!workspaceSurface.openPanelSnapshot().connected) continue;
|
|
|
|
|
const clientSurface: ChatPanelClientSurface = view.surface;
|
2026-06-22 07:34:51 +00:00
|
|
|
if (!clientSurface.canServeAppServerContext(context)) continue;
|
2026-06-16 00:43:13 +00:00
|
|
|
return clientSurface;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-16 12:44:28 +00:00
|
|
|
private configuredAppServerContext(): AppServerQueryContext {
|
2026-06-15 02:32:45 +00:00
|
|
|
return {
|
|
|
|
|
codexPath: this.options.settingsRef.settings.codexPath,
|
|
|
|
|
vaultPath: this.options.settingsRef.vaultPath,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|