mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Centralize connected client acquisition
This commit is contained in:
parent
fe7f63e669
commit
dbc00031b0
11 changed files with 73 additions and 66 deletions
|
|
@ -22,7 +22,7 @@ export interface ComposerSubmitActionsHost {
|
|||
sendTurnText(text: string, codexInputOverride?: CodexInput, referencedThread?: ReferencedThreadMetadata): Promise<void>;
|
||||
};
|
||||
connection: {
|
||||
ensureConnected: () => Promise<void>;
|
||||
connectedClient: () => Promise<AppServerClient | null>;
|
||||
currentClient: () => AppServerClient | null;
|
||||
};
|
||||
status: {
|
||||
|
|
@ -52,11 +52,9 @@ async function sendMessage(host: ComposerSubmitActionsHost): Promise<void> {
|
|||
const text = host.composer.trimmedDraft;
|
||||
if (!text) return;
|
||||
|
||||
await host.connection.ensureConnected();
|
||||
if (!host.connection.currentClient()) return;
|
||||
|
||||
const slashCommand = parseSlashCommand(text);
|
||||
if (slashCommand) {
|
||||
if (!(await host.connection.connectedClient())) return;
|
||||
host.composer.setDraft("", { clearSuggestions: true });
|
||||
const result = await host.slashCommandExecutor.execute(slashCommand.command, slashCommand.args);
|
||||
if (result?.composerDraft !== undefined) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export interface ConversationTurnActionsContext {
|
|||
localItemIds: LocalIdSource;
|
||||
client: {
|
||||
currentClient: () => AppServerClient | null;
|
||||
ensureConnected: () => Promise<void>;
|
||||
connectedClient: () => Promise<AppServerClient | null>;
|
||||
};
|
||||
status: {
|
||||
set: (status: string) => void;
|
||||
|
|
@ -64,8 +64,7 @@ interface ConversationThreadStarter {
|
|||
|
||||
export interface PlanImplementationHost {
|
||||
stateStore: ChatStateStore;
|
||||
currentClient(): AppServerClient | null;
|
||||
ensureConnected(): Promise<void>;
|
||||
connectedClient(): Promise<AppServerClient | null>;
|
||||
sendTurnText(text: string): Promise<void>;
|
||||
requestDefaultCollaborationModeForNextTurn(): void;
|
||||
}
|
||||
|
|
@ -88,7 +87,7 @@ export function createConversationTurnActions(
|
|||
stateStore,
|
||||
vaultPath,
|
||||
localItemIds,
|
||||
currentClient: client.currentClient,
|
||||
connectedClient: client.connectedClient,
|
||||
ensureRestoredThreadLoaded: thread.ensureRestoredThreadLoaded,
|
||||
startThread: (preview) => refs.threadStarter.startThread(preview),
|
||||
notifyActiveThreadIdentityChanged: thread.notifyIdentityChanged,
|
||||
|
|
@ -121,8 +120,7 @@ export function createConversationTurnActions(
|
|||
};
|
||||
const planImplementationHost: PlanImplementationHost = {
|
||||
stateStore,
|
||||
currentClient: client.currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
connectedClient: client.connectedClient,
|
||||
sendTurnText: (text) => turnSubmission.sendTurnText(text),
|
||||
requestDefaultCollaborationModeForNextTurn: () => {
|
||||
refs.runtimeSettings.requestDefaultCollaborationModeForNextTurn();
|
||||
|
|
@ -141,8 +139,8 @@ export function createConversationTurnActions(
|
|||
},
|
||||
turnSubmission,
|
||||
connection: {
|
||||
connectedClient: client.connectedClient,
|
||||
currentClient: client.currentClient,
|
||||
ensureConnected: client.ensureConnected,
|
||||
},
|
||||
status: {
|
||||
setStatus: status.set,
|
||||
|
|
@ -168,8 +166,7 @@ async function startThreadForGoal(starter: ConversationThreadStarter, objective:
|
|||
|
||||
export async function implementPlan(host: PlanImplementationHost, itemId: string): Promise<void> {
|
||||
if (!canImplementPlanItemId(host.stateStore.getState(), itemId)) return;
|
||||
await host.ensureConnected();
|
||||
if (!host.currentClient() || !activeThreadId(host.stateStore.getState())) return;
|
||||
if (!(await host.connectedClient()) || !activeThreadId(host.stateStore.getState())) return;
|
||||
|
||||
host.requestDefaultCollaborationModeForNextTurn();
|
||||
host.stateStore.dispatch({ type: "ui/panel-set", panel: null });
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export interface TurnSubmissionActionsHost {
|
|||
stateStore: ChatStateStore;
|
||||
vaultPath: string;
|
||||
localItemIds: LocalIdSource;
|
||||
currentClient: () => AppServerClient | null;
|
||||
connectedClient: () => Promise<AppServerClient | null>;
|
||||
ensureRestoredThreadLoaded: () => Promise<boolean>;
|
||||
startThread: (preview?: string) => Promise<unknown>;
|
||||
notifyActiveThreadIdentityChanged: () => void;
|
||||
|
|
@ -61,9 +61,9 @@ async function sendTurnText(
|
|||
codexInputOverride?: CodexInput,
|
||||
referencedThread?: ReferencedThreadMetadata,
|
||||
): Promise<void> {
|
||||
if (!(await host.ensureRestoredThreadLoaded())) return;
|
||||
const client = host.currentClient();
|
||||
const client = await host.connectedClient();
|
||||
if (!client) return;
|
||||
if (!(await host.ensureRestoredThreadLoaded())) return;
|
||||
|
||||
const initialState = submissionStateSnapshot(host.stateStore.getState());
|
||||
const plan = planTurnSubmission(initialState);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export interface ThreadGoalSyncHost {
|
|||
}
|
||||
|
||||
export interface GoalActionsHost extends ThreadGoalSyncHost {
|
||||
ensureConnected: () => Promise<void>;
|
||||
connectedClient: () => Promise<AppServerClient | null>;
|
||||
startThread: (preview?: string, options?: { syncGoal?: boolean }) => Promise<{ threadId: string } | null>;
|
||||
}
|
||||
|
||||
|
|
@ -125,8 +125,7 @@ function setGoalStatus(host: GoalActionsHost, threadId: string, status: ThreadGo
|
|||
}
|
||||
|
||||
async function clearGoal(host: GoalActionsHost, threadId: string): Promise<boolean> {
|
||||
await host.ensureConnected();
|
||||
const client = host.currentClient();
|
||||
const client = await host.connectedClient();
|
||||
if (!client) return false;
|
||||
try {
|
||||
await client.clearThreadGoal(threadId);
|
||||
|
|
@ -139,8 +138,7 @@ async function clearGoal(host: GoalActionsHost, threadId: string): Promise<boole
|
|||
}
|
||||
|
||||
async function setGoal(host: GoalActionsHost, threadId: string, params: ThreadGoalUpdate): Promise<boolean> {
|
||||
await host.ensureConnected();
|
||||
const client = host.currentClient();
|
||||
const client = await host.connectedClient();
|
||||
if (!client) return false;
|
||||
try {
|
||||
return applyGoalIfActive(host, threadId, await setThreadGoal(client, threadId, params), { reportChange: true });
|
||||
|
|
@ -193,7 +191,8 @@ async function startThreadAndSaveObjective(
|
|||
plan: Extract<GoalObjectiveSavePlan, { kind: "start-thread-and-save" }>,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await host.ensureConnected();
|
||||
const client = await host.connectedClient();
|
||||
if (!client) return false;
|
||||
const response = await host.startThread(plan.objective, { syncGoal: false });
|
||||
const threadId = response?.threadId ?? null;
|
||||
return threadId ? await setObjective(host, threadId, plan.objective, plan.tokenBudget) : false;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export interface ThreadManagementActionsHost {
|
|||
stateStore: ChatStateStore;
|
||||
vaultPath: string;
|
||||
operations: ThreadOperations;
|
||||
ensureConnected: () => Promise<void>;
|
||||
connectedClient: () => Promise<AppServerClient | null>;
|
||||
currentClient: () => AppServerClient | null;
|
||||
addSystemMessage: (text: string) => void;
|
||||
setStatus: (status: string) => void;
|
||||
|
|
@ -101,8 +101,7 @@ async function compactActiveThread(host: ThreadManagementActionsHost): Promise<v
|
|||
}
|
||||
|
||||
async function compactThread(host: ThreadManagementActionsHost, threadId: string): Promise<void> {
|
||||
await host.ensureConnected();
|
||||
const client = host.currentClient();
|
||||
const client = await host.connectedClient();
|
||||
if (!client) return;
|
||||
const initialActiveThreadId = threadManagementState(host).activeThread.id;
|
||||
try {
|
||||
|
|
@ -148,8 +147,7 @@ async function forkThreadFromTurn(
|
|||
host.addSystemMessage(finishBeforeForkingThreadsMessage());
|
||||
return;
|
||||
}
|
||||
await host.ensureConnected();
|
||||
const client = host.currentClient();
|
||||
const client = await host.connectedClient();
|
||||
if (!client) return;
|
||||
|
||||
const initialActiveThreadId = threadManagementState(host).activeThread.id;
|
||||
|
|
@ -215,8 +213,7 @@ async function rollbackThread(host: ThreadManagementActionsHost, threadId: strin
|
|||
host.addSystemMessage(interruptBeforeRollbackMessage());
|
||||
return;
|
||||
}
|
||||
await host.ensureConnected();
|
||||
const client = host.currentClient();
|
||||
const client = await host.connectedClient();
|
||||
if (!client) return;
|
||||
|
||||
const candidate = messageStreamRollbackCandidate(threadManagementState(host).messageStream);
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ interface ChatPanelComposerAndTurnInput {
|
|||
connection: ConnectionManager;
|
||||
localItemIds: LocalIdSource;
|
||||
ensureConnected: () => Promise<void>;
|
||||
connectedClient: () => Promise<ReturnType<CurrentAppServerClient>>;
|
||||
currentClient: CurrentAppServerClient;
|
||||
status: ChatPanelSessionStatus;
|
||||
inboundHandler: ChatInboundHandler;
|
||||
|
|
@ -256,10 +257,14 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
const connectionController = controller;
|
||||
const { threads: serverThreads } = connectionBundle.serverActions;
|
||||
const ensureConnected = () => connectionController.ensureConnected();
|
||||
const connectedClient = async () => {
|
||||
await ensureConnected();
|
||||
return currentClient();
|
||||
};
|
||||
const refreshActiveThreads = () => connectionController.refreshActiveThreads();
|
||||
const threadOperations = createSessionThreadOperations(environment, currentClient);
|
||||
const runtimeSettings = createSessionRuntimeSettingsActions(host, currentClient, status);
|
||||
const goals = createSessionGoalActions(host, currentClient, localItemIds, ensureConnected, status, serverThreads);
|
||||
const goals = createSessionGoalActions(host, currentClient, localItemIds, connectedClient, status, serverThreads);
|
||||
const rename = createSessionThreadRenameEditorActions(stateStore, threadOperations, titleService, ensureConnected, status);
|
||||
const threadLifecycle = createSessionThreadLifecycle(
|
||||
host,
|
||||
|
|
@ -286,7 +291,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
};
|
||||
const threadActionParts = createThreadActionParts(host, {
|
||||
operations: threadOperations,
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
currentClient,
|
||||
status,
|
||||
composerController,
|
||||
|
|
@ -297,6 +302,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
connection,
|
||||
localItemIds,
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
currentClient,
|
||||
status,
|
||||
inboundHandler,
|
||||
|
|
@ -579,7 +585,7 @@ function createSessionGoalActions(
|
|||
host: ChatPanelSessionGraphHost,
|
||||
currentClient: CurrentAppServerClient,
|
||||
localItemIds: LocalIdSource,
|
||||
ensureConnected: () => Promise<void>,
|
||||
connectedClient: () => Promise<ReturnType<CurrentAppServerClient>>,
|
||||
status: ChatPanelSessionStatus,
|
||||
serverThreads: ChatServerThreadActions,
|
||||
): ChatPanelGoalActions {
|
||||
|
|
@ -587,7 +593,7 @@ function createSessionGoalActions(
|
|||
stateStore: host.stateStore,
|
||||
currentClient,
|
||||
localItemIds,
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
startThread: (preview, options) => serverThreads.startThread(preview, options),
|
||||
addSystemMessage: (text) => {
|
||||
status.addSystemMessage(text);
|
||||
|
|
@ -719,7 +725,7 @@ function createThreadActionParts(
|
|||
host: ChatPanelSessionGraphHost,
|
||||
input: {
|
||||
operations: ThreadOperations;
|
||||
ensureConnected: () => Promise<void>;
|
||||
connectedClient: () => Promise<ReturnType<CurrentAppServerClient>>;
|
||||
currentClient: CurrentAppServerClient;
|
||||
status: ChatPanelSessionStatus;
|
||||
composerController: ChatComposerController;
|
||||
|
|
@ -727,13 +733,13 @@ function createThreadActionParts(
|
|||
refreshActiveThreads: () => Promise<void>;
|
||||
},
|
||||
): ChatPanelThreadActionParts {
|
||||
const { operations, ensureConnected, currentClient, status, composerController, resume, refreshActiveThreads } = input;
|
||||
const { operations, connectedClient, currentClient, status, composerController, resume, refreshActiveThreads } = input;
|
||||
const { environment, stateStore } = host;
|
||||
const threadManagementHost: ThreadManagementActionsHost = {
|
||||
stateStore,
|
||||
vaultPath: environment.plugin.settingsRef.vaultPath,
|
||||
operations,
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
currentClient,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
setStatus: status.set,
|
||||
|
|
@ -777,6 +783,7 @@ function createComposerAndTurnActions(
|
|||
connection,
|
||||
localItemIds,
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
currentClient,
|
||||
status,
|
||||
inboundHandler,
|
||||
|
|
@ -831,7 +838,7 @@ function createComposerAndTurnActions(
|
|||
localItemIds,
|
||||
client: {
|
||||
currentClient,
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
},
|
||||
status,
|
||||
runtime: {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ function createHost(draft: string) {
|
|||
const sendTurnText = vi.fn().mockResolvedValue(undefined);
|
||||
const execute = vi.fn().mockResolvedValue(undefined);
|
||||
const followBottom = vi.fn();
|
||||
const connectedClient = vi.fn().mockResolvedValue(client);
|
||||
const host = {
|
||||
stateStore,
|
||||
composer: {
|
||||
|
|
@ -36,8 +37,8 @@ function createHost(draft: string) {
|
|||
slashCommandExecutor: { execute },
|
||||
turnSubmission: { sendTurnText },
|
||||
connection: {
|
||||
connectedClient,
|
||||
currentClient: () => client,
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
status: {
|
||||
setStatus: vi.fn(),
|
||||
|
|
@ -45,16 +46,17 @@ function createHost(draft: string) {
|
|||
},
|
||||
scroll: { followBottom },
|
||||
};
|
||||
return { host, execute, followBottom, interruptTurn, sendTurnText, setDraft, stateStore };
|
||||
return { host, connectedClient, execute, followBottom, interruptTurn, sendTurnText, setDraft, stateStore };
|
||||
}
|
||||
|
||||
describe("submitComposer", () => {
|
||||
it("sends plain drafts as turn text", async () => {
|
||||
const { host, followBottom, sendTurnText } = createHost("hello");
|
||||
const { host, connectedClient, followBottom, sendTurnText } = createHost("hello");
|
||||
|
||||
await submitComposer(host);
|
||||
|
||||
expect(followBottom).toHaveBeenCalledOnce();
|
||||
expect(connectedClient).not.toHaveBeenCalled();
|
||||
expect(sendTurnText).toHaveBeenCalledWith("hello");
|
||||
const [followBottomOrder] = followBottom.mock.invocationCallOrder;
|
||||
const [sendTurnTextOrder] = sendTurnText.mock.invocationCallOrder;
|
||||
|
|
@ -65,24 +67,26 @@ describe("submitComposer", () => {
|
|||
});
|
||||
|
||||
it("executes slash commands and forwards command send results", async () => {
|
||||
const { host, execute, followBottom, sendTurnText, setDraft } = createHost("/clear hello");
|
||||
const { host, connectedClient, execute, followBottom, sendTurnText, setDraft } = createHost("/clear hello");
|
||||
execute.mockResolvedValue({ sendText: "hello" });
|
||||
|
||||
await submitComposer(host);
|
||||
|
||||
expect(setDraft).toHaveBeenCalledWith("", { clearSuggestions: true });
|
||||
expect(connectedClient).toHaveBeenCalledOnce();
|
||||
expect(execute).toHaveBeenCalledWith("clear", "hello");
|
||||
expect(followBottom).toHaveBeenCalledOnce();
|
||||
expect(sendTurnText).toHaveBeenCalledWith("hello", undefined, undefined);
|
||||
});
|
||||
|
||||
it("restores slash command composer drafts from command results", async () => {
|
||||
const { host, execute, followBottom, sendTurnText, setDraft } = createHost("/goal edit");
|
||||
const { host, connectedClient, execute, followBottom, sendTurnText, setDraft } = createHost("/goal edit");
|
||||
execute.mockResolvedValue({ composerDraft: "/goal set Current objective" });
|
||||
|
||||
await submitComposer(host);
|
||||
|
||||
expect(setDraft).toHaveBeenCalledWith("", { clearSuggestions: true });
|
||||
expect(connectedClient).toHaveBeenCalledOnce();
|
||||
expect(setDraft).toHaveBeenCalledWith("/goal set Current objective", { focus: true, clearSuggestions: true });
|
||||
expect(followBottom).not.toHaveBeenCalled();
|
||||
expect(sendTurnText).not.toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -43,20 +43,19 @@ function resumeThread(stateStore: ChatStateStore, items: readonly MessageStreamI
|
|||
|
||||
function createController({ client = {} as AppServerClient } = {}) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const ensureConnected = vi.fn().mockResolvedValue(undefined);
|
||||
const connectedClient = vi.fn().mockResolvedValue(client);
|
||||
const sendTurnText = vi.fn().mockResolvedValue(undefined);
|
||||
const requestDefaultCollaborationModeForNextTurn = vi.fn(() => {
|
||||
stateStore.dispatch({ type: "runtime/requested-collaboration-mode-set", collaborationMode: "default" });
|
||||
});
|
||||
const host: PlanImplementationHost = {
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
sendTurnText,
|
||||
requestDefaultCollaborationModeForNextTurn,
|
||||
};
|
||||
return {
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
host,
|
||||
requestDefaultCollaborationModeForNextTurn,
|
||||
sendTurnText,
|
||||
|
|
@ -88,14 +87,14 @@ describe("implementPlan", () => {
|
|||
});
|
||||
|
||||
it("switches out of plan mode and submits the implementation prompt", async () => {
|
||||
const { host, ensureConnected, requestDefaultCollaborationModeForNextTurn, sendTurnText, stateStore } = createController();
|
||||
const { host, connectedClient, requestDefaultCollaborationModeForNextTurn, sendTurnText, stateStore } = createController();
|
||||
const plan = planItem("plan");
|
||||
resumeThread(stateStore, [plan]);
|
||||
stateStore.dispatch({ type: "ui/panel-set", panel: "status-panel" });
|
||||
|
||||
await implementPlan(host, plan.id);
|
||||
|
||||
expect(ensureConnected).toHaveBeenCalledOnce();
|
||||
expect(connectedClient).toHaveBeenCalledOnce();
|
||||
expect(requestDefaultCollaborationModeForNextTurn).toHaveBeenCalledOnce();
|
||||
expect(stateStore.getState().runtime.selectedCollaborationMode).toBe("default");
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
|
|
@ -103,14 +102,14 @@ describe("implementPlan", () => {
|
|||
});
|
||||
|
||||
it("ignores stale plan items", async () => {
|
||||
const { host, ensureConnected, sendTurnText, stateStore } = createController();
|
||||
const { host, connectedClient, sendTurnText, stateStore } = createController();
|
||||
const first = planItem("first");
|
||||
const latest = planItem("latest");
|
||||
resumeThread(stateStore, [first, latest]);
|
||||
|
||||
await implementPlan(host, first.id);
|
||||
|
||||
expect(ensureConnected).not.toHaveBeenCalled();
|
||||
expect(connectedClient).not.toHaveBeenCalled();
|
||||
expect(sendTurnText).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ function createHost(overrides: TurnSubmissionHostOverrides = {}) {
|
|||
const host: TurnSubmissionActionsHost = {
|
||||
stateStore,
|
||||
vaultPath: "/vault",
|
||||
currentClient: () => client,
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
ensureRestoredThreadLoaded: vi.fn().mockResolvedValue(true),
|
||||
startThread: vi.fn().mockImplementation(async () => {
|
||||
stateStore.dispatch({
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage: vi.fn(),
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -43,7 +43,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -76,7 +76,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent,
|
||||
|
|
@ -109,7 +109,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -137,7 +137,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -166,7 +166,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent,
|
||||
|
|
@ -218,7 +218,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread,
|
||||
addSystemMessage: vi.fn(),
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -240,14 +240,14 @@ describe("createGoalActions", () => {
|
|||
|
||||
it("rejects empty goal objective saves before connecting or starting a thread", async () => {
|
||||
const stateStore = createChatStateStore(chatStateFixture());
|
||||
const ensureConnected = vi.fn().mockResolvedValue(undefined);
|
||||
const connectedClient = vi.fn().mockResolvedValue({ setThreadGoal: vi.fn() } as unknown as AppServerClient);
|
||||
const startThread = vi.fn().mockResolvedValue({ threadId: "thread" });
|
||||
const addSystemMessage = vi.fn();
|
||||
const controller = createGoalActions({
|
||||
stateStore,
|
||||
currentClient: () => ({ setThreadGoal: vi.fn() }) as unknown as AppServerClient,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
startThread,
|
||||
addSystemMessage,
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -257,7 +257,7 @@ describe("createGoalActions", () => {
|
|||
await expect(controller.saveObjective(" ", null)).resolves.toBe(false);
|
||||
|
||||
expect(addSystemMessage).toHaveBeenCalledWith("Goal objective cannot be empty.");
|
||||
expect(ensureConnected).not.toHaveBeenCalled();
|
||||
expect(connectedClient).not.toHaveBeenCalled();
|
||||
expect(startThread).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -300,7 +300,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
@ -325,7 +325,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage: vi.fn(),
|
||||
addGoalEvent,
|
||||
|
|
@ -351,7 +351,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent,
|
||||
|
|
@ -375,7 +375,7 @@ describe("createGoalActions", () => {
|
|||
stateStore,
|
||||
currentClient: () => client,
|
||||
localItemIds: createLocalIdSource({ nowMs: () => 1, seed: "goal" }),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
connectedClient: vi.fn().mockResolvedValue(client),
|
||||
startThread: vi.fn().mockResolvedValue({ threadId: "thread" }),
|
||||
addSystemMessage,
|
||||
addGoalEvent: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ describe("thread management actions", () => {
|
|||
|
||||
await controller.compactThread("source");
|
||||
|
||||
expect(host.connectedClient).toHaveBeenCalledOnce();
|
||||
expect(host.ensureConnected).toHaveBeenCalledOnce();
|
||||
expect(client.compactThread).toHaveBeenCalledWith("source");
|
||||
expect(host.addSystemMessage).toHaveBeenCalledWith("Compaction requested.");
|
||||
|
|
@ -531,10 +532,15 @@ function hostMock({
|
|||
const notifyThreadRenamed = vi.fn();
|
||||
const showNotice = vi.fn();
|
||||
const ensureConnected = vi.fn().mockResolvedValue(undefined);
|
||||
const connectedClient = vi.fn(async () => {
|
||||
await ensureConnected();
|
||||
return (currentClient ?? (() => client as unknown as AppServerClient))();
|
||||
});
|
||||
return {
|
||||
stateStore,
|
||||
vaultPath: "/vault",
|
||||
ensureConnected,
|
||||
connectedClient,
|
||||
currentClient: currentClient ?? (() => client as unknown as AppServerClient),
|
||||
addSystemMessage: vi.fn(),
|
||||
setStatus: vi.fn(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue