mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Simplify thin chat composition wrappers
This commit is contained in:
parent
89099deed6
commit
57feea86ea
20 changed files with 121 additions and 285 deletions
|
|
@ -15,17 +15,7 @@ export interface ChatReconnectActionsHost {
|
|||
addSystemMessage: (text: string) => void;
|
||||
}
|
||||
|
||||
export interface ChatReconnectActions {
|
||||
reconnectPanel: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function createChatReconnectActions(host: ChatReconnectActionsHost): ChatReconnectActions {
|
||||
return {
|
||||
reconnectPanel: () => reconnectPanel(host),
|
||||
};
|
||||
}
|
||||
|
||||
async function reconnectPanel(host: ChatReconnectActionsHost): Promise<void> {
|
||||
export async function reconnectPanel(host: ChatReconnectActionsHost): Promise<void> {
|
||||
const threadId = activeThreadId(host.stateStore.getState());
|
||||
host.stateStore.dispatch({ type: "ui/panel-set", panel: null });
|
||||
host.invalidateConnectionWork();
|
||||
|
|
|
|||
|
|
@ -37,13 +37,7 @@ export interface ComposerSubmitActions {
|
|||
submit: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function createComposerSubmitActions(host: ComposerSubmitActionsHost): ComposerSubmitActions {
|
||||
return {
|
||||
submit: () => submitComposer(host),
|
||||
};
|
||||
}
|
||||
|
||||
async function submitComposer(host: ComposerSubmitActionsHost): Promise<void> {
|
||||
export async function submitComposer(host: ComposerSubmitActionsHost): Promise<void> {
|
||||
const draft = host.composer.trimmedDraft;
|
||||
const state = submissionStateSnapshot(host.stateStore.getState());
|
||||
if (state.busy && state.activeThreadId && state.activeTurnId && draft.length === 0) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import type { AppServerClient } from "../../../../app-server/connection/client";
|
||||
import type { CodexInput } from "../../../../domain/chat/input";
|
||||
import type { ChatReconnectActions } from "../connection/reconnect-actions";
|
||||
import type { MessageStreamNoticeSection } from "../../domain/message-stream/items";
|
||||
import type { ChatRuntimeSettingsActions } from "../runtime/settings-actions";
|
||||
import { canImplementPlan } from "../state/selectors";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
import type { ThreadManagementActions } from "../threads/thread-management-actions";
|
||||
import type { GoalActions } from "../threads/goal-actions";
|
||||
import { createComposerSubmitActions, type ComposerSubmitActions } from "./composer-submit-actions";
|
||||
import { createPlanImplementation, type PlanImplementation } from "./plan-implementation";
|
||||
import { createSlashCommandHandler } from "./slash-command-handler";
|
||||
import { submitComposer, type ComposerSubmitActions, type ComposerSubmitActionsHost } from "./composer-submit-actions";
|
||||
import { implementPlan, type PlanImplementation, type PlanImplementationHost } from "./plan-implementation";
|
||||
import { executeSlashCommandWithState, type SlashCommandHandlerHost } from "./slash-command-handler";
|
||||
import { TurnSubmissionController } from "./turn-submission-controller";
|
||||
|
||||
export interface ConversationTurnActionsContext {
|
||||
|
|
@ -51,7 +51,7 @@ export interface ConversationTurnActionsRefs {
|
|||
threadStarter: ConversationThreadStarter;
|
||||
runtimeSettings: ChatRuntimeSettingsActions;
|
||||
threadActions: ThreadManagementActions;
|
||||
reconnectActions: ChatReconnectActions;
|
||||
reconnectPanel: () => Promise<void>;
|
||||
goals: GoalActions;
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ export function createConversationTurnActions(
|
|||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const slashCommands = createSlashCommandHandler({
|
||||
const slashCommandHost: SlashCommandHandlerHost = {
|
||||
stateStore,
|
||||
currentClient: client.currentClient,
|
||||
codexInput: composer.codexInput,
|
||||
|
|
@ -95,7 +95,7 @@ export function createConversationTurnActions(
|
|||
compactThread: (threadId) => refs.threadActions.compactThread(threadId),
|
||||
archiveThread: (threadId, saveMarkdown) => refs.threadActions.archiveThread(threadId, saveMarkdown),
|
||||
renameThread: (threadId, name) => refs.threadActions.renameThread(threadId, name).then(() => undefined),
|
||||
reconnect: () => refs.reconnectActions.reconnectPanel(),
|
||||
reconnect: refs.reconnectPanel,
|
||||
toggleFastMode: () => refs.runtimeSettings.toggleFastMode(),
|
||||
toggleCollaborationMode: () => refs.runtimeSettings.toggleCollaborationMode(),
|
||||
toggleAutoReview: () => void refs.runtimeSettings.toggleAutoReview(),
|
||||
|
|
@ -115,8 +115,8 @@ export function createConversationTurnActions(
|
|||
mcpStatusLines: runtime.mcpStatusLines,
|
||||
modelStatusLines: runtime.modelStatusLines,
|
||||
effortStatusLines: runtime.effortStatusLines,
|
||||
});
|
||||
const planImplementation = createPlanImplementation({
|
||||
};
|
||||
const planImplementationHost: PlanImplementationHost = {
|
||||
stateStore,
|
||||
currentClient: client.currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
|
|
@ -124,8 +124,8 @@ export function createConversationTurnActions(
|
|||
requestDefaultCollaborationModeForNextTurn: () => {
|
||||
refs.runtimeSettings.requestDefaultCollaborationModeForNextTurn();
|
||||
},
|
||||
});
|
||||
const composerSubmit = createComposerSubmitActions({
|
||||
};
|
||||
const composerSubmitHost: ComposerSubmitActionsHost = {
|
||||
stateStore,
|
||||
composer: {
|
||||
get trimmedDraft() {
|
||||
|
|
@ -133,7 +133,9 @@ export function createConversationTurnActions(
|
|||
},
|
||||
setDraft: composer.setDraft,
|
||||
},
|
||||
slashCommands,
|
||||
slashCommands: {
|
||||
execute: (command, args) => executeSlashCommandWithState(slashCommandHost, command, args),
|
||||
},
|
||||
turnSubmission,
|
||||
connection: {
|
||||
currentClient: client.currentClient,
|
||||
|
|
@ -144,11 +146,16 @@ export function createConversationTurnActions(
|
|||
addSystemMessage: status.addSystemMessage,
|
||||
},
|
||||
scroll,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
planImplementation,
|
||||
composerSubmit,
|
||||
planImplementation: {
|
||||
canImplement: (item) => canImplementPlan(stateStore.getState(), item),
|
||||
implement: (item) => implementPlan(planImplementationHost, item),
|
||||
},
|
||||
composerSubmit: {
|
||||
submit: () => submitComposer(composerSubmitHost),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,14 +18,7 @@ export interface PlanImplementation {
|
|||
implement: (item: MessageStreamItem) => Promise<void>;
|
||||
}
|
||||
|
||||
export function createPlanImplementation(host: PlanImplementationHost): PlanImplementation {
|
||||
return {
|
||||
canImplement: (item) => canImplementPlan(host.stateStore.getState(), item),
|
||||
implement: (item) => implementPlan(host, item),
|
||||
};
|
||||
}
|
||||
|
||||
async function implementPlan(host: PlanImplementationHost, item: MessageStreamItem): Promise<void> {
|
||||
export async function implementPlan(host: PlanImplementationHost, item: MessageStreamItem): Promise<void> {
|
||||
if (!canImplementPlan(host.stateStore.getState(), item)) return;
|
||||
await host.ensureConnected();
|
||||
if (!host.currentClient() || !activeThreadId(host.stateStore.getState())) return;
|
||||
|
|
|
|||
|
|
@ -27,17 +27,7 @@ export interface SlashCommandHandlerHost extends Omit<SlashCommandExecutionConte
|
|||
setStatus: (status: string) => void;
|
||||
}
|
||||
|
||||
export interface SlashCommandHandler {
|
||||
execute: (command: SlashCommandName, args: string) => Promise<SlashCommandExecutionResult | undefined>;
|
||||
}
|
||||
|
||||
export function createSlashCommandHandler(host: SlashCommandHandlerHost): SlashCommandHandler {
|
||||
return {
|
||||
execute: (command, args) => executeSlashCommand(host, command, args),
|
||||
};
|
||||
}
|
||||
|
||||
async function executeSlashCommand(
|
||||
export async function executeSlashCommandWithState(
|
||||
host: SlashCommandHandlerHost,
|
||||
command: SlashCommandName,
|
||||
args: string,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import type { CollaborationMode } from "../../domain/runtime/pending-settings";
|
||||
import type { TurnCollaborationModeWarning } from "../../domain/runtime/warnings";
|
||||
import type { TurnCollaborationModeWarning } from "./thread-settings-update";
|
||||
|
||||
const COLLABORATION_MODE_WARNING_MESSAGES: Record<TurnCollaborationModeWarning, string> = {
|
||||
"missing-model": "No effective model is available. Sending without a mode override.",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import { currentModel, currentReasoningEffort, fastRuntimeServiceTierRequestValu
|
|||
import type { RuntimeConfigSnapshot } from "../../../../domain/runtime/config";
|
||||
import type { RuntimeSnapshot } from "../../domain/runtime/snapshot";
|
||||
import { effectiveCollaborationMode, type PendingRuntimeSetting } from "../../domain/runtime/pending-settings";
|
||||
import type { TurnCollaborationModeWarning } from "../../domain/runtime/warnings";
|
||||
|
||||
export type TurnCollaborationModeWarning = "missing-model";
|
||||
|
||||
type TurnCollaborationModeSettings =
|
||||
| {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import { createSelectionActions } from "./selection-actions";
|
|||
import type { ChatResumeWorkTracker, ChatViewDeferredTasks } from "../lifecycle";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
import type { PluginSettingsRef, ThreadSurfaceBroadcaster, WorkspacePanels } from "../ports/chat-host";
|
||||
import { createThreadNamingParts } from "./naming-parts";
|
||||
import { createThreadManagementActions } from "./thread-management-actions";
|
||||
import { AutoTitleController } from "./auto-title-controller";
|
||||
import { ThreadRenameEditorController } from "./rename-editor-controller";
|
||||
import { createThreadManagementActions, type ThreadManagementActionsHost } from "./thread-management-actions";
|
||||
import { createThreadLifecycleParts } from "./lifecycle-parts";
|
||||
|
||||
interface ThreadPartsContext {
|
||||
|
|
@ -74,39 +75,55 @@ export function createThreadParts(context: ThreadPartsContext) {
|
|||
const stateStore = state.stateStore;
|
||||
const currentClient = client.getClient;
|
||||
|
||||
const naming = createThreadNamingParts({
|
||||
settingsRef,
|
||||
threadSurfaces,
|
||||
const rename = new ThreadRenameEditorController({
|
||||
stateStore,
|
||||
client: {
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
},
|
||||
status: {
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
});
|
||||
const autoTitle = new AutoTitleController({
|
||||
stateStore,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
currentClient,
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
});
|
||||
const { rename, autoTitle } = naming;
|
||||
|
||||
const managementActions = createThreadManagementActions({
|
||||
obsidian,
|
||||
settingsRef,
|
||||
workspace,
|
||||
threadSurfaces,
|
||||
const threadManagementHost: ThreadManagementActionsHost = {
|
||||
stateStore,
|
||||
client: {
|
||||
currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
archiveAdapter: obsidian.archiveAdapter,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
showNotice: notify.showNotice,
|
||||
setStatus: status.set,
|
||||
setComposerText: composer.setText,
|
||||
openThreadInNewView: (threadId) => workspace.openThreadInNewView(threadId),
|
||||
openThreadInCurrentPanel: (threadId) => thread.selectThread(threadId),
|
||||
notifyThreadArchived: (threadId) => {
|
||||
threadSurfaces.notifyThreadArchived(threadId);
|
||||
},
|
||||
status,
|
||||
notify,
|
||||
thread: {
|
||||
selectThread: thread.selectThread,
|
||||
refreshThreads: thread.refreshThreads,
|
||||
notifyIdentityChanged: thread.notifyIdentityChanged,
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
composer,
|
||||
});
|
||||
notifyActiveThreadIdentityChanged: () => {
|
||||
thread.notifyIdentityChanged();
|
||||
},
|
||||
refreshThreads: () => thread.refreshThreads(),
|
||||
refreshSharedThreadListFromOpenSurface: () => {
|
||||
threadSurfaces.refreshSharedThreadListFromOpenSurface();
|
||||
},
|
||||
};
|
||||
const managementActions = createThreadManagementActions(threadManagementHost);
|
||||
const goals = createGoalActions({
|
||||
stateStore,
|
||||
currentClient,
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
import type { AppServerClient } from "../../../../app-server/connection/client";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
import type { PluginSettingsRef, ThreadSurfaceBroadcaster } from "../ports/chat-host";
|
||||
import { AutoTitleController } from "./auto-title-controller";
|
||||
import { ThreadRenameEditorController } from "./rename-editor-controller";
|
||||
|
||||
export interface ThreadNamingPartsContext {
|
||||
settingsRef: PluginSettingsRef;
|
||||
threadSurfaces: ThreadSurfaceBroadcaster;
|
||||
stateStore: ChatStateStore;
|
||||
client: {
|
||||
currentClient: () => AppServerClient | null;
|
||||
ensureConnected: () => Promise<void>;
|
||||
};
|
||||
status: {
|
||||
addSystemMessage: (text: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ThreadNamingParts {
|
||||
rename: ThreadRenameEditorController;
|
||||
autoTitle: AutoTitleController;
|
||||
}
|
||||
|
||||
export function createThreadNamingParts(context: ThreadNamingPartsContext): ThreadNamingParts {
|
||||
const { settingsRef, threadSurfaces, stateStore, client, status } = context;
|
||||
const rename = new ThreadRenameEditorController({
|
||||
stateStore,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient: client.currentClient,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
});
|
||||
const autoTitle = new AutoTitleController({
|
||||
stateStore,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
currentClient: client.currentClient,
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
rename,
|
||||
autoTitle,
|
||||
};
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import type { CodexPanelSettings } from "../../../../settings/model";
|
|||
import type { ArchiveExportAdapter } from "../../../thread-operations/archive-markdown";
|
||||
import { archiveThreadOnAppServer } from "../../../thread-operations/archive";
|
||||
import { renameThreadOnAppServer, threadRenameFromValue, type ThreadRename } from "../../../thread-operations/rename";
|
||||
import type { PluginSettingsRef, ThreadSurfaceBroadcaster, WorkspacePanels } from "../ports/chat-host";
|
||||
import {
|
||||
archivedSourceOpenForkFailedMessage,
|
||||
finishBeforeArchivingThreadsMessage,
|
||||
|
|
@ -56,35 +55,6 @@ export interface ThreadManagementActions {
|
|||
rollbackThread: (threadId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface ThreadManagementActionsContext {
|
||||
obsidian: {
|
||||
archiveAdapter: () => ArchiveExportAdapter;
|
||||
};
|
||||
settingsRef: PluginSettingsRef;
|
||||
workspace: Pick<WorkspacePanels, "openThreadInNewView">;
|
||||
threadSurfaces: Pick<ThreadSurfaceBroadcaster, "notifyThreadArchived" | "notifyThreadRenamed" | "refreshSharedThreadListFromOpenSurface">;
|
||||
stateStore: ChatStateStore;
|
||||
client: {
|
||||
currentClient: () => AppServerClient | null;
|
||||
ensureConnected: () => Promise<void>;
|
||||
};
|
||||
status: {
|
||||
set: (status: string) => void;
|
||||
addSystemMessage: (text: string) => void;
|
||||
};
|
||||
notify: {
|
||||
showNotice: (text: string) => void;
|
||||
};
|
||||
thread: {
|
||||
selectThread: (threadId: string) => Promise<void>;
|
||||
refreshThreads: () => Promise<void>;
|
||||
notifyIdentityChanged: () => void;
|
||||
};
|
||||
composer: {
|
||||
setText: (text: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
type RenameThreadHost = Pick<
|
||||
ThreadManagementActionsHost,
|
||||
"ensureConnected" | "currentClient" | "stateStore" | "addSystemMessage" | "notifyThreadRenamed"
|
||||
|
|
@ -95,32 +65,7 @@ type ConnectedRenameThreadHost = Pick<
|
|||
"currentClient" | "stateStore" | "addSystemMessage" | "notifyThreadRenamed"
|
||||
>;
|
||||
|
||||
export function createThreadManagementActions(context: ThreadManagementActionsContext): ThreadManagementActions {
|
||||
const { obsidian, settingsRef, workspace, threadSurfaces, stateStore, client, status, notify, thread, composer } = context;
|
||||
const host: ThreadManagementActionsHost = {
|
||||
stateStore,
|
||||
vaultPath: settingsRef.vaultPath,
|
||||
settings: () => settingsRef.settings,
|
||||
archiveAdapter: obsidian.archiveAdapter,
|
||||
ensureConnected: client.ensureConnected,
|
||||
currentClient: client.currentClient,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
showNotice: notify.showNotice,
|
||||
setStatus: status.set,
|
||||
setComposerText: composer.setText,
|
||||
openThreadInNewView: workspace.openThreadInNewView,
|
||||
openThreadInCurrentPanel: thread.selectThread,
|
||||
notifyThreadArchived: threadSurfaces.notifyThreadArchived,
|
||||
notifyThreadRenamed: (threadId, name) => {
|
||||
threadSurfaces.notifyThreadRenamed(threadId, name);
|
||||
},
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
refreshThreads: thread.refreshThreads,
|
||||
refreshSharedThreadListFromOpenSurface: () => {
|
||||
threadSurfaces.refreshSharedThreadListFromOpenSurface();
|
||||
},
|
||||
};
|
||||
|
||||
export function createThreadManagementActions(host: ThreadManagementActionsHost): ThreadManagementActions {
|
||||
return {
|
||||
compactThread: (threadId) => compactThread(host, threadId),
|
||||
archiveThread: (threadId, saveMarkdown) => archiveThread(host, threadId, saveMarkdown),
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export type TurnCollaborationModeWarning = "missing-model";
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import type { App, Component } from "obsidian";
|
||||
import type { AppServerClient } from "../../../app-server/connection/client";
|
||||
import type { ChatStateStore } from "../application/state/store";
|
||||
import type { ChatReconnectActions } from "../application/connection/reconnect-actions";
|
||||
import { PendingRequestController } from "../application/pending-requests/controller";
|
||||
import type { ChatRuntimeSettingsActions } from "../application/runtime/settings-actions";
|
||||
import type { ThreadManagementActions } from "../application/threads/thread-management-actions";
|
||||
|
|
@ -74,7 +73,7 @@ export function createConversationParts(
|
|||
threadStarter: ConversationThreadStarter;
|
||||
runtimeSettings: ChatRuntimeSettingsActions;
|
||||
threadActions: ThreadManagementActions;
|
||||
reconnectActions: ChatReconnectActions;
|
||||
reconnectPanel: () => Promise<void>;
|
||||
goals: GoalActions;
|
||||
history: HistoryController;
|
||||
},
|
||||
|
|
@ -137,7 +136,7 @@ export function createConversationParts(
|
|||
threadStarter: refs.threadStarter,
|
||||
runtimeSettings: refs.runtimeSettings,
|
||||
threadActions: refs.threadActions,
|
||||
reconnectActions: refs.reconnectActions,
|
||||
reconnectPanel: refs.reconnectPanel,
|
||||
goals: refs.goals,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type { OpenCodexPanelSnapshot } from "../../../workspace/open-panel-snaps
|
|||
import type { ArchiveExportAdapter } from "../../thread-operations/archive-markdown";
|
||||
import type { CodexChatHost } from "../application/ports/chat-host";
|
||||
import { ChatConnectionController } from "../application/connection/connection-controller";
|
||||
import { createChatReconnectActions } from "../application/connection/reconnect-actions";
|
||||
import { reconnectPanel, type ChatReconnectActionsHost } from "../application/connection/reconnect-actions";
|
||||
import { createChatServerDiagnosticsActions, type ChatServerDiagnosticsActions } from "../app-server/actions/diagnostics";
|
||||
import { createChatServerMetadataActions, type ChatServerMetadataActions } from "../app-server/actions/metadata";
|
||||
import { createChatServerThreadActions, type ChatServerThreadActions } from "../app-server/actions/threads";
|
||||
|
|
@ -450,7 +450,7 @@ export class ChatPanelSession {
|
|||
);
|
||||
selectionRef.set(selection);
|
||||
|
||||
const reconnectActions = createChatReconnectActions({
|
||||
const reconnectHost: ChatReconnectActionsHost = {
|
||||
stateStore: this.stateStore,
|
||||
invalidateConnectionWork: () => {
|
||||
this.connectionWork.invalidate();
|
||||
|
|
@ -468,7 +468,8 @@ export class ChatPanelSession {
|
|||
ensureConnected: sessionPorts.ensureConnected,
|
||||
resumeThread: (threadId) => resume.resumeThread(threadId),
|
||||
addSystemMessage: sideEffects.status.addSystemMessage,
|
||||
});
|
||||
};
|
||||
const reconnect = () => reconnectPanel(reconnectHost);
|
||||
const serverParts = this.createServerParts({
|
||||
connection,
|
||||
sideEffects,
|
||||
|
|
@ -488,7 +489,7 @@ export class ChatPanelSession {
|
|||
},
|
||||
{
|
||||
connectionController,
|
||||
reconnectActions,
|
||||
reconnectPanel: reconnect,
|
||||
inboundController,
|
||||
threadActions,
|
||||
toolbarPanels,
|
||||
|
|
@ -578,7 +579,7 @@ export class ChatPanelSession {
|
|||
threadStarter: serverThreads,
|
||||
runtimeSettings,
|
||||
threadActions,
|
||||
reconnectActions,
|
||||
reconnectPanel: reconnect,
|
||||
goals,
|
||||
history,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import type { ThreadManagementActions } from "../application/threads/thread-mana
|
|||
import type { ChatAction, ChatState } from "../application/state/root-reducer";
|
||||
import type { ChatStateStore } from "../application/state/store";
|
||||
import type { ChatConnectionController } from "../application/connection/connection-controller";
|
||||
import type { ChatReconnectActions } from "../application/connection/reconnect-actions";
|
||||
import type { ThreadRenameEditorController } from "../application/threads/rename-editor-controller";
|
||||
import type { SelectionActions } from "../application/threads/selection-actions";
|
||||
import type { ChatInboundController } from "../app-server/inbound/controller";
|
||||
|
|
@ -33,7 +32,7 @@ export interface ChatPanelToolbarActionsHost {
|
|||
|
||||
export interface ChatPanelToolbarActionDependencies {
|
||||
connectionController: ChatConnectionController;
|
||||
reconnectActions: ChatReconnectActions;
|
||||
reconnectPanel: () => Promise<void>;
|
||||
inboundController: ChatInboundController;
|
||||
threadActions: ThreadManagementActions;
|
||||
toolbarPanels: ToolbarPanelActions;
|
||||
|
|
@ -154,7 +153,7 @@ export function createChatPanelToolbarActions(host: ChatPanelToolbarActionsHost,
|
|||
deps.toolbarPanels.toggleStatus();
|
||||
},
|
||||
connect: () => {
|
||||
void deps.reconnectActions.reconnectPanel();
|
||||
void deps.reconnectPanel();
|
||||
},
|
||||
refreshStatus: () => {
|
||||
void deps.connectionController.refreshStatusPanel();
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
|
||||
import { createChatState } from "../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore } from "../../../../src/features/chat/application/state/store";
|
||||
import {
|
||||
createChatReconnectActions,
|
||||
type ChatReconnectActionsHost,
|
||||
} from "../../../../src/features/chat/application/connection/reconnect-actions";
|
||||
import { reconnectPanel, type ChatReconnectActionsHost } from "../../../../src/features/chat/application/connection/reconnect-actions";
|
||||
|
||||
function createHost(overrides: Partial<ChatReconnectActionsHost> = {}) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
|
|
@ -36,12 +33,11 @@ function createHost(overrides: Partial<ChatReconnectActionsHost> = {}) {
|
|||
return { host, stateStore };
|
||||
}
|
||||
|
||||
describe("createChatReconnectActions", () => {
|
||||
describe("reconnectPanel", () => {
|
||||
it("resets local connection state before reconnecting and resumes the active thread", async () => {
|
||||
const { host, stateStore } = createHost();
|
||||
const controller = createChatReconnectActions(host);
|
||||
|
||||
await controller.reconnectPanel();
|
||||
await reconnectPanel(host);
|
||||
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
expect(host.invalidateConnectionWork).toHaveBeenCalledOnce();
|
||||
|
|
@ -57,9 +53,8 @@ describe("createChatReconnectActions", () => {
|
|||
const { host } = createHost({
|
||||
resumeThread: vi.fn().mockRejectedValue(new Error("resume failed")),
|
||||
});
|
||||
const controller = createChatReconnectActions(host);
|
||||
|
||||
await controller.reconnectPanel();
|
||||
await reconnectPanel(host);
|
||||
|
||||
expect(host.addSystemMessage).toHaveBeenCalledWith("resume failed");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
import type { AppServerClient } from "../../../../../src/app-server/connection/client";
|
||||
import { createChatState } from "../../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore } from "../../../../../src/features/chat/application/state/store";
|
||||
import { createComposerSubmitActions } from "../../../../../src/features/chat/application/conversation/composer-submit-actions";
|
||||
import { submitComposer } from "../../../../../src/features/chat/application/conversation/composer-submit-actions";
|
||||
import type { Thread } from "../../../../../src/domain/threads/model";
|
||||
|
||||
function thread(id: string): Thread {
|
||||
|
|
@ -17,7 +17,7 @@ function thread(id: string): Thread {
|
|||
};
|
||||
}
|
||||
|
||||
function createController(draft: string) {
|
||||
function createHost(draft: string) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const interruptTurn = vi.fn().mockResolvedValue({});
|
||||
const client = { interruptTurn } as unknown as AppServerClient;
|
||||
|
|
@ -25,7 +25,7 @@ function createController(draft: string) {
|
|||
const sendTurnText = vi.fn().mockResolvedValue(undefined);
|
||||
const execute = vi.fn().mockResolvedValue(undefined);
|
||||
const followBottom = vi.fn();
|
||||
const controller = createComposerSubmitActions({
|
||||
const host = {
|
||||
stateStore,
|
||||
composer: {
|
||||
get trimmedDraft() {
|
||||
|
|
@ -44,15 +44,15 @@ function createController(draft: string) {
|
|||
addSystemMessage: vi.fn(),
|
||||
},
|
||||
scroll: { followBottom },
|
||||
});
|
||||
return { controller, execute, followBottom, interruptTurn, sendTurnText, setDraft, stateStore };
|
||||
};
|
||||
return { host, execute, followBottom, interruptTurn, sendTurnText, setDraft, stateStore };
|
||||
}
|
||||
|
||||
describe("createComposerSubmitActions", () => {
|
||||
describe("submitComposer", () => {
|
||||
it("sends plain drafts as turn text", async () => {
|
||||
const { controller, followBottom, sendTurnText } = createController("hello");
|
||||
const { host, followBottom, sendTurnText } = createHost("hello");
|
||||
|
||||
await controller.submit();
|
||||
await submitComposer(host);
|
||||
|
||||
expect(followBottom).toHaveBeenCalledOnce();
|
||||
expect(sendTurnText).toHaveBeenCalledWith("hello");
|
||||
|
|
@ -65,10 +65,10 @@ describe("createComposerSubmitActions", () => {
|
|||
});
|
||||
|
||||
it("executes slash commands and forwards command send results", async () => {
|
||||
const { controller, execute, followBottom, sendTurnText, setDraft } = createController("/clear hello");
|
||||
const { host, execute, followBottom, sendTurnText, setDraft } = createHost("/clear hello");
|
||||
execute.mockResolvedValue({ sendText: "hello" });
|
||||
|
||||
await controller.submit();
|
||||
await submitComposer(host);
|
||||
|
||||
expect(setDraft).toHaveBeenCalledWith("", { clearSuggestions: true });
|
||||
expect(execute).toHaveBeenCalledWith("clear", "hello");
|
||||
|
|
@ -77,10 +77,10 @@ describe("createComposerSubmitActions", () => {
|
|||
});
|
||||
|
||||
it("restores slash command composer drafts from command results", async () => {
|
||||
const { controller, execute, followBottom, sendTurnText, setDraft } = createController("/goal edit");
|
||||
const { host, execute, followBottom, sendTurnText, setDraft } = createHost("/goal edit");
|
||||
execute.mockResolvedValue({ composerDraft: "/goal set Current objective" });
|
||||
|
||||
await controller.submit();
|
||||
await submitComposer(host);
|
||||
|
||||
expect(setDraft).toHaveBeenCalledWith("", { clearSuggestions: true });
|
||||
expect(setDraft).toHaveBeenCalledWith("/goal set Current objective", { focus: true, clearSuggestions: true });
|
||||
|
|
@ -89,7 +89,7 @@ describe("createComposerSubmitActions", () => {
|
|||
});
|
||||
|
||||
it("interrupts a running turn when submitting an empty draft", async () => {
|
||||
const { controller, followBottom, interruptTurn, stateStore } = createController("");
|
||||
const { host, followBottom, interruptTurn, stateStore } = createHost("");
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
thread: thread("thread"),
|
||||
|
|
@ -103,7 +103,7 @@ describe("createComposerSubmitActions", () => {
|
|||
});
|
||||
stateStore.dispatch({ type: "turn/started", threadId: "thread", turnId: "turn" });
|
||||
|
||||
await controller.submit();
|
||||
await submitComposer(host);
|
||||
|
||||
expect(followBottom).not.toHaveBeenCalled();
|
||||
expect(interruptTurn).toHaveBeenCalledWith("thread", "turn");
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ import type { AppServerClient } from "../../../../../src/app-server/connection/c
|
|||
import { createChatState } from "../../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore, type ChatStateStore } from "../../../../../src/features/chat/application/state/store";
|
||||
import { implementPlanCandidateFromState } from "../../../../../src/features/chat/application/state/selectors";
|
||||
import {
|
||||
createPlanImplementation,
|
||||
type PlanImplementationHost,
|
||||
} from "../../../../../src/features/chat/application/conversation/plan-implementation";
|
||||
import { implementPlan, type PlanImplementationHost } from "../../../../../src/features/chat/application/conversation/plan-implementation";
|
||||
import type { MessageStreamItem } from "../../../../../src/features/chat/domain/message-stream/items";
|
||||
|
||||
const planItem = (id: string): MessageStreamItem => ({
|
||||
|
|
@ -59,15 +56,15 @@ function createController({ client = {} as AppServerClient } = {}) {
|
|||
requestDefaultCollaborationModeForNextTurn,
|
||||
};
|
||||
return {
|
||||
controller: createPlanImplementation(host),
|
||||
ensureConnected,
|
||||
host,
|
||||
requestDefaultCollaborationModeForNextTurn,
|
||||
sendTurnText,
|
||||
stateStore,
|
||||
};
|
||||
}
|
||||
|
||||
describe("createPlanImplementation", () => {
|
||||
describe("implementPlan", () => {
|
||||
it("finds the latest proposed plan only when the thread is idle and in plan mode", () => {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const first = planItem("first");
|
||||
|
|
@ -91,12 +88,12 @@ describe("createPlanImplementation", () => {
|
|||
});
|
||||
|
||||
it("switches out of plan mode and submits the implementation prompt", async () => {
|
||||
const { controller, ensureConnected, requestDefaultCollaborationModeForNextTurn, sendTurnText, stateStore } = createController();
|
||||
const { host, ensureConnected, requestDefaultCollaborationModeForNextTurn, sendTurnText, stateStore } = createController();
|
||||
const plan = planItem("plan");
|
||||
resumeThread(stateStore, [plan]);
|
||||
stateStore.dispatch({ type: "ui/panel-set", panel: "status-panel" });
|
||||
|
||||
await controller.implement(plan);
|
||||
await implementPlan(host, plan);
|
||||
|
||||
expect(ensureConnected).toHaveBeenCalledOnce();
|
||||
expect(requestDefaultCollaborationModeForNextTurn).toHaveBeenCalledOnce();
|
||||
|
|
@ -106,12 +103,12 @@ describe("createPlanImplementation", () => {
|
|||
});
|
||||
|
||||
it("ignores stale plan items", async () => {
|
||||
const { controller, ensureConnected, sendTurnText, stateStore } = createController();
|
||||
const { host, ensureConnected, sendTurnText, stateStore } = createController();
|
||||
const first = planItem("first");
|
||||
const latest = planItem("latest");
|
||||
resumeThread(stateStore, [first, latest]);
|
||||
|
||||
await controller.implement(first);
|
||||
await implementPlan(host, first);
|
||||
|
||||
expect(ensureConnected).not.toHaveBeenCalled();
|
||||
expect(sendTurnText).not.toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { TurnItem, TurnRecord } from "../../../../../src/app-server/protoco
|
|||
import { createChatState } from "../../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore } from "../../../../../src/features/chat/application/state/store";
|
||||
import {
|
||||
createSlashCommandHandler,
|
||||
executeSlashCommandWithState,
|
||||
type SlashCommandHandlerHost,
|
||||
} from "../../../../../src/features/chat/application/conversation/slash-command-handler";
|
||||
import type { Thread } from "../../../../../src/domain/threads/model";
|
||||
|
|
@ -68,12 +68,11 @@ function createHost(overrides: SlashCommandHostOverrides = {}) {
|
|||
return { compactThread, host, stateStore, threadTurnsList };
|
||||
}
|
||||
|
||||
describe("createSlashCommandHandler", () => {
|
||||
describe("executeSlashCommandWithState", () => {
|
||||
it("executes slash commands against the current chat state", async () => {
|
||||
const { host } = createHost();
|
||||
const controller = createSlashCommandHandler(host);
|
||||
|
||||
const result = await controller.execute("clear", "");
|
||||
const result = await executeSlashCommandWithState(host, "clear", "");
|
||||
|
||||
expect(host.startNewThread).toHaveBeenCalledOnce();
|
||||
expect(result).toBeUndefined();
|
||||
|
|
@ -96,9 +95,8 @@ describe("createSlashCommandHandler", () => {
|
|||
approvalsReviewer: null,
|
||||
activePermissionProfile: null,
|
||||
});
|
||||
const controller = createSlashCommandHandler(host);
|
||||
|
||||
await controller.execute("compact", "");
|
||||
await executeSlashCommandWithState(host, "compact", "");
|
||||
|
||||
expect(compactThread).toHaveBeenCalledWith("thread");
|
||||
});
|
||||
|
|
@ -116,18 +114,16 @@ describe("createSlashCommandHandler", () => {
|
|||
approvalsReviewer: null,
|
||||
activePermissionProfile: null,
|
||||
});
|
||||
const controller = createSlashCommandHandler(host);
|
||||
|
||||
await controller.execute("compact", "");
|
||||
await executeSlashCommandWithState(host, "compact", "");
|
||||
|
||||
expect(compactThread).toHaveBeenCalledWith("thread");
|
||||
});
|
||||
|
||||
it("starts an empty panel before setting a slash command goal", async () => {
|
||||
const { host } = createHost();
|
||||
const controller = createSlashCommandHandler(host);
|
||||
|
||||
await controller.execute("goal", "set Ship this");
|
||||
await executeSlashCommandWithState(host, "goal", "set Ship this");
|
||||
|
||||
expect(host.startThreadForGoal).toHaveBeenCalledWith("Ship this");
|
||||
expect(host.setGoalObjective).toHaveBeenCalledWith("thread-new", "Ship this", null);
|
||||
|
|
@ -135,9 +131,8 @@ describe("createSlashCommandHandler", () => {
|
|||
|
||||
it("runs reconnect even when there is no current app-server client", async () => {
|
||||
const { host } = createHost({ currentClient: () => null });
|
||||
const controller = createSlashCommandHandler(host);
|
||||
|
||||
await controller.execute("reconnect", "");
|
||||
await executeSlashCommandWithState(host, "reconnect", "");
|
||||
|
||||
expect(host.reconnect).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
|
@ -148,9 +143,8 @@ describe("createSlashCommandHandler", () => {
|
|||
type: "thread-list/applied",
|
||||
threads: [thread("other", "Other")],
|
||||
});
|
||||
const controller = createSlashCommandHandler(host);
|
||||
|
||||
const result = await controller.execute("refer", "Other summarize");
|
||||
const result = await executeSlashCommandWithState(host, "refer", "Other summarize");
|
||||
|
||||
expect(threadTurnsList).toHaveBeenCalledWith("other", null, 20);
|
||||
expect(result).toBeUndefined();
|
||||
|
|
@ -167,9 +161,8 @@ describe("createSlashCommandHandler", () => {
|
|||
data: [turn([userMessage("u1", "元の依頼"), agentMessage("a1", "回答")])],
|
||||
nextCursor: null,
|
||||
});
|
||||
const controller = createSlashCommandHandler(host);
|
||||
|
||||
const result = await controller.execute("refer", "Other summarize");
|
||||
const result = await executeSlashCommandWithState(host, "refer", "Other summarize");
|
||||
|
||||
expect(result?.sendText).toBe("summarize");
|
||||
expect(host.setStatus).toHaveBeenCalledWith("Referencing 019abcde (1/20 turns).");
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ function toolbarActionDeps(): Parameters<typeof createChatPanelToolbarActions>[1
|
|||
connectionController: { refreshStatusPanel: vi.fn() } as unknown as Parameters<
|
||||
typeof createChatPanelToolbarActions
|
||||
>[1]["connectionController"],
|
||||
reconnectActions: { reconnectPanel: vi.fn() } as Parameters<typeof createChatPanelToolbarActions>[1]["reconnectActions"],
|
||||
reconnectPanel: vi.fn(),
|
||||
inboundController: { addSystemMessage: vi.fn() } as unknown as Parameters<typeof createChatPanelToolbarActions>[1]["inboundController"],
|
||||
threadActions: {
|
||||
archiveThread: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
|
|||
|
|
@ -406,39 +406,7 @@ function clientMock() {
|
|||
}
|
||||
|
||||
function threadManagementActions(host: ThreadManagementActionsHost): ThreadManagementActions {
|
||||
return createThreadManagementActions({
|
||||
obsidian: { archiveAdapter: host.archiveAdapter },
|
||||
settingsRef: {
|
||||
get settings() {
|
||||
return host.settings();
|
||||
},
|
||||
get vaultPath() {
|
||||
return host.vaultPath;
|
||||
},
|
||||
},
|
||||
workspace: { openThreadInNewView: host.openThreadInNewView },
|
||||
threadSurfaces: {
|
||||
notifyThreadArchived: host.notifyThreadArchived,
|
||||
notifyThreadRenamed: host.notifyThreadRenamed,
|
||||
refreshSharedThreadListFromOpenSurface: host.refreshSharedThreadListFromOpenSurface,
|
||||
},
|
||||
stateStore: host.stateStore,
|
||||
client: {
|
||||
currentClient: host.currentClient,
|
||||
ensureConnected: host.ensureConnected,
|
||||
},
|
||||
status: {
|
||||
set: host.setStatus,
|
||||
addSystemMessage: host.addSystemMessage,
|
||||
},
|
||||
notify: { showNotice: host.showNotice },
|
||||
thread: {
|
||||
selectThread: host.openThreadInCurrentPanel,
|
||||
refreshThreads: host.refreshThreads,
|
||||
notifyIdentityChanged: host.notifyActiveThreadIdentityChanged,
|
||||
},
|
||||
composer: { setText: host.setComposerText },
|
||||
});
|
||||
return createThreadManagementActions(host);
|
||||
}
|
||||
|
||||
function hostMock({
|
||||
|
|
|
|||
Loading…
Reference in a new issue