refactor(server/tests): replace unsafe type assertions with Reflect + Zod (T1.b partial) (#752)

Cluster T1.b — first wave of no-unsafe-type-assertion fixes across agent
provider and MCP test files. Clears all type-aware lint errors in 6 files;
remaining 4 files (class-mock patterns) deferred to T1.b2.
This commit is contained in:
Mohamed Boudra
2026-05-05 19:58:56 +07:00
parent b1d3867da9
commit eb16829f51
6 changed files with 233 additions and 185 deletions
@@ -22,7 +22,7 @@ interface McpToolResult {
} }
interface McpClient { interface McpClient {
callTool: (input: { name: string; args?: StructuredContent }) => Promise<unknown>; callTool: (input: { name: string; args?: StructuredContent }) => Promise<McpToolResult>;
close: () => Promise<void>; close: () => Promise<void>;
} }
@@ -61,23 +61,29 @@ function getStructuredContent(result: McpToolResult): StructuredContent | null {
} }
const content = result.content?.[0]; const content = result.content?.[0];
if (content && typeof content === "object" && "structuredContent" in content) { if (content && typeof content === "object" && "structuredContent" in content) {
const structured = (content as { structuredContent?: StructuredContent }).structuredContent; if (content.structuredContent) return content.structuredContent;
if (structured) return structured;
} }
if (content && typeof content === "object") { if (content && typeof content === "object") {
return content as StructuredContent; return content;
} }
return null; return null;
} }
async function createMcpClient(url: string): Promise<McpClient> {
const transport = new StreamableHTTPClientTransport(new URL(url));
const rawClient = await experimental_createMCPClient({ transport });
const boundCallTool: McpClient["callTool"] = Reflect.get(rawClient, "callTool").bind(rawClient);
return { callTool: boundCallTool, close: () => rawClient.close() };
}
async function waitForAgentCompletion(options: { async function waitForAgentCompletion(options: {
client: McpClient; client: McpClient;
agentId: string; agentId: string;
}): Promise<void> { }): Promise<void> {
const waitResult = (await options.client.callTool({ const waitResult = await options.client.callTool({
name: "wait_for_agent", name: "wait_for_agent",
args: { agentId: options.agentId }, args: { agentId: options.agentId },
})) as McpToolResult; });
const payload = getStructuredContent(waitResult); const payload = getStructuredContent(waitResult);
if (!payload) { if (!payload) {
throw new Error("wait_for_agent returned no structured payload"); throw new Error("wait_for_agent returned no structured payload");
@@ -87,7 +93,7 @@ async function waitForAgentCompletion(options: {
} }
const status = payload.status; const status = payload.status;
if (status === "running" || status === "initializing") { if (status === "running" || status === "initializing") {
throw new Error(`Agent still running after wait_for_agent (status=${String(status)})`); throw new Error(`Agent still running after wait_for_agent (status=${status})`);
} }
} }
@@ -113,10 +119,7 @@ describe("agent MCP end-to-end (offline)", () => {
const daemon = await createPaseoDaemon(daemonConfig, pino({ level: "silent" })); const daemon = await createPaseoDaemon(daemonConfig, pino({ level: "silent" }));
await daemon.start(); await daemon.start();
const transport = new StreamableHTTPClientTransport( const client = await createMcpClient(`http://127.0.0.1:${port}/mcp/agents`);
new URL(`http://127.0.0.1:${port}/mcp/agents`),
);
const client = (await experimental_createMCPClient({ transport })) as McpClient;
let agentId: string | null = null; let agentId: string | null = null;
try { try {
@@ -129,7 +132,7 @@ describe("agent MCP end-to-end (offline)", () => {
"Do not respond before the command finishes.", "Do not respond before the command finishes.",
].join("\n"); ].join("\n");
const result = (await client.callTool({ const result = await client.callTool({
name: "create_agent", name: "create_agent",
args: { args: {
cwd: agentCwd, cwd: agentCwd,
@@ -139,10 +142,10 @@ describe("agent MCP end-to-end (offline)", () => {
initialPrompt, initialPrompt,
background: false, background: false,
}, },
})) as McpToolResult; });
const payload = getStructuredContent(result); const payload = getStructuredContent(result);
agentId = (payload?.agentId as string | undefined) ?? null; agentId = typeof payload?.agentId === "string" ? payload.agentId : null;
expect(agentId).toBeTruthy(); expect(agentId).toBeTruthy();
await waitForAgentCompletion({ client, agentId: agentId! }); await waitForAgentCompletion({ client, agentId: agentId! });
@@ -186,10 +189,7 @@ describe("agent MCP end-to-end (offline)", () => {
const daemon = await createPaseoDaemon(daemonConfig, pino({ level: "silent" })); const daemon = await createPaseoDaemon(daemonConfig, pino({ level: "silent" }));
await daemon.start(); await daemon.start();
const transport = new StreamableHTTPClientTransport( const client = await createMcpClient(`http://127.0.0.1:${port}/mcp/agents`);
new URL(`http://127.0.0.1:${port}/mcp/agents`),
);
const client = (await experimental_createMCPClient({ transport })) as McpClient;
const disabledPaseoHome = await mkdtemp(path.join(os.tmpdir(), "paseo-home-disabled-")); const disabledPaseoHome = await mkdtemp(path.join(os.tmpdir(), "paseo-home-disabled-"));
const disabledStaticDir = await mkdtemp(path.join(os.tmpdir(), "paseo-static-disabled-")); const disabledStaticDir = await mkdtemp(path.join(os.tmpdir(), "paseo-static-disabled-"));
@@ -210,17 +210,12 @@ describe("agent MCP end-to-end (offline)", () => {
const disabledDaemon = await createPaseoDaemon(disabledDaemonConfig, pino({ level: "silent" })); const disabledDaemon = await createPaseoDaemon(disabledDaemonConfig, pino({ level: "silent" }));
await disabledDaemon.start(); await disabledDaemon.start();
const disabledTransport = new StreamableHTTPClientTransport( const disabledClient = await createMcpClient(`http://127.0.0.1:${disabledPort}/mcp/agents`);
new URL(`http://127.0.0.1:${disabledPort}/mcp/agents`),
);
const disabledClient = (await experimental_createMCPClient({
transport: disabledTransport,
})) as McpClient;
let agentId: string | null = null; let agentId: string | null = null;
let disabledAgentId: string | null = null; let disabledAgentId: string | null = null;
try { try {
const result = (await client.callTool({ const result = await client.callTool({
name: "create_agent", name: "create_agent",
args: { args: {
cwd: agentCwd, cwd: agentCwd,
@@ -230,9 +225,9 @@ describe("agent MCP end-to-end (offline)", () => {
initialPrompt: "reply with done and stop", initialPrompt: "reply with done and stop",
background: true, background: true,
}, },
})) as McpToolResult; });
const payload = getStructuredContent(result); const payload = getStructuredContent(result);
agentId = (payload?.agentId as string | undefined) ?? null; agentId = typeof payload?.agentId === "string" ? payload.agentId : null;
expect(agentId).toBeTruthy(); expect(agentId).toBeTruthy();
const injectedAgent = daemon.agentManager.getAgent(agentId!); const injectedAgent = daemon.agentManager.getAgent(agentId!);
@@ -243,7 +238,7 @@ describe("agent MCP end-to-end (offline)", () => {
}, },
}); });
const disabledResult = (await disabledClient.callTool({ const disabledResult = await disabledClient.callTool({
name: "create_agent", name: "create_agent",
args: { args: {
cwd: disabledAgentCwd, cwd: disabledAgentCwd,
@@ -253,9 +248,10 @@ describe("agent MCP end-to-end (offline)", () => {
initialPrompt: "reply with done and stop", initialPrompt: "reply with done and stop",
background: true, background: true,
}, },
})) as McpToolResult; });
const disabledPayload = getStructuredContent(disabledResult); const disabledPayload = getStructuredContent(disabledResult);
disabledAgentId = (disabledPayload?.agentId as string | undefined) ?? null; disabledAgentId =
typeof disabledPayload?.agentId === "string" ? disabledPayload.agentId : null;
expect(disabledAgentId).toBeTruthy(); expect(disabledAgentId).toBeTruthy();
const disabledAgent = disabledDaemon.agentManager.getAgent(disabledAgentId!); const disabledAgent = disabledDaemon.agentManager.getAgent(disabledAgentId!);
@@ -301,10 +297,7 @@ describe("agent MCP end-to-end (offline)", () => {
const daemon = await createPaseoDaemon(daemonConfig, pino({ level: "silent" })); const daemon = await createPaseoDaemon(daemonConfig, pino({ level: "silent" }));
await daemon.start(); await daemon.start();
const transport = new StreamableHTTPClientTransport( const client = await createMcpClient(`http://127.0.0.1:${port}/mcp/agents`);
new URL(`http://127.0.0.1:${port}/mcp/agents`),
);
const client = (await experimental_createMCPClient({ transport })) as McpClient;
let agentId: string | null = null; let agentId: string | null = null;
try { try {
@@ -339,7 +332,7 @@ describe("agent MCP end-to-end (offline)", () => {
stdio: "pipe", stdio: "pipe",
}); });
const result = (await withTimeout({ const result = await withTimeout({
promise: client.callTool({ promise: client.callTool({
name: "create_agent", name: "create_agent",
args: { args: {
@@ -355,12 +348,12 @@ describe("agent MCP end-to-end (offline)", () => {
}), }),
timeoutMs: 2500, timeoutMs: 2500,
label: "create_agent should not block on setup", label: "create_agent should not block on setup",
})) as McpToolResult; });
const payload = getStructuredContent(result); const payload = getStructuredContent(result);
agentId = (payload?.agentId as string | undefined) ?? null; agentId = typeof payload?.agentId === "string" ? payload.agentId : null;
expect(agentId).toBeTruthy(); expect(agentId).toBeTruthy();
const worktreePath = (payload?.cwd as string | undefined) ?? ""; const worktreePath = typeof payload?.cwd === "string" ? payload.cwd : "";
expect(worktreePath).toContain(`${path.sep}worktrees${path.sep}`); expect(worktreePath).toContain(`${path.sep}worktrees${path.sep}`);
expect(existsSync(path.join(worktreePath, "setup-done.txt"))).toBe(false); expect(existsSync(path.join(worktreePath, "setup-done.txt"))).toBe(false);
expect(existsSync(path.join(worktreePath, "dev-terminal.txt"))).toBe(false); expect(existsSync(path.join(worktreePath, "dev-terminal.txt"))).toBe(false);
@@ -5,6 +5,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { afterAll, beforeAll, describe, expect, test } from "vitest";
import { experimental_createMCPClient } from "ai"; import { experimental_createMCPClient } from "ai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { z } from "zod";
import { AGENT_WAIT_TIMEOUT_MS } from "./mcp-shared.js"; import { AGENT_WAIT_TIMEOUT_MS } from "./mcp-shared.js";
import { createTestPaseoDaemon, type TestPaseoDaemon } from "../test-utils/paseo-daemon.js"; import { createTestPaseoDaemon, type TestPaseoDaemon } from "../test-utils/paseo-daemon.js";
@@ -20,10 +21,22 @@ interface McpToolResult {
} }
interface McpClient { interface McpClient {
callTool: (input: { name: string; args?: StructuredContent }) => Promise<unknown>; callTool: (input: { name: string; args?: StructuredContent }) => Promise<McpToolResult>;
close: () => Promise<void>; close: () => Promise<void>;
} }
function str(val: unknown): string {
return z.string().parse(val);
}
function recordArr(val: unknown): StructuredContent[] {
return z.array(z.record(z.unknown())).parse(val);
}
function strArrOptional(val: unknown): string[] | undefined {
return z.array(z.string()).optional().parse(val);
}
function formatHostForHttpUrl(host: string): string { function formatHostForHttpUrl(host: string): string {
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
} }
@@ -43,20 +56,21 @@ function getStructuredContent(result: McpToolResult): StructuredContent | null {
} }
const content = result.content?.[0]; const content = result.content?.[0];
if (content && typeof content === "object" && "structuredContent" in content) { if (content && typeof content === "object" && "structuredContent" in content) {
const structured = (content as { structuredContent?: StructuredContent }).structuredContent; if (content.structuredContent) {
if (structured) { return content.structuredContent;
return structured;
} }
} }
if (content && typeof content === "object") { if (content && typeof content === "object") {
return content as StructuredContent; return content;
} }
return null; return null;
} }
async function createMcpClient(url: string): Promise<McpClient> { async function createMcpClient(url: string): Promise<McpClient> {
const transport = new StreamableHTTPClientTransport(new URL(url)); const transport = new StreamableHTTPClientTransport(new URL(url));
return (await experimental_createMCPClient({ transport })) as McpClient; const rawClient = await experimental_createMCPClient({ transport });
const boundCallTool: McpClient["callTool"] = Reflect.get(rawClient, "callTool").bind(rawClient);
return { callTool: boundCallTool, close: () => rawClient.close() };
} }
async function callToolStructured( async function callToolStructured(
@@ -64,7 +78,7 @@ async function callToolStructured(
name: string, name: string,
args?: StructuredContent, args?: StructuredContent,
): Promise<StructuredContent> { ): Promise<StructuredContent> {
const result = (await client.callTool({ name, args: args ?? {} })) as McpToolResult; const result = await client.callTool({ name, args: args ?? {} });
const payload = getStructuredContent(result); const payload = getStructuredContent(result);
if (!payload) { if (!payload) {
throw new Error(`${name} returned no structured payload`); throw new Error(`${name} returned no structured payload`);
@@ -78,10 +92,14 @@ async function expectToolError(
args: StructuredContent, args: StructuredContent,
pattern: RegExp, pattern: RegExp,
): Promise<void> { ): Promise<void> {
const result = (await client.callTool({ name, args })) as McpToolResult; const result = await client.callTool({ name, args });
expect(result.isError).toBe(true); expect(result.isError).toBe(true);
const content = result.content?.[0] as { text?: string } | undefined; const contentItem = result.content?.[0];
expect(content?.text ?? "").toMatch(pattern); const contentText: string | undefined =
contentItem != null && typeof contentItem === "object"
? Reflect.get(contentItem, "text")
: undefined;
expect(contentText ?? "").toMatch(pattern);
} }
async function sleep(ms: number): Promise<void> { async function sleep(ms: number): Promise<void> {
@@ -118,7 +136,7 @@ async function makeCwd(prefix: string): Promise<string> {
} }
async function createTopLevelAgent(args?: Partial<StructuredContent>): Promise<string> { async function createTopLevelAgent(args?: Partial<StructuredContent>): Promise<string> {
const cwd = (args?.cwd as string | undefined) ?? (await makeCwd("agent-cwd")); const cwd = typeof args?.cwd === "string" ? args.cwd : await makeCwd("agent-cwd");
const payload = await callToolStructured(topLevelClient, "create_agent", { const payload = await callToolStructured(topLevelClient, "create_agent", {
cwd, cwd,
title: "Parity agent", title: "Parity agent",
@@ -128,7 +146,7 @@ async function createTopLevelAgent(args?: Partial<StructuredContent>): Promise<s
background: true, background: true,
...args, ...args,
}); });
return payload.agentId as string; return str(payload.agentId);
} }
async function createChildAgent(args?: Partial<StructuredContent>): Promise<string> { async function createChildAgent(args?: Partial<StructuredContent>): Promise<string> {
@@ -139,7 +157,7 @@ async function createChildAgent(args?: Partial<StructuredContent>): Promise<stri
background: true, background: true,
...args, ...args,
}); });
return payload.agentId as string; return str(payload.agentId);
} }
async function archiveAgentIfPresent(agentId: string | null | undefined): Promise<void> { async function archiveAgentIfPresent(agentId: string | null | undefined): Promise<void> {
@@ -213,7 +231,7 @@ beforeAll(async () => {
mode: "bypassPermissions", mode: "bypassPermissions",
background: true, background: true,
}); });
parentAgentId = parentPayload.agentId as string; parentAgentId = str(parentPayload.agentId);
agentScopedClient = await createMcpClient( agentScopedClient = await createMcpClient(
`http://127.0.0.1:${daemonHandle.port}/mcp/agents?callerAgentId=${parentAgentId}`, `http://127.0.0.1:${daemonHandle.port}/mcp/agents?callerAgentId=${parentAgentId}`,
@@ -362,10 +380,10 @@ describe("Suite B: Terminal Tools", () => {
const created = await callToolStructured(agentScopedClient, "create_terminal", { const created = await callToolStructured(agentScopedClient, "create_terminal", {
name: "Parity terminal", name: "Parity terminal",
}); });
terminalId = created.id as string; terminalId = str(created.id);
const listed = await callToolStructured(agentScopedClient, "list_terminals"); const listed = await callToolStructured(agentScopedClient, "list_terminals");
const terminals = listed.terminals as Array<StructuredContent>; const terminals = recordArr(listed.terminals);
expect(terminals).toEqual( expect(terminals).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
@@ -386,7 +404,7 @@ describe("Suite B: Terminal Tools", () => {
const created = await callToolStructured(agentScopedClient, "create_terminal", { const created = await callToolStructured(agentScopedClient, "create_terminal", {
name: "Parity capture terminal", name: "Parity capture terminal",
}); });
terminalId = created.id as string; terminalId = str(created.id);
await callToolStructured(agentScopedClient, "send_terminal_keys", { await callToolStructured(agentScopedClient, "send_terminal_keys", {
terminalId, terminalId,
@@ -404,7 +422,7 @@ describe("Suite B: Terminal Tools", () => {
terminalId, terminalId,
scrollback: true, scrollback: true,
}); });
const lines = (payload.lines as string[] | undefined) ?? []; const lines = strArrOptional(payload.lines) ?? [];
return lines.some((line) => line.includes("hello")) ? payload : null; return lines.some((line) => line.includes("hello")) ? payload : null;
}, },
}); });
@@ -421,7 +439,7 @@ describe("Suite B: Terminal Tools", () => {
const created = await callToolStructured(agentScopedClient, "create_terminal", { const created = await callToolStructured(agentScopedClient, "create_terminal", {
name: "Parity kill terminal", name: "Parity kill terminal",
}); });
terminalId = created.id as string; terminalId = str(created.id);
await callToolStructured(agentScopedClient, "kill_terminal", { terminalId }); await callToolStructured(agentScopedClient, "kill_terminal", { terminalId });
terminalId = null; terminalId = null;
@@ -432,11 +450,11 @@ describe("Suite B: Terminal Tools", () => {
label: "terminal removal", label: "terminal removal",
check: async () => { check: async () => {
const payload = await callToolStructured(agentScopedClient, "list_terminals"); const payload = await callToolStructured(agentScopedClient, "list_terminals");
const terminals = payload.terminals as Array<StructuredContent>; const terminals = recordArr(payload.terminals);
return terminals.some((terminal) => terminal.id === created.id) ? null : payload; return terminals.some((terminal) => terminal.id === created.id) ? null : payload;
}, },
}); });
const terminals = listed.terminals as Array<StructuredContent>; const terminals = recordArr(listed.terminals);
expect(terminals.some((terminal) => terminal.id === created.id)).toBe(false); expect(terminals.some((terminal) => terminal.id === created.id)).toBe(false);
} finally { } finally {
await killTerminalIfPresent(terminalId); await killTerminalIfPresent(terminalId);
@@ -462,10 +480,10 @@ describe("Suite C: Schedule Tools", () => {
every: "5m", every: "5m",
name: "Parity schedule list", name: "Parity schedule list",
}); });
scheduleId = created.id as string; scheduleId = str(created.id);
const listed = await callToolStructured(topLevelClient, "list_schedules"); const listed = await callToolStructured(topLevelClient, "list_schedules");
const schedules = listed.schedules as Array<StructuredContent>; const schedules = recordArr(listed.schedules);
expect(schedules).toEqual( expect(schedules).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
@@ -488,7 +506,7 @@ describe("Suite C: Schedule Tools", () => {
name: "Parity provider schedule", name: "Parity provider schedule",
provider: "codex/gpt-5.4", provider: "codex/gpt-5.4",
}); });
scheduleId = created.id as string; scheduleId = str(created.id);
expect(created.target).toMatchObject({ expect(created.target).toMatchObject({
type: "new-agent", type: "new-agent",
config: { config: {
@@ -509,7 +527,7 @@ describe("Suite C: Schedule Tools", () => {
every: "5m", every: "5m",
name: "Parity inspect schedule", name: "Parity inspect schedule",
}); });
scheduleId = created.id as string; scheduleId = str(created.id);
const inspected = await callToolStructured(topLevelClient, "inspect_schedule", { const inspected = await callToolStructured(topLevelClient, "inspect_schedule", {
id: scheduleId, id: scheduleId,
@@ -533,7 +551,7 @@ describe("Suite C: Schedule Tools", () => {
every: "5m", every: "5m",
name: "Parity pause schedule", name: "Parity pause schedule",
}); });
scheduleId = created.id as string; scheduleId = str(created.id);
await callToolStructured(topLevelClient, "pause_schedule", { id: scheduleId }); await callToolStructured(topLevelClient, "pause_schedule", { id: scheduleId });
const paused = await callToolStructured(topLevelClient, "inspect_schedule", { const paused = await callToolStructured(topLevelClient, "inspect_schedule", {
@@ -559,13 +577,13 @@ describe("Suite C: Schedule Tools", () => {
every: "5m", every: "5m",
name: "Parity delete schedule", name: "Parity delete schedule",
}); });
scheduleId = created.id as string; scheduleId = str(created.id);
await callToolStructured(topLevelClient, "delete_schedule", { id: scheduleId }); await callToolStructured(topLevelClient, "delete_schedule", { id: scheduleId });
scheduleId = null; scheduleId = null;
const listed = await callToolStructured(topLevelClient, "list_schedules"); const listed = await callToolStructured(topLevelClient, "list_schedules");
const schedules = listed.schedules as Array<StructuredContent>; const schedules = recordArr(listed.schedules);
expect(schedules.some((schedule) => schedule.id === created.id)).toBe(false); expect(schedules.some((schedule) => schedule.id === created.id)).toBe(false);
} finally { } finally {
await deleteScheduleIfPresent(scheduleId); await deleteScheduleIfPresent(scheduleId);
@@ -581,7 +599,7 @@ describe("Suite C: Schedule Tools", () => {
name: "Parity self schedule", name: "Parity self schedule",
target: "self", target: "self",
}); });
scheduleId = created.id as string; scheduleId = str(created.id);
expect(created.target).toMatchObject({ expect(created.target).toMatchObject({
type: "agent", type: "agent",
agentId: parentAgentId, agentId: parentAgentId,
@@ -599,7 +617,7 @@ describe("Suite C: Schedule Tools", () => {
every: "5m", every: "5m",
provider: "codex/gpt-5.4", provider: "codex/gpt-5.4",
}); });
scheduleId = created.id as string; scheduleId = str(created.id);
expect(created.target).toMatchObject({ expect(created.target).toMatchObject({
type: "new-agent", type: "new-agent",
config: { config: {
@@ -629,7 +647,7 @@ describe("Suite C: Schedule Tools", () => {
describe("Suite D: Provider Tools", () => { describe("Suite D: Provider Tools", () => {
test("list_providers returns providers", async () => { test("list_providers returns providers", async () => {
const payload = await callToolStructured(topLevelClient, "list_providers"); const payload = await callToolStructured(topLevelClient, "list_providers");
const providers = payload.providers as Array<StructuredContent>; const providers = recordArr(payload.providers);
expect(Array.isArray(providers)).toBe(true); expect(Array.isArray(providers)).toBe(true);
expect(providers.length).toBeGreaterThan(0); expect(providers.length).toBeGreaterThan(0);
expect(providers[0]).toEqual( expect(providers[0]).toEqual(
@@ -667,12 +685,12 @@ describe("Suite E: Worktree Tools", () => {
branchName, branchName,
baseBranch: "main", baseBranch: "main",
}); });
worktreePath = created.worktreePath as string; worktreePath = str(created.worktreePath);
const listed = await callToolStructured(topLevelClient, "list_worktrees", { const listed = await callToolStructured(topLevelClient, "list_worktrees", {
cwd: worktreeRepoCwd, cwd: worktreeRepoCwd,
}); });
const worktrees = listed.worktrees as Array<StructuredContent>; const worktrees = recordArr(listed.worktrees);
expect(worktrees).toEqual( expect(worktrees).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
@@ -695,7 +713,7 @@ describe("Suite E: Worktree Tools", () => {
branchName, branchName,
baseBranch: "main", baseBranch: "main",
}); });
worktreePath = created.worktreePath as string; worktreePath = str(created.worktreePath);
await callToolStructured(topLevelClient, "archive_worktree", { await callToolStructured(topLevelClient, "archive_worktree", {
cwd: worktreeRepoCwd, cwd: worktreeRepoCwd,
@@ -706,7 +724,7 @@ describe("Suite E: Worktree Tools", () => {
const listed = await callToolStructured(topLevelClient, "list_worktrees", { const listed = await callToolStructured(topLevelClient, "list_worktrees", {
cwd: worktreeRepoCwd, cwd: worktreeRepoCwd,
}); });
const worktrees = listed.worktrees as Array<StructuredContent>; const worktrees = recordArr(listed.worktrees);
expect(worktrees.some((worktree) => worktree.path === created.worktreePath)).toBe(false); expect(worktrees.some((worktree) => worktree.path === created.worktreePath)).toBe(false);
} finally { } finally {
await archiveWorktreeIfPresent({ cwd: worktreeRepoCwd, worktreePath }); await archiveWorktreeIfPresent({ cwd: worktreeRepoCwd, worktreePath });
@@ -14,7 +14,6 @@ import { AgentListItemPayloadSchema, AgentSnapshotPayloadSchema } from "../../sh
import type { PersistedProjectRecord, PersistedWorkspaceRecord } from "../workspace-registry.js"; import type { PersistedProjectRecord, PersistedWorkspaceRecord } from "../workspace-registry.js";
import type { CreateScheduleInput, StoredSchedule } from "../schedule/types.js"; import type { CreateScheduleInput, StoredSchedule } from "../schedule/types.js";
import type { ScheduleService } from "../schedule/service.js"; import type { ScheduleService } from "../schedule/service.js";
import type { AgentProvider } from "./agent-sdk-types.js";
import type { WorkspaceGitService } from "../workspace-git-service.js"; import type { WorkspaceGitService } from "../workspace-git-service.js";
import { import {
createPaseoWorktree as createPaseoWorktreeService, createPaseoWorktree as createPaseoWorktreeService,
@@ -48,15 +47,12 @@ interface RegisteredMcpTool {
}>; }>;
} }
interface McpServerInternals {
_registeredTools: Record<string, RegisteredMcpTool>;
}
function lookupTool( function lookupTool(
server: Awaited<ReturnType<typeof createAgentMcpServer>>, server: Awaited<ReturnType<typeof createAgentMcpServer>>,
name: string, name: string,
): RegisteredMcpTool | undefined { ): RegisteredMcpTool | undefined {
return (server as unknown as McpServerInternals)._registeredTools[name]; const tools: Record<string, RegisteredMcpTool> = Reflect.get(server, "_registeredTools");
return tools[name];
} }
function registeredTool( function registeredTool(
@@ -73,7 +69,7 @@ function registeredTool(
function agentsOf(response: { function agentsOf(response: {
structuredContent: LooseStructuredContent; structuredContent: LooseStructuredContent;
}): Array<Record<string, unknown>> { }): Array<Record<string, unknown>> {
return response.structuredContent.agents as Array<Record<string, unknown>>; return z.array(z.record(z.unknown())).parse(response.structuredContent.agents);
} }
type AgentManagerSpies = ReturnType<typeof buildAgentManagerSpies>; type AgentManagerSpies = ReturnType<typeof buildAgentManagerSpies>;
@@ -736,7 +732,7 @@ describe("create_agent MCP tool", () => {
background: true, background: true,
}); });
const agentCwd = spies.agentManager.createAgent.mock.calls[0]?.[0].cwd as string; const agentCwd = z.string().parse(spies.agentManager.createAgent.mock.calls[0]?.[0].cwd);
const initialBranch = execSync("git branch --show-current", { cwd: agentCwd, stdio: "pipe" }) const initialBranch = execSync("git branch --show-current", { cwd: agentCwd, stdio: "pipe" })
.toString() .toString()
.trim(); .trim();
@@ -808,7 +804,7 @@ describe("create_agent MCP tool", () => {
background: true, background: true,
}); });
const agentCwd = spies.agentManager.createAgent.mock.calls[0]?.[0].cwd as string; const agentCwd = z.string().parse(spies.agentManager.createAgent.mock.calls[0]?.[0].cwd);
expect( expect(
execSync("git branch --show-current", { cwd: agentCwd, stdio: "pipe" }).toString().trim(), execSync("git branch --show-current", { cwd: agentCwd, stdio: "pipe" }).toString().trim(),
).toBe("existing-feature"); ).toBe("existing-feature");
@@ -1611,7 +1607,9 @@ describe("agent snapshot MCP serialization", () => {
const server = await createAgentMcpServer({ agentManager, agentStorage, logger }); const server = await createAgentMcpServer({ agentManager, agentStorage, logger });
const tool = registeredTool(server, "list_agents"); const tool = registeredTool(server, "list_agents");
const response = await tool.callback({}); const response = await tool.callback({});
const structured = response.structuredContent as { agents: Array<Record<string, unknown>> }; const structured = z
.object({ agents: z.array(z.record(z.unknown())) })
.parse(response.structuredContent);
expect(structured).toEqual({ expect(structured).toEqual({
agents: [ agents: [
@@ -1659,7 +1657,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "get_agent_status"); const tool = registeredTool(server, "get_agent_status");
const response = await tool.callback({ agentId: "archived-agent" }); const response = await tool.callback({ agentId: "archived-agent" });
@@ -1719,7 +1717,7 @@ describe("agent snapshot MCP serialization", () => {
const server = await createAgentMcpServer({ agentManager, agentStorage, logger }); const server = await createAgentMcpServer({ agentManager, agentStorage, logger });
const tool = registeredTool(server, "get_agent_status"); const tool = registeredTool(server, "get_agent_status");
const response = await tool.callback({ agentId: "full-detail-agent" }); const response = await tool.callback({ agentId: "full-detail-agent" });
const snapshot = response.structuredContent.snapshot as Record<string, unknown>; const snapshot = z.record(z.unknown()).parse(response.structuredContent.snapshot);
const parsed = AgentSnapshotPayloadSchema.safeParse(snapshot); const parsed = AgentSnapshotPayloadSchema.safeParse(snapshot);
if (!parsed.success) { if (!parsed.success) {
@@ -1790,7 +1788,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "get_agent_status"); const tool = registeredTool(server, "get_agent_status");
@@ -1834,7 +1832,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
callerAgentId: "caller-agent", callerAgentId: "caller-agent",
}); });
const tool = registeredTool(server, "list_agents"); const tool = registeredTool(server, "list_agents");
@@ -1883,7 +1881,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "list_agents"); const tool = registeredTool(server, "list_agents");
const response = await tool.callback({ const response = await tool.callback({
@@ -1924,7 +1922,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "list_agents"); const tool = registeredTool(server, "list_agents");
const response = await tool.callback({ includeArchived: true }); const response = await tool.callback({ includeArchived: true });
@@ -1967,7 +1965,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "list_agents"); const tool = registeredTool(server, "list_agents");
const response = await tool.callback({ cwd: "/tmp/repo", includeArchived: true }); const response = await tool.callback({ cwd: "/tmp/repo", includeArchived: true });
@@ -2075,7 +2073,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "list_agents"); const tool = registeredTool(server, "list_agents");
const response = await tool.callback({ includeArchived: true }); const response = await tool.callback({ includeArchived: true });
@@ -2115,7 +2113,7 @@ describe("agent snapshot MCP serialization", () => {
logger, logger,
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "get_agent_activity"); const tool = registeredTool(server, "get_agent_activity");
const response = await tool.callback({ agentId: "archived-activity-agent" }); const response = await tool.callback({ agentId: "archived-activity-agent" });
@@ -2153,7 +2151,7 @@ describe("agent snapshot MCP serialization", () => {
logger: createTestLogger(), logger: createTestLogger(),
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "get_agent_activity"); const tool = registeredTool(server, "get_agent_activity");
const response = await tool.callback({ agentId: "live-activity-agent", limit: 1 }); const response = await tool.callback({ agentId: "live-activity-agent", limit: 1 });
@@ -2184,7 +2182,7 @@ describe("agent snapshot MCP serialization", () => {
logger: createTestLogger(), logger: createTestLogger(),
providerRegistry: { providerRegistry: {
claude: createProviderDefinition({}), claude: createProviderDefinition({}),
} as unknown as Record<AgentProvider, ProviderDefinition>, },
}); });
const tool = registeredTool(server, "get_agent_activity"); const tool = registeredTool(server, "get_agent_activity");
const response = await tool.callback({ agentId: "live-activity-agent-2", limit: 2 }); const response = await tool.callback({ agentId: "live-activity-agent-2", limit: 2 });
@@ -1,49 +1,60 @@
import { expect, it, vi } from "vitest"; import { expect, it, vi } from "vitest";
import { createTestLogger } from "../../test-utils/test-logger.js"; import { createTestLogger } from "../../test-utils/test-logger.js";
import { AgentManager } from "./agent-manager.js";
import { AgentStorage } from "./agent-storage.js";
import { setupFinishNotification } from "./mcp-shared.js"; import { setupFinishNotification } from "./mcp-shared.js";
import type { AgentManager, AgentManagerEvent, ManagedAgent } from "./agent-manager.js"; import type { AgentManagerEvent, ManagedAgent } from "./agent-manager.js";
import type { AgentStorage } from "./agent-storage.js";
it("does not notify archived callers", async () => { it("does not notify archived callers", async () => {
let subscriber: ((event: AgentManagerEvent) => void) | null = null; let subscriber: ((event: AgentManagerEvent) => void) | null = null;
const childAgent = { const childAgent: ManagedAgent = Object.create(null);
id: "child-agent", Reflect.set(childAgent, "id", "child-agent");
lifecycle: "idle", Reflect.set(childAgent, "lifecycle", "idle");
config: { title: "Child Agent" }, Reflect.set(childAgent, "config", { title: "Child Agent" });
} as ManagedAgent;
const agentManager = { const callerAgent: ManagedAgent = Object.create(null);
getAgent: vi.fn((agentId: string) => { Reflect.set(callerAgent, "id", "caller-agent");
Reflect.set(callerAgent, "lifecycle", "idle");
Reflect.set(callerAgent, "config", { title: "Caller Agent" });
const streamAgentSpy = vi.fn(() => (async function* noop() {})());
const replaceAgentRunSpy = vi.fn(() => (async function* noop() {})());
const agentManager: AgentManager = Object.create(AgentManager.prototype);
Reflect.set(
agentManager,
"getAgent",
vi.fn((agentId: string) => {
if (agentId === "child-agent") { if (agentId === "child-agent") {
return childAgent; return childAgent;
} }
if (agentId === "caller-agent") { if (agentId === "caller-agent") {
return { return callerAgent;
id: "caller-agent",
lifecycle: "idle",
config: { title: "Caller Agent" },
} as ManagedAgent;
} }
return null; return null;
}), }),
subscribe: vi.fn((callback: (event: AgentManagerEvent) => void) => { );
Reflect.set(
agentManager,
"subscribe",
vi.fn((callback: (event: AgentManagerEvent) => void) => {
subscriber = callback; subscriber = callback;
return () => { return () => {
subscriber = null; subscriber = null;
}; };
}), }),
hasInFlightRun: vi.fn().mockReturnValue(false), );
streamAgent: vi.fn(() => (async function* noop() {})()), Reflect.set(agentManager, "hasInFlightRun", vi.fn().mockReturnValue(false));
replaceAgentRun: vi.fn(() => (async function* noop() {})()), Reflect.set(agentManager, "streamAgent", streamAgentSpy);
} as unknown as AgentManager; Reflect.set(agentManager, "replaceAgentRun", replaceAgentRunSpy);
const agentStorage = { const agentStorageGetSpy = vi.fn(async (agentId: string) =>
get: vi.fn(async (agentId: string) => agentId === "caller-agent" ? { archivedAt: "2024-01-01" } : null,
agentId === "caller-agent" ? { archivedAt: "2024-01-01" } : null, );
), const agentStorage: AgentStorage = Object.create(AgentStorage.prototype);
} as unknown as AgentStorage; Reflect.set(agentStorage, "get", agentStorageGetSpy);
setupFinishNotification({ setupFinishNotification({
agentManager, agentManager,
@@ -68,13 +79,9 @@ it("does not notify archived callers", async () => {
}); });
await vi.waitFor(() => { await vi.waitFor(() => {
expect(agentStorage.get).toHaveBeenCalledWith("caller-agent"); expect(agentStorageGetSpy).toHaveBeenCalledWith("caller-agent");
}); });
expect( expect(streamAgentSpy).not.toHaveBeenCalled();
(agentManager as unknown as { streamAgent: ReturnType<typeof vi.fn> }).streamAgent, expect(replaceAgentRunSpy).not.toHaveBeenCalled();
).not.toHaveBeenCalled();
expect(
(agentManager as unknown as { replaceAgentRun: ReturnType<typeof vi.fn> }).replaceAgentRun,
).not.toHaveBeenCalled();
}); });
@@ -1,6 +1,6 @@
import type { Logger } from "pino";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import { createTestLogger } from "../../test-utils/test-logger.js";
import { resolveAgentModel } from "./model-resolver.js"; import { resolveAgentModel } from "./model-resolver.js";
vi.mock("./provider-registry.js", () => ({ vi.mock("./provider-registry.js", () => ({
@@ -11,10 +11,20 @@ vi.mock("./provider-registry.js", () => ({
import { buildProviderRegistry } from "./provider-registry.js"; import { buildProviderRegistry } from "./provider-registry.js";
const mockedBuildProviderRegistry = vi.mocked(buildProviderRegistry); const mockedBuildProviderRegistry = vi.mocked(buildProviderRegistry);
const testLoggerWarn = vi.fn(); const testLogger = createTestLogger();
const testLogger = { warn: testLoggerWarn } as unknown as Logger; const testLoggerWarn = vi.spyOn(testLogger, "warn");
type ProviderRegistryMock = ReturnType<typeof buildProviderRegistry>; type ProviderRegistryMock = ReturnType<typeof buildProviderRegistry>;
function makeMockRegistry(
entries: Record<string, { enabled: boolean; fetchModels: ReturnType<typeof vi.fn> }>,
): ProviderRegistryMock {
const registry: ProviderRegistryMock = Object.create(null);
for (const [key, val] of Object.entries(entries)) {
Reflect.set(registry, key, val);
}
return registry;
}
describe("resolveAgentModel", () => { describe("resolveAgentModel", () => {
beforeEach(() => { beforeEach(() => {
mockedBuildProviderRegistry.mockReset(); mockedBuildProviderRegistry.mockReset();
@@ -22,11 +32,13 @@ describe("resolveAgentModel", () => {
}); });
it("returns the trimmed requested model when provided", async () => { it("returns the trimmed requested model when provided", async () => {
mockedBuildProviderRegistry.mockReturnValue({ mockedBuildProviderRegistry.mockReturnValue(
claude: { enabled: true, fetchModels: vi.fn() }, makeMockRegistry({
codex: { enabled: true, fetchModels: vi.fn() }, claude: { enabled: true, fetchModels: vi.fn() },
opencode: { enabled: true, fetchModels: vi.fn() }, codex: { enabled: true, fetchModels: vi.fn() },
} as unknown as ProviderRegistryMock); opencode: { enabled: true, fetchModels: vi.fn() },
}),
);
const result = await resolveAgentModel({ const result = await resolveAgentModel({
provider: "codex", provider: "codex",
@@ -44,11 +56,13 @@ describe("resolveAgentModel", () => {
{ id: "claude-3.5-haiku", isDefault: false }, { id: "claude-3.5-haiku", isDefault: false },
{ id: "claude-3.5-sonnet", isDefault: true }, { id: "claude-3.5-sonnet", isDefault: true },
]); ]);
mockedBuildProviderRegistry.mockReturnValue({ mockedBuildProviderRegistry.mockReturnValue(
claude: { enabled: true, fetchModels }, makeMockRegistry({
codex: { enabled: true, fetchModels: vi.fn() }, claude: { enabled: true, fetchModels },
opencode: { enabled: true, fetchModels: vi.fn() }, codex: { enabled: true, fetchModels: vi.fn() },
} as unknown as ProviderRegistryMock); opencode: { enabled: true, fetchModels: vi.fn() },
}),
);
const result = await resolveAgentModel({ const result = await resolveAgentModel({
provider: "claude", provider: "claude",
@@ -68,11 +82,13 @@ describe("resolveAgentModel", () => {
{ id: "model-a", isDefault: false }, { id: "model-a", isDefault: false },
{ id: "model-b", isDefault: false }, { id: "model-b", isDefault: false },
]); ]);
mockedBuildProviderRegistry.mockReturnValue({ mockedBuildProviderRegistry.mockReturnValue(
claude: { enabled: true, fetchModels: vi.fn() }, makeMockRegistry({
codex: { enabled: true, fetchModels }, claude: { enabled: true, fetchModels: vi.fn() },
opencode: { enabled: true, fetchModels: vi.fn() }, codex: { enabled: true, fetchModels },
} as unknown as ProviderRegistryMock); opencode: { enabled: true, fetchModels: vi.fn() },
}),
);
const result = await resolveAgentModel({ provider: "codex", logger: testLogger }); const result = await resolveAgentModel({ provider: "codex", logger: testLogger });
@@ -81,11 +97,13 @@ describe("resolveAgentModel", () => {
it("returns undefined when the catalog lookup fails", async () => { it("returns undefined when the catalog lookup fails", async () => {
const fetchModels = vi.fn().mockRejectedValue(new Error("boom")); const fetchModels = vi.fn().mockRejectedValue(new Error("boom"));
mockedBuildProviderRegistry.mockReturnValue({ mockedBuildProviderRegistry.mockReturnValue(
claude: { enabled: true, fetchModels: vi.fn() }, makeMockRegistry({
codex: { enabled: true, fetchModels }, claude: { enabled: true, fetchModels: vi.fn() },
opencode: { enabled: true, fetchModels: vi.fn() }, codex: { enabled: true, fetchModels },
} as unknown as ProviderRegistryMock); opencode: { enabled: true, fetchModels: vi.fn() },
}),
);
const result = await resolveAgentModel({ provider: "codex", logger: testLogger }); const result = await resolveAgentModel({ provider: "codex", logger: testLogger });
@@ -95,11 +113,13 @@ describe("resolveAgentModel", () => {
it("returns undefined for a disabled provider without fetching default models", async () => { it("returns undefined for a disabled provider without fetching default models", async () => {
const fetchModels = vi.fn().mockResolvedValue([{ id: "model-a", isDefault: true }]); const fetchModels = vi.fn().mockResolvedValue([{ id: "model-a", isDefault: true }]);
mockedBuildProviderRegistry.mockReturnValue({ mockedBuildProviderRegistry.mockReturnValue(
claude: { enabled: true, fetchModels: vi.fn() }, makeMockRegistry({
codex: { enabled: false, fetchModels }, claude: { enabled: true, fetchModels: vi.fn() },
opencode: { enabled: true, fetchModels: vi.fn() }, codex: { enabled: false, fetchModels },
} as unknown as ProviderRegistryMock); opencode: { enabled: true, fetchModels: vi.fn() },
}),
);
const result = await resolveAgentModel({ provider: "codex", logger: testLogger }); const result = await resolveAgentModel({ provider: "codex", logger: testLogger });
@@ -110,11 +130,13 @@ describe("resolveAgentModel", () => {
it("returns undefined for a requested model from a disabled provider", async () => { it("returns undefined for a requested model from a disabled provider", async () => {
const fetchModels = vi.fn(); const fetchModels = vi.fn();
mockedBuildProviderRegistry.mockReturnValue({ mockedBuildProviderRegistry.mockReturnValue(
claude: { enabled: true, fetchModels: vi.fn() }, makeMockRegistry({
codex: { enabled: false, fetchModels }, claude: { enabled: true, fetchModels: vi.fn() },
opencode: { enabled: true, fetchModels: vi.fn() }, codex: { enabled: false, fetchModels },
} as unknown as ProviderRegistryMock); opencode: { enabled: true, fetchModels: vi.fn() },
}),
);
const result = await resolveAgentModel({ const result = await resolveAgentModel({
provider: "codex", provider: "codex",
@@ -23,11 +23,12 @@ const mockState = vi.hoisted(() => {
isCommandAvailable: vi.fn(async (_command: string) => false), isCommandAvailable: vi.fn(async (_command: string) => false),
runtimeModels: new Map<string, AgentModelDefinition[]>(), runtimeModels: new Map<string, AgentModelDefinition[]>(),
reset() { reset() {
for (const key of Object.keys(this.constructorArgs) as Array< this.constructorArgs.claude = [];
keyof typeof this.constructorArgs this.constructorArgs.codex = [];
>) { this.constructorArgs.copilot = [];
this.constructorArgs[key] = []; this.constructorArgs.opencode = [];
} this.constructorArgs.pi = [];
this.constructorArgs.genericAcp = [];
this.isCommandAvailable.mockReset(); this.isCommandAvailable.mockReset();
this.isCommandAvailable.mockImplementation(async (_command: string) => false); this.isCommandAvailable.mockImplementation(async (_command: string) => false);
this.runtimeModels.clear(); this.runtimeModels.clear();
@@ -76,8 +77,10 @@ vi.mock("./providers/claude-agent.js", () => ({
} }
async isAvailable(): Promise<boolean> { async isAvailable(): Promise<boolean> {
const command = (this.runtimeSettings as { command?: { mode?: string; argv?: string[] } }) const command: { mode?: string; argv?: string[] } | undefined =
?.command; typeof this.runtimeSettings === "object" && this.runtimeSettings !== null
? Reflect.get(this.runtimeSettings, "command")
: undefined;
if (command?.mode === "replace") { if (command?.mode === "replace") {
const { isCommandAvailable } = await import("../../utils/executable.js"); const { isCommandAvailable } = await import("../../utils/executable.js");
return await isCommandAvailable(command.argv?.[0] ?? ""); return await isCommandAvailable(command.argv?.[0] ?? "");
@@ -122,8 +125,10 @@ vi.mock("./providers/codex-app-server-agent.js", () => ({
} }
async isAvailable(): Promise<boolean> { async isAvailable(): Promise<boolean> {
const command = (this.runtimeSettings as { command?: { mode?: string; argv?: string[] } }) const command: { mode?: string; argv?: string[] } | undefined =
?.command; typeof this.runtimeSettings === "object" && this.runtimeSettings !== null
? Reflect.get(this.runtimeSettings, "command")
: undefined;
if (command?.mode === "replace") { if (command?.mode === "replace") {
const { isCommandAvailable } = await import("../../utils/executable.js"); const { isCommandAvailable } = await import("../../utils/executable.js");
return await isCommandAvailable(command.argv?.[0] ?? ""); return await isCommandAvailable(command.argv?.[0] ?? "");
@@ -170,8 +175,10 @@ vi.mock("./providers/copilot-acp-agent.js", () => ({
} }
async isAvailable(): Promise<boolean> { async isAvailable(): Promise<boolean> {
const command = (this.runtimeSettings as { command?: { mode?: string; argv?: string[] } }) const command: { mode?: string; argv?: string[] } | undefined =
?.command; typeof this.runtimeSettings === "object" && this.runtimeSettings !== null
? Reflect.get(this.runtimeSettings, "command")
: undefined;
if (command?.mode === "replace") { if (command?.mode === "replace") {
const { isCommandAvailable } = await import("../../utils/executable.js"); const { isCommandAvailable } = await import("../../utils/executable.js");
return await isCommandAvailable(command.argv?.[0] ?? ""); return await isCommandAvailable(command.argv?.[0] ?? "");
@@ -545,18 +552,19 @@ test("derived provider inherits and merges disallowedTools from base", () => {
}, },
}); });
const zaiArgs = mockState.constructorArgs.claude.find( const zaiArgs = mockState.constructorArgs.claude.find((entry) => {
(entry) => const disallowedTools: string[] | undefined =
Array.isArray((entry.runtimeSettings as { disallowedTools?: string[] })?.disallowedTools) && typeof entry.runtimeSettings === "object" && entry.runtimeSettings !== null
(entry.runtimeSettings as { disallowedTools: string[] }).disallowedTools.includes( ? Reflect.get(entry.runtimeSettings, "disallowedTools")
"ComputerUse", : undefined;
), return Array.isArray(disallowedTools) && disallowedTools.includes("ComputerUse");
); });
expect(zaiArgs).toBeDefined(); expect(zaiArgs).toBeDefined();
expect((zaiArgs!.runtimeSettings as { disallowedTools: string[] }).disallowedTools).toEqual([ const zaiDisallowedTools: string[] =
"WebSearch", typeof zaiArgs!.runtimeSettings === "object" && zaiArgs!.runtimeSettings !== null
"ComputerUse", ? Reflect.get(zaiArgs!.runtimeSettings, "disallowedTools")
]); : [];
expect(zaiDisallowedTools).toEqual(["WebSearch", "ComputerUse"]);
}); });
test("extension inherits base override — override claude command, zai extends claude gets overridden command", () => { test("extension inherits base override — override claude command, zai extends claude gets overridden command", () => {
@@ -574,11 +582,13 @@ test("extension inherits base override — override claude command, zai extends
expect(mockState.constructorArgs.claude).toHaveLength(2); expect(mockState.constructorArgs.claude).toHaveLength(2);
expect( expect(
mockState.constructorArgs.claude.every( mockState.constructorArgs.claude.every((entry) => {
(entry) => const command: { argv?: string[] } | undefined =
(entry.runtimeSettings as { command?: { argv?: string[] } }).command?.argv?.[0] === typeof entry.runtimeSettings === "object" && entry.runtimeSettings !== null
"/opt/custom-claude", ? Reflect.get(entry.runtimeSettings, "command")
), : undefined;
return command?.argv?.[0] === "/opt/custom-claude";
}),
).toBe(true); ).toBe(true);
}); });