Show agent history errors without a one-minute wait (#2124)

* fix(client): fail invalid RPC responses immediately

Correlated responses that failed schema validation were discarded before request matching, leaving callers blocked until the RPC timeout. Preserve only the raw correlation identity at the boundary so the matching request fails with a protocol error.

* test(client): preserve invalid response diagnostics

* fix(client): ignore invalid correlated progress events
This commit is contained in:
Mohamed Boudra
2026-07-16 16:44:01 +02:00
committed by GitHub
parent e528a0db06
commit d42ab91971
2 changed files with 145 additions and 11 deletions
+58 -8
View File
@@ -4947,9 +4947,13 @@ test("parses canonical fetch_agent_timeline_response payloads without crashing",
expect(logger.warn).not.toHaveBeenCalled();
});
test("drops invalid fetch_agent_timeline_response tool_call payloads and logs validation warning", async () => {
const logger = createMockLogger();
test("rejects and logs a correlated response that violates the protocol schema", async () => {
const mock = createMockTransport();
const warnings: string[] = [];
const logger: Logger = {
...noopLogger,
warn: (_fields, message) => warnings.push(message ?? ""),
};
const client = new DaemonClient({
url: "ws://test",
@@ -4964,9 +4968,9 @@ test("drops invalid fetch_agent_timeline_response tool_call payloads and logs va
mock.triggerOpen();
await connectPromise;
const received: unknown[] = [];
const unsubscribe = client.on("fetch_agent_timeline_response", (msg) => {
received.push(msg);
const response = client.fetchAgentTimeline("agent_cli", {
requestId: "req-invalid",
timeout: 1,
});
mock.triggerMessage(
@@ -5013,10 +5017,56 @@ test("drops invalid fetch_agent_timeline_response tool_call payloads and logs va
}),
);
unsubscribe();
await expect(response).rejects.toMatchObject({
requestId: "req-invalid",
message: expect.stringMatching(/validation/i),
});
expect(warnings).toEqual(["Message validation failed"]);
});
expect(received).toHaveLength(0);
expect(logger.warn).toHaveBeenCalled();
test("does not reject a request for an invalid correlated progress event", async () => {
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_unit_test",
logger: noopLogger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen();
await connectPromise;
const response = client.updateDaemon("req-update");
mock.triggerMessage(
wrapSessionMessage({
type: "daemon.update.progress",
payload: {
requestId: "req-update",
phase: "verifying",
},
}),
);
mock.triggerMessage(
wrapSessionMessage({
type: "daemon.update.response",
payload: {
requestId: "req-update",
success: true,
error: null,
previousVersion: "0.1.106",
newVersion: "0.1.107",
},
}),
);
await expect(response).resolves.toMatchObject({
requestId: "req-update",
success: true,
});
});
test("sends subscribe/unsubscribe terminals messages", async () => {