2026-06-27 01:32:29 +00:00
|
|
|
import { Notice } from "obsidian";
|
|
|
|
|
|
|
|
|
|
import type { AppServerClientAccess } from "../../../app-server/connection/client-access";
|
2026-06-27 07:33:55 +00:00
|
|
|
import { recoverRolloutTokenUsage } from "../../../app-server/services/rollout-token-usage";
|
2026-06-27 01:32:29 +00:00
|
|
|
import { normalizeExplicitThreadName } from "../../../domain/threads/model";
|
|
|
|
|
import type { LocalIdSource } from "../../../shared/id/local-id";
|
|
|
|
|
import { createThreadOperations, type ThreadOperations } from "../../threads/thread-operations";
|
|
|
|
|
import { createThreadTitleService, type ThreadTitleService } from "../../threads/thread-title-service";
|
|
|
|
|
import type { ChatServerThreadActions } from "../app-server/actions/threads";
|
2026-06-27 09:16:01 +00:00
|
|
|
import { createChatThreadGoalReadTransport, createChatThreadGoalTransport } from "../app-server/goals/transport";
|
|
|
|
|
import { createChatThreadHistoryTransport, createChatThreadResumeTransport } from "../app-server/threads/loading-transport";
|
2026-06-27 08:11:25 +00:00
|
|
|
import { createChatThreadMutationTransport } from "../app-server/threads/transport";
|
2026-06-27 01:32:29 +00:00
|
|
|
import type { ChatResumeWorkTracker } from "../application/lifecycle";
|
|
|
|
|
import { messageStreamItems } from "../application/state/message-stream";
|
|
|
|
|
import type { ChatStateStore } from "../application/state/store";
|
|
|
|
|
import type { ActiveThreadIdentitySync } from "../application/threads/active-thread-identity-sync";
|
|
|
|
|
import { type AutoTitleCoordinator, createAutoTitleCoordinator } from "../application/threads/auto-title-coordinator";
|
|
|
|
|
import { createGoalActions, createThreadGoalSyncActions } from "../application/threads/goal-actions";
|
|
|
|
|
import { HistoryController } from "../application/threads/history-controller";
|
|
|
|
|
import { createThreadLifecycleParts } from "../application/threads/lifecycle-parts";
|
|
|
|
|
import {
|
|
|
|
|
activeThreadRenameTitleContext,
|
|
|
|
|
createThreadRenameEditorActions,
|
|
|
|
|
type ThreadRenameEditorActions,
|
|
|
|
|
} from "../application/threads/rename-editor-actions";
|
|
|
|
|
import type { RestorationController } from "../application/threads/restoration-controller";
|
|
|
|
|
import type { ResumeActions } from "../application/threads/resume-actions";
|
|
|
|
|
import { createThreadManagementActions, type ThreadManagementActionsHost } from "../application/threads/thread-management-actions";
|
|
|
|
|
import { createThreadNavigationActions } from "../application/threads/thread-navigation-actions";
|
|
|
|
|
import { threadTitleContextFromMessageStreamItems } from "../application/threads/title-context";
|
|
|
|
|
import type { ChatComposerController } from "../panel/composer-controller";
|
|
|
|
|
import { createToolbarPanelActions, type ToolbarPanelActions } from "../panel/toolbar-actions";
|
|
|
|
|
import type { CurrentAppServerClient } from "./connection-bundle";
|
|
|
|
|
import type { ChatPanelEnvironment } from "./contracts";
|
|
|
|
|
|
|
|
|
|
type ChatPanelGoalSyncActions = ReturnType<typeof createThreadGoalSyncActions>;
|
|
|
|
|
export type ChatPanelGoalActions = ReturnType<typeof createGoalActions>;
|
|
|
|
|
export type ChatPanelThreadLifecycle = ReturnType<typeof createThreadLifecycleParts>;
|
|
|
|
|
export type ChatPanelThreadActions = ReturnType<typeof createThreadManagementActions>;
|
|
|
|
|
export type ChatPanelThreadNavigationActions = ReturnType<typeof createThreadNavigationActions>;
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadStatus {
|
|
|
|
|
set: (statusText: string) => void;
|
|
|
|
|
addSystemMessage: (text: string) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadHost {
|
|
|
|
|
environment: ChatPanelEnvironment;
|
|
|
|
|
stateStore: ChatStateStore;
|
|
|
|
|
resumeWork: ChatResumeWorkTracker;
|
|
|
|
|
messageScrollController: {
|
|
|
|
|
showLatest(): void;
|
|
|
|
|
};
|
|
|
|
|
getClosing: () => boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadFoundationInput {
|
|
|
|
|
currentClient: CurrentAppServerClient;
|
|
|
|
|
localItemIds: LocalIdSource;
|
|
|
|
|
status: ChatPanelThreadStatus;
|
|
|
|
|
refreshLiveState: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadFoundation {
|
|
|
|
|
titleService: ThreadTitleService;
|
|
|
|
|
autoTitleCoordinator: AutoTitleCoordinator;
|
|
|
|
|
history: HistoryController;
|
|
|
|
|
goalSync: ChatPanelGoalSyncActions;
|
|
|
|
|
threadOperations: ThreadOperations;
|
|
|
|
|
invalidateThreadWork(): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadLifecycleInput {
|
|
|
|
|
currentClient: CurrentAppServerClient;
|
|
|
|
|
localItemIds: LocalIdSource;
|
|
|
|
|
connectedClient: () => Promise<ReturnType<CurrentAppServerClient>>;
|
|
|
|
|
ensureConnected: () => Promise<void>;
|
|
|
|
|
status: ChatPanelThreadStatus;
|
|
|
|
|
serverThreads: ChatServerThreadActions;
|
|
|
|
|
foundation: ChatPanelThreadFoundation;
|
|
|
|
|
refreshTabHeader: () => void;
|
|
|
|
|
refreshLiveState: () => void;
|
|
|
|
|
notifyActiveThreadIdentityChanged: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadLifecycleBundle {
|
|
|
|
|
goals: ChatPanelGoalActions;
|
|
|
|
|
rename: ThreadRenameEditorActions;
|
|
|
|
|
lifecycle: ChatPanelThreadLifecycle;
|
|
|
|
|
identity: ActiveThreadIdentitySync;
|
|
|
|
|
restoration: RestorationController;
|
|
|
|
|
resume: ResumeActions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadActionInput {
|
|
|
|
|
currentClient: CurrentAppServerClient;
|
|
|
|
|
connectedClient: () => Promise<ReturnType<CurrentAppServerClient>>;
|
|
|
|
|
status: ChatPanelThreadStatus;
|
|
|
|
|
composerController: ChatComposerController;
|
|
|
|
|
foundation: ChatPanelThreadFoundation;
|
|
|
|
|
lifecycle: ChatPanelThreadLifecycleBundle;
|
|
|
|
|
refreshActiveThreads: () => Promise<void>;
|
|
|
|
|
notifyActiveThreadIdentityChanged: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChatPanelThreadActionBundle {
|
|
|
|
|
actions: ChatPanelThreadActions;
|
|
|
|
|
toolbarPanelActions: ToolbarPanelActions;
|
|
|
|
|
navigation: ChatPanelThreadNavigationActions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createThreadFoundation(host: ChatPanelThreadHost, input: ChatPanelThreadFoundationInput): ChatPanelThreadFoundation {
|
|
|
|
|
const { currentClient, localItemIds, status, refreshLiveState } = input;
|
|
|
|
|
const titleService = createSessionThreadTitleService(host, currentClient);
|
|
|
|
|
const autoTitleCoordinator = createSessionAutoTitleCoordinator(host, currentClient, titleService);
|
|
|
|
|
const history = createSessionHistoryController(host, currentClient, status, autoTitleCoordinator);
|
|
|
|
|
const invalidateThreadWork = () => {
|
|
|
|
|
host.resumeWork.invalidate();
|
|
|
|
|
history.invalidate();
|
|
|
|
|
};
|
|
|
|
|
const goalSync = createSessionGoalSyncActions(host, currentClient, localItemIds, status, refreshLiveState);
|
|
|
|
|
const threadOperations = createSessionThreadOperations(host.environment, currentClient);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
titleService,
|
|
|
|
|
autoTitleCoordinator,
|
|
|
|
|
history,
|
|
|
|
|
goalSync,
|
|
|
|
|
threadOperations,
|
|
|
|
|
invalidateThreadWork,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createThreadLifecycleBundle(
|
|
|
|
|
host: ChatPanelThreadHost,
|
|
|
|
|
input: ChatPanelThreadLifecycleInput,
|
|
|
|
|
): ChatPanelThreadLifecycleBundle {
|
|
|
|
|
const {
|
|
|
|
|
currentClient,
|
|
|
|
|
localItemIds,
|
|
|
|
|
connectedClient,
|
|
|
|
|
ensureConnected,
|
|
|
|
|
status,
|
|
|
|
|
serverThreads,
|
|
|
|
|
foundation,
|
|
|
|
|
refreshTabHeader,
|
|
|
|
|
refreshLiveState,
|
|
|
|
|
notifyActiveThreadIdentityChanged,
|
|
|
|
|
} = input;
|
|
|
|
|
const goals = createSessionGoalActions(host, currentClient, localItemIds, connectedClient, status, serverThreads, refreshLiveState);
|
|
|
|
|
const rename = createSessionThreadRenameEditorActions(
|
|
|
|
|
host.stateStore,
|
|
|
|
|
foundation.threadOperations,
|
|
|
|
|
foundation.titleService,
|
|
|
|
|
ensureConnected,
|
|
|
|
|
status,
|
|
|
|
|
);
|
|
|
|
|
const lifecycle = createSessionThreadLifecycle(host, {
|
|
|
|
|
currentClient,
|
2026-06-27 09:16:01 +00:00
|
|
|
connectedClient,
|
2026-06-27 01:32:29 +00:00
|
|
|
status,
|
|
|
|
|
goals,
|
|
|
|
|
autoTitleCoordinator: foundation.autoTitleCoordinator,
|
|
|
|
|
history: foundation.history,
|
|
|
|
|
invalidateThreadWork: () => {
|
|
|
|
|
foundation.invalidateThreadWork();
|
|
|
|
|
},
|
|
|
|
|
refreshTabHeader,
|
|
|
|
|
refreshLiveState,
|
|
|
|
|
notifyActiveThreadIdentityChanged,
|
|
|
|
|
});
|
|
|
|
|
const { identity, restoration, resume } = lifecycle;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
goals,
|
|
|
|
|
rename,
|
|
|
|
|
lifecycle,
|
|
|
|
|
identity,
|
|
|
|
|
restoration,
|
|
|
|
|
resume,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createThreadActionBundle(host: ChatPanelThreadHost, input: ChatPanelThreadActionInput): ChatPanelThreadActionBundle {
|
|
|
|
|
const {
|
|
|
|
|
currentClient,
|
|
|
|
|
connectedClient,
|
|
|
|
|
status,
|
|
|
|
|
composerController,
|
|
|
|
|
foundation,
|
|
|
|
|
lifecycle,
|
|
|
|
|
refreshActiveThreads,
|
|
|
|
|
notifyActiveThreadIdentityChanged,
|
|
|
|
|
} = input;
|
|
|
|
|
const { environment, stateStore } = host;
|
|
|
|
|
const threadManagementHost: ThreadManagementActionsHost = {
|
|
|
|
|
stateStore,
|
|
|
|
|
operations: foundation.threadOperations,
|
2026-06-27 08:11:25 +00:00
|
|
|
threadTransport: createChatThreadMutationTransport({
|
|
|
|
|
vaultPath: environment.plugin.settingsRef.vaultPath,
|
|
|
|
|
currentClient,
|
|
|
|
|
connectedClient,
|
|
|
|
|
}),
|
2026-06-27 01:32:29 +00:00
|
|
|
addSystemMessage: status.addSystemMessage,
|
|
|
|
|
setStatus: status.set,
|
|
|
|
|
setComposerText: (text) => {
|
|
|
|
|
composerController.setDraft(text, { focus: true });
|
|
|
|
|
},
|
|
|
|
|
openThreadInNewView: (threadId) => environment.plugin.workspace.openThreadInNewView(threadId),
|
|
|
|
|
openThreadInCurrentPanel: (threadId) => lifecycle.resume.resumeThread(threadId),
|
|
|
|
|
notifyActiveThreadIdentityChanged,
|
|
|
|
|
refreshAfterThreadMutation: async () => {
|
|
|
|
|
await refreshActiveThreads();
|
|
|
|
|
},
|
|
|
|
|
applyThreadCatalogEvent: (event) => {
|
|
|
|
|
environment.plugin.threadCatalog.apply(event);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const actions = createThreadManagementActions(threadManagementHost);
|
|
|
|
|
const toolbarPanelActions = createToolbarPanelActions({
|
|
|
|
|
stateStore,
|
|
|
|
|
threadActions: actions,
|
|
|
|
|
});
|
|
|
|
|
const navigation = createThreadNavigationActions({
|
|
|
|
|
stateStore,
|
|
|
|
|
identity: lifecycle.identity,
|
|
|
|
|
closeForThreadSelection: () => {
|
|
|
|
|
toolbarPanelActions.closeForThreadSelection();
|
|
|
|
|
},
|
|
|
|
|
focusThreadInOpenView: (threadId) => environment.plugin.workspace.focusThreadInOpenView(threadId),
|
|
|
|
|
resumeThread: (threadId) => lifecycle.resume.resumeThread(threadId),
|
|
|
|
|
addSystemMessage: status.addSystemMessage,
|
|
|
|
|
focusComposer: () => {
|
2026-06-27 02:36:00 +00:00
|
|
|
composerController.focusComposer();
|
2026-06-27 01:32:29 +00:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return { actions, toolbarPanelActions, navigation };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionThreadTitleService(host: ChatPanelThreadHost, currentClient: CurrentAppServerClient): ThreadTitleService {
|
|
|
|
|
const { environment, stateStore } = host;
|
|
|
|
|
return createThreadTitleService({
|
|
|
|
|
codexPath: () => environment.plugin.settingsRef.settings.codexPath,
|
|
|
|
|
vaultPath: environment.plugin.settingsRef.vaultPath,
|
|
|
|
|
threadNamingModel: () => environment.plugin.settingsRef.settings.threadNamingModel,
|
|
|
|
|
threadNamingEffort: () => environment.plugin.settingsRef.settings.threadNamingEffort,
|
|
|
|
|
clientAccess: createCurrentClientAccess(currentClient),
|
|
|
|
|
visibleContext: (threadId) => activeThreadRenameTitleContext(stateStore.getState(), threadId),
|
|
|
|
|
visibleCompletedTurnContext: (turnId) =>
|
|
|
|
|
threadTitleContextFromMessageStreamItems(turnId, messageStreamItems(stateStore.getState().messageStream)),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionAutoTitleCoordinator(
|
|
|
|
|
host: ChatPanelThreadHost,
|
|
|
|
|
currentClient: CurrentAppServerClient,
|
|
|
|
|
titleService: ThreadTitleService,
|
|
|
|
|
): AutoTitleCoordinator {
|
|
|
|
|
return createAutoTitleCoordinator({
|
|
|
|
|
stateStore: host.stateStore,
|
|
|
|
|
completedTurnTitleContext: (turnId, completedSummary) => titleService.completedTurnContext(turnId, completedSummary),
|
|
|
|
|
generateTitleFromContext: (context) => titleService.generate(context),
|
|
|
|
|
renameGeneratedTitle: async (threadId, title, options) => {
|
|
|
|
|
const name = normalizeExplicitThreadName(title);
|
|
|
|
|
if (!name) return false;
|
|
|
|
|
const client = currentClient();
|
|
|
|
|
if (!client) return false;
|
|
|
|
|
|
|
|
|
|
await client.setThreadName(threadId, name);
|
|
|
|
|
if (currentClient() !== client) return false;
|
|
|
|
|
if (options.shouldPublish()) {
|
|
|
|
|
host.environment.plugin.threadCatalog.apply({ type: "thread-renamed", threadId, name });
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionHistoryController(
|
|
|
|
|
host: ChatPanelThreadHost,
|
|
|
|
|
currentClient: CurrentAppServerClient,
|
|
|
|
|
status: ChatPanelThreadStatus,
|
|
|
|
|
autoTitleCoordinator: AutoTitleCoordinator,
|
|
|
|
|
): HistoryController {
|
|
|
|
|
return new HistoryController({
|
|
|
|
|
stateStore: host.stateStore,
|
2026-06-27 09:16:01 +00:00
|
|
|
historyTransport: createChatThreadHistoryTransport({
|
|
|
|
|
currentClient,
|
|
|
|
|
}),
|
2026-06-27 01:32:29 +00:00
|
|
|
addSystemMessage: status.addSystemMessage,
|
|
|
|
|
showLatestPageAtBottom: () => {
|
|
|
|
|
host.messageScrollController.showLatest();
|
|
|
|
|
},
|
|
|
|
|
setThreadTurnPresence: (hadTurns) => {
|
|
|
|
|
autoTitleCoordinator.resetThreadTurnPresence(hadTurns);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionGoalSyncActions(
|
|
|
|
|
host: ChatPanelThreadHost,
|
|
|
|
|
currentClient: CurrentAppServerClient,
|
|
|
|
|
localItemIds: LocalIdSource,
|
|
|
|
|
status: ChatPanelThreadStatus,
|
|
|
|
|
refreshLiveState: () => void,
|
|
|
|
|
): ChatPanelGoalSyncActions {
|
|
|
|
|
return createThreadGoalSyncActions({
|
|
|
|
|
stateStore: host.stateStore,
|
2026-06-27 09:16:01 +00:00
|
|
|
goalTransport: createChatThreadGoalReadTransport({
|
2026-06-27 08:11:25 +00:00
|
|
|
currentClient,
|
|
|
|
|
}),
|
2026-06-27 01:32:29 +00:00
|
|
|
localItemIds,
|
|
|
|
|
addSystemMessage: (text) => {
|
|
|
|
|
status.addSystemMessage(text);
|
|
|
|
|
},
|
|
|
|
|
addGoalEvent: (item) => {
|
|
|
|
|
host.stateStore.dispatch({ type: "message-stream/item-upserted", item });
|
|
|
|
|
},
|
|
|
|
|
refreshLiveState,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionThreadOperations(environment: ChatPanelEnvironment, currentClient: CurrentAppServerClient): ThreadOperations {
|
|
|
|
|
return createThreadOperations({
|
|
|
|
|
clientAccess: createCurrentClientAccess(currentClient),
|
|
|
|
|
archiveExport: {
|
|
|
|
|
settings: () => ({
|
|
|
|
|
archiveExportFolderTemplate: environment.plugin.settingsRef.settings.archiveExportFolderTemplate,
|
|
|
|
|
archiveExportFilenameTemplate: environment.plugin.settingsRef.settings.archiveExportFilenameTemplate,
|
|
|
|
|
archiveExportTags: environment.plugin.settingsRef.settings.archiveExportTags,
|
|
|
|
|
}),
|
|
|
|
|
enabled: () => environment.plugin.settingsRef.settings.archiveExportEnabled,
|
|
|
|
|
vaultPath: environment.plugin.settingsRef.vaultPath,
|
|
|
|
|
vaultConfigDir: environment.obsidian.app.vault.configDir,
|
|
|
|
|
},
|
|
|
|
|
archiveDestination: environment.obsidian.archiveDestination,
|
|
|
|
|
catalog: environment.plugin.threadCatalog,
|
|
|
|
|
notice: (text) => {
|
|
|
|
|
new Notice(text);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createCurrentClientAccess(currentClient: CurrentAppServerClient): AppServerClientAccess {
|
|
|
|
|
return {
|
|
|
|
|
withClient: async (operation) => {
|
|
|
|
|
const client = currentClient();
|
|
|
|
|
if (!client) throw new Error("Codex app-server is not connected.");
|
|
|
|
|
const result = await operation(client);
|
|
|
|
|
if (currentClient() !== client) {
|
|
|
|
|
throw new Error("Codex app-server connection changed while running the operation.");
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionGoalActions(
|
|
|
|
|
host: ChatPanelThreadHost,
|
|
|
|
|
currentClient: CurrentAppServerClient,
|
|
|
|
|
localItemIds: LocalIdSource,
|
|
|
|
|
connectedClient: () => Promise<ReturnType<CurrentAppServerClient>>,
|
|
|
|
|
status: ChatPanelThreadStatus,
|
|
|
|
|
serverThreads: ChatServerThreadActions,
|
|
|
|
|
refreshLiveState: () => void,
|
|
|
|
|
): ChatPanelGoalActions {
|
|
|
|
|
return createGoalActions({
|
|
|
|
|
stateStore: host.stateStore,
|
2026-06-27 08:11:25 +00:00
|
|
|
goalTransport: createChatThreadGoalTransport({
|
|
|
|
|
currentClient,
|
|
|
|
|
connectedClient,
|
|
|
|
|
}),
|
2026-06-27 01:32:29 +00:00
|
|
|
localItemIds,
|
|
|
|
|
startThread: (preview, options) => serverThreads.startThread(preview, options),
|
|
|
|
|
addSystemMessage: (text) => {
|
|
|
|
|
status.addSystemMessage(text);
|
|
|
|
|
},
|
|
|
|
|
addGoalEvent: (item) => {
|
|
|
|
|
host.stateStore.dispatch({ type: "message-stream/item-upserted", item });
|
|
|
|
|
},
|
|
|
|
|
refreshLiveState,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionThreadRenameEditorActions(
|
|
|
|
|
stateStore: ChatStateStore,
|
|
|
|
|
operations: ThreadOperations,
|
|
|
|
|
titleService: ThreadTitleService,
|
|
|
|
|
ensureConnected: () => Promise<void>,
|
|
|
|
|
status: ChatPanelThreadStatus,
|
|
|
|
|
): ThreadRenameEditorActions {
|
|
|
|
|
return createThreadRenameEditorActions({
|
|
|
|
|
stateStore,
|
|
|
|
|
ensureConnected,
|
|
|
|
|
addSystemMessage: status.addSystemMessage,
|
2026-06-27 09:16:01 +00:00
|
|
|
renameThread: (threadId, value) => operations.renameThread(threadId, value),
|
2026-06-27 01:32:29 +00:00
|
|
|
generateThreadTitle: (threadId) => titleService.generateTitle(threadId),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSessionThreadLifecycle(
|
|
|
|
|
host: ChatPanelThreadHost,
|
|
|
|
|
input: {
|
|
|
|
|
currentClient: CurrentAppServerClient;
|
2026-06-27 09:16:01 +00:00
|
|
|
connectedClient: () => Promise<ReturnType<CurrentAppServerClient>>;
|
2026-06-27 01:32:29 +00:00
|
|
|
status: ChatPanelThreadStatus;
|
|
|
|
|
goals: ChatPanelGoalActions;
|
|
|
|
|
autoTitleCoordinator: AutoTitleCoordinator;
|
|
|
|
|
history: HistoryController;
|
|
|
|
|
invalidateThreadWork: () => void;
|
|
|
|
|
refreshTabHeader: () => void;
|
|
|
|
|
refreshLiveState: () => void;
|
|
|
|
|
notifyActiveThreadIdentityChanged: () => void;
|
|
|
|
|
},
|
|
|
|
|
): ChatPanelThreadLifecycle {
|
|
|
|
|
const {
|
|
|
|
|
currentClient,
|
2026-06-27 09:16:01 +00:00
|
|
|
connectedClient,
|
2026-06-27 01:32:29 +00:00
|
|
|
status,
|
|
|
|
|
goals,
|
|
|
|
|
autoTitleCoordinator,
|
|
|
|
|
history,
|
|
|
|
|
invalidateThreadWork,
|
|
|
|
|
refreshTabHeader,
|
|
|
|
|
refreshLiveState,
|
|
|
|
|
notifyActiveThreadIdentityChanged,
|
|
|
|
|
} = input;
|
|
|
|
|
return createThreadLifecycleParts({
|
|
|
|
|
stateStore: host.stateStore,
|
2026-06-27 09:16:01 +00:00
|
|
|
resumeTransport: createChatThreadResumeTransport({
|
|
|
|
|
vaultPath: host.environment.plugin.settingsRef.vaultPath,
|
2026-06-27 01:32:29 +00:00
|
|
|
currentClient,
|
2026-06-27 09:16:01 +00:00
|
|
|
connectedClient,
|
|
|
|
|
}),
|
2026-06-27 01:32:29 +00:00
|
|
|
lifecycle: {
|
|
|
|
|
resumeWork: host.resumeWork,
|
|
|
|
|
history,
|
|
|
|
|
invalidateThreadWork,
|
|
|
|
|
getClosing: host.getClosing,
|
2026-06-27 07:33:55 +00:00
|
|
|
recoverTokenUsageFromRollout: (path) =>
|
|
|
|
|
recoverRolloutTokenUsage(path, async (filePath, options) => {
|
|
|
|
|
const response = await currentClient()?.readFile(filePath, options);
|
|
|
|
|
return response?.dataBase64 ?? "";
|
|
|
|
|
}),
|
2026-06-27 01:32:29 +00:00
|
|
|
},
|
|
|
|
|
thread: {
|
|
|
|
|
notifyIdentityChanged: notifyActiveThreadIdentityChanged,
|
|
|
|
|
refreshTabHeader,
|
|
|
|
|
},
|
|
|
|
|
status,
|
|
|
|
|
liveState: {
|
|
|
|
|
refresh: refreshLiveState,
|
|
|
|
|
},
|
|
|
|
|
goals,
|
|
|
|
|
resetThreadTurnPresence: (hadTurns) => {
|
|
|
|
|
autoTitleCoordinator.resetThreadTurnPresence(hadTurns);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|