From fb135d047c9719cb0cf34f2be844e4704c579ef3 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 12 May 2026 14:23:05 +0900 Subject: [PATCH] test(tools): remove unsafe test assertions Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../create-background-task.metadata.test.ts | 8 +-- .../create-background-task.test.ts | 8 +-- src/tools/background-task/tools.test.ts | 28 ++++---- .../call-omo-agent/agent-resolver.test.ts | 2 +- .../call-omo-agent/session-creator.test.ts | 4 +- .../subagent-session-creator.test.ts | 4 +- .../call-omo-agent/sync-executor.test.ts | 2 +- src/tools/call-omo-agent/sync-executor.ts | 10 ++- src/tools/delegate-task/available-models.ts | 31 +++++--- .../delegate-task/category-resolver.test.ts | 4 +- .../delegate-task/metadata-await.test.ts | 4 +- .../metadata-model-unification.test.ts | 48 ++++++------- .../metadata-task-id-consistency.test.ts | 56 +++++++-------- src/tools/delegate-task/task-schema.test.ts | 4 +- .../unstable-agent-permission.test.ts | 4 +- .../hashline-edit/normalize-edits.test.ts | 4 +- src/tools/hashline-edit/tools.test.ts | 4 +- .../look-at/multimodal-agent-metadata.test.ts | 4 +- src/tools/look-at/session-poller.test.ts | 12 ++-- src/tools/look-at/tools.test.ts | 70 +++++++++---------- src/tools/lsp/client.test.ts | 4 +- src/tools/lsp/lsp-process.ts | 28 +++++++- src/tools/session-manager/storage.test.ts | 10 +-- .../zauc-mocks-skill-tools/tools.test.ts | 2 +- 24 files changed, 199 insertions(+), 156 deletions(-) diff --git a/src/tools/background-task/create-background-task.metadata.test.ts b/src/tools/background-task/create-background-task.metadata.test.ts index d21e69c09..1378b2162 100644 --- a/src/tools/background-task/create-background-task.metadata.test.ts +++ b/src/tools/background-task/create-background-task.metadata.test.ts @@ -18,7 +18,7 @@ describe("createBackgroundTask metadata", () => { // #given clearPendingStore() - const manager = { + const manager = testCoerce({ launch: mock(() => Promise.resolve({ id: "task-1", sessionID: null, @@ -27,12 +27,12 @@ describe("createBackgroundTask metadata", () => { status: "pending", })), getTask: mock(() => undefined), - } as unknown as BackgroundManager - const client = { + }) + const client = testCoerce({ session: { messages: mock(() => Promise.resolve({ data: [] })), }, - } as unknown as PluginInput["client"] + }) let capturedMetadata: { title?: string; metadata?: Record } | undefined const tool = createBackgroundTask(manager, client) diff --git a/src/tools/background-task/create-background-task.test.ts b/src/tools/background-task/create-background-task.test.ts index a7b588ff6..89b378ac9 100644 --- a/src/tools/background-task/create-background-task.test.ts +++ b/src/tools/background-task/create-background-task.test.ts @@ -21,16 +21,16 @@ describe("createBackgroundTask", () => { })) const getTaskMock = mock() - const mockManager = { + const mockManager = testCoerce({ launch: launchMock, getTask: getTaskMock, - } as unknown as BackgroundManager + }) - const mockClient = { + const mockClient = testCoerce({ session: { messages: mock(() => Promise.resolve({ data: [] })), }, - } as unknown as PluginInput["client"] + }) const tool = createBackgroundTask(mockManager, mockClient) diff --git a/src/tools/background-task/tools.test.ts b/src/tools/background-task/tools.test.ts index 12404bf72..0db5ce046 100644 --- a/src/tools/background-task/tools.test.ts +++ b/src/tools/background-task/tools.test.ts @@ -66,10 +66,10 @@ describe("background_output full_session", () => { const manager = createMockManager(task) const client = createMockClient({}) const tool = createBackgroundOutput(manager, client) - const ctxWithCallId = { + const ctxWithCallId = testCoerce({ ...mockContext, callID: "call-1", - } as unknown as ToolContext + }) // #when await tool.execute({ task_id: "task-1" }, ctxWithCallId) @@ -93,10 +93,10 @@ describe("background_output full_session", () => { const manager = createMockManager(task) const client = createMockClient({}) const tool = createBackgroundOutput(manager, client) - const ctxWithCallId = { + const ctxWithCallId = testCoerce({ ...mockContext, callID: "call-1", - } as unknown as ToolContext + }) // #when await tool.execute({ task_id: "task-1" }, ctxWithCallId) @@ -387,7 +387,7 @@ describe("background_cancel", () => { // #given const task = createTask({ status: "running" }) const cancelled: string[] = [] - const manager = { + const manager = testCoerce({ getTask: (id: string) => (id === task.id ? task : undefined), getAllDescendantTasks: () => [task], cancelTask: async (taskId: string) => { @@ -395,7 +395,7 @@ describe("background_cancel", () => { task.status = "cancelled" return true }, - } as unknown as BackgroundManager + }) const client = { session: { abort: async () => ({}) } } as BackgroundCancelClient const tool = createBackgroundCancel(manager, client) @@ -412,7 +412,7 @@ describe("background_cancel", () => { const taskA = createTask({ id: "task-a", status: "running" }) const taskB = createTask({ id: "task-b", status: "pending" }) const cancelled: string[] = [] - const manager = { + const manager = testCoerce({ getTask: () => undefined, getAllDescendantTasks: () => [taskA, taskB], cancelTask: async (taskId: string) => { @@ -421,7 +421,7 @@ describe("background_cancel", () => { task.status = "cancelled" return true }, - } as unknown as BackgroundManager + }) const client = { session: { abort: async () => ({}) } } as BackgroundCancelClient const tool = createBackgroundCancel(manager, client) @@ -437,7 +437,7 @@ describe("background_cancel", () => { // #given const taskA = createTask({ id: "task-a", status: "running", sessionId: "ses-a", description: "running task" }) const taskB = createTask({ id: "task-b", status: "pending", sessionId: undefined, description: "pending task" }) - const manager = { + const manager = testCoerce({ getTask: () => undefined, getAllDescendantTasks: () => [taskA, taskB], cancelTask: async (taskId: string) => { @@ -445,7 +445,7 @@ describe("background_cancel", () => { task.status = "cancelled" return true }, - } as unknown as BackgroundManager + }) const client = { session: { abort: async () => ({}) } } as BackgroundCancelClient const tool = createBackgroundCancel(manager, client) @@ -461,7 +461,7 @@ describe("background_cancel", () => { // #given const task = createTask({ id: "task-1", status: "running" }) const cancelOptions: Array<{ taskId: string; options: unknown }> = [] - const manager = { + const manager = testCoerce({ getTask: (id: string) => (id === task.id ? task : undefined), getAllDescendantTasks: () => [task], cancelTask: async (taskId: string, options?: unknown) => { @@ -469,7 +469,7 @@ describe("background_cancel", () => { task.status = "cancelled" return true }, - } as unknown as BackgroundManager + }) const client = { session: { abort: async () => ({}) } } as BackgroundCancelClient const tool = createBackgroundCancel(manager, client) @@ -487,7 +487,7 @@ describe("background_cancel", () => { // #given const task = createTask({ id: "task-1", status: "running" }) const cancelOptions: Array<{ taskId: string; options: unknown }> = [] - const manager = { + const manager = testCoerce({ getTask: (id: string) => (id === task.id ? task : undefined), getAllDescendantTasks: () => [task], cancelTask: async (taskId: string, options?: unknown) => { @@ -495,7 +495,7 @@ describe("background_cancel", () => { task.status = "cancelled" return true }, - } as unknown as BackgroundManager + }) const client = { session: { abort: async () => ({}) } } as BackgroundCancelClient const tool = createBackgroundCancel(manager, client) diff --git a/src/tools/call-omo-agent/agent-resolver.test.ts b/src/tools/call-omo-agent/agent-resolver.test.ts index 773cd27aa..f2afa1f18 100644 --- a/src/tools/call-omo-agent/agent-resolver.test.ts +++ b/src/tools/call-omo-agent/agent-resolver.test.ts @@ -2,7 +2,7 @@ const { describe, test, expect, mock, beforeEach } = require("bun:test") const { resolveCallableAgents, clearCallableAgentsCache } = require("./agent-resolver") const { ALLOWED_AGENTS } = require("./constants") -function createMockClient(agents = []) { +function createMockClient(agents: Array> = []) { return { app: { agents: mock(() => Promise.resolve({ data: agents })), diff --git a/src/tools/call-omo-agent/session-creator.test.ts b/src/tools/call-omo-agent/session-creator.test.ts index db231651d..b3c3bc23c 100644 --- a/src/tools/call-omo-agent/session-creator.test.ts +++ b/src/tools/call-omo-agent/session-creator.test.ts @@ -37,12 +37,12 @@ describe("call-omo-agent createOrGetSession", () => { } // when - const result = await createOrGetSession(args as any, toolContext as any, ctx as any) + const result = await createOrGetSession(testCoerce(args), testCoerce(toolContext), testCoerce(ctx)) // then expect(result).toEqual({ sessionID: "ses_child", isNew: true }) expect(createCalls).toHaveLength(1) - const createBody = (createCalls[0] as any)?.body + const createBody = (testCoerce(createCalls[0]))?.body expect(createBody?.parentID).toBe("ses_parent") expect(createBody?.permission).toBeUndefined() expect(subagentSessions.has("ses_child")).toBe(true) diff --git a/src/tools/call-omo-agent/subagent-session-creator.test.ts b/src/tools/call-omo-agent/subagent-session-creator.test.ts index dea60d524..e98d533dc 100644 --- a/src/tools/call-omo-agent/subagent-session-creator.test.ts +++ b/src/tools/call-omo-agent/subagent-session-creator.test.ts @@ -19,7 +19,7 @@ describe("call-omo-agent resolveOrCreateSessionId", () => { const { parentDirectory, contextDirectory } = options const parentSessionData = parentDirectory ? { data: { directory: parentDirectory } } : { data: {} } - const ctx = { + const ctx = testCoerce[0]>({ directory: contextDirectory, client: { session: { @@ -31,7 +31,7 @@ describe("call-omo-agent resolveOrCreateSessionId", () => { }, }, }, - } as unknown as Parameters[0] + }) const args = { description: "sync test", diff --git a/src/tools/call-omo-agent/sync-executor.test.ts b/src/tools/call-omo-agent/sync-executor.test.ts index aab485706..1ea512269 100644 --- a/src/tools/call-omo-agent/sync-executor.test.ts +++ b/src/tools/call-omo-agent/sync-executor.test.ts @@ -389,7 +389,7 @@ describe("executeSync", () => { } //#when - await executeSync(args, toolContext, ctx as any, deps, undefined, spawnReservation) + await executeSync(args, toolContext, testCoerce(ctx), deps, undefined, spawnReservation) //#then expect(spawnReservation.commit).toHaveBeenCalledTimes(1) diff --git a/src/tools/call-omo-agent/sync-executor.ts b/src/tools/call-omo-agent/sync-executor.ts index 56e22a80a..31ae8beb8 100644 --- a/src/tools/call-omo-agent/sync-executor.ts +++ b/src/tools/call-omo-agent/sync-executor.ts @@ -14,6 +14,10 @@ type SessionWithPromptAsync = { promptAsync: (opts: { path: { id: string }; body: Record }) => Promise } +function hasPromptAsync(session: PluginInput["client"]["session"]): session is PluginInput["client"]["session"] & SessionWithPromptAsync { + return "promptAsync" in session && typeof session.promptAsync === "function" +} + type ExecuteSyncDeps = { createOrGetSession: typeof createOrGetSession waitForCompletion: typeof waitForCompletion @@ -102,7 +106,11 @@ export async function executeSync( const normalizedSubagentType = stripAgentListSortPrefix(args.subagent_type) try { - await (ctx.client.session as unknown as SessionWithPromptAsync).promptAsync({ + if (!hasPromptAsync(ctx.client.session)) { + return `Error: Failed to send prompt: promptAsync is not available on this OpenCode client.\n\n\nsession_id: ${sessionID}\n` + } + + await ctx.client.session.promptAsync({ path: { id: sessionID }, body: { agent: normalizedSubagentType, diff --git a/src/tools/delegate-task/available-models.ts b/src/tools/delegate-task/available-models.ts index 711ac1920..9ec078cf7 100644 --- a/src/tools/delegate-task/available-models.ts +++ b/src/tools/delegate-task/available-models.ts @@ -1,7 +1,25 @@ import type { OpencodeClient } from "./types" import { log } from "../../shared/logger" +import { isRecord } from "../../shared/record-type-guard" import { readConnectedProvidersCache, readProviderModelsCache } from "../../shared/connected-providers-cache" +type ModelListClient = OpencodeClient & { + model: { list: () => Promise } +} + +function hasModelList(client: OpencodeClient): client is ModelListClient { + return "model" in client && isRecord(client.model) && typeof client.model.list === "function" +} + +function isModelRow(value: unknown): value is { provider: string; id: string } { + return isRecord(value) && typeof value.provider === "string" && typeof value.id === "string" +} + +function extractModelRows(result: unknown): Array<{ provider: string; id: string }> { + const rows = Array.isArray(result) ? result : isRecord(result) && Array.isArray(result.data) ? result.data : [] + return rows.filter(isModelRow) +} + function addFromProviderModels( out: Set, providerID: string, @@ -35,24 +53,17 @@ export async function getAvailableModelsForDelegateTask(client: OpencodeClient): return new Set() } - const modelList = (client as unknown as { model?: { list?: () => Promise } }) - ?.model - ?.list - - if (!modelList) { + if (!hasModelList(client)) { return new Set() } try { - const result = await modelList() - const rows = Array.isArray(result) - ? result - : ((result as { data?: unknown }).data as Array<{ provider?: string; id?: string }> | undefined) ?? [] + const result = await client.model.list() + const rows = extractModelRows(result) const connected = new Set(connectedProviders) const out = new Set() for (const row of rows) { - if (!row?.provider || !row?.id) continue if (!connected.has(row.provider)) continue out.add(`${row.provider}/${row.id}`) } diff --git a/src/tools/delegate-task/category-resolver.test.ts b/src/tools/delegate-task/category-resolver.test.ts index 8077054d0..9f7b72e44 100644 --- a/src/tools/delegate-task/category-resolver.test.ts +++ b/src/tools/delegate-task/category-resolver.test.ts @@ -26,8 +26,8 @@ describe("resolveCategoryExecution", () => { }) const createMockExecutorContext = (): ExecutorContext => ({ - client: {} as any, - manager: {} as any, + client: testCoerce({}), + manager: testCoerce({}), directory: "/tmp/test", userCategories: {}, sisyphusJuniorModel: undefined, diff --git a/src/tools/delegate-task/metadata-await.test.ts b/src/tools/delegate-task/metadata-await.test.ts index 6592457c5..247df5fdc 100644 --- a/src/tools/delegate-task/metadata-await.test.ts +++ b/src/tools/delegate-task/metadata-await.test.ts @@ -28,7 +28,7 @@ describe("task tool metadata awaiting", () => { subagent_type: "explore", } - const executorCtx = { + const executorCtx = testCoerce({ manager: { launch: async () => ({ id: "task_1", @@ -40,7 +40,7 @@ describe("task tool metadata awaiting", () => { }), getTask: () => undefined, }, - } as any + }) const parentContext = { sessionID: "ses_parent", diff --git a/src/tools/delegate-task/metadata-model-unification.test.ts b/src/tools/delegate-task/metadata-model-unification.test.ts index 3a64022ab..b93824c21 100644 --- a/src/tools/delegate-task/metadata-model-unification.test.ts +++ b/src/tools/delegate-task/metadata-model-unification.test.ts @@ -63,7 +63,7 @@ describe("metadata model unification", () => { load_skills: [], run_in_background: true, subagent_type: "explore", } - await executeBackgroundTask(args, ctx, { + await executeBackgroundTask(args, ctx, testCoerce({ manager: { launch: async () => ({ id: "bg_1", description: "test", agent: "explore", @@ -71,7 +71,7 @@ describe("metadata model unification", () => { }), getTask: () => undefined, }, - } as any, parentContext, "explore", MODEL, undefined) + }), parentContext, "explore", MODEL, undefined) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -92,7 +92,7 @@ describe("metadata model unification", () => { } await executeUnstableAgentTask( args, ctx, - { + testCoerce({ manager: { launch: async () => launchedTask, getTask: () => launchedTask, @@ -109,7 +109,7 @@ describe("metadata model unification", () => { }, }, syncPollTimeoutMs: 100, - } as any, + }), parentContext, "explore", MODEL, undefined, "anthropic/claude-sonnet-4-6", ) @@ -126,14 +126,14 @@ describe("metadata model unification", () => { load_skills: [], run_in_background: true, task_id: "ses_resumed", } - await executeBackgroundContinuation(args, ctx, { + await executeBackgroundContinuation(args, ctx, testCoerce({ manager: { resume: async () => ({ id: "bg_2", description: "continue", agent: "explore", status: "running", sessionId: "ses_resumed", model: MODEL, }), }, - } as any, parentContext) + }), parentContext) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -153,7 +153,7 @@ describe("metadata model unification", () => { fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ @@ -162,7 +162,7 @@ describe("metadata model unification", () => { prompt: async () => ({}), }, }, - } as any, parentContext, deps) + }), parentContext, deps) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -206,7 +206,7 @@ describe("metadata model unification", () => { load_skills: [], run_in_background: true, subagent_type: "explore", } - await executeBackgroundTask(args, ctx, { + await executeBackgroundTask(args, ctx, testCoerce({ manager: { launch: async () => ({ id: "bg_1", description: "test", agent: "explore", @@ -214,7 +214,7 @@ describe("metadata model unification", () => { }), getTask: () => undefined, }, - } as any, parentContext, "explore", undefined, undefined) + }), parentContext, "explore", undefined, undefined) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -236,7 +236,7 @@ describe("metadata model unification", () => { await executeUnstableAgentTask( args, ctx, - { + testCoerce({ manager: { launch: async () => launchedTask, getTask: () => launchedTask, @@ -253,7 +253,7 @@ describe("metadata model unification", () => { }, }, syncPollTimeoutMs: 100, - } as any, + }), parentContext, "explore", undefined, undefined, "anthropic/claude-sonnet-4-6", ) @@ -270,14 +270,14 @@ describe("metadata model unification", () => { load_skills: [], run_in_background: true, task_id: "ses_resumed", } - await executeBackgroundContinuation(args, ctx, { + await executeBackgroundContinuation(args, ctx, testCoerce({ manager: { resume: async () => ({ id: "bg_2", description: "continue", agent: "explore", status: "running", sessionId: "ses_resumed", }), }, - } as any, parentContext) + }), parentContext) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -297,14 +297,14 @@ describe("metadata model unification", () => { fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ data: [] }), prompt: async () => ({}), }, }, - } as any, parentContext, deps) + }), parentContext, deps) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -381,7 +381,7 @@ describe("metadata model unification", () => { category: "visual-engineering", load_skills: [], run_in_background: true, subagent_type: "explore", } - await executeBackgroundTask(args, ctx, { + await executeBackgroundTask(args, ctx, testCoerce({ manager: { launch: async () => ({ id: "bg_variant", description: "test", agent: "explore", @@ -389,7 +389,7 @@ describe("metadata model unification", () => { }), getTask: () => undefined, }, - } as any, parentContext, "explore", MODEL_WITH_VARIANT, undefined) + }), parentContext, "explore", MODEL_WITH_VARIANT, undefined) const meta = ctx.captured.find((metadataEvent: any) => metadataEvent.metadata?.sessionId) expect(meta).toBeDefined() @@ -411,7 +411,7 @@ describe("metadata model unification", () => { await executeUnstableAgentTask( args, ctx, - { + testCoerce({ manager: { launch: async () => launchedTask, getTask: () => launchedTask, @@ -428,7 +428,7 @@ describe("metadata model unification", () => { }, }, syncPollTimeoutMs: 100, - } as any, + }), parentContext, "explore", MODEL_WITH_VARIANT, undefined, "google/gemini-3.1-pro high", ) @@ -445,14 +445,14 @@ describe("metadata model unification", () => { load_skills: [], run_in_background: true, task_id: "ses_resumed_variant", } - await executeBackgroundContinuation(args, ctx, { + await executeBackgroundContinuation(args, ctx, testCoerce({ manager: { resume: async () => ({ id: "bg_resume_variant", description: "continue", agent: "explore", status: "running", sessionId: "ses_resumed_variant", model: MODEL_WITH_VARIANT, }), }, - } as any, parentContext) + }), parentContext) const meta = ctx.captured.find((metadataEvent: any) => metadataEvent.metadata?.sessionId) expect(meta).toBeDefined() @@ -472,7 +472,7 @@ describe("metadata model unification", () => { fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ @@ -481,7 +481,7 @@ describe("metadata model unification", () => { prompt: async () => ({}), }, }, - } as any, parentContext, deps) + }), parentContext, deps) const meta = ctx.captured.find((metadataEvent: any) => metadataEvent.metadata?.sessionId) expect(meta).toBeDefined() diff --git a/src/tools/delegate-task/metadata-task-id-consistency.test.ts b/src/tools/delegate-task/metadata-task-id-consistency.test.ts index 23ce7d64a..209dc19ac 100644 --- a/src/tools/delegate-task/metadata-task-id-consistency.test.ts +++ b/src/tools/delegate-task/metadata-task-id-consistency.test.ts @@ -64,7 +64,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { load_skills: [], run_in_background: true, subagent_type: "explore", } - await executeBackgroundTask(args, ctx, { + await executeBackgroundTask(args, ctx, testCoerce({ manager: { launch: async () => ({ id: "bg_abc123", description: "test", agent: "explore", @@ -72,7 +72,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { }), getTask: () => undefined, }, - } as any, parentContext, "explore", MODEL, undefined) + }), parentContext, "explore", MODEL, undefined) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -98,7 +98,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { await executeUnstableAgentTask( args, ctx, - { + testCoerce({ manager: { launch: async () => launchedTask, getTask: () => launchedTask, @@ -115,7 +115,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { }, }, syncPollTimeoutMs: 100, - } as any, + }), parentContext, "explore", MODEL, undefined, "anthropic/claude-sonnet-4-6", ) @@ -136,14 +136,14 @@ describe("taskId and backgroundTaskId metadata consistency", () => { load_skills: [], run_in_background: true, task_id: "ses_resumed_x", } - await executeBackgroundContinuation(args, ctx, { + await executeBackgroundContinuation(args, ctx, testCoerce({ manager: { resume: async () => ({ id: "bg_resumed_y", description: "continue", agent: "explore", status: "running", sessionId: "ses_resumed_x", model: MODEL, }), }, - } as any, parentContext) + }), parentContext) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -160,14 +160,14 @@ describe("taskId and backgroundTaskId metadata consistency", () => { load_skills: [], run_in_background: true, task_id: "ses_resumed_x", } - await executeBackgroundContinuation(args, ctx, { + await executeBackgroundContinuation(args, ctx, testCoerce({ manager: { resume: async () => ({ id: "bg_resumed_y", description: "continue", agent: "explore", status: "running", sessionId: "ses_resumed_x", model: MODEL, category: "deep", }), }, - } as any, parentContext) + }), parentContext) const meta = ctx.captured.find((item: any) => item.metadata?.sessionId) expect(meta).toBeDefined() @@ -187,14 +187,14 @@ describe("taskId and backgroundTaskId metadata consistency", () => { task_id: "ses_resumed_x", } - await executeBackgroundContinuation(args, ctx, { + await executeBackgroundContinuation(args, ctx, testCoerce({ manager: { resume: async () => ({ id: "bg_resumed_y", description: "continue", agent: "explore", status: "running", sessionId: "ses_resumed_x", model: MODEL, }), }, - } as any, parentContext) + }), parentContext) const meta = ctx.captured.find((item: any) => item.metadata?.sessionId) expect(meta).toBeDefined() @@ -216,7 +216,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ @@ -225,7 +225,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { prompt: async () => ({}), }, }, - } as any, parentContext, deps) + }), parentContext, deps) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -246,7 +246,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ @@ -255,7 +255,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { prompt: async () => ({}), }, }, - } as any, parentContext, deps) + }), parentContext, deps) const meta = ctx.captured.find((item: any) => item.metadata?.sessionId) expect(meta).toBeDefined() @@ -275,7 +275,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ @@ -284,7 +284,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { prompt: async () => ({}), }, }, - } as any, parentContext, deps) + }), parentContext, deps) const meta = ctx.captured.find((item: any) => item.metadata?.sessionId) expect(meta).toBeDefined() @@ -309,7 +309,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ @@ -318,7 +318,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { prompt: async () => ({}), }, }, - } as any, parentContext, deps) + }), parentContext, deps) const meta = ctx.captured.find((item: any) => item.metadata?.sessionId) expect(meta).toBeDefined() @@ -368,7 +368,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { run_in_background: true, } - await executeBackgroundTask(args, ctx, { + await executeBackgroundTask(args, ctx, testCoerce({ manager: { launch: async () => ({ id: "bg_abc123", description: "test", agent: "Sisyphus-Junior", @@ -376,7 +376,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { }), getTask: () => undefined, }, - } as any, parentContext, "Sisyphus-Junior", MODEL, undefined) + }), parentContext, "Sisyphus-Junior", MODEL, undefined) const meta = ctx.captured.find((item: any) => item.metadata?.sessionId) expect(meta).toBeDefined() @@ -402,7 +402,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { await executeUnstableAgentTask( args, ctx, - { + testCoerce({ manager: { launch: async () => launchedTask, getTask: () => launchedTask, @@ -419,7 +419,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { }, }, syncPollTimeoutMs: 100, - } as any, + }), parentContext, "Sisyphus-Junior", MODEL, undefined, "anthropic/claude-sonnet-4-6", ) @@ -438,14 +438,14 @@ describe("taskId and backgroundTaskId metadata consistency", () => { load_skills: [], run_in_background: true, task_id: "ses_resume_title", } - await executeBackgroundContinuation(args, ctx, { + await executeBackgroundContinuation(args, ctx, testCoerce({ manager: { resume: async () => ({ id: "bg_resume_title", description: "continue work", agent: "explore", status: "running", sessionId: "ses_resume_title", model: MODEL, }), }, - } as any, parentContext) + }), parentContext) const meta = ctx.captured.find((item: any) => item.metadata?.sessionId) expect(meta).toBeDefined() @@ -460,7 +460,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { load_skills: [], run_in_background: false, task_id: "ses_sync_title", } - await executeSyncContinuation(args, ctx, { + await executeSyncContinuation(args, ctx, testCoerce({ client: { session: { messages: async () => ({ @@ -469,7 +469,7 @@ describe("taskId and backgroundTaskId metadata consistency", () => { prompt: async () => ({}), }, }, - } as any, parentContext, { + }), parentContext, { pollSyncSession: async () => null, fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), }) @@ -500,8 +500,8 @@ describe("taskId and backgroundTaskId metadata consistency", () => { }, } - const bgOutput = createBackgroundOutput(manager as any, client as any) - await bgOutput.execute({ task_id: "bg_output_xyz" } as any, ctx as any) + const bgOutput = createBackgroundOutput(testCoerce(manager), testCoerce(client)) + await bgOutput.execute(testCoerce({ task_id: "bg_output_xyz" }), testCoerce(ctx)) const meta = ctx.captured.find((m: any) => m.metadata?.backgroundTaskId) expect(meta).toBeDefined() diff --git a/src/tools/delegate-task/task-schema.test.ts b/src/tools/delegate-task/task-schema.test.ts index c50d175bc..c6747b834 100644 --- a/src/tools/delegate-task/task-schema.test.ts +++ b/src/tools/delegate-task/task-schema.test.ts @@ -18,14 +18,14 @@ function createDelegateTask(...args: Parameters(toolDefinition.args.category) //#then expect(categorySchema.def.type).toBe("optional") diff --git a/src/tools/delegate-task/unstable-agent-permission.test.ts b/src/tools/delegate-task/unstable-agent-permission.test.ts index 50b96bad6..31eebc084 100644 --- a/src/tools/delegate-task/unstable-agent-permission.test.ts +++ b/src/tools/delegate-task/unstable-agent-permission.test.ts @@ -33,7 +33,7 @@ describe("executeUnstableAgentTask session permission", () => { metadata: () => {}, abort: new AbortController().signal, } satisfies Parameters[1] - const executorContext = { + const executorContext = testCoerce[2]>({ manager: mockManager, client: { session: { @@ -41,7 +41,7 @@ describe("executeUnstableAgentTask session permission", () => { messages: async () => ({ data: [] }), }, }, - } as unknown as Parameters[2] + }) const parentContext = { sessionID: "parent-session", messageID: "msg_parent", diff --git a/src/tools/hashline-edit/normalize-edits.test.ts b/src/tools/hashline-edit/normalize-edits.test.ts index 45cf6f253..66ee57596 100644 --- a/src/tools/hashline-edit/normalize-edits.test.ts +++ b/src/tools/hashline-edit/normalize-edits.test.ts @@ -51,9 +51,9 @@ describe("normalizeHashlineEdits", () => { it("rejects legacy payload without op", () => { //#given - const input = [{ type: "set_line", line: "2#VK", text: "updated" }] as unknown as Parameters< + const input = testCoerce[0] + >[0]>([{ type: "set_line", line: "2#VK", text: "updated" }]) //#when / #then expect(() => normalizeHashlineEdits(input)).toThrow(/legacy format was removed/i) diff --git a/src/tools/hashline-edit/tools.test.ts b/src/tools/hashline-edit/tools.test.ts index 1158ca3d2..e362a6364 100644 --- a/src/tools/hashline-edit/tools.test.ts +++ b/src/tools/hashline-edit/tools.test.ts @@ -8,14 +8,14 @@ import * as os from "node:os" import * as path from "node:path" function createMockContext(): ToolContext { - return { + return testCoerce({ sessionID: "test", messageID: "test", agent: "test", abort: new AbortController().signal, metadata: mock(() => {}), ask: async () => {}, - } as unknown as ToolContext + }) } describe("createHashlineEditTool", () => { diff --git a/src/tools/look-at/multimodal-agent-metadata.test.ts b/src/tools/look-at/multimodal-agent-metadata.test.ts index aa057eb34..3286c9482 100644 --- a/src/tools/look-at/multimodal-agent-metadata.test.ts +++ b/src/tools/look-at/multimodal-agent-metadata.test.ts @@ -32,8 +32,8 @@ describe("resolveMultimodalLookerAgentMetadata", () => { afterEach(() => { clearVisionCapableModelsCache() - ;(modelAvailability.fetchAvailableModels as unknown as { mockRestore?: () => void }).mockRestore?.() - ;(connectedProvidersCache.readConnectedProvidersCache as unknown as { mockRestore?: () => void }).mockRestore?.() + ;(testCoerce<{ mockRestore?: () => void }>(modelAvailability.fetchAvailableModels)).mockRestore?.() + ;(testCoerce<{ mockRestore?: () => void }>(connectedProvidersCache.readConnectedProvidersCache)).mockRestore?.() }) test("returns configured multimodal-looker model when it already matches a vision-capable override", async () => { diff --git a/src/tools/look-at/session-poller.test.ts b/src/tools/look-at/session-poller.test.ts index 757327a3d..70908255f 100644 --- a/src/tools/look-at/session-poller.test.ts +++ b/src/tools/look-at/session-poller.test.ts @@ -30,7 +30,7 @@ describe("pollSessionUntilIdle", () => { { data: { ses_test: { type: "idle" } } }, ]) - await pollSessionUntilIdle(client as any, "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) + await pollSessionUntilIdle(testCoerce(client), "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) expect(client.session.status).toHaveBeenCalledTimes(3) }) @@ -43,7 +43,7 @@ describe("pollSessionUntilIdle", () => { { data: {} }, ]) - await pollSessionUntilIdle(client as any, "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) + await pollSessionUntilIdle(testCoerce(client), "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) expect(client.session.status).toHaveBeenCalledTimes(1) }) @@ -57,7 +57,7 @@ describe("pollSessionUntilIdle", () => { ]) await expect( - pollSessionUntilIdle(client as any, "ses_test", { pollIntervalMs: 10, timeoutMs: 50 }) + pollSessionUntilIdle(testCoerce(client), "ses_test", { pollIntervalMs: 10, timeoutMs: 50 }) ).rejects.toThrow("timed out") }) @@ -69,7 +69,7 @@ describe("pollSessionUntilIdle", () => { { error: new Error("API error") }, ]) - await pollSessionUntilIdle(client as any, "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) + await pollSessionUntilIdle(testCoerce(client), "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) expect(client.session.status).toHaveBeenCalledTimes(1) }) @@ -85,7 +85,7 @@ describe("pollSessionUntilIdle", () => { { data: {} }, ]) - await pollSessionUntilIdle(client as any, "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) + await pollSessionUntilIdle(testCoerce(client), "ses_test", { pollIntervalMs: 10, timeoutMs: 5000 }) expect(client.session.status).toHaveBeenCalledTimes(4) }) @@ -98,7 +98,7 @@ describe("pollSessionUntilIdle", () => { { data: {} }, ]) - await pollSessionUntilIdle(client as any, "ses_test") + await pollSessionUntilIdle(testCoerce(client), "ses_test") expect(client.session.status).toHaveBeenCalledTimes(1) }) diff --git a/src/tools/look-at/tools.test.ts b/src/tools/look-at/tools.test.ts index 9067032de..d92a1ef60 100644 --- a/src/tools/look-at/tools.test.ts +++ b/src/tools/look-at/tools.test.ts @@ -14,7 +14,7 @@ describe("look-at tool", () => { // then should normalize to file_path test("normalizes path to file_path for LLM compatibility", () => { const args = { path: "/some/file.png", goal: "analyze" } - const normalized = normalizeArgs(args as any) + const normalized = normalizeArgs(testCoerce(args)) expect(normalized.file_path).toBe("/some/file.png") expect(normalized.goal).toBe("analyze") }) @@ -33,7 +33,7 @@ describe("look-at tool", () => { // then prefer file_path test("prefers file_path over path when both provided", () => { const args = { file_path: "/preferred.png", path: "/fallback.png", goal: "test" } - const normalized = normalizeArgs(args as any) + const normalized = normalizeArgs(testCoerce(args)) expect(normalized.file_path).toBe("/preferred.png") }) @@ -42,7 +42,7 @@ describe("look-at tool", () => { // then preserve image_data in normalized args test("preserves image_data when provided", () => { const args = { image_data: "data:image/png;base64,iVBORw0KGgo=", goal: "analyze" } - const normalized = normalizeArgs(args as any) + const normalized = normalizeArgs(testCoerce(args)) expect(normalized.image_data).toBe("data:image/png;base64,iVBORw0KGgo=") expect(normalized.file_path).toBeUndefined() }) @@ -69,7 +69,7 @@ describe("look-at tool", () => { // when validated // then clear error message test("returns error when neither file_path nor image_data provided", () => { - const args = { goal: "analyze" } as any + const args = testCoerce({ goal: "analyze" }) const error = validateArgs(args) expect(error).toContain("file_path") expect(error).toContain("image_data") @@ -88,7 +88,7 @@ describe("look-at tool", () => { // when validated // then clear error message test("returns error when goal is missing", () => { - const args = { file_path: "/some/path.png" } as any + const args = testCoerce({ file_path: "/some/path.png" }) const error = validateArgs(args) expect(error).toContain("goal") expect(error).toContain("required") @@ -156,10 +156,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -193,10 +193,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -230,10 +230,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -291,10 +291,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -346,10 +346,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -395,10 +395,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -437,10 +437,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -486,10 +486,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const result = await tool.execute( { file_path: "/test/file.png", goal: "analyze" }, @@ -515,10 +515,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const result = await tool.execute( { file_path: "/test/file.png", goal: "analyze" }, @@ -539,10 +539,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const result = await tool.execute( { file_path: "/test/file.png", goal: "analyze" }, @@ -579,10 +579,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -632,10 +632,10 @@ describe("look-at tool", () => { }, } - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) const toolContext: ToolContext = { sessionID: "parent-session", @@ -701,10 +701,10 @@ describe("look-at tool", () => { test("instructs agent to analyze attached file when Read is disabled (file_path mode)", async () => { const { mockClient, captured } = captureLastPromptBody() - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) await tool.execute( { file_path: "/test/file.png", goal: "describe contents" }, @@ -726,10 +726,10 @@ describe("look-at tool", () => { test("instructs agent to analyze attached image when image_data is provided", async () => { const { mockClient, captured } = captureLastPromptBody() - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) await tool.execute( { image_data: "data:image/png;base64,iVBORw0KGgo=", goal: "describe image" }, @@ -751,10 +751,10 @@ describe("look-at tool", () => { test("explicitly warns the agent not to attempt Read when Read is disabled", async () => { const { mockClient, captured } = captureLastPromptBody() - const tool = createLookAt({ + const tool = createLookAt(testCoerce({ client: mockClient, directory: "/project", - } as any) + })) await tool.execute( { file_path: "/test/file.pdf", goal: "extract text" }, diff --git a/src/tools/lsp/client.test.ts b/src/tools/lsp/client.test.ts index f89de579f..3bbcb1cd5 100644 --- a/src/tools/lsp/client.test.ts +++ b/src/tools/lsp/client.test.ts @@ -36,7 +36,7 @@ describe("LSPClient", () => { const originalSetTimeout = globalThis.setTimeout globalThis.setTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => { fn() - return 0 as unknown as ReturnType + return testCoerce>(0) }) as typeof setTimeout const server: ResolvedServer = { @@ -50,7 +50,7 @@ describe("LSPClient", () => { // Stub protocol output: we only want to assert notifications. const sendNotificationSpy = spyOn( - client as unknown as { sendNotification: (m: string, p?: unknown) => void }, + testCoerce<{ sendNotification: (m: string, p?: unknown) => void }>(client), "sendNotification" ) diff --git a/src/tools/lsp/lsp-process.ts b/src/tools/lsp/lsp-process.ts index 91d940b94..634e66b2b 100644 --- a/src/tools/lsp/lsp-process.ts +++ b/src/tools/lsp/lsp-process.ts @@ -1,4 +1,4 @@ -import { spawn as bunSpawn } from "../../shared/bun-spawn-shim" +import { spawn as bunSpawn, type SpawnedProcess } from "../../shared/bun-spawn-shim" import { spawn as nodeSpawn, type ChildProcess } from "node:child_process" import { existsSync, statSync } from "fs" import { log } from "../../shared/logger" @@ -127,6 +127,30 @@ function wrapNodeProcess(proc: ChildProcess): UnifiedProcess { }, } } + +function wrapBunProcess(proc: SpawnedProcess): UnifiedProcess { + return { + stdin: { + write(chunk: Uint8Array | string) { + proc.stdin.write(chunk) + }, + }, + stdout: { + getReader: () => proc.stdout.getReader(), + }, + stderr: { + getReader: () => proc.stderr.getReader(), + }, + get exitCode() { + return proc.exitCode + }, + exited: proc.exited, + kill(signal?: string) { + proc.kill(signal === "SIGKILL" ? "SIGKILL" : undefined) + }, + } +} + export function spawnProcess( command: string[], options: { cwd: string; env: Record } @@ -154,5 +178,5 @@ export function spawnProcess( cwd: options.cwd, env: options.env, }) - return proc as unknown as UnifiedProcess + return wrapBunProcess(proc) } diff --git a/src/tools/session-manager/storage.test.ts b/src/tools/session-manager/storage.test.ts index 1fbdb4e37..4f4bdf163 100644 --- a/src/tools/session-manager/storage.test.ts +++ b/src/tools/session-manager/storage.test.ts @@ -448,7 +448,7 @@ describe("session-manager storage - SDK path (beta mode)", () => { // Re-import to get fresh module with mocked isSqliteBackend const { setStorageClient, getMainSessions } = await import("./storage") - setStorageClient(mockClient as unknown as Parameters[0]) + setStorageClient(testCoerce[0]>(mockClient)) // when const sessions = await getMainSessions({ directory: "/test" }) @@ -473,7 +473,7 @@ describe("session-manager storage - SDK path (beta mode)", () => { })) const { setStorageClient, getAllSessions } = await import("./storage") - setStorageClient(mockClient as unknown as Parameters[0]) + setStorageClient(testCoerce[0]>(mockClient)) // when const sessionIDs = await getAllSessions() @@ -503,7 +503,7 @@ describe("session-manager storage - SDK path (beta mode)", () => { })) const { setStorageClient, readSessionMessages } = await import("./storage") - setStorageClient(mockClient as unknown as Parameters[0]) + setStorageClient(testCoerce[0]>(mockClient)) // when const messages = await readSessionMessages("ses_test") @@ -531,7 +531,7 @@ describe("session-manager storage - SDK path (beta mode)", () => { })) const { setStorageClient, readSessionTodos } = await import("./storage") - setStorageClient(mockClient as unknown as Parameters[0]) + setStorageClient(testCoerce[0]>(mockClient)) // when const todos = await readSessionTodos("ses_test") @@ -555,7 +555,7 @@ describe("session-manager storage - SDK path (beta mode)", () => { })) const { setStorageClient, readSessionMessages } = await import("./storage") - setStorageClient(mockClient as unknown as Parameters[0]) + setStorageClient(testCoerce[0]>(mockClient)) await expect(readSessionMessages("ses_test")).rejects.toThrow("API error") }) diff --git a/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts b/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts index 32dd83bde..229a764fb 100644 --- a/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts +++ b/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts @@ -205,7 +205,7 @@ describe("skill tool - agent restriction", () => { // given const loadedSkills = [createMockSkill("sisyphus-only-skill", { agent: "sisyphus" })] const tool = createSkillTool({ skills: loadedSkills }) - const contextWithoutAgent = { ...mockContext, agent: undefined as unknown as string } + const contextWithoutAgent = { ...mockContext, agent: testCoerce(undefined) } // when / #then return expect(tool.execute({ name: "sisyphus-only-skill" }, contextWithoutAgent)).rejects.toThrow(