返回 DeepSeek-Reasonix
turnSubmissionFailure.ts
根目录 / desktop / frontend / src / lib / turnSubmissionFailure.ts
1 import type { AppBindings } from "./bridge";
2 import { asArray } from "./array";
3 import { removeEmptyAssistantItems } from "./assistantItems";
4 import type { Item, State } from "./useController";
5 import { removeLocalSubmission, updateLocalSubmission } from "./localSubmissionState";
6
7 export function reduceSubmitFailure(
8 state: State,
9 submissionId: string,
10 error: string,
11 conservative: boolean,
12 observedAt: number,
13 ): State {
14 if (!state.localSubmissions[submissionId] || state.localSubmissions[submissionId].settled) return state;
15 const ownsRequest = state.pendingSubmissionId === submissionId;
16 const ownsTurn = Boolean(state.activeTurnId && state.localSubmissions[submissionId].turnId === state.activeTurnId);
17 if (!ownsRequest && (state.pendingSubmissionId || !ownsTurn)) {
18 return updateLocalSubmission(state, submissionId, { status: "failed" });
19 }
20 const next = updateLocalSubmission({
21 ...state,
22 pendingUser: undefined,
23 pendingSubmissionId: undefined,
24 deliveryRecoveryActive: false,
25 cancelRequested: false,
26 seq: state.seq + 1,
27 items: [...(state.transcriptProtocol === 2 ? state.items : removeEmptyAssistantItems(state.items)), { kind: "notice", id: `n${state.seq}`, local: true, level: "warn", text: error } as Item],
28 }, submissionId, { status: "failed" });
29 return {
30 ...next,
31 running: conservative,
32 turnActive: conservative,
33 pendingPrompt: conservative && Boolean(state.approval || state.ask || state.mcpInteraction),
34 cancellable: conservative,
35 ...(conservative ? {} : {
36 activeTurnId: undefined,
37 currentAssistant: undefined,
38 assistantSegmentOrdinal: 0,
39 live: undefined,
40 streamAttemptJournal: undefined,
41 turnLifecycleObservedAt: observedAt,
42 }),
43 };
44 }
45
46 export function reduceManagementConfirmation(state: State, submissionId: string, observedAt: number): State {
47 if (state.pendingSubmissionId !== submissionId) return removeLocalSubmission(state, submissionId);
48 return removeLocalSubmission({
49 ...state,
50 pendingUser: undefined,
51 pendingSubmissionId: undefined,
52 running: false,
53 turnActive: false,
54 pendingPrompt: false,
55 cancelRequested: false,
56 cancellable: false,
57 activeTurnId: undefined,
58 currentAssistant: undefined,
59 assistantSegmentOrdinal: 0,
60 live: undefined,
61 streamAttemptJournal: undefined,
62 deliveryRecoveryActive: false,
63 turnLifecycleObservedAt: observedAt,
64 }, submissionId);
65 }
66
67 export async function findTabAfterSubmitFailure(
68 binding: Pick<AppBindings, "ListTabs">,
69 tabId: string,
70 delays: readonly number[],
71 clock: () => number,
72 ) {
73 for (const delay of delays) {
74 if (delay) await new Promise((resolve) => setTimeout(resolve, delay));
75 try {
76 // Fence at read start, so a delayed response cannot override a turn or
77 // prompt observed while it was in flight. Preserve a sub-tick advance
78 // for synchronous bridges called in the initiating event's clock tick.
79 const snapshotAt = clock() + 0.001;
80 const tab = asArray(await binding.ListTabs()).find((candidate) => candidate.id === tabId);
81 return [tab, snapshotAt] as const;
82 } catch {
83 // The caller's stale-turn watchdog remains the long-tail backstop.
84 }
85 }
86 return undefined;
87 }
88
88 lines TYPESCRIPT