Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions FORK_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
35 changes: 4 additions & 31 deletions packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof setTimeout> | undefined
if (sessionID == null) {
const workspace = workspaceSelection()
const workspaceID = iife(() => {
Expand Down Expand Up @@ -1628,7 +1623,6 @@ export function Prompt(props: PromptProps) {
}

sessionID = res.data.id
createdSessionID = sessionID
}

const messageID = MessageID.ascending()
Expand Down Expand Up @@ -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",
Expand All @@ -1787,16 +1761,15 @@ export function Prompt(props: PromptProps) {
duration: 5000,
})
})
if (editorParts.length > 0) editor.markSelectionSent()
}

clearSubmittedPrompt(currentMode)

// 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,
Expand Down
16 changes: 7 additions & 9 deletions packages/opencode/test/cli/tui/prompt.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand Down Expand Up @@ -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[] = []
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down
Loading