返回 DeepSeek-Reasonix
useSessionControlCommands.ts
根目录 / desktop / frontend / src / app-runtime / useSessionControlCommands.ts
1 import { useCommittedCommand } from "../lib/useCommittedCommand";
2 import type { CancelOutcome } from "../lib/inboxCancel";
3 import type { SessionResource, useSessionOperations } from "./useSessionOperations";
4
5 export type SessionControlCommandsInput = {
6 activeTabId: string | undefined;
7 resources: readonly SessionResource[];
8 operations: ReturnType<typeof useSessionOperations>;
9 showToast: (message: string, level: "error") => void;
10 clearWorkspaceConflict: () => void;
11 ports: {
12 cancel(queuedItemIDs: string[]): Promise<CancelOutcome>;
13 cancelForTab(tabId: string, queuedItemIDs: string[]): Promise<CancelOutcome>;
14 acceptDelivery(tabId: string): Promise<unknown>;
15 disconnectRemote(hostId: string): Promise<unknown>;
16 cancelJobForTab(tabId: string, jobId: string): Promise<boolean>;
17 refreshBackgroundRuntimes(): Promise<void>;
18 };
19 };
20
21 /**
22 * Owns the session control commands: active-turn cancel (capturing the
23 * committed source tab at the event boundary so presentation never reads the
24 * active-tab mirror mid-flight), delivery accept, remote host disconnect,
25 * workspace-conflict cancel and per-job runtime cancel through the session
26 * operations authority.
27 */
28 export function useSessionControlCommands(input: SessionControlCommandsInput) {
29 const { activeTabId, resources, operations, showToast, ports } = input;
30
31 const cancelRuntimeJob = useCommittedCommand(async (tabId: string, jobId: string): Promise<boolean> => {
32 const target = resources.find(resource => resource.tabId === tabId);
33 if (!target) return false;
34 const outcome = await operations(target, `runtime-cancel:${jobId}`, {}, async (_operationInput, authority) =>
35 (await import("./sessionRuntimeOwner")).executeCancelRuntimeJob(target, jobId, {
36 cancelForTab: (sourceTabId, sourceJobId) => ports.cancelJobForTab(sourceTabId, sourceJobId),
37 refresh: () => ports.refreshBackgroundRuntimes(),
38 }, authority),
39 );
40 if (outcome.status === "failed") {
41 showToast(outcome.error instanceof Error ? outcome.error.message : String(outcome.error), "error");
42 return false;
43 }
44 return outcome.status === "completed" ? outcome.value : false;
45 });
46
47 const handleCancelActive = useCommittedCommand((queuedItemIDs: string[] = []) => {
48 const sourceTabId = activeTabId;
49 return sourceTabId ? ports.cancelForTab(sourceTabId, queuedItemIDs) : ports.cancel(queuedItemIDs);
50 });
51
52 const handleStopActive = useCommittedCommand(async () => {
53 const outcome = await handleCancelActive();
54 if (outcome.error !== undefined) throw new Error(outcome.error);
55 });
56
57 // Capture the committed source tab at the event boundary. Presentation must
58 // never read the active-tab mirror while an async delivery operation is in flight.
59 const handleAcceptDelivery = useCommittedCommand(() => {
60 const sourceTabId = activeTabId;
61 if (!sourceTabId) return;
62 void ports.acceptDelivery(sourceTabId).catch((error) => {
63 console.warn("Failed to accept delivery", error);
64 });
65 });
66
67 const handleDisconnectRemote = useCommittedCommand((hostId: string) => {
68 void ports.disconnectRemote(hostId).catch((error) => {
69 console.warn("Failed to disconnect remote host", error);
70 });
71 });
72
73 const cancelWorkspaceConflict = useCommittedCommand(() => {
74 void handleCancelActive();
75 input.clearWorkspaceConflict();
76 });
77
78 return {
79 cancelRuntimeJob,
80 handleCancelActive,
81 handleStopActive,
82 handleAcceptDelivery,
83 handleDisconnectRemote,
84 cancelWorkspaceConflict,
85 };
86 }
87
87 lines TYPESCRIPT