返回 DeepSeek-Reasonix
useSessionPromptCommands.ts
根目录 / desktop / frontend / src / app-runtime / useSessionPromptCommands.ts
1 import { useCallback, useMemo } from "react";
2 import { useCommittedCommand } from "../lib/useCommittedCommand";
3 import { CommandCancelled } from "../lib/commandOutcome";
4 import { hasSessionGeneration } from "../lib/sessionIdentity";
5 import type { PromptDiagnosticTarget } from "../lib/promptSubmissionDiagnostics";
6 import type { QuestionAnswer, ToolApprovalMode, WireApproval, WireAsk, WireMCPInteraction } from "../lib/types";
7 import { executeSessionPrompt, type PromptPorts, type PromptRequest, type SessionPromptKind } from "./sessionPromptExecutor";
8 import { interactionInstanceKey, type InteractionKind, type InteractionTarget } from "../lib/interactionTarget";
9 import type { SessionRef } from "../lib/sessionRef";
10 import type { MCPInteractionAction, RecoveryAction } from "./sessionActionOwner";
11 import type { SessionResource, useSessionOperations } from "./useSessionOperations";
12
13 type Input = {
14 target: SessionResource;
15 sessionGeneration?: number;
16 session?: SessionRef | null;
17 approval?: WireApproval;
18 question?: WireAsk;
19 mcpInteraction?: WireMCPInteraction;
20 remote: boolean;
21 goal: string;
22 toolApprovalMode: ToolApprovalMode;
23 ports: PromptPorts;
24 operations: ReturnType<typeof useSessionOperations>;
25 reportError: (error: unknown) => void;
26 };
27
28 // Diagnostics must neither block authorization nor turn a logging failure into
29 // a failed user action. The recorder is shared with session diagnostic exports.
30 function recordOutcome(target: PromptDiagnosticTarget, status: string, error?: unknown) {
31 void import("../lib/promptSubmissionDiagnostics").then(({ notePromptSubmission, promptFailureClass }) => {
32 notePromptSubmission(target, "command", error === undefined ? status : `${status}:${promptFailureClass(error)}`);
33 }).catch(() => {});
34 }
35
36 export function useSessionPromptCommands(input: Input) {
37 const { target: sessionTarget, session, sessionGeneration, operations, ports } = input;
38 const makeTarget = useCallback((prompt: WireApproval | WireAsk | WireMCPInteraction | undefined, promptKind: SessionPromptKind): InteractionTarget | undefined => {
39 if (!prompt?.id || !sessionTarget.tabId || !session?.sessionId || !hasSessionGeneration(sessionGeneration)) return undefined;
40 let kind: InteractionKind;
41 if (promptKind === "mcpInteraction") kind = "mcp";
42 else if (promptKind === "ask") kind = "ask";
43 else {
44 const approval = prompt as WireApproval;
45 kind = approval.kind === "recovery" || approval.recovery
46 ? "recovery" : approval.tool === "exit_plan_mode" ? "plan" : "approval";
47 }
48 const base = {
49 ...sessionTarget,
50 hostId: session.hostId,
51 sessionId: session.sessionId,
52 sessionGeneration,
53 promptId: prompt.id,
54 turnId: prompt.turnId,
55 runtimeEpoch: prompt.runtimeEpoch,
56 kind,
57 requestGeneration: "generation" in prompt ? prompt.generation : undefined,
58 permissionRevision: "permissionRevision" in prompt ? prompt.permissionRevision : undefined,
59 };
60 return { ...base, instanceKey: interactionInstanceKey(base) };
61 }, [sessionTarget, session, sessionGeneration]);
62 const approvalTarget = makeTarget(input.approval, "approval");
63 const questionTarget = makeTarget(input.question, "ask");
64 const mcpTarget = makeTarget(input.mcpInteraction, "mcpInteraction");
65 const run = useCallback(async (target: InteractionTarget | undefined, promptKind: SessionPromptKind, request: PromptRequest) => {
66 const diagnosticTarget = target ?? { ...sessionTarget, sessionId: session?.sessionId, sessionGeneration };
67 recordOutcome(diagnosticTarget, "started");
68 if (!target) {
69 recordOutcome(diagnosticTarget, "not-ready");
70 throw new CommandCancelled("not-ready");
71 }
72 const result = await operations(
73 { tabId: target.tabId, sessionKey: target.sessionKey },
74 `prompt:${promptKind}`,
75 { target, promptKind, request, ports },
76 executeSessionPrompt,
77 );
78 recordOutcome(diagnosticTarget, result.status === "cancelled" ? result.reason : result.status,
79 result.status === "failed" ? result.error : undefined);
80 if (result.status === "failed") throw result.error;
81 if (result.status === "cancelled") throw new CommandCancelled(result.reason);
82 }, [operations, ports, sessionTarget, session, sessionGeneration]);
83 const plan = useCallback((action: "start_execution" | "revise_plan" | "exit_plan", revision?: string) => run(approvalTarget, "approval", {
84 kind: "plan", action, leavePlanMode: action !== "revise_plan", remote: input.remote,
85 goal: input.goal, toolApprovalMode: input.toolApprovalMode, revision,
86 }), [approvalTarget, input.remote, input.goal, input.toolApprovalMode, run]);
87 const report = useCommittedCommand(input.reportError);
88 const handleApprovalAnswer = useCallback((allow: boolean, session: boolean, persist: boolean) => (
89 input.approval?.tool === "exit_plan_mode"
90 ? plan(allow ? "start_execution" : "revise_plan")
91 : run(approvalTarget, "approval", { kind: "approval", allow, session, persist })
92 ), [approvalTarget, input.approval?.tool, plan, run]);
93 const handleRecoveryAnswer = useCallback((action: RecoveryAction, feedback = "") => {
94 return run(approvalTarget, "approval", { kind: "recovery", action, feedback });
95 }, [approvalTarget, run]);
96 const handleRevisePlan = useCallback((revision: string) => plan("revise_plan", revision), [plan]);
97 const handleExitPlan = useCallback(() => plan("exit_plan"), [plan]);
98 const handleQuestionAnswer = useCallback((_id: string, answers: QuestionAnswer[]) => run(questionTarget, "ask", { kind: "question", answers }), [questionTarget, run]);
99 const handleQuestionDismiss = useCallback(() => run(questionTarget, "ask", { kind: "question", answers: [] }), [questionTarget, run]);
100 const handleMCPAnswer = useCallback((_id: string, action: MCPInteractionAction, content?: Record<string, unknown>) => {
101 void run(mcpTarget, "mcpInteraction", { kind: "mcp", action, content }).catch(report);
102 }, [mcpTarget, report, run]);
103 return useMemo(() => ({
104 approvalTarget, questionTarget, mcpTarget,
105 handleApprovalAnswer, handleRecoveryAnswer, handleRevisePlan, handleExitPlan,
106 handleQuestionAnswer, handleQuestionDismiss, handleMCPAnswer,
107 }), [approvalTarget, questionTarget, mcpTarget, handleApprovalAnswer, handleRecoveryAnswer, handleRevisePlan, handleExitPlan, handleQuestionAnswer, handleQuestionDismiss, handleMCPAnswer]);
108 }
109
109 lines TYPESCRIPT