fix(server): push workspace updates instantly, reconcile in background

emitWorkspaceUpdateForCwd and emitWorkspaceUpdatesForCwds blocked on
reconcileActiveWorkspaceRecords() (stat + reconcile on every active
workspace) before pushing the target workspace update to clients. This
caused new worktrees to appear in the sidebar with a noticeable delay
after the agent was already visible.

Emit the target workspace immediately from the registry snapshot, then
run reconciliation in the background via reconcileAndEmitWorkspaceUpdates.
Ensure the dedupeGitState early-return still triggers background
reconciliation so metadata changes (branch renames, display names)
are not silently dropped.
This commit is contained in:
Mohamed Boudra
2026-04-07 09:50:39 +07:00
parent cdbaa8d29c
commit 51b83768c7
+28 -32
View File
@@ -5784,66 +5784,60 @@ export class Session {
return; return;
} }
const changedWorkspaceIds = await this.reconcileActiveWorkspaceRecords();
const activeWorkspaces = (await this.workspaceRegistry.list()).filter( const activeWorkspaces = (await this.workspaceRegistry.list()).filter(
(workspace) => !workspace.archivedAt, (workspace) => !workspace.archivedAt,
); );
const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(cwd, activeWorkspaces); const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(cwd, activeWorkspaces);
const all = await this.listWorkspaceDescriptorsSnapshot(); const all = await this.listWorkspaceDescriptorsSnapshot();
const descriptorsByWorkspaceId = new Map(all.map((entry) => [entry.id, entry] as const)); const descriptorsByWorkspaceId = new Map(all.map((entry) => [entry.id, entry] as const));
const workspaceIdsToEmit = new Set<string>([
workspaceId,
...changedWorkspaceIds,
]);
for (const nextWorkspaceId of workspaceIdsToEmit) { const workspace = descriptorsByWorkspaceId.get(workspaceId);
const workspace = descriptorsByWorkspaceId.get(nextWorkspaceId); const nextWorkspace =
const nextWorkspace = workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })
workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter }) ? workspace
? workspace : null;
: null; if (
if ( options?.dedupeGitState &&
options?.dedupeGitState && this.shouldSkipWorkspaceGitWatchUpdate(workspaceId, nextWorkspace)
this.shouldSkipWorkspaceGitWatchUpdate(nextWorkspaceId, nextWorkspace) ) {
) { void this.reconcileAndEmitWorkspaceUpdates();
continue; return;
} }
this.rememberWorkspaceGitWatchFingerprint(nextWorkspaceId, nextWorkspace); this.rememberWorkspaceGitWatchFingerprint(workspaceId, nextWorkspace);
if (!nextWorkspace) {
this.bufferOrEmitWorkspaceUpdate(subscription, {
kind: "remove",
id: nextWorkspaceId,
});
continue;
}
if (!nextWorkspace) {
this.bufferOrEmitWorkspaceUpdate(subscription, {
kind: "remove",
id: workspaceId,
});
} else {
this.bufferOrEmitWorkspaceUpdate(subscription, { this.bufferOrEmitWorkspaceUpdate(subscription, {
kind: "upsert", kind: "upsert",
workspace: nextWorkspace, workspace: nextWorkspace,
}); });
} }
void this.reconcileAndEmitWorkspaceUpdates();
} }
private async emitWorkspaceUpdatesForCwds(cwds: Iterable<string>): Promise<void> { private async emitWorkspaceUpdatesForCwds(cwds: Iterable<string>): Promise<void> {
if (!this.workspaceUpdatesSubscription) { const subscription = this.workspaceUpdatesSubscription;
if (!subscription) {
return; return;
} }
const changedWorkspaceIds = await this.reconcileActiveWorkspaceRecords();
const activeWorkspaces = (await this.workspaceRegistry.list()).filter( const activeWorkspaces = (await this.workspaceRegistry.list()).filter(
(workspace) => !workspace.archivedAt, (workspace) => !workspace.archivedAt,
); );
const uniqueWorkspaceCwds = new Set<string>(changedWorkspaceIds); const uniqueWorkspaceIds = new Set<string>();
for (const cwd of cwds) { for (const cwd of cwds) {
uniqueWorkspaceCwds.add(this.resolveRegisteredWorkspaceIdForCwd(cwd, activeWorkspaces)); uniqueWorkspaceIds.add(this.resolveRegisteredWorkspaceIdForCwd(cwd, activeWorkspaces));
} }
const subscription = this.workspaceUpdatesSubscription;
const all = await this.listWorkspaceDescriptorsSnapshot(); const all = await this.listWorkspaceDescriptorsSnapshot();
const descriptorsByWorkspaceId = new Map(all.map((entry) => [entry.id, entry] as const)); const descriptorsByWorkspaceId = new Map(all.map((entry) => [entry.id, entry] as const));
for (const workspaceId of uniqueWorkspaceCwds) { for (const workspaceId of uniqueWorkspaceIds) {
const workspace = descriptorsByWorkspaceId.get(workspaceId); const workspace = descriptorsByWorkspaceId.get(workspaceId);
const nextWorkspace = const nextWorkspace =
workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter }) workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })
@@ -5864,6 +5858,8 @@ export class Session {
workspace: nextWorkspace, workspace: nextWorkspace,
}); });
} }
void this.reconcileAndEmitWorkspaceUpdates();
} }
private async handleFetchAgents( private async handleFetchAgents(