返回 DeepSeek-Reasonix
inboxCancel.ts
根目录 / desktop / frontend / src / lib / inboxCancel.ts
1 import { isTurnNotRunning } from "./inboxError";
2
3 export type InboxCancelReceipt = {
4 discardedItemIds: string[];
5 warning?: string;
6 };
7
8 export type CancelOutcome = InboxCancelReceipt & {
9 restoredText?: string;
10 /** Local failure receipt; decision cards must not treat it as accepted Stop. */
11 error?: string;
12 };
13
14 type InboxCancelBridge = {
15 CancelTab(tabId: string): Promise<void>;
16 CancelSessionForTab?(tabId: string): Promise<{ accepted: boolean; alreadyIdle: boolean; recoveryRequired: boolean }>;
17 CancelTabWithInboxItems(tabId: string, itemIds: string[]): Promise<void>;
18 CancelTabWithInboxItemsResult?(tabId: string, itemIds: string[]): Promise<InboxCancelReceipt>;
19 InterruptTurnForTab?(tabId: string, turnId: string): Promise<void>;
20 InterruptTurnWithInboxItemsForTab?(tabId: string, turnId: string, itemIds: string[]): Promise<InboxCancelReceipt>;
21 };
22
23 // Stop is a session-level request: a turn-id fence rejection (stale or
24 // replaced turn) still stops whatever is running now, and an idle backend is
25 // not a failure. Only the unconditional path's own error reaches the caller.
26 export async function requestSessionCancel(
27 app: InboxCancelBridge,
28 tabId: string,
29 itemIds: string[],
30 turnId?: string,
31 ): Promise<InboxCancelReceipt> {
32 try {
33 return await requestInboxCancel(app, tabId, itemIds, turnId);
34 } catch (error) {
35 if (isTurnNotRunning(error)) return { discardedItemIds: [] };
36 if (!turnId) throw error;
37 return requestInboxCancel(app, tabId, itemIds, undefined);
38 }
39 }
40
41 export async function requestInboxCancel(
42 app: InboxCancelBridge,
43 tabId: string,
44 itemIds: string[],
45 turnId?: string,
46 ): Promise<InboxCancelReceipt> {
47 if (itemIds.length === 0 && typeof app.CancelSessionForTab === "function") {
48 await app.CancelSessionForTab(tabId);
49 return { discardedItemIds: [] };
50 }
51 if (turnId && itemIds.length > 0 && typeof app.InterruptTurnWithInboxItemsForTab === "function") {
52 const receipt = await app.InterruptTurnWithInboxItemsForTab(tabId, turnId, itemIds);
53 return {
54 discardedItemIds: Array.isArray(receipt?.discardedItemIds) ? receipt.discardedItemIds.map(String) : [],
55 warning: receipt?.warning?.trim() || undefined,
56 };
57 }
58 if (turnId && itemIds.length === 0 && typeof app.InterruptTurnForTab === "function") {
59 await app.InterruptTurnForTab(tabId, turnId);
60 return { discardedItemIds: [] };
61 }
62 if (itemIds.length > 0 && typeof app.CancelTabWithInboxItemsResult === "function") {
63 const receipt = await app.CancelTabWithInboxItemsResult(tabId, itemIds);
64 return {
65 discardedItemIds: Array.isArray(receipt?.discardedItemIds) ? receipt.discardedItemIds.map(String) : [],
66 warning: receipt?.warning?.trim() || undefined,
67 };
68 }
69 if (itemIds.length > 0) {
70 // Compatibility fallback: an old backend has no per-item receipt, so
71 // durable messages remain in the queue instead of returning to the draft.
72 await app.CancelTabWithInboxItems(tabId, itemIds);
73 } else {
74 await app.CancelTab(tabId);
75 }
76 return { discardedItemIds: [] };
77 }
78
78 lines TYPESCRIPT