diff --git a/apps/sim/app/api/desktop/tool/authorize/route.test.ts b/apps/sim/app/api/desktop/tool/authorize/route.test.ts index 0b833983df9..6d965335655 100644 --- a/apps/sim/app/api/desktop/tool/authorize/route.test.ts +++ b/apps/sim/app/api/desktop/tool/authorize/route.test.ts @@ -87,6 +87,25 @@ describe('desktop tool authorization', () => { expect(claimPendingAsyncToolCall).toHaveBeenCalledWith('browser-tool', 'desktop-browser') }) + it('never returns presentation activity as an executable browser argument', async () => { + const fields = [{ elementId: 1, kind: 'text', text: 'a' }] + getAsyncToolCall.mockResolvedValueOnce({ + toolCallId: 'form-tool', + runId: 'run-1', + status: 'pending', + toolName: 'browser_fill_form', + args: { activity: { description: 'Filling the form' }, fields }, + }) + + const response = await POST(request('form-tool')) + expect(response.status).toBe(200) + expect(await response.json()).toEqual({ + chatId: 'chat-1', + toolName: 'browser_fill_form', + args: { fields }, + }) + }) + it('rejects retired browser tools retained only for history', async () => { getAsyncToolCall.mockResolvedValueOnce({ toolCallId: 'retired-browser-tool', diff --git a/apps/sim/app/api/desktop/tool/authorize/route.ts b/apps/sim/app/api/desktop/tool/authorize/route.ts index fb735203a7a..9a657073b1e 100644 --- a/apps/sim/app/api/desktop/tool/authorize/route.ts +++ b/apps/sim/app/api/desktop/tool/authorize/route.ts @@ -1,6 +1,6 @@ import { isCurrentBrowserToolName } from '@sim/browser-protocol' import { isTerminalToolName } from '@sim/terminal-protocol' -import { isRecordLike } from '@sim/utils/object' +import { isRecordLike, omit } from '@sim/utils/object' import { type NextRequest, NextResponse } from 'next/server' import { authorizeDesktopToolContract } from '@/lib/api/contracts/desktop-tool-authorization' import { parseRequest } from '@/lib/api/server' @@ -24,7 +24,8 @@ import { isUserLocalVfsToolCall } from '@/lib/mothership/tools/local-filesystem' * Electron calls this endpoint from the main process before every privileged * native model action. It returns only server-persisted canonical tool args; * Electron validates local-file requests against them and uses them directly - * for browser and terminal tools. + * for browser and terminal tools. The presentation-only `activity` field is + * dropped: desktop actions reject arguments they do not declare. */ export const POST = withRouteHandler(async (request: NextRequest) => { const { userId, isAuthenticated } = await authenticateCopilotRequestSessionOnly() @@ -117,7 +118,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ toolName: toolCall.toolName, - args, + args: omit(args, ['activity']), chatId: run.chatId, }) })