pi-agent-browser-native 0.2.72 → 0.2.74
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 +27 -0
- package/README.md +14 -12
- package/dist/extensions/agent-browser/index.js +104 -16
- package/dist/extensions/agent-browser/lib/argv-grammar.js +122 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +11 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
- package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
- package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +367 -0
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +625 -429
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +136 -56
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +28 -40
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
- package/dist/extensions/agent-browser/lib/playbook.js +9 -8
- package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
- package/dist/extensions/agent-browser/lib/process.js +270 -34
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +35 -12
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
- package/dist/extensions/agent-browser/lib/runtime.js +85 -85
- package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
- package/dist/extensions/agent-browser/lib/temp.js +13 -25
- package/docs/ARCHITECTURE.md +9 -8
- package/docs/COMMAND_REFERENCE.md +43 -23
- package/docs/ELECTRON.md +10 -10
- package/docs/RELEASE.md +3 -2
- package/docs/SUPPORT_MATRIX.md +19 -18
- package/docs/TOOL_CONTRACT.md +28 -25
- package/docs/platform-smoke.md +2 -2
- package/package.json +1 -1
- package/platform-smoke.config.mjs +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +11 -3
|
@@ -81,6 +81,12 @@ const COMMAND_CAPABILITIES = [
|
|
|
81
81
|
command: "errors",
|
|
82
82
|
readOnlyDiagnosticSessionTarget: true,
|
|
83
83
|
},
|
|
84
|
+
{
|
|
85
|
+
command: "eval",
|
|
86
|
+
invalidatesBatchRefs: true,
|
|
87
|
+
navigationObservable: true,
|
|
88
|
+
triggersPostMutationSnapshot: true,
|
|
89
|
+
},
|
|
84
90
|
{
|
|
85
91
|
command: "fill",
|
|
86
92
|
eligibleForElectronHealthProbe: true,
|
|
@@ -305,6 +311,11 @@ export function isElectronPostCommandHealthCommand(command) {
|
|
|
305
311
|
export function isNavigationObservableCommandName(command) {
|
|
306
312
|
return hasCommandCapability(command, "navigationObservable");
|
|
307
313
|
}
|
|
314
|
+
export function isUnverifiedPageTransitionCommand(command, subcommand) {
|
|
315
|
+
return ["back", "connect", "eval", "forward", "reload"].includes(command ?? "")
|
|
316
|
+
|| (command === "state" && subcommand === "load")
|
|
317
|
+
|| (command === "tab" && subcommand !== undefined && !["list", "new"].includes(subcommand));
|
|
318
|
+
}
|
|
308
319
|
export function isPageMutationCommand(command) {
|
|
309
320
|
return hasCommandCapability(command, "triggersPostMutationSnapshot");
|
|
310
321
|
}
|
|
@@ -33,11 +33,11 @@ export function parseCdpTargets(value) {
|
|
|
33
33
|
webSocketDebuggerUrl: asString(target.webSocketDebuggerUrl),
|
|
34
34
|
}));
|
|
35
35
|
}
|
|
36
|
-
export async function fetchCdpJson(url) {
|
|
36
|
+
export async function fetchCdpJson(url, signal) {
|
|
37
37
|
const controller = new AbortController();
|
|
38
38
|
const timeout = setTimeout(() => controller.abort(), ELECTRON_CDP_FETCH_TIMEOUT_MS);
|
|
39
39
|
try {
|
|
40
|
-
const response = await fetch(url, { signal: controller.signal });
|
|
40
|
+
const response = await fetch(url, { signal: signal ? AbortSignal.any([signal, controller.signal]) : controller.signal });
|
|
41
41
|
if (!response.ok)
|
|
42
42
|
return undefined;
|
|
43
43
|
return await response.json();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Responsibilities: Resolve Electron targets, enforce caller-owned allow/deny policy, create isolated userDataDir profiles, launch with remote debugging on an OS-chosen port, poll DevToolsActivePort, and read bounded CDP version/target metadata.
|
|
4
4
|
* Scope: Host-side Electron lifecycle setup only; upstream agent-browser attach/presentation stays in the extension entrypoint.
|
|
5
5
|
* Usage: Called by the agent_browser electron.launch shorthand before routing through upstream `connect`.
|
|
6
|
-
* Invariants/Assumptions: The wrapper only launches targets with Electron framework evidence, always uses an isolated temp profile,
|
|
6
|
+
* Invariants/Assumptions: The wrapper only launches targets with Electron framework evidence, always uses an isolated temp profile, never accepts a caller-supplied remote debugging port, and cleans any spawned process/profile when cancellation interrupts readiness.
|
|
7
7
|
*/
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
@@ -24,8 +24,18 @@ function normalizeTimeoutMs(timeoutMs) {
|
|
|
24
24
|
return ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS;
|
|
25
25
|
return Math.min(timeoutMs, ELECTRON_LAUNCH_MAX_TIMEOUT_MS);
|
|
26
26
|
}
|
|
27
|
-
function sleep(ms) {
|
|
28
|
-
|
|
27
|
+
function sleep(ms, signal) {
|
|
28
|
+
if (signal?.aborted)
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const timer = setTimeout(done, ms);
|
|
32
|
+
function done() {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
signal?.removeEventListener("abort", done);
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
38
|
+
});
|
|
29
39
|
}
|
|
30
40
|
function normalizeIdentifier(value) {
|
|
31
41
|
const trimmed = value?.trim().toLowerCase();
|
|
@@ -110,6 +120,8 @@ async function readDevToolsActivePort(userDataDir) {
|
|
|
110
120
|
async function pollDevToolsActivePort(options) {
|
|
111
121
|
let devToolsActivePort;
|
|
112
122
|
while (Date.now() <= options.deadlineMs) {
|
|
123
|
+
if (options.signal?.aborted)
|
|
124
|
+
return { devToolsActivePort, failure: "aborted" };
|
|
113
125
|
const spawnError = options.getSpawnError();
|
|
114
126
|
if (spawnError)
|
|
115
127
|
return { devToolsActivePort, failure: "spawn-error", spawnError };
|
|
@@ -120,20 +132,24 @@ async function pollDevToolsActivePort(options) {
|
|
|
120
132
|
if (exit.code !== null || exit.signal !== null) {
|
|
121
133
|
return { devToolsActivePort, failure: exit.code === 0 ? "single-instance-conflict" : "spawn-error" };
|
|
122
134
|
}
|
|
123
|
-
await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
|
|
135
|
+
await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS, options.signal);
|
|
124
136
|
}
|
|
125
137
|
return { devToolsActivePort, failure: "timeout" };
|
|
126
138
|
}
|
|
127
|
-
async function pollCdpMetadata(port, deadlineMs) {
|
|
139
|
+
async function pollCdpMetadata(port, deadlineMs, signal) {
|
|
128
140
|
while (Date.now() <= deadlineMs) {
|
|
129
|
-
|
|
141
|
+
if (signal?.aborted)
|
|
142
|
+
return { aborted: true };
|
|
143
|
+
const version = parseCdpVersion(await fetchCdpJson(`http://127.0.0.1:${port}/json/version`, signal));
|
|
144
|
+
if (signal?.aborted)
|
|
145
|
+
return { aborted: true };
|
|
130
146
|
if (version) {
|
|
131
|
-
const targets = parseCdpTargets(await fetchCdpJson(`http://127.0.0.1:${port}/json/list
|
|
132
|
-
return { targets, version };
|
|
147
|
+
const targets = parseCdpTargets(await fetchCdpJson(`http://127.0.0.1:${port}/json/list`, signal));
|
|
148
|
+
return signal?.aborted ? { aborted: true } : { aborted: false, metadata: { targets, version } };
|
|
133
149
|
}
|
|
134
|
-
await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
|
|
150
|
+
await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS, signal);
|
|
135
151
|
}
|
|
136
|
-
return
|
|
152
|
+
return { aborted: false };
|
|
137
153
|
}
|
|
138
154
|
function buildLaunchArgs(userDataDir, appArgs) {
|
|
139
155
|
return [
|
|
@@ -210,6 +226,8 @@ function buildLaunchRecord(options) {
|
|
|
210
226
|
function launchFailureMessage(reason, target, detail) {
|
|
211
227
|
const label = target ? `${target.name} (${target.appPath ?? target.executablePath})` : "target";
|
|
212
228
|
switch (reason) {
|
|
229
|
+
case "aborted":
|
|
230
|
+
return `Electron launch was aborted${target ? ` before ${label} finished starting` : " before the app started"}.`;
|
|
213
231
|
case "non-electron-target":
|
|
214
232
|
return `Electron launch rejected: ${label} does not have Electron framework evidence.`;
|
|
215
233
|
case "policy-blocked":
|
|
@@ -226,7 +244,11 @@ function launchFailureMessage(reason, target, detail) {
|
|
|
226
244
|
}
|
|
227
245
|
export async function launchElectronApp(options) {
|
|
228
246
|
const appArgs = options.appArgs ?? [];
|
|
247
|
+
if (options.signal?.aborted)
|
|
248
|
+
return { ok: false, failure: { appArgs, error: launchFailureMessage("aborted", undefined), reason: "aborted" } };
|
|
229
249
|
const target = await resolveElectronLaunchTarget(options);
|
|
250
|
+
if (options.signal?.aborted)
|
|
251
|
+
return { ok: false, failure: { appArgs, error: launchFailureMessage("aborted", target), reason: "aborted", target } };
|
|
230
252
|
if (!target) {
|
|
231
253
|
return {
|
|
232
254
|
ok: false,
|
|
@@ -254,6 +276,16 @@ export async function launchElectronApp(options) {
|
|
|
254
276
|
const startedAtMs = Date.now();
|
|
255
277
|
const deadlineMs = startedAtMs + timeoutMs;
|
|
256
278
|
const userDataDir = await createSecureTempDirectory(ELECTRON_PROFILE_DIR_PREFIX);
|
|
279
|
+
if (options.signal?.aborted) {
|
|
280
|
+
let cleanupError;
|
|
281
|
+
try {
|
|
282
|
+
await rm(userDataDir, { force: true, recursive: true });
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
cleanupError = error instanceof Error ? error.message : String(error);
|
|
286
|
+
}
|
|
287
|
+
return { ok: false, failure: { appArgs, cleanupError, error: launchFailureMessage("aborted", target), reason: "aborted", target, userDataDir } };
|
|
288
|
+
}
|
|
257
289
|
let cleanupError;
|
|
258
290
|
let spawnError;
|
|
259
291
|
let exitCode = null;
|
|
@@ -312,15 +344,19 @@ export async function launchElectronApp(options) {
|
|
|
312
344
|
deadlineMs,
|
|
313
345
|
getChildExit: () => ({ code: exitCode, signal: exitSignal }),
|
|
314
346
|
getSpawnError: () => spawnError,
|
|
347
|
+
signal: options.signal,
|
|
315
348
|
userDataDir,
|
|
316
349
|
});
|
|
317
350
|
if (!portResult.port) {
|
|
318
351
|
return fail(portResult.failure ?? "timeout", portResult.spawnError?.message, { devToolsActivePort: portResult.devToolsActivePort });
|
|
319
352
|
}
|
|
320
|
-
const
|
|
321
|
-
if (
|
|
353
|
+
const metadataResult = await pollCdpMetadata(portResult.port, deadlineMs, options.signal);
|
|
354
|
+
if (metadataResult.aborted)
|
|
355
|
+
return fail("aborted", undefined, { devToolsActivePort: portResult.devToolsActivePort, port: portResult.port });
|
|
356
|
+
if (!metadataResult.metadata) {
|
|
322
357
|
return fail("port-not-found", undefined, { cdpVersionReached: false, devToolsActivePort: portResult.devToolsActivePort, port: portResult.port });
|
|
323
358
|
}
|
|
359
|
+
const metadata = metadataResult.metadata;
|
|
324
360
|
const record = buildLaunchRecord({
|
|
325
361
|
createdAtMs: Date.now(),
|
|
326
362
|
pid: child.pid,
|
|
@@ -7,138 +7,136 @@ import { JsonSchema } from "../json-schema.js";
|
|
|
7
7
|
import { StringEnum as localStringEnum } from "../string-enum-schema.js";
|
|
8
8
|
import { ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS, ELECTRON_DISCOVERY_MAX_RESULTS, } from "../electron/discovery.js";
|
|
9
9
|
import { AGENT_BROWSER_ELECTRON_HANDOFFS, AGENT_BROWSER_ELECTRON_TARGET_TYPES, AGENT_BROWSER_JOB_STEP_ACTIONS, AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS, AGENT_BROWSER_QA_LOAD_STATES, AGENT_BROWSER_SEMANTIC_ACTIONS, AGENT_BROWSER_SEMANTIC_LOCATORS, DEFAULT_SESSION_MODE, SOURCE_LOOKUP_MAX_WORKSPACE_FILES, } from "./types.js";
|
|
10
|
+
// Keep descriptions terse: Pi sends this schema every turn; workflows belong in prompt guidance and docs.
|
|
10
11
|
// ponytail: the four electron.launch variants differ only in their single target field
|
|
11
|
-
// (appPath/appName/bundleId/executablePath); the action literal and
|
|
12
|
-
//
|
|
12
|
+
// (appPath/appName/bundleId/executablePath); the action literal and shared launch fields
|
|
13
|
+
// are identical, so this helper keeps the schema variants in sync.
|
|
13
14
|
function electronLaunchVariant(Type, StringEnum, targetField) {
|
|
14
15
|
return Type.Object({
|
|
15
|
-
action: StringEnum(["launch"]
|
|
16
|
+
action: StringEnum(["launch"]),
|
|
16
17
|
...targetField,
|
|
17
|
-
appArgs: Type.Optional(Type.Array(Type.String({
|
|
18
|
-
handoff: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_HANDOFFS
|
|
19
|
-
targetType: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_TARGET_TYPES
|
|
20
|
-
timeoutMs: Type.Optional(Type.Integer({
|
|
21
|
-
allow: Type.Optional(Type.Array(Type.String({
|
|
22
|
-
deny: Type.Optional(Type.Array(Type.String({
|
|
18
|
+
appArgs: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
19
|
+
handoff: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_HANDOFFS)),
|
|
20
|
+
targetType: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_TARGET_TYPES)),
|
|
21
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
22
|
+
allow: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
23
|
+
deny: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
23
24
|
}, { additionalProperties: false });
|
|
24
25
|
}
|
|
25
26
|
export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
26
27
|
return Type.Object({
|
|
27
|
-
args: Type.Optional(Type.Array(Type.String(
|
|
28
|
-
description: "
|
|
28
|
+
args: Type.Optional(Type.Array(Type.String(), {
|
|
29
|
+
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.",
|
|
29
30
|
minItems: 1,
|
|
30
31
|
})),
|
|
31
32
|
semanticAction: Type.Optional(Type.Object({
|
|
32
|
-
action: StringEnum(AGENT_BROWSER_SEMANTIC_ACTIONS,
|
|
33
|
-
|
|
34
|
-
}),
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
})),
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
role: Type.Optional(Type.String({ description: "Role locator value for locator=role. May be used instead of value; when both are set they must match." })),
|
|
43
|
-
name: Type.Optional(Type.String({ description: "Accessible name filter for locator=role; compiles to --name <name>." })),
|
|
44
|
-
session: Type.Optional(Type.String({ description: "Optional upstream session name; prepends --session <name> before the compiled command." })),
|
|
45
|
-
}, { additionalProperties: false })),
|
|
33
|
+
action: StringEnum(AGENT_BROWSER_SEMANTIC_ACTIONS),
|
|
34
|
+
locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Locator for check/click/fill." })),
|
|
35
|
+
value: Type.Optional(Type.String({ description: "Locator value, or one select option." })),
|
|
36
|
+
values: Type.Optional(Type.Array(Type.String(), { description: "Select options.", minItems: 1 })),
|
|
37
|
+
selector: Type.Optional(Type.String({ description: "Direct selector or @ref." })),
|
|
38
|
+
text: Type.Optional(Type.String({ description: "Fill text." })),
|
|
39
|
+
role: Type.Optional(Type.String({ description: "Role locator; alternative to value." })),
|
|
40
|
+
name: Type.Optional(Type.String({ description: "Accessible name." })),
|
|
41
|
+
session: Type.Optional(Type.String({ description: "Upstream session name." })),
|
|
42
|
+
}, { additionalProperties: false, description: "Stable locator or direct-selector action." })),
|
|
46
43
|
qa: Type.Optional(Type.Union([
|
|
47
44
|
Type.Object({
|
|
48
|
-
attached: Type.Literal(true
|
|
49
|
-
expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())]
|
|
50
|
-
expectedSelector: Type.Optional(Type.String(
|
|
51
|
-
screenshotPath: Type.Optional(Type.String(
|
|
52
|
-
checkConsole: Type.Optional(Type.Boolean(
|
|
53
|
-
checkErrors: Type.Optional(Type.Boolean(
|
|
54
|
-
checkNetwork: Type.Optional(Type.Boolean(
|
|
55
|
-
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES
|
|
45
|
+
attached: Type.Literal(true),
|
|
46
|
+
expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
|
|
47
|
+
expectedSelector: Type.Optional(Type.String()),
|
|
48
|
+
screenshotPath: Type.Optional(Type.String()),
|
|
49
|
+
checkConsole: Type.Optional(Type.Boolean()),
|
|
50
|
+
checkErrors: Type.Optional(Type.Boolean()),
|
|
51
|
+
checkNetwork: Type.Optional(Type.Boolean()),
|
|
52
|
+
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES)),
|
|
56
53
|
}, { additionalProperties: false }),
|
|
57
54
|
Type.Object({
|
|
58
|
-
url: Type.String(
|
|
59
|
-
attached: Type.Optional(Type.Literal(false
|
|
60
|
-
expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())]
|
|
61
|
-
expectedSelector: Type.Optional(Type.String(
|
|
62
|
-
screenshotPath: Type.Optional(Type.String(
|
|
63
|
-
checkConsole: Type.Optional(Type.Boolean(
|
|
64
|
-
checkErrors: Type.Optional(Type.Boolean(
|
|
65
|
-
checkNetwork: Type.Optional(Type.Boolean(
|
|
66
|
-
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES
|
|
55
|
+
url: Type.String(),
|
|
56
|
+
attached: Type.Optional(Type.Literal(false)),
|
|
57
|
+
expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
|
|
58
|
+
expectedSelector: Type.Optional(Type.String()),
|
|
59
|
+
screenshotPath: Type.Optional(Type.String()),
|
|
60
|
+
checkConsole: Type.Optional(Type.Boolean()),
|
|
61
|
+
checkErrors: Type.Optional(Type.Boolean()),
|
|
62
|
+
checkNetwork: Type.Optional(Type.Boolean()),
|
|
63
|
+
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES)),
|
|
67
64
|
}, { additionalProperties: false }),
|
|
68
|
-
], { description: "
|
|
65
|
+
], { description: "QA a URL or current session (attached=true). Default readiness: domcontentloaded; use networkidle only without long-lived requests. URL diagnostics default on, attached diagnostics off." })),
|
|
69
66
|
sourceLookup: Type.Optional(Type.Object({
|
|
70
|
-
selector: Type.Optional(Type.String({ description: "Visible selector or @ref
|
|
71
|
-
reactFiberId: Type.Optional(Type.String({ description: "React fiber id
|
|
72
|
-
componentName: Type.Optional(Type.String({ description: "Component
|
|
73
|
-
includeDomHints: Type.Optional(Type.Boolean({ description: "
|
|
74
|
-
maxWorkspaceFiles: Type.Optional(Type.Number({ description: "
|
|
75
|
-
}, { additionalProperties: false, description: "EXPERIMENTAL
|
|
67
|
+
selector: Type.Optional(Type.String({ description: "Visible selector or @ref." })),
|
|
68
|
+
reactFiberId: Type.Optional(Type.String({ description: "React fiber id; requires --enable react-devtools." })),
|
|
69
|
+
componentName: Type.Optional(Type.String({ description: "Component for local source search." })),
|
|
70
|
+
includeDomHints: Type.Optional(Type.Boolean({ description: "Inspect DOM source hints; default true." })),
|
|
71
|
+
maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
|
|
72
|
+
}, { additionalProperties: false, description: "EXPERIMENTAL UI-to-source candidates; not guaranteed mappings." })),
|
|
76
73
|
networkSourceLookup: Type.Optional(Type.Object({
|
|
77
|
-
filter: Type.Optional(Type.String({ description: "
|
|
78
|
-
namespace: Type.Optional(Type.String(
|
|
79
|
-
requestId: Type.Optional(Type.String({ description: "
|
|
80
|
-
session: Type.Optional(Type.String(
|
|
81
|
-
url: Type.Optional(Type.String({ description: "
|
|
82
|
-
maxWorkspaceFiles: Type.Optional(Type.Number({ description: "
|
|
83
|
-
}, { additionalProperties: false, description: "EXPERIMENTAL
|
|
74
|
+
filter: Type.Optional(Type.String({ description: "Network request filter." })),
|
|
75
|
+
namespace: Type.Optional(Type.String()),
|
|
76
|
+
requestId: Type.Optional(Type.String({ description: "Request id to inspect." })),
|
|
77
|
+
session: Type.Optional(Type.String()),
|
|
78
|
+
url: Type.Optional(Type.String({ description: "Failed URL or fragment." })),
|
|
79
|
+
maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
|
|
80
|
+
}, { additionalProperties: false, description: "EXPERIMENTAL failed-request-to-source candidates; not proof." })),
|
|
84
81
|
electron: Type.Optional(Type.Union([
|
|
85
82
|
Type.Object({
|
|
86
|
-
action: StringEnum(["list"]
|
|
87
|
-
query: Type.Optional(Type.String({ description: "
|
|
88
|
-
maxResults: Type.Optional(Type.Integer({ description: `
|
|
83
|
+
action: StringEnum(["list"]),
|
|
84
|
+
query: Type.Optional(Type.String({ description: "Case-insensitive app filter.", minLength: 1 })),
|
|
85
|
+
maxResults: Type.Optional(Type.Integer({ description: `Result cap; default ${ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS}, values over ${ELECTRON_DISCOVERY_MAX_RESULTS} are clamped.`, minimum: 1 })),
|
|
89
86
|
}, { additionalProperties: false }),
|
|
90
|
-
electronLaunchVariant(Type, StringEnum, { appPath: Type.String({ description: "
|
|
91
|
-
electronLaunchVariant(Type, StringEnum, { appName: Type.String({ description: "
|
|
92
|
-
electronLaunchVariant(Type, StringEnum, { bundleId: Type.String({ description: "
|
|
93
|
-
electronLaunchVariant(Type, StringEnum, { executablePath: Type.String({ description: "
|
|
87
|
+
electronLaunchVariant(Type, StringEnum, { appPath: Type.String({ description: "macOS .app path.", minLength: 1 }) }),
|
|
88
|
+
electronLaunchVariant(Type, StringEnum, { appName: Type.String({ description: "Name from electron.list.", minLength: 1 }) }),
|
|
89
|
+
electronLaunchVariant(Type, StringEnum, { bundleId: Type.String({ description: "Bundle id from electron.list.", minLength: 1 }) }),
|
|
90
|
+
electronLaunchVariant(Type, StringEnum, { executablePath: Type.String({ description: "Executable path.", minLength: 1 }) }),
|
|
94
91
|
Type.Object({
|
|
95
|
-
action: StringEnum(["status", "cleanup"]
|
|
96
|
-
launchId: Type.String({ description: "
|
|
97
|
-
timeoutMs: Type.Optional(Type.Integer({
|
|
92
|
+
action: StringEnum(["status", "cleanup"]),
|
|
93
|
+
launchId: Type.String({ description: "Tracked launch id.", minLength: 1 }),
|
|
94
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
98
95
|
}, { additionalProperties: false }),
|
|
99
96
|
Type.Object({
|
|
100
|
-
action: StringEnum(["status", "cleanup"]
|
|
101
|
-
all: Type.Literal(true
|
|
102
|
-
timeoutMs: Type.Optional(Type.Integer({
|
|
97
|
+
action: StringEnum(["status", "cleanup"]),
|
|
98
|
+
all: Type.Literal(true),
|
|
99
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
103
100
|
}, { additionalProperties: false }),
|
|
104
101
|
Type.Object({
|
|
105
|
-
action: StringEnum(["status", "cleanup"]
|
|
106
|
-
timeoutMs: Type.Optional(Type.Integer({
|
|
102
|
+
action: StringEnum(["status", "cleanup"]),
|
|
103
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
107
104
|
}, { additionalProperties: false }),
|
|
108
105
|
Type.Object({
|
|
109
|
-
action: StringEnum(["probe"]
|
|
110
|
-
launchId: Type.Optional(Type.String({ description: "
|
|
111
|
-
timeoutMs: Type.Optional(Type.Integer({
|
|
106
|
+
action: StringEnum(["probe"]),
|
|
107
|
+
launchId: Type.Optional(Type.String({ description: "Tracked launch id.", minLength: 1 })),
|
|
108
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
112
109
|
}, { additionalProperties: false }),
|
|
113
|
-
], { description: "Electron
|
|
110
|
+
], { description: "Electron discovery, isolated-profile launch, status, probe, or cleanup. Launch defaults: handoff=snapshot, targetType=page; deny wins over allow; lifecycle/debug appArgs rejected." })),
|
|
114
111
|
job: Type.Optional(Type.Object({
|
|
115
|
-
failFast: Type.Optional(Type.Boolean({ description: "Stop
|
|
112
|
+
failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure; default true." })),
|
|
116
113
|
steps: Type.Array(Type.Object({
|
|
117
|
-
action: StringEnum(AGENT_BROWSER_JOB_STEP_ACTIONS,
|
|
118
|
-
|
|
119
|
-
}),
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
press: Type.Optional(Type.String({ description: "Optional key to press after a type step, for example Enter." })),
|
|
132
|
-
milliseconds: Type.Optional(Type.Number({ description: "Milliseconds for wait steps." })),
|
|
114
|
+
action: StringEnum(AGENT_BROWSER_JOB_STEP_ACTIONS),
|
|
115
|
+
url: Type.Optional(Type.String({ description: "Open URL or assertUrl glob." })),
|
|
116
|
+
loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES, { description: "Readiness wait after open." })),
|
|
117
|
+
selector: Type.Optional(Type.String({ description: "Selector or @ref." })),
|
|
118
|
+
locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Locator when selector is omitted." })),
|
|
119
|
+
role: Type.Optional(Type.String({ description: "Role locator." })),
|
|
120
|
+
name: Type.Optional(Type.String({ description: "Accessible name." })),
|
|
121
|
+
text: Type.Optional(Type.String({ description: "Fill text or assertText target." })),
|
|
122
|
+
value: Type.Optional(Type.String({ description: "Select option or locator value." })),
|
|
123
|
+
values: Type.Optional(Type.Array(Type.String(), { description: "Select options.", minItems: 1 })),
|
|
124
|
+
path: Type.Optional(Type.String({ description: "Download or screenshot path." })),
|
|
125
|
+
delayMs: Type.Optional(Type.Integer({ description: `Per-character type delay; text is capped at ${AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS} characters.`, minimum: 1 })),
|
|
126
|
+
press: Type.Optional(Type.String({ description: "Key to press after typing." })),
|
|
127
|
+
milliseconds: Type.Optional(Type.Number({ description: "Wait duration in milliseconds." })),
|
|
133
128
|
}, { additionalProperties: false }), { minItems: 1 }),
|
|
134
|
-
}, { additionalProperties: false })),
|
|
135
|
-
stdin: Type.Optional(Type.String({ description: "
|
|
136
|
-
outputPath: Type.Optional(Type.String({ description: "
|
|
137
|
-
timeoutMs: Type.Optional(Type.Integer({ description: "
|
|
129
|
+
}, { additionalProperties: false, description: "Constrained multi-step batch." })),
|
|
130
|
+
stdin: Type.Optional(Type.String({ description: "Raw stdin for batch, eval --stdin, or auth save --password-stdin; unavailable with structured modes and electron." })),
|
|
131
|
+
outputPath: Type.Optional(Type.String({ description: "Workspace-relative or absolute result path.", minLength: 1 })),
|
|
132
|
+
timeoutMs: Type.Optional(Type.Integer({ description: "Wrapper timeout in ms; exceed explicit waits. Electron uses electron.timeoutMs.", minimum: 1 })),
|
|
138
133
|
sessionMode: Type.Optional(StringEnum(["auto", "fresh"], {
|
|
139
|
-
description: "
|
|
134
|
+
description: "auto reuses the managed session; fresh starts one for launch-only flags, then makes it the managed session.",
|
|
140
135
|
default: DEFAULT_SESSION_MODE,
|
|
141
136
|
})),
|
|
142
|
-
}, {
|
|
137
|
+
}, {
|
|
138
|
+
additionalProperties: false,
|
|
139
|
+
description: "Choose one input mode: args, semanticAction, job, qa, sourceLookup, networkSourceLookup, or electron.",
|
|
140
|
+
});
|
|
143
141
|
}
|
|
144
142
|
export const AGENT_BROWSER_PARAMS = createAgentBrowserParamsSchema();
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Purpose: Canonical launch-scoped agent-browser flag metadata shared by runtime planning and agent-facing guidance.
|
|
3
|
-
* Responsibilities: Define which upstream flags require a fresh launch, explain why,
|
|
4
|
-
* Scope:
|
|
3
|
+
* Responsibilities: Define which upstream flags require a fresh launch, explain why, expose stable guidance labels, and share launch-token detection used by runtime and managed restore policy.
|
|
4
|
+
* Scope: Launch-scoped metadata and token classification; broader execution planning lives in runtime.ts.
|
|
5
5
|
*/
|
|
6
|
+
import { findCommandStartIndex } from "./argv-descriptor.js";
|
|
7
|
+
import { isBooleanFlagEnabled, optionalGlobalValueFlagConsumesNext } from "./argv-grammar.js";
|
|
6
8
|
export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
|
|
7
9
|
{
|
|
8
10
|
flag: "--auto-connect",
|
|
@@ -88,3 +90,87 @@ export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
|
|
|
88
90
|
export const LAUNCH_SCOPED_FLAGS = LAUNCH_SCOPED_FLAG_DEFINITIONS.map((definition) => definition.flag);
|
|
89
91
|
export const LAUNCH_SCOPED_FLAG_LABEL = LAUNCH_SCOPED_FLAGS.join(", ");
|
|
90
92
|
export const OPEN_RESULT_TAB_CORRECTION_FLAGS = new Set(["--profile", "--restore", "--session-name", "--state"]);
|
|
93
|
+
/** Launch modes that must never be combined with wrapper-managed restore state. */
|
|
94
|
+
export const MANAGED_RESTORE_INCOMPATIBLE_FLAGS = [
|
|
95
|
+
"--restore",
|
|
96
|
+
"--restore-save",
|
|
97
|
+
"--restore-check-url",
|
|
98
|
+
"--restore-check-text",
|
|
99
|
+
"--restore-check-fn",
|
|
100
|
+
"--allowed-domains",
|
|
101
|
+
"--profile",
|
|
102
|
+
"--state",
|
|
103
|
+
"--cdp",
|
|
104
|
+
"--auto-connect",
|
|
105
|
+
"--session-name",
|
|
106
|
+
"--provider",
|
|
107
|
+
"-p",
|
|
108
|
+
"--executable-path",
|
|
109
|
+
"--extension",
|
|
110
|
+
"--init-script",
|
|
111
|
+
"--enable",
|
|
112
|
+
"--args",
|
|
113
|
+
"--user-agent",
|
|
114
|
+
"--proxy",
|
|
115
|
+
"--proxy-bypass",
|
|
116
|
+
"--ignore-https-errors",
|
|
117
|
+
"--allow-file-access",
|
|
118
|
+
"--webgpu",
|
|
119
|
+
"--device",
|
|
120
|
+
"--engine",
|
|
121
|
+
];
|
|
122
|
+
/** Nonempty upstream env defaults that can replace or mutate the browser receiving restored state. */
|
|
123
|
+
export const MANAGED_RESTORE_INCOMPATIBLE_ENVS = [
|
|
124
|
+
"AGENT_BROWSER_RESTORE",
|
|
125
|
+
"AGENT_BROWSER_RESTORE_SAVE",
|
|
126
|
+
"AGENT_BROWSER_RESTORE_CHECK_URL",
|
|
127
|
+
"AGENT_BROWSER_RESTORE_CHECK_TEXT",
|
|
128
|
+
"AGENT_BROWSER_RESTORE_CHECK_FN",
|
|
129
|
+
"AGENT_BROWSER_ALLOWED_DOMAINS",
|
|
130
|
+
"AGENT_BROWSER_PROFILE",
|
|
131
|
+
"AGENT_BROWSER_STATE",
|
|
132
|
+
"AGENT_BROWSER_CDP",
|
|
133
|
+
"AGENT_BROWSER_NAMESPACE",
|
|
134
|
+
"AGENT_BROWSER_SESSION_NAME",
|
|
135
|
+
"AGENT_BROWSER_PROVIDER",
|
|
136
|
+
"AGENT_BROWSER_EXECUTABLE_PATH",
|
|
137
|
+
"AGENT_BROWSER_EXTENSIONS",
|
|
138
|
+
"AGENT_BROWSER_INIT_SCRIPTS",
|
|
139
|
+
"AGENT_BROWSER_ENABLE",
|
|
140
|
+
"AGENT_BROWSER_ARGS",
|
|
141
|
+
"AGENT_BROWSER_USER_AGENT",
|
|
142
|
+
"AGENT_BROWSER_PROXY",
|
|
143
|
+
"AGENT_BROWSER_PROXY_BYPASS",
|
|
144
|
+
"AGENT_BROWSER_PLUGINS",
|
|
145
|
+
"AGENT_BROWSER_IOS_DEVICE",
|
|
146
|
+
"AGENT_BROWSER_IOS_UDID",
|
|
147
|
+
"AGENT_BROWSER_ENGINE",
|
|
148
|
+
"HTTP_PROXY",
|
|
149
|
+
"HTTPS_PROXY",
|
|
150
|
+
"ALL_PROXY",
|
|
151
|
+
"http_proxy",
|
|
152
|
+
"https_proxy",
|
|
153
|
+
"all_proxy",
|
|
154
|
+
];
|
|
155
|
+
/** Boolean launch mutators block restore only when enabled. */
|
|
156
|
+
export const MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS = [
|
|
157
|
+
"AGENT_BROWSER_AUTO_CONNECT",
|
|
158
|
+
"AGENT_BROWSER_IGNORE_HTTPS_ERRORS",
|
|
159
|
+
"AGENT_BROWSER_ALLOW_FILE_ACCESS",
|
|
160
|
+
"AGENT_BROWSER_WEBGPU",
|
|
161
|
+
];
|
|
162
|
+
export function hasLaunchScopedFlagToken(args, flag) {
|
|
163
|
+
const commandStartIndex = findCommandStartIndex(args);
|
|
164
|
+
const command = commandStartIndex === undefined ? undefined : args[commandStartIndex];
|
|
165
|
+
return args.some((token, index) => {
|
|
166
|
+
if (token !== flag && !token.startsWith(`${flag}=`))
|
|
167
|
+
return false;
|
|
168
|
+
if (flag === "--auto-connect")
|
|
169
|
+
return isBooleanFlagEnabled(args, flag);
|
|
170
|
+
if (flag === "--restore" && token === "--restore" && optionalGlobalValueFlagConsumesNext(flag, args[index + 1]))
|
|
171
|
+
return true;
|
|
172
|
+
if (flag === "--state" && command === "wait" && commandStartIndex !== undefined && index > commandStartIndex)
|
|
173
|
+
return false;
|
|
174
|
+
return true;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Purpose: Identify and redact wrapper-owned managed restore capabilities.
|
|
2
|
+
// Scope: Pure string helpers shared by storage, policy, and presentation paths.
|
|
3
|
+
const MANAGED_SESSION_RESTORE_KEY_PATTERN = /piab-r(?:2)?-[a-f\d]{32}/gi;
|
|
4
|
+
const MANAGED_SESSION_RESTORE_KEY_EXACT_PATTERN = /^piab-r2-[a-f\d]{32}$/i;
|
|
5
|
+
const WRAPPER_MANAGED_SESSION_PREFIX = "piab-";
|
|
6
|
+
export function isWrapperManagedSessionName(value) {
|
|
7
|
+
return typeof value === "string" && value.toLowerCase().startsWith(WRAPPER_MANAGED_SESSION_PREFIX);
|
|
8
|
+
}
|
|
9
|
+
export function isManagedSessionRestoreKey(value) {
|
|
10
|
+
return typeof value === "string" && MANAGED_SESSION_RESTORE_KEY_EXACT_PATTERN.test(value);
|
|
11
|
+
}
|
|
12
|
+
export function extractManagedSessionRestoreKeys(value) {
|
|
13
|
+
return [...new Set(value.match(MANAGED_SESSION_RESTORE_KEY_PATTERN)?.map((key) => key.toLowerCase()) ?? [])];
|
|
14
|
+
}
|
|
15
|
+
export function containsManagedSessionRestoreKey(value) {
|
|
16
|
+
MANAGED_SESSION_RESTORE_KEY_PATTERN.lastIndex = 0;
|
|
17
|
+
return MANAGED_SESSION_RESTORE_KEY_PATTERN.test(value);
|
|
18
|
+
}
|
|
19
|
+
export function redactManagedSessionRestoreKeys(value) {
|
|
20
|
+
MANAGED_SESSION_RESTORE_KEY_PATTERN.lastIndex = 0;
|
|
21
|
+
return value.replace(MANAGED_SESSION_RESTORE_KEY_PATTERN, "[REDACTED MANAGED STATE]");
|
|
22
|
+
}
|