mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Narrow chat host dependencies around session composition
This commit is contained in:
parent
597046ff73
commit
46fb2edb76
11 changed files with 229 additions and 123 deletions
|
|
@ -4,19 +4,39 @@ import type { CodexPanelSettings } from "../../../../settings/model";
|
|||
import type { ChatTurnDiffViewState } from "../../domain/turn-diff";
|
||||
|
||||
export interface CodexChatHost {
|
||||
readonly settingsRef: PluginSettingsRef;
|
||||
readonly workspace: WorkspacePanels;
|
||||
readonly sharedCache: SharedAppServerCacheFacade;
|
||||
readonly threadSurfaces: ThreadSurfaceBroadcaster;
|
||||
readonly appServerIdentity: AppServerIdentityPublisher;
|
||||
}
|
||||
|
||||
export interface PluginSettingsRef {
|
||||
readonly settings: CodexPanelSettings;
|
||||
readonly vaultPath: string;
|
||||
}
|
||||
|
||||
export interface WorkspacePanels {
|
||||
openThreadInNewView(threadId: string): Promise<unknown>;
|
||||
focusThreadInOpenView(threadId: string): Promise<boolean>;
|
||||
openTurnDiff(state: ChatTurnDiffViewState): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ThreadSurfaceBroadcaster {
|
||||
notifyThreadArchived(threadId: string): void;
|
||||
notifyThreadRenamed(threadId: string, name: string | null): void;
|
||||
refreshThreadsViewLiveState(): void;
|
||||
refreshSharedThreadListFromOpenSurface(): void;
|
||||
applyThreadListSnapshot(threads: readonly Thread[]): void;
|
||||
publishAppServerMetadata(metadata: SharedServerMetadata): void;
|
||||
}
|
||||
|
||||
interface SharedAppServerCacheFacade {
|
||||
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
|
||||
cachedThreadList(): readonly Thread[] | null;
|
||||
publishAppServerMetadata(metadata: SharedServerMetadata): void;
|
||||
publishAppServerIdentity(userAgent: string | null): void;
|
||||
cachedAppServerMetadata(): SharedServerMetadata | null;
|
||||
}
|
||||
|
||||
interface AppServerIdentityPublisher {
|
||||
publishAppServerIdentity(userAgent: string | null): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { createGoalActions } from "./goal-actions";
|
|||
import { createSelectionActions } from "./selection-actions";
|
||||
import type { ChatResumeWorkTracker, ChatViewDeferredTasks } from "../lifecycle";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
import type { CodexChatHost } from "../ports/chat-host";
|
||||
import type { PluginSettingsRef, ThreadSurfaceBroadcaster, WorkspacePanels } from "../ports/chat-host";
|
||||
import { createThreadNamingParts } from "./naming-parts";
|
||||
import { createThreadManagementActions } from "./thread-management-actions";
|
||||
import { createThreadLifecycleParts } from "./lifecycle-parts";
|
||||
|
|
@ -13,7 +13,9 @@ interface ThreadPartsContext {
|
|||
obsidian: {
|
||||
archiveAdapter: () => ArchiveExportAdapter;
|
||||
};
|
||||
plugin: CodexChatHost;
|
||||
settingsRef: PluginSettingsRef;
|
||||
workspace: Pick<WorkspacePanels, "focusThreadInOpenView" | "openThreadInNewView">;
|
||||
threadSurfaces: ThreadSurfaceBroadcaster;
|
||||
state: {
|
||||
stateStore: ChatStateStore;
|
||||
};
|
||||
|
|
@ -54,12 +56,27 @@ interface ThreadPartsContext {
|
|||
}
|
||||
|
||||
export function createThreadParts(context: ThreadPartsContext) {
|
||||
const { obsidian, plugin, state, thread, status, notify, liveState, scroll, client, composer, lifecycle } = context;
|
||||
const {
|
||||
obsidian,
|
||||
settingsRef,
|
||||
workspace,
|
||||
threadSurfaces,
|
||||
state,
|
||||
thread,
|
||||
status,
|
||||
notify,
|
||||
liveState,
|
||||
scroll,
|
||||
client,
|
||||
composer,
|
||||
lifecycle,
|
||||
} = context;
|
||||
const stateStore = state.stateStore;
|
||||
const currentClient = client.getClient;
|
||||
|
||||
const naming = createThreadNamingParts({
|
||||
plugin,
|
||||
settingsRef,
|
||||
threadSurfaces,
|
||||
stateStore,
|
||||
client: {
|
||||
currentClient,
|
||||
|
|
@ -73,7 +90,9 @@ export function createThreadParts(context: ThreadPartsContext) {
|
|||
|
||||
const managementActions = createThreadManagementActions({
|
||||
obsidian,
|
||||
plugin,
|
||||
settingsRef,
|
||||
workspace,
|
||||
threadSurfaces,
|
||||
stateStore,
|
||||
client: {
|
||||
currentClient,
|
||||
|
|
@ -99,7 +118,7 @@ export function createThreadParts(context: ThreadPartsContext) {
|
|||
refreshLiveState: liveState.refresh,
|
||||
});
|
||||
const threadLifecycle = createThreadLifecycleParts({
|
||||
plugin,
|
||||
settingsRef,
|
||||
stateStore,
|
||||
client: {
|
||||
currentClient,
|
||||
|
|
@ -134,7 +153,7 @@ export function createThreadParts(context: ThreadPartsContext) {
|
|||
}
|
||||
|
||||
interface ThreadSelectionActionsContext {
|
||||
plugin: CodexChatHost;
|
||||
workspace: Pick<WorkspacePanels, "focusThreadInOpenView">;
|
||||
state: {
|
||||
stateStore: ChatStateStore;
|
||||
};
|
||||
|
|
@ -152,13 +171,13 @@ export function createThreadSelectionActions(
|
|||
closeForThreadSelection: () => void;
|
||||
},
|
||||
) {
|
||||
const { plugin, thread, status } = context;
|
||||
const { workspace, thread, status } = context;
|
||||
const stateStore = context.state.stateStore;
|
||||
|
||||
return createSelectionActions({
|
||||
stateStore,
|
||||
closeForThreadSelection: refs.closeForThreadSelection,
|
||||
focusThreadInOpenView: (threadId) => plugin.focusThreadInOpenView(threadId),
|
||||
focusThreadInOpenView: workspace.focusThreadInOpenView,
|
||||
resumeThread: thread.resumeThread,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { AppServerClient } from "../../../../app-server/connection/client";
|
|||
import { recoverRolloutTokenUsage } from "../../../../app-server/services/rollout-token-usage";
|
||||
import type { ChatResumeWorkTracker, ChatViewDeferredTasks } from "../lifecycle";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
import type { CodexChatHost } from "../ports/chat-host";
|
||||
import type { PluginSettingsRef } from "../ports/chat-host";
|
||||
import type { GoalActions } from "./goal-actions";
|
||||
import { HistoryController } from "./history-controller";
|
||||
import { createIdentitySync } from "./identity-sync";
|
||||
|
|
@ -10,7 +10,7 @@ import { ResumeController } from "./resume-controller";
|
|||
import { RestorationController } from "./restoration-controller";
|
||||
|
||||
export interface ThreadLifecyclePartsContext {
|
||||
plugin: CodexChatHost;
|
||||
settingsRef: PluginSettingsRef;
|
||||
stateStore: ChatStateStore;
|
||||
client: {
|
||||
currentClient: () => AppServerClient | null;
|
||||
|
|
@ -50,7 +50,7 @@ export interface ThreadLifecycleParts {
|
|||
}
|
||||
|
||||
export function createThreadLifecycleParts(context: ThreadLifecyclePartsContext): ThreadLifecycleParts {
|
||||
const { plugin, stateStore, client, lifecycle, thread, status, liveState, scroll, goals, resetThreadTurnPresence } = context;
|
||||
const { settingsRef, stateStore, client, lifecycle, thread, status, liveState, scroll, goals, resetThreadTurnPresence } = context;
|
||||
const { deferredTasks, resumeWork } = lifecycle;
|
||||
const history = new HistoryController({
|
||||
stateStore,
|
||||
|
|
@ -75,7 +75,7 @@ export function createThreadLifecycleParts(context: ThreadLifecyclePartsContext)
|
|||
});
|
||||
const resume = new ResumeController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
resumeWork,
|
||||
history,
|
||||
restoration,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import type { AppServerClient } from "../../../../app-server/connection/client";
|
||||
import type { ChatStateStore } from "../state/reducer";
|
||||
import type { CodexChatHost } from "../ports/chat-host";
|
||||
import type { PluginSettingsRef, ThreadSurfaceBroadcaster } from "../ports/chat-host";
|
||||
import { AutoTitleController } from "./auto-title-controller";
|
||||
import { ThreadRenameEditorController } from "./rename-editor-controller";
|
||||
|
||||
export interface ThreadNamingPartsContext {
|
||||
plugin: CodexChatHost;
|
||||
settingsRef: PluginSettingsRef;
|
||||
threadSurfaces: ThreadSurfaceBroadcaster;
|
||||
stateStore: ChatStateStore;
|
||||
client: {
|
||||
currentClient: () => AppServerClient | null;
|
||||
|
|
@ -22,22 +23,26 @@ export interface ThreadNamingParts {
|
|||
}
|
||||
|
||||
export function createThreadNamingParts(context: ThreadNamingPartsContext): ThreadNamingParts {
|
||||
const { plugin, stateStore, client, status } = context;
|
||||
const { settingsRef, threadSurfaces, stateStore, client, status } = context;
|
||||
const rename = new ThreadRenameEditorController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient: client.currentClient,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
});
|
||||
const autoTitle = new AutoTitleController({
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
currentClient: client.currentClient,
|
||||
notifyThreadRenamed: plugin.notifyThreadRenamed.bind(plugin),
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { inheritedForkThreadName, normalizeExplicitThreadName } from "../../../.
|
|||
import type { CodexPanelSettings } from "../../../../settings/model";
|
||||
import type { ArchiveExportAdapter } from "../../../thread-export/archive-markdown";
|
||||
import { exportArchivedThreadMarkdown } from "../../../thread-export/archive-markdown";
|
||||
import type { CodexChatHost } from "../ports/chat-host";
|
||||
import type { PluginSettingsRef, ThreadSurfaceBroadcaster, WorkspacePanels } from "../ports/chat-host";
|
||||
import {
|
||||
archivedSourceOpenForkFailedMessage,
|
||||
finishBeforeArchivingThreadsMessage,
|
||||
|
|
@ -58,7 +58,9 @@ export interface ThreadManagementActionsContext {
|
|||
obsidian: {
|
||||
archiveAdapter: () => ArchiveExportAdapter;
|
||||
};
|
||||
plugin: CodexChatHost;
|
||||
settingsRef: PluginSettingsRef;
|
||||
workspace: Pick<WorkspacePanels, "openThreadInNewView">;
|
||||
threadSurfaces: Pick<ThreadSurfaceBroadcaster, "notifyThreadArchived" | "notifyThreadRenamed" | "refreshSharedThreadListFromOpenSurface">;
|
||||
stateStore: ChatStateStore;
|
||||
client: {
|
||||
currentClient: () => AppServerClient | null;
|
||||
|
|
@ -92,11 +94,11 @@ type ConnectedRenameThreadHost = Pick<
|
|||
>;
|
||||
|
||||
export function createThreadManagementActions(context: ThreadManagementActionsContext): ThreadManagementActions {
|
||||
const { obsidian, plugin, stateStore, client, status, notify, thread, composer } = context;
|
||||
const { obsidian, settingsRef, workspace, threadSurfaces, stateStore, client, status, notify, thread, composer } = context;
|
||||
const host: ThreadManagementActionsHost = {
|
||||
stateStore,
|
||||
vaultPath: plugin.vaultPath,
|
||||
settings: () => plugin.settings,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
archiveAdapter: obsidian.archiveAdapter,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient: client.currentClient,
|
||||
|
|
@ -104,16 +106,16 @@ export function createThreadManagementActions(context: ThreadManagementActionsCo
|
|||
showNotice: notify.showNotice,
|
||||
setStatus: status.set,
|
||||
setComposerText: composer.setText,
|
||||
openThreadInNewView: (threadId) => plugin.openThreadInNewView(threadId),
|
||||
openThreadInNewView: workspace.openThreadInNewView,
|
||||
openThreadInCurrentPanel: thread.selectThread,
|
||||
notifyThreadArchived: plugin.notifyThreadArchived.bind(plugin),
|
||||
notifyThreadArchived: threadSurfaces.notifyThreadArchived,
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
plugin.notifyThreadRenamed(threadId, name);
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
refreshThreads: thread.refreshThreads,
|
||||
refreshSharedThreadListFromOpenSurface: () => {
|
||||
plugin.refreshSharedThreadListFromOpenSurface();
|
||||
threadSurfaces.refreshSharedThreadListFromOpenSurface();
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import { currentModel, runtimeConfigOrDefault } from "../domain/runtime/effectiv
|
|||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
import { activeTurnId, type ChatStateStore } from "../application/state/reducer";
|
||||
import type { ChatPanelComposerShellState } from "../panel/shell-state";
|
||||
import type { CodexChatHost } from "../application/ports/chat-host";
|
||||
import type { PluginSettingsRef } from "../application/ports/chat-host";
|
||||
import type { ChatRuntimeSettingsActions } from "../application/runtime/settings-actions";
|
||||
import { ChatComposerController } from "../panel/composer-controller";
|
||||
import type { ChatPanelComposerProjection } from "../panel/surface/model";
|
||||
|
||||
export interface ConversationComposerContext {
|
||||
app: App;
|
||||
plugin: CodexChatHost;
|
||||
settingsRef: PluginSettingsRef;
|
||||
stateStore: ChatStateStore;
|
||||
viewId: string;
|
||||
surface: {
|
||||
|
|
@ -36,14 +36,14 @@ export function createConversationComposer(
|
|||
context: ConversationComposerContext,
|
||||
refs: ConversationComposerRefs,
|
||||
): ConversationComposerParts {
|
||||
const { app, plugin, stateStore, viewId, surface, liveState } = context;
|
||||
const { app, settingsRef, stateStore, viewId, surface, liveState } = context;
|
||||
const scrollBridge = new MessageStreamScrollBridge();
|
||||
const controller = new ChatComposerController({
|
||||
app,
|
||||
stateStore,
|
||||
viewId,
|
||||
sendShortcut: () => plugin.settings.sendShortcut,
|
||||
scrollThreadFromComposerEdges: () => plugin.settings.scrollThreadFromComposerEdges,
|
||||
sendShortcut: () => settingsRef.settings.sendShortcut,
|
||||
scrollThreadFromComposerEdges: () => settingsRef.settings.scrollThreadFromComposerEdges,
|
||||
canInterrupt: (state) => {
|
||||
return state.turn.lifecycle.kind !== "idle" && Boolean(state.activeThread.id && activeTurnId(state));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type { HistoryController } from "../application/threads/history-controlle
|
|||
import type { ChatInboundController } from "../app-server/inbound/controller";
|
||||
import type { MessageStreamItem, MessageStreamNoticeSection } from "../domain/message-stream/items";
|
||||
import type { ChatPanelComposerShellState } from "../panel/shell-state";
|
||||
import type { CodexChatHost } from "../application/ports/chat-host";
|
||||
import type { PluginSettingsRef, WorkspacePanels } from "../application/ports/chat-host";
|
||||
import { MessageStreamPresenter } from "../panel/surface/message-stream-presenter";
|
||||
import type { ChatMessageScrollIntentState } from "../panel/surface/message-stream-scroll-intent";
|
||||
import type { ChatPanelComposerProjection } from "../panel/surface/model";
|
||||
|
|
@ -23,7 +23,8 @@ interface ConversationPartsContext {
|
|||
owner: Component;
|
||||
viewId: string;
|
||||
};
|
||||
plugin: CodexChatHost;
|
||||
settingsRef: PluginSettingsRef;
|
||||
workspace: WorkspacePanels;
|
||||
state: {
|
||||
stateStore: ChatStateStore;
|
||||
};
|
||||
|
|
@ -78,7 +79,7 @@ export function createConversationParts(
|
|||
history: HistoryController;
|
||||
},
|
||||
) {
|
||||
const { plugin, state, surface, runtime, thread, liveState, status, lifecycle, client, scroll } = context;
|
||||
const { settingsRef, workspace, state, surface, runtime, thread, liveState, status, lifecycle, client, scroll } = context;
|
||||
const { app, owner, viewId } = context.obsidian;
|
||||
const stateStore = state.stateStore;
|
||||
const currentClient = client.getClient;
|
||||
|
|
@ -86,7 +87,7 @@ export function createConversationParts(
|
|||
const composer = createConversationComposer(
|
||||
{
|
||||
app,
|
||||
plugin,
|
||||
settingsRef,
|
||||
stateStore,
|
||||
viewId,
|
||||
surface: {
|
||||
|
|
@ -112,7 +113,7 @@ export function createConversationParts(
|
|||
|
||||
const turnActions = createConversationTurnActions(
|
||||
{
|
||||
vaultPath: plugin.vaultPath,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
stateStore,
|
||||
client: {
|
||||
currentClient,
|
||||
|
|
@ -150,7 +151,7 @@ export function createConversationParts(
|
|||
store: stateStore,
|
||||
},
|
||||
workspace: {
|
||||
vaultPath: plugin.vaultPath,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
},
|
||||
scroll: {
|
||||
consumeIntent: () => lifecycle.messageScrollIntent.consumeIntent(),
|
||||
|
|
@ -166,7 +167,7 @@ export function createConversationParts(
|
|||
rollbackThread: (threadId) => void refs.threadActions.rollbackThread(threadId),
|
||||
forkThreadFromTurn: (threadId, turnId, archiveSource) => void refs.threadActions.forkThreadFromTurn(threadId, turnId, archiveSource),
|
||||
implementPlan: (item: MessageStreamItem) => void turnActions.planImplementation.implement(item),
|
||||
openTurnDiff: (state) => void plugin.openTurnDiff(state),
|
||||
openTurnDiff: (state) => void workspace.openTurnDiff(state),
|
||||
},
|
||||
requests: {
|
||||
pendingSignature: surface.pendingRequestsSignature,
|
||||
|
|
|
|||
|
|
@ -340,8 +340,8 @@ export class ChatPanelSession {
|
|||
private createSessionParts(): ChatPanelSessionParts {
|
||||
const connectionHandlers = this.createConnectionHandlers();
|
||||
const connection = new ConnectionManager(
|
||||
() => this.environment.plugin.settings.codexPath,
|
||||
this.environment.plugin.vaultPath,
|
||||
() => this.environment.plugin.settingsRef.settings.codexPath,
|
||||
this.environment.plugin.settingsRef.vaultPath,
|
||||
connectionHandlers.handlers,
|
||||
);
|
||||
const connectionControllerRef = createChatPanelSessionDeferredRef<ChatConnectionController>("chat connection controller");
|
||||
|
|
@ -372,7 +372,9 @@ export class ChatPanelSession {
|
|||
obsidian: {
|
||||
archiveAdapter: this.environment.obsidian.archiveAdapter,
|
||||
},
|
||||
plugin: this.environment.plugin,
|
||||
settingsRef: this.environment.plugin.settingsRef,
|
||||
workspace: this.environment.plugin.workspace,
|
||||
threadSurfaces: this.environment.plugin.threadSurfaces,
|
||||
state: {
|
||||
stateStore: this.stateStore,
|
||||
},
|
||||
|
|
@ -427,7 +429,7 @@ export class ChatPanelSession {
|
|||
});
|
||||
const selection = createThreadSelectionActions(
|
||||
{
|
||||
plugin: this.environment.plugin,
|
||||
workspace: this.environment.plugin.workspace,
|
||||
state: {
|
||||
stateStore: this.stateStore,
|
||||
},
|
||||
|
|
@ -492,8 +494,8 @@ export class ChatPanelSession {
|
|||
);
|
||||
const surface = createChatPanelSurface(
|
||||
{
|
||||
settings: this.environment.plugin.settings,
|
||||
vaultPath: this.environment.plugin.vaultPath,
|
||||
settings: this.environment.plugin.settingsRef.settings,
|
||||
vaultPath: this.environment.plugin.settingsRef.vaultPath,
|
||||
stateStore: this.stateStore,
|
||||
restoredThreadPlaceholder: () => restoration.placeholder(),
|
||||
},
|
||||
|
|
@ -513,7 +515,8 @@ export class ChatPanelSession {
|
|||
owner: this.environment.obsidian.owner,
|
||||
viewId: this.environment.obsidian.viewId,
|
||||
},
|
||||
plugin: this.environment.plugin,
|
||||
settingsRef: this.environment.plugin.settingsRef,
|
||||
workspace: this.environment.plugin.workspace,
|
||||
state: {
|
||||
stateStore: this.stateStore,
|
||||
},
|
||||
|
|
@ -623,28 +626,28 @@ export class ChatPanelSession {
|
|||
}): ChatPanelSessionServerParts {
|
||||
const serverMetadata = createChatServerMetadataActions({
|
||||
stateStore: this.stateStore,
|
||||
vaultPath: this.environment.plugin.vaultPath,
|
||||
vaultPath: this.environment.plugin.settingsRef.vaultPath,
|
||||
currentClient: sessionPorts.currentClient,
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
this.environment.plugin.publishAppServerMetadata(metadata);
|
||||
this.environment.plugin.threadSurfaces.publishAppServerMetadata(metadata);
|
||||
},
|
||||
});
|
||||
const serverDiagnostics = createChatServerDiagnosticsActions({
|
||||
stateStore: this.stateStore,
|
||||
vaultPath: this.environment.plugin.vaultPath,
|
||||
vaultPath: this.environment.plugin.settingsRef.vaultPath,
|
||||
currentClient: sessionPorts.currentClient,
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
this.environment.plugin.publishAppServerMetadata(metadata);
|
||||
this.environment.plugin.threadSurfaces.publishAppServerMetadata(metadata);
|
||||
},
|
||||
serverMetadataSnapshot: () => serverMetadata.serverMetadataSnapshot(),
|
||||
});
|
||||
const serverThreads = createChatServerThreadActions({
|
||||
stateStore: this.stateStore,
|
||||
vaultPath: this.environment.plugin.vaultPath,
|
||||
vaultPath: this.environment.plugin.settingsRef.vaultPath,
|
||||
currentClient: sessionPorts.currentClient,
|
||||
runtimeSnapshotForState: runtimeSnapshotForChatState,
|
||||
publishThreadList: (threads) => {
|
||||
this.environment.plugin.applyThreadListSnapshot(threads);
|
||||
this.environment.plugin.threadSurfaces.applyThreadListSnapshot(threads);
|
||||
},
|
||||
syncThreadGoal: (threadId) => {
|
||||
void goals.syncThreadGoal(threadId);
|
||||
|
|
@ -667,8 +670,12 @@ export class ChatPanelSession {
|
|||
maybeNameThread: (threadId, turnId, completedSummary) => {
|
||||
autoTitle.maybeAutoTitleThread(threadId, turnId, completedSummary);
|
||||
},
|
||||
notifyThreadArchived: this.environment.plugin.notifyThreadArchived.bind(this.environment.plugin),
|
||||
notifyThreadRenamed: this.environment.plugin.notifyThreadRenamed.bind(this.environment.plugin),
|
||||
notifyThreadArchived: (threadId) => {
|
||||
this.environment.plugin.threadSurfaces.notifyThreadArchived(threadId);
|
||||
},
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
this.environment.plugin.threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
recordMcpStartupStatus: (name, status, message) => {
|
||||
serverDiagnostics.recordMcpStartupStatus(name, status, message);
|
||||
},
|
||||
|
|
@ -707,9 +714,9 @@ export class ChatPanelSession {
|
|||
setStatus: sideEffects.status.set,
|
||||
addSystemMessage: sideEffects.status.addSystemMessage,
|
||||
publishAppServerIdentity: (userAgent) => {
|
||||
this.environment.plugin.publishAppServerIdentity(userAgent);
|
||||
this.environment.plugin.appServerIdentity.publishAppServerIdentity(userAgent);
|
||||
},
|
||||
configuredCommand: () => this.environment.plugin.settings.codexPath,
|
||||
configuredCommand: () => this.environment.plugin.settingsRef.settings.codexPath,
|
||||
refreshLiveState: () => {
|
||||
this.refreshLiveState();
|
||||
},
|
||||
|
|
@ -783,9 +790,9 @@ export class ChatPanelSession {
|
|||
}
|
||||
|
||||
private applyCachedAppServerState(): void {
|
||||
const threads = this.environment.plugin.cachedThreadList();
|
||||
const threads = this.environment.plugin.sharedCache.cachedThreadList();
|
||||
if (threads) this.parts.serverActions.threads.applyThreadList(threads);
|
||||
const metadata = this.environment.plugin.cachedAppServerMetadata();
|
||||
const metadata = this.environment.plugin.sharedCache.cachedAppServerMetadata();
|
||||
if (metadata) this.parts.serverActions.metadata.applyAppServerMetadata(metadata);
|
||||
}
|
||||
|
||||
|
|
@ -794,7 +801,7 @@ export class ChatPanelSession {
|
|||
if (!root) return;
|
||||
renderChatPanelShell(root, {
|
||||
stateStore: this.stateStore,
|
||||
showToolbar: this.environment.plugin.settings.showToolbar,
|
||||
showToolbar: this.environment.plugin.settingsRef.settings.showToolbar,
|
||||
parts: {
|
||||
toolbar: {
|
||||
surface: this.parts.surface.toolbar,
|
||||
|
|
@ -828,7 +835,7 @@ export class ChatPanelSession {
|
|||
}
|
||||
|
||||
private async loadSharedThreadList(): Promise<void> {
|
||||
const threads = await this.environment.plugin.refreshThreadList(() => this.parts.serverActions.threads.loadThreadList());
|
||||
const threads = await this.environment.plugin.sharedCache.refreshThreadList(() => this.parts.serverActions.threads.loadThreadList());
|
||||
this.parts.serverActions.threads.applyThreadList(threads);
|
||||
}
|
||||
|
||||
|
|
@ -842,7 +849,7 @@ export class ChatPanelSession {
|
|||
}
|
||||
|
||||
private refreshLiveState(): void {
|
||||
this.environment.plugin.refreshThreadsViewLiveState();
|
||||
this.environment.plugin.threadSurfaces.refreshThreadsViewLiveState();
|
||||
}
|
||||
|
||||
private deferLiveStateRefresh(): void {
|
||||
|
|
@ -909,7 +916,7 @@ export class ChatPanelSession {
|
|||
return connectionDiagnosticsModel({
|
||||
state: this.state,
|
||||
connected: this.parts.connection.manager.isConnected(),
|
||||
configuredCommand: this.environment.plugin.settings.codexPath,
|
||||
configuredCommand: this.environment.plugin.settingsRef.settings.codexPath,
|
||||
}).map((section) => ({
|
||||
title: section.title,
|
||||
auditFacts: section.rows.map((row) => ({ key: row.label, value: row.value })),
|
||||
|
|
|
|||
57
src/main.ts
57
src/main.ts
|
|
@ -165,35 +165,42 @@ export default class CodexPanelPlugin extends Plugin {
|
|||
|
||||
private chatHost(): CodexChatHost {
|
||||
return {
|
||||
settings: this.settings,
|
||||
vaultPath: this.vaultPath,
|
||||
openThreadInNewView: (threadId) => this.panels.openThreadInNewView(threadId),
|
||||
focusThreadInOpenView: (threadId) => this.panels.focusThreadInOpenView(threadId),
|
||||
openTurnDiff: (state) => this.openTurnDiff(state),
|
||||
notifyThreadArchived: (threadId) => {
|
||||
this.threadSurfaces.notifyThreadArchived(threadId);
|
||||
settingsRef: this,
|
||||
workspace: {
|
||||
openThreadInNewView: (threadId) => this.panels.openThreadInNewView(threadId),
|
||||
focusThreadInOpenView: (threadId) => this.panels.focusThreadInOpenView(threadId),
|
||||
openTurnDiff: (state) => this.openTurnDiff(state),
|
||||
},
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
this.threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
threadSurfaces: {
|
||||
notifyThreadArchived: (threadId) => {
|
||||
this.threadSurfaces.notifyThreadArchived(threadId);
|
||||
},
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
this.threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
refreshThreadsViewLiveState: () => {
|
||||
this.threadSurfaces.refreshThreadsViewLiveState();
|
||||
},
|
||||
refreshSharedThreadListFromOpenSurface: () => {
|
||||
this.threadSurfaces.refreshSharedThreadListFromOpenSurface();
|
||||
},
|
||||
applyThreadListSnapshot: (threads) => {
|
||||
this.applyThreadListSnapshot(threads);
|
||||
},
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
this.publishAppServerMetadata(metadata);
|
||||
},
|
||||
},
|
||||
refreshThreadsViewLiveState: () => {
|
||||
this.threadSurfaces.refreshThreadsViewLiveState();
|
||||
sharedCache: {
|
||||
refreshThreadList: (fetchThreads) => this.refreshThreadList(fetchThreads),
|
||||
cachedThreadList: () => this.cachedThreadList(),
|
||||
cachedAppServerMetadata: () => this.sharedAppServerCache.cachedAppServerMetadata(this.sharedAppServerCacheContext()),
|
||||
},
|
||||
refreshSharedThreadListFromOpenSurface: () => {
|
||||
this.threadSurfaces.refreshSharedThreadListFromOpenSurface();
|
||||
appServerIdentity: {
|
||||
publishAppServerIdentity: (userAgent) => {
|
||||
this.publishAppServerIdentity(userAgent);
|
||||
},
|
||||
},
|
||||
applyThreadListSnapshot: (threads) => {
|
||||
this.applyThreadListSnapshot(threads);
|
||||
},
|
||||
refreshThreadList: (fetchThreads) => this.refreshThreadList(fetchThreads),
|
||||
cachedThreadList: () => this.cachedThreadList(),
|
||||
publishAppServerMetadata: (metadata) => {
|
||||
this.publishAppServerMetadata(metadata);
|
||||
},
|
||||
publishAppServerIdentity: (userAgent) => {
|
||||
this.publishAppServerIdentity(userAgent);
|
||||
},
|
||||
cachedAppServerMetadata: () => this.sharedAppServerCache.cachedAppServerMetadata(this.sharedAppServerCacheContext()),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1242,30 +1242,61 @@ async function flushAsyncTicks(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function chatHost(overrides: Partial<CodexChatHost> = {}): CodexChatHost {
|
||||
interface ChatHostFixtureOverrides {
|
||||
settings?: Partial<typeof DEFAULT_SETTINGS>;
|
||||
vaultPath?: string;
|
||||
openThreadInNewView?: CodexChatHost["workspace"]["openThreadInNewView"];
|
||||
focusThreadInOpenView?: CodexChatHost["workspace"]["focusThreadInOpenView"];
|
||||
openTurnDiff?: CodexChatHost["workspace"]["openTurnDiff"];
|
||||
notifyThreadArchived?: CodexChatHost["threadSurfaces"]["notifyThreadArchived"];
|
||||
notifyThreadRenamed?: CodexChatHost["threadSurfaces"]["notifyThreadRenamed"];
|
||||
refreshSharedThreadListFromOpenSurface?: CodexChatHost["threadSurfaces"]["refreshSharedThreadListFromOpenSurface"];
|
||||
refreshThreadsViewLiveState?: CodexChatHost["threadSurfaces"]["refreshThreadsViewLiveState"];
|
||||
applyThreadListSnapshot?: CodexChatHost["threadSurfaces"]["applyThreadListSnapshot"];
|
||||
publishAppServerMetadata?: CodexChatHost["threadSurfaces"]["publishAppServerMetadata"];
|
||||
refreshThreadList?: CodexChatHost["sharedCache"]["refreshThreadList"];
|
||||
cachedThreadList?: CodexChatHost["sharedCache"]["cachedThreadList"];
|
||||
cachedAppServerMetadata?: CodexChatHost["sharedCache"]["cachedAppServerMetadata"];
|
||||
publishAppServerIdentity?: CodexChatHost["appServerIdentity"]["publishAppServerIdentity"];
|
||||
}
|
||||
|
||||
function chatHost(overrides: ChatHostFixtureOverrides = {}): CodexChatHost {
|
||||
const settings = {
|
||||
...DEFAULT_SETTINGS,
|
||||
codexPath: "codex",
|
||||
sendShortcut: "enter" as const,
|
||||
...overrides.settings,
|
||||
};
|
||||
return {
|
||||
settings: {
|
||||
...DEFAULT_SETTINGS,
|
||||
codexPath: "codex",
|
||||
sendShortcut: "enter",
|
||||
settingsRef: {
|
||||
settings,
|
||||
vaultPath: overrides.vaultPath ?? "/vault",
|
||||
},
|
||||
workspace: {
|
||||
openThreadInNewView: overrides.openThreadInNewView ?? vi.fn(),
|
||||
focusThreadInOpenView: overrides.focusThreadInOpenView ?? vi.fn().mockResolvedValue(false),
|
||||
openTurnDiff: overrides.openTurnDiff ?? vi.fn(),
|
||||
},
|
||||
threadSurfaces: {
|
||||
notifyThreadArchived: overrides.notifyThreadArchived ?? vi.fn(),
|
||||
notifyThreadRenamed: overrides.notifyThreadRenamed ?? vi.fn(),
|
||||
refreshSharedThreadListFromOpenSurface: overrides.refreshSharedThreadListFromOpenSurface ?? vi.fn(),
|
||||
refreshThreadsViewLiveState: overrides.refreshThreadsViewLiveState ?? vi.fn(),
|
||||
applyThreadListSnapshot: overrides.applyThreadListSnapshot ?? vi.fn(),
|
||||
publishAppServerMetadata: overrides.publishAppServerMetadata ?? vi.fn(),
|
||||
},
|
||||
sharedCache: {
|
||||
refreshThreadList:
|
||||
overrides.refreshThreadList ??
|
||||
(vi.fn(
|
||||
(fetchThreads: () => Promise<unknown>) => fetchThreads() as Promise<never[]>,
|
||||
) as CodexChatHost["sharedCache"]["refreshThreadList"]),
|
||||
cachedThreadList: overrides.cachedThreadList ?? vi.fn(() => null),
|
||||
cachedAppServerMetadata: overrides.cachedAppServerMetadata ?? vi.fn(() => null),
|
||||
},
|
||||
appServerIdentity: {
|
||||
publishAppServerIdentity: overrides.publishAppServerIdentity ?? vi.fn(),
|
||||
},
|
||||
vaultPath: "/vault",
|
||||
openThreadInNewView: vi.fn(),
|
||||
focusThreadInOpenView: vi.fn().mockResolvedValue(false),
|
||||
openTurnDiff: vi.fn(),
|
||||
notifyThreadArchived: vi.fn(),
|
||||
notifyThreadRenamed: vi.fn(),
|
||||
refreshSharedThreadListFromOpenSurface: vi.fn(),
|
||||
refreshThreadsViewLiveState: vi.fn(),
|
||||
applyThreadListSnapshot: vi.fn(),
|
||||
refreshThreadList: vi.fn(
|
||||
(fetchThreads: () => Promise<unknown>) => fetchThreads() as Promise<never[]>,
|
||||
) as CodexChatHost["refreshThreadList"],
|
||||
cachedThreadList: vi.fn(() => null),
|
||||
publishAppServerMetadata: vi.fn(),
|
||||
publishAppServerIdentity: vi.fn(),
|
||||
cachedAppServerMetadata: vi.fn(() => null),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -478,16 +478,16 @@ describe("CodexPanelPlugin boot restored panel loading", () => {
|
|||
const plugin = await pluginWithLeaves([]);
|
||||
const host = chatHost(plugin);
|
||||
|
||||
host.publishAppServerIdentity("codex-cli/1.2.3");
|
||||
await host.refreshThreadList(() => Promise.resolve([thread("versioned")]));
|
||||
host.appServerIdentity.publishAppServerIdentity("codex-cli/1.2.3");
|
||||
await host.sharedCache.refreshThreadList(() => Promise.resolve([thread("versioned")]));
|
||||
|
||||
expect(
|
||||
sharedAppServerCache(plugin).cachedThreadList(sharedAppServerCacheContext(plugin, { appServerUserAgent: "codex-cli/1.2.3" })),
|
||||
).toEqual([thread("versioned")]);
|
||||
|
||||
host.publishAppServerIdentity("codex-cli/9.9.9");
|
||||
host.appServerIdentity.publishAppServerIdentity("codex-cli/9.9.9");
|
||||
|
||||
expect(host.cachedThreadList()).toBeNull();
|
||||
expect(host.sharedCache.cachedThreadList()).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the previous shared thread list when refresh fails", async () => {
|
||||
|
|
@ -616,24 +616,38 @@ function chatView(CodexChatViewCtor: typeof CodexChatView, leaf: TestLeaf) {
|
|||
},
|
||||
containerEl,
|
||||
} as never,
|
||||
{
|
||||
chatHostFixture(),
|
||||
);
|
||||
}
|
||||
|
||||
function chatHostFixture(): CodexChatHost {
|
||||
return {
|
||||
settingsRef: {
|
||||
settings: { ...DEFAULT_SETTINGS, codexPath: "codex", sendShortcut: "enter" },
|
||||
vaultPath: "/vault",
|
||||
},
|
||||
workspace: {
|
||||
openThreadInNewView: vi.fn(),
|
||||
focusThreadInOpenView: vi.fn(),
|
||||
openTurnDiff: vi.fn(),
|
||||
},
|
||||
threadSurfaces: {
|
||||
notifyThreadArchived: vi.fn(),
|
||||
notifyThreadRenamed: vi.fn(),
|
||||
refreshSharedThreadListFromOpenSurface: vi.fn(),
|
||||
refreshThreadsViewLiveState: vi.fn(),
|
||||
applyThreadListSnapshot: vi.fn(),
|
||||
publishAppServerMetadata: vi.fn(),
|
||||
},
|
||||
sharedCache: {
|
||||
refreshThreadList: vi.fn((fetchThreads: () => Promise<unknown>) => fetchThreads() as Promise<never[]>),
|
||||
cachedThreadList: vi.fn(() => null),
|
||||
publishAppServerMetadata: vi.fn(),
|
||||
publishAppServerIdentity: vi.fn(),
|
||||
cachedAppServerMetadata: vi.fn(() => null),
|
||||
},
|
||||
);
|
||||
appServerIdentity: {
|
||||
publishAppServerIdentity: vi.fn(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function thread(id: string): Thread {
|
||||
|
|
|
|||
Loading…
Reference in a new issue