diff --git a/packages/agent/src/server/agent-server.test.ts b/packages/agent/src/server/agent-server.test.ts index 99e0c67a9d..447ed9b43e 100644 --- a/packages/agent/src/server/agent-server.test.ts +++ b/packages/agent/src/server/agent-server.test.ts @@ -1905,6 +1905,46 @@ describe("AgentServer HTTP Mode", () => { }); describe("POST /command", () => { + it("acknowledges an asynchronous user message before its turn completes", async () => { + const s = createServer(); + await s.start(); + let resolvePrompt!: (value: { stopReason: string }) => void; + const promptPending = new Promise<{ stopReason: string }>((resolve) => { + resolvePrompt = resolve; + }); + const serverInternals = s as unknown as { + session: { + clientConnection: { prompt: () => Promise<{ stopReason: string }> }; + }; + }; + serverInternals.session.clientConnection.prompt = () => promptPending; + + const response = await fetch(`http://localhost:${port}/command`, { + method: "POST", + headers: { + Authorization: `Bearer ${createToken({ run_id: "test-run-id" })}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "async-message", + method: "user_message", + params: { + content: "do the thing", + messageId: "message-1", + waitForCompletion: false, + }, + }), + }); + + expect(await response.json()).toEqual({ + jsonrpc: "2.0", + id: "async-message", + result: { accepted: true }, + }); + resolvePrompt({ stopReason: "end_turn" }); + }); + it("returns 401 without authorization", async () => { await createServer().start(); diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index 3a1853f7b9..5f5b2fe6fe 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -712,10 +712,28 @@ export class AgentServer { } try { - const result = await this.executeCommand( + const commandPromise = this.executeCommand( command.method, (command.params as Record) || {}, ); + if ( + (command.method === POSTHOG_NOTIFICATIONS.USER_MESSAGE || + command.method === "user_message") && + command.params?.waitForCompletion === false + ) { + void commandPromise.catch((error) => + this.logger.error("Asynchronous user_message command failed", { + error, + messageId: command.params?.messageId, + }), + ); + return c.json({ + jsonrpc: "2.0", + id: command.id, + result: { accepted: true }, + }); + } + const result = await commandPromise; return c.json({ jsonrpc: "2.0", id: command.id, diff --git a/packages/agent/src/server/schemas.ts b/packages/agent/src/server/schemas.ts index 56fa3b608b..596d39f692 100644 --- a/packages/agent/src/server/schemas.ts +++ b/packages/agent/src/server/schemas.ts @@ -69,6 +69,7 @@ export const userMessageParamsSchema = z artifacts: z.array(z.record(z.string(), z.unknown())).optional(), messageId: z.string().min(1).optional(), steer: z.boolean().optional(), + waitForCompletion: z.boolean().optional(), }) .refine( (params) => {