all repos — sz-chrome-remote-cdp @ 96934b772378b57aed243919d3c1d6ad6e96df1f

pi-browser-harness, patched to drive a Chrome on another host over CDP, with castle_cdp_search web search

feat(domains): migrate drag_and_drop
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:35:30 +0530
commit

96934b772378b57aed243919d3c1d6ad6e96df1f

parent

dc07a5157188c09b79487af8b47fbda51b6af7d6

3 files changed, 68 insertions(+), 49 deletions(-)

jump to
A src/domains/drag.ts

@@ -0,0 +1,66 @@

+import { Type } from "typebox"; +import { type Result, err, ok } from "../util/result"; +import { defineBrowserTool, type ToolErr, type ToolOk } from "../util/tool"; + +const DragArgs = Type.Object({ + startX: Type.Number(), + startY: Type.Number(), + endX: Type.Number(), + endY: Type.Number(), + dataTransfer: Type.Optional( + Type.Record(Type.String(), Type.String(), { + description: "Optional MIME → data map for DataTransfer (e.g., { 'text/plain': 'hello' })", + }), + ), +}); + +export const dragAndDropTool = defineBrowserTool({ + name: "browser_drag_and_drop", + label: "Browser Drag & Drop", + description: "Drag from (startX, startY) to (endX, endY) using CDP Input.dispatchDragEvent.", + promptSnippet: "Drag and drop between two coordinates", + promptGuidelines: [ + "Coordinates in CSS pixels.", + "dataTransfer is an optional MIME→data map (e.g., { 'text/plain': 'hello' }).", + ], + parameters: DragArgs, + async handler(args, { client }): Promise<Result<ToolOk, ToolErr>> { + const data = args.dataTransfer + ? { + items: Object.entries(args.dataTransfer).map(([mimeType, payload]) => ({ + mimeType, + data: Buffer.from(payload).toString("base64"), + })), + dragOperationsMask: 1, + } + : { items: [], dragOperationsMask: 1 }; + const cdp = client.session(); + const calls: ReadonlyArray<readonly [string, Record<string, unknown>]> = [ + ["Input.dispatchMouseEvent", { type: "mousePressed", x: args.startX, y: args.startY, button: "left", clickCount: 1 }], + ["Input.dispatchDragEvent", { type: "dragEnter", x: args.startX, y: args.startY, data, modifiers: 0 }], + // 5 interpolated dragOver steps from start → end so the page sees a smooth drag. + ...Array.from({ length: 5 }, (_, i): readonly [string, Record<string, unknown>] => { + const t = (i + 1) / 5; + return [ + "Input.dispatchDragEvent", + { + type: "dragOver", + x: Math.round(args.startX + (args.endX - args.startX) * t), + y: Math.round(args.startY + (args.endY - args.startY) * t), + data, + modifiers: 0, + }, + ]; + }), + ["Input.dispatchDragEvent", { type: "drop", x: args.endX, y: args.endY, data, modifiers: 0 }], + ["Input.dispatchMouseEvent", { type: "mouseReleased", x: args.endX, y: args.endY, button: "left", clickCount: 1 }], + ]; + for (const [method, params] of calls) { + const r = await cdp.call(method, params); + if (!r.success) return err({ kind: "cdp_error", message: r.error.message }); + } + return ok({ + text: `Dragged from (${args.startX},${args.startY}) to (${args.endX},${args.endY})`, + }); + }, +});
M src/registry.ts → src/registry.ts

@@ -12,6 +12,7 @@ import { goBackTool, goForwardTool, reloadTool } from "./domains/history";

import { listTabsTool, currentTabTool, switchTabTool, newTabTool } from "./domains/tabs"; import { uploadFileTool, downloadTool, printToPdfTool } from "./domains/files"; import { viewportResizeTool } from "./domains/viewport"; +import { dragAndDropTool } from "./domains/drag"; const TOOLS: ReadonlyArray<AnyBrowserToolDefinition> = [ clickTool,

@@ -37,6 +38,7 @@ uploadFileTool,

downloadTool, printToPdfTool, viewportResizeTool, + dragAndDropTool, ]; export const registerAllTools = (pi: ExtensionAPI, client: BrowserClient): void => {
M src/tools.ts → src/tools.ts

@@ -270,55 +270,6 @@ },

}); // ═══════════════════════════════════════════════════════════════════════════ - // browser_drag_and_drop - // ═══════════════════════════════════════════════════════════════════════════ - pi.registerTool({ - name: "browser_drag_and_drop", - label: "Browser Drag And Drop", - description: - "Perform a drag-and-drop operation from one viewport coordinate to another. " + - "Use for kanban boards, file uploads via drag, reorderable lists, and " + - "other drag-based UIs.", - promptSnippet: "Drag from (startX, startY) and drop at (endX, endY)", - promptGuidelines: [ - "Use browser_drag_and_drop for drag-based interactions like moving cards, reordering items, or drag-to-upload.", - "Capture a screenshot BEFORE to find the start and end coordinates.", - "Capture a screenshot AFTER to verify the drag-and-drop worked.", - "If the target site expects DOM-level drag events (not just compositor events), try browser_upload_file instead for file uploads.", - ], - parameters: Type.Object({ - startX: Type.Number({ description: "X coordinate to start dragging from" }), - startY: Type.Number({ description: "Y coordinate to start dragging from" }), - endX: Type.Number({ description: "X coordinate to drop at" }), - endY: Type.Number({ description: "Y coordinate to drop at" }), - }), - async execute(_id, params) { - try { - await daemon.ensureAlive(); - await daemon.dragAndDrop( - params.startX, - params.startY, - params.endX, - params.endY, - ); - return { - content: [{ - type: "text" as const, - text: `Dragged from (${params.startX}, ${params.startY}) to (${params.endX}, ${params.endY}).`, - }], - details: undefined, - }; - } catch (err) { - return { - isError: true, - content: [{ type: "text" as const, text: `Drag-and-drop failed: ${err instanceof Error ? err.message : String(err)}` }], - details: undefined, - }; - } - }, - }); - - // ═══════════════════════════════════════════════════════════════════════════ // browser_get_network_log // ═══════════════════════════════════════════════════════════════════════════ pi.registerTool({