pi-agent-browser-native 0.2.77 → 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 +154 -0
- package/README.md +69 -35
- package/dist/extensions/agent-browser/index.js +911 -121
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
- package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/config.js +0 -6
- package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
- package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
- 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 +27 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
- 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 +22 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +148 -34
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/parsing.js +0 -7
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
- package/dist/extensions/agent-browser/lib/playbook.js +20 -24
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
- package/dist/extensions/agent-browser/lib/process.js +131 -48
- package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
- package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
- package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
- package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
- package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
- package/dist/extensions/agent-browser/lib/results/network.js +0 -7
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
- package/dist/extensions/agent-browser/lib/results/text.js +0 -7
- package/dist/extensions/agent-browser/lib/runtime.js +119 -52
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/temp.js +0 -7
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +0 -5
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +50 -31
- package/docs/COMMAND_REFERENCE.md +97 -41
- package/docs/ELECTRON.md +3 -19
- package/docs/RELEASE.md +8 -20
- package/docs/REQUIREMENTS.md +8 -6
- package/docs/SUPPORT_MATRIX.md +32 -25
- package/docs/TOOL_CONTRACT.md +107 -64
- package/docs/platform-smoke.md +1 -1
- package/package.json +7 -6
- 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 +8 -7
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
- package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
- package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
- package/dist/extensions/agent-browser/lib/results.js +0 -14
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Build safe, deterministic agent-browser invocations and persisted session state for the pi-agent-browser extension.
|
|
3
|
-
* Responsibilities: Validate raw tool arguments, derive extension-managed session names from the pi session identity, restore managed-session state from persisted tool details, redact sensitive invocation text, classify browser-oriented prompts, and build the effective CLI argument list passed to the upstream agent-browser binary.
|
|
4
|
-
* Scope: Runtime-planning helpers only; no subprocess execution or filesystem access.
|
|
5
|
-
* Usage: Imported by the extension entrypoint and unit tests before spawning the upstream CLI.
|
|
6
|
-
* Invariants/Assumptions: The wrapper stays thin, preserves upstream command vocabulary, keeps plain-text inspection stateless,
|
|
7
|
-
* and only injects wrapper-owned flags: `--json`, an extension-managed `--session` when appropriate, the narrow
|
|
8
|
-
* site-specific headless compatibility `--user-agent` when that workaround applies.
|
|
9
|
-
*/
|
|
10
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
2
|
import { basename } from "node:path";
|
|
12
|
-
import { findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
|
|
13
|
-
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";
|
|
14
6
|
import { needsManagedSession } from "./command-policy.js";
|
|
15
7
|
import { isWrapperManagedSessionName, redactManagedSessionRestoreKeys } from "./managed-session-capabilities.js";
|
|
16
|
-
import { isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
8
|
+
import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
17
9
|
import { hasLaunchScopedFlagToken, LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL, } from "./launch-scoped-flags.js";
|
|
18
10
|
import { MANAGED_SESSION_NAME_PREFIX, } from "./managed-session-restore.js";
|
|
19
|
-
export { extractCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo } from "./argv-descriptor.js";
|
|
11
|
+
export { extractCommandTokens, extractUpstreamCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, parseWaitCommandTokens } from "./argv-descriptor.js";
|
|
20
12
|
import { isRecord } from "./parsing.js";
|
|
13
|
+
import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
|
|
14
|
+
import { TARGET_AGENT_BROWSER_VERSION } from "./upstream-version.js";
|
|
21
15
|
const OPENAI_HEADLESS_COMPAT_HOSTS = new Set(["chat.com", "chat.openai.com", "chatgpt.com"]);
|
|
22
16
|
const CLOUDFLARE_HEADLESS_COMPAT_HOST = "dash.cloudflare.com";
|
|
23
17
|
const AGENT_BROWSER_IDLE_TIMEOUT_ENV = "AGENT_BROWSER_IDLE_TIMEOUT_MS";
|
|
@@ -37,6 +31,7 @@ const DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM = {
|
|
|
37
31
|
};
|
|
38
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";
|
|
39
33
|
const SHELL_OPERATOR_TOKENS = new Set(["&&", "||", "|", ";", ">", ">>", "<"]);
|
|
34
|
+
const ANDROID_SESSION_NAME_IDENTITY_LENGTH = 20;
|
|
40
35
|
const MAX_PROJECT_SLUG_LENGTH = 24;
|
|
41
36
|
const SESSION_NAME_CWD_HASH_LENGTH = 8;
|
|
42
37
|
const SESSION_NAME_SESSION_ID_LENGTH = 12;
|
|
@@ -320,7 +315,7 @@ function parseTimeoutMs(rawValue, minimumValue) {
|
|
|
320
315
|
}
|
|
321
316
|
return parsedValue;
|
|
322
317
|
}
|
|
323
|
-
export function getImplicitSessionIdleTimeoutMs(env =
|
|
318
|
+
export function getImplicitSessionIdleTimeoutMs(env = getAgentBrowserProcessEnvironment()) {
|
|
324
319
|
return parseTimeoutMs(env[IMPLICIT_SESSION_IDLE_TIMEOUT_ENV], 0) ??
|
|
325
320
|
parseTimeoutMs(env[AGENT_BROWSER_IDLE_TIMEOUT_ENV], 0) ??
|
|
326
321
|
DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS;
|
|
@@ -342,7 +337,7 @@ function getUnsupportedLeadingIdentityAssignment(args) {
|
|
|
342
337
|
}
|
|
343
338
|
return undefined;
|
|
344
339
|
}
|
|
345
|
-
export function getImplicitSessionCloseTimeoutMs(env =
|
|
340
|
+
export function getImplicitSessionCloseTimeoutMs(env = getAgentBrowserProcessEnvironment()) {
|
|
346
341
|
return parseTimeoutMs(env[IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV], 0) ?? DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS;
|
|
347
342
|
}
|
|
348
343
|
export function resolveManagedSessionState(options) {
|
|
@@ -368,7 +363,7 @@ export function resolveManagedSessionState(options) {
|
|
|
368
363
|
sessionName: managedSessionName,
|
|
369
364
|
};
|
|
370
365
|
}
|
|
371
|
-
function isRestorableManagedSessionName(sessionName, fallbackSessionName) {
|
|
366
|
+
export function isRestorableManagedSessionName(sessionName, fallbackSessionName) {
|
|
372
367
|
return sessionName === fallbackSessionName || sessionName.startsWith(`${fallbackSessionName}-fresh-`);
|
|
373
368
|
}
|
|
374
369
|
function getManagedSessionRestoreRank(options) {
|
|
@@ -390,15 +385,18 @@ function getManagedSessionRestoreRank(options) {
|
|
|
390
385
|
function getRestorableManagedSessionName(value, fallbackSessionName) {
|
|
391
386
|
return typeof value === "string" && isRestorableManagedSessionName(value, fallbackSessionName) ? value : undefined;
|
|
392
387
|
}
|
|
393
|
-
function
|
|
388
|
+
function getElectronCleanupClosedManagedSessions(details, fallbackSessionName) {
|
|
394
389
|
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
395
390
|
const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
|
|
396
391
|
const results = Array.isArray(cleanup?.results) ? cleanup.results : [];
|
|
397
|
-
const
|
|
392
|
+
const closedSessions = [];
|
|
398
393
|
for (const result of results) {
|
|
399
394
|
if (!isRecord(result) || !Array.isArray(result.steps))
|
|
400
395
|
continue;
|
|
401
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;
|
|
402
400
|
for (const step of result.steps) {
|
|
403
401
|
if (!isRecord(step) || step.resource !== "managed-session")
|
|
404
402
|
continue;
|
|
@@ -406,11 +404,12 @@ function getElectronCleanupClosedManagedSessionNames(details, fallbackSessionNam
|
|
|
406
404
|
continue;
|
|
407
405
|
const sessionName = getRestorableManagedSessionName(step.sessionName, fallbackSessionName)
|
|
408
406
|
?? getRestorableManagedSessionName(record?.sessionName, fallbackSessionName);
|
|
407
|
+
const namespace = typeof step.namespace === "string" ? step.namespace : fallbackNamespace;
|
|
409
408
|
if (sessionName)
|
|
410
|
-
|
|
409
|
+
closedSessions.push({ namespace, sessionName });
|
|
411
410
|
}
|
|
412
411
|
}
|
|
413
|
-
return
|
|
412
|
+
return closedSessions;
|
|
414
413
|
}
|
|
415
414
|
export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName) {
|
|
416
415
|
const restoreDisabledIdentities = new Map();
|
|
@@ -450,16 +449,23 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
450
449
|
if (isPlainTextInspectionArgs(args)) {
|
|
451
450
|
continue;
|
|
452
451
|
}
|
|
453
|
-
for (const
|
|
454
|
-
applyManagedClose(sessionName);
|
|
452
|
+
for (const cleanupSession of getElectronCleanupClosedManagedSessions(details, fallbackSessionName)) {
|
|
453
|
+
applyManagedClose(cleanupSession.sessionName, cleanupSession.namespace);
|
|
455
454
|
}
|
|
456
455
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
457
456
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
458
457
|
const namespace = canonicalizeAgentBrowserNamespace(typeof details.namespace === "string" ? details.namespace : undefined);
|
|
459
458
|
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
460
459
|
const usedImplicitSession = details.usedImplicitSession === true;
|
|
460
|
+
const commandTokens = extractUpstreamCommandTokens(args);
|
|
461
461
|
const command = typeof details.command === "string" ? details.command : parseCommandInfo(args).command;
|
|
462
|
-
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;
|
|
463
469
|
const outcome = typeof details.managedSessionOutcome === "object" && details.managedSessionOutcome !== null ? details.managedSessionOutcome : undefined;
|
|
464
470
|
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
465
471
|
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
@@ -468,6 +474,14 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
468
474
|
? outcomeAttemptedSessionName ?? getRestorableManagedSessionName(outcomeCurrentSessionName, fallbackSessionName) ?? getRestorableManagedSessionName(sessionName, fallbackSessionName)
|
|
469
475
|
: undefined;
|
|
470
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
|
+
}
|
|
471
485
|
const explicitCloseSessionName = commandClosesSession && explicitSessionName && restorableDetailSessionName === explicitSessionName
|
|
472
486
|
? restorableDetailSessionName
|
|
473
487
|
: undefined;
|
|
@@ -476,13 +490,13 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
476
490
|
if (details.managedSessionRestoreDisabled === true && typeof sessionName === "string") {
|
|
477
491
|
restoreDisabledIdentities.set(getAgentBrowserSessionIdentityKey(sessionName, namespace), { namespace, sessionName });
|
|
478
492
|
}
|
|
479
|
-
const managedSessionName = !explicitSessionName &&
|
|
493
|
+
const managedSessionName = outcomeClosedSessionName ?? (!explicitSessionName &&
|
|
480
494
|
restorableDetailSessionName &&
|
|
481
495
|
(usedImplicitSession || sessionMode === "fresh")
|
|
482
496
|
? restorableDetailSessionName
|
|
483
497
|
: commandClosesSession
|
|
484
|
-
?
|
|
485
|
-
: undefined;
|
|
498
|
+
? explicitCloseSessionName
|
|
499
|
+
: undefined);
|
|
486
500
|
if (!managedSessionName) {
|
|
487
501
|
continue;
|
|
488
502
|
}
|
|
@@ -499,9 +513,11 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
499
513
|
const exitCode = typeof details.exitCode === "number" ? details.exitCode : undefined;
|
|
500
514
|
const outcomeActiveAfter = outcome?.activeAfter === true;
|
|
501
515
|
const outcomeRepresentsActiveCurrentSession = outcomeActiveAfter && outcomeCurrentSessionName === managedSessionName && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged");
|
|
502
|
-
const succeeded = outcomeRepresentsActiveCurrentSession
|
|
503
|
-
|
|
504
|
-
|
|
516
|
+
const succeeded = outcomeRepresentsActiveCurrentSession || nestedBatchRemainsActive
|
|
517
|
+
? true
|
|
518
|
+
: messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
|
|
519
|
+
if (commandClosesSession || outcomeClosedSessionName) {
|
|
520
|
+
if (nestedBatchEndsClosed || outcomeClosedSessionName || succeeded) {
|
|
505
521
|
restoreDisabledIdentities.delete(getAgentBrowserSessionIdentityKey(managedSessionName, namespace));
|
|
506
522
|
applyManagedClose(managedSessionName, namespace);
|
|
507
523
|
}
|
|
@@ -538,15 +554,24 @@ export function createEphemeralSessionSeed() {
|
|
|
538
554
|
function createCwdHash(cwd) {
|
|
539
555
|
return createHash("sha256").update(`cwd:${cwd}`).digest("hex").slice(0, SESSION_NAME_CWD_HASH_LENGTH);
|
|
540
556
|
}
|
|
541
|
-
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
|
+
}
|
|
542
564
|
const slug = basename(cwd)
|
|
543
565
|
.toLowerCase()
|
|
544
566
|
.replace(/[^a-z0-9]+/g, "-")
|
|
545
567
|
.replace(/^-+|-+$/g, "")
|
|
546
568
|
.slice(0, MAX_PROJECT_SLUG_LENGTH) || "project";
|
|
547
569
|
const cwdHash = createCwdHash(cwd);
|
|
548
|
-
|
|
549
|
-
|
|
570
|
+
if (normalizedSessionId) {
|
|
571
|
+
const stableSessionId = createHash("sha256")
|
|
572
|
+
.update(`session:${normalizedSessionId}`)
|
|
573
|
+
.digest("hex")
|
|
574
|
+
.slice(0, SESSION_NAME_SESSION_ID_LENGTH);
|
|
550
575
|
return `${MANAGED_SESSION_NAME_PREFIX}${slug}-${stableSessionId}-${cwdHash}`;
|
|
551
576
|
}
|
|
552
577
|
const digest = createHash("sha256")
|
|
@@ -563,7 +588,7 @@ export function createFreshSessionName(baseSessionName, ephemeralSeed, ordinal)
|
|
|
563
588
|
return `${baseSessionName}-fresh-${suffix}`;
|
|
564
589
|
}
|
|
565
590
|
function getSingleKeyCommandValidationError(args) {
|
|
566
|
-
const { commandInfo, commandTokens } = parseArgvDescriptor(args);
|
|
591
|
+
const { commandInfo, upstreamCommandTokens: commandTokens } = parseArgvDescriptor(args);
|
|
567
592
|
const command = commandInfo.command;
|
|
568
593
|
if (command !== "press" && command !== "key" && command !== "keydown" && command !== "keyup")
|
|
569
594
|
return undefined;
|
|
@@ -573,13 +598,19 @@ function getSingleKeyCommandValidationError(args) {
|
|
|
573
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).`;
|
|
574
599
|
}
|
|
575
600
|
function getBareMcpValidationError(args) {
|
|
576
|
-
const { commandInfo, commandTokens } = parseArgvDescriptor(args);
|
|
601
|
+
const { commandInfo, upstreamCommandTokens: commandTokens } = parseArgvDescriptor(args);
|
|
577
602
|
if (commandInfo.command !== "mcp")
|
|
578
603
|
return undefined;
|
|
579
604
|
if (commandTokens.includes("--help") || commandTokens.includes("-h"))
|
|
580
605
|
return undefined;
|
|
581
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.";
|
|
582
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
|
+
}
|
|
583
614
|
export function validateToolArgs(args) {
|
|
584
615
|
if (args.length === 0) {
|
|
585
616
|
return "`args` must contain at least one agent-browser command token.";
|
|
@@ -592,7 +623,7 @@ export function validateToolArgs(args) {
|
|
|
592
623
|
if (sessionModeArg) {
|
|
593
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\" }.";
|
|
594
625
|
}
|
|
595
|
-
return getBareMcpValidationError(args) ?? getSingleKeyCommandValidationError(args);
|
|
626
|
+
return getBareMcpValidationError(args) ?? getSingleKeyCommandValidationError(args) ?? getUnsupportedInlineWaitDownloadError(args);
|
|
596
627
|
}
|
|
597
628
|
function getInvalidValueFlagDetails(args) {
|
|
598
629
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -616,7 +647,7 @@ function getInvalidValueFlagDetails(args) {
|
|
|
616
647
|
continue;
|
|
617
648
|
}
|
|
618
649
|
const receivedToken = args[index + 1];
|
|
619
|
-
if (receivedToken === undefined) {
|
|
650
|
+
if (receivedToken === undefined || (normalizedToken === "--args" && receivedToken.length === 0)) {
|
|
620
651
|
return {
|
|
621
652
|
flag: normalizedToken,
|
|
622
653
|
index,
|
|
@@ -692,16 +723,15 @@ function parseComparableNavigationUrl(url) {
|
|
|
692
723
|
export function getDefaultHeadlessCompatUserAgent(platform = process.platform) {
|
|
693
724
|
return DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM[platform] ?? FALLBACK_HEADLESS_COMPAT_USER_AGENT;
|
|
694
725
|
}
|
|
695
|
-
export function canUseHeadlessCompatibilityUserAgent(args, env =
|
|
726
|
+
export function canUseHeadlessCompatibilityUserAgent(args, env = getAgentBrowserProcessEnvironment()) {
|
|
696
727
|
if (hasFlagToken(args, "--user-agent") || hasFlagToken(args, "--args"))
|
|
697
728
|
return false;
|
|
698
729
|
if (hasFlagToken(args, "--cdp") || hasFlagToken(args, "--provider") || hasFlagToken(args, "-p"))
|
|
699
730
|
return false;
|
|
700
731
|
if (env.AGENT_BROWSER_USER_AGENT !== undefined || env.AGENT_BROWSER_ARGS !== undefined || env.AGENT_BROWSER_CDP !== undefined || env.AGENT_BROWSER_PROVIDER !== undefined)
|
|
701
732
|
return false;
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|| (getBooleanFlagValue(args, "--auto-connect") ?? envFlagEnabled(env.AGENT_BROWSER_AUTO_CONNECT)))
|
|
733
|
+
if ((getBooleanFlagValue(args, "--headed") ?? isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_HEADED))
|
|
734
|
+
|| (getBooleanFlagValue(args, "--auto-connect") ?? isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_AUTO_CONNECT)))
|
|
705
735
|
return false;
|
|
706
736
|
const engine = scanUpstreamGlobalFlagOccurrences(args, "--engine").at(-1)?.value ?? env.AGENT_BROWSER_ENGINE;
|
|
707
737
|
return !engine || engine === "chrome";
|
|
@@ -726,6 +756,14 @@ function getCompatibilityWorkaround(args, commandInfo) {
|
|
|
726
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.",
|
|
727
757
|
};
|
|
728
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
|
+
}
|
|
729
767
|
function stripExplicitNamespaceArgs(args) {
|
|
730
768
|
const namespaceTokenIndexes = new Set();
|
|
731
769
|
for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, "--namespace")) {
|
|
@@ -751,7 +789,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
751
789
|
const commandInfo = argvDescriptor.commandInfo;
|
|
752
790
|
const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
|
|
753
791
|
const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
|
|
754
|
-
let namespace = explicitNamespace;
|
|
792
|
+
let namespace = explicitNamespacePresent ? explicitNamespace ?? "" : undefined;
|
|
755
793
|
if (plainTextInspection) {
|
|
756
794
|
return {
|
|
757
795
|
commandInfo,
|
|
@@ -780,7 +818,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
780
818
|
plainTextInspection: false,
|
|
781
819
|
startupScopedFlags: [],
|
|
782
820
|
usedImplicitSession: false,
|
|
783
|
-
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.`,
|
|
784
822
|
};
|
|
785
823
|
}
|
|
786
824
|
for (const flag of ["--session", "--namespace"]) {
|
|
@@ -797,7 +835,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
797
835
|
}
|
|
798
836
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
799
837
|
if (explicitSessionName && !isWrapperManagedSessionName(explicitSessionName)) {
|
|
800
|
-
namespace = resolveAgentBrowserNamespace(args,
|
|
838
|
+
namespace = resolveAgentBrowserNamespace(args, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
|
|
801
839
|
}
|
|
802
840
|
const shouldCreateFreshManagedSession = !explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
|
|
803
841
|
let argsToAppend = args;
|
|
@@ -826,8 +864,8 @@ export function buildExecutionPlan(args, options) {
|
|
|
826
864
|
].join(" ");
|
|
827
865
|
}
|
|
828
866
|
else {
|
|
829
|
-
namespace = explicitNamespacePresent ? explicitNamespace : managedSessionNamespace;
|
|
830
|
-
if (namespace)
|
|
867
|
+
namespace = explicitNamespacePresent ? explicitNamespace ?? "" : managedSessionNamespace;
|
|
868
|
+
if (namespace !== undefined)
|
|
831
869
|
effectiveArgs.push("--namespace", namespace);
|
|
832
870
|
effectiveArgs.push("--session", options.managedSessionName);
|
|
833
871
|
if (explicitNamespacePresent)
|
|
@@ -838,7 +876,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
838
876
|
}
|
|
839
877
|
}
|
|
840
878
|
else if (shouldCreateFreshManagedSession && commandNeedsManagedSession) {
|
|
841
|
-
if (namespace)
|
|
879
|
+
if (namespace !== undefined)
|
|
842
880
|
effectiveArgs.push("--namespace", namespace);
|
|
843
881
|
effectiveArgs.push("--session", options.freshSessionName);
|
|
844
882
|
if (explicitNamespacePresent)
|
|
@@ -846,14 +884,43 @@ export function buildExecutionPlan(args, options) {
|
|
|
846
884
|
managedSessionName = options.freshSessionName;
|
|
847
885
|
sessionName = options.freshSessionName;
|
|
848
886
|
}
|
|
849
|
-
|
|
850
|
-
&& canUseHeadlessCompatibilityUserAgent(args)
|
|
851
|
-
&& options.managedSessionActive
|
|
887
|
+
const targetsActiveManagedSession = options.managedSessionActive
|
|
852
888
|
&& sessionName
|
|
853
|
-
&& getAgentBrowserSessionIdentityKey(sessionName, namespace) === getAgentBrowserSessionIdentityKey(options.managedSessionName, options.managedSessionNamespace)
|
|
854
|
-
|
|
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
|
+
}
|
|
855
922
|
}
|
|
856
|
-
if (compatibilityWorkaround) {
|
|
923
|
+
if (compatibilityWorkaround && !targetsActiveManagedSession) {
|
|
857
924
|
effectiveArgs.push("--user-agent", getDefaultHeadlessCompatUserAgent());
|
|
858
925
|
}
|
|
859
926
|
effectiveArgs.push(...argsToAppend);
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Usage: `index.ts` creates one store per Pi session lifecycle and records observations through update tokens.
|
|
6
|
-
* Invariants/Assumptions: One tool-call update token must govern all page-state observations from that invocation; stale overlapping updates must not overwrite newer state.
|
|
7
|
-
*/
|
|
8
|
-
import { getAgentBrowserSessionIdentityKey } from "./argv-grammar.js";
|
|
9
|
-
import { isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
|
|
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";
|
|
10
5
|
import { isRecord } from "./parsing.js";
|
|
11
6
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
12
7
|
import { enrichSnapshotRefEntries, getSnapshotRefEntries } from "./results/snapshot-refs.js";
|
|
@@ -100,6 +95,11 @@ export function extractSessionTabTargetFromBatchResults(data) {
|
|
|
100
95
|
}
|
|
101
96
|
const [name, subcommand] = extractBatchResultCommand(item);
|
|
102
97
|
const result = item.result;
|
|
98
|
+
if (isCloseCommand(name)) {
|
|
99
|
+
currentTarget = undefined;
|
|
100
|
+
pendingTitle = undefined;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
103
|
if (name === "get" && subcommand === "title") {
|
|
104
104
|
pendingTitle = extractStringResultField(result, "title");
|
|
105
105
|
continue;
|
|
@@ -204,6 +204,12 @@ export function buildNoActivePageRefSnapshotInvalidation() {
|
|
|
204
204
|
summary: "The latest snapshot for this session reported No active page. Old page-scoped refs are invalid until snapshot -i succeeds.",
|
|
205
205
|
};
|
|
206
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
|
+
}
|
|
207
213
|
export function isNoActivePageSnapshotFailure(command, text) {
|
|
208
214
|
return command === "snapshot" && /\bno active page\b/i.test(text ?? "");
|
|
209
215
|
}
|
|
@@ -214,7 +220,16 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
214
220
|
for (const item of data) {
|
|
215
221
|
if (!isRecord(item))
|
|
216
222
|
continue;
|
|
217
|
-
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
|
+
}
|
|
218
233
|
if (name !== "snapshot")
|
|
219
234
|
continue;
|
|
220
235
|
if (item.success === false) {
|
|
@@ -232,9 +247,10 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
232
247
|
}
|
|
233
248
|
function getRestoredRefSnapshotInvalidation(details, command) {
|
|
234
249
|
const invalidation = isRecord(details.refSnapshotInvalidation) ? details.refSnapshotInvalidation : undefined;
|
|
235
|
-
if (invalidation
|
|
250
|
+
if (invalidation?.reason === "no-active-page")
|
|
236
251
|
return buildNoActivePageRefSnapshotInvalidation();
|
|
237
|
-
|
|
252
|
+
if (invalidation?.reason === "page-transition")
|
|
253
|
+
return buildPageTransitionRefSnapshotInvalidation(typeof invalidation.summary === "string" ? invalidation.summary : undefined);
|
|
238
254
|
const errorText = typeof details.error === "string"
|
|
239
255
|
? details.error
|
|
240
256
|
: typeof details.summary === "string"
|
|
@@ -323,14 +339,27 @@ export class SessionPageState {
|
|
|
323
339
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
324
340
|
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
325
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
|
+
}
|
|
326
356
|
if (!sessionKey)
|
|
327
357
|
continue;
|
|
328
|
-
|
|
329
|
-
const subcommand = typeof details.subcommand === "string" ? details.subcommand : undefined;
|
|
330
|
-
if (isCloseCommand(command) && message.isError !== true) {
|
|
358
|
+
if (!closeAllApplied && ((isCloseCommand(command) && message.isError !== true) || batchCloseLifecycle)) {
|
|
331
359
|
restoredOrder += 1;
|
|
332
360
|
state.clearSession(sessionKey);
|
|
333
|
-
|
|
361
|
+
if (isCloseCommand(command) || batchCloseLifecycle?.endsClosed === true)
|
|
362
|
+
continue;
|
|
334
363
|
}
|
|
335
364
|
const tabTarget = getRestoredSessionTabTarget(details, command, subcommand);
|
|
336
365
|
const tabTargetUnknown = details.sessionTabTargetUnknown === true;
|
|
@@ -340,8 +369,11 @@ export class SessionPageState {
|
|
|
340
369
|
continue;
|
|
341
370
|
restoredOrder += 1;
|
|
342
371
|
if (tabTargetUnknown) {
|
|
343
|
-
state.refSnapshotInvalidations.delete(sessionKey);
|
|
344
372
|
state.refSnapshots.delete(sessionKey);
|
|
373
|
+
if (refSnapshotInvalidation)
|
|
374
|
+
state.refSnapshotInvalidations.set(sessionKey, { ...refSnapshotInvalidation, order: restoredOrder });
|
|
375
|
+
else
|
|
376
|
+
state.refSnapshotInvalidations.delete(sessionKey);
|
|
345
377
|
state.tabTargets.delete(sessionKey);
|
|
346
378
|
state.tabTargetUnknownOrders.set(sessionKey, restoredOrder);
|
|
347
379
|
continue;
|
|
@@ -438,6 +470,19 @@ export class SessionPageState {
|
|
|
438
470
|
this.tabTargetUnknownOrders.delete(sessionName);
|
|
439
471
|
this.tabTargets.delete(sessionName);
|
|
440
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
|
+
}
|
|
441
486
|
markPinning(sessionName, reason) {
|
|
442
487
|
this.tabPinningReasons.set(sessionName, reason);
|
|
443
488
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Build compact JSON-schema string enums without importing pi runtime helpers.
|
|
3
|
-
* Responsibilities: Mirror pi-ai StringEnum's `{ type: "string", enum: [...] }` shape while keeping extension startup imports light.
|
|
4
|
-
* Scope: Schema construction only.
|
|
5
|
-
*/
|
|
6
1
|
import { JsonSchema } from "./json-schema.js";
|
|
7
2
|
export function StringEnum(values, options) {
|
|
8
3
|
return JsonSchema.Unsafe({
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Create private temporary and persisted spill files for the pi-agent-browser extension without leaking artifacts broadly on disk.
|
|
3
|
-
* Responsibilities: Maintain a process-private temp root, stamp explicit ownership/protected-child markers, enforce an aggregate temp-artifact disk budget, create securely permissioned temp files, create session-scoped persisted spill files for resumable sessions, prune explicitly owned stale temp roots from prior runs without deleting protected children, and best-effort clean all owned roots on process exit.
|
|
4
|
-
* Scope: Artifact lifecycle helpers only; callers decide what data to write and when to delete or retain long-lived references.
|
|
5
|
-
* Usage: Imported by result/process helpers when they need secure spill files instead of world-readable shared tmp paths.
|
|
6
|
-
* Invariants/Assumptions: Temp artifacts live under the OS temp directory, each active run uses a dedicated 0700 directory, files are created with exclusive 0600 permissions, session-scoped persisted artifacts stay under the pi session directory, and stale pruning only touches roots with an explicit pi-agent-browser ownership marker.
|
|
7
|
-
*/
|
|
8
1
|
import { randomBytes } from "node:crypto";
|
|
9
2
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
10
3
|
import { chmod, mkdir, mkdtemp, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -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
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Provide the optional provider-backed `agent_browser_web_search` companion tool.
|
|
3
|
-
* Responsibilities: Define strict search input schema, resolve configured Brave/Exa credentials lazily, call the selected search API with cancellation/timeout, normalize compact results, and keep secrets out of content/details.
|
|
4
|
-
* Scope: Live web search only; browser automation remains in the `agent_browser` tool.
|
|
5
|
-
*/
|
|
6
1
|
import { JsonSchema } from "./json-schema.js";
|
|
7
2
|
import { WEB_SEARCH_PROMPT_GUIDELINE } from "./playbook.js";
|
|
8
3
|
import { StringEnum as localStringEnum } from "./string-enum-schema.js";
|