pi-agent-browser-native 0.2.43 → 0.2.45

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 (66) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +26 -16
  3. package/docs/ARCHITECTURE.md +12 -10
  4. package/docs/COMMAND_REFERENCE.md +49 -27
  5. package/docs/ELECTRON.md +1 -1
  6. package/docs/RELEASE.md +16 -9
  7. package/docs/REQUIREMENTS.md +6 -3
  8. package/docs/SUPPORT_MATRIX.md +18 -14
  9. package/docs/TOOL_CONTRACT.md +87 -46
  10. package/docs/platform-smoke.md +15 -9
  11. package/extensions/agent-browser/index.ts +29 -445
  12. package/extensions/agent-browser/lib/bash-guard.ts +205 -0
  13. package/extensions/agent-browser/lib/electron/cdp.ts +69 -0
  14. package/extensions/agent-browser/lib/electron/cleanup.ts +5 -58
  15. package/extensions/agent-browser/lib/electron/discovery.ts +2 -9
  16. package/extensions/agent-browser/lib/electron/launch.ts +11 -65
  17. package/extensions/agent-browser/lib/electron/text.ts +13 -0
  18. package/extensions/agent-browser/lib/fs-utils.ts +18 -0
  19. package/extensions/agent-browser/lib/input-modes/job.ts +207 -21
  20. package/extensions/agent-browser/lib/input-modes/params.ts +17 -7
  21. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +22 -2
  22. package/extensions/agent-browser/lib/input-modes/types.ts +5 -1
  23. package/extensions/agent-browser/lib/input-modes.ts +1 -0
  24. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +82 -11
  25. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +153 -30
  26. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +53 -2
  27. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +1 -0
  28. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +751 -32
  29. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +38 -7
  30. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -46
  31. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +10 -1
  32. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +28 -1
  33. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +1 -6
  34. package/extensions/agent-browser/lib/orchestration/input-plan.ts +15 -3
  35. package/extensions/agent-browser/lib/orchestration/output-file.ts +86 -0
  36. package/extensions/agent-browser/lib/pi-tool-rendering.ts +231 -0
  37. package/extensions/agent-browser/lib/playbook.ts +26 -26
  38. package/extensions/agent-browser/lib/process.ts +1 -1
  39. package/extensions/agent-browser/lib/prompt-policy.ts +1 -18
  40. package/extensions/agent-browser/lib/results/artifact-manifest.ts +1 -4
  41. package/extensions/agent-browser/lib/results/artifact-state.ts +7 -3
  42. package/extensions/agent-browser/lib/results/contracts.ts +6 -2
  43. package/extensions/agent-browser/lib/results/envelope.ts +11 -2
  44. package/extensions/agent-browser/lib/results/network-routes.ts +7 -4
  45. package/extensions/agent-browser/lib/results/network.ts +7 -1
  46. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +88 -20
  47. package/extensions/agent-browser/lib/results/presentation/batch.ts +84 -12
  48. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +81 -26
  49. package/extensions/agent-browser/lib/results/presentation/errors.ts +13 -0
  50. package/extensions/agent-browser/lib/results/presentation/registry.ts +60 -0
  51. package/extensions/agent-browser/lib/results/presentation.ts +10 -1
  52. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +16 -5
  53. package/extensions/agent-browser/lib/results/snapshot.ts +2 -0
  54. package/extensions/agent-browser/lib/runtime.ts +10 -1
  55. package/extensions/agent-browser/lib/session-page-state.ts +15 -6
  56. package/extensions/agent-browser/lib/web-search.ts +1 -1
  57. package/package.json +5 -5
  58. package/platform-smoke.config.mjs +15 -3
  59. package/scripts/doctor.mjs +70 -1
  60. package/scripts/platform-smoke/build-ubuntu-image.mjs +25 -0
  61. package/scripts/platform-smoke/crabbox-runner.mjs +62 -30
  62. package/scripts/platform-smoke/doctor.mjs +28 -11
  63. package/scripts/platform-smoke/linux-image/Dockerfile +3 -5
  64. package/scripts/platform-smoke/targets.mjs +60 -22
  65. package/scripts/platform-smoke.mjs +1 -0
  66. package/extensions/agent-browser/lib/orchestration/browser-run/browser-action-model.ts +0 -154
@@ -1,5 +1,6 @@
1
1
  /** Target/suite runner for pi-agent-browser-native platform smoke. */
2
2
 
3
+ import { execFileSync } from "node:child_process";
3
4
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
5
  import { dirname, resolve } from "node:path";
5
6
 
@@ -14,7 +15,8 @@ import {
14
15
  writeManifest,
15
16
  writeSummary,
16
17
  } from "./artifacts.mjs";
17
- import { cleanupStaleTargetState, runOnLease, stopLease, warmupLease } from "./crabbox-runner.mjs";
18
+ import { CAPABILITY_BASELINE } from "../agent-browser-capability-baseline.mjs";
19
+ import { cleanupStaleTargetState, crabboxBin, describeTarget, runOnLease, stopLease, warmupLease } from "./crabbox-runner.mjs";
18
20
 
19
21
  export function platformFor(targetName) {
20
22
  return targetName === "windows-native" ? "powershell" : "posix";
@@ -38,6 +40,45 @@ function authEnvAllowList(config = {}) {
38
40
  return names.map((name) => String(name).trim()).filter(Boolean);
39
41
  }
40
42
 
43
+ function packageVersion() {
44
+ try {
45
+ return JSON.parse(readFileSync("package.json", "utf8")).version ?? null;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function crabboxVersion() {
52
+ try {
53
+ return execFileSync(crabboxBin(), ["--version"], { encoding: "utf8", stdio: "pipe", timeout: 10_000 }).trim().split(/\r?\n/)[0] ?? null;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function targetEvidence(config, targetName, runId, slug) {
60
+ const target = describeTarget(targetName, config);
61
+ return {
62
+ targetName,
63
+ platform: platformFor(targetName),
64
+ runId,
65
+ slug,
66
+ packageName: config.packageName,
67
+ packageVersion: packageVersion(),
68
+ crabbox: {
69
+ binary: crabboxBin(),
70
+ version: crabboxVersion(),
71
+ provider: target.provider,
72
+ target: target.crabboxTarget,
73
+ workRoot: target.workRoot,
74
+ image: target.image,
75
+ windowsMode: target.windowsMode,
76
+ sourceVm: target.sourceVm,
77
+ snapshot: target.snapshot,
78
+ },
79
+ };
80
+ }
81
+
41
82
  function writeRedacted(path, text, secretValues) {
42
83
  writeFileSync(path, redactSecrets(text ?? "", secretValues));
43
84
  }
@@ -141,12 +182,11 @@ function finalizeSuite(suiteDir, checks, summary, expectedFiles) {
141
182
  return { assertions: finalAssertions, manifest: writeManifest(suiteDir, [...expectedFiles, "failures.md"]) };
142
183
  }
143
184
 
144
- export function createLeaseCleanupResult(config, targetName, leaseId, stopResult, staleCleanupResult = null) {
185
+ export function createLeaseCleanupResult(config, targetName, leaseId, stopResult, staleCleanupResult = null, runId = makeRunId()) {
145
186
  const suiteName = "lease-cleanup";
146
- const runId = makeRunId();
147
187
  const suiteDir = createSuiteDir(config.artifactRoot, runId, targetName, suiteName);
148
188
  const secretValues = collectSecretValues(authEnvAllowList(config));
149
- writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify({ targetName, platform: platformFor(targetName), runId, slug: `${config.packageName}-${targetName}` }, null, 2));
189
+ writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify(targetEvidence(config, targetName, runId, `${config.packageName}-${targetName}`), null, 2));
150
190
  writeFileSync(resolve(suiteDir, "suite.json"), JSON.stringify({ suiteName, leaseId, modelCalls: 0 }, null, 2));
151
191
  writeCommand(suiteDir, `crabbox stop ${targetName} --id ${leaseId}`);
152
192
  writeExitCode(suiteDir, stopResult.code, stopResult.signal);
@@ -180,16 +220,15 @@ export function createLeaseCleanupResult(config, targetName, leaseId, stopResult
180
220
  return { ok: assertions.ok, suiteDir, assertions };
181
221
  }
182
222
 
183
- export function createLeaseCleanupFailureResult(config, targetName, leaseId, stopResult) {
184
- return createLeaseCleanupResult(config, targetName, leaseId, stopResult);
223
+ export function createLeaseCleanupFailureResult(config, targetName, leaseId, stopResult, runId) {
224
+ return createLeaseCleanupResult(config, targetName, leaseId, stopResult, null, runId);
185
225
  }
186
226
 
187
- export function createLeaseWarmupFailureResult(config, targetName, warmupResult) {
227
+ export function createLeaseWarmupFailureResult(config, targetName, warmupResult, runId = makeRunId()) {
188
228
  const suiteName = "lease-warmup";
189
- const runId = makeRunId();
190
229
  const suiteDir = createSuiteDir(config.artifactRoot, runId, targetName, suiteName);
191
230
  const secretValues = collectSecretValues(authEnvAllowList(config));
192
- writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify({ targetName, platform: platformFor(targetName), runId, slug: `${config.packageName}-${targetName}` }, null, 2));
231
+ writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify(targetEvidence(config, targetName, runId, `${config.packageName}-${targetName}`), null, 2));
193
232
  writeFileSync(resolve(suiteDir, "suite.json"), JSON.stringify({ suiteName, modelCalls: 0 }, null, 2));
194
233
  writeCommand(suiteDir, `crabbox warmup ${targetName}`);
195
234
  writeExitCode(suiteDir, warmupResult.code, warmupResult.signal);
@@ -262,7 +301,7 @@ export function buildPlatformBuildCommand(targetName, packageName = "pi-agent-br
262
301
  return lines.join("\n");
263
302
  }
264
303
 
265
- export function buildBrowserDogfoodCommand(targetName, agentBrowserVersion = "0.27.1") {
304
+ export function buildBrowserDogfoodCommand(targetName, agentBrowserVersion = CAPABILITY_BASELINE.targetVersion) {
266
305
  if (platformFor(targetName) === "powershell") {
267
306
  return `powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\\scripts\\platform-smoke\\browser-dogfood-windows.ps1 -AgentBrowserVersion ${psSingleQuote(agentBrowserVersion)}`;
268
307
  }
@@ -301,14 +340,13 @@ export function buildBrowserDogfoodCommand(targetName, agentBrowserVersion = "0.
301
340
  return lines.join("\n");
302
341
  }
303
342
 
304
- async function runBrowserDogfoodSuite(config, targetName, suiteName, leaseSession) {
305
- const runId = makeRunId();
343
+ async function runBrowserDogfoodSuite(config, targetName, suiteName, leaseSession, runId = makeRunId()) {
306
344
  const suiteDir = createSuiteDir(config.artifactRoot, runId, targetName, suiteName);
307
345
  const startedAt = Date.now();
308
346
  const platform = platformFor(targetName);
309
347
  const slug = `${config.packageName}-${targetName}`;
310
348
  const command = buildBrowserDogfoodCommand(targetName, config.agentBrowserVersion);
311
- writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify({ targetName, platform, runId, slug }, null, 2));
349
+ writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify(targetEvidence(config, targetName, runId, slug), null, 2));
312
350
  writeFileSync(resolve(suiteDir, "suite.json"), JSON.stringify({ suiteName, modelCalls: 0, realBrowser: true }, null, 2));
313
351
  writeCommand(suiteDir, command);
314
352
 
@@ -367,15 +405,14 @@ async function runBrowserDogfoodSuite(config, targetName, suiteName, leaseSessio
367
405
  return { ok: assertions.ok, suiteDir, assertions };
368
406
  }
369
407
 
370
- async function runPlatformBuildSuite(config, targetName, suiteName, leaseSession) {
371
- const runId = makeRunId();
408
+ async function runPlatformBuildSuite(config, targetName, suiteName, leaseSession, runId = makeRunId()) {
372
409
  const suiteDir = createSuiteDir(config.artifactRoot, runId, targetName, suiteName);
373
410
  const startedAt = Date.now();
374
411
  const platform = platformFor(targetName);
375
412
  const slug = `${config.packageName}-${targetName}`;
376
413
  const command = buildPlatformBuildCommand(targetName, config.packageName, config.nodeValidationMajor);
377
414
  mkdirSync(dirname(suiteDir), { recursive: true });
378
- writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify({ targetName, platform, runId, slug }, null, 2));
415
+ writeFileSync(resolve(suiteDir, "target.json"), JSON.stringify(targetEvidence(config, targetName, runId, slug), null, 2));
379
416
  writeFileSync(resolve(suiteDir, "suite.json"), JSON.stringify({ suiteName, modelCalls: 0 }, null, 2));
380
417
  writeCommand(suiteDir, command);
381
418
 
@@ -436,17 +473,18 @@ async function runPlatformBuildSuite(config, targetName, suiteName, leaseSession
436
473
  return { ok: assertions.ok, suiteDir, assertions };
437
474
  }
438
475
 
439
- export async function runTargetSuite(config, targetName, suiteName, leaseSession) {
440
- if (suiteName === "platform-build") return await runPlatformBuildSuite(config, targetName, suiteName, leaseSession);
441
- if (suiteName === "browser-dogfood-smoke") return await runBrowserDogfoodSuite(config, targetName, suiteName, leaseSession);
476
+ export async function runTargetSuite(config, targetName, suiteName, leaseSession, runId) {
477
+ if (suiteName === "platform-build") return await runPlatformBuildSuite(config, targetName, suiteName, leaseSession, runId);
478
+ if (suiteName === "browser-dogfood-smoke") return await runBrowserDogfoodSuite(config, targetName, suiteName, leaseSession, runId);
442
479
  throw new Error(`unknown suite: ${suiteName}`);
443
480
  }
444
481
 
445
482
  export async function runTargetSuites(config, targetName, suiteNames) {
446
483
  const slug = `${config.packageName}-${targetName}`;
484
+ const runId = makeRunId();
447
485
  const lease = await warmupLease(targetName, slug, config);
448
486
  if (!lease.ok) {
449
- const warmupFailure = createLeaseWarmupFailureResult(config, targetName, lease);
487
+ const warmupFailure = createLeaseWarmupFailureResult(config, targetName, lease, runId);
450
488
  return { ok: false, results: [warmupFailure] };
451
489
  }
452
490
  const results = [];
@@ -455,7 +493,7 @@ export async function runTargetSuites(config, targetName, suiteNames) {
455
493
  try {
456
494
  let sync = true;
457
495
  for (const suiteName of suiteNames) {
458
- const result = await runTargetSuite(config, targetName, suiteName, { ...lease, sync });
496
+ const result = await runTargetSuite(config, targetName, suiteName, { ...lease, sync }, runId);
459
497
  results.push(result);
460
498
  sync = false;
461
499
  if (!result.ok) break;
@@ -465,7 +503,7 @@ export async function runTargetSuites(config, targetName, suiteNames) {
465
503
  staleCleanupResult = await cleanupStaleTargetState(targetName, config);
466
504
  }
467
505
  if (stopResult) {
468
- results.push(createLeaseCleanupResult(config, targetName, lease.leaseId, stopResult, staleCleanupResult));
506
+ results.push(createLeaseCleanupResult(config, targetName, lease.leaseId, stopResult, staleCleanupResult, runId));
469
507
  }
470
508
  return { ok: results.every((result) => result.ok), results };
471
509
  }
@@ -61,6 +61,7 @@ Environment:
61
61
  PLATFORM_SMOKE_MAC_HOST macOS SSH host; default localhost
62
62
  PLATFORM_SMOKE_MAC_USER macOS SSH user; default $USER
63
63
  PLATFORM_SMOKE_MAC_WORK_ROOT macOS Crabbox work root
64
+ PLATFORM_SMOKE_MAC_PORT macOS SSH port; default 22
64
65
  PLATFORM_SMOKE_UBUNTU_IMAGE Ubuntu local-container image; default pi-agent-browser-native-platform:node24-agent-browser0.27.1
65
66
  PLATFORM_SMOKE_WINDOWS_VM Parallels Windows template VM
66
67
  PLATFORM_SMOKE_WINDOWS_SNAPSHOT Parallels snapshot name
@@ -1,154 +0,0 @@
1
- /**
2
- * Purpose: Normalize planned browser argv into a small action model for prompt-derived guards.
3
- * Responsibilities: Map command tokens and batch stdin steps to click-like and keyboard-submit actions with target labels.
4
- * Scope: Best-effort finalizing-action detection only; does not model eval, generic fill/type, or non-Enter keyboard flows.
5
- */
6
-
7
- import type { SessionRefSnapshot } from "../../session-page-state.js";
8
- import { parseValidBatchStepEntries } from "../batch-stdin.js";
9
-
10
- const FINAL_ACTION_PATTERN = /\b(?:finish|place\s+(?:the\s+)?order|submit\s+(?:the\s+)?order|complete\s+(?:the\s+)?order|confirm\s+(?:the\s+)?order|purchase|buy\s+now|pay\s+now|finali[sz]e|submit\s+payment|checkout\s+complete)\b/i;
11
-
12
- const CLICK_LIKE_COMMANDS = new Set(["click", "dblclick", "tap"]);
13
- const FIND_CLICK_ACTIONS = new Set(["click", "dblclick", "tap"]);
14
- const KEYBOARD_SUBMIT_KEYS = new Set(["enter", "return"]);
15
-
16
- export type BrowserFinalizingActionKind = "click-like" | "keyboard-submit";
17
-
18
- export interface BrowserFinalizingAction {
19
- command: string[];
20
- kind: BrowserFinalizingActionKind;
21
- stepIndex?: number;
22
- targetLabel?: string;
23
- }
24
-
25
- export const STOP_BOUNDARY_GUARD_SCOPE = {
26
- covered: [
27
- "standalone click, dblclick, and tap",
28
- "find … click|dblclick|tap",
29
- "batch steps with the click-like shapes above",
30
- "press <key> and key <key> when key is Enter or Return",
31
- ],
32
- excluded: [
33
- "eval --stdin and other scripted activation",
34
- "fill, type, select, drag, and upload without an explicit click-like command",
35
- "keyboard type/inserttext and keyboard shortcuts other than Enter/Return",
36
- "semanticAction and job/qa compiled plans unless their batch stdin contains a covered step",
37
- ],
38
- } as const;
39
-
40
- function normalizeTargetText(value: string): string {
41
- return value
42
- .replace(/[_-]+/g, " ")
43
- .replace(/[\[\]{}()#.'\"=:/]+/g, " ")
44
- .replace(/\s+/g, " ")
45
- .trim();
46
- }
47
-
48
- export function matchesFinalActionLabel(value: string | undefined): boolean {
49
- return value !== undefined && FINAL_ACTION_PATTERN.test(normalizeTargetText(value));
50
- }
51
-
52
- function parseRefId(value: string | undefined): string | undefined {
53
- if (!value) return undefined;
54
- const trimmed = value.trim();
55
- const candidate = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
56
- return /^e\d+$/.test(candidate) ? candidate : undefined;
57
- }
58
-
59
- function getRefTargetLabel(refSnapshot: SessionRefSnapshot | undefined, refId: string | undefined): string | undefined {
60
- if (!refId) return undefined;
61
- const ref = refSnapshot?.refs?.[refId];
62
- return ref ? [ref.role, ref.name].filter(Boolean).join(" ") : undefined;
63
- }
64
-
65
- function getFlagValue(tokens: string[], flag: string): string | undefined {
66
- for (const [index, token] of tokens.entries()) {
67
- if (token === flag) return tokens[index + 1];
68
- if (token.startsWith(`${flag}=`)) return token.slice(flag.length + 1);
69
- }
70
- return undefined;
71
- }
72
-
73
- function getClickLikeTargetLabel(command: string[], refSnapshot: SessionRefSnapshot | undefined): string | undefined {
74
- const target = command[1];
75
- return getRefTargetLabel(refSnapshot, parseRefId(target)) ?? target;
76
- }
77
-
78
- function getFindClickTargetLabel(command: string[]): string | undefined {
79
- if (command[0] !== "find") return undefined;
80
- const actionIndex = command.findIndex((token, index) => index >= 3 && FIND_CLICK_ACTIONS.has(token));
81
- if (actionIndex === -1) return undefined;
82
- return getFlagValue(command, "--name") ?? command[2];
83
- }
84
-
85
- function getKeyboardSubmitKey(command: string[]): string | undefined {
86
- const commandName = command[0];
87
- if (commandName === "press" || commandName === "key") return command[1];
88
- return undefined;
89
- }
90
-
91
- function collectActionsFromCommand(command: string[], refSnapshot: SessionRefSnapshot | undefined, stepIndex?: number): BrowserFinalizingAction[] {
92
- const actions: BrowserFinalizingAction[] = [];
93
- if (CLICK_LIKE_COMMANDS.has(command[0] ?? "")) {
94
- actions.push({
95
- command,
96
- kind: "click-like",
97
- stepIndex,
98
- targetLabel: getClickLikeTargetLabel(command, refSnapshot),
99
- });
100
- return actions;
101
- }
102
- if (command[0] === "find") {
103
- const actionIndex = command.findIndex((token, index) => index >= 3 && FIND_CLICK_ACTIONS.has(token));
104
- if (actionIndex !== -1) {
105
- actions.push({
106
- command,
107
- kind: "click-like",
108
- stepIndex,
109
- targetLabel: getFindClickTargetLabel(command),
110
- });
111
- }
112
- return actions;
113
- }
114
- const submitKey = getKeyboardSubmitKey(command)?.trim().toLowerCase();
115
- if (submitKey && KEYBOARD_SUBMIT_KEYS.has(submitKey)) {
116
- actions.push({
117
- command,
118
- kind: "keyboard-submit",
119
- stepIndex,
120
- targetLabel: submitKey,
121
- });
122
- }
123
- return actions;
124
- }
125
-
126
- export function collectBrowserFinalizingActions(options: {
127
- commandTokens: string[];
128
- refSnapshot?: SessionRefSnapshot;
129
- stdin?: string;
130
- }): BrowserFinalizingAction[] {
131
- const actions = collectActionsFromCommand(options.commandTokens, options.refSnapshot);
132
- if (options.commandTokens[0] !== "batch") return actions;
133
- for (const { index, step } of parseValidBatchStepEntries(options.stdin)) {
134
- actions.push(...collectActionsFromCommand(step, options.refSnapshot, index));
135
- }
136
- return actions;
137
- }
138
-
139
- export function shouldBlockFinalizingAction(action: BrowserFinalizingAction): boolean {
140
- if (action.kind === "keyboard-submit") return true;
141
- return matchesFinalActionLabel(action.targetLabel);
142
- }
143
-
144
- export function findBlockedFinalizingAction(options: {
145
- commandTokens: string[];
146
- refSnapshot?: SessionRefSnapshot;
147
- stdin?: string;
148
- }): BrowserFinalizingAction | undefined {
149
- for (const action of collectBrowserFinalizingActions(options)) {
150
- if (!shouldBlockFinalizingAction(action)) continue;
151
- return action;
152
- }
153
- return undefined;
154
- }