fix(app,server): discover server-cached setup snapshots across sessions

807cce6a hides the "Show setup" action when there is no workspace setup
snapshot. That broke the case where setup genuinely ran on a different
WebSocket session than the one the browser reconnected with: the snapshot
was cached per-Session, so discovery returned null and the action stayed
hidden.

Share the setup snapshot cache at the websocket-server level and inject
it into every Session. On focus, the workspace screen now requests
fetchWorkspaceSetupStatus when it has no local snapshot; workspaces with
no setup history still return null, preserving 807cce6a's intent.
This commit is contained in:
Mohamed Boudra
2026-04-21 23:04:47 +07:00
parent 1582028902
commit 3fe63d5a2c
3 changed files with 57 additions and 1 deletions
@@ -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<string | null>(null);
const autoOpenedSetupTabWorkspaceRef = useRef<string | null>(null);
const requestedWorkspaceSetupStatusKeyRef = useRef<string | null>(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;
+4 -1
View File
@@ -508,6 +508,7 @@ export type SessionOptions = {
providerSnapshotManager?: ProviderSnapshotManager;
scriptRouteStore?: ScriptRouteStore;
scriptRuntimeStore?: WorkspaceScriptRuntimeStore;
workspaceSetupSnapshots?: Map<string, WorkspaceSetupSnapshot>;
onBranchChanged?: (
workspaceId: string,
oldBranch: string | null,
@@ -726,7 +727,7 @@ export class Session {
);
private readonly checkoutDiffSubscriptions = new Map<string, () => void>();
private readonly workspaceGitWatchTargets = new Map<string, WorkspaceGitWatchTarget>();
private readonly workspaceSetupSnapshots = new Map<string, WorkspaceSetupSnapshot>();
private readonly workspaceSetupSnapshots: Map<string, WorkspaceSetupSnapshot>;
private readonly workspaceGitFetchSubscriptions = new Map<string, () => void>();
private readonly workspaceGitSubscriptions = new Map<string, () => 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;
@@ -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<string, VoiceSpeakHandler>();
private readonly voiceCallerContexts = new Map<string, VoiceCallerContext>();
private readonly workspaceSetupSnapshots = new Map<string, WorkspaceSetupSnapshot>();
private readonly agentProviderRuntimeSettings: AgentProviderRuntimeSettingsMap | undefined;
private readonly providerOverrides: Record<string, ProviderOverride> | 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,