Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

206 lines
6.7 KiB
TypeScript

import {
useCallback,
useState,
type Dispatch,
type RefObject,
type SetStateAction
} from "react";
import { flushSync } from "react-dom";
import type { AgentGUIRuntime } from "../../../agentActivityRuntime";
import type { useAgentHostApi } from "../../../agentActivityHost";
import type { AgentSessionViewRef } from "../../../contexts/workspace/presentation/renderer/agentSessions/useAgentSessionPagingState";
import type { useAgentGUIActivation } from "./useAgentGUIActivation";
import { type AgentGUIConversationSummary } from "../model/agentGuiConversationModel";
import type {
AgentComposerDraft,
SubmittedDraftSnapshot
} from "../model/agentGuiNodeTypes";
import { resolveAgentComposerDraftScopeKey } from "../model/agentComposerDraftScope";
import { deleteSubmittedDraftSnapshotsForScopes } from "./agentGuiController.draftMessageHelpers";
import { getAgentGUIErrorMessage } from "./agentGuiController.errors";
import {
reportAgentGUIRuntimeError,
showAgentGUIControllerErrorToast
} from "./agentGuiController.reporting";
import { type ConversationIntent } from "./useAgentConversationSelection";
import {
parseTuttiProtectedDeletionConflict,
type TuttiProtectedDeletionConflict
} from "./tuttiProtectedDeletion";
export interface UseAgentGUIConversationDeletionInput {
isDeletingConversation: boolean;
conversations: AgentGUIConversationSummary[];
setPendingDeleteConversation: Dispatch<
SetStateAction<AgentGUIConversationSummary | null>
>;
setDetailError: Dispatch<SetStateAction<string | null>>;
pendingDeleteConversation: AgentGUIConversationSummary | null;
setIsDeletingConversation: Dispatch<SetStateAction<boolean>>;
activeConversationIdRef: RefObject<string | null>;
setIsLoadingMessages: Dispatch<SetStateAction<boolean>>;
sessionViewRef: (agentSessionId: string | null | undefined) => {
workspaceId: string;
agentSessionId: string | null | undefined;
origin: string;
};
activation: ReturnType<typeof useAgentGUIActivation>;
agentActivityRuntime: AgentGUIRuntime;
setDraftByScopeKey: Dispatch<
SetStateAction<Record<string, AgentComposerDraft>>
>;
submittedDraftSnapshotsRef: RefObject<Record<string, SubmittedDraftSnapshot>>;
deleteAgentSessionView: (ref: AgentSessionViewRef) => void;
setIntent: Dispatch<SetStateAction<ConversationIntent>>;
setActiveConversationId: Dispatch<SetStateAction<string | null>>;
persistActiveConversation: (agentSessionId: string | null) => void;
removeConversations: (agentSessionIds: readonly string[]) => void;
agentHostApi: ReturnType<typeof useAgentHostApi>;
workspaceId: string;
}
export function useAgentGUIConversationDeletion(
input: UseAgentGUIConversationDeletionInput
) {
const [protectedDeleteConflict, setProtectedDeleteConflict] =
useState<TuttiProtectedDeletionConflict | null>(null);
const {
isDeletingConversation,
conversations,
setPendingDeleteConversation,
setDetailError,
pendingDeleteConversation,
setIsDeletingConversation,
activeConversationIdRef,
setIsLoadingMessages,
sessionViewRef,
activation,
agentActivityRuntime,
setDraftByScopeKey,
submittedDraftSnapshotsRef,
deleteAgentSessionView,
setIntent,
setActiveConversationId,
persistActiveConversation,
removeConversations,
agentHostApi,
workspaceId
} = input;
const requestDeleteConversation = useCallback(
(agentSessionId: string) => {
const normalized = agentSessionId.trim();
if (!normalized || isDeletingConversation) {
return;
}
const conversation = conversations.find(
(candidate) => candidate.id === normalized
);
if (!conversation) {
return;
}
setPendingDeleteConversation(conversation);
setDetailError(null);
},
[conversations, isDeletingConversation]
);
const cancelDeleteConversation = useCallback(() => {
if (isDeletingConversation) {
return;
}
setPendingDeleteConversation(null);
}, [isDeletingConversation]);
const confirmDeleteConversation = useCallback(() => {
const target = pendingDeleteConversation;
if (!target || isDeletingConversation) {
return;
}
setIsDeletingConversation(true);
setDetailError(null);
void agentActivityRuntime
.deleteSession({
workspaceId,
agentSessionId: target.id
})
.then(() => {
if (activeConversationIdRef.current === target.id) {
activeConversationIdRef.current = null;
flushSync(() => {
setIsLoadingMessages(false);
setIntent({ tag: "home" });
setActiveConversationId(null);
});
persistActiveConversation(null);
}
return activation.unactivate(target.id);
})
.then(() => {
removeConversations([target.id]);
const deletedScopeKey = resolveAgentComposerDraftScopeKey({
agentSessionId: target.id
});
setDraftByScopeKey((current) => {
const next = { ...current };
delete next[deletedScopeKey];
return next;
});
deleteSubmittedDraftSnapshotsForScopes({
snapshots: submittedDraftSnapshotsRef.current,
scopeKeys: new Set([deletedScopeKey]),
targetAgentSessionIds: new Set([target.id])
});
deleteAgentSessionView(sessionViewRef(target.id));
setPendingDeleteConversation(null);
})
.catch((error) => {
const conflict = parseTuttiProtectedDeletionConflict(error);
if (conflict) {
setProtectedDeleteConflict(conflict);
return;
}
const message = getAgentGUIErrorMessage(error);
reportAgentGUIRuntimeError({
agentSessionId: target.id,
error,
phase: "delete_conversation",
provider: target.provider,
runtime: agentActivityRuntime,
workspaceId
});
showAgentGUIControllerErrorToast(agentHostApi.toast, message);
})
.finally(() => {
setIsDeletingConversation(false);
});
}, [
activation,
activeConversationIdRef,
agentActivityRuntime,
agentHostApi.toast,
deleteAgentSessionView,
isDeletingConversation,
pendingDeleteConversation,
persistActiveConversation,
removeConversations,
sessionViewRef,
setActiveConversationId,
setDetailError,
setDraftByScopeKey,
setIntent,
setIsDeletingConversation,
setIsLoadingMessages,
setPendingDeleteConversation,
submittedDraftSnapshotsRef,
workspaceId
]);
return {
requestDeleteConversation,
cancelDeleteConversation,
confirmDeleteConversation,
protectedDeleteConflict,
dismissProtectedDeleteConflict: () => setProtectedDeleteConflict(null)
};
}