mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 02:15:02 +00:00
Cover child completion before create_agent returns (#6008)
Adds a regression test for #5928: a child that finishes its initial turn before its turn start returns still sends one finish notification to its parent. The fix itself is on main from 0009a7e.
Co-authored-by: Ahmet İzgi <ahmetizgi@gmail.com>
This commit is contained in:
co-authored by
Ahmet İzgi
parent
12aaded6c5
commit
b1a638482d
@@ -3296,6 +3296,50 @@ describe("create_agent MCP tool", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("notifies the caller when a created child finishes its initial turn before the turn start returns", async () => {
|
||||
const workdir = await mkdtemp(join(tmpdir(), "mcp-create-fast-child-"));
|
||||
const storage = new AgentStorage(join(workdir, "agents"), logger);
|
||||
const parentClient = new HeldTurnAgentClient("claude", false);
|
||||
const childClient = new HeldTurnAgentClient("codex", false, { finishTurnsDuringStart: true });
|
||||
const agentManager = new AgentManager({
|
||||
clients: { claude: parentClient, codex: childClient },
|
||||
registry: storage,
|
||||
logger,
|
||||
});
|
||||
|
||||
try {
|
||||
const parent = await agentManager.createAgent(
|
||||
{ provider: "claude", cwd: existingCwd },
|
||||
undefined,
|
||||
{ workspaceId: "wks_parent" },
|
||||
);
|
||||
const server = await createAgentMcpServer({
|
||||
agentManager,
|
||||
agentStorage: storage,
|
||||
callerAgentId: parent.id,
|
||||
providerSnapshotManager: createOpenCodeManager().manager,
|
||||
logger,
|
||||
});
|
||||
|
||||
const response = await invokeToolWithParsedInput(registeredTool(server, "create_agent"), {
|
||||
...subagentCurrentWorkspace(),
|
||||
title: "Fast Child",
|
||||
provider: "codex/gpt-5.4",
|
||||
initialPrompt: "Finish immediately",
|
||||
});
|
||||
const childId = z.object({ agentId: z.string() }).parse(response.structuredContent).agentId;
|
||||
|
||||
await vi.waitFor(() => {
|
||||
const parentPrompts = parentClient.sessions[0]!.prompts;
|
||||
expect(parentPrompts).toHaveLength(1);
|
||||
expect(parentPrompts[0]).toContain(childId);
|
||||
expect(parentPrompts[0]).toContain("finished");
|
||||
});
|
||||
} finally {
|
||||
await removeAgentStateDir(agentManager, storage, workdir);
|
||||
}
|
||||
});
|
||||
|
||||
it("creates detached caller agents without a parent label", async () => {
|
||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||
spies.agentManager.getAgent.mockReturnValue({
|
||||
@@ -3770,6 +3814,11 @@ const HELD_TURN_CAPABILITIES = {
|
||||
supportsToolInvocations: false,
|
||||
} as const;
|
||||
|
||||
interface HeldTurnOptions {
|
||||
/** Run the whole turn before the turn-start request returns. */
|
||||
finishTurnsDuringStart?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider session that records every prompt it receives. With `holdTurns`, a started
|
||||
* turn stays running until `finishTurn()` so a caller's bounded wait can run out first.
|
||||
@@ -3786,6 +3835,7 @@ class HeldTurnAgentSession implements AgentSession {
|
||||
constructor(
|
||||
readonly provider: AgentProvider,
|
||||
private readonly holdTurns: boolean,
|
||||
private readonly options: HeldTurnOptions = {},
|
||||
) {}
|
||||
|
||||
/** Hold the next turn's acknowledgment, as a provider awaiting its turn-start request does. */
|
||||
@@ -3819,6 +3869,13 @@ class HeldTurnAgentSession implements AgentSession {
|
||||
throw error;
|
||||
}
|
||||
const turnId = randomUUID();
|
||||
if (this.options.finishTurnsDuringStart) {
|
||||
// The whole turn runs before the turn-start request returns, as a provider whose
|
||||
// start acknowledgment arrives together with the turn's completion does.
|
||||
this.pushEvent({ type: "turn_started", provider: this.provider, turnId });
|
||||
this.pushEvent({ type: "turn_completed", provider: this.provider, turnId });
|
||||
return { turnId };
|
||||
}
|
||||
this.activeTurnId = turnId;
|
||||
this.pushEvent({
|
||||
type: "timeline",
|
||||
@@ -3926,6 +3983,7 @@ class HeldTurnAgentClient implements AgentClient {
|
||||
constructor(
|
||||
readonly provider: AgentProvider,
|
||||
private readonly holdTurns: boolean,
|
||||
private readonly options: HeldTurnOptions = {},
|
||||
) {}
|
||||
|
||||
async isAvailable(): Promise<boolean> {
|
||||
@@ -3933,7 +3991,7 @@ class HeldTurnAgentClient implements AgentClient {
|
||||
}
|
||||
|
||||
async createSession(): Promise<AgentSession> {
|
||||
const session = new HeldTurnAgentSession(this.provider, this.holdTurns);
|
||||
const session = new HeldTurnAgentSession(this.provider, this.holdTurns, this.options);
|
||||
this.sessions.push(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user