mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Avoid git refresh for agent status updates
This commit is contained in:
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user