pi-agent-browser-native 0.6.6 → 0.6.7
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 +24 -1
- package/README.md +20 -5
- package/dist/extensions/agent-browser/index.js +138 -75
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +6 -5
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +10 -1
- package/dist/extensions/agent-browser/lib/input-modes/params.js +20 -7
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +0 -1
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +13 -12
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +6 -25
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +2 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +10 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +3 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +104 -117
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +61 -35
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +100 -135
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +3 -1
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +3 -1
- package/dist/extensions/agent-browser/lib/page-target-validation.js +10 -10
- package/dist/extensions/agent-browser/lib/parsing.js +7 -0
- package/dist/extensions/agent-browser/lib/playbook.js +5 -8
- package/dist/extensions/agent-browser/lib/process.js +23 -7
- package/dist/extensions/agent-browser/lib/recording-reservations.js +3 -1
- package/dist/extensions/agent-browser/lib/results/envelope.js +4 -1
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +45 -43
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +4 -4
- package/dist/extensions/agent-browser/lib/runtime.js +18 -2
- package/dist/extensions/agent-browser/lib/session-page-state.js +29 -10
- package/docs/ARCHITECTURE.md +7 -4
- package/docs/COMMAND_REFERENCE.md +22 -15
- package/docs/ELECTRON.md +6 -6
- package/docs/RELEASE.md +14 -5
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +13 -3
- package/docs/TOOL_CONTRACT.md +33 -22
- package/package.json +1 -1
|
@@ -143,7 +143,6 @@ const COMMAND_CAPABILITIES = [
|
|
|
143
143
|
command: "keyboard",
|
|
144
144
|
eligibleForElectronHealthProbe: true,
|
|
145
145
|
eligibleForPageChangeSummary: true,
|
|
146
|
-
guardsPageRefs: true,
|
|
147
146
|
invalidatesBatchRefs: true,
|
|
148
147
|
triggersPostMutationSnapshot: true,
|
|
149
148
|
},
|
|
@@ -157,7 +156,6 @@ const COMMAND_CAPABILITIES = [
|
|
|
157
156
|
{
|
|
158
157
|
command: "mouse",
|
|
159
158
|
eligibleForElectronHealthProbe: true,
|
|
160
|
-
guardsPageRefs: true,
|
|
161
159
|
invalidatesBatchRefs: true,
|
|
162
160
|
},
|
|
163
161
|
{
|
|
@@ -181,7 +179,6 @@ const COMMAND_CAPABILITIES = [
|
|
|
181
179
|
command: "press",
|
|
182
180
|
eligibleForElectronHealthProbe: true,
|
|
183
181
|
eligibleForPageChangeSummary: true,
|
|
184
|
-
guardsPageRefs: true,
|
|
185
182
|
invalidatesBatchRefs: true,
|
|
186
183
|
triggersPostMutationSnapshot: true,
|
|
187
184
|
},
|
|
@@ -330,8 +327,11 @@ export function isRecordPageTransitionCommand(tokens) {
|
|
|
330
327
|
export function isWebMcpPageMutationCommand(tokens) {
|
|
331
328
|
return isWebMcpPageMutation(tokens[0], tokens[1]);
|
|
332
329
|
}
|
|
330
|
+
export function isWindowOrDiffPageTransitionCommand(command, subcommand) {
|
|
331
|
+
return (command === "window" && subcommand === "new") || (command === "diff" && subcommand === "url");
|
|
332
|
+
}
|
|
333
333
|
export function isRefInvalidatingBatchCommand(step) {
|
|
334
|
-
return hasCommandCapability(step[0], "invalidatesBatchRefs") || isRecordPageTransitionCommand(step) || isWebMcpPageMutationCommand(step);
|
|
334
|
+
return hasCommandCapability(step[0], "invalidatesBatchRefs") || isRecordPageTransitionCommand(step) || isWebMcpPageMutationCommand(step) || isWindowOrDiffPageTransitionCommand(step[0], step[1]);
|
|
335
335
|
}
|
|
336
336
|
export function isRefGuardedCommand(command) {
|
|
337
337
|
return hasCommandCapability(command, "guardsPageRefs");
|
|
@@ -343,12 +343,13 @@ function isWebMcpPageMutation(command, subcommand) {
|
|
|
343
343
|
return command === "webmcp" && WEBMCP_PAGE_MUTATION_SUBCOMMANDS.has(subcommand ?? "");
|
|
344
344
|
}
|
|
345
345
|
export function isNavigationObservableCommandName(command, subcommand) {
|
|
346
|
-
return hasCommandCapability(command, "navigationObservable") || isWebMcpPageMutation(command, subcommand);
|
|
346
|
+
return hasCommandCapability(command, "navigationObservable") || isWebMcpPageMutation(command, subcommand) || isWindowOrDiffPageTransitionCommand(command, subcommand);
|
|
347
347
|
}
|
|
348
348
|
export function isUnverifiedPageTransitionCommand(command, subcommand) {
|
|
349
349
|
return ["back", "connect", "eval", "forward", "reload"].includes(command ?? "")
|
|
350
350
|
|| (command === "state" && subcommand === "load")
|
|
351
351
|
|| (command === "tab" && subcommand !== undefined && !["list", "new"].includes(subcommand))
|
|
352
|
+
|| isWindowOrDiffPageTransitionCommand(command, subcommand)
|
|
352
353
|
|| isWebMcpPageMutation(command, subcommand);
|
|
353
354
|
}
|
|
354
355
|
export function isPageMutationCommand(command, subcommand) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { rm } from "node:fs/promises";
|
|
2
|
+
import { lstat, rm } from "node:fs/promises";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { fetchCdpJson, parseCdpTargets, parseCdpVersion } from "./cdp.js";
|
|
5
5
|
import { ELECTRON_PROFILE_DIR_PREFIX } from "./launch.js";
|
|
@@ -33,6 +33,14 @@ async function isPortAlive(port) {
|
|
|
33
33
|
}
|
|
34
34
|
export async function inspectElectronLaunchStatus(record) {
|
|
35
35
|
const cdp = await isPortAlive(record.port);
|
|
36
|
+
let userDataDirState;
|
|
37
|
+
try {
|
|
38
|
+
await lstat(record.userDataDir);
|
|
39
|
+
userDataDirState = "present";
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
userDataDirState = error.code === "ENOENT" ? "absent" : "unknown";
|
|
43
|
+
}
|
|
36
44
|
return {
|
|
37
45
|
cleanupState: record.cleanupState,
|
|
38
46
|
launchId: record.launchId,
|
|
@@ -41,6 +49,7 @@ export async function inspectElectronLaunchStatus(record) {
|
|
|
41
49
|
port: record.port,
|
|
42
50
|
portAlive: cdp.version !== undefined,
|
|
43
51
|
targets: cdp.targets,
|
|
52
|
+
userDataDirState,
|
|
44
53
|
version: cdp.version,
|
|
45
54
|
};
|
|
46
55
|
}
|
|
@@ -11,7 +11,7 @@ export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = l
|
|
|
11
11
|
maxLength: AGENT_BROWSER_SCRIPT_CODE_MAX_BYTES,
|
|
12
12
|
})),
|
|
13
13
|
args: Type.Optional(Type.Array(Type.String(), {
|
|
14
|
-
description: "Raw agent-browser argv only: no binary, shell operators, or --json. Start with open → snapshot -i → act on current @refs; re-snapshot after page changes.",
|
|
14
|
+
description: "Raw agent-browser argv only: no binary, shell operators, or --json. Start with open → snapshot -i → act on current @refs; re-snapshot after page changes. Input: type <selector> <text>, or keyboard type <text> at current focus. Wait duration: wait <ms> (no --time). Artifacts: screenshot [selector] [path] [--full/-f]; record start <path> [url]; record restart <path> [url]; record stop. Paths are positional (no --path); use --full, not --full-page.",
|
|
15
15
|
minItems: 1,
|
|
16
16
|
})),
|
|
17
17
|
semanticAction: Type.Optional(Type.Object({
|
|
@@ -21,10 +21,23 @@ export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = l
|
|
|
21
21
|
values: Type.Optional(Type.Array(Type.String(), { description: "Select options; required for select by label.", minItems: 1 })),
|
|
22
22
|
selector: Type.Optional(Type.String({ description: "Direct selector or @ref." })),
|
|
23
23
|
text: Type.Optional(Type.String({ description: "Fill text." })),
|
|
24
|
-
role: Type.Optional(Type.String({ description: "Role locator;
|
|
24
|
+
role: Type.Optional(Type.String({ description: "Role for locator=role; select needs combobox or listbox." })),
|
|
25
25
|
name: Type.Optional(Type.String({ description: "Accessible name." })),
|
|
26
26
|
session: Type.Optional(Type.String({ description: "Upstream session name." })),
|
|
27
|
-
}, {
|
|
27
|
+
}, {
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
// Pi normalizes optional nulls through properties, not union branches.
|
|
30
|
+
anyOf: [
|
|
31
|
+
Type.Object({
|
|
32
|
+
action: StringEnum(["select"]),
|
|
33
|
+
locator: Type.Optional(StringEnum(["role", "label"])),
|
|
34
|
+
}, { not: { required: ["text"] } }),
|
|
35
|
+
Type.Object({
|
|
36
|
+
action: StringEnum(["check", "click", "fill"]),
|
|
37
|
+
}, { not: { required: ["values"] } }),
|
|
38
|
+
],
|
|
39
|
+
description: "Stable locator or direct-selector action. values only with action=select.",
|
|
40
|
+
})),
|
|
28
41
|
qa: Type.Optional(Type.Union([
|
|
29
42
|
Type.Object({
|
|
30
43
|
attached: Type.Literal(true),
|
|
@@ -112,7 +125,7 @@ export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = l
|
|
|
112
125
|
locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Locator when selector is omitted." })),
|
|
113
126
|
role: Type.Optional(Type.String({ description: "Role locator." })),
|
|
114
127
|
name: Type.Optional(Type.String({ description: "Accessible name." })),
|
|
115
|
-
text: Type.Optional(Type.String({ description: "Fill text
|
|
128
|
+
text: Type.Optional(Type.String({ description: "Fill/type text; assertText uses only text, not selector/locator fields." })),
|
|
116
129
|
value: Type.Optional(Type.String({ description: "Select option or locator value." })),
|
|
117
130
|
values: Type.Optional(Type.Array(Type.String(), { description: "Select options.", minItems: 1 })),
|
|
118
131
|
path: Type.Optional(Type.String({ description: "Download or screenshot path." })),
|
|
@@ -120,10 +133,10 @@ export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = l
|
|
|
120
133
|
press: Type.Optional(Type.String({ description: "Key to press after typing." })),
|
|
121
134
|
milliseconds: Type.Optional(Type.Number({ description: "Wait duration in milliseconds." })),
|
|
122
135
|
}, { additionalProperties: false }), { minItems: 1 }),
|
|
123
|
-
}, { additionalProperties: false, description: "Constrained multi-step batch." })),
|
|
124
|
-
stdin: Type.Optional(Type.String({ description: "Raw
|
|
136
|
+
}, { additionalProperties: false, description: "Constrained multi-step batch. Clicks can stale later refs; split and re-snapshot before using them." })),
|
|
137
|
+
stdin: Type.Optional(Type.String({ description: "For batch, a JSON array of token arrays, e.g. [[\"get\",\"title\"]]. Raw text only for eval --stdin or auth save --password-stdin; unavailable with structured modes and electron." })),
|
|
125
138
|
outputPath: Type.Optional(Type.String({ description: "Workspace-relative or absolute result-data path; keep it distinct from screenshot, download, recording, and other browser artifact destinations.", minLength: 1 })),
|
|
126
|
-
timeoutMs: Type.Optional(Type.Integer({ description: "Wrapper timeout in ms; exceed explicit waits. Electron
|
|
139
|
+
timeoutMs: Type.Optional(Type.Integer({ description: "Wrapper timeout in ms; exceed explicit waits. electron.list has no configurable timeout; other Electron actions use electron.timeoutMs.", minimum: 1 })),
|
|
127
140
|
sessionMode: Type.Optional(StringEnum(["auto", "fresh"], {
|
|
128
141
|
description: "auto reuses the managed session; fresh starts one for launch-only flags, then makes it the managed session.",
|
|
129
142
|
default: DEFAULT_SESSION_MODE,
|
|
@@ -150,7 +150,6 @@ export const MANAGED_RESTORE_INCOMPATIBLE_ENVS = [
|
|
|
150
150
|
"AGENT_BROWSER_PROFILE",
|
|
151
151
|
"AGENT_BROWSER_STATE",
|
|
152
152
|
"AGENT_BROWSER_CDP",
|
|
153
|
-
"AGENT_BROWSER_NAMESPACE",
|
|
154
153
|
"AGENT_BROWSER_SESSION_NAME",
|
|
155
154
|
"AGENT_BROWSER_PROVIDER",
|
|
156
155
|
"AGENT_BROWSER_EXECUTABLE_PATH",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { extractUpstreamCommandTokens, parseCommandInfo } from "./argv-descriptor.js";
|
|
3
|
-
import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, extractRequestedRestoreKey, getAgentBrowserSessionIdentityKey, isUpstreamEnvFlagEnabled, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
|
|
3
|
+
import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, extractRequestedRestoreKey, getAgentBrowserSessionIdentityKey, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
|
|
4
4
|
import { hasLaunchScopedFlagToken, MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS, MANAGED_RESTORE_INCOMPATIBLE_ENVS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS, } from "./launch-scoped-flags.js";
|
|
5
5
|
import { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreScope, getManagedSessionRestoreProtectedStorageEnv, hasManagedSessionRestoreProjectIdentity, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
|
|
6
6
|
import { parseUserBatchStdin } from "./orchestration/batch-stdin.js";
|
|
@@ -11,7 +11,7 @@ const AGENT_BROWSER_CONFIG_ENV = "AGENT_BROWSER_CONFIG";
|
|
|
11
11
|
const AGENT_BROWSER_RESTORE_ENV = "AGENT_BROWSER_RESTORE";
|
|
12
12
|
const MANAGED_SESSION_RESTORE_ENV = "PI_AGENT_BROWSER_MANAGED_SESSION_RESTORE";
|
|
13
13
|
export const MANAGED_SESSION_NAME_PREFIX = "piab-";
|
|
14
|
-
const MANAGED_SESSION_RESTORE_SPAWN_PINNED_ENVS = new Set([AGENT_BROWSER_CONFIG_ENV, AGENT_BROWSER_RESTORE_ENV
|
|
14
|
+
const MANAGED_SESSION_RESTORE_SPAWN_PINNED_ENVS = new Set([AGENT_BROWSER_CONFIG_ENV, AGENT_BROWSER_RESTORE_ENV]);
|
|
15
15
|
function isDisabledEnvFlag(value) {
|
|
16
16
|
if (value === undefined)
|
|
17
17
|
return false;
|
|
@@ -86,12 +86,13 @@ export function resolveOwnedManagedSessionContext(options) {
|
|
|
86
86
|
}
|
|
87
87
|
return undefined;
|
|
88
88
|
}
|
|
89
|
-
function ownedContextMatches(sessionName,
|
|
89
|
+
function ownedContextMatches(sessionName, args) {
|
|
90
90
|
const owned = ownedManagedSessionStorage.getStore();
|
|
91
|
-
return owned && sessionName === owned.sessionName
|
|
91
|
+
return owned && sessionName === owned.sessionName
|
|
92
|
+
&& canonicalizeAgentBrowserNamespace(resolveAgentBrowserNamespace(args, owned.namespace)) === owned.namespace ? owned : undefined;
|
|
92
93
|
}
|
|
93
94
|
export function isOwnedManagedSessionTarget(args) {
|
|
94
|
-
return ownedContextMatches(extractExplicitSessionName(args),
|
|
95
|
+
return ownedContextMatches(extractExplicitSessionName(args), args) !== undefined;
|
|
95
96
|
}
|
|
96
97
|
function hasExplicitConfigArg(args) {
|
|
97
98
|
return scanUpstreamGlobalFlagOccurrences(args, "--config").length > 0;
|
|
@@ -141,7 +142,7 @@ function managedSessionRestoreOptedOut(options) {
|
|
|
141
142
|
const effectiveEnv = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
|
|
142
143
|
return isDisabledEnvFlag(effectiveEnv[MANAGED_SESSION_RESTORE_ENV]);
|
|
143
144
|
}
|
|
144
|
-
function isManagedSessionRestoreIncompatible(options) {
|
|
145
|
+
function isManagedSessionRestoreIncompatible(options, namespace = extractExplicitNamespace(options.args)) {
|
|
145
146
|
if (hasManagedSessionRestoreLaunchConflict(options))
|
|
146
147
|
return true;
|
|
147
148
|
if (managedSessionRestoreOptedOut(options))
|
|
@@ -152,13 +153,13 @@ function isManagedSessionRestoreIncompatible(options) {
|
|
|
152
153
|
return true;
|
|
153
154
|
if (options.cwd && agentBrowserConfigBlocksManagedRestore(options.cwd, effectiveEnv, args))
|
|
154
155
|
return true;
|
|
155
|
-
return !ensureManagedSessionRestoreStorageIsSecure(effectiveEnv, process.platform,
|
|
156
|
+
return !ensureManagedSessionRestoreStorageIsSecure(effectiveEnv, process.platform, namespace);
|
|
156
157
|
}
|
|
157
158
|
function resolveManagedSessionRestorePolicy(options) {
|
|
158
159
|
const parentEnv = options.parentEnv ?? getAgentBrowserProcessEnvironment();
|
|
159
160
|
const sessionName = extractExplicitSessionName(options.args);
|
|
160
|
-
const
|
|
161
|
-
const
|
|
161
|
+
const ownedContext = ownedContextMatches(sessionName, options.args);
|
|
162
|
+
const namespace = ownedContext ? ownedContext.namespace : extractExplicitNamespace(options.args);
|
|
162
163
|
const restoreState = ownedContext?.restoreState ?? options.restoreState;
|
|
163
164
|
const owned = (options.ownedManagedSession || ownedContext !== undefined) && restoreState !== undefined;
|
|
164
165
|
return { namespace, owned, ownedContext, parentEnv, restoreState, sessionName };
|
|
@@ -234,7 +235,7 @@ export function getManagedSessionRestoreEnv(options) {
|
|
|
234
235
|
return { [AGENT_BROWSER_RESTORE_ENV]: ownedContext.restoreKey };
|
|
235
236
|
}
|
|
236
237
|
const policyOptions = { ...options, parentEnv };
|
|
237
|
-
if (managedSessionRestoreOptedOut(policyOptions) || ownedContext?.restoreSuppressed || isManagedSessionRestoreIncompatible(policyOptions))
|
|
238
|
+
if (managedSessionRestoreOptedOut(policyOptions) || ownedContext?.restoreSuppressed || isManagedSessionRestoreIncompatible(policyOptions, namespace))
|
|
238
239
|
return {};
|
|
239
240
|
if (restoreState.isDisabled(sessionName, namespace) || !sessionName)
|
|
240
241
|
return {};
|
|
@@ -263,7 +264,7 @@ export function commitManagedSessionRestoreSuppression(options) {
|
|
|
263
264
|
return;
|
|
264
265
|
}
|
|
265
266
|
const policyOptions = { ...options, parentEnv };
|
|
266
|
-
if (managedSessionRestoreOptedOut(policyOptions) || ownedContext?.restoreSuppressed || isManagedSessionRestoreIncompatible(policyOptions))
|
|
267
|
+
if (managedSessionRestoreOptedOut(policyOptions) || ownedContext?.restoreSuppressed || isManagedSessionRestoreIncompatible(policyOptions, namespace))
|
|
267
268
|
restoreState.disable(sessionName, namespace);
|
|
268
269
|
}
|
|
269
270
|
export function buildOwnedManagedSessionRestoreContext(options) {
|
|
@@ -281,7 +282,7 @@ export function buildOwnedManagedSessionRestoreContext(options) {
|
|
|
281
282
|
};
|
|
282
283
|
const optedOut = managedSessionRestoreOptedOut(policyOptions);
|
|
283
284
|
const projectIdentityAvailable = !optedOut && hasManagedSessionRestoreProjectIdentity(ownedCwd);
|
|
284
|
-
const incompatible = !optedOut && isManagedSessionRestoreIncompatible(policyOptions);
|
|
285
|
+
const incompatible = !optedOut && isManagedSessionRestoreIncompatible(policyOptions, owned.namespace);
|
|
285
286
|
const enabled = !optedOut && !incompatible;
|
|
286
287
|
const effectiveEnv = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
|
|
287
288
|
const restoreScope = getManagedSessionRestoreScope(owned.sessionName);
|
|
@@ -1,45 +1,28 @@
|
|
|
1
|
-
import { isRecord } from "../../parsing.js";
|
|
1
|
+
import { isRecord, parseRefId } from "../../parsing.js";
|
|
2
2
|
import { redactSensitiveText } from "../../runtime.js";
|
|
3
3
|
import { withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
4
4
|
import { runSessionCommandData } from "./session-state.js";
|
|
5
5
|
const CLICK_DISPATCH_MARKER_PREFIX = "__piAgentBrowserClickDispatchProbe_";
|
|
6
6
|
const CLICK_DISPATCH_CLEANUP_TIMEOUT_MS = 2_000;
|
|
7
7
|
const ACCESSIBLE_REF_CLICK_DISPATCH_ROLES = new Set(["button", "checkbox", "menuitem", "radio", "switch", "tab"]);
|
|
8
|
-
function parseClickRefId(selector) {
|
|
9
|
-
const trimmed = selector.trim();
|
|
10
|
-
const candidate = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
|
|
11
|
-
return /^e\d+$/.test(candidate) ? candidate : undefined;
|
|
12
|
-
}
|
|
13
8
|
function normalizeAccessibleName(name) {
|
|
14
9
|
return name.replace(/\s+/g, " ").trim().toLowerCase();
|
|
15
10
|
}
|
|
16
|
-
function getAccessibleRefDuplicateIndex(refSnapshot, refId, role, name) {
|
|
17
|
-
if (!refSnapshot?.refs)
|
|
18
|
-
return undefined;
|
|
19
|
-
const normalizedRole = role.toLowerCase();
|
|
20
|
-
const normalizedName = normalizeAccessibleName(name);
|
|
21
|
-
const matchingRefIds = refSnapshot.refIds.filter((candidateRefId) => {
|
|
22
|
-
const candidate = refSnapshot.refs?.[candidateRefId];
|
|
23
|
-
return candidate?.role.toLowerCase() === normalizedRole && normalizeAccessibleName(candidate.name) === normalizedName;
|
|
24
|
-
});
|
|
25
|
-
if (matchingRefIds.length <= 1)
|
|
26
|
-
return undefined;
|
|
27
|
-
const duplicateIndex = matchingRefIds.indexOf(refId);
|
|
28
|
-
return duplicateIndex >= 0 ? duplicateIndex : undefined;
|
|
29
|
-
}
|
|
30
11
|
function getClickDispatchProbeTarget(commandTokens, refSnapshot) {
|
|
31
12
|
if (commandTokens[0] !== "click" || commandTokens.includes("--new-tab"))
|
|
32
13
|
return undefined;
|
|
33
14
|
const selector = commandTokens[1];
|
|
34
15
|
if (!selector || selector.startsWith("-"))
|
|
35
16
|
return undefined;
|
|
36
|
-
const refId =
|
|
17
|
+
const refId = parseRefId(selector);
|
|
37
18
|
if (refId) {
|
|
38
19
|
const ref = refSnapshot?.refs?.[refId];
|
|
39
20
|
if (!ref || !ACCESSIBLE_REF_CLICK_DISPATCH_ROLES.has(ref.role))
|
|
40
21
|
return undefined;
|
|
41
|
-
const
|
|
42
|
-
|
|
22
|
+
const matchingRefs = Object.values(refSnapshot?.refs ?? {}).filter((candidate) => candidate.role.toLowerCase() === ref.role.toLowerCase() && normalizeAccessibleName(candidate.name) === normalizeAccessibleName(ref.name));
|
|
23
|
+
if (matchingRefs.length !== 1)
|
|
24
|
+
return undefined;
|
|
25
|
+
return { kind: "accessible", name: ref.name, refId, role: ref.role };
|
|
43
26
|
}
|
|
44
27
|
if (selector.startsWith("xpath="))
|
|
45
28
|
return { kind: "xpath", selector: selector.slice("xpath=".length) };
|
|
@@ -58,7 +41,6 @@ function buildClickDispatchProbeInstallScript(probe) {
|
|
|
58
41
|
const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
|
|
59
42
|
const expectedRole = ${JSON.stringify(target.role)};
|
|
60
43
|
const expectedName = normalize(${JSON.stringify(target.name)});
|
|
61
|
-
const duplicateIndex = ${JSON.stringify(target.duplicateIndex)};
|
|
62
44
|
const inferRole = (element) => {
|
|
63
45
|
const explicit = element.getAttribute("role");
|
|
64
46
|
if (explicit) return explicit;
|
|
@@ -81,7 +63,6 @@ function buildClickDispatchProbeInstallScript(probe) {
|
|
|
81
63
|
return element.getClientRects().length > 0;
|
|
82
64
|
};
|
|
83
65
|
const candidates = Array.from(document.querySelectorAll("button,a[href],input,select,textarea,summary,[role],[onclick],[tabindex]")).filter((element) => inferRole(element) === expectedRole && inferName(element) === expectedName && isVisible(element));
|
|
84
|
-
if (typeof duplicateIndex === "number") return candidates[duplicateIndex] || null;
|
|
85
66
|
return candidates.length === 1 ? candidates[0] : null;
|
|
86
67
|
})()`;
|
|
87
68
|
return `(() => {
|
|
@@ -54,10 +54,9 @@ export function buildSemanticActionCandidateActions(compiled) {
|
|
|
54
54
|
return [];
|
|
55
55
|
}
|
|
56
56
|
export function buildWrapperRecoveryHint(options) {
|
|
57
|
-
|
|
58
|
-
if (wrapperManagedContexts.length === 0)
|
|
57
|
+
if (!options.sessionTabCorrection)
|
|
59
58
|
return undefined;
|
|
60
|
-
return
|
|
59
|
+
return "Wrapper recovery hint: this call used session tab correction. Inspect details.effectiveArgs and details.sessionTabCorrection; if the selected tab looks wrong, run tab list for the same session before retrying.";
|
|
61
60
|
}
|
|
62
61
|
export function redactExactSensitiveText(text, sensitiveValues) {
|
|
63
62
|
let redacted = text;
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { runAgentBrowserProcess, withAttachedBrowserSessionContext } from "../../process.js";
|
|
2
|
+
import { isRecord } from "../../parsing.js";
|
|
2
3
|
import { withOwnedManagedSessionContext } from "../../managed-session-restore.js";
|
|
3
4
|
import { cleanupClickDispatchProbe } from "./click-dispatch.js";
|
|
4
|
-
import { applyBrowserRunStatePatch } from "./session-state.js";
|
|
5
|
+
import { applyBrowserRunStatePatch, getSessionContextKey } from "./session-state.js";
|
|
5
6
|
import { buildMissingBinaryFailureResult } from "./final-result.js";
|
|
6
7
|
import { prepareBrowserRun } from "./prepare.js";
|
|
7
8
|
import { processBrowserOutput } from "./process-output.js";
|
|
8
9
|
export { closeManagedSession } from "./managed-session-daemon-policy.js";
|
|
9
10
|
export { getSessionContextKey } from "./session-state.js";
|
|
10
11
|
export async function runAgentBrowserTool(options) {
|
|
11
|
-
|
|
12
|
+
const result = await withAttachedBrowserSessionContext(options.preserveAttachedBrowserSession === true, () => runAgentBrowserToolInContext(options));
|
|
13
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
14
|
+
const sessionKey = getSessionContextKey(typeof details?.sessionName === "string" ? details.sessionName : undefined, typeof details?.namespace === "string" ? details.namespace : undefined);
|
|
15
|
+
const page = options.state.sessionPageState.get(sessionKey);
|
|
16
|
+
return page.tabReopenPending === undefined ? result : {
|
|
17
|
+
...result,
|
|
18
|
+
details: { ...details, sessionTabReopenPending: page.tabReopenPending, ...(page.refSnapshotInvalidation ? { refSnapshotInvalidation: page.refSnapshotInvalidation } : {}) },
|
|
19
|
+
};
|
|
12
20
|
}
|
|
13
21
|
async function runAgentBrowserToolInContext(options) {
|
|
14
22
|
const preparedResult = await prepareBrowserRun(options);
|
|
@@ -34,7 +42,6 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
34
42
|
signal: options.signal,
|
|
35
43
|
stdin: prepared.processStdin,
|
|
36
44
|
timeoutMs: prepared.processTimeoutMs,
|
|
37
|
-
trustedFirstBatchTabSelection: prepared.pinnedBatchUnwrapMode !== undefined,
|
|
38
45
|
});
|
|
39
46
|
const missingBinaryResult = await buildMissingBinaryFailureResult({
|
|
40
47
|
compatibilityWorkaround: prepared.compatibilityWorkaround,
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js
CHANGED
|
@@ -100,6 +100,9 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
100
100
|
context.restoreState.recordDaemonRestoreKey(context.sessionName, context.namespace, daemon.restoreKey);
|
|
101
101
|
return !["inactive", "missing-binary"].includes(daemon.status) && !activePolicyMatches
|
|
102
102
|
? {
|
|
103
|
+
cleanupOnlyReason: daemon.status === "active" && restoreDisabledPolicyNeedsProvenance && !hasKnownDaemonRestoreKey
|
|
104
|
+
? "restore-disabled-daemon-without-provenance"
|
|
105
|
+
: undefined,
|
|
103
106
|
daemonStatus: daemon.status,
|
|
104
107
|
error: [
|
|
105
108
|
"This wrapper-owned session's live daemon does not match the requested managed-restore policy.",
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js
CHANGED
|
@@ -25,7 +25,7 @@ function findCommandTimeoutMs(commandTokens) {
|
|
|
25
25
|
const firstWaitArgument = commandTokens[0] === "wait" ? commandTokens[1] : undefined;
|
|
26
26
|
return firstWaitArgument && !firstWaitArgument.startsWith("-") ? parseMillisecondsToken(firstWaitArgument) : undefined;
|
|
27
27
|
}
|
|
28
|
-
function findFirstPositionalArgument(commandTokens) {
|
|
28
|
+
export function findFirstPositionalArgument(commandTokens) {
|
|
29
29
|
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
30
30
|
const token = commandTokens[index];
|
|
31
31
|
const flag = token.split("=", 1)[0];
|