Просмотр исходного кода

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.
Shoubhit Dash 1 месяц назад
Родитель
Сommit
6bfce604bf

+ 2 - 2
packages/app/src/pages/layout/sidebar-items.tsx

@@ -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(() => {

+ 2 - 2
packages/app/src/pages/session.tsx

@@ -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(() => {

+ 0 - 46
packages/app/src/pages/session/activity.test.ts

@@ -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()
-  })
-})

+ 0 - 10
packages/app/src/pages/session/activity.ts

@@ -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"

+ 7 - 4
packages/app/src/pages/session/message-timeline.tsx

@@ -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({

+ 3 - 1
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -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(() => {