fix(session): ignore stale pending ui state
Inline the app-side running checks and make the tui only treat the trailing assistant as pending so old crashed messages do not keep sessions active or queued.pull/17593/head
parent
060f482eb2
commit
6bfce604bf
|
|
@ -15,7 +15,6 @@ import { useLanguage } from "@/context/language"
|
|||
import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
|
||||
import { useNotification } from "@/context/notification"
|
||||
import { usePermission } from "@/context/permission"
|
||||
import { working } from "@/pages/session/activity"
|
||||
import { messageAgentColor } from "@/utils/agent"
|
||||
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
|
||||
import { hasProjectPermissions } from "./helpers"
|
||||
|
|
@ -205,7 +204,8 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|||
})
|
||||
const isWorking = createMemo(() => {
|
||||
if (hasPermissions()) return false
|
||||
return working(sessionStore.session_status[props.session.id])
|
||||
const status = sessionStore.session_status[props.session.id]
|
||||
return status !== undefined && status.type !== "idle"
|
||||
})
|
||||
|
||||
const tint = createMemo(() => {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import { useSettings } from "@/context/settings"
|
|||
import { useSync } from "@/context/sync"
|
||||
import { useTerminal } from "@/context/terminal"
|
||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||
import { working } from "@/pages/session/activity"
|
||||
import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
|
||||
import { createOpenReviewFile, createSessionTabs, createSizing, focusTerminalById } from "@/pages/session/helpers"
|
||||
import { MessageTimeline } from "@/pages/session/message-timeline"
|
||||
|
|
@ -1362,7 +1361,8 @@ export default function Page() {
|
|||
})
|
||||
|
||||
const busy = (sessionID: string) => {
|
||||
return working(sync.data.session_status[sessionID])
|
||||
const status = sync.data.session_status[sessionID]
|
||||
return status !== undefined && status.type !== "idle"
|
||||
}
|
||||
|
||||
const queuedFollowups = createMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import type { AssistantMessage, Message as MessageType, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { pending, working } from "./activity"
|
||||
|
||||
const user = (id: string) =>
|
||||
({
|
||||
id,
|
||||
sessionID: "ses_1",
|
||||
role: "user",
|
||||
time: { created: 1 },
|
||||
}) as UserMessage
|
||||
|
||||
const assistant = (id: string, parentID: string, completed?: number) =>
|
||||
({
|
||||
id,
|
||||
sessionID: "ses_1",
|
||||
parentID,
|
||||
role: "assistant",
|
||||
time: completed === undefined ? { created: 2 } : { created: 2, completed },
|
||||
}) as AssistantMessage
|
||||
|
||||
describe("session activity", () => {
|
||||
test("treats only non-idle status as running", () => {
|
||||
expect(working(undefined)).toBe(false)
|
||||
expect(working({ type: "idle" })).toBe(false)
|
||||
expect(working({ type: "busy" })).toBe(true)
|
||||
expect(working({ type: "retry", attempt: 1, message: "retry", next: 1 })).toBe(true)
|
||||
})
|
||||
|
||||
test("returns the trailing incomplete assistant", () => {
|
||||
const messages: MessageType[] = [user("msg_1"), assistant("msg_2", "msg_1")]
|
||||
|
||||
expect(pending(messages)?.id).toBe("msg_2")
|
||||
})
|
||||
|
||||
test("ignores older incomplete assistants once a later assistant completed", () => {
|
||||
const messages: MessageType[] = [
|
||||
user("msg_1"),
|
||||
assistant("msg_2", "msg_1"),
|
||||
user("msg_3"),
|
||||
assistant("msg_4", "msg_3", 4),
|
||||
]
|
||||
|
||||
expect(pending(messages)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import type { AssistantMessage, Message as MessageType } from "@opencode-ai/sdk/v2"
|
||||
import type { SessionStatus } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
export const pending = (messages: readonly MessageType[]) => {
|
||||
const item = messages.findLast((item): item is AssistantMessage => item.role === "assistant")
|
||||
if (!item || typeof item.time.completed === "number") return
|
||||
return item
|
||||
}
|
||||
|
||||
export const working = (status: SessionStatus | undefined) => status !== undefined && status.type !== "idle"
|
||||
|
|
@ -12,7 +12,7 @@ import { Spinner } from "@opencode-ai/ui/spinner"
|
|||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
import type { Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
|
|
@ -27,7 +27,6 @@ import { usePlatform } from "@/context/platform"
|
|||
import { useSettings } from "@/context/settings"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { pending, working } from "@/pages/session/activity"
|
||||
import { messageAgentColor } from "@/utils/agent"
|
||||
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
||||
|
||||
|
|
@ -237,13 +236,17 @@ export function MessageTimeline(props: {
|
|||
if (!id) return emptyMessages
|
||||
return sync.data.message[id] ?? emptyMessages
|
||||
})
|
||||
const assistant = createMemo(() => pending(sessionMessages()))
|
||||
const assistant = createMemo(() => {
|
||||
const item = sessionMessages().findLast((item): item is AssistantMessage => item.role === "assistant")
|
||||
if (!item || typeof item.time.completed === "number") return
|
||||
return item
|
||||
})
|
||||
const sessionStatus = createMemo(() => {
|
||||
const id = sessionID()
|
||||
if (!id) return idle
|
||||
return sync.data.session_status[id] ?? idle
|
||||
})
|
||||
const busy = createMemo(() => working(sessionStatus()))
|
||||
const busy = createMemo(() => sessionStatus().type !== "idle")
|
||||
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent))
|
||||
|
||||
const [slot, setSlot] = createStore({
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ export function Session() {
|
|||
})
|
||||
|
||||
const pending = createMemo(() => {
|
||||
return messages().findLast((x) => x.role === "assistant" && !x.time.completed)?.id
|
||||
const last = messages().findLast((x) => x.role === "assistant")
|
||||
if (!last || last.time.completed) return
|
||||
return last.id
|
||||
})
|
||||
|
||||
const lastAssistant = createMemo(() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue