From fe5944ee39f6dea0bd8bf2112ff6c5e08bc690b1 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Fri, 20 Feb 2026 01:31:41 +0100 Subject: [PATCH 1/2] Add mapped-type handler builders and kind discriminants to IPC protocol Replace per-definition requestHandler/commandHandler wrappers with buildRequestHandlers and buildCommandHandlers that enforce compile-time completeness over the full API surface. - Add kind discriminant to RequestDef, CommandDef, and NotificationDef interfaces and their factory functions - Add RequestHandlerMap and CommandHandlerMap mapped types that require a handler for every definition of the corresponding kind - Add buildRequestHandlers and buildCommandHandlers builder functions with typed overloads for compile-time completeness checking - Replace manual method-keyed handler maps in tasksPanelProvider with builder calls using property-name keys - Replace raw sendNotification calls with a typed notify helper --- packages/shared/src/ipc/protocol.ts | 75 +++++++++++--- src/webviews/tasks/tasksPanelProvider.ts | 122 +++++++---------------- 2 files changed, 98 insertions(+), 99 deletions(-) diff --git a/packages/shared/src/ipc/protocol.ts b/packages/shared/src/ipc/protocol.ts index 12bf3e4e..05bf1f87 100644 --- a/packages/shared/src/ipc/protocol.ts +++ b/packages/shared/src/ipc/protocol.ts @@ -9,6 +9,7 @@ /** Request definition: params P, response R */ export interface RequestDef

{ + readonly kind: "request"; readonly method: string; /** @internal Phantom types for inference - not present at runtime */ readonly _types?: { params: P; response: R }; @@ -16,6 +17,7 @@ export interface RequestDef

{ /** Command definition: params P, no response */ export interface CommandDef

{ + readonly kind: "command"; readonly method: string; /** @internal Phantom type for inference - not present at runtime */ readonly _types?: { params: P }; @@ -23,6 +25,7 @@ export interface CommandDef

{ /** Notification definition: data D (extension to webview) */ export interface NotificationDef { + readonly kind: "notification"; readonly method: string; /** @internal Phantom type for inference - not present at runtime */ readonly _types?: { data: D }; @@ -34,19 +37,19 @@ export interface NotificationDef { export function defineRequest

( method: string, ): RequestDef { - return { method } as RequestDef; + return { kind: "request", method } as RequestDef; } /** Define a fire-and-forget command */ export function defineCommand

(method: string): CommandDef

{ - return { method } as CommandDef

; + return { kind: "command", method } as CommandDef

; } /** Define a push notification (extension to webview) */ export function defineNotification( method: string, ): NotificationDef { - return { method } as NotificationDef; + return { kind: "notification", method } as NotificationDef; } // --- Wire format --- @@ -83,18 +86,60 @@ export type ResponseOf = T extends { _types?: { response: infer R } } ? R : void; -/** Type-safe request handler - infers params and return type from definition */ -export function requestHandler( - _def: RequestDef, - fn: (params: P) => Promise, -): (params: unknown) => Promise { - return fn as (params: unknown) => Promise; +// --- Mapped types for handler completeness --- + +/** Requires a handler for every RequestDef in Api. Compile error if one is missing. */ +export type RequestHandlerMap = { + [K in keyof Api as Api[K] extends { kind: "request" } + ? K + : never]: Api[K] extends RequestDef + ? (params: P) => Promise + : never; +}; + +/** Requires a handler for every CommandDef in Api. Compile error if one is missing. */ +export type CommandHandlerMap = { + [K in keyof Api as Api[K] extends { kind: "command" } + ? K + : never]: Api[K] extends CommandDef + ? (params: P) => void | Promise + : never; +}; + +// --- Builder functions --- + +/** Build a method-indexed map of request handlers with compile-time completeness. */ +export function buildRequestHandlers< + Api extends Record, +>( + api: Api, + handlers: RequestHandlerMap, +): Record Promise>; +export function buildRequestHandlers( + api: Record, + handlers: Record Promise>, +) { + const result: Record Promise> = {}; + for (const key of Object.keys(handlers)) { + result[api[key].method] = handlers[key]; + } + return result; } -/** Type-safe command handler - infers params type from definition */ -export function commandHandler

( - _def: CommandDef

, - fn: (params: P) => void | Promise, -): (params: unknown) => void | Promise { - return fn as (params: unknown) => void | Promise; +/** Build a method-indexed map of command handlers with compile-time completeness. */ +export function buildCommandHandlers< + Api extends Record, +>( + api: Api, + handlers: CommandHandlerMap, +): Record void | Promise>; +export function buildCommandHandlers( + api: Record, + handlers: Record void | Promise>, +) { + const result: Record void | Promise> = {}; + for (const key of Object.keys(handlers)) { + result[api[key].method] = handlers[key]; + } + return result; } diff --git a/src/webviews/tasks/tasksPanelProvider.ts b/src/webviews/tasks/tasksPanelProvider.ts index 5e2e2f7e..61671de7 100644 --- a/src/webviews/tasks/tasksPanelProvider.ts +++ b/src/webviews/tasks/tasksPanelProvider.ts @@ -3,18 +3,18 @@ import stripAnsi from "strip-ansi"; import * as vscode from "vscode"; import { - commandHandler, + buildCommandHandlers, + buildRequestHandlers, isBuildingWorkspace, isAgentStarting, getTaskPermissions, getTaskLabel, isStableTask, - requestHandler, TasksApi, type CreateTaskParams, - type IpcNotification, type IpcRequest, type IpcResponse, + type NotificationDef, type TaskDetails, type TaskLogs, type TaskTemplate, @@ -100,71 +100,28 @@ export class TasksPanelProvider logs: TaskLogs; }; - /** - * Request handlers indexed by method name. - * Type safety is ensured at definition time via requestHandler(). - */ - private readonly requestHandlers: Record< - string, - (params: unknown) => Promise - > = { - [TasksApi.getTasks.method]: requestHandler(TasksApi.getTasks, () => - this.fetchTasks(), - ), - [TasksApi.getTemplates.method]: requestHandler(TasksApi.getTemplates, () => - this.fetchTemplates(), - ), - [TasksApi.getTask.method]: requestHandler(TasksApi.getTask, (p) => - this.client.getTask("me", p.taskId), - ), - [TasksApi.getTaskDetails.method]: requestHandler( - TasksApi.getTaskDetails, - (p) => this.handleGetTaskDetails(p.taskId), - ), - [TasksApi.createTask.method]: requestHandler(TasksApi.createTask, (p) => - this.handleCreateTask(p), - ), - [TasksApi.deleteTask.method]: requestHandler(TasksApi.deleteTask, (p) => - this.handleDeleteTask(p.taskId, p.taskName), - ), - [TasksApi.pauseTask.method]: requestHandler(TasksApi.pauseTask, (p) => - this.handlePauseTask(p.taskId, p.taskName), - ), - [TasksApi.resumeTask.method]: requestHandler(TasksApi.resumeTask, (p) => - this.handleResumeTask(p.taskId, p.taskName), - ), - [TasksApi.downloadLogs.method]: requestHandler(TasksApi.downloadLogs, (p) => - this.handleDownloadLogs(p.taskId), - ), - [TasksApi.sendTaskMessage.method]: requestHandler( - TasksApi.sendTaskMessage, - (p) => this.handleSendMessage(p.taskId, p.message), - ), - }; - - /** - * Command handlers indexed by method name. - * Type safety is ensured at definition time via commandHandler(). - */ - private readonly commandHandlers: Record< - string, - (params: unknown) => void | Promise - > = { - [TasksApi.viewInCoder.method]: commandHandler(TasksApi.viewInCoder, (p) => - this.handleViewInCoder(p.taskId), - ), - [TasksApi.viewLogs.method]: commandHandler(TasksApi.viewLogs, (p) => - this.handleViewLogs(p.taskId), - ), - [TasksApi.stopStreamingWorkspaceLogs.method]: commandHandler( - TasksApi.stopStreamingWorkspaceLogs, - () => { - this.streamingTaskId = null; - this.buildLogStream.close(); - this.agentLogStream.close(); - }, - ), - }; + private readonly requestHandlers = buildRequestHandlers(TasksApi, { + getTasks: () => this.fetchTasks(), + getTemplates: () => this.fetchTemplates(), + getTask: (p) => this.client.getTask("me", p.taskId), + getTaskDetails: (p) => this.handleGetTaskDetails(p.taskId), + createTask: (p) => this.handleCreateTask(p), + deleteTask: (p) => this.handleDeleteTask(p.taskId, p.taskName), + pauseTask: (p) => this.handlePauseTask(p.taskId, p.taskName), + resumeTask: (p) => this.handleResumeTask(p.taskId, p.taskName), + downloadLogs: (p) => this.handleDownloadLogs(p.taskId), + sendTaskMessage: (p) => this.handleSendMessage(p.taskId, p.message), + }); + + private readonly commandHandlers = buildCommandHandlers(TasksApi, { + viewInCoder: (p) => this.handleViewInCoder(p.taskId), + viewLogs: (p) => this.handleViewLogs(p.taskId), + stopStreamingWorkspaceLogs: () => { + this.streamingTaskId = null; + this.buildLogStream.close(); + this.agentLogStream.close(); + }, + }); constructor( private readonly extensionUri: vscode.Uri, @@ -173,12 +130,12 @@ export class TasksPanelProvider ) {} public showCreateForm(): void { - this.sendNotification({ type: TasksApi.showCreateForm.method }); + this.notify(TasksApi.showCreateForm); } public refresh(): void { this.cachedLogs = undefined; - this.sendNotification({ type: TasksApi.refresh.method }); + this.notify(TasksApi.refresh); } resolveWebviewView( @@ -452,10 +409,7 @@ export class TasksPanelProvider const clean = stripAnsi(line); // Skip lines that were purely ANSI codes, but keep intentional blank lines. if (line.length > 0 && clean.length === 0) return; - this.sendNotification({ - type: TasksApi.workspaceLogsAppend.method, - data: [clean], - }); + this.notify(TasksApi.workspaceLogsAppend, [clean]); }; const onStreamClose = () => { @@ -514,10 +468,7 @@ export class TasksPanelProvider try { const tasks = await this.fetchTasks(); if (tasks !== null) { - this.sendNotification({ - type: TasksApi.tasksUpdated.method, - data: tasks, - }); + this.notify(TasksApi.tasksUpdated, [...tasks]); } } catch (err) { this.logger.warn("Failed to refresh tasks after action", err); @@ -527,10 +478,7 @@ export class TasksPanelProvider private async refreshAndNotifyTask(taskId: string): Promise { try { const task = await this.client.getTask("me", taskId); - this.sendNotification({ - type: TasksApi.taskUpdated.method, - data: task, - }); + this.notify(TasksApi.taskUpdated, task); } catch (err) { this.logger.warn("Failed to refresh task after action", err); } @@ -616,8 +564,14 @@ export class TasksPanelProvider this.view?.webview.postMessage(response); } - private sendNotification(notification: IpcNotification): void { - this.view?.webview.postMessage(notification); + private notify( + def: NotificationDef, + ...args: D extends void ? [] : [data: D] + ): void { + this.view?.webview.postMessage({ + type: def.method, + ...(args.length > 0 ? { data: args[0] } : {}), + }); } dispose(): void { From e8ad2ec6278be4b1137f99403b5b56d9f92be258 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Fri, 20 Feb 2026 02:01:33 +0100 Subject: [PATCH 2/2] Auto-generate useTasksApi from TasksApi at runtime Add buildApiHook and ApiHook to protocol.ts so that useTasksApi can be derived from the TasksApi definition object with zero manual sync. - Add ApiHook mapped type that derives request, command, and notification subscription signatures from an API definition - Add buildApiHook() that iterates API entries by kind at runtime, dispatching to ipc.request/command/onNotification - Replace ~60 lines of manual method listings in useTasksApi with a single buildApiHook(TasksApi, useIpc()) call - Update 5 consumer call sites from positional args to params objects - Inline intermediate const declarations in api.ts into TasksApi object - Remove unused ParamsOf and ResponseOf utility types --- packages/shared/src/ipc/protocol.ts | 85 ++++++++++++++++--- packages/shared/src/tasks/api.ts | 69 +++++---------- packages/tasks/src/components/ErrorBanner.tsx | 2 +- .../tasks/src/components/TaskMessageInput.tsx | 3 +- .../tasks/src/components/useTaskMenuItems.ts | 5 +- packages/tasks/src/hooks/useSelectedTask.ts | 2 +- packages/tasks/src/hooks/useTasksApi.ts | 58 +------------ test/webview/tasks/ErrorBanner.test.tsx | 2 +- test/webview/tasks/TaskMessageInput.test.tsx | 8 +- test/webview/tasks/useTaskMenuItems.test.tsx | 2 +- 10 files changed, 112 insertions(+), 124 deletions(-) diff --git a/packages/shared/src/ipc/protocol.ts b/packages/shared/src/ipc/protocol.ts index 05bf1f87..223d7302 100644 --- a/packages/shared/src/ipc/protocol.ts +++ b/packages/shared/src/ipc/protocol.ts @@ -76,16 +76,6 @@ export interface IpcNotification { readonly data?: D; } -// --- Handler utilities --- - -/** Extract params type from a request/command definition */ -export type ParamsOf = T extends { _types?: { params: infer P } } ? P : void; - -/** Extract response type from a request definition */ -export type ResponseOf = T extends { _types?: { response: infer R } } - ? R - : void; - // --- Mapped types for handler completeness --- /** Requires a handler for every RequestDef in Api. Compile error if one is missing. */ @@ -106,6 +96,31 @@ export type CommandHandlerMap = { : never; }; +// --- API hook type --- + +/** Derives a fully typed hook interface from an API definition object. */ +export type ApiHook = { + [K in keyof Api as Api[K] extends { kind: "request" } + ? K + : never]: Api[K] extends RequestDef + ? (...args: P extends void ? [] : [params: P]) => Promise + : never; +} & { + [K in keyof Api as Api[K] extends { kind: "command" } + ? K + : never]: Api[K] extends CommandDef + ? (...args: P extends void ? [] : [params: P]) => void + : never; +} & { + [K in keyof Api as Api[K] extends { kind: "notification" } + ? `on${Capitalize}` + : never]: Api[K] extends NotificationDef + ? D extends void + ? (cb: () => void) => () => void + : (cb: (data: D) => void) => () => void + : never; +}; + // --- Builder functions --- /** Build a method-indexed map of request handlers with compile-time completeness. */ @@ -143,3 +158,53 @@ export function buildCommandHandlers( } return result; } + +/** Build a typed API hook from an API definition and IPC primitives. */ +export function buildApiHook< + Api extends Record, +>( + api: Api, + ipc: { + request: ( + def: { method: string; _types?: { params: P; response: R } }, + ...args: P extends void ? [] : [params: P] + ) => Promise; + command:

( + def: { method: string; _types?: { params: P } }, + ...args: P extends void ? [] : [params: P] + ) => void; + onNotification: ( + def: { method: string; _types?: { data: D } }, + cb: (data: D) => void, + ) => () => void; + }, +): ApiHook; +export function buildApiHook( + api: Record, + ipc: { + request: (def: { method: string }, params: unknown) => Promise; + command: (def: { method: string }, params: unknown) => void; + onNotification: ( + def: { method: string }, + cb: (data: unknown) => void, + ) => () => void; + }, +) { + const result: Record = {}; + for (const [key, def] of Object.entries(api)) { + switch (def.kind) { + case "request": + result[key] = (params: unknown) => ipc.request(def, params); + break; + case "command": + result[key] = (params: unknown) => ipc.command(def, params); + break; + case "notification": + result[`on${key[0].toUpperCase()}${key.slice(1)}`] = ( + cb: (data: unknown) => void, + ) => ipc.onNotification(def, cb); + break; + } + } + return result; +} diff --git a/packages/shared/src/tasks/api.ts b/packages/shared/src/tasks/api.ts index efd70509..032ec016 100644 --- a/packages/shared/src/tasks/api.ts +++ b/packages/shared/src/tasks/api.ts @@ -21,65 +21,40 @@ export interface TaskIdParams { taskId: string; } -const getTasks = defineRequest("getTasks"); -const getTemplates = defineRequest( - "getTemplates", -); -const getTask = defineRequest("getTask"); -const getTaskDetails = defineRequest( - "getTaskDetails", -); - export interface CreateTaskParams { templateVersionId: string; prompt: string; presetId?: string; } -const createTask = defineRequest("createTask"); export interface TaskActionParams extends TaskIdParams { taskName: string; } -const deleteTask = defineRequest("deleteTask"); -const pauseTask = defineRequest("pauseTask"); -const resumeTask = defineRequest("resumeTask"); -const downloadLogs = defineRequest("downloadLogs"); -const sendTaskMessage = defineRequest( - "sendTaskMessage", -); - -const viewInCoder = defineCommand("viewInCoder"); -const viewLogs = defineCommand("viewLogs"); -const stopStreamingWorkspaceLogs = defineCommand( - "stopStreamingWorkspaceLogs", -); - -const taskUpdated = defineNotification("taskUpdated"); -const tasksUpdated = defineNotification("tasksUpdated"); -const workspaceLogsAppend = defineNotification("workspaceLogsAppend"); -const refresh = defineNotification("refresh"); -const showCreateForm = defineNotification("showCreateForm"); export const TasksApi = { // Requests - getTasks, - getTemplates, - getTask, - getTaskDetails, - createTask, - deleteTask, - pauseTask, - resumeTask, - downloadLogs, - sendTaskMessage, + getTasks: defineRequest("getTasks"), + getTemplates: defineRequest( + "getTemplates", + ), + getTask: defineRequest("getTask"), + getTaskDetails: defineRequest("getTaskDetails"), + createTask: defineRequest("createTask"), + deleteTask: defineRequest("deleteTask"), + pauseTask: defineRequest("pauseTask"), + resumeTask: defineRequest("resumeTask"), + downloadLogs: defineRequest("downloadLogs"), + sendTaskMessage: defineRequest( + "sendTaskMessage", + ), // Commands - viewInCoder, - viewLogs, - stopStreamingWorkspaceLogs, + viewInCoder: defineCommand("viewInCoder"), + viewLogs: defineCommand("viewLogs"), + stopStreamingWorkspaceLogs: defineCommand("stopStreamingWorkspaceLogs"), // Notifications - taskUpdated, - tasksUpdated, - workspaceLogsAppend, - refresh, - showCreateForm, + taskUpdated: defineNotification("taskUpdated"), + tasksUpdated: defineNotification("tasksUpdated"), + workspaceLogsAppend: defineNotification("workspaceLogsAppend"), + refresh: defineNotification("refresh"), + showCreateForm: defineNotification("showCreateForm"), } as const; diff --git a/packages/tasks/src/components/ErrorBanner.tsx b/packages/tasks/src/components/ErrorBanner.tsx index 17b262f3..31b77f18 100644 --- a/packages/tasks/src/components/ErrorBanner.tsx +++ b/packages/tasks/src/components/ErrorBanner.tsx @@ -19,7 +19,7 @@ export function ErrorBanner({ task }: ErrorBannerProps) { diff --git a/packages/tasks/src/components/TaskMessageInput.tsx b/packages/tasks/src/components/TaskMessageInput.tsx index 9434dcd8..f2820269 100644 --- a/packages/tasks/src/components/TaskMessageInput.tsx +++ b/packages/tasks/src/components/TaskMessageInput.tsx @@ -69,7 +69,8 @@ export function TaskMessageInput({ task }: TaskMessageInputProps) { }); const { mutate: sendMessage, isPending: isSending } = useMutation({ - mutationFn: (msg: string) => api.sendTaskMessage(task.id, msg), + mutationFn: (msg: string) => + api.sendTaskMessage({ taskId: task.id, message: msg }), onSuccess: () => setMessage(""), onError: (err) => logger.error("Failed to send message", err), }); diff --git a/packages/tasks/src/components/useTaskMenuItems.ts b/packages/tasks/src/components/useTaskMenuItems.ts index 3ffdae03..630fb83c 100644 --- a/packages/tasks/src/components/useTaskMenuItems.ts +++ b/packages/tasks/src/components/useTaskMenuItems.ts @@ -73,13 +73,14 @@ export function useTaskMenuItems({ menuItems.push({ label: "View in Coder", icon: "link-external", - onClick: () => api.viewInCoder(task.id), + onClick: () => api.viewInCoder({ taskId: task.id }), }); menuItems.push({ label: "Download Logs", icon: "cloud-download", - onClick: () => run("downloading", () => api.downloadLogs(task.id)), + onClick: () => + run("downloading", () => api.downloadLogs({ taskId: task.id })), loading: action === "downloading", }); diff --git a/packages/tasks/src/hooks/useSelectedTask.ts b/packages/tasks/src/hooks/useSelectedTask.ts index 23648253..b9adeb2c 100644 --- a/packages/tasks/src/hooks/useSelectedTask.ts +++ b/packages/tasks/src/hooks/useSelectedTask.ts @@ -30,7 +30,7 @@ export function useSelectedTask(tasks: readonly Task[]) { ? queryKeys.taskDetail(selectedTaskId) : queryKeys.details, queryFn: selectedTaskId - ? () => api.getTaskDetails(selectedTaskId) + ? () => api.getTaskDetails({ taskId: selectedTaskId }) : skipToken, refetchInterval: (query) => { const task = query.state.data?.task; diff --git a/packages/tasks/src/hooks/useTasksApi.ts b/packages/tasks/src/hooks/useTasksApi.ts index 04660738..ce6b8041 100644 --- a/packages/tasks/src/hooks/useTasksApi.ts +++ b/packages/tasks/src/hooks/useTasksApi.ts @@ -1,60 +1,6 @@ -/** - * Tasks API hook - provides type-safe access to all Tasks operations. - * - * @example - * ```tsx - * const api = useTasksApi(); - * const tasks = await api.getTasks(); - * api.viewInCoder("task-id"); - * ``` - */ - -import { - TasksApi, - type CreateTaskParams, - type Task, - type TaskActionParams, -} from "@repo/shared"; +import { TasksApi, buildApiHook } from "@repo/shared"; import { useIpc } from "@repo/webview-shared/react"; export function useTasksApi() { - const { request, command, onNotification } = useIpc(); - - return { - // Requests - getTasks: () => request(TasksApi.getTasks), - getTemplates: () => request(TasksApi.getTemplates), - getTask: (taskId: string) => request(TasksApi.getTask, { taskId }), - getTaskDetails: (taskId: string) => - request(TasksApi.getTaskDetails, { taskId }), - createTask: (params: CreateTaskParams) => - request(TasksApi.createTask, params), - deleteTask: (params: TaskActionParams) => - request(TasksApi.deleteTask, params), - pauseTask: (params: TaskActionParams) => - request(TasksApi.pauseTask, params), - resumeTask: (params: TaskActionParams) => - request(TasksApi.resumeTask, params), - downloadLogs: (taskId: string) => - request(TasksApi.downloadLogs, { taskId }), - sendTaskMessage: (taskId: string, message: string) => - request(TasksApi.sendTaskMessage, { taskId, message }), - - // Commands - viewInCoder: (taskId: string) => command(TasksApi.viewInCoder, { taskId }), - viewLogs: (taskId: string) => command(TasksApi.viewLogs, { taskId }), - stopStreamingWorkspaceLogs: () => - command(TasksApi.stopStreamingWorkspaceLogs), - - // Notifications - onTaskUpdated: (cb: (task: Task) => void) => - onNotification(TasksApi.taskUpdated, cb), - onTasksUpdated: (cb: (tasks: Task[]) => void) => - onNotification(TasksApi.tasksUpdated, cb), - onWorkspaceLogsAppend: (cb: (lines: string[]) => void) => - onNotification(TasksApi.workspaceLogsAppend, cb), - onRefresh: (cb: () => void) => onNotification(TasksApi.refresh, cb), - onShowCreateForm: (cb: () => void) => - onNotification(TasksApi.showCreateForm, cb), - }; + return buildApiHook(TasksApi, useIpc()); } diff --git a/test/webview/tasks/ErrorBanner.test.tsx b/test/webview/tasks/ErrorBanner.test.tsx index 25a92482..425dd8b5 100644 --- a/test/webview/tasks/ErrorBanner.test.tsx +++ b/test/webview/tasks/ErrorBanner.test.tsx @@ -58,6 +58,6 @@ describe("ErrorBanner", () => { const t = task({ id: "task-42", status: "error" }); renderWithQuery(); fireEvent.click(screen.getByText("View logs")); - expect(mockApi.viewLogs).toHaveBeenCalledWith("task-42"); + expect(mockApi.viewLogs).toHaveBeenCalledWith({ taskId: "task-42" }); }); }); diff --git a/test/webview/tasks/TaskMessageInput.test.tsx b/test/webview/tasks/TaskMessageInput.test.tsx index bdad1e44..dc7927b6 100644 --- a/test/webview/tasks/TaskMessageInput.test.tsx +++ b/test/webview/tasks/TaskMessageInput.test.tsx @@ -162,10 +162,10 @@ describe("TaskMessageInput", () => { fireEvent.keyDown(getTextarea(), { key: "Enter", ctrlKey: true }); await waitFor(() => { - expect(mockApi.sendTaskMessage).toHaveBeenCalledWith( - "task-1", - "Hello agent", - ); + expect(mockApi.sendTaskMessage).toHaveBeenCalledWith({ + taskId: "task-1", + message: "Hello agent", + }); }); await waitFor(() => { expect(getTextarea()).toHaveValue(""); diff --git a/test/webview/tasks/useTaskMenuItems.test.tsx b/test/webview/tasks/useTaskMenuItems.test.tsx index 071395e7..a7022728 100644 --- a/test/webview/tasks/useTaskMenuItems.test.tsx +++ b/test/webview/tasks/useTaskMenuItems.test.tsx @@ -134,7 +134,7 @@ describe("useTaskMenuItems", () => { const { result } = renderTask(testTask); clickItem(result.current.menuItems, label); await waitFor(() => { - expect(mockApi[apiMethod]).toHaveBeenCalledWith(testTask.id); + expect(mockApi[apiMethod]).toHaveBeenCalledWith({ taskId: testTask.id }); }); });