fix: use cached git service snapshots for fetchWorkspaces instead of shelling out
describeWorkspaceRecordWithGitData was calling buildProjectPlacement → getCheckoutStatusLite which spawned 3-4 sequential git child processes per workspace. With 8 active workspaces and event loop pressure from running agents, this caused fetchWorkspaces to take 35-45s. Now uses workspaceGitService.getSnapshot() which returns instantly from cache (warm path) or awaits the git service's own concurrency-managed refresh (cold path).
This commit is contained in:
@@ -114,6 +114,7 @@ import type {
|
|||||||
import { AgentStorage, type StoredAgentRecord } from "./agent/agent-storage.js";
|
import { AgentStorage, type StoredAgentRecord } from "./agent/agent-storage.js";
|
||||||
import {
|
import {
|
||||||
buildProjectPlacementForCwd,
|
buildProjectPlacementForCwd,
|
||||||
|
checkoutLiteFromGitSnapshot,
|
||||||
detectStaleWorkspaces,
|
detectStaleWorkspaces,
|
||||||
deriveProjectKind,
|
deriveProjectKind,
|
||||||
deriveProjectRootPath,
|
deriveProjectRootPath,
|
||||||
@@ -5406,26 +5407,17 @@ export class Session {
|
|||||||
projectRecord?: PersistedProjectRecord | null,
|
projectRecord?: PersistedProjectRecord | null,
|
||||||
): Promise<WorkspaceDescriptorPayload> {
|
): Promise<WorkspaceDescriptorPayload> {
|
||||||
const base = await this.describeWorkspaceRecord(workspace, projectRecord);
|
const base = await this.describeWorkspaceRecord(workspace, projectRecord);
|
||||||
let displayName = workspace.displayName;
|
const snapshot = await this.workspaceGitService.getSnapshot(workspace.cwd);
|
||||||
try {
|
|
||||||
const placement = await this.buildProjectPlacement(workspace.cwd);
|
|
||||||
displayName = deriveWorkspaceDisplayName({
|
|
||||||
cwd: workspace.cwd,
|
|
||||||
checkout: placement.checkout,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// Fall back to the persisted label if checkout metadata is unavailable.
|
|
||||||
}
|
|
||||||
|
|
||||||
let snapshot: WorkspaceGitRuntimeSnapshot | null = null;
|
const checkout = checkoutLiteFromGitSnapshot(workspace.cwd, snapshot.git);
|
||||||
snapshot = this.workspaceGitService.peekSnapshot(workspace.cwd);
|
const displayName = deriveWorkspaceDisplayName({ cwd: workspace.cwd, checkout });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
name: displayName,
|
name: displayName,
|
||||||
diffStat: snapshot?.git.diffStat ?? null,
|
diffStat: snapshot.git.diffStat ?? null,
|
||||||
gitRuntime: snapshot ? this.buildWorkspaceGitRuntimePayload(snapshot) : undefined,
|
gitRuntime: this.buildWorkspaceGitRuntimePayload(snapshot),
|
||||||
githubRuntime: snapshot ? this.buildWorkspaceGitHubRuntimePayload(snapshot) : undefined,
|
githubRuntime: this.buildWorkspaceGitHubRuntimePayload(snapshot),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1845,9 +1845,7 @@ describe("workspace aggregation", () => {
|
|||||||
});
|
});
|
||||||
const workspaceGitService = createNoopWorkspaceGitService();
|
const workspaceGitService = createNoopWorkspaceGitService();
|
||||||
workspaceGitService.peekSnapshot = vi.fn(() => runtimeSnapshot);
|
workspaceGitService.peekSnapshot = vi.fn(() => runtimeSnapshot);
|
||||||
workspaceGitService.getSnapshot = vi.fn(async () => {
|
workspaceGitService.getSnapshot = vi.fn(async () => runtimeSnapshot);
|
||||||
throw new Error("fetch_workspaces should not trigger per-workspace refreshes");
|
|
||||||
});
|
|
||||||
workspaceGitService.subscribe = vi.fn(async () => ({
|
workspaceGitService.subscribe = vi.fn(async () => ({
|
||||||
initial: runtimeSnapshot,
|
initial: runtimeSnapshot,
|
||||||
unsubscribe: () => {},
|
unsubscribe: () => {},
|
||||||
@@ -1901,8 +1899,7 @@ describe("workspace aggregation", () => {
|
|||||||
| { type: "fetch_workspaces_response"; payload: any }
|
| { type: "fetch_workspaces_response"; payload: any }
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
expect(workspaceGitService.peekSnapshot).toHaveBeenCalledWith("/tmp/repo");
|
expect(workspaceGitService.getSnapshot).toHaveBeenCalledWith("/tmp/repo");
|
||||||
expect(workspaceGitService.getSnapshot).not.toHaveBeenCalled();
|
|
||||||
expect(response?.payload.entries).toEqual([
|
expect(response?.payload.entries).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "/tmp/repo",
|
id: "/tmp/repo",
|
||||||
@@ -1953,9 +1950,7 @@ describe("workspace aggregation", () => {
|
|||||||
});
|
});
|
||||||
const workspaceGitService = createNoopWorkspaceGitService();
|
const workspaceGitService = createNoopWorkspaceGitService();
|
||||||
workspaceGitService.peekSnapshot = vi.fn(() => runtimeSnapshot);
|
workspaceGitService.peekSnapshot = vi.fn(() => runtimeSnapshot);
|
||||||
workspaceGitService.getSnapshot = vi.fn(async () => {
|
workspaceGitService.getSnapshot = vi.fn(async () => runtimeSnapshot);
|
||||||
throw new Error("workspace updates should use passive workspace git snapshots");
|
|
||||||
});
|
|
||||||
|
|
||||||
const session = createSessionForWorkspaceTests({
|
const session = createSessionForWorkspaceTests({
|
||||||
workspaceGitService,
|
workspaceGitService,
|
||||||
@@ -2008,8 +2003,7 @@ describe("workspace aggregation", () => {
|
|||||||
skipReconcile: true,
|
skipReconcile: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(workspaceGitService.peekSnapshot).toHaveBeenCalledWith("/tmp/repo");
|
expect(workspaceGitService.getSnapshot).toHaveBeenCalledWith("/tmp/repo");
|
||||||
expect(workspaceGitService.getSnapshot).not.toHaveBeenCalled();
|
|
||||||
expect(emitted).toContainEqual({
|
expect(emitted).toContainEqual({
|
||||||
type: "workspace_update",
|
type: "workspace_update",
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
@@ -137,6 +137,50 @@ export function deriveWorkspaceKind(checkout: ProjectCheckoutLitePayload): Persi
|
|||||||
return checkout.isPaseoOwnedWorktree ? "worktree" : "local_checkout";
|
return checkout.isPaseoOwnedWorktree ? "worktree" : "local_checkout";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkoutLiteFromGitSnapshot(
|
||||||
|
cwd: string,
|
||||||
|
git: {
|
||||||
|
isGit: boolean;
|
||||||
|
currentBranch: string | null;
|
||||||
|
remoteUrl: string | null;
|
||||||
|
repoRoot: string | null;
|
||||||
|
isPaseoOwnedWorktree: boolean;
|
||||||
|
mainRepoRoot: string | null;
|
||||||
|
},
|
||||||
|
): ProjectCheckoutLitePayload {
|
||||||
|
if (!git.isGit) {
|
||||||
|
return {
|
||||||
|
cwd,
|
||||||
|
isGit: false,
|
||||||
|
currentBranch: null,
|
||||||
|
remoteUrl: null,
|
||||||
|
worktreeRoot: null,
|
||||||
|
isPaseoOwnedWorktree: false,
|
||||||
|
mainRepoRoot: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (git.isPaseoOwnedWorktree && git.mainRepoRoot) {
|
||||||
|
return {
|
||||||
|
cwd,
|
||||||
|
isGit: true,
|
||||||
|
currentBranch: git.currentBranch,
|
||||||
|
remoteUrl: git.remoteUrl,
|
||||||
|
worktreeRoot: git.repoRoot ?? cwd,
|
||||||
|
isPaseoOwnedWorktree: true,
|
||||||
|
mainRepoRoot: git.mainRepoRoot,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
cwd,
|
||||||
|
isGit: true,
|
||||||
|
currentBranch: git.currentBranch,
|
||||||
|
remoteUrl: git.remoteUrl,
|
||||||
|
worktreeRoot: git.repoRoot ?? cwd,
|
||||||
|
isPaseoOwnedWorktree: false,
|
||||||
|
mainRepoRoot: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function detectStaleWorkspaces(
|
export async function detectStaleWorkspaces(
|
||||||
input: DetectStaleWorkspacesInput,
|
input: DetectStaleWorkspacesInput,
|
||||||
): Promise<Set<string>> {
|
): Promise<Set<string>> {
|
||||||
|
|||||||
Reference in New Issue
Block a user