pi-agent-browser-native 0.2.60 → 0.2.62
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 +30 -0
- package/README.md +11 -11
- package/dist/extensions/agent-browser/index.js +51 -30
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +29 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +10 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +7 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +1 -23
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +5 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +2 -1
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +25 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +4 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +25 -17
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +14 -11
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +3 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +5 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +43 -27
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +66 -54
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +24 -15
- package/dist/extensions/agent-browser/lib/playbook.js +4 -4
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +8 -7
- package/dist/extensions/agent-browser/lib/results/presentation.js +2 -1
- package/dist/extensions/agent-browser/lib/runtime.js +62 -10
- package/dist/extensions/agent-browser/lib/session-page-state.js +14 -7
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/COMMAND_REFERENCE.md +66 -24
- package/docs/SUPPORT_MATRIX.md +10 -8
- package/docs/TOOL_CONTRACT.md +24 -24
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +36 -4
|
@@ -5,7 +5,7 @@ import { formatSessionArtifactRetentionSummary } from "../artifact-manifest.js";
|
|
|
5
5
|
import { classifyAgentBrowserFailureCategory } from "../categories.js";
|
|
6
6
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
7
7
|
import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../network-routes.js";
|
|
8
|
-
import { withOptionalSessionArgs } from "../next-actions.js";
|
|
8
|
+
import { applyNamespaceToNextActions, withOptionalSessionArgs } from "../next-actions.js";
|
|
9
9
|
import { stringifyModelFacing } from "./common.js";
|
|
10
10
|
import { buildArtifactVerificationSummary, classifyPresentationSuccessCategory, manifestHasNewNoticeWorthyEntries } from "./artifacts.js";
|
|
11
11
|
import { formatBatchStepCommand, getPresentationImages, getPresentationPaths, getPresentationText, isStringArray } from "./content.js";
|
|
@@ -181,7 +181,7 @@ function formatBatchStepsText(steps) {
|
|
|
181
181
|
return lines.join("\n\n");
|
|
182
182
|
}
|
|
183
183
|
async function buildBatchStepPresentation(options) {
|
|
184
|
-
const { artifactManifest, artifactRequest, buildNestedToolPresentation, cwd, index, item, networkRoutes, persistentArtifactStore, sessionName } = options;
|
|
184
|
+
const { artifactManifest, artifactRequest, buildNestedToolPresentation, cwd, index, item, namespace, networkRoutes, persistentArtifactStore, sessionName } = options;
|
|
185
185
|
const command = isStringArray(item.command) ? item.command : undefined;
|
|
186
186
|
const redactedCommand = command ? redactInvocationArgs(command) : undefined;
|
|
187
187
|
const commandText = formatBatchStepCommand(hasModelFacingArgRedaction(redactedCommand) ? redactedCommand : command, index);
|
|
@@ -196,13 +196,13 @@ async function buildBatchStepPresentation(options) {
|
|
|
196
196
|
errorText,
|
|
197
197
|
});
|
|
198
198
|
const confirmationRequired = detectConfirmationRequired(item.error);
|
|
199
|
-
const nextActions = mergePresentationNextActions(buildAgentBrowserNextActions({
|
|
199
|
+
const nextActions = applyNamespaceToNextActions(mergePresentationNextActions(buildAgentBrowserNextActions({
|
|
200
200
|
args: command,
|
|
201
201
|
command: command?.[0],
|
|
202
202
|
confirmationId: confirmationRequired?.id,
|
|
203
203
|
failureCategory,
|
|
204
204
|
resultCategory: "failure",
|
|
205
|
-
}), isWaitTextAssertionCommand(command) ? [buildWaitTextAssertionFailureNextAction(sessionName)] : undefined);
|
|
205
|
+
}), isWaitTextAssertionCommand(command) ? [buildWaitTextAssertionFailureNextAction(sessionName)] : undefined), namespace);
|
|
206
206
|
const presentation = {
|
|
207
207
|
content: [{ type: "text", text: errorText }],
|
|
208
208
|
failureCategory,
|
|
@@ -254,7 +254,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
254
254
|
});
|
|
255
255
|
const text = getPresentationText(presentation) || presentation.summary;
|
|
256
256
|
const stepSucceeded = presentation.resultCategory !== "failure";
|
|
257
|
-
const nextActions = presentation.nextActions ?? buildAgentBrowserNextActions({
|
|
257
|
+
const nextActions = applyNamespaceToNextActions(presentation.nextActions ?? buildAgentBrowserNextActions({
|
|
258
258
|
artifacts: presentation.artifacts,
|
|
259
259
|
args: command,
|
|
260
260
|
command: command?.[0],
|
|
@@ -262,7 +262,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
262
262
|
resultCategory: stepSucceeded ? "success" : "failure",
|
|
263
263
|
savedFilePath: presentation.savedFilePath,
|
|
264
264
|
successCategory: presentation.successCategory,
|
|
265
|
-
});
|
|
265
|
+
}), namespace);
|
|
266
266
|
const pageChangeSummary = buildPageChangeSummary({
|
|
267
267
|
artifacts: presentation.artifacts,
|
|
268
268
|
commandInfo: commandInfoWithTokens,
|
|
@@ -299,7 +299,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
299
299
|
};
|
|
300
300
|
}
|
|
301
301
|
export async function buildBatchPresentation(options) {
|
|
302
|
-
const { artifactRequests, buildNestedToolPresentation, cwd, data, networkRoutes, persistentArtifactStore, sessionName, summary } = options;
|
|
302
|
+
const { artifactRequests, buildNestedToolPresentation, cwd, data, namespace, networkRoutes, persistentArtifactStore, sessionName, summary } = options;
|
|
303
303
|
const steps = [];
|
|
304
304
|
const protectedPersistentPaths = [];
|
|
305
305
|
let currentArtifactManifest = options.artifactManifest;
|
|
@@ -312,6 +312,7 @@ export async function buildBatchPresentation(options) {
|
|
|
312
312
|
cwd,
|
|
313
313
|
index,
|
|
314
314
|
item,
|
|
315
|
+
namespace,
|
|
315
316
|
networkRoutes: currentNetworkRoutes,
|
|
316
317
|
persistentArtifactStore: persistentArtifactStore ? { ...persistentArtifactStore, protectedPaths: protectedPersistentPaths } : undefined,
|
|
317
318
|
sessionName,
|
|
@@ -37,7 +37,7 @@ function shouldAddAnnotatedScreenshotGuidance(commandInfo, args) {
|
|
|
37
37
|
return commandInfo.command === "screenshot" && (args?.includes("--annotate") ?? false);
|
|
38
38
|
}
|
|
39
39
|
export async function buildToolPresentation(options) {
|
|
40
|
-
const { args, artifactManifest, artifactRequest, commandInfo, compiledSemanticAction, cwd, envelope, errorText, networkRouteDiagnostics, networkRoutes, persistentArtifactStore, sessionName, } = options;
|
|
40
|
+
const { args, artifactManifest, artifactRequest, commandInfo, compiledSemanticAction, cwd, envelope, errorText, namespace, networkRouteDiagnostics, networkRoutes, persistentArtifactStore, sessionName, } = options;
|
|
41
41
|
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens: extractCommandTokens(args) };
|
|
42
42
|
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfoWithTokens, compiledSemanticAction);
|
|
43
43
|
if (errorText) {
|
|
@@ -58,6 +58,7 @@ export async function buildToolPresentation(options) {
|
|
|
58
58
|
buildNestedToolPresentation: buildToolPresentation,
|
|
59
59
|
cwd,
|
|
60
60
|
data,
|
|
61
|
+
namespace,
|
|
61
62
|
networkRoutes,
|
|
62
63
|
persistentArtifactStore,
|
|
63
64
|
sessionName,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
11
|
import { basename } from "node:path";
|
|
12
12
|
import { findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
|
|
13
|
-
import { GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, PREVALIDATED_VALUE_FLAGS, } from "./argv-grammar.js";
|
|
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
16
|
import { LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL, LAUNCH_SCOPED_TAB_CORRECTION_FLAGS } from "./launch-scoped-flags.js";
|
|
@@ -326,18 +326,22 @@ export function getImplicitSessionCloseTimeoutMs(env = process.env) {
|
|
|
326
326
|
return parseTimeoutMs(env[IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV], 0) ?? DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS;
|
|
327
327
|
}
|
|
328
328
|
export function resolveManagedSessionState(options) {
|
|
329
|
-
const { command, managedSessionName, priorActive, priorSessionName, succeeded } = options;
|
|
329
|
+
const { command, managedSessionName, managedSessionNamespace, priorActive, priorNamespace, priorSessionName, succeeded } = options;
|
|
330
330
|
if (!managedSessionName) {
|
|
331
|
-
return { active: priorActive, sessionName: priorSessionName };
|
|
331
|
+
return { active: priorActive, ...(priorNamespace ? { namespace: priorNamespace } : {}), sessionName: priorSessionName };
|
|
332
332
|
}
|
|
333
333
|
if (isCloseCommand(command) && managedSessionName === priorSessionName) {
|
|
334
|
-
|
|
334
|
+
if (managedSessionNamespace !== priorNamespace)
|
|
335
|
+
return { active: priorActive, ...(priorNamespace ? { namespace: priorNamespace } : {}), sessionName: priorSessionName };
|
|
336
|
+
const namespace = succeeded ? undefined : priorNamespace;
|
|
337
|
+
return { active: succeeded ? false : priorActive, ...(namespace ? { namespace } : {}), sessionName: priorSessionName };
|
|
335
338
|
}
|
|
336
339
|
if (!succeeded) {
|
|
337
|
-
return { active: priorActive, sessionName: priorSessionName };
|
|
340
|
+
return { active: priorActive, ...(priorNamespace ? { namespace: priorNamespace } : {}), sessionName: priorSessionName };
|
|
338
341
|
}
|
|
339
342
|
return {
|
|
340
343
|
active: true,
|
|
344
|
+
...(managedSessionNamespace ? { namespace: managedSessionNamespace } : {}),
|
|
341
345
|
replacedSessionName: priorActive && priorSessionName !== managedSessionName ? priorSessionName : undefined,
|
|
342
346
|
sessionName: managedSessionName,
|
|
343
347
|
};
|
|
@@ -395,13 +399,13 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
395
399
|
let closedSessionName;
|
|
396
400
|
let freshSessionOrdinal = 0;
|
|
397
401
|
const freshSessionRanks = new Map();
|
|
398
|
-
const applyManagedClose = (sessionName) => {
|
|
402
|
+
const applyManagedClose = (sessionName, namespace) => {
|
|
399
403
|
const restoreRank = getManagedSessionRestoreRank({
|
|
400
404
|
fallbackSessionName,
|
|
401
405
|
freshSessionRanks,
|
|
402
406
|
sessionName,
|
|
403
407
|
});
|
|
404
|
-
if (restoreRank === undefined || sessionName !== restoredState.sessionName)
|
|
408
|
+
if (restoreRank === undefined || sessionName !== restoredState.sessionName || namespace !== restoredState.namespace)
|
|
405
409
|
return;
|
|
406
410
|
restoredState = { active: false, sessionName: restoredState.sessionName };
|
|
407
411
|
closedSessionName = sessionName;
|
|
@@ -427,6 +431,7 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
427
431
|
}
|
|
428
432
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
429
433
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
434
|
+
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
430
435
|
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
431
436
|
const usedImplicitSession = details.usedImplicitSession === true;
|
|
432
437
|
const command = typeof details.command === "string" ? details.command : parseCommandInfo(args).command;
|
|
@@ -468,7 +473,7 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
468
473
|
const succeeded = outcomeRepresentsActiveCurrentSession ? true : messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
|
|
469
474
|
if (commandClosesSession) {
|
|
470
475
|
if (succeeded)
|
|
471
|
-
applyManagedClose(managedSessionName);
|
|
476
|
+
applyManagedClose(managedSessionName, namespace);
|
|
472
477
|
continue;
|
|
473
478
|
}
|
|
474
479
|
const staleCompletion = succeeded && restoreRank < activeRestoreRank;
|
|
@@ -478,7 +483,9 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
478
483
|
restoredState = resolveManagedSessionState({
|
|
479
484
|
command,
|
|
480
485
|
managedSessionName,
|
|
486
|
+
managedSessionNamespace: namespace,
|
|
481
487
|
priorActive: restoredState.active,
|
|
488
|
+
priorNamespace: restoredState.namespace,
|
|
482
489
|
priorSessionName: restoredState.sessionName,
|
|
483
490
|
succeeded,
|
|
484
491
|
});
|
|
@@ -720,6 +727,31 @@ export function extractExplicitSessionName(args) {
|
|
|
720
727
|
}
|
|
721
728
|
return undefined;
|
|
722
729
|
}
|
|
730
|
+
export function extractExplicitNamespace(args) {
|
|
731
|
+
for (const [index, token] of args.entries()) {
|
|
732
|
+
if (token === "--namespace") {
|
|
733
|
+
return args[index + 1];
|
|
734
|
+
}
|
|
735
|
+
if (token.startsWith("--namespace=")) {
|
|
736
|
+
return token.slice("--namespace=".length);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return undefined;
|
|
740
|
+
}
|
|
741
|
+
function stripExplicitNamespaceArgs(args) {
|
|
742
|
+
const stripped = [];
|
|
743
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
744
|
+
const token = args[index];
|
|
745
|
+
if (token === "--namespace") {
|
|
746
|
+
index += 1;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (token.startsWith("--namespace="))
|
|
750
|
+
continue;
|
|
751
|
+
stripped.push(token);
|
|
752
|
+
}
|
|
753
|
+
return stripped;
|
|
754
|
+
}
|
|
723
755
|
function hasLaunchScopedFlagToken(args, flag) {
|
|
724
756
|
const commandStartIndex = findCommandStartIndex(args);
|
|
725
757
|
const command = commandStartIndex === undefined ? undefined : args[commandStartIndex];
|
|
@@ -728,6 +760,8 @@ function hasLaunchScopedFlagToken(args, flag) {
|
|
|
728
760
|
return false;
|
|
729
761
|
if (flag === "--auto-connect")
|
|
730
762
|
return isBooleanFlagEnabled(args, flag);
|
|
763
|
+
if (flag === "--restore" && token === "--restore" && optionalGlobalValueFlagConsumesNext(flag, args[index + 1]))
|
|
764
|
+
return true;
|
|
731
765
|
if (flag === "--state" && command === "wait" && commandStartIndex !== undefined && index > commandStartIndex) {
|
|
732
766
|
return false;
|
|
733
767
|
}
|
|
@@ -750,13 +784,15 @@ export function hasLaunchScopedTabCorrectionFlag(args) {
|
|
|
750
784
|
}
|
|
751
785
|
export function buildExecutionPlan(args, options) {
|
|
752
786
|
const invalidValueFlag = getInvalidValueFlagDetails(args);
|
|
753
|
-
const
|
|
787
|
+
const explicitNamespace = extractExplicitNamespace(args);
|
|
788
|
+
const startupScopedFlags = getStartupScopedFlags(args).filter((flag) => !(flag === "--namespace" && explicitNamespace === options.managedSessionNamespace));
|
|
754
789
|
const plainTextInspection = isPlainTextInspectionArgs(args);
|
|
755
790
|
const argvDescriptor = parseArgvDescriptor(args);
|
|
756
791
|
const commandTokens = argvDescriptor.commandTokens;
|
|
757
792
|
const commandInfo = argvDescriptor.commandInfo;
|
|
758
793
|
const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
|
|
759
794
|
const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
|
|
795
|
+
let namespace = explicitNamespace;
|
|
760
796
|
if (invalidValueFlag) {
|
|
761
797
|
return {
|
|
762
798
|
commandInfo: {},
|
|
@@ -772,6 +808,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
772
808
|
return {
|
|
773
809
|
commandInfo,
|
|
774
810
|
effectiveArgs,
|
|
811
|
+
namespace,
|
|
775
812
|
plainTextInspection,
|
|
776
813
|
startupScopedFlags,
|
|
777
814
|
usedImplicitSession: false,
|
|
@@ -779,7 +816,12 @@ export function buildExecutionPlan(args, options) {
|
|
|
779
816
|
}
|
|
780
817
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
781
818
|
const shouldCreateFreshManagedSession = !explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
|
|
819
|
+
let argsToAppend = args;
|
|
782
820
|
const compatibilityWorkaround = getCompatibilityWorkaround(args, commandInfo);
|
|
821
|
+
if (explicitSessionName && explicitNamespace) {
|
|
822
|
+
effectiveArgs.push("--namespace", explicitNamespace);
|
|
823
|
+
argsToAppend = stripExplicitNamespaceArgs(args);
|
|
824
|
+
}
|
|
783
825
|
let managedSessionName;
|
|
784
826
|
let recoveryHint;
|
|
785
827
|
let sessionName = explicitSessionName;
|
|
@@ -799,26 +841,36 @@ export function buildExecutionPlan(args, options) {
|
|
|
799
841
|
].join(" ");
|
|
800
842
|
}
|
|
801
843
|
else {
|
|
844
|
+
namespace = explicitNamespace ?? options.managedSessionNamespace;
|
|
845
|
+
if (namespace)
|
|
846
|
+
effectiveArgs.push("--namespace", namespace);
|
|
802
847
|
effectiveArgs.push("--session", options.managedSessionName);
|
|
848
|
+
if (explicitNamespace)
|
|
849
|
+
argsToAppend = stripExplicitNamespaceArgs(args);
|
|
803
850
|
managedSessionName = options.managedSessionName;
|
|
804
851
|
sessionName = options.managedSessionName;
|
|
805
852
|
usedImplicitSession = true;
|
|
806
853
|
}
|
|
807
854
|
}
|
|
808
855
|
else if (shouldCreateFreshManagedSession && commandNeedsManagedSession) {
|
|
856
|
+
if (namespace)
|
|
857
|
+
effectiveArgs.push("--namespace", namespace);
|
|
809
858
|
effectiveArgs.push("--session", options.freshSessionName);
|
|
859
|
+
if (explicitNamespace)
|
|
860
|
+
argsToAppend = stripExplicitNamespaceArgs(args);
|
|
810
861
|
managedSessionName = options.freshSessionName;
|
|
811
862
|
sessionName = options.freshSessionName;
|
|
812
863
|
}
|
|
813
864
|
if (compatibilityWorkaround) {
|
|
814
865
|
effectiveArgs.push("--user-agent", getDefaultHeadlessCompatUserAgent());
|
|
815
866
|
}
|
|
816
|
-
effectiveArgs.push(...
|
|
867
|
+
effectiveArgs.push(...argsToAppend);
|
|
817
868
|
return {
|
|
818
869
|
commandInfo,
|
|
819
870
|
compatibilityWorkaround,
|
|
820
871
|
effectiveArgs,
|
|
821
872
|
managedSessionName,
|
|
873
|
+
namespace,
|
|
822
874
|
plainTextInspection,
|
|
823
875
|
recoveryHint,
|
|
824
876
|
sessionName,
|
|
@@ -291,6 +291,11 @@ function stripRefSnapshotOrder(snapshot) {
|
|
|
291
291
|
function stripRefSnapshotInvalidationOrder(invalidation) {
|
|
292
292
|
return invalidation ? { reason: invalidation.reason, summary: invalidation.summary } : undefined;
|
|
293
293
|
}
|
|
294
|
+
export function getSessionPageStateKey(sessionName, namespace) {
|
|
295
|
+
if (!sessionName)
|
|
296
|
+
return undefined;
|
|
297
|
+
return namespace ? `${namespace}\u0000${sessionName}` : sessionName;
|
|
298
|
+
}
|
|
294
299
|
export class SessionPageState {
|
|
295
300
|
refSnapshotInvalidations = new Map();
|
|
296
301
|
refSnapshots = new Map();
|
|
@@ -310,13 +315,15 @@ export class SessionPageState {
|
|
|
310
315
|
if (!details)
|
|
311
316
|
continue;
|
|
312
317
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
313
|
-
|
|
318
|
+
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
319
|
+
const sessionKey = getSessionPageStateKey(sessionName, namespace);
|
|
320
|
+
if (!sessionKey)
|
|
314
321
|
continue;
|
|
315
322
|
const command = typeof details.command === "string" ? details.command : undefined;
|
|
316
323
|
const subcommand = typeof details.subcommand === "string" ? details.subcommand : undefined;
|
|
317
324
|
if (isCloseCommand(command) && message.isError !== true) {
|
|
318
325
|
restoredOrder += 1;
|
|
319
|
-
state.clearSession(
|
|
326
|
+
state.clearSession(sessionKey);
|
|
320
327
|
continue;
|
|
321
328
|
}
|
|
322
329
|
const tabTarget = getRestoredSessionTabTarget(details, command, subcommand);
|
|
@@ -326,14 +333,14 @@ export class SessionPageState {
|
|
|
326
333
|
continue;
|
|
327
334
|
restoredOrder += 1;
|
|
328
335
|
if (tabTarget)
|
|
329
|
-
state.tabTargets.set(
|
|
336
|
+
state.tabTargets.set(sessionKey, { order: restoredOrder, target: tabTarget });
|
|
330
337
|
if (refSnapshotInvalidation) {
|
|
331
|
-
state.refSnapshots.delete(
|
|
332
|
-
state.refSnapshotInvalidations.set(
|
|
338
|
+
state.refSnapshots.delete(sessionKey);
|
|
339
|
+
state.refSnapshotInvalidations.set(sessionKey, { ...refSnapshotInvalidation, order: restoredOrder });
|
|
333
340
|
}
|
|
334
341
|
else if (refSnapshot) {
|
|
335
|
-
state.refSnapshotInvalidations.delete(
|
|
336
|
-
state.refSnapshots.set(
|
|
342
|
+
state.refSnapshotInvalidations.delete(sessionKey);
|
|
343
|
+
state.refSnapshots.set(sessionKey, { ...refSnapshot, order: restoredOrder });
|
|
337
344
|
}
|
|
338
345
|
}
|
|
339
346
|
state.updateOrder = Math.max(restoredOrder, getLatestTabTargetOrder(state.tabTargets), getLatestRefStateOrder(state.refSnapshots, state.refSnapshotInvalidations));
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -153,9 +153,9 @@ Practical policy:
|
|
|
153
153
|
- once the wrapper observes tab-drift risk for a session (profile restore correction, overlapping stale opens, or restored session state), later active-tab commands may synthesize a tiny upstream `batch` that re-selects that tab and then runs the requested command in the same upstream invocation; routine same-session commands avoid `tab list` preflights to reduce probes that can perturb upstream click behavior
|
|
154
154
|
- for sessions with observed tab-drift risk, after a successful command on a known tab target, the wrapper may best-effort restore that same target again if restored/background tabs steal focus after the command returns; routine same-session commands skip this post-command `tab list` probe
|
|
155
155
|
- keep a per-session `refSnapshot` aligned with the last successful `snapshot` (including refs merged from a successful `batch` by taking the last successful `snapshot` step in batch result order): restore it from persisted tool `details` when reloading, resuming, or moving to a different Pi session-tree branch, store bounded ref role/name metadata from the same snapshot for wrapper-side current-ref diagnostics, drop it on successful close commands (`close`, `quit`, or `exit`), and refuse mutation-prone `@e…` argv before spawn when the active tab URL no longer matches the snapshot URL, when a ref id was never in that snapshot, or when `batch` stdin would reuse `@e…` on a guarded step after an earlier invalidating step without a later `snapshot` step in the same stdin array. Same-snapshot `fill @e…` rows are guarded but do not themselves set that invalidation latch, so ordinary form fills can precede a click/submit row in one batch—see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) for the agent-visible contract and failure text; typed per-session tab/ref/pinning state lives in `extensions/agent-browser/lib/session-page-state.ts` and is updated from `extensions/agent-browser/index.ts` after each tool result
|
|
156
|
-
- for top-level non-Electron direct `click` commands, install a bounded in-page target-specific event probe before upstream runs; if upstream reports success but no trusted pointer/mouse/click event reached the resolved target, fail the tool and report `details.clickDispatch` with explicit retry/inspect next actions (the wrapper does not replay clicks in-page). The probe
|
|
156
|
+
- for top-level non-Electron direct `click` commands with an eligible target, install a bounded in-page target-specific event probe before upstream runs; if upstream reports success but no trusted pointer/mouse/click event reached the resolved target, fail the tool and report `details.clickDispatch` with explicit retry/inspect next actions (the wrapper does not replay clicks in-page). The probe covers `xpath=` targets and current `@e…` / `ref=` refs whose latest stored `refSnapshot.refs` role is `button`, `checkbox`, `menuitem`, `radio`, `switch`, or `tab`; it uses that role/name metadata, including snapshot-order `duplicateIndex` for duplicate-name refs, instead of taking a fresh pre-click snapshot that could recycle upstream refs. The probe is intentionally skipped for CSS selector clicks, unresolved `find … click` locators, and `batch`/`job`/`qa` click steps
|
|
157
157
|
- derive narrow prompt guards only for concrete evidence invariants: exact required screenshot paths block browser close until the artifact manifest verifies those paths. The wrapper intentionally does not infer broad business/user intent from prompt text such as order/payment/post boundaries; agents must follow those instructions themselves. The artifact guard is bounded preflight policy (`details.promptGuard`, `failureCategory: "policy-blocked"`), not a reusable browser recipe layer
|
|
158
|
-
- after successful `get text` on a non-ref CSS selector, optionally issue one read-only `eval --stdin` probe per
|
|
158
|
+
- after successful `get text` on a qualifying non-ref CSS selector, optionally issue one read-only `eval --stdin` probe per selector when multiple DOM matches or a hidden first match with visible peers could misread tabbed or off-screen content; simple id selectors and sensitive-looking literals skip this probe. Merge `details.selectorTextVisibility` / `selectorTextVisibilityAll`, visible warning lines, and `inspect-visible-text-candidates*` next actions as documented in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details) and `RQ-0074` in [`SUPPORT_MATRIX.md`](SUPPORT_MATRIX.md)
|
|
159
159
|
- for local Unix launches, set a short private socket directory so extension-generated session names do not fail on the upstream Unix socket-path length limit
|
|
160
160
|
- keep wrapper-spawned upstream CLI calls bounded by clamping `AGENT_BROWSER_DEFAULT_TIMEOUT` to the upstream documented 25-second default while deriving a longer subprocess watchdog for explicit long `wait <ms>` / `wait --timeout <ms>` calls; dialog commands, likely dialog-trigger clicks/taps/finds, and `eval --stdin` snippets that look like alert/confirm/prompt/dialog triggers use shorter wrapper subprocess budgets so blocking JavaScript prompts surface recovery actions before the full default watchdog
|
|
161
161
|
|
|
@@ -166,7 +166,7 @@ This is primarily about ownership clarity and avoiding surprise, not adding a he
|
|
|
166
166
|
`agent-browser` startup flags are sticky once a session is already running.
|
|
167
167
|
The extension should surface that clearly and avoid hidden restart behavior in v1.
|
|
168
168
|
|
|
169
|
-
That means explicit startup-scoping flags like `--auto-connect`, `--cdp`, `--enable`, `--executable-path`, `--init-script`, `--device`, `--profile`, `--provider`, `-p`, `--session-name`, and `--state` should remain explicit upstream choices instead of being wrapped in extra hidden restart or cloning logic.
|
|
169
|
+
That means explicit startup-scoping flags like `--auto-connect`, `--cdp`, `--enable`, `--executable-path`, `--init-script`, `--device`, `--namespace`, `--profile`, `--provider`, `-p`, `--restore`, `--restore-save`, restore check flags, `--session-name`, and `--state` should remain explicit upstream choices instead of being wrapped in extra hidden restart or cloning logic.
|
|
170
170
|
|
|
171
171
|
The wrapper may still apply narrow compatibility normalizations when observed behavior justifies them and the result remains thin, local, and opt-out. For example, if a specific site starts rejecting the default local headless Chrome user agent while the same flow works with a normal Chrome UA, the extension may inject a domain-specific fallback UA only when the caller did not already choose `--user-agent`, `--headed`, `--cdp`, `--auto-connect`, or a provider-backed launch.
|
|
172
172
|
|
|
@@ -177,10 +177,10 @@ That failure should include a structured recovery hint pointing to `sessionMode:
|
|
|
177
177
|
Implementation detail lives in `extensions/agent-browser/lib/launch-scoped-flags.ts` (canonical flag metadata shared with playbook/docs assertions), `extensions/agent-browser/lib/argv-descriptor.ts` and `extensions/agent-browser/lib/argv-grammar.ts` (command discovery, `VALUE_FLAGS`, `parseArgvDescriptor`) plus `extensions/agent-browser/lib/runtime.ts` (`getStartupScopedFlags`, `buildExecutionPlan`):
|
|
178
178
|
|
|
179
179
|
- **Command discovery:** Leading argv is scanned with a value-taking allowlist so known global flags and documented command flags consume their values before the upstream command word is identified. Missing-value prevalidation is intentionally limited to upstream global value flags; command-scoped flags and literal text are left to upstream parsing so values like `fill #field --password` are not rejected by wrapper heuristics before the CLI sees them. When upstream adds new global flags that take values ahead of the command, extend both the command-discovery and prevalidation allowlists; when it adds command-specific flags, extend only command discovery/redaction as needed. A smaller set of global boolean flags may be followed by an optional `true`/`false` literal; when present, that literal is consumed as the flag value before command discovery continues.
|
|
180
|
-
- **`--state` disambiguation:** Persisted browser `--state` before the command participates in launch-scoped validation and tab-correction hints. The same flag spelling after a `wait` command is excluded from startup-scoped detection so upstream help examples such as `wait @ref --state hidden` do not spuriously require `sessionMode: "fresh"` while an implicit session is active. As of upstream
|
|
180
|
+
- **`--state` disambiguation:** Persisted browser `--state` before the command participates in launch-scoped validation and tab-correction hints. The same flag spelling after a `wait` command is excluded from startup-scoped detection so upstream help examples such as `wait @ref --state hidden` do not spuriously require `sessionMode: "fresh"` while an implicit session is active. As of the current upstream baseline, the parser still does not implement those `wait --state` examples as distinct wait modes, so agent-facing docs recommend `wait --fn` predicates for disappearance checks instead.
|
|
181
181
|
- **`--auto-connect`:** Treated as launch-scoped only when enabled (`--auto-connect` bare or `true`). `--auto-connect false` is ignored for startup-scoped blocking so disabled attach hints do not force a fresh launch.
|
|
182
182
|
|
|
183
|
-
**Sessionless inspection and local commands:** Plain-text global help and version probes (`--help`, `-h`, `--version`, `-V`) must never allocate or bind the extension-managed session. The same session-ownership rule applies to read-only upstream `skills list`, `skills get …`, and `skills path …`, local auth profile management (`auth save/list/show/delete/remove`), plus local/setup surfaces such as `profiles`, `dashboard start/stop`, `device list`, `doctor`, `install`, `upgrade`, `session list`, and targeted/all local saved-state maintenance (`state list/show`, `state clear --all`, `state clear -a`, `state clear <session-name>`, `state clean --older-than <days>`, `state rename`). Non-plain-text sessionless commands still run with `--json` for machine-readable output, but the planner does not prepend the implicit managed `--session`, so an agent can inspect local capabilities or start/stop the standalone dashboard without consuming the implicit session slot before a real `open`. Browser-backed, context-dependent, or incomplete commands such as root `session`, untargeted `state clear`, bare `state clean`, `auth login`, `state save`, and `state load` keep normal managed-session injection. Command-shape allowlisting lives in `extensions/agent-browser/lib/command-policy.ts` (`needsManagedSession`), while `extensions/agent-browser/lib/runtime.ts` (`isPlainTextInspectionArgs`, `buildExecutionPlan`) applies that decision to execution planning.
|
|
183
|
+
**Sessionless inspection and local commands:** Plain-text global help and version probes (`--help`, `-h`, `--version`, `-V`) must never allocate or bind the extension-managed session. The same session-ownership rule applies to read-only upstream `skills list`, `skills get …`, and `skills path …`, local auth profile management (`auth save/list/show/delete/remove`), plus local/setup surfaces such as `profiles`, `dashboard start/stop`, `device list`, `doctor`, `install`, `upgrade`, `session id`, `session info`, `session list`, and targeted/all local saved-state maintenance (`state list/show`, `state clear --all`, `state clear -a`, `state clear <session-name>`, `state clean --older-than <days>`, `state rename`). Non-plain-text sessionless commands still run with `--json` for machine-readable output, but the planner does not prepend the implicit managed `--session`, so an agent can inspect local capabilities or start/stop the standalone dashboard without consuming the implicit session slot before a real `open`. Browser-backed, context-dependent, or incomplete commands such as root `session`, untargeted `state clear`, bare `state clean`, `auth login`, `state save`, and `state load` keep normal managed-session injection. Command-shape allowlisting lives in `extensions/agent-browser/lib/command-policy.ts` (`needsManagedSession`), while `extensions/agent-browser/lib/runtime.ts` (`isPlainTextInspectionArgs`, `buildExecutionPlan`) applies that decision to execution planning.
|
|
184
184
|
|
|
185
185
|
A successful unnamed `sessionMode: "fresh"` launch should become the new extension-managed session so later default calls follow that browser instead of silently snapping back to the older managed session.
|
|
186
186
|
|