refactor(server): parse daemon/ws/wire test responses with Zod (T1.c typeaware sweep) (#750)

Fixes all reachable typescript-eslint(no-unsafe-type-assertion) errors
in the four in-scope test files by:
- Adding Zod schemas (WireEnvelopeSchema, BinaryFrameSchema,
  RuntimeMetricsLogSchema) and helpers (parseSentEnvelope, parseSentFrame,
  assertStr) to parse wire frames instead of casting JSON.parse() results
- Replacing `JSON.parse(x) as { ... }` with Zod-validated helpers
- Replacing `mock.sent[N] as string` / `String(mock.sent[N])` with assertStr()
- Replacing `frame as Uint8Array` with direct type annotation (Buffer extends Uint8Array)
- Replacing `session.args.onMessage as (() => void) | undefined` with typeof
  function guard and direct invocation
- Replacing `metricsCall![0] as RuntimeMetricsLog` with RuntimeMetricsLogSchema.parse()

Remaining `as unknown as SomeClass` patterns in constructor mocks and
`client as unknown as { ... }` internal-access patterns are out of scope:
they require extracting interfaces from concrete classes (AgentManager,
AgentStorage, FileBackedChatService, etc.) with private members, or
refactoring the client API to expose internal methods — neither is safe
to do in a test-only sweep.
This commit is contained in:
Mohamed Boudra
2026-05-05 19:58:56 +07:00
parent 006a79ed01
commit b1d3867da9
4 changed files with 238 additions and 424 deletions
+179 -376
View File
@@ -1,4 +1,5 @@
import { afterEach, expect, expectTypeOf, test, vi } from "vitest";
import { z } from "zod";
import { DaemonClient, type DaemonTransport } from "./daemon-client";
import { encodeFileTransferFrame, FileTransferOpcode } from "../shared/binary-frames/index.js";
import {
@@ -94,6 +95,22 @@ function wrapSessionMessage(message: unknown): string {
});
}
function assertStr(data: string | Uint8Array | ArrayBuffer | undefined): string {
if (typeof data !== "string") throw new Error("Expected string frame");
return data;
}
function parseSentFrame(
data: string | Uint8Array | ArrayBuffer | undefined,
): Record<string, unknown> {
return z
.object({
type: z.literal("session"),
message: z.record(z.unknown()),
})
.parse(JSON.parse(assertStr(data))).message;
}
const clients: DaemonClient[] = [];
afterEach(async () => {
@@ -123,10 +140,7 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: { type: "checkout_status_request"; cwd: string; requestId: string };
};
const request = parseSentFrame(mock.sent[0]);
const response = {
type: "session",
@@ -135,7 +149,7 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
payload: {
cwd: "/tmp/project",
error: null,
requestId: request.message.requestId,
requestId: request.requestId,
isGit: false,
isPaseoOwnedWorktree: false,
repoRoot: null,
@@ -155,12 +169,12 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
const [r1, r2] = await Promise.all([p1, p2]);
expect(r1).toMatchObject({
cwd: "/tmp/project",
requestId: request.message.requestId,
requestId: request.requestId,
isGit: false,
});
expect(r2).toMatchObject({
cwd: "/tmp/project",
requestId: request.message.requestId,
requestId: request.requestId,
isGit: false,
});
@@ -168,24 +182,21 @@ test("dedupes in-flight checkout status requests per agentId", async () => {
const p3 = client.getCheckoutStatus("/tmp/project");
expect(mock.sent).toHaveLength(2);
const request2 = JSON.parse(mock.sent[1]) as {
type: "session";
message: { type: "checkout_status_request"; cwd: string; requestId: string };
};
const request2 = parseSentFrame(mock.sent[1]);
mock.triggerMessage(
JSON.stringify({
...response,
message: {
...response.message,
payload: { ...response.message.payload, requestId: request2.message.requestId },
payload: { ...response.message.payload, requestId: request2.requestId },
},
}),
);
await expect(p3).resolves.toMatchObject({
cwd: "/tmp/project",
requestId: request2.message.requestId,
requestId: request2.requestId,
isGit: false,
});
});
@@ -234,7 +245,7 @@ test("advertises reasoning_merge_enum in hello", async () => {
await connectPromise;
expect(mock.sent).toHaveLength(1);
expect(JSON.parse(mock.sent[0] as string)).toEqual({
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
type: "hello",
clientId: "clsk_unit_test",
clientType: "cli",
@@ -289,7 +300,7 @@ test("listDirectory sends a list file explorer request and returns directory ent
const responsePromise = client.listDirectory("/tmp/project", "src", "req-list");
expect(JSON.parse(String(mock.sent[0]))).toEqual({
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
type: "session",
message: {
type: "file_explorer_request",
@@ -359,7 +370,7 @@ test("readFile hides legacy base64 behind bytes", async () => {
const responsePromise = client.readFile("/tmp/project", "logo.png", "req-file");
expect(JSON.parse(String(mock.sent[0]))).toEqual({
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
type: "session",
message: {
type: "file_explorer_request",
@@ -424,7 +435,7 @@ test("readFile resolves from binary file frames when the daemon supports them",
const responsePromise = client.readFile("/tmp/project", "logo.png", "req-binary");
expect(JSON.parse(String(mock.sent[0]))).toEqual({
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
type: "session",
message: {
type: "file_explorer_request",
@@ -575,15 +586,8 @@ test("sends create_agent_request with string workspace ids", async () => {
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "create_agent_request";
requestId: string;
workspaceId: string;
};
};
expect(request.message).toEqual(
const request = parseSentFrame(mock.sent[0]);
expect(request).toEqual(
expect.objectContaining({
type: "create_agent_request",
workspaceId: "ws-feature-a",
@@ -595,7 +599,7 @@ test("sends create_agent_request with string workspace ids", async () => {
type: "status",
payload: {
status: "agent_create_failed",
requestId: request.message.requestId,
requestId: request.requestId,
error: "compat test sentinel",
},
}),
@@ -639,15 +643,8 @@ test("sends structured attachments with create_agent_request", async () => {
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "create_agent_request";
requestId: string;
attachments: Array<{ type: string; mimeType: string; number: number }>;
};
};
expect(request.message.attachments).toEqual([
const request = parseSentFrame(mock.sent[0]);
expect(request.attachments).toEqual([
{
type: "github_pr",
mimeType: "application/github-pr",
@@ -664,7 +661,7 @@ test("sends structured attachments with create_agent_request", async () => {
type: "status",
payload: {
status: "agent_create_failed",
requestId: request.message.requestId,
requestId: request.requestId,
error: "attachment test sentinel",
},
}),
@@ -704,21 +701,8 @@ test("sends worktree base-ref fields in create_agent_request git options", async
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "create_agent_request";
requestId: string;
git: {
createWorktree: boolean;
worktreeSlug: string;
refName: string;
action: string;
githubPrNumber: number;
};
};
};
expect(request.message.git).toEqual({
const request = parseSentFrame(mock.sent[0]);
expect(request.git).toEqual({
createWorktree: true,
worktreeSlug: "review-pr-123",
refName: "feature/worktree-base-ref",
@@ -731,7 +715,7 @@ test("sends worktree base-ref fields in create_agent_request git options", async
type: "status",
payload: {
status: "agent_create_failed",
requestId: request.message.requestId,
requestId: request.requestId,
error: "git ref fields sentinel",
},
}),
@@ -767,7 +751,7 @@ test("omitting create_agent_request worktree base-ref fields preserves legacy wi
},
});
expect(String(mock.sent[0])).toBe(
expect(assertStr(mock.sent[0])).toBe(
JSON.stringify({
type: "session",
message: {
@@ -834,17 +818,11 @@ test("sends structured first-agent context attachments with create_paseo_worktre
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "create_paseo_worktree_request";
requestId: string;
firstAgentContext: {
attachments: Array<{ type: string; mimeType: string; number: number }>;
};
};
};
expect(request.message.firstAgentContext.attachments).toEqual([
const request = parseSentFrame(mock.sent[0]);
const firstAgentContext = z
.object({ attachments: z.array(z.unknown()) })
.parse(request.firstAgentContext);
expect(firstAgentContext.attachments).toEqual([
{
type: "github_pr",
mimeType: "application/github-pr",
@@ -858,7 +836,7 @@ test("sends structured first-agent context attachments with create_paseo_worktre
wrapSessionMessage({
type: "create_paseo_worktree_response",
payload: {
requestId: request.message.requestId,
requestId: request.requestId,
workspace: null,
error: "worktree attachment sentinel",
setupTerminalId: null,
@@ -867,7 +845,7 @@ test("sends structured first-agent context attachments with create_paseo_worktre
);
await expect(createPromise).resolves.toEqual({
requestId: request.message.requestId,
requestId: request.requestId,
workspace: null,
error: "worktree attachment sentinel",
setupTerminalId: null,
@@ -903,19 +881,8 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async ()
);
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "create_paseo_worktree_request";
requestId: string;
cwd: string;
worktreeSlug: string;
refName: string;
action: string;
githubPrNumber: number;
};
};
expect(request.message).toEqual({
const request = parseSentFrame(mock.sent[0]);
expect(request).toEqual({
type: "create_paseo_worktree_request",
cwd: "/tmp/project",
worktreeSlug: "review-pr-123",
@@ -929,7 +896,7 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async ()
wrapSessionMessage({
type: "create_paseo_worktree_response",
payload: {
requestId: request.message.requestId,
requestId: request.requestId,
workspace: null,
error: "worktree ref fields sentinel",
setupTerminalId: null,
@@ -938,7 +905,7 @@ test("sends worktree base-ref fields in create_paseo_worktree_request", async ()
);
await expect(createPromise).resolves.toEqual({
requestId: request.message.requestId,
requestId: request.requestId,
workspace: null,
error: "worktree ref fields sentinel",
setupTerminalId: null,
@@ -970,7 +937,7 @@ test("omitting create_paseo_worktree_request worktree base-ref fields preserves
"req-worktree-legacy",
);
expect(String(mock.sent[0])).toBe(
expect(assertStr(mock.sent[0])).toBe(
JSON.stringify({
type: "session",
message: {
@@ -1031,14 +998,8 @@ test("sends explicit shutdown_server_request via shutdownServer", async () => {
const promise = lifecycleClient.shutdownServer("req-shutdown-1");
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: string;
requestId: string;
};
};
expect(request.message).toEqual({
const request = parseSentFrame(mock.sent[0]);
expect(request).toEqual({
type: "shutdown_server_request",
requestId: "req-shutdown-1",
});
@@ -1081,15 +1042,8 @@ test("restartServer remains restart-only and sends restart_server_request", asyn
const promise = client.restartServer("settings_update", "req-restart-1");
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: string;
reason?: string;
requestId: string;
};
};
expect(request.message).toEqual({
const request = parseSentFrame(mock.sent[0]);
expect(request).toEqual({
type: "restart_server_request",
reason: "settings_update",
requestId: "req-restart-1",
@@ -1142,7 +1096,9 @@ test("transitions out of connecting when connect timeout elapses", async () => {
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toBeInstanceOf(Error);
expect((result.error as Error).message).toContain("Connection timed out");
if (result.error instanceof Error) {
expect(result.error.message).toContain("Connection timed out");
}
}
expect(client.getConnectionState().status).toBe("disconnected");
} finally {
@@ -1234,12 +1190,14 @@ test("logs configured runtime generation in connection transition events", async
mock.triggerOpen();
await connectPromise;
const transitionPayloads = logger.debug.mock.calls
.filter(([, message]) => message === "DaemonClientTransition")
.map(([payload]) => payload as { generation?: number | null });
const transitionPayloads = logger.debug.mock.calls.filter(
([, message]) => message === "DaemonClientTransition",
);
expect(transitionPayloads.length).toBeGreaterThan(0);
for (const payload of transitionPayloads) {
expect(payload.generation).toBe(7);
for (const [payload] of transitionPayloads) {
expect(
z.object({ generation: z.number().nullable().optional() }).parse(payload).generation,
).toBe(7);
}
});
@@ -1267,20 +1225,11 @@ test("subscribes to checkout diff updates via RPC handshake", async () => {
);
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "subscribe_checkout_diff_request";
subscriptionId: string;
cwd: string;
compare: { mode: "uncommitted" | "base"; baseRef?: string };
requestId: string;
};
};
expect(request.message.type).toBe("subscribe_checkout_diff_request");
expect(request.message.subscriptionId).toBe("checkout-sub-1");
expect(request.message.cwd).toBe("/tmp/project");
expect(request.message.compare).toEqual({ mode: "uncommitted" });
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("subscribe_checkout_diff_request");
expect(request.subscriptionId).toBe("checkout-sub-1");
expect(request.cwd).toBe("/tmp/project");
expect(request.compare).toEqual({ mode: "uncommitted" });
mock.triggerMessage(
JSON.stringify({
@@ -1292,7 +1241,7 @@ test("subscribes to checkout diff updates via RPC handshake", async () => {
cwd: "/tmp/project",
files: [],
error: null,
requestId: request.message.requestId,
requestId: request.requestId,
},
},
}),
@@ -1303,7 +1252,7 @@ test("subscribes to checkout diff updates via RPC handshake", async () => {
cwd: "/tmp/project",
files: [],
error: null,
requestId: request.message.requestId,
requestId: request.requestId,
});
});
@@ -1327,19 +1276,10 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => {
const promise = client.getCheckoutDiff("/tmp/project", { mode: "base", baseRef: "main" });
expect(mock.sent).toHaveLength(1);
const subscribeRequest = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "subscribe_checkout_diff_request";
subscriptionId: string;
cwd: string;
compare: { mode: "uncommitted" | "base"; baseRef?: string };
requestId: string;
};
};
expect(subscribeRequest.message.type).toBe("subscribe_checkout_diff_request");
expect(subscribeRequest.message.cwd).toBe("/tmp/project");
expect(subscribeRequest.message.compare).toEqual({
const subscribeRequest = parseSentFrame(mock.sent[0]);
expect(subscribeRequest.type).toBe("subscribe_checkout_diff_request");
expect(subscribeRequest.cwd).toBe("/tmp/project");
expect(subscribeRequest.compare).toEqual({
mode: "base",
baseRef: "main",
});
@@ -1350,11 +1290,11 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => {
message: {
type: "subscribe_checkout_diff_response",
payload: {
subscriptionId: subscribeRequest.message.subscriptionId,
subscriptionId: subscribeRequest.subscriptionId,
cwd: "/tmp/project",
files: [],
error: null,
requestId: subscribeRequest.message.requestId,
requestId: subscribeRequest.requestId,
},
},
}),
@@ -1364,19 +1304,13 @@ test("getCheckoutDiff uses one-shot subscription protocol", async () => {
cwd: "/tmp/project",
files: [],
error: null,
requestId: subscribeRequest.message.requestId,
requestId: subscribeRequest.requestId,
});
expect(mock.sent).toHaveLength(2);
const unsubscribeRequest = JSON.parse(mock.sent[1]) as {
type: "session";
message: {
type: "unsubscribe_checkout_diff_request";
subscriptionId: string;
};
};
expect(unsubscribeRequest.message.type).toBe("unsubscribe_checkout_diff_request");
expect(unsubscribeRequest.message.subscriptionId).toBe(subscribeRequest.message.subscriptionId);
const unsubscribeRequest = parseSentFrame(mock.sent[1]);
expect(unsubscribeRequest.type).toBe("unsubscribe_checkout_diff_request");
expect(unsubscribeRequest.subscriptionId).toBe(subscribeRequest.subscriptionId);
});
test("requests branch suggestions via RPC", async () => {
@@ -1402,21 +1336,12 @@ test("requests branch suggestions via RPC", async () => {
);
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "branch_suggestions_request";
cwd: string;
query?: string;
limit?: number;
requestId: string;
};
};
expect(request.message.type).toBe("branch_suggestions_request");
expect(request.message.cwd).toBe("/tmp/project");
expect(request.message.query).toBe("mai");
expect(request.message.limit).toBe(5);
expect(request.message.requestId).toBe("req-branches");
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("branch_suggestions_request");
expect(request.cwd).toBe("/tmp/project");
expect(request.query).toBe("mai");
expect(request.limit).toBe(5);
expect(request.requestId).toBe("req-branches");
mock.triggerMessage(
JSON.stringify({
@@ -1458,11 +1383,8 @@ test("reads project config via correlated RPC", async () => {
const promise = client.readProjectConfig("/repo/app", "read-project-config-1");
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: { type: "read_project_config_request"; requestId: string; repoRoot: string };
};
expect(request.message).toEqual({
const request = parseSentFrame(mock.sent[0]);
expect(request).toEqual({
type: "read_project_config_request",
requestId: "read-project-config-1",
repoRoot: "/repo/app",
@@ -1514,17 +1436,8 @@ test("writes project config via correlated RPC and returns inline failures", asy
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "write_project_config_request";
requestId: string;
repoRoot: string;
config: unknown;
expectedRevision: unknown;
};
};
expect(request.message).toEqual({
const request = parseSentFrame(mock.sent[0]);
expect(request).toEqual({
type: "write_project_config_request",
requestId: "write-project-config-1",
repoRoot: "/repo/app",
@@ -1587,25 +1500,14 @@ test("requests directory suggestions via RPC", async () => {
);
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "directory_suggestions_request";
query: string;
cwd?: string;
includeFiles?: boolean;
includeDirectories?: boolean;
limit?: number;
requestId: string;
};
};
expect(request.message.type).toBe("directory_suggestions_request");
expect(request.message.query).toBe("proj");
expect(request.message.cwd).toBe("/tmp/project");
expect(request.message.includeFiles).toBe(true);
expect(request.message.includeDirectories).toBe(true);
expect(request.message.limit).toBe(10);
expect(request.message.requestId).toBe("req-directories");
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("directory_suggestions_request");
expect(request.query).toBe("proj");
expect(request.cwd).toBe("/tmp/project");
expect(request.includeFiles).toBe(true);
expect(request.includeDirectories).toBe(true);
expect(request.limit).toBe(10);
expect(request.requestId).toBe("req-directories");
mock.triggerMessage(
JSON.stringify({
@@ -1654,21 +1556,12 @@ test("requests checkout merge from base via RPC", async () => {
);
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "checkout_merge_from_base_request";
cwd: string;
baseRef?: string;
requireCleanTarget?: boolean;
requestId: string;
};
};
expect(request.message.type).toBe("checkout_merge_from_base_request");
expect(request.message.cwd).toBe("/tmp/project");
expect(request.message.baseRef).toBe("main");
expect(request.message.requireCleanTarget).toBe(true);
expect(request.message.requestId).toBe("req-merge-from-base");
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("checkout_merge_from_base_request");
expect(request.cwd).toBe("/tmp/project");
expect(request.baseRef).toBe("main");
expect(request.requireCleanTarget).toBe(true);
expect(request.requestId).toBe("req-merge-from-base");
mock.triggerMessage(
JSON.stringify({
@@ -1713,17 +1606,10 @@ test("requests checkout pull via RPC", async () => {
const promise = client.checkoutPull("/tmp/project", "req-pull");
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "checkout_pull_request";
cwd: string;
requestId: string;
};
};
expect(request.message.type).toBe("checkout_pull_request");
expect(request.message.cwd).toBe("/tmp/project");
expect(request.message.requestId).toBe("req-pull");
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("checkout_pull_request");
expect(request.cwd).toBe("/tmp/project");
expect(request.requestId).toBe("req-pull");
mock.triggerMessage(
JSON.stringify({
@@ -1777,22 +1663,13 @@ test("resubscribes checkout diff streams after reconnect", async () => {
await connectPromise;
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "subscribe_checkout_diff_request";
subscriptionId: string;
cwd: string;
compare: { mode: "uncommitted" | "base"; baseRef?: string };
requestId: string;
};
};
expect(request.message.type).toBe("subscribe_checkout_diff_request");
expect(request.message.subscriptionId).toBe("checkout-sub-1");
expect(request.message.cwd).toBe("/tmp/project");
expect(request.message.compare).toEqual({ mode: "base", baseRef: "main" });
expect(typeof request.message.requestId).toBe("string");
expect(request.message.requestId.length).toBeGreaterThan(0);
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("subscribe_checkout_diff_request");
expect(request.subscriptionId).toBe("checkout-sub-1");
expect(request.cwd).toBe("/tmp/project");
expect(request.compare).toEqual({ mode: "base", baseRef: "main" });
expect(typeof request.requestId).toBe("string");
expect(z.string().parse(request.requestId).length).toBeGreaterThan(0);
});
test("fetches agents via RPC with filters, sort, and pagination", async () => {
@@ -1823,27 +1700,14 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => {
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "fetch_agents_request";
requestId: string;
filter?: { labels?: Record<string, string> };
sort?: Array<{
key: "status_priority" | "created_at" | "updated_at" | "title";
direction: "asc" | "desc";
}>;
page?: { limit: number; cursor?: string };
subscribe?: { subscriptionId?: string };
};
};
expect(request.message.type).toBe("fetch_agents_request");
expect(request.message.sort).toEqual([
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("fetch_agents_request");
expect(request.sort).toEqual([
{ key: "status_priority", direction: "asc" },
{ key: "created_at", direction: "desc" },
]);
expect(request.message.page).toEqual({ limit: 25, cursor: "cursor-1" });
expect(request.message.subscribe).toEqual({ subscriptionId: "sub-1" });
expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" });
expect(request.subscribe).toEqual({ subscriptionId: "sub-1" });
mock.triggerMessage(
JSON.stringify({
@@ -1851,7 +1715,7 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => {
message: {
type: "fetch_agents_response",
payload: {
requestId: request.message.requestId,
requestId: request.requestId,
subscriptionId: "sub-1",
entries: [],
pageInfo: {
@@ -1865,7 +1729,7 @@ test("fetches agents via RPC with filters, sort, and pagination", async () => {
);
await expect(promise).resolves.toEqual({
requestId: request.message.requestId,
requestId: request.requestId,
subscriptionId: "sub-1",
entries: [],
pageInfo: {
@@ -1899,15 +1763,8 @@ test("sends active-scoped fetch_agents_request", async () => {
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "fetch_agents_request";
requestId: string;
scope?: "active";
};
};
expect(request.message).toMatchObject({
const request = parseSentFrame(mock.sent[0]);
expect(request).toMatchObject({
type: "fetch_agents_request",
scope: "active",
});
@@ -1918,7 +1775,7 @@ test("sends active-scoped fetch_agents_request", async () => {
message: {
type: "fetch_agents_response",
payload: {
requestId: request.message.requestId,
requestId: request.requestId,
entries: [],
pageInfo: {
nextCursor: null,
@@ -1931,7 +1788,7 @@ test("sends active-scoped fetch_agents_request", async () => {
);
await expect(promise).resolves.toMatchObject({
requestId: request.message.requestId,
requestId: request.requestId,
entries: [],
});
});
@@ -1959,16 +1816,9 @@ test("fetches paginated agent history separately from active agents", async () =
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "fetch_agent_history_request";
requestId: string;
page?: { limit: number; cursor?: string };
};
};
expect(request.message.type).toBe("fetch_agent_history_request");
expect(request.message.page).toEqual({ limit: 25, cursor: "cursor-1" });
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("fetch_agent_history_request");
expect(request.page).toEqual({ limit: 25, cursor: "cursor-1" });
mock.triggerMessage(
JSON.stringify({
@@ -1976,7 +1826,7 @@ test("fetches paginated agent history separately from active agents", async () =
message: {
type: "fetch_agent_history_response",
payload: {
requestId: request.message.requestId,
requestId: request.requestId,
entries: [],
pageInfo: {
nextCursor: null,
@@ -1989,7 +1839,7 @@ test("fetches paginated agent history separately from active agents", async () =
);
await expect(promise).resolves.toEqual({
requestId: request.message.requestId,
requestId: request.requestId,
entries: [],
pageInfo: {
nextCursor: null,
@@ -2037,7 +1887,9 @@ test("uses server-provided dictation finish timeout budget", async () => {
await vi.advanceTimersByTimeAsync(5_101);
const error = await finishError;
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toContain("Timeout waiting for dictation finalization (5100ms)");
if (error instanceof Error) {
expect(error.message).toContain("Timeout waiting for dictation finalization (5100ms)");
}
vi.useRealTimers();
});
@@ -2147,11 +1999,8 @@ test("lists available providers via RPC", async () => {
const promise = client.listAvailableProviders();
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: { type: "list_available_providers_request"; requestId: string };
};
expect(request.message.type).toBe("list_available_providers_request");
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("list_available_providers_request");
mock.triggerMessage(
JSON.stringify({
@@ -2165,7 +2014,7 @@ test("lists available providers via RPC", async () => {
],
error: null,
fetchedAt: "2026-02-12T00:00:00.000Z",
requestId: request.message.requestId,
requestId: request.requestId,
},
},
}),
@@ -2178,7 +2027,7 @@ test("lists available providers via RPC", async () => {
],
error: null,
fetchedAt: "2026-02-12T00:00:00.000Z",
requestId: request.message.requestId,
requestId: request.requestId,
});
});
@@ -2210,24 +2059,10 @@ test("lists commands with draft config via RPC", async () => {
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "list_commands_request";
agentId: string;
draftConfig?: {
provider: string;
cwd: string;
modeId?: string;
model?: string;
thinkingOptionId?: string;
};
requestId: string;
};
};
expect(request.message.type).toBe("list_commands_request");
expect(request.message.agentId).toBe("__new_agent__");
expect(request.message.draftConfig).toEqual({
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("list_commands_request");
expect(request.agentId).toBe("__new_agent__");
expect(request.draftConfig).toEqual({
provider: "codex",
cwd: "/tmp/project",
modeId: "bypassPermissions",
@@ -2244,7 +2079,7 @@ test("lists commands with draft config via RPC", async () => {
agentId: "__new_agent__",
commands: [{ name: "help", description: "Show help", argumentHint: "" }],
error: null,
requestId: request.message.requestId,
requestId: request.requestId,
},
},
}),
@@ -2254,7 +2089,7 @@ test("lists commands with draft config via RPC", async () => {
agentId: "__new_agent__",
commands: [{ name: "help", description: "Show help", argumentHint: "" }],
error: null,
requestId: request.message.requestId,
requestId: request.requestId,
});
});
@@ -2278,19 +2113,11 @@ test("lists commands with legacy requestId signature via RPC", async () => {
const promise = client.listCommands("agent-1", "req-legacy");
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(mock.sent[0]) as {
type: "session";
message: {
type: "list_commands_request";
agentId: string;
draftConfig?: unknown;
requestId: string;
};
};
expect(request.message.type).toBe("list_commands_request");
expect(request.message.agentId).toBe("agent-1");
expect(request.message.requestId).toBe("req-legacy");
expect(request.message.draftConfig).toBeUndefined();
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("list_commands_request");
expect(request.agentId).toBe("agent-1");
expect(request.requestId).toBe("req-legacy");
expect(request.draftConfig).toBeUndefined();
mock.triggerMessage(
JSON.stringify({
@@ -2688,25 +2515,17 @@ test("parses canonical agent_stream tool_call payloads without crashing", async
unsubscribe();
expect(received).toHaveLength(1);
const streamMsg = received[0] as {
expect(received[0]).toMatchObject({
payload: {
event: {
type: "timeline";
item: {
type: "tool_call";
status: string;
error: unknown;
detail: {
type: string;
};
};
};
};
};
expect(streamMsg.payload.event.item.status).toBe("running");
expect(streamMsg.payload.event.item.error).toBeNull();
expect(streamMsg.payload.event.item.detail.type).toBe("shell");
status: "running",
error: null,
detail: { type: "shell" },
},
},
},
});
expect(logger.warn).not.toHaveBeenCalled();
});
@@ -2832,28 +2651,20 @@ test("parses canonical fetch_agent_timeline_response payloads without crashing",
unsubscribe();
expect(received).toHaveLength(1);
const timelineMsg = received[0] as {
expect(received[0]).toMatchObject({
payload: {
entries: Array<{
item: {
type: "tool_call";
status: string;
error: unknown;
detail: {
type: string;
};
};
}>;
};
};
const firstEntry = timelineMsg.payload.entries[0];
expect(firstEntry?.item.type).toBe("tool_call");
if (firstEntry?.item.type === "tool_call") {
expect(firstEntry.item.status).toBe("running");
expect(firstEntry.item.error).toBeNull();
expect(firstEntry.item.detail.type).toBe("shell");
}
entries: [
{
item: {
type: "tool_call",
status: "running",
error: null,
detail: { type: "shell" },
},
},
],
},
});
expect(logger.warn).not.toHaveBeenCalled();
});
@@ -2950,14 +2761,14 @@ test("sends subscribe/unsubscribe terminals messages", async () => {
client.unsubscribeTerminals({ cwd: "/tmp/project" });
expect(mock.sent).toHaveLength(2);
expect(JSON.parse(String(mock.sent[0]))).toEqual({
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
type: "session",
message: {
type: "subscribe_terminals_request",
cwd: "/tmp/project",
},
});
expect(JSON.parse(String(mock.sent[1]))).toEqual({
expect(JSON.parse(assertStr(mock.sent[1]))).toEqual({
type: "session",
message: {
type: "unsubscribe_terminals_request",
@@ -3041,7 +2852,7 @@ test("sends close_items_request and resolves close_items_response", async () =>
"req-close-items",
);
expect(JSON.parse(String(mock.sent[0]))).toEqual({
expect(JSON.parse(assertStr(mock.sent[0]))).toEqual({
type: "session",
message: {
type: "close_items_request",
@@ -3091,18 +2902,10 @@ test("waitForFinish with timeout=0 omits timeoutMs and has no client deadline",
const waitPromise = client.waitForFinish("agent-wait-zero-timeout", 0);
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "wait_for_finish_request";
requestId: string;
agentId: string;
timeoutMs?: number;
};
};
expect(request.message.type).toBe("wait_for_finish_request");
expect(request.message.agentId).toBe("agent-wait-zero-timeout");
expect(request.message).not.toHaveProperty("timeoutMs");
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("wait_for_finish_request");
expect(request.agentId).toBe("agent-wait-zero-timeout");
expect(request).not.toHaveProperty("timeoutMs");
const settled = vi.fn();
void waitPromise.then(
@@ -3117,7 +2920,7 @@ test("waitForFinish with timeout=0 omits timeoutMs and has no client deadline",
wrapSessionMessage({
type: "wait_for_finish_response",
payload: {
requestId: request.message.requestId,
requestId: request.requestId,
status: "idle",
final: null,
error: null,
@@ -181,7 +181,8 @@ function connectClient(
function readAttentionRequiredMessage(ws: ReturnType<typeof createOpenSocket>) {
const rawMessage = ws.send.mock.calls[0]?.[0];
expect(typeof rawMessage).toBe("string");
const message = JSON.parse(rawMessage as string);
if (typeof rawMessage !== "string") throw new Error("Expected string WebSocket frame");
const message = JSON.parse(rawMessage);
expect(message.type).toBe("session");
expect(message.message.type).toBe("agent_stream");
expect(message.message.payload.event.type).toBe("attention_required");
@@ -96,6 +96,7 @@ vi.mock("./push/push-service.js", () => ({
},
}));
import { z } from "zod";
import { VoiceAssistantWebSocketServer } from "./websocket-server";
import { parseServerInfoStatusPayload } from "./messages.js";
import type { SpeechReadinessSnapshot } from "./speech/speech-runtime.js";
@@ -106,6 +107,27 @@ interface WebSocketServerInternals {
const TEST_DAEMON_VERSION = "1.2.3-test";
const WireEnvelopeSchema = z.object({
type: z.string().optional(),
message: z
.object({
type: z.string().optional(),
payload: z.unknown().optional(),
})
.optional(),
});
function parseSentEnvelope(data: unknown): z.infer<typeof WireEnvelopeSchema> {
if (typeof data !== "string") throw new Error("Expected string frame");
return WireEnvelopeSchema.parse(JSON.parse(data));
}
const BinaryFrameSchema = z.object({
opcode: z.number(),
slot: z.number(),
payload: z.instanceof(Uint8Array),
});
class MockSocket {
readyState = 1;
bufferedAmount = 0;
@@ -331,10 +353,7 @@ async function attachRelayAndHello(params: {
params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId)));
await Promise.resolve();
expect(params.socket.sent.length).toBeGreaterThan(0);
const envelope = JSON.parse(params.socket.sent[0] as string) as {
type?: unknown;
message?: { type?: unknown; payload?: unknown };
};
const envelope = parseSentEnvelope(params.socket.sent[0]);
expect(envelope.type).toBe("session");
const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload);
expect(envelope.message?.type).toBe("status");
@@ -354,10 +373,7 @@ async function attachDirectAndHello(params: {
params.socket.emit("message", JSON.stringify(createHelloMessage(params.clientId)));
await Promise.resolve();
expect(params.socket.sent.length).toBeGreaterThan(0);
const envelope = JSON.parse(params.socket.sent[0] as string) as {
type?: unknown;
message?: { type?: unknown; payload?: unknown };
};
const envelope = parseSentEnvelope(params.socket.sent[0]);
expect(envelope.type).toBe("session");
const serverInfo = parseServerInfoStatusPayload(envelope.message?.payload);
expect(envelope.message?.type).toBe("status");
@@ -441,7 +457,7 @@ describe("relay external socket reconnect behavior", () => {
let closeReason = "";
socket.on("close", (code: unknown, reason: unknown) => {
closeCode = typeof code === "number" ? code : null;
closeReason = typeof reason === "string" ? reason : String(reason ?? "");
closeReason = typeof reason === "string" ? reason : "";
});
await (server as unknown as WebSocketServerInternals).attachSocket(
@@ -509,7 +525,7 @@ describe("relay external socket reconnect behavior", () => {
let closeReason = "";
socket.on("close", (code: unknown, reason: unknown) => {
closeCode = typeof code === "number" ? code : null;
closeReason = typeof reason === "string" ? reason : String(reason ?? "");
closeReason = typeof reason === "string" ? reason : "";
});
await server.attachExternalSocket(socket, { transport: "relay" });
@@ -583,14 +599,14 @@ describe("relay external socket reconnect behavior", () => {
});
expect(sessionMock.instances).toHaveLength(1);
const onMessage = session.args.onMessage as
| ((msg: { type: "status"; payload: { status: string } }) => void)
| undefined;
const { onMessage } = session.args;
expect(onMessage).toBeTypeOf("function");
onMessage?.({
type: "status",
payload: { status: "ok" },
});
if (typeof onMessage === "function") {
onMessage({
type: "status",
payload: { status: "ok" },
});
}
expect(directSocket.sent.length).toBeGreaterThan(0);
expect(relaySocket.sent.length).toBeGreaterThan(0);
@@ -672,9 +688,7 @@ describe("relay external socket reconnect behavior", () => {
server.publishSpeechReadiness(speechReadiness);
expect(socket.sent).toHaveLength(2);
const secondEnvelope = JSON.parse(socket.sent[1] as string) as {
message?: { payload?: unknown };
};
const secondEnvelope = parseSentEnvelope(socket.sent[1]);
const secondPayload = parseServerInfoStatusPayload(secondEnvelope.message?.payload);
expect(secondPayload?.capabilities?.voice?.dictation.enabled).toBe(true);
expect(secondPayload?.capabilities?.voice?.voice.enabled).toBe(true);
@@ -699,9 +713,7 @@ describe("relay external socket reconnect behavior", () => {
server.publishSpeechReadiness(createDownloadInProgressSpeechReadinessSnapshot());
expect(socket.sent).toHaveLength(2);
const envelope = JSON.parse(socket.sent[1] as string) as {
message?: { payload?: unknown };
};
const envelope = parseSentEnvelope(socket.sent[1]);
const payload = parseServerInfoStatusPayload(envelope.message?.payload);
expect(payload?.capabilities?.voice?.dictation.enabled).toBe(true);
expect(payload?.capabilities?.voice?.voice.enabled).toBe(true);
@@ -736,11 +748,7 @@ describe("relay external socket reconnect behavior", () => {
await Promise.resolve();
expect(session.handleBinaryFrame).toHaveBeenCalledTimes(1);
const frame = session.handleBinaryFrame.mock.calls[0]?.[0] as {
opcode: number;
slot: number;
payload: Uint8Array;
};
const frame = BinaryFrameSchema.parse(session.handleBinaryFrame.mock.calls[0]?.[0]);
expect(frame.opcode).toBe(TerminalStreamOpcode.Input);
expect(frame.slot).toBe(9);
expect(new TextDecoder().decode(frame.payload)).toBe("ls\r");
@@ -760,12 +768,11 @@ describe("relay external socket reconnect behavior", () => {
expect(sessionMock.instances).toHaveLength(1);
const session = sessionMock.instances[0];
const onBinaryMessage = session.args.onBinaryMessage as
| ((frame: Uint8Array) => void)
| undefined;
const { onBinaryMessage } = session.args;
expect(onBinaryMessage).toBeTypeOf("function");
onBinaryMessage?.(new Uint8Array([TerminalStreamOpcode.Output, 12, 0x6f, 0x6b]));
if (typeof onBinaryMessage === "function") {
onBinaryMessage(new Uint8Array([TerminalStreamOpcode.Output, 12, 0x6f, 0x6b]));
}
expect(socket.sent).toHaveLength(2);
const binaryPayload = asUint8Array(socket.sent[1]);
@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { z } from "zod";
import type { Server as HTTPServer } from "http";
import type pino from "pino";
import type { AgentManager } from "./agent/agent-manager.js";
@@ -42,17 +43,19 @@ interface WebSocketServerInternals {
sessions: Map<unknown, unknown>;
}
interface RuntimeMetricsLog {
outboundMessageTypesTop: Array<[string, number]>;
outboundSessionMessageTypesTop: Array<[string, number]>;
outboundAgentStreamTypesTop: Array<[string, number]>;
outboundAgentStreamAgentsTop: Array<[string, number]>;
outboundBinaryFrameTypesTop: Array<[string, number]>;
bufferedAmount: {
p95: number;
max: number;
};
}
const RuntimeMetricsLogSchema = z.object({
outboundMessageTypesTop: z.array(z.tuple([z.string(), z.number()])),
outboundSessionMessageTypesTop: z.array(z.tuple([z.string(), z.number()])),
outboundAgentStreamTypesTop: z.array(z.tuple([z.string(), z.number()])),
outboundAgentStreamAgentsTop: z.array(z.tuple([z.string(), z.number()])),
outboundBinaryFrameTypesTop: z.array(z.tuple([z.string(), z.number()])),
bufferedAmount: z.object({
p95: z.number(),
max: z.number(),
}),
});
type RuntimeMetricsLog = z.infer<typeof RuntimeMetricsLogSchema>;
interface TestSocket {
readyState: number;
@@ -155,7 +158,7 @@ function flushRuntimeMetrics(server: VoiceAssistantWebSocketServer): void {
function getRuntimeMetricsLog(logger: ReturnType<typeof createLogger>): RuntimeMetricsLog {
const metricsCall = logger.info.mock.calls.find((call) => call[1] === "ws_runtime_metrics");
expect(metricsCall).toBeDefined();
return metricsCall![0] as RuntimeMetricsLog;
return RuntimeMetricsLogSchema.parse(metricsCall![0]);
}
function sendToClient(
@@ -350,11 +353,11 @@ describe("VoiceAssistantWebSocketServer runtime metrics", () => {
const logger = createLogger();
const server = createServer(logger);
const socket = createSocket([12, 24]);
const frame = new Proxy(Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0xc0]), {
const frame: Uint8Array = new Proxy(Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0xc0]), {
get(_target, property) {
throw new Error(`Binary frame payload was unexpectedly accessed via ${String(property)}`);
},
}) as unknown as Uint8Array;
});
expect(() => sendBinaryToClient(server, socket, frame)).not.toThrow();