mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-22 17:22:47 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b4d14672c |
@@ -366,6 +366,10 @@ type FetchAgentsResponsePageInfo = FetchAgentsResponsePayload["pageInfo"];
|
|||||||
type AgentUpdatesFilter = FetchAgentsRequestFilter;
|
type AgentUpdatesFilter = FetchAgentsRequestFilter;
|
||||||
type CreateAgentRequestMessage = Extract<SessionInboundMessage, { type: "create_agent_request" }>;
|
type CreateAgentRequestMessage = Extract<SessionInboundMessage, { type: "create_agent_request" }>;
|
||||||
|
|
||||||
|
interface CreatedAgentRequest {
|
||||||
|
agent: AgentSnapshotPayload;
|
||||||
|
}
|
||||||
|
|
||||||
interface ResolvedSessionCreateAgentIntent {
|
interface ResolvedSessionCreateAgentIntent {
|
||||||
config: AgentSessionConfig;
|
config: AgentSessionConfig;
|
||||||
intent: CreateAgentIntent;
|
intent: CreateAgentIntent;
|
||||||
@@ -676,6 +680,10 @@ export class Session {
|
|||||||
private readonly hubExecutionController: HubExecutionController | null;
|
private readonly hubExecutionController: HubExecutionController | null;
|
||||||
private readonly workspaceScripts: WorkspaceScriptsService;
|
private readonly workspaceScripts: WorkspaceScriptsService;
|
||||||
private readonly createAgentLifecycleDispatch: CreateAgentLifecycleDispatch;
|
private readonly createAgentLifecycleDispatch: CreateAgentLifecycleDispatch;
|
||||||
|
private readonly inFlightAgentCreationsByClientMessageId = new Map<
|
||||||
|
string,
|
||||||
|
Promise<CreatedAgentRequest>
|
||||||
|
>();
|
||||||
|
|
||||||
constructor(options: SessionOptions) {
|
constructor(options: SessionOptions) {
|
||||||
const {
|
const {
|
||||||
@@ -3102,10 +3110,86 @@ export class Session {
|
|||||||
* Handle create agent request
|
* Handle create agent request
|
||||||
*/
|
*/
|
||||||
private async handleCreateAgentRequest(msg: CreateAgentRequestMessage): Promise<void> {
|
private async handleCreateAgentRequest(msg: CreateAgentRequestMessage): Promise<void> {
|
||||||
|
const clientMessageId = msg.clientMessageId?.trim();
|
||||||
|
const existingCreation = clientMessageId
|
||||||
|
? this.inFlightAgentCreationsByClientMessageId.get(clientMessageId)
|
||||||
|
: undefined;
|
||||||
|
if (existingCreation) {
|
||||||
|
try {
|
||||||
|
const created = await existingCreation;
|
||||||
|
this.emitCreatedAgentResponse(msg.requestId, created.agent);
|
||||||
|
} catch (error) {
|
||||||
|
this.emitFailedAgentCreateResponse(msg.requestId, error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const creation = this.createAgentForRequest(msg);
|
||||||
|
if (clientMessageId) {
|
||||||
|
this.inFlightAgentCreationsByClientMessageId.set(clientMessageId, creation);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const created = await creation;
|
||||||
|
this.emitCreatedAgentResponse(msg.requestId, created.agent);
|
||||||
|
} catch (error) {
|
||||||
|
const wireError = toWorktreeWireError(error);
|
||||||
|
this.sessionLogger.error({ err: error }, "Failed to create agent");
|
||||||
|
this.emitFailedAgentCreateResponse(msg.requestId, wireError);
|
||||||
|
this.emit({
|
||||||
|
type: "activity_log",
|
||||||
|
payload: {
|
||||||
|
id: uuidv4(),
|
||||||
|
timestamp: new Date(),
|
||||||
|
type: "error",
|
||||||
|
content: `Failed to create agent: ${wireError.message}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
if (
|
||||||
|
clientMessageId &&
|
||||||
|
this.inFlightAgentCreationsByClientMessageId.get(clientMessageId) === creation
|
||||||
|
) {
|
||||||
|
this.inFlightAgentCreationsByClientMessageId.delete(clientMessageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitCreatedAgentResponse(
|
||||||
|
requestId: string | undefined,
|
||||||
|
agent: AgentSnapshotPayload,
|
||||||
|
): void {
|
||||||
|
if (!requestId) return;
|
||||||
|
this.emit({
|
||||||
|
type: "status",
|
||||||
|
payload: {
|
||||||
|
status: "agent_created",
|
||||||
|
agentId: agent.id,
|
||||||
|
requestId,
|
||||||
|
agent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitFailedAgentCreateResponse(requestId: string | undefined, error: unknown): void {
|
||||||
|
if (!requestId) return;
|
||||||
|
const wireError = toWorktreeWireError(error);
|
||||||
|
this.emit({
|
||||||
|
type: "status",
|
||||||
|
payload: {
|
||||||
|
status: "agent_create_failed",
|
||||||
|
requestId,
|
||||||
|
error: wireError.message,
|
||||||
|
errorCode: wireError.code,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createAgentForRequest(
|
||||||
|
msg: CreateAgentRequestMessage,
|
||||||
|
): Promise<CreatedAgentRequest> {
|
||||||
const {
|
const {
|
||||||
config,
|
config,
|
||||||
worktreeName,
|
worktreeName,
|
||||||
requestId,
|
|
||||||
initialPrompt,
|
initialPrompt,
|
||||||
clientMessageId,
|
clientMessageId,
|
||||||
outputSchema,
|
outputSchema,
|
||||||
@@ -3205,50 +3289,19 @@ export class Session {
|
|||||||
agentId: snapshot.id,
|
agentId: snapshot.id,
|
||||||
createdWorktree,
|
createdWorktree,
|
||||||
});
|
});
|
||||||
if (requestId) {
|
const agentPayload = await this.buildAgentPayload(liveSnapshot);
|
||||||
const agentPayload = await this.buildAgentPayload(liveSnapshot);
|
|
||||||
this.emit({
|
|
||||||
type: "status",
|
|
||||||
payload: {
|
|
||||||
status: "agent_created",
|
|
||||||
agentId: liveSnapshot.id,
|
|
||||||
requestId,
|
|
||||||
agent: agentPayload,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sessionLogger.info(
|
this.sessionLogger.info(
|
||||||
{ agentId: snapshot.id, provider: snapshot.provider },
|
{ agentId: snapshot.id, provider: snapshot.provider },
|
||||||
`Created agent ${snapshot.id} (${snapshot.provider})`,
|
`Created agent ${snapshot.id} (${snapshot.provider})`,
|
||||||
);
|
);
|
||||||
|
return { agent: agentPayload };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.createAgentLifecycleDispatch.cleanupCreatedWorktreeAfterFailedAgentCreate({
|
await this.createAgentLifecycleDispatch.cleanupCreatedWorktreeAfterFailedAgentCreate({
|
||||||
createdWorktree: createdWorktreeForCleanup,
|
createdWorktree: createdWorktreeForCleanup,
|
||||||
createdAgentId,
|
createdAgentId,
|
||||||
});
|
});
|
||||||
const wireError = toWorktreeWireError(error);
|
throw error;
|
||||||
this.sessionLogger.error({ err: error }, "Failed to create agent");
|
|
||||||
if (requestId) {
|
|
||||||
this.emit({
|
|
||||||
type: "status",
|
|
||||||
payload: {
|
|
||||||
status: "agent_create_failed",
|
|
||||||
requestId,
|
|
||||||
error: wireError.message,
|
|
||||||
errorCode: wireError.code,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.emit({
|
|
||||||
type: "activity_log",
|
|
||||||
payload: {
|
|
||||||
id: uuidv4(),
|
|
||||||
timestamp: new Date(),
|
|
||||||
type: "error",
|
|
||||||
content: `Failed to create agent: ${wireError.message}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -872,7 +872,7 @@ test("client heartbeat clears attention for the focused terminal", async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create_agent_request keeps requested child cwd when grouped under an existing parent workspace", async () => {
|
test("create_agent_request deduplicates concurrent draft submission retries", async () => {
|
||||||
const workdir = mkdtempSync(path.join(tmpdir(), "paseo-create-agent-cwd-"));
|
const workdir = mkdtempSync(path.join(tmpdir(), "paseo-create-agent-cwd-"));
|
||||||
try {
|
try {
|
||||||
const parent = path.join(workdir, "parent");
|
const parent = path.join(workdir, "parent");
|
||||||
@@ -981,14 +981,26 @@ test("create_agent_request keeps requested child cwd when grouped under an exist
|
|||||||
terminalManager: null,
|
terminalManager: null,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
await session.handleMessage({
|
await Promise.all([
|
||||||
type: "create_agent_request",
|
session.handleMessage({
|
||||||
requestId: "req-create-child",
|
type: "create_agent_request",
|
||||||
config: { provider: "codex", cwd: child },
|
requestId: "req-create-child-1",
|
||||||
attachments: [],
|
clientMessageId: "draft-submission-1",
|
||||||
});
|
config: { provider: "codex", cwd: child },
|
||||||
|
attachments: [],
|
||||||
|
}),
|
||||||
|
session.handleMessage({
|
||||||
|
type: "create_agent_request",
|
||||||
|
requestId: "req-create-child-2",
|
||||||
|
clientMessageId: "draft-submission-1",
|
||||||
|
config: { provider: "codex", cwd: child },
|
||||||
|
attachments: [],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
const [createdAgent] = agentManager.listAgents();
|
const createdAgents = agentManager.listAgents();
|
||||||
|
expect(createdAgents).toHaveLength(1);
|
||||||
|
const [createdAgent] = createdAgents;
|
||||||
expect(createdAgent?.cwd).toBe(child);
|
expect(createdAgent?.cwd).toBe(child);
|
||||||
const createdWorkspace = await workspaceRegistry.get(createdAgent!.workspaceId!);
|
const createdWorkspace = await workspaceRegistry.get(createdAgent!.workspaceId!);
|
||||||
expect(createdWorkspace).not.toBeNull();
|
expect(createdWorkspace).not.toBeNull();
|
||||||
@@ -1007,10 +1019,22 @@ test("create_agent_request keeps requested child cwd when grouped under an exist
|
|||||||
projectKey: createdWorkspace!.projectId,
|
projectKey: createdWorkspace!.projectId,
|
||||||
checkout: { cwd: child },
|
checkout: { cwd: child },
|
||||||
});
|
});
|
||||||
expect(findByType(emitted, "status")?.payload).toMatchObject({
|
expect(
|
||||||
status: "agent_created",
|
emitted.filter((message) => message.type === "status").map((message) => message.payload),
|
||||||
agent: { cwd: child },
|
).toEqual(
|
||||||
});
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
status: "agent_created",
|
||||||
|
requestId: "req-create-child-1",
|
||||||
|
agent: expect.objectContaining({ cwd: child }),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
status: "agent_created",
|
||||||
|
requestId: "req-create-child-2",
|
||||||
|
agent: expect.objectContaining({ cwd: child }),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
rmSync(workdir, { recursive: true, force: true });
|
rmSync(workdir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user