@midscene/web 1.7.6-beta-20260425023224.0 → 1.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/es/bridge-mode/io-client.mjs +1 -1
  2. package/dist/es/bridge-mode/io-server.mjs +2 -2
  3. package/dist/es/bridge-mode/io-server.mjs.map +1 -1
  4. package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
  5. package/dist/es/bridge-mode/page-browser-side.mjs.map +1 -1
  6. package/dist/es/cli.mjs +1 -1
  7. package/dist/es/mcp-server.mjs +1 -1
  8. package/dist/es/mcp-tools-cdp.mjs +5 -12
  9. package/dist/es/mcp-tools-cdp.mjs.map +1 -1
  10. package/dist/es/mcp-tools-puppeteer.mjs +4 -24
  11. package/dist/es/mcp-tools-puppeteer.mjs.map +1 -1
  12. package/dist/es/mcp-tools.mjs +4 -24
  13. package/dist/es/mcp-tools.mjs.map +1 -1
  14. package/dist/es/puppeteer/agent-launcher.mjs +2 -2
  15. package/dist/es/puppeteer/agent-launcher.mjs.map +1 -1
  16. package/dist/lib/bridge-mode/io-client.js +1 -1
  17. package/dist/lib/bridge-mode/io-server.js +2 -2
  18. package/dist/lib/bridge-mode/io-server.js.map +1 -1
  19. package/dist/lib/bridge-mode/page-browser-side.js +1 -1
  20. package/dist/lib/bridge-mode/page-browser-side.js.map +1 -1
  21. package/dist/lib/cli.js +1 -1
  22. package/dist/lib/mcp-server.js +1 -1
  23. package/dist/lib/mcp-tools-cdp.js +5 -12
  24. package/dist/lib/mcp-tools-cdp.js.map +1 -1
  25. package/dist/lib/mcp-tools-puppeteer.js +4 -24
  26. package/dist/lib/mcp-tools-puppeteer.js.map +1 -1
  27. package/dist/lib/mcp-tools.js +4 -24
  28. package/dist/lib/mcp-tools.js.map +1 -1
  29. package/dist/lib/puppeteer/agent-launcher.js +2 -2
  30. package/dist/lib/puppeteer/agent-launcher.js.map +1 -1
  31. package/dist/types/mcp-tools-cdp.d.ts +0 -1
  32. package/dist/types/mcp-tools-puppeteer.d.ts +0 -1
  33. package/dist/types/mcp-tools.d.ts +0 -1
  34. package/package.json +4 -4
@@ -23,7 +23,7 @@ class BridgeClient {
23
23
  ]
24
24
  } : {},
25
25
  query: {
26
- version: "1.7.6-beta-20260425023224.0"
26
+ version: "1.7.6"
27
27
  }
28
28
  });
29
29
  const timeout = setTimeout(()=>{
@@ -86,7 +86,7 @@ class BridgeServer {
86
86
  logMsg('one client connected');
87
87
  this.socket = socket;
88
88
  const clientVersion = socket.handshake.query.version;
89
- logMsg(`Bridge connected, cli-side version v1.7.6-beta-20260425023224.0, browser-side version v${clientVersion}`);
89
+ logMsg(`Bridge connected, cli-side version v1.7.6, browser-side version v${clientVersion}`);
90
90
  socket.on(BridgeEvent.CallResponse, (params)=>{
91
91
  const id = params.id;
92
92
  const response = params.response;
@@ -110,7 +110,7 @@ class BridgeServer {
110
110
  setTimeout(()=>{
111
111
  this.onConnect?.();
112
112
  const payload = {
113
- version: "1.7.6-beta-20260425023224.0"
113
+ version: "1.7.6"
114
114
  };
115
115
  socket.emit(BridgeEvent.Connected, payload);
116
116
  Promise.resolve().then(()=>{
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/io-server.mjs","sources":["../../../src/bridge-mode/io-server.ts"],"sourcesContent":["import { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\ndeclare const __VERSION__: string;\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n // console.error('failed to kill port', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server and start listening on the specified host and port\n const httpServer = createServer();\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\n }\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (this.socket?.connected) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mode/io-server.mjs","sources":["../../../src/bridge-mode/io-server.ts"],"sourcesContent":["import { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\ndeclare const __VERSION__: string;\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n // console.error('failed to kill port', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server and start listening on the specified host and port\n const httpServer = createServer();\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\n }\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (this.socket?.connected) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
@@ -65,7 +65,7 @@ class ExtensionBridgePageBrowserSide extends page {
65
65
  throw new Error('Connection denied by user');
66
66
  }
67
67
  }
68
- this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.7.6-beta-20260425023224.0`, 'log');
68
+ this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.7.6`, 'log');
69
69
  }
70
70
  async connect() {
71
71
  return await this.setupBridgeClient();
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/page-browser-side.mjs","sources":["../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","Promise","resolve","BridgeClient","method","args","allowed","Error","BridgeEvent","tabId","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","url","options","tab","chrome","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mode/page-browser-side.mjs","sources":["../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","Promise","resolve","BridgeClient","method","args","allowed","Error","BridgeEvent","tabId","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","url","options","tab","chrome","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":";;;;;;;;;;;;;;AAkBO,MAAMA,uCAAuCC;IAuBlD,MAAc,oBAAoB;QAChC,MAAMC,WACJ,IAAI,CAAC,cAAc,IAAI,CAAC,eAAe,EAAEC,yBAAyB;QAMpE,IAAIC,0BAAsD,KAAO;QACjE,IAAI,IAAI,CAAC,mBAAmB,EAC1B,IAAI,CAAC,mBAAmB,GAAG,IAAIC,QAAiB,CAACC;YAC/CF,0BAA0BE;QAC5B;QAGF,IAAI,CAAC,YAAY,GAAG,IAAIC,aACtBL,UACA,OAAOM,QAAQC;YAEb,IAAI,IAAI,CAAC,mBAAmB,EAAE;gBAC5B,MAAMC,UAAU,MAAM,IAAI,CAAC,mBAAmB;gBAC9C,IAAI,CAACA,SACH,MAAM,IAAIC,MAAM;YAEpB;YAEA,IAAI,CAAC,YAAY,CAAC,CAAC,2BAA2B,EAAEH,QAAQ,EAAE;YAC1D,IAAIA,WAAWI,YAAY,oBAAoB,EAC7C,OAAO,IAAI,CAAC,oBAAoB,CAAC,KAAK,CACpC,IAAI,EACJH;YAIJ,IAAID,WAAWI,YAAY,iBAAiB,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,YAAY,cAAc,EACvC,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAEH;YAGzC,IAAID,WAAWI,YAAY,iBAAiB,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,YAAY,iBAAiB,EAC1C,OAAO,IAAI,CAAC,YAAY,CAACH,IAAI,CAAC,EAAE,EAAY;YAG9C,MAAMI,QAAQ,MAAM,IAAI,CAAC,cAAc;YACvC,IAAI,CAACA,SAASA,AAAU,MAAVA,OACZ,MAAM,IAAIF,MAAM;YAKlB,IAAIH,OAAO,UAAU,CAACM,WAAW,MAAM,GAAG;gBACxC,MAAMC,aAAaP,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,KAAK,CAACO,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAEN;YAClD;YAEA,IAAID,OAAO,UAAU,CAACQ,cAAc,MAAM,GAAG;gBAC3C,MAAMD,aAAaP,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,QAAQ,CAACO,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAEN;YACxD;YAEA,IAAI,CAAC,IAAI,CAACD,OAAyC,EAAE,YACnD,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEA,QAAQ,EAAE;YAInD,IAAI;gBAEF,MAAMS,SAAS,MAAM,IAAI,CAACT,OAAyC,IAC9DC;gBAEL,OAAOQ;YACT,EAAE,OAAOC,GAAG;gBACV,MAAMC,eAAeD,aAAaP,QAAQO,EAAE,OAAO,GAAG;gBACtD,IAAI,CAAC,YAAY,CACf,CAAC,sBAAsB,EAAEV,OAAO,EAAE,EAAEW,cAAc,EAClD;gBAEF,MAAM,IAAIR,MAAMQ,cAAc;oBAAE,OAAOD;gBAAE;YAC3C;QACF,GAEA,IACS,IAAI,CAAC,OAAO;QAGvB,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO;QAG/B,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC5B,IAAI,CAAC,YAAY,CAAC,oCAAoC;YACtD,MAAMR,UAAU,MAAM,IAAI,CAAC,mBAAmB;YAC9CN,wBAAwBM;YACxB,IAAI,CAAC,mBAAmB,GAAG;YAE3B,IAAI,CAACA,SAAS;gBACZ,IAAI,CAAC,YAAY,CAAC,6BAA6B;gBAC/C,IAAI,CAAC,YAAY,CAAC,UAAU;gBAC5B,IAAI,CAAC,YAAY,GAAG;gBACpB,MAAM,IAAIC,MAAM;YAClB;QACF;QAEA,IAAI,CAAC,YAAY,CACf,uCAAuC,IAAI,CAAC,YAAY,CAAC,aAAa,+BAAwC,EAC9G;IAEJ;IAEA,MAAa,UAAU;QACrB,OAAO,MAAM,IAAI,CAAC,iBAAiB;IACrC;IAEA,MAAa,qBACXS,GAAW,EACXC,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QACA,MAAMC,MAAM,MAAMC,OAAO,IAAI,CAAC,MAAM,CAAC;YAAEH;QAAI;QAC3C,MAAMP,QAAQS,IAAI,EAAE;QACpBE,OAAOX,OAAO;QAGd,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEO,KAAK,EAAE;QAC9C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAACP;QAE7B,IAAIQ,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAGhC,MAAM,IAAI,CAAC,cAAc,CAACR;IAC5B;IAEA,MAAa,kBACXQ,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QACA,MAAMI,OAAO,MAAMF,OAAO,IAAI,CAAC,KAAK,CAAC;YAAE,QAAQ;YAAM,eAAe;QAAK;QACzE,MAAMV,QAAQY,IAAI,CAAC,EAAE,EAAE;QACvBD,OAAOX,OAAO;QAEd,IAAI,CAAC,YAAY,CAAC,CAAC,0BAA0B,EAAEY,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE;QAE/D,IAAIJ,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAGhC,MAAM,IAAI,CAAC,cAAc,CAACR;IAC5B;IAEA,MAAa,kBAAkBQ,OAAiC,EAAE;QAChE,IAAI,CAAC,cAAc,GAAGA;IACxB;IAEA,MAAM,UAAU;QACd,IAAI,IAAI,CAAC,cAAc,EAAE,YAAY,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,GAAG;YACvE,IAAI,CAAC,YAAY,CAAC,+CAA+C;YACjE,KAAK,MAAMR,SAAS,IAAI,CAAC,kBAAkB,CACzC,MAAMU,OAAO,IAAI,CAAC,MAAM,CAACV;YAE3B,IAAI,CAAC,kBAAkB,GAAG,EAAE;QAC9B;QAEA,MAAM,KAAK,CAAC;QAEZ,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,IAAI,CAAC,YAAY,CAAC,UAAU;YAC5B,IAAI,CAAC,YAAY,GAAG;YACpB,IAAI,CAAC,YAAY;QACnB;IACF;IArMA,YACSa,cAAuB,EACvBC,eAA2B,KAAO,CAAC,EACnCC,eAGK,KAAO,CAAC,EACpBC,yBAAyB,IAAI,EACtBC,mBAA4C,CACnD;QACA,KAAK,CAACD,yBAAAA,iBAAAA,IAAAA,EAAAA,kBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,uBAAAA,KAAAA,IAnBR,uBAAO,gBAAP,SAEA,uBAAQ,kBAAR,SAEA,uBAAQ,sBAAR,SAGA,uBAAQ,uBAAR,cAGSH,cAAc,GAAdA,gBAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CAKAE,mBAAmB,GAAnBA,qBAAAA,IAAAA,CAjBF,YAAY,GAAwB,WAInC,kBAAkB,GAAa,EAAE,OAGjC,mBAAmB,GAA4B;IAavD;AA2LF"}
package/dist/es/cli.mjs CHANGED
@@ -40,7 +40,7 @@ tools = isBridge ? new WebMidsceneTools() : isCdp ? new WebCdpMidsceneTools(cdpE
40
40
  runToolsCLI(tools, 'midscene-web', {
41
41
  stripPrefix: 'web_',
42
42
  argv,
43
- version: "1.7.6-beta-20260425023224.0",
43
+ version: "1.7.6",
44
44
  extraCommands: createReportCliCommands()
45
45
  }).catch((e)=>{
46
46
  process.exit(reportCLIError(e));
@@ -7,7 +7,7 @@ class WebMCPServer extends BaseMCPServer {
7
7
  constructor(toolsManager){
8
8
  super({
9
9
  name: '@midscene/web-bridge-mcp',
10
- version: "1.7.6-beta-20260425023224.0",
10
+ version: "1.7.6",
11
11
  description: 'Control the browser using natural language commands'
12
12
  }, toolsManager);
13
13
  }
@@ -88,11 +88,9 @@ class WebCdpMidsceneTools extends BaseMidsceneTools {
88
88
  const targetId = getTargetId(page);
89
89
  if (targetId) saveTargetId(targetId);
90
90
  else debug('No targetId on page.target(); cross-command tab reuse disabled until puppeteer integration is updated.');
91
- const reportFileName = this.pendingReportFileName ?? this.readCliReportFileName();
91
+ const reportOptions = this.readCliReportAgentOptions();
92
92
  this.agent = new PuppeteerAgent(page, {
93
- ...reportFileName ? {
94
- reportFileName
95
- } : {}
93
+ ...reportOptions ?? {}
96
94
  });
97
95
  return this.agent;
98
96
  }
@@ -121,14 +119,9 @@ class WebCdpMidsceneTools extends BaseMidsceneTools {
121
119
  }
122
120
  this.agent = void 0;
123
121
  }
124
- const reportSession = this.createNewCliReportSession();
125
- this.pendingReportFileName = reportSession?.reportFileName;
126
- try {
127
- this.agent = await this.ensureAgent(url);
128
- } finally{
129
- this.pendingReportFileName = void 0;
130
- }
122
+ const reportSession = this.createNewCliReportSession(url ?? 'current-page');
131
123
  this.commitCliReportSession(reportSession);
124
+ this.agent = await this.ensureAgent(url);
132
125
  const screenshot = await this.agent.page?.screenshotBase64();
133
126
  const label = url ?? 'current page';
134
127
  return {
@@ -166,7 +159,7 @@ class WebCdpMidsceneTools extends BaseMidsceneTools {
166
159
  ];
167
160
  }
168
161
  constructor(cdpEndpoint){
169
- super(), _define_property(this, "cdpEndpoint", void 0), _define_property(this, "activeBrowser", null), _define_property(this, "pendingReportFileName", void 0);
162
+ super(), _define_property(this, "cdpEndpoint", void 0), _define_property(this, "activeBrowser", null);
170
163
  this.cdpEndpoint = cdpEndpoint;
171
164
  }
172
165
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools-cdp.mjs","sources":["../../src/mcp-tools-cdp.ts"],"sourcesContent":["import { ScreenshotItem, z } from '@midscene/core';\nimport { getDebug } from '@midscene/shared/logger';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { getProxyEndpoint } from './cdp-proxy-manager';\nimport {\n cleanupTargetIdFile,\n readSavedTargetId,\n saveTargetId,\n} from './cdp-target-store';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst debug = getDebug('mcp:cdp');\n\n/** CDP target discovery may need a brief moment after WebSocket open. */\nconst CDP_TARGET_DISCOVERY_DELAY_MS = 500;\n\n/**\n * puppeteer-core does not expose a public method for the underlying CDP\n * target id, so we reach into `_targetId`. Centralised here so a future\n * puppeteer release exposing this properly only requires one change.\n * Callers must treat the result as optional.\n */\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n *\n * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups\n * when Chrome's settings-based remote debugging is used.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n private pendingReportFileName?: string;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect via proxy to avoid repeated Chrome permission popups\n if (!this.activeBrowser) {\n const endpoint = await getProxyEndpoint(this.cdpEndpoint);\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n let pages = await browser.pages();\n\n // If no pages discovered, wait briefly and retry — some CDP targets\n // need a moment to appear after the WebSocket connection is established.\n if (pages.length === 0) {\n await new Promise((r) => setTimeout(r, CDP_TARGET_DISCOVERY_DELAY_MS));\n pages = await browser.pages();\n }\n\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n debug(\n 'Found %d page(s), %d web page(s): %o',\n pages.length,\n webPages.length,\n pages.map((p) => p.url()),\n );\n let page: Page;\n\n if (navigateToUrl) {\n if (webPages.length > 0) {\n // Reuse an existing page and navigate it — avoids creating invisible\n // tabs when Chrome uses settings-based remote debugging (no HTTP\n // discovery endpoints, /devtools/page/* returns 403).\n page = webPages[webPages.length - 1];\n await page.bringToFront();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // No existing web pages — fall back to creating a new tab\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n }\n } else {\n // Try to find the exact tab from a previous `connect` command via saved targetId.\n const savedTargetId = readSavedTargetId();\n let matchedPage: Page | undefined;\n\n if (savedTargetId && pages.length > 0) {\n matchedPage = pages.find((p) => getTargetId(p) === savedTargetId);\n if (matchedPage) {\n debug('Matched saved targetId %s', savedTargetId);\n } else {\n debug(\n 'Saved targetId %s not found among %d pages, falling back',\n savedTargetId,\n pages.length,\n );\n }\n }\n\n if (matchedPage) {\n page = matchedPage;\n } else if (webPages.length > 0) {\n page = webPages[webPages.length - 1];\n } else if (pages.length > 0) {\n page = pages[pages.length - 1];\n } else {\n page = await browser.newPage();\n }\n\n await page.bringToFront();\n }\n\n // Persist the targetId so subsequent CLI commands can find this exact tab\n const targetId = getTargetId(page);\n if (targetId) {\n saveTargetId(targetId);\n } else {\n // If puppeteer ever drops the private _targetId field, this branch\n // makes the regression visible instead of silently disabling the\n // cross-command tab reuse path.\n debug(\n 'No targetId on page.target(); cross-command tab reuse disabled until puppeteer integration is updated.',\n );\n }\n\n const reportFileName =\n this.pendingReportFileName ?? this.readCliReportFileName();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportFileName ? { reportFileName } : {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during connect:', e);\n }\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession();\n this.pendingReportFileName = reportSession?.reportFileName;\n try {\n this.agent = await this.ensureAgent(url);\n } finally {\n this.pendingReportFileName = undefined;\n }\n this.commitCliReportSession(reportSession);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected via CDP to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during disconnect:', e);\n }\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n cleanupTargetIdFile();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n"],"names":["debug","getDebug","CDP_TARGET_DISCOVERY_DELAY_MS","getTargetId","page","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","console","undefined","endpoint","getProxyEndpoint","puppeteer","browser","pages","Promise","r","setTimeout","webPages","p","savedTargetId","readSavedTargetId","matchedPage","targetId","saveTargetId","reportFileName","PuppeteerAgent","z","args","url","e","reportSession","screenshot","label","cleanupTargetIdFile","cdpEndpoint"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-tools-cdp.mjs","sources":["../../src/mcp-tools-cdp.ts"],"sourcesContent":["import { ScreenshotItem, z } from '@midscene/core';\nimport { getDebug } from '@midscene/shared/logger';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { getProxyEndpoint } from './cdp-proxy-manager';\nimport {\n cleanupTargetIdFile,\n readSavedTargetId,\n saveTargetId,\n} from './cdp-target-store';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst debug = getDebug('mcp:cdp');\n\n/** CDP target discovery may need a brief moment after WebSocket open. */\nconst CDP_TARGET_DISCOVERY_DELAY_MS = 500;\n\n/**\n * puppeteer-core does not expose a public method for the underlying CDP\n * target id, so we reach into `_targetId`. Centralised here so a future\n * puppeteer release exposing this properly only requires one change.\n * Callers must treat the result as optional.\n */\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n *\n * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups\n * when Chrome's settings-based remote debugging is used.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect via proxy to avoid repeated Chrome permission popups\n if (!this.activeBrowser) {\n const endpoint = await getProxyEndpoint(this.cdpEndpoint);\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n let pages = await browser.pages();\n\n // If no pages discovered, wait briefly and retry — some CDP targets\n // need a moment to appear after the WebSocket connection is established.\n if (pages.length === 0) {\n await new Promise((r) => setTimeout(r, CDP_TARGET_DISCOVERY_DELAY_MS));\n pages = await browser.pages();\n }\n\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n debug(\n 'Found %d page(s), %d web page(s): %o',\n pages.length,\n webPages.length,\n pages.map((p) => p.url()),\n );\n let page: Page;\n\n if (navigateToUrl) {\n if (webPages.length > 0) {\n // Reuse an existing page and navigate it — avoids creating invisible\n // tabs when Chrome uses settings-based remote debugging (no HTTP\n // discovery endpoints, /devtools/page/* returns 403).\n page = webPages[webPages.length - 1];\n await page.bringToFront();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // No existing web pages — fall back to creating a new tab\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n }\n } else {\n // Try to find the exact tab from a previous `connect` command via saved targetId.\n const savedTargetId = readSavedTargetId();\n let matchedPage: Page | undefined;\n\n if (savedTargetId && pages.length > 0) {\n matchedPage = pages.find((p) => getTargetId(p) === savedTargetId);\n if (matchedPage) {\n debug('Matched saved targetId %s', savedTargetId);\n } else {\n debug(\n 'Saved targetId %s not found among %d pages, falling back',\n savedTargetId,\n pages.length,\n );\n }\n }\n\n if (matchedPage) {\n page = matchedPage;\n } else if (webPages.length > 0) {\n page = webPages[webPages.length - 1];\n } else if (pages.length > 0) {\n page = pages[pages.length - 1];\n } else {\n page = await browser.newPage();\n }\n\n await page.bringToFront();\n }\n\n // Persist the targetId so subsequent CLI commands can find this exact tab\n const targetId = getTargetId(page);\n if (targetId) {\n saveTargetId(targetId);\n } else {\n // If puppeteer ever drops the private _targetId field, this branch\n // makes the regression visible instead of silently disabling the\n // cross-command tab reuse path.\n debug(\n 'No targetId on page.target(); cross-command tab reuse disabled until puppeteer integration is updated.',\n );\n }\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportOptions ?? {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during connect:', e);\n }\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected via CDP to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during disconnect:', e);\n }\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n cleanupTargetIdFile();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n"],"names":["debug","getDebug","CDP_TARGET_DISCOVERY_DELAY_MS","getTargetId","page","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","console","undefined","endpoint","getProxyEndpoint","puppeteer","browser","pages","Promise","r","setTimeout","webPages","p","savedTargetId","readSavedTargetId","matchedPage","targetId","saveTargetId","reportOptions","PuppeteerAgent","z","args","url","e","reportSession","screenshot","label","cleanupTargetIdFile","cdpEndpoint"],"mappings":";;;;;;;;;;;;;;;;;;AAgBA,MAAMA,QAAQC,SAAS;AAGvB,MAAMC,gCAAgC;AAQtC,SAASC,YAAYC,IAAU;IAC7B,OAAQA,KAAK,MAAM,GAAyC,SAAS;AACvE;AAWO,MAAMC,4BAA4BC;IAC7B,0BAA0B;QAClC,OAAO;IACT;IASU,wBAAwB;QAChC,OAAO,IAAIC,WAAW;YACpB,YAAYC,eAAe,MAAM,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YAAYC,aAAsB,EAA2B;QAE3E,IAAI,IAAI,CAAC,KAAK,IAAIA,eAAe;YAC/B,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAOC,OAAO;gBACdC,QAAQ,KAAK,CAAC,2CAA2CD;YAC3D;YACA,IAAI,CAAC,KAAK,GAAGE;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAGjC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE;YACvB,MAAMC,WAAW,MAAMC,iBAAiB,IAAI,CAAC,WAAW;YACxD,IAAI,CAAC,aAAa,GAAG,MAAMC,eAAAA,OAAiB,CAAC;gBAC3C,mBAAmBF;gBACnB,iBAAiB;YACnB;QACF;QAEA,MAAMG,UAAU,IAAI,CAAC,aAAa;QAClC,IAAIC,QAAQ,MAAMD,QAAQ,KAAK;QAI/B,IAAIC,AAAiB,MAAjBA,MAAM,MAAM,EAAQ;YACtB,MAAM,IAAIC,QAAQ,CAACC,IAAMC,WAAWD,GAAGlB;YACvCgB,QAAQ,MAAMD,QAAQ,KAAK;QAC7B;QAEA,MAAMK,WAAWJ,MAAM,MAAM,CAAC,CAACK,IAAM,eAAe,IAAI,CAACA,EAAE,GAAG;QAC9DvB,MACE,wCACAkB,MAAM,MAAM,EACZI,SAAS,MAAM,EACfJ,MAAM,GAAG,CAAC,CAACK,IAAMA,EAAE,GAAG;QAExB,IAAInB;QAEJ,IAAIM,eACF,IAAIY,SAAS,MAAM,GAAG,GAAG;YAIvBlB,OAAOkB,QAAQ,CAACA,SAAS,MAAM,GAAG,EAAE;YACpC,MAAMlB,KAAK,YAAY;YACvB,MAAMA,KAAK,IAAI,CAACM,eAAe;gBAC7B,SAAS;gBACT,WAAW;YACb;QACF,OAAO;YAELN,OAAO,MAAMa,QAAQ,OAAO;YAC5B,MAAMb,KAAK,IAAI,CAACM,eAAe;gBAC7B,SAAS;gBACT,WAAW;YACb;QACF;aACK;YAEL,MAAMc,gBAAgBC;YACtB,IAAIC;YAEJ,IAAIF,iBAAiBN,MAAM,MAAM,GAAG,GAAG;gBACrCQ,cAAcR,MAAM,IAAI,CAAC,CAACK,IAAMpB,YAAYoB,OAAOC;gBAC/CE,cACF1B,MAAM,6BAA6BwB,iBAEnCxB,MACE,4DACAwB,eACAN,MAAM,MAAM;YAGlB;YAGEd,OADEsB,cACKA,cACEJ,SAAS,MAAM,GAAG,IACpBA,QAAQ,CAACA,SAAS,MAAM,GAAG,EAAE,GAC3BJ,MAAM,MAAM,GAAG,IACjBA,KAAK,CAACA,MAAM,MAAM,GAAG,EAAE,GAEvB,MAAMD,QAAQ,OAAO;YAG9B,MAAMb,KAAK,YAAY;QACzB;QAGA,MAAMuB,WAAWxB,YAAYC;QAC7B,IAAIuB,UACFC,aAAaD;aAKb3B,MACE;QAIJ,MAAM6B,gBAAgB,IAAI,CAAC,yBAAyB;QACpD,IAAI,CAAC,KAAK,GAAG,IAAIC,eAAe1B,MAAkC;YAChE,GAAIyB,iBAAiB,CAAC,CAAC;QACzB;QACA,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAa,UAAyB;QACpC,MAAM,KAAK,CAAC;QACZ,IAAI,IAAI,CAAC,aAAa,EAAE;YACtB,IAAI,CAAC,aAAa,CAAC,UAAU;YAC7B,IAAI,CAAC,aAAa,GAAG;QACvB;IACF;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKE,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAOC;oBACd,MAAM,EAAEC,GAAG,EAAE,GAAGD;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAOE,GAAG;4BACVtB,QAAQ,KAAK,CAAC,2CAA2CsB;wBAC3D;wBACA,IAAI,CAAC,KAAK,GAAGrB;oBACf;oBAEA,MAAMsB,gBAAgB,IAAI,CAAC,yBAAyB,CAClDF,OAAO;oBAET,IAAI,CAAC,sBAAsB,CAACE;oBAC5B,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAACF;oBAEpC,MAAMG,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQJ,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,sBAAsB,EAAEI,OAAO;4BAAC;+BACnDD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAOF,GAAG;4BACVtB,QAAQ,KAAK,CAAC,8CAA8CsB;wBAC9D;wBACA,IAAI,CAAC,KAAK,GAAGrB;oBACf;oBACA,IAAI,IAAI,CAAC,aAAa,EAAE;wBACtB,IAAI,CAAC,aAAa,CAAC,UAAU;wBAC7B,IAAI,CAAC,aAAa,GAAG;oBACvB;oBACAyB;oBACA,OAAO,IAAI,CAAC,eAAe,CACzB;gBAEJ;YACF;SACD;IACH;IAxMA,YAAYC,WAAmB,CAAE;QAC/B,KAAK,IAJP,uBAAQ,eAAR,SACA,uBAAQ,iBAAgC;QAItC,IAAI,CAAC,WAAW,GAAGA;IACrB;AAsMF"}
@@ -9,16 +9,6 @@ import { resolveChromePath } from "@midscene/shared/mcp/chrome-path";
9
9
  import puppeteer_core from "puppeteer-core";
10
10
  import { PuppeteerAgent } from "./puppeteer/index.mjs";
11
11
  import { StaticPage } from "./static/index.mjs";
12
- function _define_property(obj, key, value) {
13
- if (key in obj) Object.defineProperty(obj, key, {
14
- value: value,
15
- enumerable: true,
16
- configurable: true,
17
- writable: true
18
- });
19
- else obj[key] = value;
20
- return obj;
21
- }
22
12
  const ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');
23
13
  const USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');
24
14
  const browserManager = {
@@ -154,11 +144,9 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
154
144
  page = webPages.length > 0 ? webPages[webPages.length - 1] : pages[pages.length - 1] || await browser.newPage();
155
145
  if (reused) await page.bringToFront();
156
146
  }
157
- const reportFileName = this.pendingReportFileName ?? this.readCliReportFileName();
147
+ const reportOptions = this.readCliReportAgentOptions();
158
148
  this.agent = new PuppeteerAgent(page, {
159
- ...reportFileName ? {
160
- reportFileName
161
- } : {}
149
+ ...reportOptions ?? {}
162
150
  });
163
151
  return this.agent;
164
152
  }
@@ -182,14 +170,9 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
182
170
  } catch {}
183
171
  this.agent = void 0;
184
172
  }
185
- const reportSession = this.createNewCliReportSession();
186
- this.pendingReportFileName = reportSession?.reportFileName;
187
- try {
188
- this.agent = await this.ensureAgent(url);
189
- } finally{
190
- this.pendingReportFileName = void 0;
191
- }
173
+ const reportSession = this.createNewCliReportSession(url ?? 'current-page');
192
174
  this.commitCliReportSession(reportSession);
175
+ this.agent = await this.ensureAgent(url);
193
176
  const screenshot = await this.agent.page?.screenshotBase64();
194
177
  const label = url ?? 'current page';
195
178
  return {
@@ -235,9 +218,6 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
235
218
  }
236
219
  ];
237
220
  }
238
- constructor(...args){
239
- super(...args), _define_property(this, "pendingReportFileName", void 0);
240
- }
241
221
  }
242
222
  export { WebPuppeteerMidsceneTools };
243
223
 
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools-puppeteer.mjs","sources":["../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport { resolveChromePath } from '@midscene/shared/mcp/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nconst browserManager = {\n activeBrowser: null as Browser | null,\n\n async getOrLaunch(): Promise<{ browser: Browser; reused: boolean }> {\n if (existsSync(ENDPOINT_FILE)) {\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome();\n await writeFile(ENDPOINT_FILE, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n },\n\n async closeBrowser(): Promise<void> {\n if (!existsSync(ENDPOINT_FILE)) return;\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n },\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n },\n\n async launchDetachedChrome(): Promise<string> {\n const chromePath = resolveChromePath();\n\n await mkdir(USER_DATA_DIR, { recursive: true });\n\n const args = [\n '--headless=new',\n `--user-data-dir=${USER_DATA_DIR}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n '--window-size=1280,800',\n '--force-color-profile=srgb',\n ];\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n proc.stderr!.removeListener('data', onData);\n resolve(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n proc.on('exit', (code) => {\n proc.stderr!.removeListener('data', onData);\n reject(\n new Error(\n `Chrome exited with code ${code} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n });\n\n setTimeout(\n () =>\n reject(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n ),\n 15000,\n );\n });\n },\n};\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n private pendingReportFileName?: string;\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await browserManager.getOrLaunch();\n browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n }\n\n const reportFileName =\n this.pendingReportFileName ?? this.readCliReportFileName();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportFileName ? { reportFileName } : {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession();\n this.pendingReportFileName = reportSession?.reportFileName;\n try {\n this.agent = await this.ensureAgent(url);\n } finally {\n this.pendingReportFileName = undefined;\n }\n this.commitCliReportSession(reportSession);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","browserManager","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","mkdir","args","proc","spawn","Promise","resolve","reject","output","onData","data","match","code","Error","setTimeout","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","undefined","reused","pages","page","webPages","p","reportFileName","PuppeteerAgent","z","url","reportSession","screenshot","label"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAeA,MAAMA,gBAAgBC,KAAKC,UAAU;AACrC,MAAMC,gBAAgBF,KAAKC,UAAU;AAMrC,MAAME,iBAAiB;IACrB,eAAe;IAEf,MAAM;QACJ,IAAIC,WAAWL,gBACb,IAAI;YACF,MAAMM,WAAY,OAAMC,SAASP,eAAe,QAAO,EAAG,IAAI;YAC9D,MAAMQ,UAAU,MAAMC,eAAAA,OAAiB,CAAC;gBACtC,mBAAmBH;gBACnB,iBAAiB;YACnB;YACA,OAAO;gBAAEE;gBAAS,QAAQ;YAAK;QACjC,EAAE,OAAM;YACN,IAAI;gBACF,MAAME,OAAOV;YACf,EAAE,OAAM,CAAC;QACX;QAGF,MAAMW,aAAa,MAAM,IAAI,CAAC,oBAAoB;QAClD,MAAMC,UAAUZ,eAAeW;QAE/B,MAAMH,UAAU,MAAMC,eAAAA,OAAiB,CAAC;YACtC,mBAAmBE;YACnB,iBAAiB;QACnB;QACA,OAAO;YAAEH;YAAS,QAAQ;QAAM;IAClC;IAEA,MAAM;QACJ,IAAI,CAACH,WAAWL,gBAAgB;QAChC,IAAI;YACF,MAAMM,WAAY,OAAMC,SAASP,eAAe,QAAO,EAAG,IAAI;YAC9D,MAAMQ,UAAU,MAAMC,eAAAA,OAAiB,CAAC;gBACtC,mBAAmBH;YACrB;YACA,MAAME,QAAQ,KAAK;QACrB,EAAE,OAAM,CAAC;QACT,IAAI;YACF,MAAME,OAAOV;QACf,EAAE,OAAM,CAAC;IACX;IAEA;QACE,IAAI,IAAI,CAAC,aAAa,EAAE;YACtB,IAAI,CAAC,aAAa,CAAC,UAAU;YAC7B,IAAI,CAAC,aAAa,GAAG;QACvB;IACF;IAEA,MAAM;QACJ,MAAMa,aAAaC;QAEnB,MAAMC,MAAMZ,eAAe;YAAE,WAAW;QAAK;QAE7C,MAAMa,OAAO;YACX;YACA,CAAC,gBAAgB,EAAEb,eAAe;YAClC;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QAED,MAAMc,OAAOC,MAAML,YAAYG,MAAM;YACnC,UAAU;YACV,OAAO;gBAAC;gBAAU;gBAAU;aAAO;QACrC;QACAC,KAAK,KAAK;QAEV,OAAO,IAAIE,QAAgB,CAACC,SAASC;YACnC,IAAIC,SAAS;YACb,MAAMC,SAAS,CAACC;gBACdF,UAAUE,KAAK,QAAQ;gBACvB,MAAMC,QAAQH,OAAO,KAAK,CAAC;gBAC3B,IAAIG,OAAO;oBACTR,KAAK,MAAM,CAAE,cAAc,CAAC,QAAQM;oBACpCH,QAAQK,KAAK,CAAC,EAAE;gBAClB;YACF;YACAR,KAAK,MAAM,CAAE,EAAE,CAAC,QAAQM;YAExBN,KAAK,EAAE,CAAC,QAAQ,CAACS;gBACfT,KAAK,MAAM,CAAE,cAAc,CAAC,QAAQM;gBACpCF,OACE,IAAIM,MACF,CAAC,wBAAwB,EAAED,KAAK,4CAA4C,EAAEJ,OAAO,uEAAuE,CAAC;YAGnK;YAEAM,WACE,IACEP,OACE,IAAIM,MACF,CAAC,uCAAuC,EAAEL,OAAO,uEAAuE,CAAC,IAG/H;QAEJ;IACF;AACF;AAMO,MAAMO,kCAAkCC;IACnC,0BAA0B;QAClC,OAAO;IACT;IAGU,wBAAwB;QAChC,OAAO,IAAIC,WAAW;YACpB,YAAYC,eAAe,MAAM,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YAAYC,aAAsB,EAA2B;QAE3E,IAAI,IAAI,CAAC,KAAK,IAAIA,eAAe;YAC/B,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAM,CAAC;YACT,IAAI,CAAC,KAAK,GAAGC;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAEjC,MAAM,EAAE3B,OAAO,EAAE4B,MAAM,EAAE,GAAG,MAAMhC,eAAe,WAAW;QAC5DA,eAAe,aAAa,GAAGI;QAE/B,MAAM6B,QAAQ,MAAM7B,QAAQ,KAAK;QACjC,IAAI8B;QAEJ,IAAIJ,eAAe;YACjBI,OAAO,MAAM9B,QAAQ,OAAO;YAC5B,MAAM8B,KAAK,IAAI,CAACJ,eAAe;gBAC7B,SAAS;gBACT,WAAW;YACb;QACF,OAAO;YAEL,MAAMK,WAAWF,MAAM,MAAM,CAAC,CAACG,IAAM,eAAe,IAAI,CAACA,EAAE,GAAG;YAC9DF,OACEC,SAAS,MAAM,GAAG,IACdA,QAAQ,CAACA,SAAS,MAAM,GAAG,EAAE,GAC7BF,KAAK,CAACA,MAAM,MAAM,GAAG,EAAE,IAAK,MAAM7B,QAAQ,OAAO;YAEvD,IAAI4B,QACF,MAAME,KAAK,YAAY;QAE3B;QAEA,MAAMG,iBACJ,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAAC,qBAAqB;QAC1D,IAAI,CAAC,KAAK,GAAG,IAAIC,eAAeJ,MAAkC;YAChE,GAAIG,iBAAiB;gBAAEA;YAAe,IAAI,CAAC,CAAC;QAC9C;QACA,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAa,UAAyB;QACpC,MAAM,KAAK,CAAC;QACZrC,eAAe,UAAU;IAC3B;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKuC,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAO3B;oBACd,MAAM,EAAE4B,GAAG,EAAE,GAAG5B;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGmB;oBACf;oBAEA,MAAMU,gBAAgB,IAAI,CAAC,yBAAyB;oBACpD,IAAI,CAAC,qBAAqB,GAAGA,eAAe;oBAC5C,IAAI;wBACF,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAACD;oBACtC,SAAU;wBACR,IAAI,CAAC,qBAAqB,GAAGT;oBAC/B;oBACA,IAAI,CAAC,sBAAsB,CAACU;oBAE5B,MAAMC,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQH,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,cAAc,EAAEG,OAAO;4BAAC;+BAC3CD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGX;oBACf;oBACA/B,eAAe,UAAU;oBACzB,OAAO,IAAI,CAAC,eAAe,CACzB;gBAEJ;YACF;YACA;gBACE,MAAM;gBACN,aAAa;gBACb,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAG+B;oBACf;oBACA,MAAM/B,eAAe,YAAY;oBACjC,OAAO,IAAI,CAAC,eAAe,CAAC;gBAC9B;YACF;SACD;IACH;;QA7IK,gBAIL,uBAAQ,yBAAR;;AA0IF"}
1
+ {"version":3,"file":"mcp-tools-puppeteer.mjs","sources":["../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport { resolveChromePath } from '@midscene/shared/mcp/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nconst browserManager = {\n activeBrowser: null as Browser | null,\n\n async getOrLaunch(): Promise<{ browser: Browser; reused: boolean }> {\n if (existsSync(ENDPOINT_FILE)) {\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome();\n await writeFile(ENDPOINT_FILE, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n },\n\n async closeBrowser(): Promise<void> {\n if (!existsSync(ENDPOINT_FILE)) return;\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n },\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n },\n\n async launchDetachedChrome(): Promise<string> {\n const chromePath = resolveChromePath();\n\n await mkdir(USER_DATA_DIR, { recursive: true });\n\n const args = [\n '--headless=new',\n `--user-data-dir=${USER_DATA_DIR}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n '--window-size=1280,800',\n '--force-color-profile=srgb',\n ];\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n proc.stderr!.removeListener('data', onData);\n resolve(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n proc.on('exit', (code) => {\n proc.stderr!.removeListener('data', onData);\n reject(\n new Error(\n `Chrome exited with code ${code} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n });\n\n setTimeout(\n () =>\n reject(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n ),\n 15000,\n );\n });\n },\n};\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await browserManager.getOrLaunch();\n browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n }\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportOptions ?? {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","browserManager","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","mkdir","args","proc","spawn","Promise","resolve","reject","output","onData","data","match","code","Error","setTimeout","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","undefined","reused","pages","page","webPages","p","reportOptions","PuppeteerAgent","z","url","reportSession","screenshot","label"],"mappings":";;;;;;;;;;;AAeA,MAAMA,gBAAgBC,KAAKC,UAAU;AACrC,MAAMC,gBAAgBF,KAAKC,UAAU;AAMrC,MAAME,iBAAiB;IACrB,eAAe;IAEf,MAAM;QACJ,IAAIC,WAAWL,gBACb,IAAI;YACF,MAAMM,WAAY,OAAMC,SAASP,eAAe,QAAO,EAAG,IAAI;YAC9D,MAAMQ,UAAU,MAAMC,eAAAA,OAAiB,CAAC;gBACtC,mBAAmBH;gBACnB,iBAAiB;YACnB;YACA,OAAO;gBAAEE;gBAAS,QAAQ;YAAK;QACjC,EAAE,OAAM;YACN,IAAI;gBACF,MAAME,OAAOV;YACf,EAAE,OAAM,CAAC;QACX;QAGF,MAAMW,aAAa,MAAM,IAAI,CAAC,oBAAoB;QAClD,MAAMC,UAAUZ,eAAeW;QAE/B,MAAMH,UAAU,MAAMC,eAAAA,OAAiB,CAAC;YACtC,mBAAmBE;YACnB,iBAAiB;QACnB;QACA,OAAO;YAAEH;YAAS,QAAQ;QAAM;IAClC;IAEA,MAAM;QACJ,IAAI,CAACH,WAAWL,gBAAgB;QAChC,IAAI;YACF,MAAMM,WAAY,OAAMC,SAASP,eAAe,QAAO,EAAG,IAAI;YAC9D,MAAMQ,UAAU,MAAMC,eAAAA,OAAiB,CAAC;gBACtC,mBAAmBH;YACrB;YACA,MAAME,QAAQ,KAAK;QACrB,EAAE,OAAM,CAAC;QACT,IAAI;YACF,MAAME,OAAOV;QACf,EAAE,OAAM,CAAC;IACX;IAEA;QACE,IAAI,IAAI,CAAC,aAAa,EAAE;YACtB,IAAI,CAAC,aAAa,CAAC,UAAU;YAC7B,IAAI,CAAC,aAAa,GAAG;QACvB;IACF;IAEA,MAAM;QACJ,MAAMa,aAAaC;QAEnB,MAAMC,MAAMZ,eAAe;YAAE,WAAW;QAAK;QAE7C,MAAMa,OAAO;YACX;YACA,CAAC,gBAAgB,EAAEb,eAAe;YAClC;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QAED,MAAMc,OAAOC,MAAML,YAAYG,MAAM;YACnC,UAAU;YACV,OAAO;gBAAC;gBAAU;gBAAU;aAAO;QACrC;QACAC,KAAK,KAAK;QAEV,OAAO,IAAIE,QAAgB,CAACC,SAASC;YACnC,IAAIC,SAAS;YACb,MAAMC,SAAS,CAACC;gBACdF,UAAUE,KAAK,QAAQ;gBACvB,MAAMC,QAAQH,OAAO,KAAK,CAAC;gBAC3B,IAAIG,OAAO;oBACTR,KAAK,MAAM,CAAE,cAAc,CAAC,QAAQM;oBACpCH,QAAQK,KAAK,CAAC,EAAE;gBAClB;YACF;YACAR,KAAK,MAAM,CAAE,EAAE,CAAC,QAAQM;YAExBN,KAAK,EAAE,CAAC,QAAQ,CAACS;gBACfT,KAAK,MAAM,CAAE,cAAc,CAAC,QAAQM;gBACpCF,OACE,IAAIM,MACF,CAAC,wBAAwB,EAAED,KAAK,4CAA4C,EAAEJ,OAAO,uEAAuE,CAAC;YAGnK;YAEAM,WACE,IACEP,OACE,IAAIM,MACF,CAAC,uCAAuC,EAAEL,OAAO,uEAAuE,CAAC,IAG/H;QAEJ;IACF;AACF;AAMO,MAAMO,kCAAkCC;IACnC,0BAA0B;QAClC,OAAO;IACT;IAEU,wBAAwB;QAChC,OAAO,IAAIC,WAAW;YACpB,YAAYC,eAAe,MAAM,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YAAYC,aAAsB,EAA2B;QAE3E,IAAI,IAAI,CAAC,KAAK,IAAIA,eAAe;YAC/B,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAM,CAAC;YACT,IAAI,CAAC,KAAK,GAAGC;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAEjC,MAAM,EAAE3B,OAAO,EAAE4B,MAAM,EAAE,GAAG,MAAMhC,eAAe,WAAW;QAC5DA,eAAe,aAAa,GAAGI;QAE/B,MAAM6B,QAAQ,MAAM7B,QAAQ,KAAK;QACjC,IAAI8B;QAEJ,IAAIJ,eAAe;YACjBI,OAAO,MAAM9B,QAAQ,OAAO;YAC5B,MAAM8B,KAAK,IAAI,CAACJ,eAAe;gBAC7B,SAAS;gBACT,WAAW;YACb;QACF,OAAO;YAEL,MAAMK,WAAWF,MAAM,MAAM,CAAC,CAACG,IAAM,eAAe,IAAI,CAACA,EAAE,GAAG;YAC9DF,OACEC,SAAS,MAAM,GAAG,IACdA,QAAQ,CAACA,SAAS,MAAM,GAAG,EAAE,GAC7BF,KAAK,CAACA,MAAM,MAAM,GAAG,EAAE,IAAK,MAAM7B,QAAQ,OAAO;YAEvD,IAAI4B,QACF,MAAME,KAAK,YAAY;QAE3B;QAEA,MAAMG,gBAAgB,IAAI,CAAC,yBAAyB;QACpD,IAAI,CAAC,KAAK,GAAG,IAAIC,eAAeJ,MAAkC;YAChE,GAAIG,iBAAiB,CAAC,CAAC;QACzB;QACA,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAa,UAAyB;QACpC,MAAM,KAAK,CAAC;QACZrC,eAAe,UAAU;IAC3B;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKuC,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAO3B;oBACd,MAAM,EAAE4B,GAAG,EAAE,GAAG5B;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGmB;oBACf;oBAEA,MAAMU,gBAAgB,IAAI,CAAC,yBAAyB,CAClDD,OAAO;oBAET,IAAI,CAAC,sBAAsB,CAACC;oBAC5B,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAACD;oBAEpC,MAAME,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQH,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,cAAc,EAAEG,OAAO;4BAAC;+BAC3CD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGX;oBACf;oBACA/B,eAAe,UAAU;oBACzB,OAAO,IAAI,CAAC,eAAe,CACzB;gBAEJ;YACF;YACA;gBACE,MAAM;gBACN,aAAa;gBACb,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAG+B;oBACf;oBACA,MAAM/B,eAAe,YAAY;oBACjC,OAAO,IAAI,CAAC,eAAe,CAAC;gBAC9B;YACF;SACD;IACH;AACF"}
@@ -2,16 +2,6 @@ import { ScreenshotItem, z } from "@midscene/core";
2
2
  import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
3
3
  import { AgentOverChromeBridge } from "./bridge-mode/index.mjs";
4
4
  import { StaticPage } from "./static/index.mjs";
5
- function _define_property(obj, key, value) {
6
- if (key in obj) Object.defineProperty(obj, key, {
7
- value: value,
8
- enumerable: true,
9
- configurable: true,
10
- writable: true
11
- });
12
- else obj[key] = value;
13
- return obj;
14
- }
15
5
  class WebMidsceneTools extends BaseMidsceneTools {
16
6
  getCliReportSessionName() {
17
7
  return 'midscene-web';
@@ -40,12 +30,10 @@ class WebMidsceneTools extends BaseMidsceneTools {
40
30
  return this.agent;
41
31
  }
42
32
  async initBridgeModeAgent(url) {
43
- const reportFileName = this.pendingReportFileName ?? this.readCliReportFileName();
33
+ const reportOptions = this.readCliReportAgentOptions();
44
34
  const agent = new AgentOverChromeBridge({
45
35
  closeConflictServer: true,
46
- ...reportFileName ? {
47
- reportFileName
48
- } : {}
36
+ ...reportOptions ?? {}
49
37
  });
50
38
  if (url) await agent.connectNewTabWithUrl(url);
51
39
  else await agent.connectCurrentTab();
@@ -67,14 +55,9 @@ class WebMidsceneTools extends BaseMidsceneTools {
67
55
  } catch {}
68
56
  this.agent = void 0;
69
57
  }
70
- const reportSession = this.createNewCliReportSession();
71
- this.pendingReportFileName = reportSession?.reportFileName;
72
- try {
73
- this.agent = await this.initBridgeModeAgent(url);
74
- } finally{
75
- this.pendingReportFileName = void 0;
76
- }
58
+ const reportSession = this.createNewCliReportSession(url ?? 'current-tab');
77
59
  this.commitCliReportSession(reportSession);
60
+ this.agent = await this.initBridgeModeAgent(url);
78
61
  const screenshot = await this.agent.page?.screenshotBase64();
79
62
  const label = url ?? 'current tab';
80
63
  return {
@@ -96,9 +79,6 @@ class WebMidsceneTools extends BaseMidsceneTools {
96
79
  }
97
80
  ];
98
81
  }
99
- constructor(...args){
100
- super(...args), _define_property(this, "pendingReportFileName", void 0);
101
- }
102
82
  }
103
83
  export { WebMidsceneTools };
104
84
 
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools.mjs","sources":["../../src/mcp-tools.ts"],"sourcesContent":["import { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport { AgentOverChromeBridge } from './bridge-mode';\nimport { StaticPage } from './static';\n\n/**\n * Tools manager for Web bridge-mode MCP\n */\nexport class WebMidsceneTools extends BaseMidsceneTools<AgentOverChromeBridge> {\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n private pendingReportFileName?: string;\n\n protected createTemporaryDevice() {\n // Use require to avoid type incompatibility with DeviceAction vs ActionSpaceItem\n // StaticPage.actionSpace() returns DeviceAction[] which is compatible at runtime\n // Use screenshotBase64 field to avoid async ScreenshotItem.create()\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n openNewTabWithUrl?: string,\n ): Promise<AgentOverChromeBridge> {\n // Re-init if URL provided\n if (this.agent && openNewTabWithUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect to current tab when no URL provided (handles CLI stateless calls)\n this.agent = await this.initBridgeModeAgent(openNewTabWithUrl);\n\n return this.agent;\n }\n\n private async initBridgeModeAgent(\n url?: string,\n ): Promise<AgentOverChromeBridge> {\n const reportFileName =\n this.pendingReportFileName ?? this.readCliReportFileName();\n const agent = new AgentOverChromeBridge({\n closeConflictServer: true,\n ...(reportFileName ? { reportFileName } : {}),\n });\n\n if (!url) {\n await agent.connectCurrentTab();\n } else {\n await agent.connectNewTabWithUrl(url);\n }\n\n return agent;\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to web page. If URL provided, opens new tab; otherwise connects to current tab.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to connect current tab)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Bypass ensureAgent's URL check — directly init bridge agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n const reportSession = this.createNewCliReportSession();\n this.pendingReportFileName = reportSession?.reportFileName;\n try {\n this.agent = await this.initBridgeModeAgent(url);\n } finally {\n this.pendingReportFileName = undefined;\n }\n this.commitCliReportSession(reportSession);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current tab';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page and release browser resources',\n schema: {},\n handler: this.createDisconnectHandler('web page'),\n },\n ];\n }\n}\n"],"names":["WebMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","openNewTabWithUrl","error","console","undefined","url","reportFileName","agent","AgentOverChromeBridge","z","args","reportSession","screenshot","label"],"mappings":";;;;;;;;;;;;;;AASO,MAAMA,yBAAyBC;IAC1B,0BAA0B;QAClC,OAAO;IACT;IAGU,wBAAwB;QAIhC,OAAO,IAAIC,WAAW;YACpB,YAAYC,eAAe,MAAM,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YACdC,iBAA0B,EACM;QAEhC,IAAI,IAAI,CAAC,KAAK,IAAIA,mBAAmB;YACnC,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAOC,OAAO;gBACdC,QAAQ,KAAK,CAAC,2CAA2CD;YAC3D;YACA,IAAI,CAAC,KAAK,GAAGE;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAGjC,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACH;QAE5C,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAc,oBACZI,GAAY,EACoB;QAChC,MAAMC,iBACJ,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAAC,qBAAqB;QAC1D,MAAMC,QAAQ,IAAIC,sBAAsB;YACtC,qBAAqB;YACrB,GAAIF,iBAAiB;gBAAEA;YAAe,IAAI,CAAC,CAAC;QAC9C;QAEA,IAAKD,KAGH,MAAME,MAAM,oBAAoB,CAACF;aAFjC,MAAME,MAAM,iBAAiB;QAK/B,OAAOA;IACT;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKE,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAOC;oBACd,MAAM,EAAEL,GAAG,EAAE,GAAGK;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGN;oBACf;oBACA,MAAMO,gBAAgB,IAAI,CAAC,yBAAyB;oBACpD,IAAI,CAAC,qBAAqB,GAAGA,eAAe;oBAC5C,IAAI;wBACF,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACN;oBAC9C,SAAU;wBACR,IAAI,CAAC,qBAAqB,GAAGD;oBAC/B;oBACA,IAAI,CAAC,sBAAsB,CAACO;oBAE5B,MAAMC,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQR,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,cAAc,EAAEQ,OAAO;4BAAC;+BAC3CD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS,IAAI,CAAC,uBAAuB,CAAC;YACxC;SACD;IACH;;QA5GK,gBAIL,uBAAQ,yBAAR;;AAyGF"}
1
+ {"version":3,"file":"mcp-tools.mjs","sources":["../../src/mcp-tools.ts"],"sourcesContent":["import { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport { AgentOverChromeBridge } from './bridge-mode';\nimport { StaticPage } from './static';\n\n/**\n * Tools manager for Web bridge-mode MCP\n */\nexport class WebMidsceneTools extends BaseMidsceneTools<AgentOverChromeBridge> {\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected createTemporaryDevice() {\n // Use require to avoid type incompatibility with DeviceAction vs ActionSpaceItem\n // StaticPage.actionSpace() returns DeviceAction[] which is compatible at runtime\n // Use screenshotBase64 field to avoid async ScreenshotItem.create()\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n openNewTabWithUrl?: string,\n ): Promise<AgentOverChromeBridge> {\n // Re-init if URL provided\n if (this.agent && openNewTabWithUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect to current tab when no URL provided (handles CLI stateless calls)\n this.agent = await this.initBridgeModeAgent(openNewTabWithUrl);\n\n return this.agent;\n }\n\n private async initBridgeModeAgent(\n url?: string,\n ): Promise<AgentOverChromeBridge> {\n const reportOptions = this.readCliReportAgentOptions();\n const agent = new AgentOverChromeBridge({\n closeConflictServer: true,\n ...(reportOptions ?? {}),\n });\n\n if (!url) {\n await agent.connectCurrentTab();\n } else {\n await agent.connectNewTabWithUrl(url);\n }\n\n return agent;\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to web page. If URL provided, opens new tab; otherwise connects to current tab.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to connect current tab)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Bypass ensureAgent's URL check — directly init bridge agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-tab',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.initBridgeModeAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current tab';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page and release browser resources',\n schema: {},\n handler: this.createDisconnectHandler('web page'),\n },\n ];\n }\n}\n"],"names":["WebMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","openNewTabWithUrl","error","console","undefined","url","reportOptions","agent","AgentOverChromeBridge","z","args","reportSession","screenshot","label"],"mappings":";;;;AASO,MAAMA,yBAAyBC;IAC1B,0BAA0B;QAClC,OAAO;IACT;IAEU,wBAAwB;QAIhC,OAAO,IAAIC,WAAW;YACpB,YAAYC,eAAe,MAAM,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YACdC,iBAA0B,EACM;QAEhC,IAAI,IAAI,CAAC,KAAK,IAAIA,mBAAmB;YACnC,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAOC,OAAO;gBACdC,QAAQ,KAAK,CAAC,2CAA2CD;YAC3D;YACA,IAAI,CAAC,KAAK,GAAGE;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAGjC,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACH;QAE5C,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAc,oBACZI,GAAY,EACoB;QAChC,MAAMC,gBAAgB,IAAI,CAAC,yBAAyB;QACpD,MAAMC,QAAQ,IAAIC,sBAAsB;YACtC,qBAAqB;YACrB,GAAIF,iBAAiB,CAAC,CAAC;QACzB;QAEA,IAAKD,KAGH,MAAME,MAAM,oBAAoB,CAACF;aAFjC,MAAME,MAAM,iBAAiB;QAK/B,OAAOA;IACT;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKE,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAOC;oBACd,MAAM,EAAEL,GAAG,EAAE,GAAGK;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGN;oBACf;oBACA,MAAMO,gBAAgB,IAAI,CAAC,yBAAyB,CAClDN,OAAO;oBAET,IAAI,CAAC,sBAAsB,CAACM;oBAC5B,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACN;oBAE5C,MAAMO,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQR,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,cAAc,EAAEQ,OAAO;4BAAC;+BAC3CD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS,IAAI,CAAC,uBAAuB,CAAC;YACxC;SACD;IACH;AACF"}
@@ -83,8 +83,8 @@ async function launchPuppeteerPage(target, preference, browser, existingPage) {
83
83
  let dpr = defaultViewportScale;
84
84
  if (void 0 !== target.deviceScaleFactor && null !== target.deviceScaleFactor) {
85
85
  assert('number' == typeof target.deviceScaleFactor, 'deviceScaleFactor must be a number');
86
- dpr = Number.parseInt(target.deviceScaleFactor, 10);
87
- assert(dpr >= 0, `deviceScaleFactor must be >= 0, but got ${dpr}`);
86
+ dpr = target.deviceScaleFactor;
87
+ assert(dpr > 0, `deviceScaleFactor must be > 0, but got ${dpr}`);
88
88
  }
89
89
  const viewportConfig = {
90
90
  width,