pi-agent-browser-native 0.2.64 → 0.2.66
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 +18 -0
- package/README.md +3 -3
- package/dist/extensions/agent-browser/index.js +18 -2
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +1 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +32 -23
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +3 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +32 -7
- package/dist/extensions/agent-browser/lib/playbook.js +2 -2
- package/dist/extensions/agent-browser/lib/runtime.js +1 -10
- package/docs/COMMAND_REFERENCE.md +2 -2
- package/docs/SUPPORT_MATRIX.md +8 -8
- package/docs/TOOL_CONTRACT.md +2 -2
- package/package.json +4 -4
- package/scripts/doctor.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.66 - 2026-07-11
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Updated the development and validation baseline to Pi 0.80.6, including the package doctor runtime floor and host-provided Pi peer package checks. Runtime browser behavior is unchanged.
|
|
10
|
+
|
|
11
|
+
## 0.2.65 - 2026-07-06
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fixed installed-package prompt guidance so the compiled `dist/` entrypoint points agents at package-root `README.md`, `docs/COMMAND_REFERENCE.md`, and `docs/TOOL_CONTRACT.md` instead of nonexistent `dist/docs/...` paths.
|
|
16
|
+
- Tightened managed-session tab recovery so snapshot-scoped `@e...` follow-up commands can re-select the snapshot page after about:blank/tab drift without adding routine tab-list probes to ordinary same-session commands.
|
|
17
|
+
- Resolved semantic role `click` / `fill` shortcuts from a fresh visible snapshot when exact current refs exist, avoiding stale cached-ref reuse after tab or page drift.
|
|
18
|
+
|
|
19
|
+
### Validation
|
|
20
|
+
|
|
21
|
+
- Ran `npm run verify`, focused extension-validation tests, `npm run docs`, `npm run typecheck`, `git diff --check`, and a thermo-nuclear reviewer subagent loop until it returned no material findings.
|
|
22
|
+
|
|
5
23
|
## 0.2.64 - 2026-07-01
|
|
6
24
|
|
|
7
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ The result is optimized for agent work:
|
|
|
89
89
|
|
|
90
90
|
## Fastest way to try it
|
|
91
91
|
|
|
92
|
-
Use Pi 0.80.
|
|
92
|
+
Use Pi 0.80.6 or newer. This package keeps Pi core imports as wildcard `peerDependencies` because Pi package docs require the host Pi install to provide those packages, and `pi-agent-browser-doctor` fails setup when `pi --version` is below the enforced runtime floor. The current release is audited and validated against the Pi 0.80.6 extension/package baseline, including Project Trust.
|
|
93
93
|
|
|
94
94
|
Install upstream `agent-browser` first and make sure it is on `PATH`:
|
|
95
95
|
|
|
@@ -703,8 +703,8 @@ These calls return plain text and stay stateless: the extension does not inject
|
|
|
703
703
|
|
|
704
704
|
<!-- agent-browser-playbook:start wrapper-tab-recovery -->
|
|
705
705
|
<!-- Generated from extensions/agent-browser/lib/playbook.ts. Run `npm run docs -- playbook write` to update. -->
|
|
706
|
-
- After
|
|
707
|
-
- After the wrapper observes tab-drift risk for a session (for example
|
|
706
|
+
- After open/goto/navigate calls with --profile, --restore, --session-name, or --state, agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch or reconnect.
|
|
707
|
+
- After the wrapper observes tab-drift risk for a session (for example open correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab or ref snapshot is known.
|
|
708
708
|
- For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.
|
|
709
709
|
- If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.
|
|
710
710
|
<!-- agent-browser-playbook:end wrapper-tab-recovery -->
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Usage: Loaded by pi through the package manifest in this package, or explicitly via `pi --no-extensions -e .` during local checkout development.
|
|
6
6
|
* Invariants/Assumptions: agent-browser is installed separately on PATH, the wrapper targets the current locally installed upstream version only, and no backward-compatibility shims are provided.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
10
11
|
import { Text } from "@earendil-works/pi-tui";
|
|
11
12
|
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
@@ -446,8 +447,23 @@ class AsyncExecutionQueue {
|
|
|
446
447
|
})();
|
|
447
448
|
}
|
|
448
449
|
}
|
|
450
|
+
function findPackageRoot(startDir) {
|
|
451
|
+
let currentDir = startDir;
|
|
452
|
+
while (true) {
|
|
453
|
+
const packageJsonPath = join(currentDir, "package.json");
|
|
454
|
+
if (existsSync(packageJsonPath)) {
|
|
455
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
456
|
+
if (packageJson.name === "pi-agent-browser-native")
|
|
457
|
+
return currentDir;
|
|
458
|
+
}
|
|
459
|
+
const parentDir = dirname(currentDir);
|
|
460
|
+
if (parentDir === currentDir)
|
|
461
|
+
return startDir;
|
|
462
|
+
currentDir = parentDir;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
449
465
|
function getInstalledDocsPaths() {
|
|
450
|
-
const packageRoot =
|
|
466
|
+
const packageRoot = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
|
|
451
467
|
return {
|
|
452
468
|
readmePath: join(packageRoot, "README.md"),
|
|
453
469
|
commandReferencePath: join(packageRoot, "docs", "COMMAND_REFERENCE.md"),
|
|
@@ -75,9 +75,4 @@ export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
|
|
|
75
75
|
];
|
|
76
76
|
export const LAUNCH_SCOPED_FLAGS = LAUNCH_SCOPED_FLAG_DEFINITIONS.map((definition) => definition.flag);
|
|
77
77
|
export const LAUNCH_SCOPED_FLAG_LABEL = LAUNCH_SCOPED_FLAGS.join(", ");
|
|
78
|
-
|
|
79
|
-
* The subset of launch-scoped flags that can restore browser/auth state with pre-existing tabs
|
|
80
|
-
* and are plausible wrong-active-tab sources after a fresh launch. These trigger post-open
|
|
81
|
-
* tab-correction (the `tab list` + re-select cycle).
|
|
82
|
-
*/
|
|
83
|
-
export const LAUNCH_SCOPED_TAB_CORRECTION_FLAGS = new Set(["--profile", "--session-name", "--restore", "--state"]);
|
|
78
|
+
export const OPEN_RESULT_TAB_CORRECTION_FLAGS = new Set(["--profile", "--restore", "--session-name", "--state"]);
|
|
@@ -15,7 +15,7 @@ import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextAction
|
|
|
15
15
|
import { resolveVisibleRefActionFromSnapshot } from "../../results/selector-recovery.js";
|
|
16
16
|
import { extractRefSnapshotFromData } from "../../session-page-state.js";
|
|
17
17
|
import { buildExecutionPlan, createFreshSessionName, extractCommandTokens, redactInvocationArgs, } from "../../runtime.js";
|
|
18
|
-
import { applyOpenResultTabCorrection, buildManagedSessionOutcome, buildPinnedBatchPlan, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, collectSessionTabSelection, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
|
|
18
|
+
import { applyOpenResultTabCorrection, buildManagedSessionOutcome, buildPinnedBatchPlan, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, collectAnySessionTabSelection, collectSessionTabSelection, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
|
|
19
19
|
import { parseBatchStdinJsonArray } from "../batch-stdin.js";
|
|
20
20
|
import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
|
|
21
21
|
import { prepareClickDispatchProbe } from "./click-dispatch.js";
|
|
@@ -278,14 +278,22 @@ export function validateStdinCommandContract(options) {
|
|
|
278
278
|
const commandLabel = options.command ? `\`${options.command}\`` : "the requested command";
|
|
279
279
|
return `agent_browser stdin is only supported for \`batch\`, \`eval --stdin\`, and \`auth save --password-stdin\`; remove stdin from ${commandLabel} or use one of those command forms.`;
|
|
280
280
|
}
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
function canResolveSemanticVisibleRef(compiled) {
|
|
282
|
+
return compiled !== undefined && compiled.locator === "role" && ["check", "click", "fill"].includes(compiled.action);
|
|
283
|
+
}
|
|
284
|
+
function resolveSemanticActionVisibleRefArgsFromSnapshot(compiled, snapshotData) {
|
|
285
|
+
if (!canResolveSemanticVisibleRef(compiled))
|
|
283
286
|
return undefined;
|
|
284
|
-
const
|
|
285
|
-
const resolution = resolveVisibleRefActionFromSnapshot({ allowFill: true, compiledAction: options.compiled, snapshotData });
|
|
287
|
+
const resolution = resolveVisibleRefActionFromSnapshot({ allowFill: true, compiledAction: compiled, snapshotData });
|
|
286
288
|
if (!resolution)
|
|
287
289
|
return undefined;
|
|
288
|
-
return { args: [...getCompiledSemanticActionSessionPrefix(
|
|
290
|
+
return { args: [...getCompiledSemanticActionSessionPrefix(compiled), ...resolution.args], snapshot: resolution.snapshot };
|
|
291
|
+
}
|
|
292
|
+
export async function resolveSemanticActionVisibleRefArgs(options) {
|
|
293
|
+
if (!options.compiled || !options.sessionName)
|
|
294
|
+
return undefined;
|
|
295
|
+
const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
|
|
296
|
+
return resolveSemanticActionVisibleRefArgsFromSnapshot(options.compiled, snapshotData);
|
|
289
297
|
}
|
|
290
298
|
export async function prepareBrowserRun(options) {
|
|
291
299
|
const { cwd, implicitSessionIdleTimeoutMs, onUpdate, params, signal, state } = options;
|
|
@@ -332,8 +340,14 @@ export async function prepareBrowserRun(options) {
|
|
|
332
340
|
managedSessionNamespace: state.managedSessionNamespace,
|
|
333
341
|
sessionMode,
|
|
334
342
|
});
|
|
343
|
+
const sessionStateKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
|
|
344
|
+
const priorSessionPageState = sessionPageState.get(sessionStateKey);
|
|
345
|
+
const priorSessionTabTarget = priorSessionPageState.tabTarget;
|
|
346
|
+
const sessionTabPinningReason = priorSessionPageState.pinningReason;
|
|
347
|
+
const priorRefSnapshotState = priorSessionPageState.refSnapshot;
|
|
348
|
+
const priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
|
|
335
349
|
let semanticActionVisibleRefResolution;
|
|
336
|
-
if (!executionPlan.validationError && executionPlan.managedSessionName !== freshSessionName) {
|
|
350
|
+
if (!executionPlan.validationError && executionPlan.managedSessionName !== freshSessionName && canResolveSemanticVisibleRef(compiledSemanticAction)) {
|
|
337
351
|
semanticActionVisibleRefResolution = await resolveSemanticActionVisibleRefArgs({
|
|
338
352
|
compiled: compiledSemanticAction,
|
|
339
353
|
cwd,
|
|
@@ -341,15 +355,15 @@ export async function prepareBrowserRun(options) {
|
|
|
341
355
|
sessionName: executionPlan.sessionName,
|
|
342
356
|
signal,
|
|
343
357
|
});
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
358
|
+
}
|
|
359
|
+
if (semanticActionVisibleRefResolution) {
|
|
360
|
+
executionPlan = buildExecutionPlan(semanticActionVisibleRefResolution.args, {
|
|
361
|
+
freshSessionName,
|
|
362
|
+
managedSessionActive: state.managedSessionActive,
|
|
363
|
+
managedSessionName: state.managedSessionName,
|
|
364
|
+
managedSessionNamespace: state.managedSessionNamespace,
|
|
365
|
+
sessionMode,
|
|
366
|
+
});
|
|
353
367
|
}
|
|
354
368
|
const redactedEffectiveArgs = redactInvocationArgs(executionPlan.effectiveArgs);
|
|
355
369
|
const redactedRecoveryHint = redactRecoveryHint(executionPlan.recoveryHint);
|
|
@@ -386,7 +400,6 @@ export async function prepareBrowserRun(options) {
|
|
|
386
400
|
commandTokens,
|
|
387
401
|
stdin: runtimeToolStdin,
|
|
388
402
|
});
|
|
389
|
-
const sessionStateKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
|
|
390
403
|
const traceOwnerGuardMessage = getTraceOwnerGuardMessage({
|
|
391
404
|
command: executionPlan.commandInfo.command,
|
|
392
405
|
sessionName: sessionStateKey,
|
|
@@ -430,11 +443,6 @@ export async function prepareBrowserRun(options) {
|
|
|
430
443
|
isError: true,
|
|
431
444
|
} };
|
|
432
445
|
}
|
|
433
|
-
const priorSessionPageState = sessionPageState.get(sessionStateKey);
|
|
434
|
-
const priorSessionTabTarget = priorSessionPageState.tabTarget;
|
|
435
|
-
const sessionTabPinningReason = priorSessionPageState.pinningReason;
|
|
436
|
-
const priorRefSnapshotState = priorSessionPageState.refSnapshot;
|
|
437
|
-
const priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
|
|
438
446
|
const resolvedSemanticActionRefSnapshot = semanticActionVisibleRefResolution?.snapshot
|
|
439
447
|
? { ...semanticActionVisibleRefResolution.snapshot, target: semanticActionVisibleRefResolution.snapshot.target ?? priorSessionTabTarget }
|
|
440
448
|
: undefined;
|
|
@@ -629,7 +637,8 @@ export async function prepareBrowserRun(options) {
|
|
|
629
637
|
sessionName: executionPlan.sessionName,
|
|
630
638
|
stdin: runtimeToolStdin,
|
|
631
639
|
})) {
|
|
632
|
-
const
|
|
640
|
+
const collectTabSelection = promptRefSnapshot && getGuardedRefUsage(commandTokens, runtimeToolStdin).length > 0 ? collectAnySessionTabSelection : collectSessionTabSelection;
|
|
641
|
+
const plannedSessionTabSelection = await collectTabSelection({
|
|
633
642
|
cwd,
|
|
634
643
|
namespace: executionPlan.namespace,
|
|
635
644
|
sessionName: executionPlan.sessionName,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, rm } from "node:fs/promises";
|
|
2
2
|
import { isCloseCommand, isNavigationObservableCommandName, isOpenNavigationCommand } from "../../command-taxonomy.js";
|
|
3
|
+
import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
|
|
3
4
|
import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
|
|
4
5
|
import { getAllowedDomainsViolation, parseAllowedDomainsPolicyFromArgs } from "../../navigation-policy.js";
|
|
5
6
|
import { analyzeNetworkSourceLookupResults, analyzeQaPresetResults, analyzeQaPresetTimeout, analyzeSourceLookupResults, buildQaCompactPassText, extractQaPageContext, redactNetworkSourceLookupAnalysis, } from "../../input-modes.js";
|
|
@@ -10,7 +11,7 @@ import { shouldCaptureSemanticActionNavigationSummary } from "../../results/pres
|
|
|
10
11
|
import { commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
11
12
|
import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../../temp.js";
|
|
12
13
|
import { isRecord } from "../../parsing.js";
|
|
13
|
-
import { createFreshSessionName, extractCommandTokens,
|
|
14
|
+
import { createFreshSessionName, extractCommandTokens, resolveManagedSessionState } from "../../runtime.js";
|
|
14
15
|
import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, closeManagedSession, collectOpenResultTabCorrection, collectSessionTabSelection, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, unwrapPinnedSessionBatchEnvelope, updateTraceOwnerState, } from "./session-state.js";
|
|
15
16
|
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
16
17
|
import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
|
|
@@ -194,7 +195,7 @@ export async function processBrowserOutput(input) {
|
|
|
194
195
|
presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
|
|
195
196
|
let overlayBlockerDiagnostic;
|
|
196
197
|
let openResultTabCorrection;
|
|
197
|
-
if (succeeded && prepared.executionPlan.sessionName &&
|
|
198
|
+
if (succeeded && prepared.executionPlan.sessionName && prepared.executionPlan.startupScopedFlags.some((flag) => OPEN_RESULT_TAB_CORRECTION_FLAGS.has(flag)) && isOpenNavigationCommand(prepared.executionPlan.commandInfo.command) && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens)) {
|
|
198
199
|
const targetTitle = extractStringResultField(presentationEnvelope?.data, "title");
|
|
199
200
|
const targetUrl = extractStringResultField(presentationEnvelope?.data, "url");
|
|
200
201
|
const plannedTabCorrection = await collectOpenResultTabCorrection({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal, targetTitle, targetUrl });
|
|
@@ -407,6 +407,13 @@ export function shouldCorrectSessionTabAfterCommand(options) {
|
|
|
407
407
|
options.command !== undefined &&
|
|
408
408
|
!isSessionTabPostCommandCorrectionExcludedCommand(options.command));
|
|
409
409
|
}
|
|
410
|
+
function getTabSelection(tab) {
|
|
411
|
+
if (typeof tab.tabId === "string" && tab.tabId.trim().length > 0)
|
|
412
|
+
return { selectedTab: tab.tabId.trim(), selectionKind: "tabId" };
|
|
413
|
+
if (typeof tab.label === "string" && tab.label.trim().length > 0)
|
|
414
|
+
return { selectedTab: tab.label.trim(), selectionKind: "label" };
|
|
415
|
+
return typeof tab.index === "number" ? { selectedTab: String(tab.index), selectionKind: "index" } : undefined;
|
|
416
|
+
}
|
|
410
417
|
function selectSessionTargetTab(options) {
|
|
411
418
|
return chooseOpenResultTabCorrection({
|
|
412
419
|
tabs: options.tabs,
|
|
@@ -414,6 +421,16 @@ function selectSessionTargetTab(options) {
|
|
|
414
421
|
targetUrl: options.target.url,
|
|
415
422
|
});
|
|
416
423
|
}
|
|
424
|
+
function selectAnySessionTargetTab(options) {
|
|
425
|
+
const targetUrl = typeof options.target.url === "string" ? normalizeComparableUrl(options.target.url) : undefined;
|
|
426
|
+
if (!targetUrl)
|
|
427
|
+
return undefined;
|
|
428
|
+
const matchingTabs = options.tabs.filter((tab) => normalizeComparableUrl(tab.url ?? "") === targetUrl);
|
|
429
|
+
const targetTitle = options.target.title?.trim() ?? "";
|
|
430
|
+
const selectedTab = (targetTitle ? matchingTabs.find((tab) => tab.title?.trim() === targetTitle) : undefined) ?? matchingTabs[0];
|
|
431
|
+
const selection = selectedTab ? getTabSelection(selectedTab) : undefined;
|
|
432
|
+
return selection ? { ...selection, ...(targetTitle ? { targetTitle } : {}), targetUrl } : undefined;
|
|
433
|
+
}
|
|
417
434
|
export function unwrapPinnedSessionBatchEnvelope(options) {
|
|
418
435
|
if (!options.envelope) {
|
|
419
436
|
return {};
|
|
@@ -509,13 +526,10 @@ export async function collectOpenResultTabCorrection(options) {
|
|
|
509
526
|
}));
|
|
510
527
|
return chooseOpenResultTabCorrection({ tabs, targetTitle, targetUrl });
|
|
511
528
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, namespace, sessionName, signal });
|
|
515
|
-
if (!isRecord(tabData) || !Array.isArray(tabData.tabs)) {
|
|
529
|
+
function mapTabData(tabData) {
|
|
530
|
+
if (!isRecord(tabData) || !Array.isArray(tabData.tabs))
|
|
516
531
|
return undefined;
|
|
517
|
-
|
|
518
|
-
const tabs = tabData.tabs.filter(isRecord).map((tab, index) => ({
|
|
532
|
+
return tabData.tabs.filter(isRecord).map((tab, index) => ({
|
|
519
533
|
active: tab.active === true,
|
|
520
534
|
index: typeof tab.index === "number" ? tab.index : index,
|
|
521
535
|
label: typeof tab.label === "string" ? tab.label : undefined,
|
|
@@ -523,7 +537,18 @@ export async function collectSessionTabSelection(options) {
|
|
|
523
537
|
title: typeof tab.title === "string" ? tab.title : undefined,
|
|
524
538
|
url: typeof tab.url === "string" ? tab.url : undefined,
|
|
525
539
|
}));
|
|
526
|
-
|
|
540
|
+
}
|
|
541
|
+
export async function collectSessionTabSelection(options) {
|
|
542
|
+
const { cwd, namespace, sessionName, signal, target } = options;
|
|
543
|
+
const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, namespace, sessionName, signal });
|
|
544
|
+
const tabs = mapTabData(tabData);
|
|
545
|
+
return tabs ? selectSessionTargetTab({ tabs, target }) : undefined;
|
|
546
|
+
}
|
|
547
|
+
export async function collectAnySessionTabSelection(options) {
|
|
548
|
+
const { cwd, namespace, sessionName, signal, target } = options;
|
|
549
|
+
const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, namespace, sessionName, signal });
|
|
550
|
+
const tabs = mapTabData(tabData);
|
|
551
|
+
return tabs ? selectAnySessionTargetTab({ tabs, target }) : undefined;
|
|
527
552
|
}
|
|
528
553
|
export async function applyOpenResultTabCorrection(options) {
|
|
529
554
|
const { correction, cwd, namespace, sessionName, signal } = options;
|
|
@@ -71,8 +71,8 @@ export const INSPECTION_TOOL_CALL_EXAMPLES = [
|
|
|
71
71
|
'{ "args": ["--version"] }',
|
|
72
72
|
];
|
|
73
73
|
export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
|
|
74
|
-
"After
|
|
75
|
-
"After the wrapper observes tab-drift risk for a session (for example
|
|
74
|
+
"After open/goto/navigate calls with --profile, --restore, --session-name, or --state, agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch or reconnect.",
|
|
75
|
+
"After the wrapper observes tab-drift risk for a session (for example open correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab or ref snapshot is known.",
|
|
76
76
|
"For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.",
|
|
77
77
|
"If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.",
|
|
78
78
|
];
|
|
@@ -13,7 +13,7 @@ import { findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from ".
|
|
|
13
13
|
import { GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, PREVALIDATED_VALUE_FLAGS, optionalGlobalValueFlagConsumesNext, } from "./argv-grammar.js";
|
|
14
14
|
import { needsManagedSession } from "./command-policy.js";
|
|
15
15
|
import { isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
16
|
-
import { LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL
|
|
16
|
+
import { LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL } from "./launch-scoped-flags.js";
|
|
17
17
|
export { extractCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo } from "./argv-descriptor.js";
|
|
18
18
|
import { isRecord } from "./parsing.js";
|
|
19
19
|
const OPENAI_HEADLESS_COMPAT_HOSTS = new Set(["chat.com", "chat.openai.com", "chatgpt.com"]);
|
|
@@ -773,15 +773,6 @@ export function getStartupScopedFlags(args) {
|
|
|
773
773
|
.map((definition) => definition.flag)
|
|
774
774
|
.filter((flag) => hasLaunchScopedFlagToken(args, flag));
|
|
775
775
|
}
|
|
776
|
-
export function hasLaunchScopedTabCorrectionFlag(args) {
|
|
777
|
-
return args.some((token) => {
|
|
778
|
-
for (const flag of LAUNCH_SCOPED_TAB_CORRECTION_FLAGS) {
|
|
779
|
-
if (token === flag || token.startsWith(`${flag}=`))
|
|
780
|
-
return true;
|
|
781
|
-
}
|
|
782
|
-
return false;
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
776
|
export function buildExecutionPlan(args, options) {
|
|
786
777
|
const invalidValueFlag = getInvalidValueFlagDetails(args);
|
|
787
778
|
const explicitNamespace = extractExplicitNamespace(args);
|
|
@@ -891,8 +891,8 @@ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGE
|
|
|
891
891
|
- If a `sessionMode: "fresh"` call fails (including upstream failure, timeout, missing binary, or **`qa`** reclassification after a nominally successful batch), read `details.managedSessionOutcome` before assuming where the next default call will go: `preserved` means the prior managed session remains current, while `abandoned` means no managed session became current. When the failure reason is not the fresh launch itself—for example `failureCategory: "qa-failure"`—`status`/`summary` may still describe the managed-session transition while `succeeded` on this object matches the final tool outcome.
|
|
892
892
|
<!-- agent-browser-playbook:start wrapper-tab-recovery -->
|
|
893
893
|
<!-- Generated from extensions/agent-browser/lib/playbook.ts. Run `npm run docs -- playbook write` to update. -->
|
|
894
|
-
- After
|
|
895
|
-
- After the wrapper observes tab-drift risk for a session (for example
|
|
894
|
+
- After open/goto/navigate calls with --profile, --restore, --session-name, or --state, agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch or reconnect.
|
|
895
|
+
- After the wrapper observes tab-drift risk for a session (for example open correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab or ref snapshot is known.
|
|
896
896
|
- For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.
|
|
897
897
|
- If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.
|
|
898
898
|
<!-- agent-browser-playbook:end wrapper-tab-recovery -->
|
package/docs/SUPPORT_MATRIX.md
CHANGED
|
@@ -51,23 +51,23 @@ Current summary:
|
|
|
51
51
|
|
|
52
52
|
## Verification evidence
|
|
53
53
|
|
|
54
|
-
Re-run the gates below before each release; this table records what the closure audit exercised.
|
|
54
|
+
Re-run the gates below before each release; this table records what the closure audit exercised. The Pi 0.80.6 refresh passed on 2026-07-11 against the current `agent-browser 0.31.1` baseline; older rows remain as prior release evidence.
|
|
55
55
|
|
|
56
56
|
| Gate | Evidence | Status |
|
|
57
57
|
| --- | --- | --- |
|
|
58
|
-
| Default local gate | `npm run verify` checks generated playbook drift, clean-builds generated `dist/`, runs `tsc --noEmit`, unit/fake tests, generated command-reference blocks, and live command-reference sampling. | **
|
|
58
|
+
| Default local gate | `npm run verify` checks generated playbook drift, clean-builds generated `dist/`, runs `tsc --noEmit`, unit/fake tests, generated command-reference blocks, and live command-reference sampling. | **Pi 0.80.6 refresh:** pass on 2026-07-11 (`npm run verify`; unit/fake suite plus live command-reference sampling). **Current for 0.31.1:** pass on 2026-06-26. |
|
|
59
59
|
| Pre-PR local gate | `npm run verify -- pre-pr` composes the default gate with package-content verification. Use before larger local handoffs or PR-ready claims when lifecycle/platform/live dogfood cost is not warranted. | Added 2026-06-10; orchestration is locked by `test/project-verify.test.ts` and does not change release mode. |
|
|
60
|
-
| Real upstream contract | `npm run verify -- real-upstream` runs the localhost fixture matrix against the real installed `agent-browser` matching the baseline. | **
|
|
61
|
-
| Packaged Pi smoke | `npm run verify -- package-pi` validates package contents, loads the packaged `agent_browser` tool without requiring optional Brave config, and executes fake-upstream `--version`. | **
|
|
60
|
+
| Real upstream contract | `npm run verify -- real-upstream` runs the localhost fixture matrix against the real installed `agent-browser` matching the baseline. | **Pi 0.80.6 refresh:** pass on 2026-07-11 (`npm run verify -- real-upstream`) against installed `agent-browser 0.31.1`. **Current for 0.31.1:** pass on 2026-06-26; the known CSS-selector click probe constraint remains documented in the real-upstream fixture. |
|
|
61
|
+
| Packaged Pi smoke | `npm run verify -- package-pi` validates package contents, loads the packaged `agent_browser` tool without requiring optional Brave config, and executes fake-upstream `--version`. | **Pi 0.80.6 refresh:** pass on 2026-07-11 (`npm run verify -- package-pi`; packaged tool load and fake-upstream invocation passed). **Current for 0.31.1:** pass on 2026-06-26 as part of `npm run verify -- release`. |
|
|
62
62
|
| Startup profile | `npm run verify -- startup-profile --samples <n>` clean-builds generated `dist/`, records direct package entrypoint import/factory timing in fresh Node processes, and writes `.artifacts/startup-profile/latest.json`. It must not launch Pi, tmux, mise, npm, browsers, or `agent-browser`; full Pi TUI ready-prompt profiling is intentionally excluded after it proved too invasive for routine verification. Run this opt-in evidence when package layout, the compiled entrypoint, top-level imports, schema registration, or prompt/config startup logic changes. | **Current for compiled entrypoint:** pass on 2026-06-21 (`npm run verify -- startup-profile --samples 3`; direct compiled entrypoint import+factory median 47.3 ms, below the 250 ms budget). Full-Pi startup numbers from the unsafe tmux profiler are not accepted as ongoing release evidence. |
|
|
63
|
-
| Deterministic dogfood smoke | `npm run verify -- dogfood` (`scripts/verify-agent-browser-dogfood.ts`) drives the native wrapper against a local file fixture through top-level `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close with the real `agent-browser` on `PATH`. | **
|
|
63
|
+
| Deterministic dogfood smoke | `npm run verify -- dogfood` (`scripts/verify-agent-browser-dogfood.ts`) drives the native wrapper against a local file fixture through top-level `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close with the real `agent-browser` on `PATH`. | **Pi 0.80.6 refresh:** pass on 2026-07-11 (`npm run verify -- dogfood`). **Current for 0.31.1:** pass on 2026-06-26. |
|
|
64
64
|
| Efficiency benchmark | `npm run verify -- benchmark` runs deterministic browser workflow accounting plus focused benchmark tests, including JSONL sampling fixtures and job/qa/sourceLookup/networkSourceLookup/Electron scenario coverage. | **Current:** pass on 2026-06-21 (`npm run verify -- benchmark`; 13/13 deterministic scenarios passed). |
|
|
65
|
-
| Crabbox platform smoke | `npm run check:platform-smoke` syntax-checks the harness and cheap invariants. `npm run smoke:platform:ubuntu-image` builds the project-owned Linux image, `npm run smoke:platform:doctor` checks Crabbox 0.26.0+ and local target readiness, and `npm run smoke:platform:all` runs doctor first, then fast target-local `platform-build` (`npm run verify -- platform-target`, pack, clean Pi install) plus `browser-dogfood-smoke` on Crabbox `macos`, `ubuntu`, and `windows-native`; see [`platform-smoke.md`](platform-smoke.md). Target artifacts include Crabbox/provider/work-root metadata, and release review also checks provider-specific `crabbox list` commands for leftover leases/clones. | **
|
|
65
|
+
| Crabbox platform smoke | `npm run check:platform-smoke` syntax-checks the harness and cheap invariants. `npm run smoke:platform:ubuntu-image` builds the project-owned Linux image, `npm run smoke:platform:doctor` checks Crabbox 0.26.0+ and local target readiness, and `npm run smoke:platform:all` runs doctor first, then fast target-local `platform-build` (`npm run verify -- platform-target`, pack, clean Pi install) plus `browser-dogfood-smoke` on Crabbox `macos`, `ubuntu`, and `windows-native`; see [`platform-smoke.md`](platform-smoke.md). Target artifacts include Crabbox/provider/work-root metadata, and release review also checks provider-specific `crabbox list` commands for leftover leases/clones. | **Pi 0.80.6 refresh:** pass on 2026-07-11 (`npm run smoke:platform:doctor` and the macOS, Ubuntu, and native-Windows platform smokes). **Current for 0.31.1:** pass on 2026-06-26 inside `npm run verify -- release`. |
|
|
66
66
|
| `verify -- release` / `prepublishOnly` | `npm run verify -- release` chains the default gate with the configured-source lifecycle harness, packaged Pi smoke, and the release-blocking Crabbox platform matrix (`verifySteps` `release` in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs)). `package.json` `prepublishOnly` runs that compose before `npm pack --dry-run` during `npm publish`. It intentionally omits standalone real-upstream, host-only dogfood, and benchmark modes—see [`RELEASE.md`](RELEASE.md#pre-release-checks). | **Current for 0.31.1:** pass on 2026-06-26 (`npm run verify -- release`), including default unit/fake gate, generated docs checks, live command-reference sampling, lifecycle harness, packaged Pi smoke, and macOS/Ubuntu/native-Windows Crabbox platform smoke. |
|
|
67
|
-
| Configured-source lifecycle | `npm run verify -- lifecycle` (`scripts/verify-lifecycle.mjs`) drives `/reload`, closes and relaunches Pi with the same exact `--session-id`, checks the JSONL session header id, session continuity, slash-command sentinel tokens (`v1` before reload and `v2` after full relaunch because compiled JS package modules are process-cached), persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification with a fake upstream on `PATH`. Default Pi model is `zai/glm-5.2`; default per-step wait is **180000 ms** (`DEFAULT_TIMEOUT_MS`); override model with `--model <id>` and waits with `--timeout-ms <ms>`. Passthrough flags in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs): `--keep-artifacts`, `--model`, `--verbose`, and `--timeout-ms` plus a value (for example `npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal --keep-artifacts --verbose --timeout-ms 600000`). | **
|
|
67
|
+
| Configured-source lifecycle | `npm run verify -- lifecycle` (`scripts/verify-lifecycle.mjs`) drives `/reload`, closes and relaunches Pi with the same exact `--session-id`, checks the JSONL session header id, session continuity, slash-command sentinel tokens (`v1` before reload and `v2` after full relaunch because compiled JS package modules are process-cached), persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification with a fake upstream on `PATH`. Default Pi model is `zai/glm-5.2`; default per-step wait is **180000 ms** (`DEFAULT_TIMEOUT_MS`); override model with `--model <id>` and waits with `--timeout-ms <ms>`. Passthrough flags in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs): `--keep-artifacts`, `--model`, `--verbose`, and `--timeout-ms` plus a value (for example `npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal --keep-artifacts --verbose --timeout-ms 600000`). | **Pi 0.80.6 refresh:** pass on 2026-07-11 (`npm run verify -- lifecycle`; reload/relaunch continuity and failure-patch assertions passed). **Current for 0.31.1:** pass on 2026-06-26 as part of `npm run verify -- release`. |
|
|
68
68
|
| Quick isolated Pi smoke | `pi --approve --no-extensions --no-skills -e . --tools agent_browser` from trusted repo root; native `agent_browser` only. | **Current for 0.29.1 / Pi 0.79.9:** pass on 2026-06-21 via tmux with `pi --approve --no-extensions --no-skills -e . --model openai-codex/gpt-5.5:minimal --tools agent_browser`. Covered the public Sauce Demo checkout-overview flow with clean context, native sorting/click/fill flow, screenshot and recording evidence, console/page-error/network diagnostics, and no order placement. A one-line screenshot-plus-recording close-guard smoke on `https://example.com` passed after rebuilding `dist/`, proving close succeeds after both artifact paths are verified. Temp artifacts and tmux sessions were cleaned after evidence capture. |
|
|
69
69
|
|
|
70
|
-
Runtime floor note: package metadata keeps Pi core package peer ranges wildcard per installed Pi package docs, but `pi-agent-browser-doctor` / `npm run doctor` treats `pi --version` below 0.
|
|
70
|
+
Runtime floor note: package metadata keeps Pi core package peer ranges wildcard per installed Pi package docs, but `pi-agent-browser-doctor` / `npm run doctor` treats `pi --version` below 0.80.6 as a setup failure. `npm run doctor` passed against Pi 0.80.6 on 2026-07-11. This keeps package dependency shape aligned with Pi package loading while still making unsupported host Pi versions a release and first-run blocker.
|
|
71
71
|
|
|
72
72
|
## Baseline checklist by inventory section
|
|
73
73
|
|
package/docs/TOOL_CONTRACT.md
CHANGED
|
@@ -877,8 +877,8 @@ If `agent-browser` is not on `PATH`, fail with a message that:
|
|
|
877
877
|
- pass explicit `--profile` straight through to upstream `agent-browser`; no profile-cloning or isolation layer is added in v1
|
|
878
878
|
<!-- agent-browser-playbook:start wrapper-tab-recovery -->
|
|
879
879
|
<!-- Generated from extensions/agent-browser/lib/playbook.ts. Run `npm run docs -- playbook write` to update. -->
|
|
880
|
-
- After
|
|
881
|
-
- After the wrapper observes tab-drift risk for a session (for example
|
|
880
|
+
- After open/goto/navigate calls with --profile, --restore, --session-name, or --state, agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch or reconnect.
|
|
881
|
+
- After the wrapper observes tab-drift risk for a session (for example open correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab or ref snapshot is known.
|
|
882
882
|
- For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.
|
|
883
883
|
- If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.
|
|
884
884
|
<!-- agent-browser-playbook:end wrapper-tab-recovery -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agent-browser-native",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.66",
|
|
4
4
|
"description": "pi extension that exposes agent-browser as a native tool for browser automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Mitch Fultz (https://github.com/fitchmultz)",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"typebox": "*"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@earendil-works/pi-ai": "^0.80.
|
|
67
|
-
"@earendil-works/pi-coding-agent": "^0.80.
|
|
68
|
-
"@earendil-works/pi-tui": "^0.80.
|
|
66
|
+
"@earendil-works/pi-ai": "^0.80.6",
|
|
67
|
+
"@earendil-works/pi-coding-agent": "^0.80.6",
|
|
68
|
+
"@earendil-works/pi-tui": "^0.80.6",
|
|
69
69
|
"@types/node": "^25.9.3",
|
|
70
70
|
"tsx": "^4.21.0",
|
|
71
71
|
"typebox": "^1.1.38",
|
package/scripts/doctor.mjs
CHANGED
|
@@ -25,7 +25,7 @@ const EXTENSION_ENTRYPOINTS = Object.freeze([
|
|
|
25
25
|
"dist/extensions/agent-browser/index.js",
|
|
26
26
|
]);
|
|
27
27
|
const EXPECTED_VERSION = CAPABILITY_BASELINE.targetVersion;
|
|
28
|
-
const MINIMUM_PI_VERSION = "0.80.
|
|
28
|
+
const MINIMUM_PI_VERSION = "0.80.6";
|
|
29
29
|
const DEFAULT_AGENT_DIR = resolve(homedir(), ".pi/agent");
|
|
30
30
|
const THIS_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
31
31
|
|
|
@@ -309,7 +309,7 @@ async function checkPiVersion({ runPi }) {
|
|
|
309
309
|
status: "fail",
|
|
310
310
|
title: `Pi ${MINIMUM_PI_VERSION} or newer is required; found ${version || "<empty>"}.`,
|
|
311
311
|
lines: [
|
|
312
|
-
|
|
312
|
+
`This release enforces the Pi ${MINIMUM_PI_VERSION} runtime floor through the read-only doctor and release/package validation because it depends on Project Trust, package loading, session lifecycle, TUI rendering, and tool_result patch behavior from that baseline.`,
|
|
313
313
|
"Update Pi before using this package or running lifecycle/package validation.",
|
|
314
314
|
],
|
|
315
315
|
};
|