pi-agent-browser-native 0.6.10 → 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 +32 -0
- package/README.md +49 -6
- package/dist/extensions/agent-browser/index.js +416 -430
- package/dist/extensions/agent-browser/lib/argv-grammar.js +1 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +41 -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 +40 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +3 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +8 -2
- 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 +25 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +56 -10
- 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 +3 -3
- 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 +10 -9
- 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 +60 -29
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +19 -8
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -20
- 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 +23 -7
- package/dist/extensions/agent-browser/lib/temp.js +4 -0
- package/docs/ARCHITECTURE.md +26 -10
- package/docs/COMMAND_REFERENCE.md +35 -15
- package/docs/SUPPORT_MATRIX.md +7 -3
- package/docs/TOOL_CONTRACT.md +72 -20
- package/package.json +1 -1
- package/scripts/prepare.mjs +2 -4
|
@@ -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",
|
|
@@ -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
|
}
|
|
@@ -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);
|
|
@@ -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,6 +1,7 @@
|
|
|
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";
|
|
@@ -859,6 +860,8 @@ function buildTimeoutProgressSteps(options) {
|
|
|
859
860
|
};
|
|
860
861
|
}
|
|
861
862
|
export async function collectTimeoutPartialProgress(options) {
|
|
863
|
+
if ((options.commandTokens[0] === "session" && options.commandTokens[1] === "info") || isBrowserIndependentRead(options.commandTokens, options.stdin))
|
|
864
|
+
return undefined;
|
|
862
865
|
const rawSteps = getTimeoutProgressSteps(options.compiledJob, options.commandTokens, options.stdin);
|
|
863
866
|
const artifacts = await collectTimeoutArtifactEvidence(options.cwd, rawSteps);
|
|
864
867
|
const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName });
|
|
@@ -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,6 +205,8 @@ export async function prepareFinalResultRecoveryState(options) {
|
|
|
205
205
|
return { categoryDetails, currentRefSnapshot, currentRefSnapshotInvalidation, noActivePageSnapshotFailure, richInputRecoveryDiagnostic, visibleRefFallbackDiagnostic, visibleRefFallbackSessionName };
|
|
206
206
|
}
|
|
207
207
|
function buildTimeoutPartialProgressNextActions(options) {
|
|
208
|
+
if (options.executionPlan.commandInfo.command === "session" && options.executionPlan.commandInfo.subcommand === "info")
|
|
209
|
+
return [];
|
|
208
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;
|
|
@@ -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;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { copyFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { getBooleanFlagValue, isUpstreamEnvFlagEnabled, projectUpstreamGlobalFlags } from "../../argv-grammar.js";
|
|
3
|
+
import { extractExplicitSessionName, getBooleanFlagValue, isUpstreamEnvFlagEnabled, projectUpstreamGlobalFlags, resolveAgentBrowserNamespace } from "../../argv-grammar.js";
|
|
4
4
|
import { isCloseCommand } from "../../command-taxonomy.js";
|
|
5
|
+
import { isBrowserIndependentRead } from "../../command-policy.js";
|
|
5
6
|
import { cleanupElectronLaunchResources } from "../../electron/cleanup.js";
|
|
6
7
|
import { launchElectronApp } from "../../electron/launch.js";
|
|
7
8
|
import { pathExists } from "../../fs-utils.js";
|
|
@@ -421,6 +422,8 @@ export async function prepareBrowserRun(options) {
|
|
|
421
422
|
} };
|
|
422
423
|
}
|
|
423
424
|
const userRequestedJson = runtimeToolArgs.includes("--json");
|
|
425
|
+
const routedReadConfirmation = state.sessionPageState.findReadConfirmation(preparedArgs.args, resolveAgentBrowserNamespace(preparedArgs.args, agentBrowserProcessEnv.AGENT_BROWSER_NAMESPACE));
|
|
426
|
+
const readConfirmation = routedReadConfirmation?.capabilities?.readRequiresConfirmation === true ? routedReadConfirmation : undefined;
|
|
424
427
|
let executionPlan = buildExecutionPlan(preparedArgs.args, {
|
|
425
428
|
freshSessionName,
|
|
426
429
|
managedSessionActive: state.managedSessionActive,
|
|
@@ -428,13 +431,14 @@ export async function prepareBrowserRun(options) {
|
|
|
428
431
|
managedSessionName: state.managedSessionName,
|
|
429
432
|
managedSessionNamespace: state.managedSessionNamespace,
|
|
430
433
|
sessionMode,
|
|
434
|
+
stdin: runtimeToolStdin,
|
|
435
|
+
browserIndependentReadConfirmation: readConfirmation !== undefined,
|
|
431
436
|
});
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: idleTimeoutMismatch };
|
|
437
|
+
const browserIndependent = readConfirmation !== undefined || isBrowserIndependentRead(extractUpstreamCommandTokens(preparedArgs.args), runtimeToolStdin)
|
|
438
|
+
|| (executionPlan.commandInfo.command === "session" && executionPlan.commandInfo.subcommand === "info");
|
|
435
439
|
const ownedSessionKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
|
|
436
440
|
const plannedSessionPageState = sessionPageState.get(ownedSessionKey);
|
|
437
|
-
const pageTargetError = getPageTargetValidationError({
|
|
441
|
+
const pageTargetError = readConfirmation ? undefined : getPageTargetValidationError({
|
|
438
442
|
args: executionPlan.effectiveArgs,
|
|
439
443
|
currentPageUrl: plannedSessionPageState.tabTarget?.url,
|
|
440
444
|
pageUrlUnknown: plannedSessionPageState.tabTargetUnknown === true,
|
|
@@ -446,13 +450,18 @@ export async function prepareBrowserRun(options) {
|
|
|
446
450
|
const targetsCurrentManagedSession = state.managedSessionActive
|
|
447
451
|
&& ownedSessionKey === getSessionContextKey(state.managedSessionName, state.managedSessionNamespace);
|
|
448
452
|
const targetsOffCurrentOwnedSession = recordedOwnedSession !== undefined && !targetsCurrentManagedSession;
|
|
453
|
+
const idleTimeoutMismatch = !browserIndependent && (executionPlan.managedSessionName || recordedOwnedSession || targetsCurrentManagedSession || (state.managedSessionActive && extractExplicitSessionName(preparedArgs.args) === undefined))
|
|
454
|
+
? getIdleTimeoutMismatch(preparedArgs.args, options.implicitSessionIdleTimeoutMs)
|
|
455
|
+
: undefined;
|
|
456
|
+
if (idleTimeoutMismatch)
|
|
457
|
+
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: idleTimeoutMismatch };
|
|
449
458
|
const offCurrentLaunchScopedFlags = targetsOffCurrentOwnedSession
|
|
450
459
|
? executionPlan.startupScopedFlags.filter((flag) => flag !== "--namespace")
|
|
451
460
|
: [];
|
|
452
461
|
const offCurrentCompatibilityUpgrade = targetsOffCurrentOwnedSession
|
|
453
462
|
&& executionPlan.compatibilityWorkaround !== undefined
|
|
454
463
|
&& recordedOwnedSession.compatibilityWorkaround === undefined;
|
|
455
|
-
if (targetsOffCurrentOwnedSession && canUseHeadlessCompatibilityUserAgent(preparedArgs.args, agentBrowserProcessEnv)) {
|
|
464
|
+
if (!browserIndependent && targetsOffCurrentOwnedSession && canUseHeadlessCompatibilityUserAgent(preparedArgs.args, agentBrowserProcessEnv)) {
|
|
456
465
|
const compatibilityWorkaround = executionPlan.compatibilityWorkaround ?? recordedOwnedSession.compatibilityWorkaround;
|
|
457
466
|
if (compatibilityWorkaround) {
|
|
458
467
|
const userAgentIndex = executionPlan.effectiveArgs.indexOf("--user-agent");
|
|
@@ -471,7 +480,7 @@ export async function prepareBrowserRun(options) {
|
|
|
471
480
|
?? (targetsCurrentManagedSession ? state.managedSessionHeadedAutosaveInterval : undefined);
|
|
472
481
|
const explicitAutosaveInterval = resolveExplicitAutosaveInterval(agentBrowserProcessEnv.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS);
|
|
473
482
|
const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(retainedHeadedAutosaveInterval, isCloseCommand(executionPlan.commandInfo.command));
|
|
474
|
-
if (!executionPlan.validationError && autosavePolicyChangeError) {
|
|
483
|
+
if (!browserIndependent && !executionPlan.validationError && autosavePolicyChangeError) {
|
|
475
484
|
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: autosavePolicyChangeError };
|
|
476
485
|
}
|
|
477
486
|
const headedLaunch = getBooleanFlagValue(executionPlan.effectiveArgs, "--headed") ?? isUpstreamEnvFlagEnabled(agentBrowserProcessEnv.AGENT_BROWSER_HEADED);
|
|
@@ -481,13 +490,14 @@ export async function prepareBrowserRun(options) {
|
|
|
481
490
|
const compatibilityUserAgent = executionPlan.compatibilityWorkaround ? getDefaultHeadlessCompatUserAgent() : undefined;
|
|
482
491
|
const compatibilityUserAgentApplied = compatibilityUserAgent !== undefined
|
|
483
492
|
&& executionPlan.effectiveArgs.some((token, index) => token === "--user-agent" && executionPlan.effectiveArgs[index + 1] === compatibilityUserAgent);
|
|
484
|
-
const ownedManagedSession = buildOwnedManagedSessionRestoreContext({
|
|
493
|
+
const ownedManagedSession = browserIndependent && !recordedOwnedSession && !targetsCurrentManagedSession ? undefined : buildOwnedManagedSessionRestoreContext({
|
|
485
494
|
args: executionPlan.effectiveArgs,
|
|
495
|
+
reuseOnly: browserIndependent,
|
|
486
496
|
cwd: recordedOwnedSession?.cwd ?? cwd,
|
|
487
497
|
currentManagedSessionName: state.managedSessionName,
|
|
488
498
|
currentManagedSessionNamespace: state.managedSessionNamespace,
|
|
489
|
-
headedManagedAutosaveDisabled,
|
|
490
|
-
headedManagedAutosaveInterval,
|
|
499
|
+
headedManagedAutosaveDisabled: browserIndependent ? retainedHeadedAutosaveDisabled : headedManagedAutosaveDisabled,
|
|
500
|
+
headedManagedAutosaveInterval: browserIndependent ? retainedHeadedAutosaveInterval : headedManagedAutosaveInterval,
|
|
491
501
|
managedSessionName: executionPlan.managedSessionName,
|
|
492
502
|
namespace: executionPlan.namespace,
|
|
493
503
|
parentEnv: agentBrowserProcessEnv,
|
|
@@ -500,7 +510,7 @@ export async function prepareBrowserRun(options) {
|
|
|
500
510
|
});
|
|
501
511
|
let managedSessionDaemonInactive = false;
|
|
502
512
|
let managedSessionCleanupOnlyReason;
|
|
503
|
-
if (!executionPlan.validationError && ownedManagedSession) {
|
|
513
|
+
if (!browserIndependent && !executionPlan.validationError && ownedManagedSession) {
|
|
504
514
|
const closeCommand = isCloseCommand(executionPlan.commandInfo.command);
|
|
505
515
|
const policy = await acquireOwnedManagedSessionDaemonPolicy({
|
|
506
516
|
context: ownedManagedSession,
|
|
@@ -550,7 +560,7 @@ export async function prepareBrowserRun(options) {
|
|
|
550
560
|
const sessionTabPinningReason = priorSessionPageState.pinningReason;
|
|
551
561
|
let priorRefSnapshotState = priorSessionPageState.refSnapshot;
|
|
552
562
|
let priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
|
|
553
|
-
const coldManagedSession = (managedSessionDaemonInactive || priorSessionPageState.tabReopenPending === true)
|
|
563
|
+
const coldManagedSession = !browserIndependent && (managedSessionDaemonInactive || priorSessionPageState.tabReopenPending === true)
|
|
554
564
|
&& recordedOwnedSession !== undefined
|
|
555
565
|
&& sessionTabPinningReason === "restore"
|
|
556
566
|
&& ownedManagedSession?.restoreDecision === "enabled"
|
|
@@ -573,7 +583,7 @@ export async function prepareBrowserRun(options) {
|
|
|
573
583
|
command: executionPlan.commandInfo.command,
|
|
574
584
|
commandTokens: plannedCommandTokens,
|
|
575
585
|
// URL QA clears diagnostics before its explicit open; those clears do not need the old tab.
|
|
576
|
-
pinningRequired: sessionTabPinningReason !== undefined && compiledQaPreset?.checks.url === undefined,
|
|
586
|
+
pinningRequired: !readConfirmation && sessionTabPinningReason !== undefined && compiledQaPreset?.checks.url === undefined,
|
|
577
587
|
reopenPending: coldManagedSession,
|
|
578
588
|
sessionName: executionPlan.sessionName,
|
|
579
589
|
stdin: runtimeToolStdin,
|
|
@@ -613,7 +623,7 @@ export async function prepareBrowserRun(options) {
|
|
|
613
623
|
const isCallerOwnedExplicitSession = () => executionPlan.sessionName !== undefined
|
|
614
624
|
&& executionPlan.usedImplicitSession === false
|
|
615
625
|
&& ownedManagedSession === undefined;
|
|
616
|
-
const requiresLivePageVerification = () => isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true;
|
|
626
|
+
const requiresLivePageVerification = () => !readConfirmation && (isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true);
|
|
617
627
|
const verifyLivePage = async (request) => {
|
|
618
628
|
if (!request.requirement || !executionPlan.sessionName)
|
|
619
629
|
return;
|
|
@@ -1050,6 +1060,7 @@ export async function prepareBrowserRun(options) {
|
|
|
1050
1060
|
executionPlan,
|
|
1051
1061
|
ownedManagedSessionContext: ownedManagedSession,
|
|
1052
1062
|
preparedArgs,
|
|
1063
|
+
readConfirmation,
|
|
1053
1064
|
priorRefSnapshotState,
|
|
1054
1065
|
priorSessionTabTarget,
|
|
1055
1066
|
priorSessionTabTargetUnknown,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
2
|
import { parseArgvDescriptor } from "../../argv-descriptor.js";
|
|
3
|
-
import { needsManagedSession } from "../../command-policy.js";
|
|
3
|
+
import { isBrowserIndependentRead, needsManagedSession } from "../../command-policy.js";
|
|
4
4
|
import { deleteIdentityKeysInNamespace, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "../../argv-grammar.js";
|
|
5
5
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "../../batch-lifecycle.js";
|
|
6
6
|
import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWindowOrDiffPageTransitionCommand } from "../../command-taxonomy.js";
|
|
@@ -13,11 +13,13 @@ import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../../re
|
|
|
13
13
|
import { buildToolPresentation } from "../../results/presentation.js";
|
|
14
14
|
import { compactLargePresentationOutput } from "../../results/presentation/large-output.js";
|
|
15
15
|
import { extractEnvelopeErrorText, getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
16
|
+
import { detectConfirmationRequired } from "../../results/confirmation.js";
|
|
16
17
|
import { omitUpstreamLifecycle } from "../../results/presentation/common.js";
|
|
17
18
|
import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
|
|
18
19
|
import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
|
|
19
20
|
import { buildPageTransitionRefSnapshotInvalidation, commandExplicitlyTargetsAboutBlank, getCommandRefSnapshotInvalidation, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
20
21
|
import { isRecord } from "../../parsing.js";
|
|
22
|
+
import { buildReadConfirmationNextActions, nextReadConfirmation } from "../../read-confirmation.js";
|
|
21
23
|
import { pruneOwnedManagedSessionRestoreSnapshots } from "../../managed-session-restore.js";
|
|
22
24
|
import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
|
|
23
25
|
import { createFreshSessionName, extractUpstreamCommandTokens, resolveManagedSessionState } from "../../runtime.js";
|
|
@@ -27,6 +29,7 @@ import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBl
|
|
|
27
29
|
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
28
30
|
import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
|
|
29
31
|
import { repairScreenshotData } from "./prepare.js";
|
|
32
|
+
import { mergeRecordingRecoveryPresentation, recoverRecordingStop } from "./recording-recovery.js";
|
|
30
33
|
import { getPersistentSessionArtifactStore } from "./session-state.js";
|
|
31
34
|
import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveValue, } from "./final-result.js";
|
|
32
35
|
async function repairScreenshotArtifact(options) {
|
|
@@ -161,7 +164,13 @@ export async function processBrowserOutput(input) {
|
|
|
161
164
|
const plainTextUpgrade = !prepared.executionPlan.plainTextInspection && prepared.executionPlan.commandInfo.command === "upgrade" && !needsManagedSession(parseArgvDescriptor(prepared.runtimeToolArgs));
|
|
162
165
|
const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath, plainText: plainTextUpgrade });
|
|
163
166
|
let parseError = parsed.parseError;
|
|
164
|
-
|
|
167
|
+
const recordingStopRecovery = await recoverRecordingStop({
|
|
168
|
+
artifactManifest, artifactRunStartedAtMs: input.artifactRunStartedAtMs, commandTokens: prepared.commandTokens, cwd,
|
|
169
|
+
envelope: parsed.envelope, namespace: prepared.executionPlan.namespace, parseError, processResult,
|
|
170
|
+
reservation: prepared.executionPlan.sessionName ? state.activeRecordingReservations?.get(getAgentBrowserSessionIdentityKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace)) : undefined,
|
|
171
|
+
sessionName: prepared.executionPlan.sessionName, signal, stdin: prepared.runtimeToolStdin,
|
|
172
|
+
});
|
|
173
|
+
let presentationEnvelope = recordingStopRecovery?.envelope ?? parsed.envelope;
|
|
165
174
|
let navigationSummary = undefined;
|
|
166
175
|
let failedTransitionReverification = false;
|
|
167
176
|
const repairedScreenshot = await repairScreenshotArtifact({ cwd, envelope: presentationEnvelope, request: prepared.preparedArgs.screenshotPathRequest });
|
|
@@ -178,6 +187,25 @@ export async function processBrowserOutput(input) {
|
|
|
178
187
|
: Array.isArray(presentationEnvelope?.data)
|
|
179
188
|
? presentationEnvelope.data.flatMap((row, index) => isRecord(row) ? [Array.isArray(row.command) && row.command.every((token) => typeof token === "string") ? row.command : batchCommandSteps[index] ?? []] : [])
|
|
180
189
|
: batchCommandSteps;
|
|
190
|
+
const confirmationSessionName = prepared.executionPlan.sessionName ?? "default";
|
|
191
|
+
let readConfirmation = state.sessionPageState.getReadConfirmation(getAgentBrowserSessionIdentityKey(confirmationSessionName, prepared.executionPlan.namespace));
|
|
192
|
+
let readConfirmationEvent;
|
|
193
|
+
const confirmationRows = prepared.executionPlan.commandInfo.command === "batch" && Array.isArray(presentationEnvelope?.data)
|
|
194
|
+
? presentationEnvelope.data.flatMap((row, index) => isRecord(row) ? [{ tokens: batchCommandSteps[index] ?? [], data: row.result, response: row, succeeded: row.success === true }] : [])
|
|
195
|
+
: [{ tokens: prepared.commandTokens, data: presentationEnvelope?.data, response: presentationEnvelope, succeeded: presentationEnvelope?.success === true }];
|
|
196
|
+
for (const row of confirmationRows) {
|
|
197
|
+
const transition = nextReadConfirmation({ commandTokens: row.tokens, current: readConfirmation, data: row.data, namespace: prepared.executionPlan.namespace, sessionName: confirmationSessionName, succeeded: row.succeeded });
|
|
198
|
+
if (transition) {
|
|
199
|
+
readConfirmationEvent = transition;
|
|
200
|
+
readConfirmation = transition;
|
|
201
|
+
}
|
|
202
|
+
if (row.tokens.length === 2 && row.tokens[0] === "confirm" && isRecord(row.data) && row.data.confirmed === true && row.data.action === "read" && isRecord(row.data.result) && row.data.result.success === false && row.response) {
|
|
203
|
+
row.response.success = false;
|
|
204
|
+
row.response.error = row.data.result.error;
|
|
205
|
+
if (presentationEnvelope)
|
|
206
|
+
presentationEnvelope.success = false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
181
209
|
const destinationTransition = dispatchedCommands.some((step) => {
|
|
182
210
|
const [command, subcommand] = extractUpstreamCommandTokens(step);
|
|
183
211
|
return isWindowOrDiffPageTransitionCommand(command, subcommand);
|
|
@@ -200,6 +228,7 @@ export async function processBrowserOutput(input) {
|
|
|
200
228
|
const parseFailureOutput = parseError && processResult.stdoutSpillPath
|
|
201
229
|
? { fullOutputUnavailable: "Malformed upstream output was discarded because it may contain sensitive browser data." }
|
|
202
230
|
: {};
|
|
231
|
+
const browserIndependentRead = prepared.readConfirmation !== undefined || isBrowserIndependentRead(prepared.commandTokens, prepared.runtimeToolStdin);
|
|
203
232
|
const processSucceeded = !processResult.timedOut && !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
|
|
204
233
|
const plainTextInspection = prepared.executionPlan.plainTextInspection && processSucceeded;
|
|
205
234
|
const parseSucceeded = plainTextInspection || parseError === undefined;
|
|
@@ -207,7 +236,7 @@ export async function processBrowserOutput(input) {
|
|
|
207
236
|
presentationEnvelope = { success: true, data: { alreadyEnabled: true, enabled: true, message: getEnvelopeErrorString(presentationEnvelope) ?? "Stream already enabled" } };
|
|
208
237
|
}
|
|
209
238
|
const envelopeSuccess = plainTextInspection ? true : presentationEnvelope?.success !== false;
|
|
210
|
-
let succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
|
|
239
|
+
let succeeded = (processSucceeded && parseSucceeded && envelopeSuccess) || recordingStopRecovery?.recovery.healed === true;
|
|
211
240
|
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
212
241
|
const sessionStateKey = getSessionContextKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
|
|
213
242
|
const closeAllApplied = nestedBatchClosesAll || (directCloseAllRequested && succeeded);
|
|
@@ -364,7 +393,7 @@ export async function processBrowserOutput(input) {
|
|
|
364
393
|
let fillVerificationDiagnostic;
|
|
365
394
|
let selectorTextVisibilityDiagnostics = [];
|
|
366
395
|
let electronBroadGetTextScopeDiagnostics = [];
|
|
367
|
-
const timeoutPartialProgress = processResult.timedOut ? await collectTimeoutPartialProgress({ commandTokens: prepared.commandTokens, compiledJob: prepared.compiledJob, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
|
|
396
|
+
const timeoutPartialProgress = processResult.timedOut && !recordingStopRecovery && !prepared.readConfirmation ? await collectTimeoutPartialProgress({ commandTokens: prepared.commandTokens, compiledJob: prepared.compiledJob, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
|
|
368
397
|
if (!currentSessionTabTarget && timeoutPartialProgress?.currentPage?.source === "live") {
|
|
369
398
|
currentSessionTabTarget = normalizeSessionTabTarget(timeoutPartialProgress.currentPage);
|
|
370
399
|
}
|
|
@@ -396,7 +425,7 @@ export async function processBrowserOutput(input) {
|
|
|
396
425
|
const batchRefSnapshotState = prepared.executionPlan.commandInfo.command === "batch" ? extractLatestRefSnapshotStateFromBatchResults(presentationEnvelope?.data) : undefined;
|
|
397
426
|
let currentRefSnapshot;
|
|
398
427
|
let currentRefSnapshotInvalidation;
|
|
399
|
-
if (sessionStateKey) {
|
|
428
|
+
if (sessionStateKey && !browserIndependentRead) {
|
|
400
429
|
const sessionClosed = (isCloseCommand(prepared.executionPlan.commandInfo.command) && succeeded) || nestedBatchClosed;
|
|
401
430
|
if (sessionClosed) {
|
|
402
431
|
state.attachedSessionKeys.delete(sessionStateKey);
|
|
@@ -427,7 +456,7 @@ export async function processBrowserOutput(input) {
|
|
|
427
456
|
if (!tabUpdate.applied && succeeded)
|
|
428
457
|
sessionPageState.markPinning(sessionStateKey, "drift");
|
|
429
458
|
}
|
|
430
|
-
else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged)) {
|
|
459
|
+
else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged) && !(prepared.commandTokens[0] === "session" && prepared.commandTokens[1] === "info")) {
|
|
431
460
|
sessionPageState.markTabTargetUnknown({ sessionName: sessionStateKey, update: sessionPageStateUpdate });
|
|
432
461
|
}
|
|
433
462
|
const refSnapshot = unsettledWebMcpMutation
|
|
@@ -512,7 +541,7 @@ export async function processBrowserOutput(input) {
|
|
|
512
541
|
managedSessionHeadedAutosaveDisabled = false;
|
|
513
542
|
managedSessionHeadedAutosaveInterval = undefined;
|
|
514
543
|
}
|
|
515
|
-
else if (managedTransitionSucceeded && executionTargetsManagedSession) {
|
|
544
|
+
else if (managedTransitionSucceeded && executionTargetsManagedSession && prepared.ownedManagedSessionContext && !prepared.ownedManagedSessionContext.reuseOnly) {
|
|
516
545
|
managedSessionCompatibilityWorkaround = prepared.compatibilityWorkaround;
|
|
517
546
|
managedSessionHeadedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true;
|
|
518
547
|
managedSessionHeadedAutosaveInterval = prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval;
|
|
@@ -630,7 +659,7 @@ export async function processBrowserOutput(input) {
|
|
|
630
659
|
electronLaunchRecord = electronFailedConnectCleanup.record;
|
|
631
660
|
}
|
|
632
661
|
}
|
|
633
|
-
let errorText = getAgentBrowserErrorText({ aborted: processResult.aborted, command: prepared.executionPlan.commandInfo.command, effectiveArgs: prepared.redactedProcessArgs, envelope: presentationEnvelope, exitCode: processResult.exitCode, parseError, plainTextInspection, staleRefArgs: getStaleRefArgs(prepared.commandTokens, prepared.runtimeToolStdin), spawnError: processResult.spawnError, stderr: processResult.stderr, timedOut: processResult.timedOut, timeoutMs: processResult.timeoutMs, wrapperRecoveryHint: buildWrapperRecoveryHint({ sessionTabCorrection }) });
|
|
662
|
+
let errorText = recordingStopRecovery?.recovery.healed ? undefined : getAgentBrowserErrorText({ aborted: processResult.aborted, command: prepared.executionPlan.commandInfo.command, effectiveArgs: prepared.redactedProcessArgs, envelope: presentationEnvelope, exitCode: processResult.exitCode, parseError, plainTextInspection, staleRefArgs: getStaleRefArgs(prepared.commandTokens, prepared.runtimeToolStdin), spawnError: processResult.spawnError, stderr: processResult.stderr, timedOut: processResult.timedOut, timeoutMs: processResult.timeoutMs, wrapperRecoveryHint: buildWrapperRecoveryHint({ sessionTabCorrection }) });
|
|
634
663
|
if (errorText && presentationEnvelope?.success === false && extractEnvelopeErrorText(presentationEnvelope.error) === undefined)
|
|
635
664
|
presentationEnvelope = { ...presentationEnvelope, error: errorText };
|
|
636
665
|
if (errorText) {
|
|
@@ -641,7 +670,21 @@ export async function processBrowserOutput(input) {
|
|
|
641
670
|
}
|
|
642
671
|
if (plainTextUpgrade && errorText)
|
|
643
672
|
presentationEnvelope = { ...presentationEnvelope, success: false, error: errorText };
|
|
644
|
-
let presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : await buildToolPresentation({ args: prepared.redactedProcessArgs, artifactManifest, artifactMaxUpdatedAtMs: Date.now(), artifactMinUpdatedAtMs: input.artifactRunStartedAtMs, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd, envelope: presentationEnvelope, errorText, namespace: prepared.executionPlan.namespace, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
|
|
673
|
+
let presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : recordingStopRecovery && !recordingStopRecovery.batch ? recordingStopRecovery.presentation : await buildToolPresentation({ args: prepared.redactedProcessArgs, artifactManifest, artifactMaxUpdatedAtMs: Date.now(), artifactMinUpdatedAtMs: input.artifactRunStartedAtMs, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd, envelope: presentationEnvelope, errorText, namespace: prepared.executionPlan.namespace, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, piCleanupOwnership: sessionStateKey && (state.ownedManagedSessions.has(sessionStateKey) || prepared.executionPlan.managedSessionName !== undefined) ? "wrapper-managed" : "caller-owned", sessionName: prepared.executionPlan.sessionName });
|
|
674
|
+
if (recordingStopRecovery)
|
|
675
|
+
presentation = mergeRecordingRecoveryPresentation(presentation, recordingStopRecovery);
|
|
676
|
+
const confirmation = readConfirmationEvent ?? prepared.readConfirmation;
|
|
677
|
+
if (confirmation) {
|
|
678
|
+
presentation.readConfirmation = confirmation;
|
|
679
|
+
if (confirmation.state !== "cleared" || !detectConfirmationRequired(presentationEnvelope?.data)) {
|
|
680
|
+
presentation.nextActions = buildReadConfirmationNextActions(confirmation, readConfirmationEvent?.state === "pending");
|
|
681
|
+
}
|
|
682
|
+
if (readConfirmationEvent?.state === "pending") {
|
|
683
|
+
presentation.resultCategory = "failure";
|
|
684
|
+
presentation.failureCategory = "confirmation-required";
|
|
685
|
+
presentation.successCategory = undefined;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
645
688
|
if (plainTextUpgrade && !succeeded && typeof presentationEnvelope?.data === "string") {
|
|
646
689
|
presentation.data = presentationEnvelope.data;
|
|
647
690
|
const errorContent = presentation.content[0];
|
|
@@ -751,6 +794,8 @@ export async function processBrowserOutput(input) {
|
|
|
751
794
|
const evalPageUrl = evalNavigationSummary?.url ?? currentSessionTabTarget?.url ?? prepared.priorSessionTabTarget?.url ?? evalSessionTabUrl;
|
|
752
795
|
const evalStdinHint = getEvalStdinHint({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, stdin: prepared.runtimeToolStdin });
|
|
753
796
|
const evalResultWarning = getEvalResultWarning({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary: evalNavigationSummary, pageUrl: evalPageUrl, stdin: prepared.runtimeToolStdin });
|
|
797
|
+
if (readConfirmationEvent)
|
|
798
|
+
state.sessionPageState.applyReadConfirmation(readConfirmationEvent, sessionPageStateUpdate);
|
|
754
799
|
const resultArtifactManifest = presentation.artifactManifest ?? artifactManifest;
|
|
755
800
|
const artifactCleanup = await getArtifactCleanupGuidance({ command: prepared.executionPlan.commandInfo.command, cwd, manifest: resultArtifactManifest, succeeded });
|
|
756
801
|
const recordingTransitionReached = prepared.executionPlan.commandInfo.command === "batch"
|
|
@@ -772,9 +817,10 @@ export async function processBrowserOutput(input) {
|
|
|
772
817
|
const resultRetainsPreparedManagedSession = !managedSessionOutcome || (managedSessionOutcome.activeAfter
|
|
773
818
|
&& managedSessionOutcome.attemptedSessionName === managedSessionOutcome.currentSessionName);
|
|
774
819
|
const resultHeadedManagedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true
|
|
820
|
+
&& !prepared.ownedManagedSessionContext.reuseOnly
|
|
775
821
|
&& resultRetainsPreparedManagedSession
|
|
776
822
|
&& !(commandClosesSession && succeeded);
|
|
777
|
-
const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !(commandClosesSession && succeeded)
|
|
823
|
+
const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !prepared.ownedManagedSessionContext?.reuseOnly && !(commandClosesSession && succeeded)
|
|
778
824
|
? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
|
|
779
825
|
: undefined;
|
|
780
826
|
const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|