@ai-agent-forge/plugin-browser-playwright 0.88.5 → 0.88.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.
package/agent-forge.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageFormatVersion": 1,
3
3
  "id": "agent-forge.plugin.browser-playwright",
4
- "pluginVersion": "0.88.5",
4
+ "pluginVersion": "0.88.7",
5
5
  "apiVersion": "1",
6
6
  "minHostVersion": "0.84.4",
7
7
  "capabilityRefs": [
package/dist/setup.d.ts CHANGED
@@ -18,6 +18,7 @@ export type BrowserSpawnSyncV1 = (command: string, args: readonly string[], opti
18
18
  readonly encoding: "utf8";
19
19
  readonly timeout: number;
20
20
  readonly windowsHide: boolean;
21
+ readonly shell: boolean;
21
22
  }) => BrowserSpawnResultV1;
22
23
  export interface BrowserSetupOptionsV1 {
23
24
  /** Host agent directory; `<agentDir>/browser-deps` is the install target. */
@@ -29,6 +30,10 @@ export interface BrowserSetupOptionsV1 {
29
30
  readonly spawnSync?: BrowserSpawnSyncV1;
30
31
  /** Deterministic seam for the "already installed" probe. */
31
32
  readonly resolvePlaywright?: (browserDepsDir: string) => string | undefined;
33
+ /** Deterministic seam for the Chromium executable probe. */
34
+ readonly resolveChromiumExecutable?: (browserDepsDir: string) => string | undefined;
35
+ /** Deterministic seam for the executable existence check (tests inject a fake). */
36
+ readonly executableExists?: (path: string) => boolean;
32
37
  /** Deterministic seam for directory creation (tests inject a no-op). */
33
38
  readonly ensureDir?: (dir: string) => void;
34
39
  }
@@ -39,6 +44,13 @@ export declare function browserDepsDirFor(agentDir: string): string;
39
44
  * undefined when the module is absent. Never throws.
40
45
  */
41
46
  export declare function resolvePlaywrightModule(browserDepsDir: string): string | undefined;
47
+ /**
48
+ * Resolves the Chromium executable path through the installed playwright
49
+ * module itself, so the probe targets the exact binary `chromium.launch()`
50
+ * will use. Returns undefined when the module cannot be loaded; the reported
51
+ * path may not exist on disk (the caller checks existence). Never throws.
52
+ */
53
+ export declare function resolveChromiumExecutable(browserDepsDir: string): string | undefined;
42
54
  /**
43
55
  * Structured guidance returned when Playwright is missing. The message lists
44
56
  * the two exact install commands and points at the `browser_setup` tool; the
@@ -49,7 +61,10 @@ export declare function installGuidanceMessage(browserDepsDir?: string): string;
49
61
  /**
50
62
  * Environment for the spawned installers (and, via PLAYWRIGHT_BROWSERS_PATH,
51
63
  * for the driver's own browser launches): the whitelist from the macOS AX
52
- * adapter plus PLAYWRIGHT_BROWSERS_PATH. Everything else is dropped.
64
+ * adapter plus PLAYWRIGHT_BROWSERS_PATH — an explicit user value wins so
65
+ * install and launch resolve browsers from the same directory, otherwise the
66
+ * given default browsers dir is pinned (matches connect()'s ??=). Everything
67
+ * else is dropped.
53
68
  */
54
69
  export declare function browserSetupEnvironmentV1(browsersDir: string): NodeJS.ProcessEnv;
55
70
  /** Head/tail bounded preview (75% head, rest tail) mirroring process-exec's shaping. */
@@ -58,11 +73,16 @@ export declare function boundedPreviewV1(text: string, maximumChars: number): st
58
73
  export declare function boundedTailV1(text: string, maximumChars: number): string;
59
74
  /**
60
75
  * Installs Playwright into the browser-deps directory:
61
- * 1. already resolvable -> succeed with `alreadyInstalled: true` (no spawn);
76
+ * 1. already resolvable with the Chromium executable present -> succeed with
77
+ * `alreadyInstalled: true` (no spawn); a module without its browser binary
78
+ * does not short-circuit;
62
79
  * 2. otherwise `npm install playwright --prefix <depsDir>`, then
63
80
  * `npx playwright install chromium` (cwd = depsDir, whitelisted env plus
64
- * PLAYWRIGHT_BROWSERS_PATH=<depsDir>/browsers, bounded timeout);
65
- * 3. post-install resolution check so a silent install failure is reported.
81
+ * PLAYWRIGHT_BROWSERS_PATH — explicit user value wins, else
82
+ * <depsDir>/browsers — bounded timeout);
83
+ * 3. post-install verification (module resolvable plus the Chromium
84
+ * executable present, the same probe as step 1) so a silent install
85
+ * failure is reported.
66
86
  */
67
87
  export declare function setupBrowserDepsPlaywrightV1(options?: BrowserSetupOptionsV1): Promise<BrowserSetupOutcomeV1>;
68
88
  //# sourceMappingURL=setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,eAAO,MAAM,2BAA2B,SAAU,CAAC;AACnD,eAAO,MAAM,wBAAwB,iBAAiB,CAAC;AACvD,eAAO,MAAM,4BAA4B,aAAa,CAAC;AACvD,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAClD,eAAO,MAAM,oCAAoC,OAAO,CAAC;AACzD,eAAO,MAAM,gCAAgC,OAAO,CAAC;AACrD,eAAO,MAAM,gCAAgC,OAAO,CAAC;AAErD,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IACR,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC9B,KACG,oBAAoB,CAAC;AAE1B,MAAM,WAAW,qBAAqB;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC;IACxC,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC5E,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAYtE;AAID;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAQhF;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAM3E;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAGxE;AAiCD;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CACjD,OAAO,GAAE,qBAA0B,GACjC,OAAO,CAAC,qBAAqB,CAAC,CAqDhC","sourcesContent":["/**\n * Self-installation of Playwright into `<agentDir>/browser-deps` (the\n * `browser_setup` tool's engine).\n *\n * Playwright is deliberately not a dependency of this package: it is the only\n * optional heavy dependency in the product's browser channel, and it is\n * resolved at runtime through `createRequire(<browser-deps>/package.json)`.\n * This module owns the install sequence (`npm install playwright --prefix`\n * then `npx playwright install chromium`), the environment whitelist passed\n * to the spawned installers, bounded progress/preview shaping, and the\n * structured guidance message returned when the module is missing.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport type { BrowserSetupOutcomeV1 } from \"./types.ts\";\n\nexport const BROWSER_SETUP_TIMEOUT_MS_V1 = 600_000;\nexport const BROWSER_DEPS_DIR_NAME_V1 = \"browser-deps\";\nexport const BROWSER_BROWSERS_DIR_NAME_V1 = \"browsers\";\nexport const BROWSER_MAX_PROGRESS_CHARS_V1 = 8192;\nexport const BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1 = 8192;\nexport const BROWSER_MAX_STDERR_TAIL_CHARS_V1 = 4096;\nexport const BROWSER_MAX_STDOUT_TAIL_CHARS_V1 = 2048;\n\nexport type BrowserSpawnResultV1 = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n\treadonly stderr: string;\n};\n\nexport type BrowserSpawnSyncV1 = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly cwd: string;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t},\n) => BrowserSpawnResultV1;\n\nexport interface BrowserSetupOptionsV1 {\n\t/** Host agent directory; `<agentDir>/browser-deps` is the install target. */\n\treadonly agentDir?: string;\n\t/** Direct override of the install target; wins over `agentDir`. */\n\treadonly browserDepsDir?: string;\n\treadonly timeoutMs?: number;\n\t/** Deterministic seam for tests and brokered hosts. */\n\treadonly spawnSync?: BrowserSpawnSyncV1;\n\t/** Deterministic seam for the \"already installed\" probe. */\n\treadonly resolvePlaywright?: (browserDepsDir: string) => string | undefined;\n\t/** Deterministic seam for directory creation (tests inject a no-op). */\n\treadonly ensureDir?: (dir: string) => void;\n}\n\nexport function browserDepsDirFor(agentDir: string): string {\n\treturn join(agentDir, BROWSER_DEPS_DIR_NAME_V1);\n}\n\n/**\n * Resolves the `playwright` module against `<browser-deps>` without touching\n * this package's own dependency graph. Returns the resolved entry path, or\n * undefined when the module is absent. Never throws.\n */\nexport function resolvePlaywrightModule(browserDepsDir: string): string | undefined {\n\tif (browserDepsDir === \"\") return undefined;\n\ttry {\n\t\treturn createRequire(join(browserDepsDir, \"package.json\")).resolve(\"playwright\") as string;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Structured guidance returned when Playwright is missing. The message lists\n * the two exact install commands and points at the `browser_setup` tool; the\n * capability layer passes it through verbatim (it stays under the 2048-char\n * browser error budget).\n */\nexport function installGuidanceMessage(browserDepsDir?: string): string {\n\tconst target =\n\t\tbrowserDepsDir === undefined || browserDepsDir === \"\"\n\t\t\t? join(\"<agentDir>\", BROWSER_DEPS_DIR_NAME_V1)\n\t\t\t: browserDepsDir;\n\treturn [\n\t\t\"Playwright is required by the browser tools but is not installed.\",\n\t\t\"Install it with:\",\n\t\t` npm install playwright --prefix ${target}`,\n\t\t` PLAYWRIGHT_BROWSERS_PATH=${join(target, BROWSER_BROWSERS_DIR_NAME_V1)} npx playwright install chromium`,\n\t\t\"Or call the browser_setup tool to install these dependencies automatically.\",\n\t].join(\"\\n\");\n}\n\nconst BROWSER_SETUP_ENV_KEYS_V1: readonly string[] = [\"PATH\", \"HOME\", \"APPDATA\", \"USER\", \"LOGNAME\", \"TMPDIR\"];\n\n/**\n * Environment for the spawned installers (and, via PLAYWRIGHT_BROWSERS_PATH,\n * for the driver's own browser launches): the whitelist from the macOS AX\n * adapter plus PLAYWRIGHT_BROWSERS_PATH. Everything else is dropped.\n */\nexport function browserSetupEnvironmentV1(browsersDir: string): NodeJS.ProcessEnv {\n\tconst env: NodeJS.ProcessEnv = {};\n\tfor (const key of BROWSER_SETUP_ENV_KEYS_V1) {\n\t\tconst value = process.env[key];\n\t\tif (value !== undefined) env[key] = value;\n\t}\n\tenv.PLAYWRIGHT_BROWSERS_PATH = browsersDir;\n\treturn env;\n}\n\n/** Head/tail bounded preview (75% head, rest tail) mirroring process-exec's shaping. */\nexport function boundedPreviewV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\tconst head = Math.ceil((maximumChars * 3) / 4);\n\tconst tail = Math.max(0, maximumChars - head - 40);\n\tconst omitted = text.length - head - tail;\n\treturn `${text.slice(0, head)}\\n... [${omitted} chars omitted] ...\\n${tail > 0 ? text.slice(text.length - tail) : \"\"}`;\n}\n\n/** Tail-only bounded preview, keeping the original ending intact. */\nexport function boundedTailV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\treturn `... [${text.length - maximumChars} chars omitted] ...\\n${text.slice(text.length - maximumChars)}`;\n}\n\nconst npmCommand = (): string => (process.platform === \"win32\" ? \"npm.cmd\" : \"npm\");\nconst npxCommand = (): string => (process.platform === \"win32\" ? \"npx.cmd\" : \"npx\");\n\nconst defaultSpawn: BrowserSpawnSyncV1 = (command, args, options) => {\n\tconst outcome = nodeSpawnSync(command, [...args], { ...options });\n\treturn {\n\t\terror: outcome.error,\n\t\tstatus: outcome.status,\n\t\tstdout: typeof outcome.stdout === \"string\" ? outcome.stdout : \"\",\n\t\tstderr: typeof outcome.stderr === \"string\" ? outcome.stderr : \"\",\n\t};\n};\n\nconst describeExit = (outcome: BrowserSpawnResultV1): string => {\n\tif (outcome.status === 0) return \"0\";\n\tif (outcome.status !== null) return String(outcome.status);\n\treturn outcome.error?.message ?? \"no exit status\";\n};\n\nconst spawnSucceeded = (outcome: BrowserSpawnResultV1): boolean => outcome.error === undefined && outcome.status === 0;\n\nconst failureMessage = (label: string, outcome: BrowserSpawnResultV1, depsDir: string): string => {\n\tconst parts = [`${label} failed (exit ${describeExit(outcome)})`];\n\tconst stderr = boundedTailV1(outcome.stderr, BROWSER_MAX_STDERR_TAIL_CHARS_V1);\n\tif (stderr.trim() !== \"\") parts.push(`stderr tail:\\n${stderr}`);\n\tconst stdout = boundedTailV1(outcome.stdout, BROWSER_MAX_STDOUT_TAIL_CHARS_V1);\n\tif (stdout.trim() !== \"\") parts.push(`stdout tail:\\n${stdout}`);\n\tparts.push(installGuidanceMessage(depsDir));\n\treturn boundedPreviewV1(parts.join(\"\\n\"), BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1);\n};\n\n/**\n * Installs Playwright into the browser-deps directory:\n * 1. already resolvable -> succeed with `alreadyInstalled: true` (no spawn);\n * 2. otherwise `npm install playwright --prefix <depsDir>`, then\n * `npx playwright install chromium` (cwd = depsDir, whitelisted env plus\n * PLAYWRIGHT_BROWSERS_PATH=<depsDir>/browsers, bounded timeout);\n * 3. post-install resolution check so a silent install failure is reported.\n */\nexport async function setupBrowserDepsPlaywrightV1(\n\toptions: BrowserSetupOptionsV1 = {},\n): Promise<BrowserSetupOutcomeV1> {\n\tconst depsDir =\n\t\toptions.browserDepsDir ?? (options.agentDir === undefined ? undefined : browserDepsDirFor(options.agentDir));\n\tif (depsDir === undefined || depsDir === \"\") {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `${installGuidanceMessage()}\\nThe running host did not declare an agentDir, so browser_setup cannot install anything.`,\n\t\t};\n\t}\n\tconst resolve = options.resolvePlaywright ?? resolvePlaywrightModule;\n\tconst spawn = options.spawnSync ?? defaultSpawn;\n\tconst ensureDir = options.ensureDir ?? ((dir: string) => mkdirSync(dir, { recursive: true }));\n\tconst timeoutMs = options.timeoutMs ?? BROWSER_SETUP_TIMEOUT_MS_V1;\n\tconst resolved = resolve(depsDir);\n\tif (resolved !== undefined) {\n\t\treturn { ok: true, alreadyInstalled: true, log: `playwright resolvable at ${resolved}` };\n\t}\n\tconst log: string[] = [`target: ${depsDir}`];\n\tensureDir(depsDir);\n\tconst env = browserSetupEnvironmentV1(join(depsDir, BROWSER_BROWSERS_DIR_NAME_V1));\n\tconst spawnOptions = {\n\t\tcwd: depsDir,\n\t\tenv,\n\t\tencoding: \"utf8\",\n\t\ttimeout: timeoutMs,\n\t\twindowsHide: true,\n\t} as const;\n\tconst install = spawn(npmCommand(), [\"install\", \"playwright\", \"--prefix\", depsDir], spawnOptions);\n\tif (!spawnSucceeded(install)) {\n\t\treturn { ok: false, message: failureMessage(\"npm install playwright --prefix\", install, depsDir) };\n\t}\n\tlog.push(`npm install playwright --prefix: exit ${describeExit(install)}`);\n\tconst browsers = spawn(npxCommand(), [\"playwright\", \"install\", \"chromium\"], spawnOptions);\n\tif (!spawnSucceeded(browsers)) {\n\t\treturn { ok: false, message: failureMessage(\"npx playwright install chromium\", browsers, depsDir) };\n\t}\n\tlog.push(`npx playwright install chromium: exit ${describeExit(browsers)}`);\n\tconst verified = resolve(depsDir);\n\tif (verified === undefined) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: boundedPreviewV1(\n\t\t\t\t`${installGuidanceMessage(depsDir)}\\nInstallation commands reported success but playwright is still not resolvable from ${depsDir}.`,\n\t\t\t\tBROWSER_MAX_FAILURE_MESSAGE_CHARS_V1,\n\t\t\t),\n\t\t};\n\t}\n\tlog.push(`verification: playwright resolvable at ${verified}`);\n\treturn {\n\t\tok: true,\n\t\talreadyInstalled: false,\n\t\tlog: boundedPreviewV1(log.join(\"\\n\"), BROWSER_MAX_PROGRESS_CHARS_V1),\n\t};\n}\n"]}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Self-installation of Playwright into `<agentDir>/browser-deps` (the\n * `browser_setup` tool's engine).\n *\n * Playwright is deliberately not a dependency of this package: it is the only\n * optional heavy dependency in the product's browser channel, and it is\n * resolved at runtime through `createRequire(<browser-deps>/package.json)`.\n * This module owns the install sequence (`npm install playwright --prefix`\n * then `npx playwright install chromium`), the already-installed probe\n * (module resolvable plus the Chromium executable present), the environment\n * whitelist passed to the spawned installers, bounded progress/preview\n * shaping, and the structured guidance message returned when the module is\n * missing.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport type { BrowserSetupOutcomeV1 } from \"./types.ts\";\n\nexport const BROWSER_SETUP_TIMEOUT_MS_V1 = 600_000;\nexport const BROWSER_DEPS_DIR_NAME_V1 = \"browser-deps\";\nexport const BROWSER_BROWSERS_DIR_NAME_V1 = \"browsers\";\nexport const BROWSER_MAX_PROGRESS_CHARS_V1 = 8192;\nexport const BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1 = 8192;\nexport const BROWSER_MAX_STDERR_TAIL_CHARS_V1 = 4096;\nexport const BROWSER_MAX_STDOUT_TAIL_CHARS_V1 = 2048;\n\nexport type BrowserSpawnResultV1 = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n\treadonly stderr: string;\n};\n\nexport type BrowserSpawnSyncV1 = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly cwd: string;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t\treadonly shell: boolean;\n\t},\n) => BrowserSpawnResultV1;\n\nexport interface BrowserSetupOptionsV1 {\n\t/** Host agent directory; `<agentDir>/browser-deps` is the install target. */\n\treadonly agentDir?: string;\n\t/** Direct override of the install target; wins over `agentDir`. */\n\treadonly browserDepsDir?: string;\n\treadonly timeoutMs?: number;\n\t/** Deterministic seam for tests and brokered hosts. */\n\treadonly spawnSync?: BrowserSpawnSyncV1;\n\t/** Deterministic seam for the \"already installed\" probe. */\n\treadonly resolvePlaywright?: (browserDepsDir: string) => string | undefined;\n\t/** Deterministic seam for the Chromium executable probe. */\n\treadonly resolveChromiumExecutable?: (browserDepsDir: string) => string | undefined;\n\t/** Deterministic seam for the executable existence check (tests inject a fake). */\n\treadonly executableExists?: (path: string) => boolean;\n\t/** Deterministic seam for directory creation (tests inject a no-op). */\n\treadonly ensureDir?: (dir: string) => void;\n}\n\nexport function browserDepsDirFor(agentDir: string): string {\n\treturn join(agentDir, BROWSER_DEPS_DIR_NAME_V1);\n}\n\n/**\n * Resolves the `playwright` module against `<browser-deps>` without touching\n * this package's own dependency graph. Returns the resolved entry path, or\n * undefined when the module is absent. Never throws.\n */\nexport function resolvePlaywrightModule(browserDepsDir: string): string | undefined {\n\tif (browserDepsDir === \"\") return undefined;\n\ttry {\n\t\treturn createRequire(join(browserDepsDir, \"package.json\")).resolve(\"playwright\") as string;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Minimal shape of the installed playwright module used by the browser probe. */\ninterface PlaywrightChromiumProbeV1 {\n\tchromium: {\n\t\texecutablePath(): string;\n\t};\n}\n\n/**\n * Resolves the Chromium executable path through the installed playwright\n * module itself, so the probe targets the exact binary `chromium.launch()`\n * will use. Returns undefined when the module cannot be loaded; the reported\n * path may not exist on disk (the caller checks existence). Never throws.\n */\nexport function resolveChromiumExecutable(browserDepsDir: string): string | undefined {\n\tif (browserDepsDir === \"\") return undefined;\n\ttry {\n\t\t// playwright-core computes its registry directory once at module load\n\t\t// from PLAYWRIGHT_BROWSERS_PATH; when the user has not set it, pin it\n\t\t// before the require to the same isolated directory the install flow\n\t\t// populates and connect() falls back to. An explicit user value wins\n\t\t// (same precedence as connect()'s ??=), so probe and launch agree.\n\t\tconst previous = process.env.PLAYWRIGHT_BROWSERS_PATH;\n\t\tif (previous === undefined) {\n\t\t\tprocess.env.PLAYWRIGHT_BROWSERS_PATH = join(browserDepsDir, BROWSER_BROWSERS_DIR_NAME_V1);\n\t\t}\n\t\ttry {\n\t\t\tconst require = createRequire(join(browserDepsDir, \"package.json\"));\n\t\t\tconst playwright = require(\"playwright\") as PlaywrightChromiumProbeV1;\n\t\t\treturn playwright.chromium.executablePath();\n\t\t} finally {\n\t\t\tif (previous === undefined) delete process.env.PLAYWRIGHT_BROWSERS_PATH;\n\t\t}\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Structured guidance returned when Playwright is missing. The message lists\n * the two exact install commands and points at the `browser_setup` tool; the\n * capability layer passes it through verbatim (it stays under the 2048-char\n * browser error budget).\n */\nexport function installGuidanceMessage(browserDepsDir?: string): string {\n\tconst target =\n\t\tbrowserDepsDir === undefined || browserDepsDir === \"\"\n\t\t\t? join(\"<agentDir>\", BROWSER_DEPS_DIR_NAME_V1)\n\t\t\t: browserDepsDir;\n\treturn [\n\t\t\"Playwright is required by the browser tools but is not installed.\",\n\t\t\"Install it with:\",\n\t\t` npm install playwright --prefix ${target}`,\n\t\t// With an explicit PLAYWRIGHT_BROWSERS_PATH in the environment the bare\n\t\t// command already installs where the driver looks; pinning the prefix\n\t\t// here would override the user's directory (connect honors it via ??=).\n\t\tprocess.env.PLAYWRIGHT_BROWSERS_PATH === undefined\n\t\t\t? ` PLAYWRIGHT_BROWSERS_PATH=${join(target, BROWSER_BROWSERS_DIR_NAME_V1)} npx playwright install chromium`\n\t\t\t: \" npx playwright install chromium\",\n\t\t\"Or call the browser_setup tool to install these dependencies automatically.\",\n\t].join(\"\\n\");\n}\n\nconst BROWSER_SETUP_ENV_KEYS_V1: readonly string[] = [\"PATH\", \"HOME\", \"APPDATA\", \"USER\", \"LOGNAME\", \"TMPDIR\"];\n\n/**\n * Environment for the spawned installers (and, via PLAYWRIGHT_BROWSERS_PATH,\n * for the driver's own browser launches): the whitelist from the macOS AX\n * adapter plus PLAYWRIGHT_BROWSERS_PATH — an explicit user value wins so\n * install and launch resolve browsers from the same directory, otherwise the\n * given default browsers dir is pinned (matches connect()'s ??=). Everything\n * else is dropped.\n */\nexport function browserSetupEnvironmentV1(browsersDir: string): NodeJS.ProcessEnv {\n\tconst env: NodeJS.ProcessEnv = {};\n\tfor (const key of BROWSER_SETUP_ENV_KEYS_V1) {\n\t\tconst value = process.env[key];\n\t\tif (value !== undefined) env[key] = value;\n\t}\n\tenv.PLAYWRIGHT_BROWSERS_PATH = process.env.PLAYWRIGHT_BROWSERS_PATH ?? browsersDir;\n\treturn env;\n}\n\n/** Head/tail bounded preview (75% head, rest tail) mirroring process-exec's shaping. */\nexport function boundedPreviewV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\tconst head = Math.ceil((maximumChars * 3) / 4);\n\tconst tail = Math.max(0, maximumChars - head - 40);\n\tconst omitted = text.length - head - tail;\n\treturn `${text.slice(0, head)}\\n... [${omitted} chars omitted] ...\\n${tail > 0 ? text.slice(text.length - tail) : \"\"}`;\n}\n\n/** Tail-only bounded preview, keeping the original ending intact. */\nexport function boundedTailV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\treturn `... [${text.length - maximumChars} chars omitted] ...\\n${text.slice(text.length - maximumChars)}`;\n}\n\nconst isWin32 = process.platform === \"win32\";\nconst npmCommand = (): string => (isWin32 ? \"npm.cmd\" : \"npm\");\nconst npxCommand = (): string => (isWin32 ? \"npx.cmd\" : \"npx\");\n\nconst defaultSpawn: BrowserSpawnSyncV1 = (command, args, options) => {\n\tconst outcome = nodeSpawnSync(command, [...args], { ...options });\n\treturn {\n\t\terror: outcome.error,\n\t\tstatus: outcome.status,\n\t\tstdout: typeof outcome.stdout === \"string\" ? outcome.stdout : \"\",\n\t\tstderr: typeof outcome.stderr === \"string\" ? outcome.stderr : \"\",\n\t};\n};\n\nconst describeExit = (outcome: BrowserSpawnResultV1): string => {\n\tif (outcome.status === 0) return \"0\";\n\tif (outcome.status !== null) return String(outcome.status);\n\treturn outcome.error?.message ?? \"no exit status\";\n};\n\nconst spawnSucceeded = (outcome: BrowserSpawnResultV1): boolean => outcome.error === undefined && outcome.status === 0;\n\nconst failureMessage = (label: string, outcome: BrowserSpawnResultV1, depsDir: string): string => {\n\tconst parts = [`${label} failed (exit ${describeExit(outcome)})`];\n\tconst stderr = boundedTailV1(outcome.stderr, BROWSER_MAX_STDERR_TAIL_CHARS_V1);\n\tif (stderr.trim() !== \"\") parts.push(`stderr tail:\\n${stderr}`);\n\tconst stdout = boundedTailV1(outcome.stdout, BROWSER_MAX_STDOUT_TAIL_CHARS_V1);\n\tif (stdout.trim() !== \"\") parts.push(`stdout tail:\\n${stdout}`);\n\tparts.push(installGuidanceMessage(depsDir));\n\treturn boundedPreviewV1(parts.join(\"\\n\"), BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1);\n};\n\n/**\n * Installs Playwright into the browser-deps directory:\n * 1. already resolvable with the Chromium executable present -> succeed with\n * `alreadyInstalled: true` (no spawn); a module without its browser binary\n * does not short-circuit;\n * 2. otherwise `npm install playwright --prefix <depsDir>`, then\n * `npx playwright install chromium` (cwd = depsDir, whitelisted env plus\n * PLAYWRIGHT_BROWSERS_PATH — explicit user value wins, else\n * <depsDir>/browsers — bounded timeout);\n * 3. post-install verification (module resolvable plus the Chromium\n * executable present, the same probe as step 1) so a silent install\n * failure is reported.\n */\nexport async function setupBrowserDepsPlaywrightV1(\n\toptions: BrowserSetupOptionsV1 = {},\n): Promise<BrowserSetupOutcomeV1> {\n\tconst depsDir =\n\t\toptions.browserDepsDir ?? (options.agentDir === undefined ? undefined : browserDepsDirFor(options.agentDir));\n\tif (depsDir === undefined || depsDir === \"\") {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `${installGuidanceMessage()}\\nThe running host did not declare an agentDir, so browser_setup cannot install anything.`,\n\t\t};\n\t}\n\tconst resolve = options.resolvePlaywright ?? resolvePlaywrightModule;\n\tconst resolveChromium = options.resolveChromiumExecutable ?? resolveChromiumExecutable;\n\tconst executableExists = options.executableExists ?? existsSync;\n\tconst spawn = options.spawnSync ?? defaultSpawn;\n\tconst ensureDir = options.ensureDir ?? ((dir: string) => mkdirSync(dir, { recursive: true }));\n\tconst timeoutMs = options.timeoutMs ?? BROWSER_SETUP_TIMEOUT_MS_V1;\n\tconst resolved = resolve(depsDir);\n\t// Already-installed requires BOTH the module and the browser binary:\n\t// a past run that installed the package but failed the chromium download\n\t// must re-run the install instead of reporting success the driver cannot\n\t// honor (launch would fail with \"Executable doesn't exist\").\n\tconst chromiumExecutable = resolved === undefined ? undefined : resolveChromium(depsDir);\n\tif (resolved !== undefined && chromiumExecutable !== undefined && executableExists(chromiumExecutable)) {\n\t\treturn { ok: true, alreadyInstalled: true, log: `playwright resolvable at ${resolved}` };\n\t}\n\tconst log: string[] = [`target: ${depsDir}`];\n\tensureDir(depsDir);\n\tconst env = browserSetupEnvironmentV1(join(depsDir, BROWSER_BROWSERS_DIR_NAME_V1));\n\tconst spawnOptions = {\n\t\tcwd: depsDir,\n\t\tenv,\n\t\tencoding: \"utf8\",\n\t\ttimeout: timeoutMs,\n\t\twindowsHide: true,\n\t\t// Node >= 18.20 refuses to spawn .cmd shims directly (EINVAL), so win32\n\t\t// runs through cmd.exe, which joins argv with spaces and never quotes;\n\t\t// --prefix carries an arbitrary user path (homedir may contain spaces),\n\t\t// so quote it explicitly on win32 (node-package-install-host precedent).\n\t\tshell: isWin32,\n\t} as const;\n\tconst install = spawn(\n\t\tnpmCommand(),\n\t\t[\"install\", \"playwright\", \"--prefix\", isWin32 ? `\"${depsDir}\"` : depsDir],\n\t\tspawnOptions,\n\t);\n\tif (!spawnSucceeded(install)) {\n\t\treturn { ok: false, message: failureMessage(\"npm install playwright --prefix\", install, depsDir) };\n\t}\n\tlog.push(`npm install playwright --prefix: exit ${describeExit(install)}`);\n\tconst browsers = spawn(npxCommand(), [\"playwright\", \"install\", \"chromium\"], spawnOptions);\n\tif (!spawnSucceeded(browsers)) {\n\t\treturn { ok: false, message: failureMessage(\"npx playwright install chromium\", browsers, depsDir) };\n\t}\n\tlog.push(`npx playwright install chromium: exit ${describeExit(browsers)}`);\n\tconst verified = resolve(depsDir);\n\tif (verified === undefined) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: boundedPreviewV1(\n\t\t\t\t`${installGuidanceMessage(depsDir)}\\nInstallation commands reported success but playwright is still not resolvable from ${depsDir}.`,\n\t\t\t\tBROWSER_MAX_FAILURE_MESSAGE_CHARS_V1,\n\t\t\t),\n\t\t};\n\t}\n\tlog.push(`verification: playwright resolvable at ${verified}`);\n\t// The install commands can exit 0 while the chromium download silently\n\t// failed (e.g. a blocked download host), so verify the exact binary\n\t// chromium.launch() will use — the same probe the already-installed\n\t// check above runs.\n\tconst verifiedExecutable = resolveChromium(depsDir);\n\tif (verifiedExecutable === undefined || !executableExists(verifiedExecutable)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: boundedPreviewV1(\n\t\t\t\t`${installGuidanceMessage(depsDir)}\\nInstallation commands reported success but the Chromium executable is still missing${\n\t\t\t\t\tverifiedExecutable === undefined ? \".\" : ` (expected at ${verifiedExecutable}).`\n\t\t\t\t}`,\n\t\t\t\tBROWSER_MAX_FAILURE_MESSAGE_CHARS_V1,\n\t\t\t),\n\t\t};\n\t}\n\tlog.push(`verification: chromium executable at ${verifiedExecutable}`);\n\treturn {\n\t\tok: true,\n\t\talreadyInstalled: false,\n\t\tlog: boundedPreviewV1(log.join(\"\\n\"), BROWSER_MAX_PROGRESS_CHARS_V1),\n\t};\n}\n"]}
package/dist/setup.js CHANGED
@@ -6,12 +6,14 @@
6
6
  * optional heavy dependency in the product's browser channel, and it is
7
7
  * resolved at runtime through `createRequire(<browser-deps>/package.json)`.
8
8
  * This module owns the install sequence (`npm install playwright --prefix`
9
- * then `npx playwright install chromium`), the environment whitelist passed
10
- * to the spawned installers, bounded progress/preview shaping, and the
11
- * structured guidance message returned when the module is missing.
9
+ * then `npx playwright install chromium`), the already-installed probe
10
+ * (module resolvable plus the Chromium executable present), the environment
11
+ * whitelist passed to the spawned installers, bounded progress/preview
12
+ * shaping, and the structured guidance message returned when the module is
13
+ * missing.
12
14
  */
13
15
  import { spawnSync as nodeSpawnSync } from "node:child_process";
14
- import { mkdirSync } from "node:fs";
16
+ import { existsSync, mkdirSync } from "node:fs";
15
17
  import { createRequire } from "node:module";
16
18
  import { join } from "node:path";
17
19
  export const BROWSER_SETUP_TIMEOUT_MS_V1 = 600_000;
@@ -39,6 +41,39 @@ export function resolvePlaywrightModule(browserDepsDir) {
39
41
  return undefined;
40
42
  }
41
43
  }
44
+ /**
45
+ * Resolves the Chromium executable path through the installed playwright
46
+ * module itself, so the probe targets the exact binary `chromium.launch()`
47
+ * will use. Returns undefined when the module cannot be loaded; the reported
48
+ * path may not exist on disk (the caller checks existence). Never throws.
49
+ */
50
+ export function resolveChromiumExecutable(browserDepsDir) {
51
+ if (browserDepsDir === "")
52
+ return undefined;
53
+ try {
54
+ // playwright-core computes its registry directory once at module load
55
+ // from PLAYWRIGHT_BROWSERS_PATH; when the user has not set it, pin it
56
+ // before the require to the same isolated directory the install flow
57
+ // populates and connect() falls back to. An explicit user value wins
58
+ // (same precedence as connect()'s ??=), so probe and launch agree.
59
+ const previous = process.env.PLAYWRIGHT_BROWSERS_PATH;
60
+ if (previous === undefined) {
61
+ process.env.PLAYWRIGHT_BROWSERS_PATH = join(browserDepsDir, BROWSER_BROWSERS_DIR_NAME_V1);
62
+ }
63
+ try {
64
+ const require = createRequire(join(browserDepsDir, "package.json"));
65
+ const playwright = require("playwright");
66
+ return playwright.chromium.executablePath();
67
+ }
68
+ finally {
69
+ if (previous === undefined)
70
+ delete process.env.PLAYWRIGHT_BROWSERS_PATH;
71
+ }
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
42
77
  /**
43
78
  * Structured guidance returned when Playwright is missing. The message lists
44
79
  * the two exact install commands and points at the `browser_setup` tool; the
@@ -53,7 +88,12 @@ export function installGuidanceMessage(browserDepsDir) {
53
88
  "Playwright is required by the browser tools but is not installed.",
54
89
  "Install it with:",
55
90
  ` npm install playwright --prefix ${target}`,
56
- ` PLAYWRIGHT_BROWSERS_PATH=${join(target, BROWSER_BROWSERS_DIR_NAME_V1)} npx playwright install chromium`,
91
+ // With an explicit PLAYWRIGHT_BROWSERS_PATH in the environment the bare
92
+ // command already installs where the driver looks; pinning the prefix
93
+ // here would override the user's directory (connect honors it via ??=).
94
+ process.env.PLAYWRIGHT_BROWSERS_PATH === undefined
95
+ ? ` PLAYWRIGHT_BROWSERS_PATH=${join(target, BROWSER_BROWSERS_DIR_NAME_V1)} npx playwright install chromium`
96
+ : " npx playwright install chromium",
57
97
  "Or call the browser_setup tool to install these dependencies automatically.",
58
98
  ].join("\n");
59
99
  }
@@ -61,7 +101,10 @@ const BROWSER_SETUP_ENV_KEYS_V1 = ["PATH", "HOME", "APPDATA", "USER", "LOGNAME",
61
101
  /**
62
102
  * Environment for the spawned installers (and, via PLAYWRIGHT_BROWSERS_PATH,
63
103
  * for the driver's own browser launches): the whitelist from the macOS AX
64
- * adapter plus PLAYWRIGHT_BROWSERS_PATH. Everything else is dropped.
104
+ * adapter plus PLAYWRIGHT_BROWSERS_PATH — an explicit user value wins so
105
+ * install and launch resolve browsers from the same directory, otherwise the
106
+ * given default browsers dir is pinned (matches connect()'s ??=). Everything
107
+ * else is dropped.
65
108
  */
66
109
  export function browserSetupEnvironmentV1(browsersDir) {
67
110
  const env = {};
@@ -70,7 +113,7 @@ export function browserSetupEnvironmentV1(browsersDir) {
70
113
  if (value !== undefined)
71
114
  env[key] = value;
72
115
  }
73
- env.PLAYWRIGHT_BROWSERS_PATH = browsersDir;
116
+ env.PLAYWRIGHT_BROWSERS_PATH = process.env.PLAYWRIGHT_BROWSERS_PATH ?? browsersDir;
74
117
  return env;
75
118
  }
76
119
  /** Head/tail bounded preview (75% head, rest tail) mirroring process-exec's shaping. */
@@ -88,8 +131,9 @@ export function boundedTailV1(text, maximumChars) {
88
131
  return text;
89
132
  return `... [${text.length - maximumChars} chars omitted] ...\n${text.slice(text.length - maximumChars)}`;
90
133
  }
91
- const npmCommand = () => (process.platform === "win32" ? "npm.cmd" : "npm");
92
- const npxCommand = () => (process.platform === "win32" ? "npx.cmd" : "npx");
134
+ const isWin32 = process.platform === "win32";
135
+ const npmCommand = () => (isWin32 ? "npm.cmd" : "npm");
136
+ const npxCommand = () => (isWin32 ? "npx.cmd" : "npx");
93
137
  const defaultSpawn = (command, args, options) => {
94
138
  const outcome = nodeSpawnSync(command, [...args], { ...options });
95
139
  return {
@@ -120,11 +164,16 @@ const failureMessage = (label, outcome, depsDir) => {
120
164
  };
121
165
  /**
122
166
  * Installs Playwright into the browser-deps directory:
123
- * 1. already resolvable -> succeed with `alreadyInstalled: true` (no spawn);
167
+ * 1. already resolvable with the Chromium executable present -> succeed with
168
+ * `alreadyInstalled: true` (no spawn); a module without its browser binary
169
+ * does not short-circuit;
124
170
  * 2. otherwise `npm install playwright --prefix <depsDir>`, then
125
171
  * `npx playwright install chromium` (cwd = depsDir, whitelisted env plus
126
- * PLAYWRIGHT_BROWSERS_PATH=<depsDir>/browsers, bounded timeout);
127
- * 3. post-install resolution check so a silent install failure is reported.
172
+ * PLAYWRIGHT_BROWSERS_PATH — explicit user value wins, else
173
+ * <depsDir>/browsers — bounded timeout);
174
+ * 3. post-install verification (module resolvable plus the Chromium
175
+ * executable present, the same probe as step 1) so a silent install
176
+ * failure is reported.
128
177
  */
129
178
  export async function setupBrowserDepsPlaywrightV1(options = {}) {
130
179
  const depsDir = options.browserDepsDir ?? (options.agentDir === undefined ? undefined : browserDepsDirFor(options.agentDir));
@@ -135,11 +184,18 @@ export async function setupBrowserDepsPlaywrightV1(options = {}) {
135
184
  };
136
185
  }
137
186
  const resolve = options.resolvePlaywright ?? resolvePlaywrightModule;
187
+ const resolveChromium = options.resolveChromiumExecutable ?? resolveChromiumExecutable;
188
+ const executableExists = options.executableExists ?? existsSync;
138
189
  const spawn = options.spawnSync ?? defaultSpawn;
139
190
  const ensureDir = options.ensureDir ?? ((dir) => mkdirSync(dir, { recursive: true }));
140
191
  const timeoutMs = options.timeoutMs ?? BROWSER_SETUP_TIMEOUT_MS_V1;
141
192
  const resolved = resolve(depsDir);
142
- if (resolved !== undefined) {
193
+ // Already-installed requires BOTH the module and the browser binary:
194
+ // a past run that installed the package but failed the chromium download
195
+ // must re-run the install instead of reporting success the driver cannot
196
+ // honor (launch would fail with "Executable doesn't exist").
197
+ const chromiumExecutable = resolved === undefined ? undefined : resolveChromium(depsDir);
198
+ if (resolved !== undefined && chromiumExecutable !== undefined && executableExists(chromiumExecutable)) {
143
199
  return { ok: true, alreadyInstalled: true, log: `playwright resolvable at ${resolved}` };
144
200
  }
145
201
  const log = [`target: ${depsDir}`];
@@ -151,8 +207,13 @@ export async function setupBrowserDepsPlaywrightV1(options = {}) {
151
207
  encoding: "utf8",
152
208
  timeout: timeoutMs,
153
209
  windowsHide: true,
210
+ // Node >= 18.20 refuses to spawn .cmd shims directly (EINVAL), so win32
211
+ // runs through cmd.exe, which joins argv with spaces and never quotes;
212
+ // --prefix carries an arbitrary user path (homedir may contain spaces),
213
+ // so quote it explicitly on win32 (node-package-install-host precedent).
214
+ shell: isWin32,
154
215
  };
155
- const install = spawn(npmCommand(), ["install", "playwright", "--prefix", depsDir], spawnOptions);
216
+ const install = spawn(npmCommand(), ["install", "playwright", "--prefix", isWin32 ? `"${depsDir}"` : depsDir], spawnOptions);
156
217
  if (!spawnSucceeded(install)) {
157
218
  return { ok: false, message: failureMessage("npm install playwright --prefix", install, depsDir) };
158
219
  }
@@ -170,6 +231,18 @@ export async function setupBrowserDepsPlaywrightV1(options = {}) {
170
231
  };
171
232
  }
172
233
  log.push(`verification: playwright resolvable at ${verified}`);
234
+ // The install commands can exit 0 while the chromium download silently
235
+ // failed (e.g. a blocked download host), so verify the exact binary
236
+ // chromium.launch() will use — the same probe the already-installed
237
+ // check above runs.
238
+ const verifiedExecutable = resolveChromium(depsDir);
239
+ if (verifiedExecutable === undefined || !executableExists(verifiedExecutable)) {
240
+ return {
241
+ ok: false,
242
+ message: boundedPreviewV1(`${installGuidanceMessage(depsDir)}\nInstallation commands reported success but the Chromium executable is still missing${verifiedExecutable === undefined ? "." : ` (expected at ${verifiedExecutable}).`}`, BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1),
243
+ };
244
+ }
245
+ log.push(`verification: chromium executable at ${verifiedExecutable}`);
173
246
  return {
174
247
  ok: true,
175
248
  alreadyInstalled: false,
package/dist/setup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Self-installation of Playwright into `<agentDir>/browser-deps` (the\n * `browser_setup` tool's engine).\n *\n * Playwright is deliberately not a dependency of this package: it is the only\n * optional heavy dependency in the product's browser channel, and it is\n * resolved at runtime through `createRequire(<browser-deps>/package.json)`.\n * This module owns the install sequence (`npm install playwright --prefix`\n * then `npx playwright install chromium`), the environment whitelist passed\n * to the spawned installers, bounded progress/preview shaping, and the\n * structured guidance message returned when the module is missing.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport type { BrowserSetupOutcomeV1 } from \"./types.ts\";\n\nexport const BROWSER_SETUP_TIMEOUT_MS_V1 = 600_000;\nexport const BROWSER_DEPS_DIR_NAME_V1 = \"browser-deps\";\nexport const BROWSER_BROWSERS_DIR_NAME_V1 = \"browsers\";\nexport const BROWSER_MAX_PROGRESS_CHARS_V1 = 8192;\nexport const BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1 = 8192;\nexport const BROWSER_MAX_STDERR_TAIL_CHARS_V1 = 4096;\nexport const BROWSER_MAX_STDOUT_TAIL_CHARS_V1 = 2048;\n\nexport type BrowserSpawnResultV1 = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n\treadonly stderr: string;\n};\n\nexport type BrowserSpawnSyncV1 = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly cwd: string;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t},\n) => BrowserSpawnResultV1;\n\nexport interface BrowserSetupOptionsV1 {\n\t/** Host agent directory; `<agentDir>/browser-deps` is the install target. */\n\treadonly agentDir?: string;\n\t/** Direct override of the install target; wins over `agentDir`. */\n\treadonly browserDepsDir?: string;\n\treadonly timeoutMs?: number;\n\t/** Deterministic seam for tests and brokered hosts. */\n\treadonly spawnSync?: BrowserSpawnSyncV1;\n\t/** Deterministic seam for the \"already installed\" probe. */\n\treadonly resolvePlaywright?: (browserDepsDir: string) => string | undefined;\n\t/** Deterministic seam for directory creation (tests inject a no-op). */\n\treadonly ensureDir?: (dir: string) => void;\n}\n\nexport function browserDepsDirFor(agentDir: string): string {\n\treturn join(agentDir, BROWSER_DEPS_DIR_NAME_V1);\n}\n\n/**\n * Resolves the `playwright` module against `<browser-deps>` without touching\n * this package's own dependency graph. Returns the resolved entry path, or\n * undefined when the module is absent. Never throws.\n */\nexport function resolvePlaywrightModule(browserDepsDir: string): string | undefined {\n\tif (browserDepsDir === \"\") return undefined;\n\ttry {\n\t\treturn createRequire(join(browserDepsDir, \"package.json\")).resolve(\"playwright\") as string;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Structured guidance returned when Playwright is missing. The message lists\n * the two exact install commands and points at the `browser_setup` tool; the\n * capability layer passes it through verbatim (it stays under the 2048-char\n * browser error budget).\n */\nexport function installGuidanceMessage(browserDepsDir?: string): string {\n\tconst target =\n\t\tbrowserDepsDir === undefined || browserDepsDir === \"\"\n\t\t\t? join(\"<agentDir>\", BROWSER_DEPS_DIR_NAME_V1)\n\t\t\t: browserDepsDir;\n\treturn [\n\t\t\"Playwright is required by the browser tools but is not installed.\",\n\t\t\"Install it with:\",\n\t\t` npm install playwright --prefix ${target}`,\n\t\t` PLAYWRIGHT_BROWSERS_PATH=${join(target, BROWSER_BROWSERS_DIR_NAME_V1)} npx playwright install chromium`,\n\t\t\"Or call the browser_setup tool to install these dependencies automatically.\",\n\t].join(\"\\n\");\n}\n\nconst BROWSER_SETUP_ENV_KEYS_V1: readonly string[] = [\"PATH\", \"HOME\", \"APPDATA\", \"USER\", \"LOGNAME\", \"TMPDIR\"];\n\n/**\n * Environment for the spawned installers (and, via PLAYWRIGHT_BROWSERS_PATH,\n * for the driver's own browser launches): the whitelist from the macOS AX\n * adapter plus PLAYWRIGHT_BROWSERS_PATH. Everything else is dropped.\n */\nexport function browserSetupEnvironmentV1(browsersDir: string): NodeJS.ProcessEnv {\n\tconst env: NodeJS.ProcessEnv = {};\n\tfor (const key of BROWSER_SETUP_ENV_KEYS_V1) {\n\t\tconst value = process.env[key];\n\t\tif (value !== undefined) env[key] = value;\n\t}\n\tenv.PLAYWRIGHT_BROWSERS_PATH = browsersDir;\n\treturn env;\n}\n\n/** Head/tail bounded preview (75% head, rest tail) mirroring process-exec's shaping. */\nexport function boundedPreviewV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\tconst head = Math.ceil((maximumChars * 3) / 4);\n\tconst tail = Math.max(0, maximumChars - head - 40);\n\tconst omitted = text.length - head - tail;\n\treturn `${text.slice(0, head)}\\n... [${omitted} chars omitted] ...\\n${tail > 0 ? text.slice(text.length - tail) : \"\"}`;\n}\n\n/** Tail-only bounded preview, keeping the original ending intact. */\nexport function boundedTailV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\treturn `... [${text.length - maximumChars} chars omitted] ...\\n${text.slice(text.length - maximumChars)}`;\n}\n\nconst npmCommand = (): string => (process.platform === \"win32\" ? \"npm.cmd\" : \"npm\");\nconst npxCommand = (): string => (process.platform === \"win32\" ? \"npx.cmd\" : \"npx\");\n\nconst defaultSpawn: BrowserSpawnSyncV1 = (command, args, options) => {\n\tconst outcome = nodeSpawnSync(command, [...args], { ...options });\n\treturn {\n\t\terror: outcome.error,\n\t\tstatus: outcome.status,\n\t\tstdout: typeof outcome.stdout === \"string\" ? outcome.stdout : \"\",\n\t\tstderr: typeof outcome.stderr === \"string\" ? outcome.stderr : \"\",\n\t};\n};\n\nconst describeExit = (outcome: BrowserSpawnResultV1): string => {\n\tif (outcome.status === 0) return \"0\";\n\tif (outcome.status !== null) return String(outcome.status);\n\treturn outcome.error?.message ?? \"no exit status\";\n};\n\nconst spawnSucceeded = (outcome: BrowserSpawnResultV1): boolean => outcome.error === undefined && outcome.status === 0;\n\nconst failureMessage = (label: string, outcome: BrowserSpawnResultV1, depsDir: string): string => {\n\tconst parts = [`${label} failed (exit ${describeExit(outcome)})`];\n\tconst stderr = boundedTailV1(outcome.stderr, BROWSER_MAX_STDERR_TAIL_CHARS_V1);\n\tif (stderr.trim() !== \"\") parts.push(`stderr tail:\\n${stderr}`);\n\tconst stdout = boundedTailV1(outcome.stdout, BROWSER_MAX_STDOUT_TAIL_CHARS_V1);\n\tif (stdout.trim() !== \"\") parts.push(`stdout tail:\\n${stdout}`);\n\tparts.push(installGuidanceMessage(depsDir));\n\treturn boundedPreviewV1(parts.join(\"\\n\"), BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1);\n};\n\n/**\n * Installs Playwright into the browser-deps directory:\n * 1. already resolvable -> succeed with `alreadyInstalled: true` (no spawn);\n * 2. otherwise `npm install playwright --prefix <depsDir>`, then\n * `npx playwright install chromium` (cwd = depsDir, whitelisted env plus\n * PLAYWRIGHT_BROWSERS_PATH=<depsDir>/browsers, bounded timeout);\n * 3. post-install resolution check so a silent install failure is reported.\n */\nexport async function setupBrowserDepsPlaywrightV1(\n\toptions: BrowserSetupOptionsV1 = {},\n): Promise<BrowserSetupOutcomeV1> {\n\tconst depsDir =\n\t\toptions.browserDepsDir ?? (options.agentDir === undefined ? undefined : browserDepsDirFor(options.agentDir));\n\tif (depsDir === undefined || depsDir === \"\") {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `${installGuidanceMessage()}\\nThe running host did not declare an agentDir, so browser_setup cannot install anything.`,\n\t\t};\n\t}\n\tconst resolve = options.resolvePlaywright ?? resolvePlaywrightModule;\n\tconst spawn = options.spawnSync ?? defaultSpawn;\n\tconst ensureDir = options.ensureDir ?? ((dir: string) => mkdirSync(dir, { recursive: true }));\n\tconst timeoutMs = options.timeoutMs ?? BROWSER_SETUP_TIMEOUT_MS_V1;\n\tconst resolved = resolve(depsDir);\n\tif (resolved !== undefined) {\n\t\treturn { ok: true, alreadyInstalled: true, log: `playwright resolvable at ${resolved}` };\n\t}\n\tconst log: string[] = [`target: ${depsDir}`];\n\tensureDir(depsDir);\n\tconst env = browserSetupEnvironmentV1(join(depsDir, BROWSER_BROWSERS_DIR_NAME_V1));\n\tconst spawnOptions = {\n\t\tcwd: depsDir,\n\t\tenv,\n\t\tencoding: \"utf8\",\n\t\ttimeout: timeoutMs,\n\t\twindowsHide: true,\n\t} as const;\n\tconst install = spawn(npmCommand(), [\"install\", \"playwright\", \"--prefix\", depsDir], spawnOptions);\n\tif (!spawnSucceeded(install)) {\n\t\treturn { ok: false, message: failureMessage(\"npm install playwright --prefix\", install, depsDir) };\n\t}\n\tlog.push(`npm install playwright --prefix: exit ${describeExit(install)}`);\n\tconst browsers = spawn(npxCommand(), [\"playwright\", \"install\", \"chromium\"], spawnOptions);\n\tif (!spawnSucceeded(browsers)) {\n\t\treturn { ok: false, message: failureMessage(\"npx playwright install chromium\", browsers, depsDir) };\n\t}\n\tlog.push(`npx playwright install chromium: exit ${describeExit(browsers)}`);\n\tconst verified = resolve(depsDir);\n\tif (verified === undefined) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: boundedPreviewV1(\n\t\t\t\t`${installGuidanceMessage(depsDir)}\\nInstallation commands reported success but playwright is still not resolvable from ${depsDir}.`,\n\t\t\t\tBROWSER_MAX_FAILURE_MESSAGE_CHARS_V1,\n\t\t\t),\n\t\t};\n\t}\n\tlog.push(`verification: playwright resolvable at ${verified}`);\n\treturn {\n\t\tok: true,\n\t\talreadyInstalled: false,\n\t\tlog: boundedPreviewV1(log.join(\"\\n\"), BROWSER_MAX_PROGRESS_CHARS_V1),\n\t};\n}\n"]}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Self-installation of Playwright into `<agentDir>/browser-deps` (the\n * `browser_setup` tool's engine).\n *\n * Playwright is deliberately not a dependency of this package: it is the only\n * optional heavy dependency in the product's browser channel, and it is\n * resolved at runtime through `createRequire(<browser-deps>/package.json)`.\n * This module owns the install sequence (`npm install playwright --prefix`\n * then `npx playwright install chromium`), the already-installed probe\n * (module resolvable plus the Chromium executable present), the environment\n * whitelist passed to the spawned installers, bounded progress/preview\n * shaping, and the structured guidance message returned when the module is\n * missing.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport type { BrowserSetupOutcomeV1 } from \"./types.ts\";\n\nexport const BROWSER_SETUP_TIMEOUT_MS_V1 = 600_000;\nexport const BROWSER_DEPS_DIR_NAME_V1 = \"browser-deps\";\nexport const BROWSER_BROWSERS_DIR_NAME_V1 = \"browsers\";\nexport const BROWSER_MAX_PROGRESS_CHARS_V1 = 8192;\nexport const BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1 = 8192;\nexport const BROWSER_MAX_STDERR_TAIL_CHARS_V1 = 4096;\nexport const BROWSER_MAX_STDOUT_TAIL_CHARS_V1 = 2048;\n\nexport type BrowserSpawnResultV1 = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n\treadonly stderr: string;\n};\n\nexport type BrowserSpawnSyncV1 = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly cwd: string;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t\treadonly shell: boolean;\n\t},\n) => BrowserSpawnResultV1;\n\nexport interface BrowserSetupOptionsV1 {\n\t/** Host agent directory; `<agentDir>/browser-deps` is the install target. */\n\treadonly agentDir?: string;\n\t/** Direct override of the install target; wins over `agentDir`. */\n\treadonly browserDepsDir?: string;\n\treadonly timeoutMs?: number;\n\t/** Deterministic seam for tests and brokered hosts. */\n\treadonly spawnSync?: BrowserSpawnSyncV1;\n\t/** Deterministic seam for the \"already installed\" probe. */\n\treadonly resolvePlaywright?: (browserDepsDir: string) => string | undefined;\n\t/** Deterministic seam for the Chromium executable probe. */\n\treadonly resolveChromiumExecutable?: (browserDepsDir: string) => string | undefined;\n\t/** Deterministic seam for the executable existence check (tests inject a fake). */\n\treadonly executableExists?: (path: string) => boolean;\n\t/** Deterministic seam for directory creation (tests inject a no-op). */\n\treadonly ensureDir?: (dir: string) => void;\n}\n\nexport function browserDepsDirFor(agentDir: string): string {\n\treturn join(agentDir, BROWSER_DEPS_DIR_NAME_V1);\n}\n\n/**\n * Resolves the `playwright` module against `<browser-deps>` without touching\n * this package's own dependency graph. Returns the resolved entry path, or\n * undefined when the module is absent. Never throws.\n */\nexport function resolvePlaywrightModule(browserDepsDir: string): string | undefined {\n\tif (browserDepsDir === \"\") return undefined;\n\ttry {\n\t\treturn createRequire(join(browserDepsDir, \"package.json\")).resolve(\"playwright\") as string;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Minimal shape of the installed playwright module used by the browser probe. */\ninterface PlaywrightChromiumProbeV1 {\n\tchromium: {\n\t\texecutablePath(): string;\n\t};\n}\n\n/**\n * Resolves the Chromium executable path through the installed playwright\n * module itself, so the probe targets the exact binary `chromium.launch()`\n * will use. Returns undefined when the module cannot be loaded; the reported\n * path may not exist on disk (the caller checks existence). Never throws.\n */\nexport function resolveChromiumExecutable(browserDepsDir: string): string | undefined {\n\tif (browserDepsDir === \"\") return undefined;\n\ttry {\n\t\t// playwright-core computes its registry directory once at module load\n\t\t// from PLAYWRIGHT_BROWSERS_PATH; when the user has not set it, pin it\n\t\t// before the require to the same isolated directory the install flow\n\t\t// populates and connect() falls back to. An explicit user value wins\n\t\t// (same precedence as connect()'s ??=), so probe and launch agree.\n\t\tconst previous = process.env.PLAYWRIGHT_BROWSERS_PATH;\n\t\tif (previous === undefined) {\n\t\t\tprocess.env.PLAYWRIGHT_BROWSERS_PATH = join(browserDepsDir, BROWSER_BROWSERS_DIR_NAME_V1);\n\t\t}\n\t\ttry {\n\t\t\tconst require = createRequire(join(browserDepsDir, \"package.json\"));\n\t\t\tconst playwright = require(\"playwright\") as PlaywrightChromiumProbeV1;\n\t\t\treturn playwright.chromium.executablePath();\n\t\t} finally {\n\t\t\tif (previous === undefined) delete process.env.PLAYWRIGHT_BROWSERS_PATH;\n\t\t}\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Structured guidance returned when Playwright is missing. The message lists\n * the two exact install commands and points at the `browser_setup` tool; the\n * capability layer passes it through verbatim (it stays under the 2048-char\n * browser error budget).\n */\nexport function installGuidanceMessage(browserDepsDir?: string): string {\n\tconst target =\n\t\tbrowserDepsDir === undefined || browserDepsDir === \"\"\n\t\t\t? join(\"<agentDir>\", BROWSER_DEPS_DIR_NAME_V1)\n\t\t\t: browserDepsDir;\n\treturn [\n\t\t\"Playwright is required by the browser tools but is not installed.\",\n\t\t\"Install it with:\",\n\t\t` npm install playwright --prefix ${target}`,\n\t\t// With an explicit PLAYWRIGHT_BROWSERS_PATH in the environment the bare\n\t\t// command already installs where the driver looks; pinning the prefix\n\t\t// here would override the user's directory (connect honors it via ??=).\n\t\tprocess.env.PLAYWRIGHT_BROWSERS_PATH === undefined\n\t\t\t? ` PLAYWRIGHT_BROWSERS_PATH=${join(target, BROWSER_BROWSERS_DIR_NAME_V1)} npx playwright install chromium`\n\t\t\t: \" npx playwright install chromium\",\n\t\t\"Or call the browser_setup tool to install these dependencies automatically.\",\n\t].join(\"\\n\");\n}\n\nconst BROWSER_SETUP_ENV_KEYS_V1: readonly string[] = [\"PATH\", \"HOME\", \"APPDATA\", \"USER\", \"LOGNAME\", \"TMPDIR\"];\n\n/**\n * Environment for the spawned installers (and, via PLAYWRIGHT_BROWSERS_PATH,\n * for the driver's own browser launches): the whitelist from the macOS AX\n * adapter plus PLAYWRIGHT_BROWSERS_PATH — an explicit user value wins so\n * install and launch resolve browsers from the same directory, otherwise the\n * given default browsers dir is pinned (matches connect()'s ??=). Everything\n * else is dropped.\n */\nexport function browserSetupEnvironmentV1(browsersDir: string): NodeJS.ProcessEnv {\n\tconst env: NodeJS.ProcessEnv = {};\n\tfor (const key of BROWSER_SETUP_ENV_KEYS_V1) {\n\t\tconst value = process.env[key];\n\t\tif (value !== undefined) env[key] = value;\n\t}\n\tenv.PLAYWRIGHT_BROWSERS_PATH = process.env.PLAYWRIGHT_BROWSERS_PATH ?? browsersDir;\n\treturn env;\n}\n\n/** Head/tail bounded preview (75% head, rest tail) mirroring process-exec's shaping. */\nexport function boundedPreviewV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\tconst head = Math.ceil((maximumChars * 3) / 4);\n\tconst tail = Math.max(0, maximumChars - head - 40);\n\tconst omitted = text.length - head - tail;\n\treturn `${text.slice(0, head)}\\n... [${omitted} chars omitted] ...\\n${tail > 0 ? text.slice(text.length - tail) : \"\"}`;\n}\n\n/** Tail-only bounded preview, keeping the original ending intact. */\nexport function boundedTailV1(text: string, maximumChars: number): string {\n\tif (text.length <= maximumChars) return text;\n\treturn `... [${text.length - maximumChars} chars omitted] ...\\n${text.slice(text.length - maximumChars)}`;\n}\n\nconst isWin32 = process.platform === \"win32\";\nconst npmCommand = (): string => (isWin32 ? \"npm.cmd\" : \"npm\");\nconst npxCommand = (): string => (isWin32 ? \"npx.cmd\" : \"npx\");\n\nconst defaultSpawn: BrowserSpawnSyncV1 = (command, args, options) => {\n\tconst outcome = nodeSpawnSync(command, [...args], { ...options });\n\treturn {\n\t\terror: outcome.error,\n\t\tstatus: outcome.status,\n\t\tstdout: typeof outcome.stdout === \"string\" ? outcome.stdout : \"\",\n\t\tstderr: typeof outcome.stderr === \"string\" ? outcome.stderr : \"\",\n\t};\n};\n\nconst describeExit = (outcome: BrowserSpawnResultV1): string => {\n\tif (outcome.status === 0) return \"0\";\n\tif (outcome.status !== null) return String(outcome.status);\n\treturn outcome.error?.message ?? \"no exit status\";\n};\n\nconst spawnSucceeded = (outcome: BrowserSpawnResultV1): boolean => outcome.error === undefined && outcome.status === 0;\n\nconst failureMessage = (label: string, outcome: BrowserSpawnResultV1, depsDir: string): string => {\n\tconst parts = [`${label} failed (exit ${describeExit(outcome)})`];\n\tconst stderr = boundedTailV1(outcome.stderr, BROWSER_MAX_STDERR_TAIL_CHARS_V1);\n\tif (stderr.trim() !== \"\") parts.push(`stderr tail:\\n${stderr}`);\n\tconst stdout = boundedTailV1(outcome.stdout, BROWSER_MAX_STDOUT_TAIL_CHARS_V1);\n\tif (stdout.trim() !== \"\") parts.push(`stdout tail:\\n${stdout}`);\n\tparts.push(installGuidanceMessage(depsDir));\n\treturn boundedPreviewV1(parts.join(\"\\n\"), BROWSER_MAX_FAILURE_MESSAGE_CHARS_V1);\n};\n\n/**\n * Installs Playwright into the browser-deps directory:\n * 1. already resolvable with the Chromium executable present -> succeed with\n * `alreadyInstalled: true` (no spawn); a module without its browser binary\n * does not short-circuit;\n * 2. otherwise `npm install playwright --prefix <depsDir>`, then\n * `npx playwright install chromium` (cwd = depsDir, whitelisted env plus\n * PLAYWRIGHT_BROWSERS_PATH — explicit user value wins, else\n * <depsDir>/browsers — bounded timeout);\n * 3. post-install verification (module resolvable plus the Chromium\n * executable present, the same probe as step 1) so a silent install\n * failure is reported.\n */\nexport async function setupBrowserDepsPlaywrightV1(\n\toptions: BrowserSetupOptionsV1 = {},\n): Promise<BrowserSetupOutcomeV1> {\n\tconst depsDir =\n\t\toptions.browserDepsDir ?? (options.agentDir === undefined ? undefined : browserDepsDirFor(options.agentDir));\n\tif (depsDir === undefined || depsDir === \"\") {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `${installGuidanceMessage()}\\nThe running host did not declare an agentDir, so browser_setup cannot install anything.`,\n\t\t};\n\t}\n\tconst resolve = options.resolvePlaywright ?? resolvePlaywrightModule;\n\tconst resolveChromium = options.resolveChromiumExecutable ?? resolveChromiumExecutable;\n\tconst executableExists = options.executableExists ?? existsSync;\n\tconst spawn = options.spawnSync ?? defaultSpawn;\n\tconst ensureDir = options.ensureDir ?? ((dir: string) => mkdirSync(dir, { recursive: true }));\n\tconst timeoutMs = options.timeoutMs ?? BROWSER_SETUP_TIMEOUT_MS_V1;\n\tconst resolved = resolve(depsDir);\n\t// Already-installed requires BOTH the module and the browser binary:\n\t// a past run that installed the package but failed the chromium download\n\t// must re-run the install instead of reporting success the driver cannot\n\t// honor (launch would fail with \"Executable doesn't exist\").\n\tconst chromiumExecutable = resolved === undefined ? undefined : resolveChromium(depsDir);\n\tif (resolved !== undefined && chromiumExecutable !== undefined && executableExists(chromiumExecutable)) {\n\t\treturn { ok: true, alreadyInstalled: true, log: `playwright resolvable at ${resolved}` };\n\t}\n\tconst log: string[] = [`target: ${depsDir}`];\n\tensureDir(depsDir);\n\tconst env = browserSetupEnvironmentV1(join(depsDir, BROWSER_BROWSERS_DIR_NAME_V1));\n\tconst spawnOptions = {\n\t\tcwd: depsDir,\n\t\tenv,\n\t\tencoding: \"utf8\",\n\t\ttimeout: timeoutMs,\n\t\twindowsHide: true,\n\t\t// Node >= 18.20 refuses to spawn .cmd shims directly (EINVAL), so win32\n\t\t// runs through cmd.exe, which joins argv with spaces and never quotes;\n\t\t// --prefix carries an arbitrary user path (homedir may contain spaces),\n\t\t// so quote it explicitly on win32 (node-package-install-host precedent).\n\t\tshell: isWin32,\n\t} as const;\n\tconst install = spawn(\n\t\tnpmCommand(),\n\t\t[\"install\", \"playwright\", \"--prefix\", isWin32 ? `\"${depsDir}\"` : depsDir],\n\t\tspawnOptions,\n\t);\n\tif (!spawnSucceeded(install)) {\n\t\treturn { ok: false, message: failureMessage(\"npm install playwright --prefix\", install, depsDir) };\n\t}\n\tlog.push(`npm install playwright --prefix: exit ${describeExit(install)}`);\n\tconst browsers = spawn(npxCommand(), [\"playwright\", \"install\", \"chromium\"], spawnOptions);\n\tif (!spawnSucceeded(browsers)) {\n\t\treturn { ok: false, message: failureMessage(\"npx playwright install chromium\", browsers, depsDir) };\n\t}\n\tlog.push(`npx playwright install chromium: exit ${describeExit(browsers)}`);\n\tconst verified = resolve(depsDir);\n\tif (verified === undefined) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: boundedPreviewV1(\n\t\t\t\t`${installGuidanceMessage(depsDir)}\\nInstallation commands reported success but playwright is still not resolvable from ${depsDir}.`,\n\t\t\t\tBROWSER_MAX_FAILURE_MESSAGE_CHARS_V1,\n\t\t\t),\n\t\t};\n\t}\n\tlog.push(`verification: playwright resolvable at ${verified}`);\n\t// The install commands can exit 0 while the chromium download silently\n\t// failed (e.g. a blocked download host), so verify the exact binary\n\t// chromium.launch() will use — the same probe the already-installed\n\t// check above runs.\n\tconst verifiedExecutable = resolveChromium(depsDir);\n\tif (verifiedExecutable === undefined || !executableExists(verifiedExecutable)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: boundedPreviewV1(\n\t\t\t\t`${installGuidanceMessage(depsDir)}\\nInstallation commands reported success but the Chromium executable is still missing${\n\t\t\t\t\tverifiedExecutable === undefined ? \".\" : ` (expected at ${verifiedExecutable}).`\n\t\t\t\t}`,\n\t\t\t\tBROWSER_MAX_FAILURE_MESSAGE_CHARS_V1,\n\t\t\t),\n\t\t};\n\t}\n\tlog.push(`verification: chromium executable at ${verifiedExecutable}`);\n\treturn {\n\t\tok: true,\n\t\talreadyInstalled: false,\n\t\tlog: boundedPreviewV1(log.join(\"\\n\"), BROWSER_MAX_PROGRESS_CHARS_V1),\n\t};\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-agent-forge/plugin-browser-playwright",
3
- "version": "0.88.5",
3
+ "version": "0.88.7",
4
4
  "description": "Built-in Playwright browser capability plugin for Agent Forge (seventeen prewritten browser tools: navigate/snapshot/locator_action with 13 actions/assert/collect/console/network/performance/wait/drag/mouse/press_key/tabs/storage/viewport, evaluate as the escape hatch, self-installing browser_setup; Playwright is never bundled)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "typebox": "1.3.27",
51
- "@ai-agent-forge/plugin-sdk": "^0.88.5"
51
+ "@ai-agent-forge/plugin-sdk": "^0.88.7"
52
52
  },
53
53
  "devDependencies": {
54
54
  "shx": "0.4.0",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "agent-forge.plugin.browser-playwright",
3
- "version": "0.88.5",
3
+ "version": "0.88.7",
4
4
  "apiVersion": "1-draft",
5
5
  "entry": "./dist/entry.js",
6
6
  "hostVersion": ">=0.84.4",