pi-browser-use 0.11.5 → 0.11.7

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.
@@ -6,6 +6,7 @@ export interface ChromeLaunchOptions {
6
6
  chromeArgs?: string[];
7
7
  executablePath?: string;
8
8
  readyTimeoutMs?: number;
9
+ launchAttempts?: number;
9
10
  signal?: AbortSignal;
10
11
  }
11
12
  export interface ChromeProcess {
@@ -28,6 +29,9 @@ export declare function buildChromeArgs(options: {
28
29
  headless?: boolean;
29
30
  chromeArgs?: string[];
30
31
  }): string[];
32
+ export declare class DevToolsReadyTimeoutError extends Error {
33
+ constructor(browserUrl: string, timeoutMs: number, lastError: unknown);
34
+ }
31
35
  export declare function waitForDevToolsEndpoint(port: number, options?: {
32
36
  timeoutMs?: number;
33
37
  fetchImpl?: typeof fetch;
@@ -1,5 +1,6 @@
1
1
  import { execFileSync, spawn } from 'node:child_process';
2
2
  import { existsSync } from 'node:fs';
3
+ import { once } from 'node:events';
3
4
  import { createServer } from 'node:net';
4
5
  import { setTimeout as delay } from 'node:timers/promises';
5
6
  export function chromeExecutableCandidates() {
@@ -94,6 +95,21 @@ export function buildChromeArgs(options) {
94
95
  args.push(...(options.chromeArgs ?? []));
95
96
  return args;
96
97
  }
98
+ export class DevToolsReadyTimeoutError extends Error {
99
+ constructor(browserUrl, timeoutMs, lastError) {
100
+ super(`Timed out waiting for Chrome DevTools on ${browserUrl} after ${timeoutMs}ms (${describeError(lastError)})`);
101
+ this.name = 'DevToolsReadyTimeoutError';
102
+ }
103
+ }
104
+ function describeError(error) {
105
+ if (error instanceof Error) {
106
+ const cause = error.cause;
107
+ if (cause instanceof Error && cause.message)
108
+ return cause.message;
109
+ return error.message;
110
+ }
111
+ return String(error);
112
+ }
97
113
  export async function waitForDevToolsEndpoint(port, options) {
98
114
  const timeoutMs = options?.timeoutMs ?? 15_000;
99
115
  const fetchImpl = options?.fetchImpl ?? fetch;
@@ -118,7 +134,7 @@ export async function waitForDevToolsEndpoint(port, options) {
118
134
  }
119
135
  await delay(100, undefined, { signal: options?.signal });
120
136
  }
121
- throw new Error(`Timed out waiting for Chrome DevTools on ${browserUrl} (${lastError instanceof Error ? lastError.message : String(lastError)})`);
137
+ throw new DevToolsReadyTimeoutError(browserUrl, timeoutMs, lastError);
122
138
  }
123
139
  class OwnedChromeProcess {
124
140
  port;
@@ -168,40 +184,66 @@ class OwnedChromeProcess {
168
184
  }
169
185
  }
170
186
  }
187
+ async function waitReadyWatchingExit(child, port, timeoutMs, signal) {
188
+ const endpointReady = waitForDevToolsEndpoint(port, { timeoutMs, signal });
189
+ const exited = once(child, 'exit');
190
+ exited.catch(() => { });
191
+ try {
192
+ await Promise.race([
193
+ endpointReady,
194
+ exited.then(([code, exitSignal]) => {
195
+ const reason = code !== null ? `code ${code}` : `signal ${exitSignal ?? 'unknown'}`;
196
+ throw new Error(`Chrome exited (${reason}) before the DevTools endpoint on http://127.0.0.1:${port} opened.`);
197
+ }),
198
+ ]);
199
+ }
200
+ catch (error) {
201
+ endpointReady.catch(() => { });
202
+ throw error;
203
+ }
204
+ }
171
205
  export async function launchChrome(options) {
172
206
  options.signal?.throwIfAborted();
173
207
  const executable = findChromeExecutable(options.executablePath);
174
- const port = options.port ?? (await allocateEphemeralPort());
175
- const args = buildChromeArgs({
176
- userDataDir: options.userDataDir,
177
- profileDirectory: options.profileDirectory,
178
- port,
179
- headless: options.headless,
180
- chromeArgs: options.chromeArgs,
181
- });
182
- const child = spawn(executable, args, { stdio: 'ignore', detached: false });
183
- await new Promise((resolve, reject) => {
184
- child.on('error', reject);
185
- setTimeout(resolve, 50);
186
- });
187
- if (child.exitCode !== null) {
188
- throw new Error(`Chrome exited immediately (code ${child.exitCode}).`);
189
- }
190
- try {
191
- await waitForDevToolsEndpoint(port, {
192
- timeoutMs: options.readyTimeoutMs,
193
- signal: options.signal,
208
+ const attempts = Math.max(1, options.launchAttempts ?? 2);
209
+ let lastError;
210
+ for (let attempt = 1; attempt <= attempts; attempt++) {
211
+ options.signal?.throwIfAborted();
212
+ const port = options.port ?? (await allocateEphemeralPort());
213
+ const args = buildChromeArgs({
214
+ userDataDir: options.userDataDir,
215
+ profileDirectory: options.profileDirectory,
216
+ port,
217
+ headless: options.headless,
218
+ chromeArgs: options.chromeArgs,
194
219
  });
195
- }
196
- catch (error) {
220
+ const child = spawn(executable, args, { stdio: 'ignore', detached: false });
221
+ await new Promise((resolve, reject) => {
222
+ child.on('error', reject);
223
+ setTimeout(resolve, 50);
224
+ });
225
+ if (child.exitCode !== null) {
226
+ throw new Error(`Chrome exited immediately (code ${child.exitCode}).`);
227
+ }
197
228
  try {
198
- child.kill('SIGKILL');
229
+ await waitReadyWatchingExit(child, port, options.readyTimeoutMs, options.signal);
230
+ return new OwnedChromeProcess(child, port, `http://127.0.0.1:${port}`, options.userDataDir);
199
231
  }
200
- catch {
232
+ catch (error) {
233
+ try {
234
+ child.kill('SIGKILL');
235
+ }
236
+ catch {
237
+ }
238
+ lastError = error;
239
+ if (!(error instanceof DevToolsReadyTimeoutError))
240
+ throw error;
201
241
  }
202
- throw error;
203
242
  }
204
- return new OwnedChromeProcess(child, port, `http://127.0.0.1:${port}`, options.userDataDir);
243
+ if (lastError instanceof DevToolsReadyTimeoutError && attempts > 1) {
244
+ lastError.message += ` (${attempts} launch attempts)`;
245
+ }
246
+ throw lastError;
205
247
  }
206
248
  export function buildFrontProcessScript(pid) {
207
249
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-browser-use",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
4
4
  "description": "Opinionated browser automation via chrome-devtools-mcp: native Pi extension and portable Agent Plugins 1.0 skills + MCP server.",
5
5
  "keywords": [
6
6
  "automation",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "pi-browser-use",
4
- "version": "0.11.5",
4
+ "version": "0.11.7",
5
5
  "description": "Managed persistent, fresh, and existing Chrome sessions with focus-safe browser tools and CLI-first skills.",
6
6
  "author": {
7
7
  "name": "0xPlayerOne"