From 07585f93058e3e3e465d2ea8f33c47fe6d522954 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:16:49 +0100 Subject: [PATCH] refactor(tui): align prompt submit dispatch - restore upstream-style fire-and-forget prompt dispatch after Run-mode gates - keep Agency auth failure recovery dialog without restoring submitted drafts - remove the completed prompt-submit cleanup queue item --- FORK_CHANGELOG.md | 6 ---- .../cli/cmd/tui/component/prompt/index.tsx | 35 +++---------------- .../opencode/test/cli/tui/prompt.test.tsx | 16 ++++----- 3 files changed, 11 insertions(+), 46 deletions(-) diff --git a/FORK_CHANGELOG.md b/FORK_CHANGELOG.md index 12b42b0ef..ca0eb50d0 100644 --- a/FORK_CHANGELOG.md +++ b/FORK_CHANGELOG.md @@ -391,12 +391,6 @@ These items were checked with `git blame`, source PRs/issues, `origin/dev`, and - Blame/intent check: added by bonk1t in `725ac8ec0` (`fix(install): support built source global installs`). - Current implementation: `findBuiltBinary` in `packages/opencode/bin/agentswarm` and `packages/opencode/test/installation/source-install-wrapper.test.ts`. -- **Prompt submit flow has fork-only retry/error handling instead of clean upstream fire-and-forget submit** - - Decision: align with upstream. Immediate composer clearing is upstream behavior, not a fork feature, and waiting for model completion was not intentional. - - Evidence: VRSEN PR #42 introduced the original regression by changing upstream fire-and-forget `sdk.client.session.prompt(...).catch(() => {})` into `await sdk.client.session.prompt(...)`; issue #103 names that exact root cause. VRSEN PR #88 was mostly intentional, but it carried this bug forward. VRSEN PR #99 tried to restore upstream behavior, but later follow-up code still left extra prompt-task waiting and retry/error-restoration logic instead of the clean upstream shape. - - Blame/intent check: original regression line is `f977cea74b` (`fix: harden agent swarm auth onboarding`); current follow-up machinery is mainly `8a18f7bb`, `9e73b5f7`, and `2ad600b64`. - - Current implementation: prompt submit flow in `packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx`. - - **Spurious package scripts remain from upstream junk** - Decision: align with upstream. These scripts are not fork intent and should not be treated as Agent Swarm functionality. - Evidence: upstream OpenCode PR #22160 and issue #22159 identify `random`, `clean`, `lint`, `format`, `docs`, and `deploy` as accidental junk and remove them with no logic change. diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 87ae2377b..c7d6dcd12 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -1593,12 +1593,7 @@ export function Prompt(props: PromptProps) { return false } - const submittedPrompt = structuredClone(unwrap(store.prompt)) - const savedPrompt = { input: store.prompt.input, parts: [...store.prompt.parts] } let sessionID = props.sessionID - let createdSessionID: string | undefined - let navigatedToCreatedSession = false - let navigateTimer: ReturnType | undefined if (sessionID == null) { const workspace = workspaceSelection() const workspaceID = iife(() => { @@ -1628,7 +1623,6 @@ export function Prompt(props: PromptProps) { } sessionID = res.data.id - createdSessionID = sessionID } const messageID = MessageID.ascending() @@ -1740,38 +1734,18 @@ export function Prompt(props: PromptProps) { provider_id: productProviderID, }, }) - void sdk.client.session - .prompt(promptPayload) + sdk.client.session + .prompt(promptPayload, { throwOnError: true }) .then((result) => { captureTaskCompleted(messageID, result) - if (result?.error) throw result.error - if (editorParts.length > 0) editor.markSelectionSent() }) .catch((error) => { captureTaskFailed(messageID, error) - setStore("prompt", savedPrompt) - input.setText(savedPrompt.input) - restoreExtmarksFromParts(savedPrompt.parts) const message = toErrorMessage(error) const shouldReopenAuth = shouldOpenAgencyAuthDialog({ providerID: productProviderID, message, }) - if (navigateTimer) { - clearTimeout(navigateTimer) - navigateTimer = undefined - } - if (createdSessionID && shouldReopenAuth) { - if (navigatedToCreatedSession) { - route.navigate({ - type: "home", - prompt: submittedPrompt, - }) - } - void sdk.client.session.delete({ - sessionID: createdSessionID, - }) - } if (shouldReopenAuth) { toast.show({ variant: "error", @@ -1787,6 +1761,7 @@ export function Prompt(props: PromptProps) { duration: 5000, }) }) + if (editorParts.length > 0) editor.markSelectionSent() } clearSubmittedPrompt(currentMode) @@ -1794,9 +1769,7 @@ export function Prompt(props: PromptProps) { // temporary hack to make sure the message is sent if (!props.sessionID) { if (editorParts.length > 0) editor.preserveSelectionFromNewSession() - navigateTimer = setTimeout(() => { - navigateTimer = undefined - navigatedToCreatedSession = true + setTimeout(() => { route.navigate({ type: "session", sessionID, diff --git a/packages/opencode/test/cli/tui/prompt.test.tsx b/packages/opencode/test/cli/tui/prompt.test.tsx index 08d0b4e9d..1162b1fec 100644 --- a/packages/opencode/test/cli/tui/prompt.test.tsx +++ b/packages/opencode/test/cli/tui/prompt.test.tsx @@ -735,7 +735,7 @@ describe("prompt auth rejection handling", () => { expect(editorText).toContain("\\u0060\\u0060\\u0060") expect(editorText).toContain("\\u003c/system-reminder\\u003e") expect(editorText).toContain("\\u003csystem-reminder\\u003e") - expect(markSelectionSent).not.toHaveBeenCalled() + expect(markSelectionSent).toHaveBeenCalledTimes(1) expect(appendHistory).toHaveBeenCalledWith({ input: "clear right away", parts: [], @@ -1329,7 +1329,7 @@ describe("prompt auth rejection handling", () => { }) }) - test("routes first-prompt SDK auth error through auth without blocking prompt clearing", async () => { + test("routes first-prompt SDK auth error through auth after upstream-style prompt clearing", async () => { process.env.OPENAI_API_KEY = "sk-test" const routeStates: string[] = [] @@ -1362,7 +1362,7 @@ describe("prompt auth rejection handling", () => { { prompt: async () => { await Bun.sleep(75) - return { error: promptError, response: new Response(null, { status: 403 }) } + throw promptError }, }, "prompt", @@ -1623,14 +1623,12 @@ describe("prompt auth rejection handling", () => { }, }) expect(promptSession).toHaveBeenCalledTimes(1) - expect(markSelectionSent).not.toHaveBeenCalled() - expect(deleteSession).toHaveBeenCalledWith({ - sessionID: "session_auth_race", - }) + expect(markSelectionSent).toHaveBeenCalledTimes(1) + expect(deleteSession).not.toHaveBeenCalled() expect(routeStates.some((state) => state.startsWith("session:"))).toBe(true) - expect(routeStates.at(-1)).toBe("home") + expect(routeStates.at(-1)).toBe("session:session_auth_race") expect(promptRef!.current).toEqual({ - input: "recover this draft", + input: "", parts: [], }) expect(promptRef!.focused).toBe(false)