feat(ui): hydrate session diffs on open
Fetch session-level diffs when a session is opened and keep them updated via session.diff SSE events so UI state stays in sync with server changes.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { mapSdkSessionStatus, type Session, type SessionStatus } from "../types/session"
|
||||
import type { Message } from "../types/message"
|
||||
import type { FileDiff } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
import { instances } from "./instances"
|
||||
import { preferences, setAgentModelPreference } from "./preferences"
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
setSessionInfoByInstance,
|
||||
setSessions,
|
||||
sessions,
|
||||
withSession,
|
||||
loading,
|
||||
setLoading,
|
||||
cleanupBlankSessions,
|
||||
@@ -42,6 +44,49 @@ import {
|
||||
|
||||
const log = getLogger("api")
|
||||
|
||||
const pendingSessionDiffFetches = new Map<string, Promise<void>>()
|
||||
|
||||
async function loadSessionDiff(instanceId: string, sessionId: string, force = false): Promise<void> {
|
||||
if (!instanceId || !sessionId) return
|
||||
|
||||
const key = `${instanceId}:${sessionId}`
|
||||
if (!force) {
|
||||
const existing = sessions().get(instanceId)?.get(sessionId)
|
||||
if (existing?.diff !== undefined) return
|
||||
const pending = pendingSessionDiffFetches.get(key)
|
||||
if (pending) return pending
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
const instance = instances().get(instanceId)
|
||||
if (!instance?.client) return
|
||||
|
||||
const worktreeSlug = getWorktreeSlugForSession(instanceId, sessionId)
|
||||
const client = getOrCreateWorktreeClient(instanceId, worktreeSlug)
|
||||
|
||||
try {
|
||||
const diffs = await requestData<FileDiff[]>(
|
||||
client.session.diff({ sessionID: sessionId }),
|
||||
"session.diff",
|
||||
)
|
||||
|
||||
if (!Array.isArray(diffs)) {
|
||||
return
|
||||
}
|
||||
|
||||
withSession(instanceId, sessionId, (session) => {
|
||||
session.diff = diffs
|
||||
})
|
||||
} catch (error) {
|
||||
log.warn("Failed to fetch session diff", { instanceId, sessionId, error })
|
||||
}
|
||||
})()
|
||||
|
||||
pendingSessionDiffFetches.set(key, promise)
|
||||
void promise.finally(() => pendingSessionDiffFetches.delete(key))
|
||||
return promise
|
||||
}
|
||||
|
||||
interface SessionForkResponse {
|
||||
id: string
|
||||
title?: string
|
||||
@@ -570,6 +615,11 @@ async function loadMessages(instanceId: string, sessionId: string, force = false
|
||||
throw new Error("Session not found")
|
||||
}
|
||||
|
||||
// Fetch session-level diffs in the background once the session is opened.
|
||||
void loadSessionDiff(instanceId, sessionId).catch((error) => {
|
||||
log.warn("Failed to load session diff", { instanceId, sessionId, error })
|
||||
})
|
||||
|
||||
setLoading((prev) => {
|
||||
const next = { ...prev }
|
||||
const loadingSet = next.loadingMessages.get(instanceId) || new Set()
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
} from "../types/message"
|
||||
import type {
|
||||
EventSessionCompacted,
|
||||
EventSessionDiff,
|
||||
EventSessionError,
|
||||
EventSessionIdle,
|
||||
EventSessionUpdated,
|
||||
@@ -428,6 +429,31 @@ function handleSessionUpdate(instanceId: string, event: EventSessionUpdated): vo
|
||||
}
|
||||
}
|
||||
|
||||
function handleSessionDiff(instanceId: string, event: EventSessionDiff): void {
|
||||
const sessionId = event.properties?.sessionID
|
||||
if (!sessionId) return
|
||||
|
||||
const diffs = event.properties?.diff
|
||||
if (!Array.isArray(diffs)) return
|
||||
|
||||
const existing = sessions().get(instanceId)?.get(sessionId)
|
||||
if (existing) {
|
||||
withSession(instanceId, sessionId, (session) => {
|
||||
session.diff = diffs
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// A diff event can arrive before we have hydrated the session list.
|
||||
// Best-effort: fetch the session record so the diff has somewhere to live.
|
||||
void (async () => {
|
||||
await fetchSessionInfo(instanceId, sessionId, (event as any)?.directory)
|
||||
withSession(instanceId, sessionId, (session) => {
|
||||
session.diff = diffs
|
||||
})
|
||||
})().catch((error) => log.warn("Failed to hydrate session for diff event", { instanceId, sessionId, error }))
|
||||
}
|
||||
|
||||
function handleSessionIdle(instanceId: string, event: EventSessionIdle): void {
|
||||
const sessionId = event.properties?.sessionID
|
||||
if (!sessionId) return
|
||||
@@ -605,6 +631,7 @@ export {
|
||||
handleQuestionAsked,
|
||||
handleQuestionAnswered,
|
||||
handleSessionCompacted,
|
||||
handleSessionDiff,
|
||||
handleSessionError,
|
||||
handleSessionIdle,
|
||||
handleSessionStatus,
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
handleQuestionAnswered,
|
||||
handleQuestionAsked,
|
||||
handleSessionCompacted,
|
||||
handleSessionDiff,
|
||||
handleSessionError,
|
||||
handleSessionIdle,
|
||||
handleSessionStatus,
|
||||
@@ -77,6 +78,7 @@ sseManager.onMessageRemoved = handleMessageRemoved
|
||||
sseManager.onMessagePartRemoved = handleMessagePartRemoved
|
||||
sseManager.onSessionUpdate = handleSessionUpdate
|
||||
sseManager.onSessionCompacted = handleSessionCompacted
|
||||
sseManager.onSessionDiff = handleSessionDiff
|
||||
sseManager.onSessionError = handleSessionError
|
||||
sseManager.onSessionIdle = handleSessionIdle
|
||||
sseManager.onSessionStatus = handleSessionStatus
|
||||
|
||||
Reference in New Issue
Block a user