From 80c319dc49666b3a9a81835ec4c164993929b7a4 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 22 Apr 2026 12:07:57 +0700 Subject: [PATCH] Avoid git refresh for agent status updates --- packages/server/src/server/session.ts | 54 ++++++- .../src/server/session.workspaces.test.ts | 150 ++++++++++++++++++ packages/server/src/shared/messages.ts | 2 +- .../src/shared/messages.workspaces.test.ts | 40 +++++ 4 files changed, 237 insertions(+), 9 deletions(-) diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index cb197e399..b84ed7f2e 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -134,6 +134,7 @@ import type { AgentStorage } from "./agent/agent-storage.js"; import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId as normalizePersistedWorkspaceId, + deriveProjectGroupingName, deriveWorkspaceId, deriveProjectRootPath, deriveProjectKind, @@ -1363,14 +1364,26 @@ export class Session { } } - private async findWorkspaceByDirectory(cwd: string): Promise { - const normalizedCwd = await this.resolveWorkspaceDirectory(cwd); + private async findWorkspaceByDirectory( + cwd: string, + options?: { refreshGit?: boolean }, + ): Promise { + const normalizedCwd = await this.resolveWorkspaceDirectory(cwd, options); const workspaces = await this.workspaceRegistry.list(); - return workspaces.find((workspace) => workspace.cwd === normalizedCwd) ?? null; + const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(normalizedCwd, workspaces); + return workspaces.find((workspace) => workspace.workspaceId === workspaceId) ?? null; } - private async resolveWorkspaceDirectory(cwd: string): Promise { + private async resolveWorkspaceDirectory( + cwd: string, + options?: { refreshGit?: boolean }, + ): Promise { const normalizedCwd = normalizePersistedWorkspaceId(cwd); + if (options?.refreshGit === false) { + const snapshot = this.workspaceGitService.peekSnapshot(normalizedCwd); + return normalizePersistedWorkspaceId(snapshot?.git.repoRoot ?? normalizedCwd); + } + try { const snapshot = await this.workspaceGitService.getSnapshot(normalizedCwd); return normalizePersistedWorkspaceId(snapshot.git.repoRoot ?? normalizedCwd); @@ -1424,10 +1437,32 @@ export class Session { }; } - private async buildProjectPlacementForCwd(cwd: string): Promise { - const workspace = await this.findWorkspaceByDirectory(cwd); + private async buildProjectPlacementForCwd( + cwd: string, + options?: { refreshGit?: boolean; fallback?: boolean }, + ): Promise { + const workspace = await this.findWorkspaceByDirectory(cwd, { + refreshGit: options?.refreshGit, + }); if (!workspace) { - return null; + if (!options?.fallback) { + return null; + } + + const normalizedCwd = normalizePersistedWorkspaceId(cwd); + return { + projectKey: normalizedCwd, + projectName: deriveProjectGroupingName(normalizedCwd), + checkout: { + cwd: normalizedCwd, + isGit: false, + currentBranch: null, + remoteUrl: null, + worktreeRoot: null, + isPaseoOwnedWorktree: false, + mainRepoRoot: null, + }, + }; } return this.buildProjectPlacementForWorkspace(workspace); } @@ -1437,7 +1472,10 @@ export class Session { const subscription = this.agentUpdatesSubscription; const payload = await this.buildAgentPayload(agent); if (subscription) { - const project = await this.buildProjectPlacementForCwd(payload.cwd); + const project = await this.buildProjectPlacementForCwd(payload.cwd, { + refreshGit: false, + fallback: true, + }); if (!project) { throw new Error(`Workspace not found for agent ${payload.id}`); } diff --git a/packages/server/src/server/session.workspaces.test.ts b/packages/server/src/server/session.workspaces.test.ts index 168904ee0..51aca6a88 100644 --- a/packages/server/src/server/session.workspaces.test.ts +++ b/packages/server/src/server/session.workspaces.test.ts @@ -66,6 +66,48 @@ function makeAgent(input: { }; } +function makeManagedAgent(input: { + id: string; + cwd: string; + lifecycle: AgentSnapshotPayload["status"]; + updatedAt: string; +}) { + const now = new Date(input.updatedAt); + const snapshot = makeAgent({ + id: input.id, + cwd: input.cwd, + status: input.lifecycle, + updatedAt: input.updatedAt, + }); + + return { + ...snapshot, + lifecycle: snapshot.status, + config: { + provider: snapshot.provider, + cwd: snapshot.cwd, + }, + createdAt: now, + updatedAt: now, + pendingPermissions: new Map(), + bufferedPermissionResolutions: new Map(), + inFlightPermissionResponses: new Set(), + pendingReplacement: false, + persistence: null, + historyPrimed: true, + lastUserMessageAt: null, + attention: { + requiresAttention: false, + attentionReason: null, + attentionTimestamp: now, + }, + foregroundTurnWaiters: new Set(), + unsubscribeSession: null, + session: null, + activeForegroundTurnId: input.lifecycle === "running" ? "turn-1" : null, + }; +} + function agentIdsFromEntries(entries: Array<{ agent: Pick }>) { return entries.map((entry) => entry.agent.id); } @@ -272,6 +314,114 @@ function createSessionForWorkspaceTests( } describe("workspace aggregation", () => { + test("agent_update placement does not refresh git snapshots", async () => { + const emitted: SessionOutboundMessage[] = []; + const getSnapshot = vi.fn(async () => { + throw new Error("getSnapshot should not be called for agent_update placement"); + }); + const workspaceGitService = { + ...createNoopWorkspaceGitService(), + getSnapshot, + peekSnapshot: vi.fn(() => null), + }; + const session = createSessionForWorkspaceTests({ + onMessage: (message) => emitted.push(message), + workspaceGitService, + }) as any; + const project = createPersistedProjectRecord({ + projectId: "proj-1", + rootPath: "/tmp/repo", + kind: "git", + displayName: "repo", + createdAt: "2026-03-01T12:00:00.000Z", + updatedAt: "2026-03-01T12:00:00.000Z", + }); + const workspace = createPersistedWorkspaceRecord({ + workspaceId: "ws-1", + projectId: project.projectId, + cwd: "/tmp/repo", + kind: "local_checkout", + displayName: "repo", + createdAt: "2026-03-01T12:00:00.000Z", + updatedAt: "2026-03-01T12:00:00.000Z", + }); + + session.projectRegistry.get = async (id: string) => (id === project.projectId ? project : null); + session.workspaceRegistry.list = async () => [workspace]; + session.agentUpdatesSubscription = { + subscriptionId: "sub-agents", + filter: {}, + isBootstrapping: false, + pendingUpdatesByAgentId: new Map(), + }; + + await session.forwardAgentUpdate( + makeManagedAgent({ + id: "agent-1", + cwd: "/tmp/repo", + lifecycle: "running", + updatedAt: "2026-03-30T15:00:00.000Z", + }), + ); + + expect(getSnapshot).not.toHaveBeenCalled(); + expect(emitted.find((message) => message.type === "agent_update")?.payload).toMatchObject({ + kind: "upsert", + agent: { + id: "agent-1", + status: "running", + }, + project: { + projectKey: "proj-1", + projectName: "repo", + }, + }); + }); + + test("agent_update emits fallback placement when no workspace is registered", async () => { + const emitted: SessionOutboundMessage[] = []; + const getSnapshot = vi.fn(async () => { + throw new Error("getSnapshot should not be called for fallback agent_update placement"); + }); + const session = createSessionForWorkspaceTests({ + onMessage: (message) => emitted.push(message), + workspaceGitService: { + ...createNoopWorkspaceGitService(), + getSnapshot, + peekSnapshot: vi.fn(() => null), + }, + }) as any; + + session.agentUpdatesSubscription = { + subscriptionId: "sub-agents", + filter: {}, + isBootstrapping: false, + pendingUpdatesByAgentId: new Map(), + }; + + await session.forwardAgentUpdate( + makeManagedAgent({ + id: "agent-1", + cwd: "/tmp/unregistered", + lifecycle: "running", + updatedAt: "2026-03-30T15:00:00.000Z", + }), + ); + + expect(getSnapshot).not.toHaveBeenCalled(); + expect(emitted.find((message) => message.type === "agent_update")?.payload).toMatchObject({ + kind: "upsert", + project: { + projectKey: "/tmp/unregistered", + projectName: "unregistered", + checkout: { + cwd: "/tmp/unregistered", + isGit: false, + }, + }, + }); + }); + test("archive emits an authoritative agent_update upsert for subscribed clients", async () => { const emitted: Array<{ type: string; payload: any }> = []; const archivedRecord = { diff --git a/packages/server/src/shared/messages.ts b/packages/server/src/shared/messages.ts index c1d558940..dbb78864c 100644 --- a/packages/server/src/shared/messages.ts +++ b/packages/server/src/shared/messages.ts @@ -2090,7 +2090,7 @@ export const AgentUpdateMessageSchema = z.object({ z.object({ kind: z.literal("upsert"), agent: AgentSnapshotPayloadSchema, - project: ProjectPlacementPayloadSchema, + project: ProjectPlacementPayloadSchema.nullable().optional(), }), z.object({ kind: z.literal("remove"), diff --git a/packages/server/src/shared/messages.workspaces.test.ts b/packages/server/src/shared/messages.workspaces.test.ts index 04533a4b8..93cdf3f45 100644 --- a/packages/server/src/shared/messages.workspaces.test.ts +++ b/packages/server/src/shared/messages.workspaces.test.ts @@ -42,6 +42,46 @@ describe("workspace message schemas", () => { expect(activeScoped.scope).toBe("active"); }); + test("parses agent_update without project placement", () => { + const result = SessionOutboundMessageSchema.safeParse({ + type: "agent_update", + payload: { + kind: "upsert", + agent: { + id: "agent-1", + provider: "codex", + cwd: "/tmp/repo", + model: null, + features: [], + thinkingOptionId: null, + effectiveThinkingOptionId: null, + createdAt: "2026-04-04T00:00:00.000Z", + updatedAt: "2026-04-04T00:00:00.000Z", + lastUserMessageAt: null, + status: "running", + capabilities: { + supportsStreaming: true, + supportsSessionPersistence: true, + supportsDynamicModes: true, + supportsMcpServers: true, + supportsReasoningStream: true, + supportsToolInvocations: true, + }, + currentModeId: null, + availableModes: [], + pendingPermissions: [], + persistence: null, + title: null, + labels: {}, + requiresAttention: false, + attentionReason: null, + }, + }, + }); + + expect(result.success).toBe(true); + }); + test("parses paginated fetch_agent_history_request and response", () => { const request = SessionInboundMessageSchema.parse({ type: "fetch_agent_history_request",