fix(server): recognize persisted parents during child creation

Creation only needs the caller's identity and workspace. Read the existing stored record when the parent is unloaded after restart, rather than requiring its provider session to be opened first.
This commit is contained in:
paseo-bot[bot]
2026-11-03 23:09:38 +00:00
parent 4cd23a8f17
commit 6a15fc1a46
2 changed files with 79 additions and 17 deletions
@@ -0,0 +1,64 @@
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import path from "node:path";
import { expect, test } from "vitest";
import { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
import { createDaemonTestContext, type DaemonTestContext } from "../test-utils/index.js";
test.each(["agent", "workspace"] as const)(
"%s creation recognizes an unopened persisted caller after restart",
async (kind) => {
const artifacts = path.resolve(".artifacts");
await mkdir(artifacts, { recursive: true });
const root = await mkdtemp(path.join(artifacts, "persisted-caller-"));
const cwd = path.join(root, "project");
await mkdir(cwd);
let ctx: DaemonTestContext | undefined;
try {
ctx = await createDaemonTestContext({
paseoHomeRoot: root,
staticDir: cwd,
cleanup: false,
mcpEnabled: false,
});
const parent = await ctx.client.createAgent({ provider: "claude", cwd, background: true });
await ctx.client.sendMessage(parent.id, "Reply DONE.");
expect((await ctx.client.waitForFinish(parent.id, 5_000)).status).toBe("idle");
await ctx.cleanup();
ctx = undefined;
ctx = await createDaemonTestContext({
paseoHomeRoot: root,
staticDir: cwd,
cleanup: false,
mcpEnabled: false,
});
expect(ctx.daemon.daemon.agentManager.getAgent(parent.id)).toBeNull();
expect(
(await ctx.client.fetchAgents({ filter: { includeBackground: true } })).entries,
).toContainEqual(
expect.objectContaining({ agent: expect.objectContaining({ id: parent.id }) }),
);
if (kind === "agent") {
const child = await ctx.client.createAgent({
provider: "claude",
cwd,
callerAgentId: parent.id,
});
expect(child.workspaceId).toBe(parent.workspaceId);
expect(child.labels[PARENT_AGENT_ID_LABEL]).toBe(parent.id);
} else {
const result = await ctx.client.createWorkspace({
source: { kind: "directory", path: cwd },
callerAgentId: parent.id,
});
expect(result.error).toBeNull();
expect(result.workspace?.background).toBe(true);
}
expect(ctx.daemon.daemon.agentManager.getAgent(parent.id)).toBeNull();
} finally {
await ctx?.cleanup();
await rm(root, { recursive: true, force: true });
}
},
60_000,
);
+15 -17
View File
@@ -119,7 +119,11 @@ import type {
ManagedAgent,
} from "./agent/agent-manager.js";
import { createAgentCommand } from "./agent/create-agent/create.js";
import { resolveCreateAgentIntent, type CreateAgentIntent } from "./agent/create-agent/intent.js";
import {
resolveCreateAgentIntent,
type CreateAgentIntent,
type CreateAgentCaller,
} from "./agent/create-agent/intent.js";
import {
archiveAgentCommand,
cancelAgentRunCommand,
@@ -4586,7 +4590,7 @@ export class Session {
target: worktree,
firstAgentContext,
background: msg.background,
callerWorkspaceId: this.getCallerWorkspaceId(msg.callerAgentId),
callerWorkspaceId: (await this.resolveCreationCaller(msg.callerAgentId))?.workspaceId,
});
createdWorktreeForCleanup = createdWorktree;
const resolvedIntent = await this.resolveSessionCreateAgentIntent({
@@ -4671,20 +4675,13 @@ export class Session {
workspacePromptTitle: string | null;
}): Promise<ResolvedSessionCreateAgentIntent> {
const { request, createdWorktree } = input;
const callerAgent = request.callerAgentId
? this.agentManager.getAgent(request.callerAgentId)
: null;
if (request.callerAgentId && !callerAgent) {
throw new Error(`Caller agent ${request.callerAgentId} not found`);
}
const callerAgent = await this.resolveCreationCaller(request.callerAgentId);
let config = request.config;
const intent = await resolveCreateAgentIntent({
explicitWorkspaceId: createdWorktree?.workspace.workspaceId ?? request.workspaceId,
caller: callerAgent
? { id: callerAgent.id, cwd: callerAgent.cwd, workspaceId: callerAgent.workspaceId }
: null,
caller: callerAgent,
labels: request.labels,
resolveWorkspace: async (workspaceId) => {
if (createdWorktree?.workspace.workspaceId === workspaceId) {
@@ -6964,11 +6961,12 @@ export class Session {
}
}
private getCallerWorkspaceId(callerAgentId?: string): string | undefined {
if (!callerAgentId) return undefined;
const caller = this.agentManager.getAgent(callerAgentId);
private async resolveCreationCaller(callerAgentId?: string): Promise<CreateAgentCaller | null> {
if (!callerAgentId) return null;
const caller =
this.agentManager.getAgent(callerAgentId) ?? (await this.agentStorage.get(callerAgentId));
if (!caller) throw new Error(`Caller agent ${callerAgentId} not found`);
return caller.workspaceId;
return { id: caller.id, cwd: caller.cwd, workspaceId: caller.workspaceId };
}
private async createRequestedWorkspace(
@@ -7018,7 +7016,7 @@ export class Session {
expectsInitialAgent: Boolean(request.firstAgentContext),
workspaceId,
background: request.background,
callerWorkspaceId: this.getCallerWorkspaceId(request.callerAgentId),
callerWorkspaceId: (await this.resolveCreationCaller(request.callerAgentId))?.workspaceId,
},
);
await this.syncWorkspaceGitObserverForWorkspace(workspace);
@@ -7082,7 +7080,7 @@ export class Session {
firstAgentContext: request.firstAgentContext,
title: request.title,
background: request.background,
callerWorkspaceId: this.getCallerWorkspaceId(request.callerAgentId),
callerWorkspaceId: (await this.resolveCreationCaller(request.callerAgentId))?.workspaceId,
},
source.baseBranch
? { resolveDefaultBranch: async () => source.baseBranch as string }