From 227e4756242aac37b85f86ad2d56b4b7359923ff Mon Sep 17 00:00:00 2001 From: netsky-lab Date: Wed, 1 Jul 2026 05:44:03 +0000 Subject: [PATCH] fix(tui): hide stale retry status after model switch --- .../cli/cmd/tui/component/prompt/index.tsx | 5 ++- .../cli/cmd/tui/component/prompt/status.ts | 18 +++++++++++ packages/opencode/src/session/processor.ts | 2 ++ packages/opencode/src/session/status.ts | 2 ++ .../test/cli/tui/prompt-status.test.ts | 31 +++++++++++++++++++ packages/opencode/test/session/retry.test.ts | 16 ++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 2 ++ 7 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/src/cli/cmd/tui/component/prompt/status.ts create mode 100644 packages/opencode/test/cli/tui/prompt-status.test.ts 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 f9fe899bb..bc94b1edb 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -45,6 +45,7 @@ import { DialogWorkspaceCreate, restoreWorkspaceSession } from "../dialog-worksp import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable" import { DialogAgreement, FREE_AGREEMENT_KEY, FREE_MODEL_IDS } from "../dialog-agreement" import { useArgs } from "@tui/context/args" +import { visiblePromptStatus } from "./status" export type PromptProps = { sessionID?: string @@ -116,7 +117,9 @@ export function Prompt(props: PromptProps) { const sync = useSync() const dialog = useDialog() const toast = useToast() - const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" }) + const status = createMemo(() => + visiblePromptStatus(sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" }, local.model.current()), + ) const history = usePromptHistory() const stash = usePromptStash() const command = useCommandDialog() diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/status.ts b/packages/opencode/src/cli/cmd/tui/component/prompt/status.ts new file mode 100644 index 000000000..4e0da026d --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/status.ts @@ -0,0 +1,18 @@ +import type { SessionStatus } from "@mimo-ai/sdk/v2" + +type ModelRef = { + providerID: string + modelID: string +} + +type RetryStatus = Extract & Partial + +export function visiblePromptStatus(status: SessionStatus, model: ModelRef | undefined): SessionStatus { + if (status.type !== "retry") return status + + const retry = status as RetryStatus + if (!retry.providerID || !retry.modelID || !model) return status + if (retry.providerID === model.providerID && retry.modelID === model.modelID) return status + + return { type: "idle" } +} diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 865e8820e..8157ce713 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -739,6 +739,8 @@ export const layer: Layer.Layer< attempt: info.attempt, message: info.message, next: info.next, + providerID: ctx.model.providerID, + modelID: ctx.model.id, }) : Effect.void, }), diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index a65377c54..2f45a6c2e 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -15,6 +15,8 @@ export const Info = z attempt: z.number(), message: z.string(), next: z.number(), + providerID: z.string().optional(), + modelID: z.string().optional(), }), z.object({ type: z.literal("busy"), diff --git a/packages/opencode/test/cli/tui/prompt-status.test.ts b/packages/opencode/test/cli/tui/prompt-status.test.ts new file mode 100644 index 000000000..f8d066b97 --- /dev/null +++ b/packages/opencode/test/cli/tui/prompt-status.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from "bun:test" +import { visiblePromptStatus } from "../../../src/cli/cmd/tui/component/prompt/status" + +test("hides retry status from a previously selected model", () => { + expect( + visiblePromptStatus( + { + type: "retry", + attempt: 1, + message: "quota exceeded", + next: Date.now() + 1000, + providerID: "glm", + modelID: "glm-4.5", + }, + { providerID: "mimo", modelID: "mimo-auto" }, + ), + ).toEqual({ type: "idle" }) +}) + +test("keeps retry status for the current model", () => { + const status = { + type: "retry" as const, + attempt: 1, + message: "quota exceeded", + next: Date.now() + 1000, + providerID: "glm", + modelID: "glm-4.5", + } + + expect(visiblePromptStatus(status, { providerID: "glm", modelID: "glm-4.5" })).toBe(status) +}) diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 2eb7443a5..a257c0216 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -121,6 +121,22 @@ describe("session.retry.delay", () => { }, }) }) + + test("retry status preserves the model that scheduled the retry", () => { + expect( + SessionStatus.Info.parse({ + type: "retry", + attempt: 1, + message: "quota exceeded", + next: Date.now() + 1000, + providerID: "glm", + modelID: "glm-4.5", + }), + ).toMatchObject({ + providerID: "glm", + modelID: "glm-4.5", + }) + }) }) describe("session.retry.retryable", () => { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 288fb8891..8a82856a2 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -743,6 +743,8 @@ export type SessionStatus = attempt: number message: string next: number + providerID?: string + modelID?: string } | { type: "busy"