pi-agent-browser-native 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/README.md +63 -20
- package/dist/extensions/agent-browser/index.js +787 -105
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +44 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -2
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -4
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +22 -23
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +8 -0
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -1
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -36
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +47 -29
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -24
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +26 -25
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +17 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +129 -32
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +191 -55
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +57 -29
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +15 -11
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
- package/dist/extensions/agent-browser/lib/playbook.js +18 -15
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -4
- package/dist/extensions/agent-browser/lib/process.js +131 -41
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +62 -5
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
- package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -1
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -11
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -3
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -8
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
- package/dist/extensions/agent-browser/lib/runtime.js +116 -39
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -10
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +40 -21
- package/docs/COMMAND_REFERENCE.md +90 -35
- package/docs/RELEASE.md +3 -3
- package/docs/REQUIREMENTS.md +4 -2
- package/docs/SUPPORT_MATRIX.md +26 -19
- package/docs/TOOL_CONTRACT.md +93 -52
- package/package.json +3 -1
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +7 -6
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
|
-
import { findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
|
|
4
|
-
import {
|
|
3
|
+
import { extractUpstreamCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
|
|
4
|
+
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
|
|
5
|
+
import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, getBooleanFlagValue, GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, PREVALIDATED_VALUE_FLAGS, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
|
|
5
6
|
import { needsManagedSession } from "./command-policy.js";
|
|
6
7
|
import { isWrapperManagedSessionName, redactManagedSessionRestoreKeys } from "./managed-session-capabilities.js";
|
|
7
|
-
import { isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
8
|
+
import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
8
9
|
import { hasLaunchScopedFlagToken, LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL, } from "./launch-scoped-flags.js";
|
|
9
10
|
import { MANAGED_SESSION_NAME_PREFIX, } from "./managed-session-restore.js";
|
|
10
|
-
export { extractCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo } from "./argv-descriptor.js";
|
|
11
|
+
export { extractCommandTokens, extractUpstreamCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, parseWaitCommandTokens } from "./argv-descriptor.js";
|
|
11
12
|
import { isRecord } from "./parsing.js";
|
|
13
|
+
import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
|
|
14
|
+
import { TARGET_AGENT_BROWSER_VERSION } from "./upstream-version.js";
|
|
12
15
|
const OPENAI_HEADLESS_COMPAT_HOSTS = new Set(["chat.com", "chat.openai.com", "chatgpt.com"]);
|
|
13
16
|
const CLOUDFLARE_HEADLESS_COMPAT_HOST = "dash.cloudflare.com";
|
|
14
17
|
const AGENT_BROWSER_IDLE_TIMEOUT_ENV = "AGENT_BROWSER_IDLE_TIMEOUT_MS";
|
|
@@ -28,6 +31,7 @@ const DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM = {
|
|
|
28
31
|
};
|
|
29
32
|
const FALLBACK_HEADLESS_COMPAT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
|
|
30
33
|
const SHELL_OPERATOR_TOKENS = new Set(["&&", "||", "|", ";", ">", ">>", "<"]);
|
|
34
|
+
const ANDROID_SESSION_NAME_IDENTITY_LENGTH = 20;
|
|
31
35
|
const MAX_PROJECT_SLUG_LENGTH = 24;
|
|
32
36
|
const SESSION_NAME_CWD_HASH_LENGTH = 8;
|
|
33
37
|
const SESSION_NAME_SESSION_ID_LENGTH = 12;
|
|
@@ -311,7 +315,7 @@ function parseTimeoutMs(rawValue, minimumValue) {
|
|
|
311
315
|
}
|
|
312
316
|
return parsedValue;
|
|
313
317
|
}
|
|
314
|
-
export function getImplicitSessionIdleTimeoutMs(env =
|
|
318
|
+
export function getImplicitSessionIdleTimeoutMs(env = getAgentBrowserProcessEnvironment()) {
|
|
315
319
|
return parseTimeoutMs(env[IMPLICIT_SESSION_IDLE_TIMEOUT_ENV], 0) ??
|
|
316
320
|
parseTimeoutMs(env[AGENT_BROWSER_IDLE_TIMEOUT_ENV], 0) ??
|
|
317
321
|
DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS;
|
|
@@ -333,7 +337,7 @@ function getUnsupportedLeadingIdentityAssignment(args) {
|
|
|
333
337
|
}
|
|
334
338
|
return undefined;
|
|
335
339
|
}
|
|
336
|
-
export function getImplicitSessionCloseTimeoutMs(env =
|
|
340
|
+
export function getImplicitSessionCloseTimeoutMs(env = getAgentBrowserProcessEnvironment()) {
|
|
337
341
|
return parseTimeoutMs(env[IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV], 0) ?? DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS;
|
|
338
342
|
}
|
|
339
343
|
export function resolveManagedSessionState(options) {
|
|
@@ -381,15 +385,18 @@ function getManagedSessionRestoreRank(options) {
|
|
|
381
385
|
function getRestorableManagedSessionName(value, fallbackSessionName) {
|
|
382
386
|
return typeof value === "string" && isRestorableManagedSessionName(value, fallbackSessionName) ? value : undefined;
|
|
383
387
|
}
|
|
384
|
-
function
|
|
388
|
+
function getElectronCleanupClosedManagedSessions(details, fallbackSessionName) {
|
|
385
389
|
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
386
390
|
const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
|
|
387
391
|
const results = Array.isArray(cleanup?.results) ? cleanup.results : [];
|
|
388
|
-
const
|
|
392
|
+
const closedSessions = [];
|
|
389
393
|
for (const result of results) {
|
|
390
394
|
if (!isRecord(result) || !Array.isArray(result.steps))
|
|
391
395
|
continue;
|
|
392
396
|
const record = isRecord(result.record) ? result.record : undefined;
|
|
397
|
+
const fallbackNamespace = typeof record?.namespace === "string"
|
|
398
|
+
? record.namespace
|
|
399
|
+
: typeof details.namespace === "string" ? details.namespace : undefined;
|
|
393
400
|
for (const step of result.steps) {
|
|
394
401
|
if (!isRecord(step) || step.resource !== "managed-session")
|
|
395
402
|
continue;
|
|
@@ -397,11 +404,12 @@ function getElectronCleanupClosedManagedSessionNames(details, fallbackSessionNam
|
|
|
397
404
|
continue;
|
|
398
405
|
const sessionName = getRestorableManagedSessionName(step.sessionName, fallbackSessionName)
|
|
399
406
|
?? getRestorableManagedSessionName(record?.sessionName, fallbackSessionName);
|
|
407
|
+
const namespace = typeof step.namespace === "string" ? step.namespace : fallbackNamespace;
|
|
400
408
|
if (sessionName)
|
|
401
|
-
|
|
409
|
+
closedSessions.push({ namespace, sessionName });
|
|
402
410
|
}
|
|
403
411
|
}
|
|
404
|
-
return
|
|
412
|
+
return closedSessions;
|
|
405
413
|
}
|
|
406
414
|
export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName) {
|
|
407
415
|
const restoreDisabledIdentities = new Map();
|
|
@@ -441,16 +449,23 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
441
449
|
if (isPlainTextInspectionArgs(args)) {
|
|
442
450
|
continue;
|
|
443
451
|
}
|
|
444
|
-
for (const
|
|
445
|
-
applyManagedClose(sessionName);
|
|
452
|
+
for (const cleanupSession of getElectronCleanupClosedManagedSessions(details, fallbackSessionName)) {
|
|
453
|
+
applyManagedClose(cleanupSession.sessionName, cleanupSession.namespace);
|
|
446
454
|
}
|
|
447
455
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
448
456
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
449
457
|
const namespace = canonicalizeAgentBrowserNamespace(typeof details.namespace === "string" ? details.namespace : undefined);
|
|
450
458
|
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
451
459
|
const usedImplicitSession = details.usedImplicitSession === true;
|
|
460
|
+
const commandTokens = extractUpstreamCommandTokens(args);
|
|
452
461
|
const command = typeof details.command === "string" ? details.command : parseCommandInfo(args).command;
|
|
453
|
-
const
|
|
462
|
+
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
463
|
+
const nestedBatchEndsClosed = batchCloseLifecycle?.endsClosed === true;
|
|
464
|
+
const nestedBatchRemainsActive = batchCloseLifecycle?.endsClosed === false;
|
|
465
|
+
const closeAllApplied = details.closeAllApplied === true
|
|
466
|
+
|| (message.isError !== true && isCloseAllCommand(commandTokens))
|
|
467
|
+
|| batchHasSuccessfulCloseAll(details.batchSteps);
|
|
468
|
+
const commandClosesSession = isCloseCommand(command) || nestedBatchEndsClosed;
|
|
454
469
|
const outcome = typeof details.managedSessionOutcome === "object" && details.managedSessionOutcome !== null ? details.managedSessionOutcome : undefined;
|
|
455
470
|
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
456
471
|
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
@@ -459,6 +474,14 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
459
474
|
? outcomeAttemptedSessionName ?? getRestorableManagedSessionName(outcomeCurrentSessionName, fallbackSessionName) ?? getRestorableManagedSessionName(sessionName, fallbackSessionName)
|
|
460
475
|
: undefined;
|
|
461
476
|
const restorableDetailSessionName = getRestorableManagedSessionName(sessionName, fallbackSessionName);
|
|
477
|
+
if (closeAllApplied && restoredState.active) {
|
|
478
|
+
const restoredKey = getAgentBrowserSessionIdentityKey(restoredState.sessionName, restoredState.namespace);
|
|
479
|
+
const resultKey = restorableDetailSessionName ? getAgentBrowserSessionIdentityKey(restorableDetailSessionName, namespace) : undefined;
|
|
480
|
+
const retainsCurrentSession = nestedBatchRemainsActive && resultKey === restoredKey;
|
|
481
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(restoredKey, namespace) && !retainsCurrentSession) {
|
|
482
|
+
applyManagedClose(restoredState.sessionName, restoredState.namespace);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
462
485
|
const explicitCloseSessionName = commandClosesSession && explicitSessionName && restorableDetailSessionName === explicitSessionName
|
|
463
486
|
? restorableDetailSessionName
|
|
464
487
|
: undefined;
|
|
@@ -467,13 +490,13 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
467
490
|
if (details.managedSessionRestoreDisabled === true && typeof sessionName === "string") {
|
|
468
491
|
restoreDisabledIdentities.set(getAgentBrowserSessionIdentityKey(sessionName, namespace), { namespace, sessionName });
|
|
469
492
|
}
|
|
470
|
-
const managedSessionName = !explicitSessionName &&
|
|
493
|
+
const managedSessionName = outcomeClosedSessionName ?? (!explicitSessionName &&
|
|
471
494
|
restorableDetailSessionName &&
|
|
472
495
|
(usedImplicitSession || sessionMode === "fresh")
|
|
473
496
|
? restorableDetailSessionName
|
|
474
497
|
: commandClosesSession
|
|
475
|
-
?
|
|
476
|
-
: undefined;
|
|
498
|
+
? explicitCloseSessionName
|
|
499
|
+
: undefined);
|
|
477
500
|
if (!managedSessionName) {
|
|
478
501
|
continue;
|
|
479
502
|
}
|
|
@@ -490,9 +513,11 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
490
513
|
const exitCode = typeof details.exitCode === "number" ? details.exitCode : undefined;
|
|
491
514
|
const outcomeActiveAfter = outcome?.activeAfter === true;
|
|
492
515
|
const outcomeRepresentsActiveCurrentSession = outcomeActiveAfter && outcomeCurrentSessionName === managedSessionName && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged");
|
|
493
|
-
const succeeded = outcomeRepresentsActiveCurrentSession
|
|
494
|
-
|
|
495
|
-
|
|
516
|
+
const succeeded = outcomeRepresentsActiveCurrentSession || nestedBatchRemainsActive
|
|
517
|
+
? true
|
|
518
|
+
: messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
|
|
519
|
+
if (commandClosesSession || outcomeClosedSessionName) {
|
|
520
|
+
if (nestedBatchEndsClosed || outcomeClosedSessionName || succeeded) {
|
|
496
521
|
restoreDisabledIdentities.delete(getAgentBrowserSessionIdentityKey(managedSessionName, namespace));
|
|
497
522
|
applyManagedClose(managedSessionName, namespace);
|
|
498
523
|
}
|
|
@@ -529,15 +554,24 @@ export function createEphemeralSessionSeed() {
|
|
|
529
554
|
function createCwdHash(cwd) {
|
|
530
555
|
return createHash("sha256").update(`cwd:${cwd}`).digest("hex").slice(0, SESSION_NAME_CWD_HASH_LENGTH);
|
|
531
556
|
}
|
|
532
|
-
export function createImplicitSessionName(sessionId, cwd, ephemeralSeed) {
|
|
557
|
+
export function createImplicitSessionName(sessionId, cwd, ephemeralSeed, platform = process.platform) {
|
|
558
|
+
const normalizedSessionId = sessionId?.replaceAll("-", "").toLowerCase();
|
|
559
|
+
if (platform === "android") {
|
|
560
|
+
const identity = normalizedSessionId ? `session:${normalizedSessionId}:cwd:${cwd}` : `ephemeral:${cwd}:${ephemeralSeed}`;
|
|
561
|
+
const digest = createHash("sha256").update(identity).digest("hex").slice(0, ANDROID_SESSION_NAME_IDENTITY_LENGTH);
|
|
562
|
+
return `${MANAGED_SESSION_NAME_PREFIX}${digest}`;
|
|
563
|
+
}
|
|
533
564
|
const slug = basename(cwd)
|
|
534
565
|
.toLowerCase()
|
|
535
566
|
.replace(/[^a-z0-9]+/g, "-")
|
|
536
567
|
.replace(/^-+|-+$/g, "")
|
|
537
568
|
.slice(0, MAX_PROJECT_SLUG_LENGTH) || "project";
|
|
538
569
|
const cwdHash = createCwdHash(cwd);
|
|
539
|
-
|
|
540
|
-
|
|
570
|
+
if (normalizedSessionId) {
|
|
571
|
+
const stableSessionId = createHash("sha256")
|
|
572
|
+
.update(`session:${normalizedSessionId}`)
|
|
573
|
+
.digest("hex")
|
|
574
|
+
.slice(0, SESSION_NAME_SESSION_ID_LENGTH);
|
|
541
575
|
return `${MANAGED_SESSION_NAME_PREFIX}${slug}-${stableSessionId}-${cwdHash}`;
|
|
542
576
|
}
|
|
543
577
|
const digest = createHash("sha256")
|
|
@@ -554,7 +588,7 @@ export function createFreshSessionName(baseSessionName, ephemeralSeed, ordinal)
|
|
|
554
588
|
return `${baseSessionName}-fresh-${suffix}`;
|
|
555
589
|
}
|
|
556
590
|
function getSingleKeyCommandValidationError(args) {
|
|
557
|
-
const { commandInfo, commandTokens } = parseArgvDescriptor(args);
|
|
591
|
+
const { commandInfo, upstreamCommandTokens: commandTokens } = parseArgvDescriptor(args);
|
|
558
592
|
const command = commandInfo.command;
|
|
559
593
|
if (command !== "press" && command !== "key" && command !== "keydown" && command !== "keyup")
|
|
560
594
|
return undefined;
|
|
@@ -564,13 +598,19 @@ function getSingleKeyCommandValidationError(args) {
|
|
|
564
598
|
return `agent-browser ${label} accepts exactly one key argument. Do not pass a selector or ref to ${label}; focus or click the target first, then run ${command} <key> (for example: focus @e1, then press Enter).`;
|
|
565
599
|
}
|
|
566
600
|
function getBareMcpValidationError(args) {
|
|
567
|
-
const { commandInfo, commandTokens } = parseArgvDescriptor(args);
|
|
601
|
+
const { commandInfo, upstreamCommandTokens: commandTokens } = parseArgvDescriptor(args);
|
|
568
602
|
if (commandInfo.command !== "mcp")
|
|
569
603
|
return undefined;
|
|
570
604
|
if (commandTokens.includes("--help") || commandTokens.includes("-h"))
|
|
571
605
|
return undefined;
|
|
572
606
|
return "agent-browser mcp starts a stdio MCP server for external MCP clients, not a one-shot native agent_browser tool workflow. Use the native agent_browser tool modes directly, or configure an MCP client to launch `agent-browser mcp`. Use `mcp --help` for help.";
|
|
573
607
|
}
|
|
608
|
+
function getUnsupportedInlineWaitDownloadError(args) {
|
|
609
|
+
const descriptor = parseArgvDescriptor(args);
|
|
610
|
+
if (descriptor.commandInfo.command !== "wait" || !descriptor.upstreamCommandTokens.some((token) => token.startsWith("--download=")))
|
|
611
|
+
return undefined;
|
|
612
|
+
return `agent-browser ${TARGET_AGENT_BROWSER_VERSION} does not support \`wait --download=<path>\`. Pass the optional path as a separate argument: \`wait --download <path>\` (or \`wait -d <path>\`).`;
|
|
613
|
+
}
|
|
574
614
|
export function validateToolArgs(args) {
|
|
575
615
|
if (args.length === 0) {
|
|
576
616
|
return "`args` must contain at least one agent-browser command token.";
|
|
@@ -583,7 +623,7 @@ export function validateToolArgs(args) {
|
|
|
583
623
|
if (sessionModeArg) {
|
|
584
624
|
return "Do not pass `--session-mode` in args. Use the top-level agent_browser `sessionMode` field instead, for example { args: [\"--profile\", \"Default\", \"open\", \"https://example.com\"], sessionMode: \"fresh\" }.";
|
|
585
625
|
}
|
|
586
|
-
return getBareMcpValidationError(args) ?? getSingleKeyCommandValidationError(args);
|
|
626
|
+
return getBareMcpValidationError(args) ?? getSingleKeyCommandValidationError(args) ?? getUnsupportedInlineWaitDownloadError(args);
|
|
587
627
|
}
|
|
588
628
|
function getInvalidValueFlagDetails(args) {
|
|
589
629
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -607,7 +647,7 @@ function getInvalidValueFlagDetails(args) {
|
|
|
607
647
|
continue;
|
|
608
648
|
}
|
|
609
649
|
const receivedToken = args[index + 1];
|
|
610
|
-
if (receivedToken === undefined) {
|
|
650
|
+
if (receivedToken === undefined || (normalizedToken === "--args" && receivedToken.length === 0)) {
|
|
611
651
|
return {
|
|
612
652
|
flag: normalizedToken,
|
|
613
653
|
index,
|
|
@@ -683,7 +723,7 @@ function parseComparableNavigationUrl(url) {
|
|
|
683
723
|
export function getDefaultHeadlessCompatUserAgent(platform = process.platform) {
|
|
684
724
|
return DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM[platform] ?? FALLBACK_HEADLESS_COMPAT_USER_AGENT;
|
|
685
725
|
}
|
|
686
|
-
export function canUseHeadlessCompatibilityUserAgent(args, env =
|
|
726
|
+
export function canUseHeadlessCompatibilityUserAgent(args, env = getAgentBrowserProcessEnvironment()) {
|
|
687
727
|
if (hasFlagToken(args, "--user-agent") || hasFlagToken(args, "--args"))
|
|
688
728
|
return false;
|
|
689
729
|
if (hasFlagToken(args, "--cdp") || hasFlagToken(args, "--provider") || hasFlagToken(args, "-p"))
|
|
@@ -716,6 +756,14 @@ function getCompatibilityWorkaround(args, commandInfo) {
|
|
|
716
756
|
reason: "OpenAI web properties currently challenge the default headless Chrome user agent; inject a normal Chrome user agent to preserve the default headless workflow without requiring headed mode or auto-connect.",
|
|
717
757
|
};
|
|
718
758
|
}
|
|
759
|
+
function stripExplicitSessionArgs(args) {
|
|
760
|
+
const sessionTokenIndexes = new Set();
|
|
761
|
+
for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, "--session")) {
|
|
762
|
+
sessionTokenIndexes.add(occurrence.index);
|
|
763
|
+
sessionTokenIndexes.add(occurrence.index + 1);
|
|
764
|
+
}
|
|
765
|
+
return args.filter((_token, index) => !sessionTokenIndexes.has(index));
|
|
766
|
+
}
|
|
719
767
|
function stripExplicitNamespaceArgs(args) {
|
|
720
768
|
const namespaceTokenIndexes = new Set();
|
|
721
769
|
for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, "--namespace")) {
|
|
@@ -741,7 +789,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
741
789
|
const commandInfo = argvDescriptor.commandInfo;
|
|
742
790
|
const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
|
|
743
791
|
const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
|
|
744
|
-
let namespace = explicitNamespace;
|
|
792
|
+
let namespace = explicitNamespacePresent ? explicitNamespace ?? "" : undefined;
|
|
745
793
|
if (plainTextInspection) {
|
|
746
794
|
return {
|
|
747
795
|
commandInfo,
|
|
@@ -770,7 +818,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
770
818
|
plainTextInspection: false,
|
|
771
819
|
startupScopedFlags: [],
|
|
772
820
|
usedImplicitSession: false,
|
|
773
|
-
validationError: `${unsupportedIdentityAssignment}=... is not supported by agent-browser
|
|
821
|
+
validationError: `${unsupportedIdentityAssignment}=... is not supported by agent-browser ${TARGET_AGENT_BROWSER_VERSION}. Pass ${unsupportedIdentityAssignment} and its value as separate arguments.`,
|
|
774
822
|
};
|
|
775
823
|
}
|
|
776
824
|
for (const flag of ["--session", "--namespace"]) {
|
|
@@ -787,7 +835,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
787
835
|
}
|
|
788
836
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
789
837
|
if (explicitSessionName && !isWrapperManagedSessionName(explicitSessionName)) {
|
|
790
|
-
namespace = resolveAgentBrowserNamespace(args,
|
|
838
|
+
namespace = resolveAgentBrowserNamespace(args, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
|
|
791
839
|
}
|
|
792
840
|
const shouldCreateFreshManagedSession = !explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
|
|
793
841
|
let argsToAppend = args;
|
|
@@ -816,8 +864,8 @@ export function buildExecutionPlan(args, options) {
|
|
|
816
864
|
].join(" ");
|
|
817
865
|
}
|
|
818
866
|
else {
|
|
819
|
-
namespace = explicitNamespacePresent ? explicitNamespace : managedSessionNamespace;
|
|
820
|
-
if (namespace)
|
|
867
|
+
namespace = explicitNamespacePresent ? explicitNamespace ?? "" : managedSessionNamespace;
|
|
868
|
+
if (namespace !== undefined)
|
|
821
869
|
effectiveArgs.push("--namespace", namespace);
|
|
822
870
|
effectiveArgs.push("--session", options.managedSessionName);
|
|
823
871
|
if (explicitNamespacePresent)
|
|
@@ -828,7 +876,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
828
876
|
}
|
|
829
877
|
}
|
|
830
878
|
else if (shouldCreateFreshManagedSession && commandNeedsManagedSession) {
|
|
831
|
-
if (namespace)
|
|
879
|
+
if (namespace !== undefined)
|
|
832
880
|
effectiveArgs.push("--namespace", namespace);
|
|
833
881
|
effectiveArgs.push("--session", options.freshSessionName);
|
|
834
882
|
if (explicitNamespacePresent)
|
|
@@ -836,14 +884,43 @@ export function buildExecutionPlan(args, options) {
|
|
|
836
884
|
managedSessionName = options.freshSessionName;
|
|
837
885
|
sessionName = options.freshSessionName;
|
|
838
886
|
}
|
|
839
|
-
|
|
840
|
-
&& canUseHeadlessCompatibilityUserAgent(args)
|
|
841
|
-
&& options.managedSessionActive
|
|
887
|
+
const targetsActiveManagedSession = options.managedSessionActive
|
|
842
888
|
&& sessionName
|
|
843
|
-
&& getAgentBrowserSessionIdentityKey(sessionName, namespace) === getAgentBrowserSessionIdentityKey(options.managedSessionName, options.managedSessionNamespace)
|
|
844
|
-
|
|
889
|
+
&& getAgentBrowserSessionIdentityKey(sessionName, namespace) === getAgentBrowserSessionIdentityKey(options.managedSessionName, options.managedSessionNamespace);
|
|
890
|
+
if (targetsActiveManagedSession && startupScopedFlags.length > 0 && !isCloseCommand(commandInfo.command) && !validationError) {
|
|
891
|
+
const recoveryArgs = explicitSessionName ? stripExplicitSessionArgs(args) : args;
|
|
892
|
+
recoveryHint = {
|
|
893
|
+
exampleArgs: recoveryArgs,
|
|
894
|
+
exampleParams: { args: recoveryArgs, sessionMode: "fresh" },
|
|
895
|
+
reason: `Launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}) need a fresh upstream launch once the extension-managed session is already active.`,
|
|
896
|
+
recommendedSessionMode: "fresh",
|
|
897
|
+
};
|
|
898
|
+
validationError = [
|
|
899
|
+
`The current extension-managed agent-browser session is already running, so launch-scoped flags ${startupScopedFlags.join(", ")} would replace or be ignored by upstream agent-browser.`,
|
|
900
|
+
explicitSessionName
|
|
901
|
+
? "Remove the explicit `--session` and retry with `sessionMode: \"fresh\"` to force a fresh upstream launch."
|
|
902
|
+
: "Retry this call with `sessionMode: \"fresh\"` to force a fresh upstream launch.",
|
|
903
|
+
].join(" ");
|
|
904
|
+
}
|
|
905
|
+
if (targetsActiveManagedSession && canUseHeadlessCompatibilityUserAgent(args)) {
|
|
906
|
+
if (requestedCompatibilityWorkaround && !options.managedSessionCompatibilityWorkaround && !validationError) {
|
|
907
|
+
compatibilityWorkaround = undefined;
|
|
908
|
+
const recoveryArgs = explicitSessionName ? stripExplicitSessionArgs(args) : args;
|
|
909
|
+
recoveryHint = {
|
|
910
|
+
exampleArgs: recoveryArgs,
|
|
911
|
+
exampleParams: { args: recoveryArgs, sessionMode: "fresh" },
|
|
912
|
+
reason: "The requested site compatibility user agent is launch-scoped and needs a fresh browser session.",
|
|
913
|
+
recommendedSessionMode: "fresh",
|
|
914
|
+
};
|
|
915
|
+
validationError = explicitSessionName
|
|
916
|
+
? "The current extension-managed agent-browser session is already running without the user agent required by this site. Remove the explicit `--session` and retry with `sessionMode: \"fresh\"` so the compatibility user agent is applied at launch."
|
|
917
|
+
: "The current extension-managed agent-browser session is already running without the user agent required by this site. Retry this call with `sessionMode: \"fresh\"` so the compatibility user agent is applied at launch.";
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
compatibilityWorkaround = requestedCompatibilityWorkaround ?? options.managedSessionCompatibilityWorkaround;
|
|
921
|
+
}
|
|
845
922
|
}
|
|
846
|
-
if (compatibilityWorkaround) {
|
|
923
|
+
if (compatibilityWorkaround && !targetsActiveManagedSession) {
|
|
847
924
|
effectiveArgs.push("--user-agent", getDefaultHeadlessCompatUserAgent());
|
|
848
925
|
}
|
|
849
926
|
effectiveArgs.push(...argsToAppend);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
|
+
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "./argv-grammar.js";
|
|
3
|
+
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
|
|
4
|
+
import { isCloseAllCommand, isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
|
|
3
5
|
import { isRecord } from "./parsing.js";
|
|
4
6
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
5
7
|
import { enrichSnapshotRefEntries, getSnapshotRefEntries } from "./results/snapshot-refs.js";
|
|
@@ -93,6 +95,11 @@ export function extractSessionTabTargetFromBatchResults(data) {
|
|
|
93
95
|
}
|
|
94
96
|
const [name, subcommand] = extractBatchResultCommand(item);
|
|
95
97
|
const result = item.result;
|
|
98
|
+
if (isCloseCommand(name)) {
|
|
99
|
+
currentTarget = undefined;
|
|
100
|
+
pendingTitle = undefined;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
96
103
|
if (name === "get" && subcommand === "title") {
|
|
97
104
|
pendingTitle = extractStringResultField(result, "title");
|
|
98
105
|
continue;
|
|
@@ -197,6 +204,12 @@ export function buildNoActivePageRefSnapshotInvalidation() {
|
|
|
197
204
|
summary: "The latest snapshot for this session reported No active page. Old page-scoped refs are invalid until snapshot -i succeeds.",
|
|
198
205
|
};
|
|
199
206
|
}
|
|
207
|
+
export function buildPageTransitionRefSnapshotInvalidation(summary) {
|
|
208
|
+
return {
|
|
209
|
+
reason: "page-transition",
|
|
210
|
+
summary: summary ?? "A recording command (record start, or record restart with a URL) replaced or navigated the active page and invalidated the prior snapshot. Run snapshot -i before using page-scoped refs.",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
200
213
|
export function isNoActivePageSnapshotFailure(command, text) {
|
|
201
214
|
return command === "snapshot" && /\bno active page\b/i.test(text ?? "");
|
|
202
215
|
}
|
|
@@ -207,7 +220,16 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
207
220
|
for (const item of data) {
|
|
208
221
|
if (!isRecord(item))
|
|
209
222
|
continue;
|
|
210
|
-
const
|
|
223
|
+
const commandTokens = extractBatchResultCommand(item);
|
|
224
|
+
const [name] = commandTokens;
|
|
225
|
+
if (item.success !== false && isCloseCommand(name)) {
|
|
226
|
+
latestState = undefined;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (isRecordPageTransitionCommand(commandTokens)) {
|
|
230
|
+
latestState = { invalidation: buildPageTransitionRefSnapshotInvalidation() };
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
211
233
|
if (name !== "snapshot")
|
|
212
234
|
continue;
|
|
213
235
|
if (item.success === false) {
|
|
@@ -225,9 +247,10 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
225
247
|
}
|
|
226
248
|
function getRestoredRefSnapshotInvalidation(details, command) {
|
|
227
249
|
const invalidation = isRecord(details.refSnapshotInvalidation) ? details.refSnapshotInvalidation : undefined;
|
|
228
|
-
if (invalidation
|
|
250
|
+
if (invalidation?.reason === "no-active-page")
|
|
229
251
|
return buildNoActivePageRefSnapshotInvalidation();
|
|
230
|
-
|
|
252
|
+
if (invalidation?.reason === "page-transition")
|
|
253
|
+
return buildPageTransitionRefSnapshotInvalidation(typeof invalidation.summary === "string" ? invalidation.summary : undefined);
|
|
231
254
|
const errorText = typeof details.error === "string"
|
|
232
255
|
? details.error
|
|
233
256
|
: typeof details.summary === "string"
|
|
@@ -316,14 +339,27 @@ export class SessionPageState {
|
|
|
316
339
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
317
340
|
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
318
341
|
const sessionKey = getSessionPageStateKey(sessionName, namespace);
|
|
342
|
+
const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
|
|
343
|
+
const commandTokens = extractUpstreamCommandTokens(args);
|
|
344
|
+
const command = typeof details.command === "string" ? details.command : commandTokens[0];
|
|
345
|
+
const subcommand = typeof details.subcommand === "string" ? details.subcommand : commandTokens[1];
|
|
346
|
+
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
347
|
+
const closeAllApplied = details.closeAllApplied === true
|
|
348
|
+
|| (message.isError !== true && isCloseAllCommand(commandTokens))
|
|
349
|
+
|| batchHasSuccessfulCloseAll(details.batchSteps);
|
|
350
|
+
if (closeAllApplied) {
|
|
351
|
+
restoredOrder += 1;
|
|
352
|
+
state.clearNamespace(namespace);
|
|
353
|
+
if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
319
356
|
if (!sessionKey)
|
|
320
357
|
continue;
|
|
321
|
-
|
|
322
|
-
const subcommand = typeof details.subcommand === "string" ? details.subcommand : undefined;
|
|
323
|
-
if (isCloseCommand(command) && message.isError !== true) {
|
|
358
|
+
if (!closeAllApplied && ((isCloseCommand(command) && message.isError !== true) || batchCloseLifecycle)) {
|
|
324
359
|
restoredOrder += 1;
|
|
325
360
|
state.clearSession(sessionKey);
|
|
326
|
-
|
|
361
|
+
if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
|
|
362
|
+
continue;
|
|
327
363
|
}
|
|
328
364
|
const tabTarget = getRestoredSessionTabTarget(details, command, subcommand);
|
|
329
365
|
const tabTargetUnknown = details.sessionTabTargetUnknown === true;
|
|
@@ -333,8 +369,11 @@ export class SessionPageState {
|
|
|
333
369
|
continue;
|
|
334
370
|
restoredOrder += 1;
|
|
335
371
|
if (tabTargetUnknown) {
|
|
336
|
-
state.refSnapshotInvalidations.delete(sessionKey);
|
|
337
372
|
state.refSnapshots.delete(sessionKey);
|
|
373
|
+
if (refSnapshotInvalidation)
|
|
374
|
+
state.refSnapshotInvalidations.set(sessionKey, { ...refSnapshotInvalidation, order: restoredOrder });
|
|
375
|
+
else
|
|
376
|
+
state.refSnapshotInvalidations.delete(sessionKey);
|
|
338
377
|
state.tabTargets.delete(sessionKey);
|
|
339
378
|
state.tabTargetUnknownOrders.set(sessionKey, restoredOrder);
|
|
340
379
|
continue;
|
|
@@ -431,6 +470,19 @@ export class SessionPageState {
|
|
|
431
470
|
this.tabTargetUnknownOrders.delete(sessionName);
|
|
432
471
|
this.tabTargets.delete(sessionName);
|
|
433
472
|
}
|
|
473
|
+
clearNamespace(namespace) {
|
|
474
|
+
const sessionKeys = new Set([
|
|
475
|
+
...this.refSnapshotInvalidations.keys(),
|
|
476
|
+
...this.refSnapshots.keys(),
|
|
477
|
+
...this.tabPinningReasons.keys(),
|
|
478
|
+
...this.tabTargetUnknownOrders.keys(),
|
|
479
|
+
...this.tabTargets.keys(),
|
|
480
|
+
]);
|
|
481
|
+
for (const sessionKey of sessionKeys) {
|
|
482
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, namespace))
|
|
483
|
+
this.clearSession(sessionKey);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
434
486
|
markPinning(sessionName, reason) {
|
|
435
487
|
this.tabPinningReasons.set(sessionName, reason);
|
|
436
488
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL } from "../../../scripts/agent-browser-target.mjs";
|
|
2
|
+
export { TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL };
|
|
3
|
+
export function parseAgentBrowserVersionOutput(stdout) {
|
|
4
|
+
const match = stdout.trim().match(/^agent-browser\s+(\S+)$/);
|
|
5
|
+
return match?.[1];
|
|
6
|
+
}
|
|
7
|
+
export function getAgentBrowserVersionValidationError(stdout) {
|
|
8
|
+
const observed = parseAgentBrowserVersionOutput(stdout);
|
|
9
|
+
if (observed === TARGET_AGENT_BROWSER_VERSION)
|
|
10
|
+
return undefined;
|
|
11
|
+
return observed
|
|
12
|
+
? `Installed agent-browser ${observed} does not match this extension's exact ${TARGET_AGENT_BROWSER_VERSION} capability baseline. Install ${TARGET_AGENT_BROWSER_VERSION_LABEL}, run pi-agent-browser-doctor, then reload Pi.`
|
|
13
|
+
: `agent-browser --version returned an unrecognized value; expected exactly ${JSON.stringify(TARGET_AGENT_BROWSER_VERSION_LABEL)}. Run pi-agent-browser-doctor and install the exact supported upstream version.`;
|
|
14
|
+
}
|