diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index 305b0f4e5..0c3f0cbaa 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -976,6 +976,7 @@ function WorkspaceScreenContent({ const workspaceSetupSnapshot = useWorkspaceSetupStore((state) => persistenceKey ? (state.snapshots[persistenceKey] ?? null) : null, ); + const upsertWorkspaceSetupProgress = useWorkspaceSetupStore((state) => state.upsertProgress); const showWorkspaceSetup = shouldShowWorkspaceSetup(workspaceSetupSnapshot); const uiTabs = useMemo( () => (workspaceLayout ? collectAllTabs(workspaceLayout.root) : EMPTY_UI_TABS), @@ -1191,6 +1192,55 @@ function WorkspaceScreenContent({ const emptyWorkspaceSeedRef = useRef(null); const autoOpenedSetupTabWorkspaceRef = useRef(null); + const requestedWorkspaceSetupStatusKeyRef = useRef(null); + + useEffect(() => { + if (!isRouteFocused) { + return; + } + if (!client || !normalizedServerId || !normalizedWorkspaceId || !persistenceKey) { + return; + } + if (workspaceSetupSnapshot) { + return; + } + if (requestedWorkspaceSetupStatusKeyRef.current === persistenceKey) { + return; + } + + requestedWorkspaceSetupStatusKeyRef.current = persistenceKey; + let isCancelled = false; + + client + .fetchWorkspaceSetupStatus(normalizedWorkspaceId) + .then((response) => { + if (isCancelled || response.workspaceId !== normalizedWorkspaceId || !response.snapshot) { + return; + } + upsertWorkspaceSetupProgress({ + serverId: normalizedServerId, + payload: { workspaceId: response.workspaceId, ...response.snapshot }, + }); + }) + .catch(() => { + if (requestedWorkspaceSetupStatusKeyRef.current === persistenceKey) { + requestedWorkspaceSetupStatusKeyRef.current = null; + } + }); + + return () => { + isCancelled = true; + }; + }, [ + client, + isRouteFocused, + normalizedServerId, + normalizedWorkspaceId, + persistenceKey, + upsertWorkspaceSetupProgress, + workspaceSetupSnapshot, + ]); + useEffect(() => { if (!isRouteFocused) { return; diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 69fd6adaa..8c85da290 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -508,6 +508,7 @@ export type SessionOptions = { providerSnapshotManager?: ProviderSnapshotManager; scriptRouteStore?: ScriptRouteStore; scriptRuntimeStore?: WorkspaceScriptRuntimeStore; + workspaceSetupSnapshots?: Map; onBranchChanged?: ( workspaceId: string, oldBranch: string | null, @@ -726,7 +727,7 @@ export class Session { ); private readonly checkoutDiffSubscriptions = new Map void>(); private readonly workspaceGitWatchTargets = new Map(); - private readonly workspaceSetupSnapshots = new Map(); + private readonly workspaceSetupSnapshots: Map; private readonly workspaceGitFetchSubscriptions = new Map void>(); private readonly workspaceGitSubscriptions = new Map void>(); private readonly registerVoiceSpeakHandler?: ( @@ -775,6 +776,7 @@ export class Session { providerSnapshotManager, scriptRouteStore, scriptRuntimeStore, + workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, @@ -816,6 +818,7 @@ export class Session { this.providerSnapshotManager = providerSnapshotManager ?? null; this.scriptRouteStore = scriptRouteStore ?? null; this.scriptRuntimeStore = scriptRuntimeStore ?? null; + this.workspaceSetupSnapshots = workspaceSetupSnapshots ?? new Map(); this.onBranchChanged = onBranchChanged; this.getDaemonTcpPort = getDaemonTcpPort ?? null; this.getDaemonTcpHost = getDaemonTcpHost ?? null; diff --git a/packages/server/src/server/websocket-server.ts b/packages/server/src/server/websocket-server.ts index 3a5e52a40..0d5b3c2fc 100644 --- a/packages/server/src/server/websocket-server.ts +++ b/packages/server/src/server/websocket-server.ts @@ -16,6 +16,7 @@ import type { DaemonConfigStore, MutableDaemonConfig } from "./daemon-config-sto import { type ServerInfoStatusPayload, type SessionOutboundMessage, + type WorkspaceSetupSnapshot, type WSHelloMessage, WSInboundMessageSchema, type ServerCapabilityState, @@ -317,6 +318,7 @@ export class VoiceAssistantWebSocketServer { } | null; private readonly voiceSpeakHandlers = new Map(); private readonly voiceCallerContexts = new Map(); + private readonly workspaceSetupSnapshots = new Map(); private readonly agentProviderRuntimeSettings: AgentProviderRuntimeSettingsMap | undefined; private readonly providerOverrides: Record | undefined; private readonly isDev: boolean; @@ -766,6 +768,7 @@ export class VoiceAssistantWebSocketServer { providerSnapshotManager: this.providerSnapshotManager, scriptRouteStore: this.scriptRouteStore ?? undefined, scriptRuntimeStore: this.scriptRuntimeStore ?? undefined, + workspaceSetupSnapshots: this.workspaceSetupSnapshots, onBranchChanged: this.onBranchChanged ?? undefined, getDaemonTcpPort: this.getDaemonTcpPort ?? undefined, getDaemonTcpHost: this.getDaemonTcpHost ?? undefined,