Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/agent-orchestrator/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,42 @@ describe("InvokeServer", () => {

await server.close();
});

/**
* `buildGraphInput` awaits `sessionStore.get` before the graph ever runs
* (see server.ts) -- a Redis blip there rejects that promise before the
* `this.graph.invoke(...).catch(...)` chain even exists to catch it. Left
* unhandled, that crashes the process, wiping every other in-flight
* invocation and turning their polls into "poll failed: 404" for the
* caller. This asserts the record still resolves to "failed" instead.
*/
it("marks the invocation failed (not an unhandled rejection) when buildGraphInput itself rejects", async () => {
const graph: AgentGraphLike = { invoke: vi.fn(), stream: vi.fn() };
const failingSessionStore = {
get: vi.fn().mockRejectedValue(new Error("redis: connection reset")),
set: vi.fn(),
};
const server = new InvokeServer(graph, failingSessionStore as unknown as InMemorySessionStore);
const port = await listenOn(server);

const postRes = await fetch(`http://127.0.0.1:${port}/invoke`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ request: "do a thing", session_id: "session-1" }),
});
const { id } = (await postRes.json()) as { id: string };

await new Promise((r) => setTimeout(r, 10));
const res = await fetch(`http://127.0.0.1:${port}/invoke/${id}`);
expect(res.status).toBe(200);
expect((await res.json()) as { status: string; error: string }).toMatchObject({
status: "failed",
error: "redis: connection reset",
});
expect(graph.invoke).not.toHaveBeenCalled();

await server.close();
});
});

describe("InvokeServer /invoke event field -> IntegrationRoute dispatch (ADR 0024)", () => {
Expand Down
11 changes: 11 additions & 0 deletions apps/agent-orchestrator/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,17 @@ export class InvokeServer {
...(remoteControlUrl ? { remoteControlUrl } : {}),
});
});
}).catch((err: unknown) => {
// `buildGraphInput` itself can reject (e.g. `sessionStore.get` hitting
// Redis) before the graph ever runs. Without this, that rejection is
// unhandled -- Node terminates the process, wiping the in-memory
// `invocations` Map and turning every in-flight poll (not just this
// one) into a 404 the caller reports as "poll failed: 404".
this.invocations.set(id, {
id,
status: "failed",
error: err instanceof Error ? err.message : String(err),
});
});

res.writeHead(202, { "content-type": "application/json", location: `/invoke/${id}` }).end(
Expand Down