pi-agent-browser-native 0.6.9 → 0.6.11
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 +48 -0
- package/README.md +62 -19
- package/dist/extensions/agent-browser/index.js +424 -451
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -7
- package/dist/extensions/agent-browser/lib/argv-grammar.js +7 -1
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +4 -8
- package/dist/extensions/agent-browser/lib/command-policy.js +41 -2
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +15 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/script.js +3 -2
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +42 -12
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +3 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +6 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -25
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +12 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +3 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +38 -31
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +60 -20
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +161 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +5 -5
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +2 -4
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +68 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -6
- package/dist/extensions/agent-browser/lib/page-target-validation.js +9 -5
- package/dist/extensions/agent-browser/lib/playbook.js +13 -12
- package/dist/extensions/agent-browser/lib/process-environment.js +26 -8
- package/dist/extensions/agent-browser/lib/process.js +8 -5
- package/dist/extensions/agent-browser/lib/read-confirmation.js +59 -0
- package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -1
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +8 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -5
- package/dist/extensions/agent-browser/lib/results/categories.js +4 -2
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +76 -57
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +19 -8
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +5 -25
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +40 -38
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +3 -3
- package/dist/extensions/agent-browser/lib/results/presentation.js +38 -9
- package/dist/extensions/agent-browser/lib/results/recording.js +50 -0
- package/dist/extensions/agent-browser/lib/runtime.js +72 -20
- package/dist/extensions/agent-browser/lib/session-page-state.js +24 -8
- package/dist/extensions/agent-browser/lib/temp.js +4 -0
- package/dist/scripts/agent-browser-target.mjs +1 -1
- package/docs/ARCHITECTURE.md +29 -12
- package/docs/COMMAND_REFERENCE.md +67 -31
- package/docs/RELEASE.md +6 -4
- package/docs/SUPPORT_MATRIX.md +24 -16
- package/docs/TOOL_CONTRACT.md +82 -28
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +10 -3
- package/scripts/agent-browser-target.mjs +1 -1
- package/scripts/prepare.mjs +2 -4
|
@@ -48,14 +48,13 @@ export function parseWaitCommandTokens(commandTokens) {
|
|
|
48
48
|
if (match)
|
|
49
49
|
return { subcommand: match.token };
|
|
50
50
|
}
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
const
|
|
54
|
-
const candidate = commandTokens[downloadPathIndex];
|
|
51
|
+
const downloadIndex = considered.findIndex((entry) => entry.token === "--download" || entry.token === "-d");
|
|
52
|
+
if (downloadIndex >= 0) {
|
|
53
|
+
const candidate = considered[downloadIndex + 1];
|
|
55
54
|
return {
|
|
56
|
-
downloadPath: candidate && !candidate.startsWith("--") ? candidate : undefined,
|
|
57
|
-
downloadPathIndex: candidate && !candidate.startsWith("--") ?
|
|
58
|
-
subcommand:
|
|
55
|
+
downloadPath: candidate && !candidate.token.startsWith("--") ? candidate.token : undefined,
|
|
56
|
+
downloadPathIndex: candidate && !candidate.token.startsWith("--") ? candidate.index : undefined,
|
|
57
|
+
subcommand: considered[downloadIndex].token,
|
|
59
58
|
};
|
|
60
59
|
}
|
|
61
60
|
return { subcommand: considered[0]?.token };
|
|
@@ -81,7 +81,7 @@ export const COMMAND_VALUE_FLAGS = [
|
|
|
81
81
|
export const OPTIONAL_GLOBAL_VALUE_FLAGS = new Set(["--restore"]);
|
|
82
82
|
export const VALUE_FLAGS = new Set([...GLOBAL_VALUE_FLAGS, ...COMMAND_VALUE_FLAGS]);
|
|
83
83
|
export const PREVALIDATED_VALUE_FLAGS = new Set(GLOBAL_VALUE_FLAGS);
|
|
84
|
-
export const GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE = new Set(["--args"]);
|
|
84
|
+
export const GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE = new Set(["--args", "--session"]);
|
|
85
85
|
export const GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES = new Set([
|
|
86
86
|
"--allow-file-access",
|
|
87
87
|
"--annotate",
|
|
@@ -175,6 +175,12 @@ export function isAgentBrowserSessionIdentityKeyInNamespace(identityKey, namespa
|
|
|
175
175
|
const prefix = getAgentBrowserSessionIdentityKey("", namespace);
|
|
176
176
|
return prefix ? identityKey.startsWith(prefix) : !identityKey.includes("\0");
|
|
177
177
|
}
|
|
178
|
+
export function deleteIdentityKeysInNamespace(entries, namespace) {
|
|
179
|
+
for (const key of entries.keys()) {
|
|
180
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
181
|
+
entries.delete(key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
178
184
|
/** Mirror upstream global parsing: full argv, no `--` sentinel, and only global value payloads are skipped. */
|
|
179
185
|
export function scanUpstreamGlobalFlagOccurrences(args, targetFlag) {
|
|
180
186
|
const occurrences = [];
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
2
|
import { isCloseAllCommand, isCloseCommand } from "./command-taxonomy.js";
|
|
3
3
|
import { isRecord } from "./parsing.js";
|
|
4
|
-
function getRowBrowserLaunched(row) {
|
|
5
|
-
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
6
|
-
const lifecycle = isRecord(row.lifecycle) ? row.lifecycle : isRecord(result?.lifecycle) ? result.lifecycle : undefined;
|
|
7
|
-
const effectiveLaunch = isRecord(lifecycle?.effectiveLaunch) ? lifecycle.effectiveLaunch : undefined;
|
|
8
|
-
return typeof effectiveLaunch?.browserLaunched === "boolean" ? effectiveLaunch.browserLaunched : undefined;
|
|
9
|
-
}
|
|
10
4
|
export function batchHasSuccessfulCloseAll(data, fallbackCommands = []) {
|
|
11
5
|
if (!Array.isArray(data))
|
|
12
6
|
return false;
|
|
@@ -34,7 +28,10 @@ export function getSuccessfulBatchCloseLifecycle(rows, fallbackCommands = []) {
|
|
|
34
28
|
const rowCommand = Array.isArray(row.command) && row.command.every((token) => typeof token === "string")
|
|
35
29
|
? row.command
|
|
36
30
|
: fallbackCommands[index];
|
|
37
|
-
const
|
|
31
|
+
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
32
|
+
const lifecycle = isRecord(row.lifecycle) ? row.lifecycle : isRecord(result?.lifecycle) ? result.lifecycle : undefined;
|
|
33
|
+
const effectiveLaunch = isRecord(lifecycle?.effectiveLaunch) ? lifecycle.effectiveLaunch : undefined;
|
|
34
|
+
const browserLaunched = typeof effectiveLaunch?.browserLaunched === "boolean" ? effectiveLaunch.browserLaunched : undefined;
|
|
38
35
|
if (!rowCommand) {
|
|
39
36
|
if (sawClose && browserLaunched !== false) {
|
|
40
37
|
endsClosed = false;
|
|
@@ -49,7 +46,6 @@ export function getSuccessfulBatchCloseLifecycle(rows, fallbackCommands = []) {
|
|
|
49
46
|
endsClosed = true;
|
|
50
47
|
browserActiveAfterClose = false;
|
|
51
48
|
recordingClosedAfterBatch = true;
|
|
52
|
-
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
53
49
|
statePath = typeof result?.statePath === "string" ? result.statePath : undefined;
|
|
54
50
|
}
|
|
55
51
|
else if (sawClose && command === "record") {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasOnlyBooleanFlags, hasOnlyOptionFlags, isNonFlagToken, stripSessionlessShapeGlobalFlags } from "./argv-grammar.js";
|
|
2
|
+
import { getUpstreamEffectiveBatchSteps } from "./orchestration/batch-stdin.js";
|
|
2
3
|
const SESSIONLESS_AUTH_SUBCOMMANDS = new Set(["save", "list", "show", "delete", "remove"]);
|
|
3
4
|
const PLUGIN_SESSIONLESS_SUBCOMMANDS = new Set(["list", "show", "add", "run"]);
|
|
4
5
|
const EMPTY_BOOLEAN_FLAGS = new Set();
|
|
@@ -92,6 +93,44 @@ function isSessionlessCommand(commandTokens) {
|
|
|
92
93
|
return isSessionlessStateCommand(normalizedTokens);
|
|
93
94
|
return false;
|
|
94
95
|
}
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
// undefined is a valid DOM read; null is invalid native syntax, which must not trigger page helpers.
|
|
97
|
+
export function getExplicitReadUrl(commandTokens) {
|
|
98
|
+
if (commandTokens[0] !== "read")
|
|
99
|
+
return undefined;
|
|
100
|
+
let url;
|
|
101
|
+
let llms = false;
|
|
102
|
+
let outline = false;
|
|
103
|
+
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
104
|
+
const token = commandTokens[index];
|
|
105
|
+
if (["--filter", "--llms", "--timeout"].includes(token)) {
|
|
106
|
+
const value = commandTokens[++index];
|
|
107
|
+
if (value === undefined)
|
|
108
|
+
return null;
|
|
109
|
+
if (token === "--llms") {
|
|
110
|
+
if (!["index", "full"].includes(value))
|
|
111
|
+
return null;
|
|
112
|
+
llms = true;
|
|
113
|
+
}
|
|
114
|
+
if (token === "--timeout" && (!/^\+?\d+$/.test(value) || BigInt(value) === 0n || BigInt(value) > 18446744073709551615n))
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
else if (["--raw", "--require-md", "--outline", "--json"].includes(token)) {
|
|
118
|
+
if (token === "--outline")
|
|
119
|
+
outline = true;
|
|
120
|
+
}
|
|
121
|
+
else if (token.startsWith("--") || url !== undefined)
|
|
122
|
+
return null;
|
|
123
|
+
else
|
|
124
|
+
url = token;
|
|
125
|
+
}
|
|
126
|
+
return llms && outline ? null : url;
|
|
127
|
+
}
|
|
128
|
+
export function isBrowserIndependentRead(commandTokens, stdin) {
|
|
129
|
+
if (commandTokens[0] !== "batch")
|
|
130
|
+
return getExplicitReadUrl(commandTokens) !== undefined;
|
|
131
|
+
const steps = getUpstreamEffectiveBatchSteps(commandTokens, stdin);
|
|
132
|
+
return steps.length > 0 && steps.every((step) => getExplicitReadUrl(step) !== undefined);
|
|
133
|
+
}
|
|
134
|
+
export function needsManagedSession(descriptor, stdin) {
|
|
135
|
+
return !isSessionlessCommand(descriptor.upstreamCommandTokens) && !isBrowserIndependentRead(descriptor.upstreamCommandTokens, stdin);
|
|
97
136
|
}
|
|
@@ -316,13 +316,26 @@ export function isSessionTabPinningExcludedCommand(command) {
|
|
|
316
316
|
export function isSessionTabPostCommandCorrectionExcludedCommand(command) {
|
|
317
317
|
return hasCommandCapability(command, "excludedFromPostCommandCorrection");
|
|
318
318
|
}
|
|
319
|
-
|
|
319
|
+
export function getRecordCommandOperands(tokens) {
|
|
320
|
+
if (tokens[0] !== "record" || !["start", "restart"].includes(tokens[1] ?? ""))
|
|
321
|
+
return {};
|
|
322
|
+
const operands = [];
|
|
323
|
+
for (let index = 2; index < tokens.length && operands.length < 2; index += 1) {
|
|
324
|
+
// Native validates the range; bare/non-numeric --fps keeps its old literal meaning.
|
|
325
|
+
if (tokens[index] === "--fps" && /^\+?\d+$/.test(tokens[index + 1] ?? ""))
|
|
326
|
+
index += 1;
|
|
327
|
+
else
|
|
328
|
+
operands.push(tokens[index]);
|
|
329
|
+
}
|
|
330
|
+
return operands.length > 0 ? { path: operands[0], url: operands[1] } : { path: tokens[2], url: tokens[3] };
|
|
331
|
+
}
|
|
332
|
+
/** Starts conservatively invalidate refs because older supported natives replace the page, even on failure. Restarts invalidate only when they have a URL. */
|
|
320
333
|
export function isRecordPageTransitionCommand(tokens) {
|
|
321
334
|
if (tokens[0] !== "record")
|
|
322
335
|
return false;
|
|
323
336
|
if (tokens[1] === "start")
|
|
324
337
|
return true;
|
|
325
|
-
return tokens[1] === "restart" && tokens.
|
|
338
|
+
return tokens[1] === "restart" && getRecordCommandOperands(tokens).url !== undefined;
|
|
326
339
|
}
|
|
327
340
|
export function isWebMcpPageMutationCommand(tokens) {
|
|
328
341
|
return isWebMcpPageMutation(tokens[0], tokens[1]);
|
|
@@ -138,7 +138,7 @@ export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = l
|
|
|
138
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 })),
|
|
139
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 })),
|
|
140
140
|
sessionMode: Type.Optional(StringEnum(["auto", "fresh"], {
|
|
141
|
-
description: "auto reuses the managed session
|
|
141
|
+
description: "Native configured sessions win; otherwise auto reuses the managed session and fresh starts a new managed browser for launch-only flags.",
|
|
142
142
|
default: DEFAULT_SESSION_MODE,
|
|
143
143
|
})),
|
|
144
144
|
}, {
|
|
@@ -5,7 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { parseArgvDescriptor } from "../argv-descriptor.js";
|
|
7
7
|
import { getFlagName } from "../argv-grammar.js";
|
|
8
|
-
import { needsManagedSession } from "../command-policy.js";
|
|
8
|
+
import { isBrowserIndependentRead, needsManagedSession } from "../command-policy.js";
|
|
9
9
|
import { isCloseCommand } from "../command-taxonomy.js";
|
|
10
10
|
import { LAUNCH_SCOPED_FLAGS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS } from "../launch-scoped-flags.js";
|
|
11
11
|
import { isRecord } from "../parsing.js";
|
|
@@ -44,6 +44,7 @@ const SCRIPT_FORBIDDEN_FLAGS = new Set([
|
|
|
44
44
|
...MANAGED_RESTORE_INCOMPATIBLE_FLAGS.filter((flag) => flag !== SCRIPT_ALLOWED_LAUNCH_FLAG),
|
|
45
45
|
"--namespace",
|
|
46
46
|
"--session",
|
|
47
|
+
"--config",
|
|
47
48
|
]);
|
|
48
49
|
export function compileAgentBrowserScript(input) {
|
|
49
50
|
if (typeof input !== "string")
|
|
@@ -71,7 +72,7 @@ function getScriptCallPolicyError(args) {
|
|
|
71
72
|
return "script browser calls cannot close, quit, or exit their isolated session.";
|
|
72
73
|
if (SCRIPT_FORBIDDEN_COMMANDS.has(command))
|
|
73
74
|
return `script browser calls cannot use ${command}.`;
|
|
74
|
-
if (!needsManagedSession(descriptor))
|
|
75
|
+
if (!needsManagedSession(descriptor) && !isBrowserIndependentRead(descriptor.upstreamCommandTokens))
|
|
75
76
|
return `script browser calls cannot use sessionless/local command ${command}.`;
|
|
76
77
|
for (const token of args) {
|
|
77
78
|
const flag = getFlagName(token);
|
|
@@ -104,7 +104,7 @@ export function agentBrowserExplicitConfigIsPresent(parentEnv = getAgentBrowserP
|
|
|
104
104
|
return hasExplicitConfigArg(args) || hasUpstreamEnvValue(parentEnv, AGENT_BROWSER_CONFIG_ENV);
|
|
105
105
|
}
|
|
106
106
|
/** Caller-selected upstream config disables the wrapper's automatic restore injection without blocking that config. */
|
|
107
|
-
export function agentBrowserConfigBlocksManagedRestore(
|
|
107
|
+
export function agentBrowserConfigBlocksManagedRestore(parentEnv = getAgentBrowserProcessEnvironment(), args = [], platform = process.platform) {
|
|
108
108
|
return !resolveManagedSessionRestoreHome(parentEnv, platform) || agentBrowserExplicitConfigIsPresent(parentEnv, args);
|
|
109
109
|
}
|
|
110
110
|
function omitWrapperInjectedUserAgent(args, enabled) {
|
|
@@ -151,7 +151,7 @@ function isManagedSessionRestoreIncompatible(options, namespace = extractExplici
|
|
|
151
151
|
const args = omitWrapperInjectedUserAgent(options.args, options.wrapperInjectedUserAgent);
|
|
152
152
|
if (options.cwd && !hasManagedSessionRestoreProjectIdentity(options.cwd))
|
|
153
153
|
return true;
|
|
154
|
-
if (options.cwd && agentBrowserConfigBlocksManagedRestore(
|
|
154
|
+
if (options.cwd && agentBrowserConfigBlocksManagedRestore(effectiveEnv, args))
|
|
155
155
|
return true;
|
|
156
156
|
return !ensureManagedSessionRestoreStorageIsSecure(effectiveEnv, process.platform, namespace);
|
|
157
157
|
}
|
|
@@ -192,6 +192,12 @@ export function getOwnedManagedSessionCompatibilityEnv(options) {
|
|
|
192
192
|
const explicitRawInterval = Object.hasOwn(callEnv, "AGENT_BROWSER_AUTOSAVE_INTERVAL_MS")
|
|
193
193
|
? callEnv.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS
|
|
194
194
|
: parentEnv.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS;
|
|
195
|
+
if (ownedContext.reuseOnly) {
|
|
196
|
+
return explicitRawInterval === undefined && ownedContext.headedManagedAutosaveInterval !== undefined
|
|
197
|
+
&& !agentBrowserExplicitConfigIsPresent({ ...parentEnv, ...callEnv }, options.args)
|
|
198
|
+
? { AGENT_BROWSER_AUTOSAVE_INTERVAL_MS: ownedContext.headedManagedAutosaveInterval }
|
|
199
|
+
: {};
|
|
200
|
+
}
|
|
195
201
|
const explicitIntervalMatches = resolveExplicitAutosaveInterval(explicitRawInterval) === ownedContext.headedManagedAutosaveInterval;
|
|
196
202
|
return {
|
|
197
203
|
...(ownedContext.compatibilityUserAgent ? { AGENT_BROWSER_USER_AGENT: ownedContext.compatibilityUserAgent } : {}),
|
|
@@ -209,26 +215,33 @@ export function getManagedSessionRestoreProtectedEnv(options, restoreEnv) {
|
|
|
209
215
|
}
|
|
210
216
|
export function validateManagedSessionRestoreContextForSpawn(options) {
|
|
211
217
|
const { namespace, ownedContext, parentEnv } = resolveManagedSessionRestorePolicy(options);
|
|
212
|
-
if (closesBrowserSession(options.args) || ownedContext?.restoreDecision !== "enabled")
|
|
218
|
+
if (closesBrowserSession(options.args) || ownedContext?.restoreDecision !== "enabled" || (ownedContext.reuseOnly && ownedContext.restoreSuppressed))
|
|
213
219
|
return true;
|
|
214
220
|
const ownedCwd = ownedContext.cwd ?? options.cwd;
|
|
215
221
|
if (!ownedContext.restoreKey || !ownedContext.restoreScope || createManagedSessionRestoreKey(ownedCwd, ownedContext.restoreScope) !== ownedContext.restoreKey || !hasManagedSessionRestoreProjectIdentity(ownedCwd))
|
|
216
222
|
return false;
|
|
217
223
|
const effectiveEnv = { ...parentEnv, ...options.env };
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
if (!ownedContext.reuseOnly) {
|
|
225
|
+
if (isDisabledEnvFlag(effectiveEnv[MANAGED_SESSION_RESTORE_ENV]))
|
|
226
|
+
return false;
|
|
227
|
+
if (MANAGED_RESTORE_INCOMPATIBLE_ENVS.some((name) => !MANAGED_SESSION_RESTORE_SPAWN_PINNED_ENVS.has(name) && hasUpstreamEnvValue(effectiveEnv, name)))
|
|
228
|
+
return false;
|
|
229
|
+
if (MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS.some((name) => isUpstreamEnvFlagEnabled(effectiveEnv[name])))
|
|
230
|
+
return false;
|
|
231
|
+
if (options.env?.[AGENT_BROWSER_RESTORE_ENV] !== undefined && options.env[AGENT_BROWSER_RESTORE_ENV] !== ownedContext.restoreKey)
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
226
234
|
return ensureManagedSessionRestoreStorageIsSecure({ ...effectiveEnv, ...ownedContext.protectedStorageEnv }, process.platform, namespace);
|
|
227
235
|
}
|
|
228
236
|
export function getManagedSessionRestoreEnv(options) {
|
|
229
237
|
const { namespace, owned, ownedContext, parentEnv, restoreState, sessionName } = resolveManagedSessionRestorePolicy(options);
|
|
230
238
|
if (!owned || !restoreState || closesBrowserSession(options.args))
|
|
231
239
|
return {};
|
|
240
|
+
if (ownedContext?.reuseOnly) {
|
|
241
|
+
return ownedContext.restoreKey && !ownedContext.restoreSuppressed && validateManagedSessionRestoreContextForSpawn(options)
|
|
242
|
+
? { [AGENT_BROWSER_RESTORE_ENV]: ownedContext.restoreKey }
|
|
243
|
+
: {};
|
|
244
|
+
}
|
|
232
245
|
if (ownedContext?.restoreDecision) {
|
|
233
246
|
if (ownedContext.restoreDecision !== "enabled" || restoreState.isDisabled(sessionName, namespace) || !sessionName || !ownedContext.restoreKey || !validateManagedSessionRestoreContextForSpawn(options))
|
|
234
247
|
return {};
|
|
@@ -244,7 +257,7 @@ export function getManagedSessionRestoreEnv(options) {
|
|
|
244
257
|
/** Commit sticky suppression only after an owned-context subprocess has actually started. */
|
|
245
258
|
export function commitManagedSessionRestoreSuppression(options) {
|
|
246
259
|
const { namespace, owned, ownedContext, parentEnv, restoreState, sessionName } = resolveManagedSessionRestorePolicy(options);
|
|
247
|
-
if (!owned || !restoreState || closesBrowserSession(options.args))
|
|
260
|
+
if (!owned || !restoreState || ownedContext?.reuseOnly || closesBrowserSession(options.args))
|
|
248
261
|
return;
|
|
249
262
|
if (ownedContext?.restoreDecision) {
|
|
250
263
|
const alreadyDisabled = restoreState.isDisabled(sessionName, namespace);
|
|
@@ -272,6 +285,23 @@ export function buildOwnedManagedSessionRestoreContext(options) {
|
|
|
272
285
|
if (!owned)
|
|
273
286
|
return undefined;
|
|
274
287
|
const ownedCwd = owned.cwd ?? options.cwd;
|
|
288
|
+
if (options.reuseOnly) {
|
|
289
|
+
const env = { ...(options.parentEnv ?? getAgentBrowserProcessEnvironment()), ...options.env };
|
|
290
|
+
const enabled = !options.restoreState.isDisabled(owned.sessionName, owned.namespace);
|
|
291
|
+
const restoreScope = getManagedSessionRestoreScope(owned.sessionName);
|
|
292
|
+
const knownKey = options.restoreState.getDaemonRestoreKey(owned.sessionName, owned.namespace);
|
|
293
|
+
const restoreKey = knownKey === undefined && enabled && hasManagedSessionRestoreProjectIdentity(ownedCwd)
|
|
294
|
+
? createManagedSessionRestoreKey(ownedCwd, restoreScope)
|
|
295
|
+
: knownKey;
|
|
296
|
+
return { ...owned, reuseOnly: true, restoreKey: restoreKey ?? undefined, restoreScope,
|
|
297
|
+
protectedStorageEnv: enabled ? getManagedSessionRestoreProtectedStorageEnv(true, env) : undefined,
|
|
298
|
+
restoreDecision: enabled ? "enabled" : undefined,
|
|
299
|
+
restoreSuppressed: managedSessionRestoreOptedOut(options) || agentBrowserExplicitConfigIsPresent(env, options.args)
|
|
300
|
+
|| ["--restore", "--session-name"].some(flag => hasLaunchScopedFlagToken(options.args, flag))
|
|
301
|
+
|| env.AGENT_BROWSER_RESTORE !== undefined || env.AGENT_BROWSER_SESSION_NAME !== undefined,
|
|
302
|
+
headedManagedAutosaveDisabled: options.headedManagedAutosaveDisabled,
|
|
303
|
+
headedManagedAutosaveInterval: options.headedManagedAutosaveInterval };
|
|
304
|
+
}
|
|
275
305
|
const policyOptions = {
|
|
276
306
|
args: options.args,
|
|
277
307
|
cwd: ownedCwd,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { chmodSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { directoryContainsSymlink, ensureManagedSessionRestoreStorageIsSecure, ensureOwnerOnlyDirectory, getManagedRestoreSessionsDirectory, isManagedSessionRestoreKey, resolveManagedSessionRestoreCheckoutRoot, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
|
|
5
5
|
const OWNED_RESTORE_SNAPSHOT_FAMILIES_TO_KEEP = 2;
|
|
6
6
|
const OWNED_RESTORE_SNAPSHOT_MAX_RECORDS = 256;
|
|
7
7
|
const OWNED_RESTORE_SNAPSHOT_RECORD_MAX_BYTES = 16 * 1_024;
|
|
@@ -286,10 +286,8 @@ function scanOwnedSnapshots(options) {
|
|
|
286
286
|
export function pruneOwnedManagedSessionRestoreSnapshots(options) {
|
|
287
287
|
const parentEnv = options.parentEnv ?? process.env;
|
|
288
288
|
const platform = options.platform ?? process.platform;
|
|
289
|
-
const restoreKey = options.restoreKey
|
|
290
|
-
|
|
291
|
-
: isManagedSessionRestoreKey(options.restoreKey) ? options.restoreKey : undefined;
|
|
292
|
-
if (!restoreKey)
|
|
289
|
+
const restoreKey = options.restoreKey;
|
|
290
|
+
if (!isManagedSessionRestoreKey(restoreKey))
|
|
293
291
|
return 0;
|
|
294
292
|
const home = resolveManagedSessionRestoreHome(parentEnv, platform);
|
|
295
293
|
if (!home)
|
|
@@ -2,8 +2,7 @@ import { lstatSync, readlinkSync, realpathSync, statSync } from "node:fs";
|
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { foldAgentBrowserFilesystemIdentity } from "../../argv-grammar.js";
|
|
4
4
|
import { parseWaitCommandTokens } from "../../argv-descriptor.js";
|
|
5
|
-
|
|
6
|
-
const SCREENSHOT_VALUE_FLAGS = new Set(["--screenshot-dir", "--screenshot-format", "--screenshot-quality"]);
|
|
5
|
+
import { getRecordCommandOperands } from "../../command-taxonomy.js";
|
|
7
6
|
const SCREENSHOT_IMAGE_EXTENSIONS = [".jpeg", ".jpg", ".png", ".webp"];
|
|
8
7
|
function isSingleScreenshotPathToken(token) {
|
|
9
8
|
const explicitlyRelative = token.startsWith("./") || token.startsWith("../");
|
|
@@ -17,11 +16,7 @@ function getScreenshotPositionalIndices(commandTokens) {
|
|
|
17
16
|
const positionalIndices = [];
|
|
18
17
|
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
19
18
|
const token = commandTokens[index];
|
|
20
|
-
if (
|
|
21
|
-
index += 1;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (SCREENSHOT_BOOLEAN_FLAGS.has(token))
|
|
19
|
+
if (token === "--full" || token === "-f")
|
|
25
20
|
continue;
|
|
26
21
|
positionalIndices.push(index);
|
|
27
22
|
}
|
|
@@ -54,9 +49,6 @@ function getDiffScreenshotOutputPath(commandTokens) {
|
|
|
54
49
|
}
|
|
55
50
|
return outputPath;
|
|
56
51
|
}
|
|
57
|
-
function foldArtifactPath(path, platform) {
|
|
58
|
-
return foldAgentBrowserFilesystemIdentity(path, platform);
|
|
59
|
-
}
|
|
60
52
|
function canonicalizeArtifactPath(absolutePath, platform, seenSymlinks) {
|
|
61
53
|
let cursor = absolutePath;
|
|
62
54
|
const suffix = [];
|
|
@@ -71,7 +63,7 @@ function canonicalizeArtifactPath(absolutePath, platform, seenSymlinks) {
|
|
|
71
63
|
catch {
|
|
72
64
|
// The destination does not exist yet; canonical ancestry still catches aliases.
|
|
73
65
|
}
|
|
74
|
-
return
|
|
66
|
+
return foldAgentBrowserFilesystemIdentity(canonicalPath, platform);
|
|
75
67
|
}
|
|
76
68
|
catch {
|
|
77
69
|
let symlinkTarget;
|
|
@@ -90,7 +82,7 @@ function canonicalizeArtifactPath(absolutePath, platform, seenSymlinks) {
|
|
|
90
82
|
}
|
|
91
83
|
const parent = dirname(cursor);
|
|
92
84
|
if (parent === cursor)
|
|
93
|
-
return
|
|
85
|
+
return foldAgentBrowserFilesystemIdentity(absolutePath, platform);
|
|
94
86
|
suffix.unshift(basename(cursor));
|
|
95
87
|
cursor = parent;
|
|
96
88
|
}
|
|
@@ -120,7 +112,7 @@ export function getExplicitArtifactDestination(commandTokens) {
|
|
|
120
112
|
return commandTokens[3];
|
|
121
113
|
if ((command === "trace" || command === "profiler") && subcommand === "stop")
|
|
122
114
|
return commandTokens[2];
|
|
123
|
-
if (command === "record"
|
|
124
|
-
return commandTokens
|
|
115
|
+
if (command === "record")
|
|
116
|
+
return getRecordCommandOperands(commandTokens).path;
|
|
125
117
|
return undefined;
|
|
126
118
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { isCloseCommand, isOpenNavigationCommand } from "../../command-taxonomy.js";
|
|
4
|
+
import { isBrowserIndependentRead } from "../../command-policy.js";
|
|
4
5
|
import { boundElectronProbeString } from "../../electron/cdp.js";
|
|
5
6
|
import { executableExistsOnPath } from "../../executable-path.js";
|
|
6
7
|
import { formatSessionArtifactRetentionSummary } from "../../results/artifact-manifest.js";
|
|
7
8
|
import { buildInspectOverlayStateAction, buildNextToolAction, withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
8
9
|
import { buildVisibleRefFallbackDiagnosticFromSnapshot, getVisibleRefFallbackTarget } from "../../results/selector-recovery.js";
|
|
9
10
|
import { extractRefSnapshotFromData, isAboutBlankUrl, normalizeComparableUrl } from "../../session-page-state.js";
|
|
10
|
-
import {
|
|
11
|
+
import { redactInvocationArgs, redactSensitiveText } from "../../runtime.js";
|
|
11
12
|
import { isRecord } from "../../parsing.js";
|
|
12
13
|
import { extractBatchResultCommand, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, runSessionCommandData, } from "./session-state.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
14
|
+
import { getUpstreamEffectiveBatchSteps } from "../batch-stdin.js";
|
|
15
|
+
import { getExplicitArtifactDestination } from "./artifact-paths.js";
|
|
15
16
|
const ELECTRON_FILL_VERIFICATION_TIMEOUT_MS = 2_000;
|
|
16
17
|
export function sleepMs(ms) {
|
|
17
18
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -227,7 +228,7 @@ export async function collectRecordingDependencyWarning(options) {
|
|
|
227
228
|
return undefined;
|
|
228
229
|
if (await executableExistsOnPath("ffmpeg"))
|
|
229
230
|
return undefined;
|
|
230
|
-
return { command: recordCommand, dependency: "ffmpeg", message: `${recordCommand}
|
|
231
|
+
return { command: recordCommand, dependency: "ffmpeg", message: `${recordCommand} reported a pending recording, but ffmpeg is not on PATH. Its output is unverified; install ffmpeg before starting a new recording.`, reason: "ffmpeg-missing-for-recording", recommendations: ["Install ffmpeg before recording; on macOS with Homebrew, brew install ffmpeg or brew install ffmpeg-full.", "Stop this recording, check the result, and start a new recording after ensuring Pi can find ffmpeg on PATH."] };
|
|
231
232
|
}
|
|
232
233
|
export function formatRecordingDependencyWarningText(warning) {
|
|
233
234
|
if (!warning)
|
|
@@ -711,12 +712,10 @@ export async function collectElectronHandoff(options) {
|
|
|
711
712
|
}
|
|
712
713
|
return { handoff: "snapshot", refSnapshot, snapshot, ...(snapshotRetryCount > 0 ? { snapshotRetryCount } : {}), tabs };
|
|
713
714
|
}
|
|
714
|
-
function getTimeoutProgressSteps(compiledJob,
|
|
715
|
+
function getTimeoutProgressSteps(compiledJob, commandTokens, stdin) {
|
|
715
716
|
if (compiledJob)
|
|
716
717
|
return compiledJob.steps.map((step, index) => ({ args: step.args, generatedFrom: step.generatedFrom, index: index + 1 }));
|
|
717
|
-
|
|
718
|
-
return [];
|
|
719
|
-
return parseValidBatchStepEntries(stdin).map(({ index, step }) => ({ args: step, index: index + 1 }));
|
|
718
|
+
return getUpstreamEffectiveBatchSteps(commandTokens, stdin).map((args, index) => ({ args, index: index + 1 }));
|
|
720
719
|
}
|
|
721
720
|
function getLastPositionalToken(args, startIndex = 1) {
|
|
722
721
|
for (let index = args.length - 1; index >= startIndex; index -= 1) {
|
|
@@ -726,20 +725,8 @@ function getLastPositionalToken(args, startIndex = 1) {
|
|
|
726
725
|
}
|
|
727
726
|
return undefined;
|
|
728
727
|
}
|
|
729
|
-
function getTimeoutStepArtifactPath(
|
|
730
|
-
|
|
731
|
-
const [command] = commandArgs;
|
|
732
|
-
if (command === "screenshot") {
|
|
733
|
-
const index = getScreenshotPathTokenIndex(commandArgs);
|
|
734
|
-
return index === undefined ? undefined : commandArgs[index];
|
|
735
|
-
}
|
|
736
|
-
if (command === "pdf")
|
|
737
|
-
return getLastPositionalToken(commandArgs);
|
|
738
|
-
if (command === "download")
|
|
739
|
-
return getLastPositionalToken(commandArgs, 2);
|
|
740
|
-
if (command === "wait")
|
|
741
|
-
return parseWaitCommandTokens(commandArgs).downloadPath;
|
|
742
|
-
return undefined;
|
|
728
|
+
function getTimeoutStepArtifactPath(commandTokens) {
|
|
729
|
+
return ["screenshot", "pdf", "download", "wait"].includes(commandTokens[0]) ? getExplicitArtifactDestination(commandTokens) : undefined;
|
|
743
730
|
}
|
|
744
731
|
async function statTimeoutArtifactPath(absolutePath) {
|
|
745
732
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -795,7 +782,7 @@ const TIMEOUT_RETRYABLE_COMMANDS = new Set([
|
|
|
795
782
|
]);
|
|
796
783
|
function getTimeoutStepRetry(step) {
|
|
797
784
|
const command = step.args[0];
|
|
798
|
-
return command && TIMEOUT_RETRYABLE_COMMANDS.has(command) ? { args: step.args } : undefined;
|
|
785
|
+
return command && TIMEOUT_RETRYABLE_COMMANDS.has(command) ? { args: ["batch"], stdin: JSON.stringify([step.args]) } : undefined;
|
|
799
786
|
}
|
|
800
787
|
function normalizeUrlForTimeoutComparison(url) {
|
|
801
788
|
if (!url)
|
|
@@ -873,7 +860,9 @@ function buildTimeoutProgressSteps(options) {
|
|
|
873
860
|
};
|
|
874
861
|
}
|
|
875
862
|
export async function collectTimeoutPartialProgress(options) {
|
|
876
|
-
|
|
863
|
+
if ((options.commandTokens[0] === "session" && options.commandTokens[1] === "info") || isBrowserIndependentRead(options.commandTokens, options.stdin))
|
|
864
|
+
return undefined;
|
|
865
|
+
const rawSteps = getTimeoutProgressSteps(options.compiledJob, options.commandTokens, options.stdin);
|
|
877
866
|
const artifacts = await collectTimeoutArtifactEvidence(options.cwd, rawSteps);
|
|
878
867
|
const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName });
|
|
879
868
|
const recoveredUrl = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
|
|
@@ -930,7 +919,7 @@ export function formatTimeoutPartialProgressText(progress, pageTargetUnknown = f
|
|
|
930
919
|
lines.push(`- ... ${progress.steps.length - shownSteps.length} more step${progress.steps.length - shownSteps.length === 1 ? "" : "s"} omitted`);
|
|
931
920
|
}
|
|
932
921
|
if (progress.retryStep?.retry?.args) {
|
|
933
|
-
const payload = JSON.stringify({
|
|
922
|
+
const payload = JSON.stringify({ ...progress.retryStep.retry, stdin: JSON.stringify([redactInvocationArgs(progress.retryStep.args)]) });
|
|
934
923
|
lines.push(pageTargetUnknown
|
|
935
924
|
? `Retry candidate for step ${progress.retryStep.index}: ${payload}. Verify the current URL before running it.`
|
|
936
925
|
: `Retry failed step: ${payload}`);
|
|
@@ -86,7 +86,7 @@ export function redactRecoveryHint(recoveryHint) {
|
|
|
86
86
|
}
|
|
87
87
|
export function buildJsonVisibleContent(options) {
|
|
88
88
|
const { error, presentation, succeeded, warnings } = options;
|
|
89
|
-
const payload = redactSensitiveValue({ artifacts: presentation.artifacts, data: presentation.data, error, success: succeeded, warnings: warnings && warnings.length > 0 ? warnings : undefined });
|
|
89
|
+
const payload = redactSensitiveValue({ artifacts: presentation.artifacts, data: presentation.data, error, recordingRecovery: presentation.recordingRecovery, readConfirmation: presentation.readConfirmation, success: succeeded, warnings: warnings && warnings.length > 0 ? warnings : undefined });
|
|
90
90
|
if (isRecord(payload) && isRecord(payload.data) && isRecord(presentation.data) && typeof presentation.data.wsUrl === "string")
|
|
91
91
|
payload.data.wsUrl = presentation.data.wsUrl;
|
|
92
92
|
const images = presentation.content.filter((item) => item.type === "image");
|
|
@@ -205,7 +205,9 @@ export async function prepareFinalResultRecoveryState(options) {
|
|
|
205
205
|
return { categoryDetails, currentRefSnapshot, currentRefSnapshotInvalidation, noActivePageSnapshotFailure, richInputRecoveryDiagnostic, visibleRefFallbackDiagnostic, visibleRefFallbackSessionName };
|
|
206
206
|
}
|
|
207
207
|
function buildTimeoutPartialProgressNextActions(options) {
|
|
208
|
-
|
|
208
|
+
if (options.executionPlan.commandInfo.command === "session" && options.executionPlan.commandInfo.subcommand === "info")
|
|
209
|
+
return [];
|
|
210
|
+
const retry = options.timeoutPartialProgress?.retryStep?.retry;
|
|
209
211
|
const stepIndex = options.timeoutPartialProgress?.retryStep?.index;
|
|
210
212
|
const freshSessionAbandoned = options.sessionMode === "fresh" && options.timeoutPartialProgress?.liveUrlRecovered !== true;
|
|
211
213
|
if (options.currentSessionTabTargetUnknown && !freshSessionAbandoned && options.executionPlan.sessionName) {
|
|
@@ -220,12 +222,12 @@ function buildTimeoutPartialProgressNextActions(options) {
|
|
|
220
222
|
tool: "agent_browser",
|
|
221
223
|
}];
|
|
222
224
|
}
|
|
223
|
-
if (
|
|
225
|
+
if (retry) {
|
|
224
226
|
return [{
|
|
225
227
|
id: "retry-timeout-step",
|
|
226
228
|
params: freshSessionAbandoned
|
|
227
|
-
? {
|
|
228
|
-
: { args: withOptionalSessionArgs(options.executionPlan.sessionName,
|
|
229
|
+
? { ...retry, sessionMode: "fresh" }
|
|
230
|
+
: { ...retry, args: withOptionalSessionArgs(options.executionPlan.sessionName, retry.args) },
|
|
229
231
|
reason: freshSessionAbandoned
|
|
230
232
|
? `Retry the first incomplete timed-out step${stepIndex === undefined ? "" : ` ${stepIndex}`} in a fresh browser session because the timed-out fresh session was not proven live.`
|
|
231
233
|
: `Retry the first incomplete timed-out step${stepIndex === undefined ? "" : ` ${stepIndex}`} against the current browser session.`,
|
|
@@ -271,6 +273,8 @@ function buildDialogTimeoutNextActions(options) {
|
|
|
271
273
|
];
|
|
272
274
|
}
|
|
273
275
|
function buildResultNextActions(options) {
|
|
276
|
+
if (options.presentation.recordingRecovery || options.presentation.readConfirmation)
|
|
277
|
+
return options.presentation.nextActions;
|
|
274
278
|
let nextActions = options.presentation.nextActions ? [...options.presentation.nextActions] : [];
|
|
275
279
|
const append = (actions) => {
|
|
276
280
|
if (actions && actions.length > 0)
|
|
@@ -371,7 +375,7 @@ function formatReadExecutionText(options, lifecycle) {
|
|
|
371
375
|
const source = getReadSource(options);
|
|
372
376
|
if (!source)
|
|
373
377
|
return undefined;
|
|
374
|
-
return `Read execution: source ${source}; CLI started: ${options.processResult.agentBrowserStarted ? "yes" : "no"};
|
|
378
|
+
return `Read execution: source ${source}; CLI started: ${options.processResult.agentBrowserStarted ? "yes" : "no"}; reported browserLaunched: ${lifecycle ? String(lifecycle.effectiveLaunch.browserLaunched) : "unknown"}; managed session outcome: ${options.managedSessionOutcome?.status ?? "not managed"}. An HTTP read does not establish shared-browser liveness; use session info for that.`;
|
|
375
379
|
}
|
|
376
380
|
function buildBrowserWindowStatus(options, lifecycle) {
|
|
377
381
|
if (!options.headedLaunch || options.preserveAttachedBrowserSession || options.providerLaunch || !options.succeeded || lifecycle?.effectiveLaunch.browserLaunched !== true || !options.executionPlan.managedSessionName || !options.managedSessionOutcome || !["created", "replaced"].includes(options.managedSessionOutcome.status))
|
|
@@ -417,6 +421,8 @@ function buildAgentBrowserResultDetails(options, nextActions) {
|
|
|
417
421
|
browserWindow,
|
|
418
422
|
lifecycle,
|
|
419
423
|
readSource: getReadSource(options),
|
|
424
|
+
recordingRecovery: options.presentation.recordingRecovery,
|
|
425
|
+
readConfirmation: options.presentation.readConfirmation,
|
|
420
426
|
aboutBlankSessionMismatch: options.aboutBlankSessionMismatch,
|
|
421
427
|
electronPostCommandHealth: options.electronPostCommandHealth,
|
|
422
428
|
electronRefFreshness: options.electronRefFreshnessDiagnostic,
|
|
@@ -31,6 +31,7 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
31
31
|
const artifactRunStartedAtMs = Date.now();
|
|
32
32
|
const processResult = await runAgentBrowserProcess({
|
|
33
33
|
args: prepared.processArgs,
|
|
34
|
+
browserIndependentReadConfirmation: prepared.readConfirmation !== undefined,
|
|
34
35
|
cwd: options.cwd,
|
|
35
36
|
env: ownedManagedSession
|
|
36
37
|
? { AGENT_BROWSER_IDLE_TIMEOUT_MS: options.implicitSessionIdleTimeoutMs }
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS } from "../../../argv-grammar.js";
|
|
2
2
|
import { isOpenNavigationCommand } from "../../../command-taxonomy.js";
|
|
3
|
+
import { getExplicitReadUrl, isBrowserIndependentRead } from "../../../command-policy.js";
|
|
3
4
|
import { getAgentBrowserProcessTimeoutMs } from "../../../process.js";
|
|
4
5
|
import { getUpstreamEffectiveBatchSteps } from "../../batch-stdin.js";
|
|
5
6
|
const POSITIONAL_VALUE_FLAGS = new Set([...VALUE_FLAGS, "--llms"]);
|
|
@@ -44,10 +45,10 @@ export function findFirstPositionalArgument(commandTokens) {
|
|
|
44
45
|
return undefined;
|
|
45
46
|
}
|
|
46
47
|
function readUsesActivePageUrl(commandTokens) {
|
|
47
|
-
return
|
|
48
|
+
return !isBrowserIndependentRead(commandTokens) && commandTokens.some((token) => token === "--require-md" || token === "--llms" || token.startsWith("--llms="));
|
|
48
49
|
}
|
|
49
50
|
function readRequestBudget(commandTokens, activePageUrl) {
|
|
50
|
-
const target =
|
|
51
|
+
const target = getExplicitReadUrl(commandTokens) ?? activePageUrl;
|
|
51
52
|
if (target === undefined)
|
|
52
53
|
return 1;
|
|
53
54
|
let url;
|