Avoid git refresh for agent status updates

This commit is contained in:
Mohamed Boudra
2026-04-22 12:07:57 +07:00
parent 77c8c91ab5
commit 80c319dc49
4 changed files with 237 additions and 9 deletions

View File

@@ -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<PersistedWorkspaceRecord | null> {
const normalizedCwd = await this.resolveWorkspaceDirectory(cwd);
private async findWorkspaceByDirectory(
cwd: string,
options?: { refreshGit?: boolean },
): Promise<PersistedWorkspaceRecord | null> {
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<string> {
private async resolveWorkspaceDirectory(
cwd: string,
options?: { refreshGit?: boolean },
): Promise<string> {
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<ProjectPlacementPayload | null> {
const workspace = await this.findWorkspaceByDirectory(cwd);
private async buildProjectPlacementForCwd(
cwd: string,
options?: { refreshGit?: boolean; fallback?: boolean },
): Promise<ProjectPlacementPayload | null> {
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}`);
}

View File

@@ -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<AgentSnapshotPayload, "id"> }>) {
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 = {

View File

@@ -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"),

View File

@@ -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",