@midscene/web 1.10.4-beta-20260710115744.0 → 1.10.4-beta-20260710135045.0

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 (75) hide show
  1. package/dist/es/agent-tools-puppeteer.mjs +159 -49
  2. package/dist/es/agent-tools-puppeteer.mjs.map +1 -1
  3. package/dist/es/bridge-mode/io-client.mjs +1 -1
  4. package/dist/es/bridge-mode/io-server.mjs +2 -2
  5. package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
  6. package/dist/es/cli.mjs +1 -1
  7. package/dist/es/index.mjs +3 -3
  8. package/dist/es/playwright/ai-fixture.mjs +4 -18
  9. package/dist/es/playwright/ai-fixture.mjs.map +1 -1
  10. package/dist/es/playwright/index.mjs +38 -4
  11. package/dist/es/playwright/index.mjs.map +1 -0
  12. package/dist/es/puppeteer/agent-launcher.mjs +4 -20
  13. package/dist/es/puppeteer/agent-launcher.mjs.map +1 -1
  14. package/dist/es/puppeteer/index.mjs +37 -3
  15. package/dist/es/puppeteer/index.mjs.map +1 -0
  16. package/dist/lib/agent-tools-puppeteer.js +163 -50
  17. package/dist/lib/agent-tools-puppeteer.js.map +1 -1
  18. package/dist/lib/bridge-mode/io-client.js +1 -1
  19. package/dist/lib/bridge-mode/io-server.js +2 -2
  20. package/dist/lib/bridge-mode/page-browser-side.js +1 -1
  21. package/dist/lib/cli.js +1 -1
  22. package/dist/lib/index.js +0 -12
  23. package/dist/lib/playwright/ai-fixture.js +3 -17
  24. package/dist/lib/playwright/ai-fixture.js.map +1 -1
  25. package/dist/lib/playwright/index.js +46 -10
  26. package/dist/lib/playwright/index.js.map +1 -1
  27. package/dist/lib/puppeteer/agent-launcher.js +3 -19
  28. package/dist/lib/puppeteer/agent-launcher.js.map +1 -1
  29. package/dist/lib/puppeteer/index.js +46 -10
  30. package/dist/lib/puppeteer/index.js.map +1 -1
  31. package/dist/types/agent-tools-puppeteer.d.ts +3 -0
  32. package/dist/types/index.d.ts +2 -2
  33. package/dist/types/playwright/ai-fixture.d.ts +0 -1
  34. package/dist/types/playwright/index.d.ts +8 -2
  35. package/dist/types/puppeteer/agent-launcher.d.ts +2 -2
  36. package/dist/types/puppeteer/index.d.ts +8 -2
  37. package/package.json +4 -4
  38. package/dist/es/common/browser-agent-utils.mjs +0 -41
  39. package/dist/es/common/browser-agent-utils.mjs.map +0 -1
  40. package/dist/es/common/browser-agent.mjs +0 -140
  41. package/dist/es/common/browser-agent.mjs.map +0 -1
  42. package/dist/es/playwright/agent.mjs +0 -2
  43. package/dist/es/playwright/browser-agent.mjs +0 -84
  44. package/dist/es/playwright/browser-agent.mjs.map +0 -1
  45. package/dist/es/playwright/page-agent.mjs +0 -24
  46. package/dist/es/playwright/page-agent.mjs.map +0 -1
  47. package/dist/es/puppeteer/agent.mjs +0 -2
  48. package/dist/es/puppeteer/browser-agent.mjs +0 -85
  49. package/dist/es/puppeteer/browser-agent.mjs.map +0 -1
  50. package/dist/es/puppeteer/page-agent.mjs +0 -24
  51. package/dist/es/puppeteer/page-agent.mjs.map +0 -1
  52. package/dist/lib/common/browser-agent-utils.js +0 -88
  53. package/dist/lib/common/browser-agent-utils.js.map +0 -1
  54. package/dist/lib/common/browser-agent.js +0 -180
  55. package/dist/lib/common/browser-agent.js.map +0 -1
  56. package/dist/lib/playwright/agent.js +0 -41
  57. package/dist/lib/playwright/agent.js.map +0 -1
  58. package/dist/lib/playwright/browser-agent.js +0 -118
  59. package/dist/lib/playwright/browser-agent.js.map +0 -1
  60. package/dist/lib/playwright/page-agent.js +0 -61
  61. package/dist/lib/playwright/page-agent.js.map +0 -1
  62. package/dist/lib/puppeteer/agent.js +0 -41
  63. package/dist/lib/puppeteer/agent.js.map +0 -1
  64. package/dist/lib/puppeteer/browser-agent.js +0 -119
  65. package/dist/lib/puppeteer/browser-agent.js.map +0 -1
  66. package/dist/lib/puppeteer/page-agent.js +0 -61
  67. package/dist/lib/puppeteer/page-agent.js.map +0 -1
  68. package/dist/types/common/browser-agent-utils.d.ts +0 -6
  69. package/dist/types/common/browser-agent.d.ts +0 -61
  70. package/dist/types/playwright/agent.d.ts +0 -1
  71. package/dist/types/playwright/browser-agent.d.ts +0 -24
  72. package/dist/types/playwright/page-agent.d.ts +0 -8
  73. package/dist/types/puppeteer/agent.d.ts +0 -1
  74. package/dist/types/puppeteer/browser-agent.d.ts +0 -24
  75. package/dist/types/puppeteer/page-agent.d.ts +0 -8
@@ -1,12 +1,13 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
3
+ import { mkdir, open as promises_open, readFile, unlink, writeFile } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { ScreenshotItem } from "@midscene/core";
7
7
  import { extractAgentBehaviorInitArgs, getAgentInitArgsSignature, shouldRebuildAgentForInitArgs } from "@midscene/shared/agent-tools/agent-behavior-init-args";
8
8
  import { BaseMidsceneTools } from "@midscene/shared/agent-tools/base-tools";
9
9
  import { resolveChromePath } from "@midscene/shared/agent-tools/chrome-path";
10
+ import { getDebug } from "@midscene/shared/logger";
10
11
  import puppeteer_core from "puppeteer-core";
11
12
  import { adaptWebAgentInitArgs, webAgentInitArgShape } from "./agent-init-args.mjs";
12
13
  import { defaultPuppeteerWindowViewportSize, defaultStaticPageViewportSize } from "./common/viewport.mjs";
@@ -24,7 +25,10 @@ function _define_property(obj, key, value) {
24
25
  }
25
26
  const ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');
26
27
  const USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');
28
+ const TARGET_ID_FILE = join(tmpdir(), 'midscene-puppeteer-target-id');
27
29
  const DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30000;
30
+ const DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS = 25;
31
+ const debug = getDebug('agent-tools:puppeteer');
28
32
  const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;
29
33
  function buildDetachedChromeArgs(options) {
30
34
  const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;
@@ -54,6 +58,67 @@ function terminateDetachedChrome(proc) {
54
58
  proc.kill('SIGKILL');
55
59
  } catch {}
56
60
  }
61
+ function getTargetId(page) {
62
+ return page.target()._targetId;
63
+ }
64
+ function waitForDetachedChromeEndpoint(proc, stderrFile, timeoutMs = DETACHED_CHROME_LAUNCH_TIMEOUT_MS) {
65
+ return new Promise((resolve, reject)=>{
66
+ let output = '';
67
+ let settled = false;
68
+ let exited = false;
69
+ let pollTimer;
70
+ const cleanup = ()=>{
71
+ clearTimeout(timeout);
72
+ if (pollTimer) clearTimeout(pollTimer);
73
+ proc.removeListener('exit', onExit);
74
+ proc.removeListener('error', onError);
75
+ };
76
+ const resolveOnce = (value)=>{
77
+ if (settled) return;
78
+ settled = true;
79
+ cleanup();
80
+ resolve(value);
81
+ };
82
+ const rejectOnce = (error, terminate = false)=>{
83
+ if (settled) return;
84
+ settled = true;
85
+ if (terminate) terminateDetachedChrome(proc);
86
+ cleanup();
87
+ reject(error);
88
+ };
89
+ const onExit = (code, signal)=>{
90
+ exited = true;
91
+ readFile(stderrFile, 'utf-8').catch(()=>output).then((latestOutput)=>{
92
+ output = latestOutput;
93
+ rejectOnce(new Error(`Chrome exited with code ${code ?? signal} before DevTools was ready.\nChrome stderr: ${output}\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`));
94
+ });
95
+ };
96
+ const onError = (error)=>{
97
+ rejectOnce(new Error(`Failed to launch Chrome. Stderr log: "${stderrFile}".`, {
98
+ cause: error
99
+ }), true);
100
+ };
101
+ const pollForEndpoint = async ()=>{
102
+ if (settled || exited) return;
103
+ try {
104
+ output = await readFile(stderrFile, 'utf-8');
105
+ } catch (error) {
106
+ rejectOnce(new Error(`Failed to read Chrome stderr log "${stderrFile}".`, {
107
+ cause: error
108
+ }), true);
109
+ return;
110
+ }
111
+ if (settled || exited) return;
112
+ const match = output.match(/DevTools listening on (ws:\/\/[^\s]+)/);
113
+ if (match) return void resolveOnce(match[1]);
114
+ pollTimer = setTimeout(()=>void pollForEndpoint(), DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS);
115
+ };
116
+ const timeout = setTimeout(()=>rejectOnce(new Error(`Chrome launch timeout.\nChrome stderr: ${output}\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`), true), timeoutMs);
117
+ proc.on('exit', onExit);
118
+ proc.on('error', onError);
119
+ pollForEndpoint();
120
+ });
121
+ }
57
122
  class PuppeteerBrowserManager {
58
123
  get endpointFile() {
59
124
  return this.persistence.endpointFile || ENDPOINT_FILE;
@@ -61,6 +126,53 @@ class PuppeteerBrowserManager {
61
126
  get userDataDir() {
62
127
  return this.persistence.userDataDir || USER_DATA_DIR;
63
128
  }
129
+ get targetIdFile() {
130
+ return this.persistence.targetIdFile || TARGET_ID_FILE;
131
+ }
132
+ get chromeStderrFile() {
133
+ return join(this.userDataDir, 'chrome-stderr.log');
134
+ }
135
+ async readSavedTargetId() {
136
+ let content;
137
+ try {
138
+ content = await readFile(this.targetIdFile, 'utf-8');
139
+ } catch (error) {
140
+ if ('ENOENT' === error.code) return null;
141
+ throw new Error(`Failed to read Puppeteer targetId from "${this.targetIdFile}".`, {
142
+ cause: error
143
+ });
144
+ }
145
+ const targetId = content.trim();
146
+ if (!targetId) throw new Error(`Puppeteer targetId file "${this.targetIdFile}" is empty.`);
147
+ return targetId;
148
+ }
149
+ async saveTargetId(targetId) {
150
+ try {
151
+ await writeFile(this.targetIdFile, targetId, 'utf-8');
152
+ debug('Saved Puppeteer targetId: %s', targetId);
153
+ } catch (error) {
154
+ throw new Error(`Failed to save Puppeteer targetId to "${this.targetIdFile}".`, {
155
+ cause: error
156
+ });
157
+ }
158
+ }
159
+ async cleanupTargetIdFile() {
160
+ try {
161
+ await unlink(this.targetIdFile);
162
+ } catch (error) {
163
+ if ('ENOENT' === error.code) return;
164
+ throw new Error(`Failed to remove Puppeteer targetId file "${this.targetIdFile}".`, {
165
+ cause: error
166
+ });
167
+ }
168
+ }
169
+ async cleanupChromeStderrFile() {
170
+ try {
171
+ await unlink(this.chromeStderrFile);
172
+ } catch (error) {
173
+ if ('ENOENT' !== error.code) debug('Failed to clean Chrome stderr log: %s', error);
174
+ }
175
+ }
64
176
  async getOrLaunch(viewport) {
65
177
  const endpointFile = this.endpointFile;
66
178
  if (existsSync(endpointFile)) try {
@@ -73,11 +185,14 @@ class PuppeteerBrowserManager {
73
185
  browser,
74
186
  reused: true
75
187
  };
76
- } catch {
188
+ } catch (error) {
189
+ debug('Failed to reuse persisted Puppeteer endpoint: %s', error);
77
190
  try {
78
191
  await unlink(endpointFile);
79
192
  } catch {}
193
+ await this.cleanupTargetIdFile();
80
194
  }
195
+ await this.cleanupTargetIdFile();
81
196
  const wsEndpoint = await this.launchDetachedChrome(viewport);
82
197
  await writeFile(endpointFile, wsEndpoint);
83
198
  const browser = await puppeteer_core.connect({
@@ -91,17 +206,20 @@ class PuppeteerBrowserManager {
91
206
  }
92
207
  async closeBrowser() {
93
208
  const endpointFile = this.endpointFile;
94
- if (!existsSync(endpointFile)) return;
95
- try {
209
+ if (existsSync(endpointFile)) try {
96
210
  const endpoint = (await readFile(endpointFile, 'utf-8')).trim();
97
211
  const browser = await puppeteer_core.connect({
98
212
  browserWSEndpoint: endpoint
99
213
  });
100
214
  await browser.close();
101
- } catch {}
215
+ } catch (error) {
216
+ debug('Failed to close persisted Puppeteer browser: %s', error);
217
+ }
102
218
  try {
103
- await unlink(endpointFile);
219
+ if (existsSync(endpointFile)) await unlink(endpointFile);
104
220
  } catch {}
221
+ await this.cleanupTargetIdFile();
222
+ await this.cleanupChromeStderrFile();
105
223
  }
106
224
  disconnect() {
107
225
  if (this.activeBrowser) {
@@ -119,48 +237,34 @@ class PuppeteerBrowserManager {
119
237
  userDataDir,
120
238
  viewport
121
239
  });
122
- const proc = spawn(chromePath, args, {
123
- detached: true,
124
- stdio: [
125
- 'ignore',
126
- 'ignore',
127
- 'pipe'
128
- ]
129
- });
240
+ const stderrFile = this.chromeStderrFile;
241
+ const stderrHandle = await promises_open(stderrFile, 'w');
242
+ let proc;
243
+ try {
244
+ proc = spawn(chromePath, args, {
245
+ detached: true,
246
+ stdio: [
247
+ 'ignore',
248
+ 'ignore',
249
+ stderrHandle.fd
250
+ ]
251
+ });
252
+ } catch (error) {
253
+ await stderrHandle.close();
254
+ throw error;
255
+ }
256
+ const endpointPromise = waitForDetachedChromeEndpoint(proc, stderrFile);
130
257
  proc.unref();
131
- return new Promise((resolve, reject)=>{
132
- let output = '';
133
- let settled = false;
134
- const cleanup = ()=>{
135
- clearTimeout(timeout);
136
- proc.stderr.removeListener('data', onData);
137
- proc.removeListener('exit', onExit);
138
- };
139
- const resolveOnce = (value)=>{
140
- if (settled) return;
141
- settled = true;
142
- cleanup();
143
- resolve(value);
144
- };
145
- const rejectOnce = (error, terminate = false)=>{
146
- if (settled) return;
147
- settled = true;
148
- if (terminate) terminateDetachedChrome(proc);
149
- cleanup();
150
- reject(error);
151
- };
152
- const onData = (data)=>{
153
- output += data.toString();
154
- const match = output.match(/DevTools listening on (ws:\/\/[^\s]+)/);
155
- if (match) resolveOnce(match[1]);
156
- };
157
- proc.stderr.on('data', onData);
158
- const onExit = (code, signal)=>{
159
- rejectOnce(new Error(`Chrome exited with code ${code ?? signal} before DevTools was ready.\nChrome stderr: ${output}\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`));
160
- };
161
- proc.on('exit', onExit);
162
- const timeout = setTimeout(()=>rejectOnce(new Error(`Chrome launch timeout.\nChrome stderr: ${output}\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`), true), DETACHED_CHROME_LAUNCH_TIMEOUT_MS);
163
- });
258
+ try {
259
+ await stderrHandle.close();
260
+ } catch (error) {
261
+ terminateDetachedChrome(proc);
262
+ await endpointPromise.catch(()=>void 0);
263
+ throw new Error(`Failed to close Chrome stderr log "${stderrFile}".`, {
264
+ cause: error
265
+ });
266
+ }
267
+ return endpointPromise;
164
268
  }
165
269
  constructor(persistence = {}){
166
270
  _define_property(this, "persistence", void 0);
@@ -204,11 +308,16 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
204
308
  waitUntil: 'domcontentloaded'
205
309
  });
206
310
  } else {
311
+ const savedTargetId = await this.browserManager.readSavedTargetId();
312
+ const matchedPage = savedTargetId ? pages.find((candidate)=>getTargetId(candidate) === savedTargetId) : void 0;
207
313
  const webPages = pages.filter((p)=>/^https?:\/\//.test(p.url()));
208
- page = webPages.length > 0 ? webPages[webPages.length - 1] : pages[pages.length - 1] || await browser.newPage();
314
+ page = matchedPage ?? (webPages.length > 0 ? webPages[webPages.length - 1] : pages[pages.length - 1] || await browser.newPage());
209
315
  if (reused) await page.bringToFront();
210
316
  if (this.viewport) await page.setViewport(this.viewport);
211
317
  }
318
+ const targetId = getTargetId(page);
319
+ if (!targetId) throw new Error('Failed to persist Puppeteer page session because Puppeteer did not expose a Chrome targetId.');
320
+ await this.browserManager.saveTargetId(targetId);
212
321
  const reportOptions = this.readCliReportAgentOptions();
213
322
  this.agent = new PuppeteerAgent(page, {
214
323
  ...extractAgentBehaviorInitArgs(initArgs) ?? {},
@@ -267,6 +376,7 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
267
376
  this.lastInitArgsSignature = void 0;
268
377
  }
269
378
  this.browserManager.disconnect();
379
+ await this.browserManager.cleanupTargetIdFile();
270
380
  return this.buildTextResult('Disconnected from web page (browser still running)');
271
381
  }
272
382
  },
@@ -303,6 +413,6 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
303
413
  this.browserManager = options.persistence ? new PuppeteerBrowserManager(options.persistence) : defaultBrowserManager;
304
414
  }
305
415
  }
306
- export { PUPPETEER_ENDPOINT_FILE, WebPuppeteerMidsceneTools, buildDetachedChromeArgs };
416
+ export { PUPPETEER_ENDPOINT_FILE, WebPuppeteerMidsceneTools, buildDetachedChromeArgs, waitForDetachedChromeEndpoint };
307
417
 
308
418
  //# sourceMappingURL=agent-tools-puppeteer.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-tools-puppeteer.mjs","sources":["../../src/agent-tools-puppeteer.ts"],"sourcesContent":["import { type ChildProcess, 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 } from '@midscene/core';\nimport {\n extractAgentBehaviorInitArgs,\n getAgentInitArgsSignature,\n shouldRebuildAgentForInitArgs,\n} from '@midscene/shared/agent-tools/agent-behavior-init-args';\nimport {\n BaseMidsceneTools,\n type InitArgSpec,\n} from '@midscene/shared/agent-tools/base-tools';\nimport { resolveChromePath } from '@midscene/shared/agent-tools/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/agent-tools/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type WebAgentInitArgs,\n adaptWebAgentInitArgs,\n webAgentInitArgShape,\n} from './agent-init-args';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\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');\nconst DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30_000;\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\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=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\nfunction terminateDetachedChrome(proc: ChildProcess): void {\n if (proc.killed || proc.exitCode !== null || proc.signalCode !== null) {\n return;\n }\n\n if (process.platform !== 'win32' && proc.pid) {\n try {\n process.kill(-proc.pid, 'SIGKILL');\n return;\n } catch {}\n }\n\n try {\n proc.kill('SIGKILL');\n } catch {}\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, '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(endpointFile);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, 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 const endpointFile = this.endpointFile;\n if (!existsSync(endpointFile)) return;\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(endpointFile);\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(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\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 let settled = false;\n const cleanup = () => {\n clearTimeout(timeout);\n proc.stderr!.removeListener('data', onData);\n proc.removeListener('exit', onExit);\n };\n const resolveOnce = (value: string) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n const rejectOnce = (error: Error, terminate = false) => {\n if (settled) return;\n settled = true;\n if (terminate) {\n terminateDetachedChrome(proc);\n }\n cleanup();\n reject(error);\n };\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n resolveOnce(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n rejectOnce(\n new Error(\n `Chrome exited with code ${code ?? signal} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n );\n };\n proc.on('exit', onExit);\n\n const timeout = setTimeout(\n () =>\n rejectOnce(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n true,\n ),\n DETACHED_CHROME_LAUNCH_TIMEOUT_MS,\n );\n });\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer mode.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<\n PuppeteerAgent,\n WebAgentInitArgs\n> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n private lastInitArgsSignature?: string;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected readonly initArgSpec: InitArgSpec<WebAgentInitArgs> = {\n namespace: 'web',\n shape: webAgentInitArgShape,\n cli: {\n preferBareKeys: true,\n },\n adapt: adaptWebAgentInitArgs,\n };\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n initArgs?: WebAgentInitArgs,\n ): Promise<PuppeteerAgent> {\n const navigateToUrl = initArgs?.url;\n const nextSignature = getAgentInitArgsSignature(initArgs);\n const shouldOpenUrl = typeof navigateToUrl === 'string';\n\n if (\n this.agent &&\n (shouldOpenUrl ||\n shouldRebuildAgentForInitArgs(\n this.lastInitArgsSignature,\n nextSignature,\n ))\n ) {\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 this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\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 if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n }\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(extractAgentBehaviorInitArgs(initArgs) ?? {}),\n ...(reportOptions ?? {}),\n });\n this.lastInitArgsSignature = nextSignature;\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.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: this.getAgentInitArgSchema(),\n cli: this.getAgentInitArgCliMetadata(),\n handler: async (args) => {\n const initArgs = this.extractAgentInitParam(args);\n const url = initArgs?.url;\n\n // Explicit connect always starts a fresh page session.\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n this.lastInitArgsSignature = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(initArgs);\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 this.lastInitArgsSignature = undefined;\n }\n this.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 this.lastInitArgsSignature = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","DETACHED_CHROME_LAUNCH_TIMEOUT_MS","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","terminateDetachedChrome","proc","process","PuppeteerBrowserManager","endpointFile","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","userDataDir","mkdir","args","spawn","Promise","resolve","reject","output","settled","cleanup","clearTimeout","timeout","onData","onExit","resolveOnce","value","rejectOnce","error","terminate","data","match","code","signal","Error","setTimeout","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","initArgs","navigateToUrl","nextSignature","getAgentInitArgsSignature","shouldOpenUrl","shouldRebuildAgentForInitArgs","undefined","reused","pages","page","webPages","p","reportOptions","PuppeteerAgent","extractAgentBehaviorInitArgs","url","reportSession","screenshot","label","webAgentInitArgShape","adaptWebAgentInitArgs"],"mappings":"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"}
1
+ {"version":3,"file":"agent-tools-puppeteer.mjs","sources":["../../src/agent-tools-puppeteer.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, open, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem } from '@midscene/core';\nimport {\n extractAgentBehaviorInitArgs,\n getAgentInitArgsSignature,\n shouldRebuildAgentForInitArgs,\n} from '@midscene/shared/agent-tools/agent-behavior-init-args';\nimport {\n BaseMidsceneTools,\n type InitArgSpec,\n} from '@midscene/shared/agent-tools/base-tools';\nimport { resolveChromePath } from '@midscene/shared/agent-tools/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/agent-tools/types';\nimport { getDebug } from '@midscene/shared/logger';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type WebAgentInitArgs,\n adaptWebAgentInitArgs,\n webAgentInitArgShape,\n} from './agent-init-args';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\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');\nconst TARGET_ID_FILE = join(tmpdir(), 'midscene-puppeteer-target-id');\nconst DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30_000;\nconst DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS = 25;\nconst debug = getDebug('agent-tools:puppeteer');\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n targetIdFile?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\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=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\nfunction terminateDetachedChrome(proc: ChildProcess): void {\n if (proc.killed || proc.exitCode !== null || proc.signalCode !== null) {\n return;\n }\n\n if (process.platform !== 'win32' && proc.pid) {\n try {\n process.kill(-proc.pid, 'SIGKILL');\n return;\n } catch {}\n }\n\n try {\n proc.kill('SIGKILL');\n } catch {}\n}\n\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\nexport function waitForDetachedChromeEndpoint(\n proc: ChildProcess,\n stderrFile: string,\n timeoutMs = DETACHED_CHROME_LAUNCH_TIMEOUT_MS,\n): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n let output = '';\n let settled = false;\n let exited = false;\n let pollTimer: NodeJS.Timeout | undefined;\n const cleanup = () => {\n clearTimeout(timeout);\n if (pollTimer) {\n clearTimeout(pollTimer);\n }\n proc.removeListener('exit', onExit);\n proc.removeListener('error', onError);\n };\n const resolveOnce = (value: string) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n const rejectOnce = (error: Error, terminate = false) => {\n if (settled) return;\n settled = true;\n if (terminate) {\n terminateDetachedChrome(proc);\n }\n cleanup();\n reject(error);\n };\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n exited = true;\n void readFile(stderrFile, 'utf-8')\n .catch(() => output)\n .then((latestOutput) => {\n output = latestOutput;\n rejectOnce(\n new Error(\n `Chrome exited with code ${code ?? signal} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n );\n });\n };\n const onError = (error: Error) => {\n rejectOnce(\n new Error(`Failed to launch Chrome. Stderr log: \"${stderrFile}\".`, {\n cause: error,\n }),\n true,\n );\n };\n const pollForEndpoint = async (): Promise<void> => {\n if (settled || exited) return;\n try {\n output = await readFile(stderrFile, 'utf-8');\n } catch (error) {\n rejectOnce(\n new Error(`Failed to read Chrome stderr log \"${stderrFile}\".`, {\n cause: error,\n }),\n true,\n );\n return;\n }\n\n if (settled || exited) return;\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n resolveOnce(match[1]);\n return;\n }\n pollTimer = setTimeout(\n () => void pollForEndpoint(),\n DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS,\n );\n };\n const timeout = setTimeout(\n () =>\n rejectOnce(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n true,\n ),\n timeoutMs,\n );\n\n proc.on('exit', onExit);\n proc.on('error', onError);\n void pollForEndpoint();\n });\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n private get targetIdFile() {\n return this.persistence.targetIdFile || TARGET_ID_FILE;\n }\n\n private get chromeStderrFile() {\n return join(this.userDataDir, 'chrome-stderr.log');\n }\n\n async readSavedTargetId(): Promise<string | null> {\n let content: string;\n try {\n content = await readFile(this.targetIdFile, 'utf-8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw new Error(\n `Failed to read Puppeteer targetId from \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n\n const targetId = content.trim();\n if (!targetId) {\n throw new Error(\n `Puppeteer targetId file \"${this.targetIdFile}\" is empty.`,\n );\n }\n return targetId;\n }\n\n async saveTargetId(targetId: string): Promise<void> {\n try {\n await writeFile(this.targetIdFile, targetId, 'utf-8');\n debug('Saved Puppeteer targetId: %s', targetId);\n } catch (error) {\n throw new Error(\n `Failed to save Puppeteer targetId to \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n }\n\n async cleanupTargetIdFile(): Promise<void> {\n try {\n await unlink(this.targetIdFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw new Error(\n `Failed to remove Puppeteer targetId file \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n }\n\n async cleanupChromeStderrFile(): Promise<void> {\n try {\n await unlink(this.chromeStderrFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n debug('Failed to clean Chrome stderr log: %s', error);\n }\n }\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch (error) {\n debug('Failed to reuse persisted Puppeteer endpoint: %s', error);\n try {\n await unlink(endpointFile);\n } catch {}\n await this.cleanupTargetIdFile();\n }\n }\n\n await this.cleanupTargetIdFile();\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, 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 const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch (error) {\n debug('Failed to close persisted Puppeteer browser: %s', error);\n }\n }\n try {\n if (existsSync(endpointFile)) {\n await unlink(endpointFile);\n }\n } catch {}\n await this.cleanupTargetIdFile();\n await this.cleanupChromeStderrFile();\n }\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n async launchDetachedChrome(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\n });\n\n const stderrFile = this.chromeStderrFile;\n const stderrHandle = await open(stderrFile, 'w');\n let proc: ChildProcess;\n try {\n proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', stderrHandle.fd],\n });\n } catch (error) {\n await stderrHandle.close();\n throw error;\n }\n const endpointPromise = waitForDetachedChromeEndpoint(proc, stderrFile);\n proc.unref();\n try {\n await stderrHandle.close();\n } catch (error) {\n terminateDetachedChrome(proc);\n await endpointPromise.catch(() => undefined);\n throw new Error(`Failed to close Chrome stderr log \"${stderrFile}\".`, {\n cause: error,\n });\n }\n\n return endpointPromise;\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer mode.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<\n PuppeteerAgent,\n WebAgentInitArgs\n> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n private lastInitArgsSignature?: string;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected readonly initArgSpec: InitArgSpec<WebAgentInitArgs> = {\n namespace: 'web',\n shape: webAgentInitArgShape,\n cli: {\n preferBareKeys: true,\n },\n adapt: adaptWebAgentInitArgs,\n };\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n initArgs?: WebAgentInitArgs,\n ): Promise<PuppeteerAgent> {\n const navigateToUrl = initArgs?.url;\n const nextSignature = getAgentInitArgsSignature(initArgs);\n const shouldOpenUrl = typeof navigateToUrl === 'string';\n\n if (\n this.agent &&\n (shouldOpenUrl ||\n shouldRebuildAgentForInitArgs(\n this.lastInitArgsSignature,\n nextSignature,\n ))\n ) {\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 this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n const savedTargetId = await this.browserManager.readSavedTargetId();\n const matchedPage = savedTargetId\n ? pages.find((candidate) => getTargetId(candidate) === savedTargetId)\n : undefined;\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n matchedPage ??\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 if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n }\n\n const targetId = getTargetId(page);\n if (!targetId) {\n throw new Error(\n 'Failed to persist Puppeteer page session because Puppeteer did not expose a Chrome targetId.',\n );\n }\n await this.browserManager.saveTargetId(targetId);\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(extractAgentBehaviorInitArgs(initArgs) ?? {}),\n ...(reportOptions ?? {}),\n });\n this.lastInitArgsSignature = nextSignature;\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.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: this.getAgentInitArgSchema(),\n cli: this.getAgentInitArgCliMetadata(),\n handler: async (args) => {\n const initArgs = this.extractAgentInitParam(args);\n const url = initArgs?.url;\n\n // Explicit connect always starts a fresh page session.\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n this.lastInitArgsSignature = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(initArgs);\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 this.lastInitArgsSignature = undefined;\n }\n this.browserManager.disconnect();\n await this.browserManager.cleanupTargetIdFile();\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 this.lastInitArgsSignature = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","TARGET_ID_FILE","DETACHED_CHROME_LAUNCH_TIMEOUT_MS","DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS","debug","getDebug","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","terminateDetachedChrome","proc","process","getTargetId","page","waitForDetachedChromeEndpoint","stderrFile","timeoutMs","Promise","resolve","reject","output","settled","exited","pollTimer","cleanup","clearTimeout","timeout","onExit","onError","resolveOnce","value","rejectOnce","error","terminate","code","signal","readFile","latestOutput","Error","pollForEndpoint","match","setTimeout","PuppeteerBrowserManager","content","targetId","writeFile","unlink","endpointFile","existsSync","endpoint","browser","puppeteer","wsEndpoint","chromePath","resolveChromePath","userDataDir","mkdir","args","stderrHandle","open","spawn","endpointPromise","undefined","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","initArgs","navigateToUrl","nextSignature","getAgentInitArgsSignature","shouldOpenUrl","shouldRebuildAgentForInitArgs","reused","pages","savedTargetId","matchedPage","candidate","webPages","p","reportOptions","PuppeteerAgent","extractAgentBehaviorInitArgs","url","reportSession","screenshot","label","webAgentInitArgShape","adaptWebAgentInitArgs"],"mappings":"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"}
@@ -23,7 +23,7 @@ class BridgeClient {
23
23
  ]
24
24
  } : {},
25
25
  query: {
26
- version: "1.10.4-beta-20260710115744.0"
26
+ version: "1.10.4-beta-20260710135045.0"
27
27
  }
28
28
  });
29
29
  const timeout = setTimeout(()=>{
@@ -102,7 +102,7 @@ class BridgeServer {
102
102
  logMsg('one client connected');
103
103
  this.socket = socket;
104
104
  const clientVersion = socket.handshake.query.version;
105
- logMsg(`Bridge connected, cli-side version v1.10.4-beta-20260710115744.0, browser-side version v${clientVersion}`);
105
+ logMsg(`Bridge connected, cli-side version v1.10.4-beta-20260710135045.0, browser-side version v${clientVersion}`);
106
106
  socket.on(BridgeEvent.CallResponse, (params)=>{
107
107
  const id = params.id;
108
108
  const response = params.response;
@@ -126,7 +126,7 @@ class BridgeServer {
126
126
  setTimeout(()=>{
127
127
  this.onConnect?.();
128
128
  const payload = {
129
- version: "1.10.4-beta-20260710115744.0"
129
+ version: "1.10.4-beta-20260710135045.0"
130
130
  };
131
131
  socket.emit(BridgeEvent.Connected, payload);
132
132
  Promise.resolve().then(()=>{
@@ -100,7 +100,7 @@ class ExtensionBridgePageBrowserSide extends page {
100
100
  throw new Error('Connection denied by user');
101
101
  }
102
102
  }
103
- this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.4-beta-20260710115744.0`, 'log');
103
+ this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.4-beta-20260710135045.0`, 'log');
104
104
  }
105
105
  async connect() {
106
106
  return await this.setupBridgeClient();
package/dist/es/cli.mjs CHANGED
@@ -18,7 +18,7 @@ Promise.resolve().then(()=>{
18
18
  return runToolsCLI(tools, 'midscene-web', {
19
19
  stripPrefix: 'web_',
20
20
  argv: parsedOptions.argv,
21
- version: "1.10.4-beta-20260710115744.0",
21
+ version: "1.10.4-beta-20260710135045.0",
22
22
  extraCommands: createReportCliCommands()
23
23
  });
24
24
  }).catch((e)=>{
package/dist/es/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { PlaywrightAgent, PlaywrightAiFixture, PlaywrightBrowserAgent, PlaywrightPageAgent } from "./playwright/index.mjs";
1
+ import { PlaywrightAgent, PlaywrightAiFixture } from "./playwright/index.mjs";
2
2
  import { Agent } from "@midscene/core/agent";
3
- import { PuppeteerAgent, PuppeteerBrowserAgent, PuppeteerPageAgent } from "./puppeteer/index.mjs";
3
+ import { PuppeteerAgent } from "./puppeteer/index.mjs";
4
4
  import { StaticPage, StaticPageAgent } from "./static/index.mjs";
5
5
  import { WebMidsceneTools } from "./agent-tools.mjs";
6
6
  import { webPlaygroundPlatform } from "./platform.mjs";
7
7
  import { WebCdpMidsceneTools } from "./agent-tools-cdp.mjs";
8
- export { Agent as PageAgent, PlaywrightAgent, PlaywrightAiFixture, PlaywrightBrowserAgent, PlaywrightPageAgent, PuppeteerAgent, PuppeteerBrowserAgent, PuppeteerPageAgent, StaticPage, StaticPageAgent, WebCdpMidsceneTools, WebMidsceneTools, webPlaygroundPlatform };
8
+ export { Agent as PageAgent, PlaywrightAgent, PlaywrightAiFixture, PuppeteerAgent, StaticPage, StaticPageAgent, WebCdpMidsceneTools, WebMidsceneTools, webPlaygroundPlatform };
@@ -1,5 +1,4 @@
1
- import { resolveBrowserAgentRuntimeOptions } from "../common/browser-agent.mjs";
2
- import { PlaywrightAgent, PlaywrightBrowserAgent } from "./index.mjs";
1
+ import { PlaywrightAgent } from "./index.mjs";
3
2
  import { processCacheConfig } from "@midscene/core/utils";
4
3
  import { DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT, DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from "@midscene/shared/constants";
5
4
  import { getDebug } from "@midscene/shared/logger";
@@ -32,7 +31,7 @@ const groupAndCaseForTest = (testInfo)=>{
32
31
  const midsceneAgentKeyId = '_midsceneAgentId';
33
32
  const midsceneDumpAnnotationId = 'MIDSCENE_DUMP_ANNOTATION';
34
33
  const PlaywrightAiFixture = (options)=>{
35
- const { forceSameTabNavigation = true, autoFollowNewPage = false, waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT, waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT, cache, ...sharedAgentOptions } = options ?? {};
34
+ const { forceSameTabNavigation = true, waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT, waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT, cache, ...sharedAgentOptions } = options ?? {};
36
35
  const processTestCacheConfig = (testInfo)=>{
37
36
  const { id } = groupAndCaseForTest(testInfo);
38
37
  return processCacheConfig(cache, id);
@@ -71,29 +70,16 @@ const PlaywrightAiFixture = (options)=>{
71
70
  const { file, title } = groupAndCaseForTest(testInfo);
72
71
  const cacheConfig = processTestCacheConfig(testInfo);
73
72
  const reportTag = `playwright-${title.replace(/[\\/]/g, '-')}-${idForPage}`;
74
- if (autoFollowNewPage && true === forceSameTabNavigation) throw new Error('[midscene] autoFollowNewPage cannot be used with forceSameTabNavigation: true.');
75
- const runtimeOptions = resolveBrowserAgentRuntimeOptions({
76
- agentName: 'PlaywrightAiFixture',
77
- pageScope: autoFollowNewPage ? 'browser' : 'page',
78
- forceSameTabNavigation: autoFollowNewPage ? void 0 : forceSameTabNavigation,
79
- autoFollowNewPage
80
- });
81
- const commonAgentOpts = {
73
+ const agent = new PlaywrightAgent(page, {
82
74
  testId: reportTag,
83
75
  reportFileName: reportTag,
76
+ forceSameTabNavigation,
84
77
  cache: cacheConfig,
85
78
  groupName: title,
86
79
  groupDescription: file,
87
80
  generateReport: true,
88
81
  ...sharedAgentOptions,
89
82
  ...opts
90
- };
91
- const agent = autoFollowNewPage ? new PlaywrightBrowserAgent(page.context(), page, {
92
- ...commonAgentOpts,
93
- autoFollowNewPage: runtimeOptions.autoFollowNewPage
94
- }) : new PlaywrightAgent(page, {
95
- ...commonAgentOpts,
96
- forceSameTabNavigation: runtimeOptions.forceSameTabNavigation
97
83
  });
98
84
  pageAgentMap[idForPage] = agent;
99
85
  const records = getAgentRecordsForTest(testInfo);