| 1 | import { useMemo } from "react"; |
| 2 | import { useCommittedSlot, type CommittedSlot } from "./useCommittedSlot"; |
| 3 | import { useCommittedCommand } from "./useCommittedCommand"; |
| 4 | import { CommandCancelled } from "./commandOutcome"; |
| 5 | import { executeSubmission, type InitialGoal, type SubmissionInput, type SubmissionPorts, type SubmissionResource } from "../app-runtime/sessionSubmissionOwner"; |
| 6 | import type { SessionOperationAuthority, SessionResource, useSessionOperations } from "../app-runtime/useSessionOperations"; |
| 7 | import type { StructuredInvocationSubmit } from "./invocationDisplay"; |
| 8 | |
| 9 | function bindRead(slot: CommittedSlot<readonly SubmissionResource[]>) { |
| 10 | return (target: SessionResource) => { |
| 11 | if (slot.phase !== "ready") throw new CommandCancelled("disposed"); |
| 12 | const source = slot.value?.find(value => value.target.tabId === target.tabId && value.target.sessionKey === target.sessionKey); |
| 13 | if (!source) throw new CommandCancelled("superseded"); |
| 14 | return source; |
| 15 | }; |
| 16 | } |
| 17 | |
| 18 | export function useSessionSubmission(options: { |
| 19 | target: SessionResource; resources: readonly SubmissionResource[]; |
| 20 | operations: ReturnType<typeof useSessionOperations>; ports: SubmissionPorts; missingSource: string; |
| 21 | }) { |
| 22 | const { target, resources, operations, ports, missingSource } = options; |
| 23 | const slot = useCommittedSlot(resources); |
| 24 | const read = useMemo(() => bindRead(slot), [slot]); |
| 25 | const run = useCommittedCommand(async (tab: string, request: SubmissionInput["request"]) => { |
| 26 | const source = resources.find(value => value.target.tabId === tab); |
| 27 | if (!source) throw Error(missingSource); |
| 28 | const result = await operations(source.target, request.kind === "goal" ? "composer-profile" : "send", { |
| 29 | target: source.target, request, read, ports, |
| 30 | }, executeSubmission); |
| 31 | if (result.status === "failed") throw result.error; |
| 32 | }); |
| 33 | const commitThenSend = useCommittedCommand((tab: string, display: string, submit?: string, |
| 34 | structured?: StructuredInvocationSubmit, initialGoal?: InitialGoal) => run(tab, { kind: "direct", content: { display, submit, structured, initialGoal } })); |
| 35 | const submit = useCommittedCommand((tab: string, display: string, content = display, structured?: StructuredInvocationSubmit) => |
| 36 | run(tab, { kind: "composer", content: { display, submit: content, structured } })); |
| 37 | const applyGoalForTab = useCommittedCommand((tab: string, goal: string) => run(tab, { kind: "goal", goal })); |
| 38 | const applyGoal = useCommittedCommand((goal: string) => target.tabId ? applyGoalForTab(target.tabId, goal) : Promise.resolve()); |
| 39 | // A queue already owns its request and must retain resource failures before |
| 40 | // its own UI outcome boundary. Reuse the executor, not a nested UI command. |
| 41 | const sendRevision = useCommittedCommand((source: SessionResource, text: string, authority: SessionOperationAuthority) => |
| 42 | executeSubmission({ target: source, read, ports, request: { kind: "direct", content: { display: text } } }, authority)); |
| 43 | return { commitThenSend, submit, applyGoalForTab, applyGoal, sendRevision }; |
| 44 | } |
| 45 |