pi-agent-browser-native 0.3.0 → 0.5.0
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 +127 -0
- package/README.md +63 -20
- package/dist/extensions/agent-browser/index.js +787 -105
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +44 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -2
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -4
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +22 -23
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +8 -0
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -1
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -36
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +47 -29
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -24
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +26 -25
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +17 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +129 -32
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +191 -55
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +57 -29
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +15 -11
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
- package/dist/extensions/agent-browser/lib/playbook.js +18 -15
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -4
- package/dist/extensions/agent-browser/lib/process.js +131 -41
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +62 -5
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
- package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -1
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -11
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -3
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -8
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
- package/dist/extensions/agent-browser/lib/runtime.js +116 -39
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -10
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +40 -21
- package/docs/COMMAND_REFERENCE.md +90 -35
- package/docs/RELEASE.md +3 -3
- package/docs/REQUIREMENTS.md +4 -2
- package/docs/SUPPORT_MATRIX.md +26 -19
- package/docs/TOOL_CONTRACT.md +93 -52
- package/package.json +3 -1
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +7 -6
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
|
-
import { getAgentBrowserSessionIdentityKey } from "../../argv-grammar.js";
|
|
3
|
-
import {
|
|
2
|
+
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "../../argv-grammar.js";
|
|
3
|
+
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "../../batch-lifecycle.js";
|
|
4
|
+
import { isCloseAllCommand, isCloseCommand, isNavigationObservableCommandName, isOpenNavigationCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand } from "../../command-taxonomy.js";
|
|
4
5
|
import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
|
|
5
6
|
import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
|
|
6
7
|
import { getAllowedDomainsViolation, parseAllowedDomainsPolicyFromArgs } from "../../navigation-policy.js";
|
|
7
8
|
import { getManagedSessionResultingPageState, getObservedBrowserPageValidationError, managedSessionCommandRequiresLivePageVerification } from "../../managed-session-state-policy.js";
|
|
8
9
|
import { analyzeNetworkSourceLookupResults, analyzeSourceLookupResults, redactNetworkSourceLookupAnalysis } from "../../input-modes/lookups.js";
|
|
9
|
-
import { analyzeQaPresetResults, analyzeQaPresetTimeout, buildQaCompactPassText, extractQaPageContext } from "../../input-modes/job.js";
|
|
10
|
+
import { analyzeQaPresetResults, analyzeQaPresetTimeout, buildQaCompactFailureText, buildQaCompactPassText, extractQaPageContext } from "../../input-modes/job.js";
|
|
10
11
|
import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../../results/network-routes.js";
|
|
11
12
|
import { buildToolPresentation } from "../../results/presentation.js";
|
|
12
13
|
import { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
14
|
+
import { omitUpstreamLifecycle } from "../../results/presentation/common.js";
|
|
13
15
|
import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
|
|
14
16
|
import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
|
|
15
|
-
import { commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
17
|
+
import { buildPageTransitionRefSnapshotInvalidation, commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
16
18
|
import { isRecord } from "../../parsing.js";
|
|
17
19
|
import { pruneOwnedManagedSessionRestoreSnapshots } from "../../managed-session-restore.js";
|
|
18
20
|
import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
|
|
19
|
-
import { createFreshSessionName,
|
|
21
|
+
import { createFreshSessionName, extractUpstreamCommandTokens, resolveManagedSessionState } from "../../runtime.js";
|
|
22
|
+
import { getUpstreamEffectiveBatchSteps } from "../batch-stdin.js";
|
|
20
23
|
import { closeManagedSession, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
|
|
21
24
|
import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, collectOpenResultTabCorrection, collectSessionTabSelection, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, unwrapPinnedSessionBatchEnvelope, updateTraceOwnerState, } from "./session-state.js";
|
|
22
25
|
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
@@ -71,6 +74,15 @@ function batchStartedManagedBrowser(data) {
|
|
|
71
74
|
return command === "connect" || command === "goto" || command === "navigate" || isOpenNavigationCommand(command);
|
|
72
75
|
});
|
|
73
76
|
}
|
|
77
|
+
function withoutNamespaceEntries(entries, namespace) {
|
|
78
|
+
return new Map([...entries].filter(([key]) => !isAgentBrowserSessionIdentityKeyInNamespace(key, namespace)));
|
|
79
|
+
}
|
|
80
|
+
function deleteNamespaceEntries(entries, namespace) {
|
|
81
|
+
for (const key of entries.keys()) {
|
|
82
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
83
|
+
entries.delete(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
74
86
|
function setNetworkRouteState(options) {
|
|
75
87
|
if (!options.sessionName)
|
|
76
88
|
return options.routesBySession;
|
|
@@ -95,7 +107,12 @@ function applyBatchNetworkRouteState(options) {
|
|
|
95
107
|
for (const item of options.data) {
|
|
96
108
|
if (!isRecord(item) || !Array.isArray(item.command) || !item.command.every((token) => typeof token === "string"))
|
|
97
109
|
continue;
|
|
98
|
-
|
|
110
|
+
const commandTokens = extractUpstreamCommandTokens(item.command);
|
|
111
|
+
const stepSucceeded = item.success !== false;
|
|
112
|
+
if (stepSucceeded && isCloseCommand(commandTokens[0]))
|
|
113
|
+
routes = undefined;
|
|
114
|
+
else
|
|
115
|
+
routes = applyNetworkRouteRecords(routes, commandTokens, stepSucceeded);
|
|
99
116
|
}
|
|
100
117
|
return setNetworkRouteState({ routes, routesBySession: options.routesBySession, sessionName: options.sessionName });
|
|
101
118
|
}
|
|
@@ -120,6 +137,7 @@ export async function processBrowserOutput(input) {
|
|
|
120
137
|
let parseError = parsed.parseError;
|
|
121
138
|
let presentationEnvelope = parsed.envelope;
|
|
122
139
|
let navigationSummary = undefined;
|
|
140
|
+
let failedTransitionReverification = false;
|
|
123
141
|
if (prepared.pinnedBatchUnwrapMode) {
|
|
124
142
|
const pinnedBatchResult = unwrapPinnedSessionBatchEnvelope({ envelope: parsed.envelope, includeNavigationSummary: prepared.includePinnedNavigationSummary, mode: prepared.pinnedBatchUnwrapMode });
|
|
125
143
|
parseError = pinnedBatchResult.parseError ?? parseError;
|
|
@@ -132,11 +150,22 @@ export async function processBrowserOutput(input) {
|
|
|
132
150
|
presentationEnvelope = repairedBatchScreenshots.envelope;
|
|
133
151
|
const screenshotArtifactRequest = repairedScreenshot.request;
|
|
134
152
|
const batchScreenshotArtifactRequests = repairedBatchScreenshots.requests;
|
|
153
|
+
const batchCommandSteps = prepared.executionPlan.commandInfo.command === "batch"
|
|
154
|
+
? getUpstreamEffectiveBatchSteps(prepared.commandTokens, prepared.runtimeToolStdin)
|
|
155
|
+
: [];
|
|
156
|
+
const nestedBatchClose = prepared.executionPlan.commandInfo.command === "batch"
|
|
157
|
+
? getSuccessfulBatchCloseLifecycle(presentationEnvelope?.data, batchCommandSteps)
|
|
158
|
+
: undefined;
|
|
159
|
+
const nestedBatchClosed = nestedBatchClose?.endsClosed === true;
|
|
160
|
+
const nestedBatchRemainsActive = nestedBatchClose?.endsClosed === false;
|
|
161
|
+
const nestedBatchClosesAll = prepared.executionPlan.commandInfo.command === "batch"
|
|
162
|
+
&& batchHasSuccessfulCloseAll(presentationEnvelope?.data, batchCommandSteps);
|
|
163
|
+
const directCloseAllRequested = isCloseAllCommand(extractUpstreamCommandTokens(prepared.commandTokens));
|
|
135
164
|
const rawCloseStatePath = isCloseCommand(prepared.executionPlan.commandInfo.command)
|
|
136
165
|
&& isRecord(presentationEnvelope?.data)
|
|
137
166
|
&& typeof presentationEnvelope.data.statePath === "string"
|
|
138
167
|
? presentationEnvelope.data.statePath
|
|
139
|
-
:
|
|
168
|
+
: nestedBatchClose?.statePath;
|
|
140
169
|
if (presentationEnvelope && prepared.exactSensitiveValues.length > 0)
|
|
141
170
|
presentationEnvelope = redactExactSensitiveValue(presentationEnvelope, prepared.exactSensitiveValues);
|
|
142
171
|
const parseFailureOutput = parseError && processResult.stdoutSpillPath
|
|
@@ -152,7 +181,44 @@ export async function processBrowserOutput(input) {
|
|
|
152
181
|
let succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
|
|
153
182
|
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
154
183
|
const sessionStateKey = getSessionContextKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
|
|
155
|
-
|
|
184
|
+
const closeAllApplied = nestedBatchClosesAll || (directCloseAllRequested && succeeded);
|
|
185
|
+
if (closeAllApplied) {
|
|
186
|
+
allowedDomainsBySession = withoutNamespaceEntries(allowedDomainsBySession, prepared.executionPlan.namespace);
|
|
187
|
+
networkRoutesBySession = withoutNamespaceEntries(networkRoutesBySession, prepared.executionPlan.namespace);
|
|
188
|
+
deleteNamespaceEntries(state.attachedSessionKeys, prepared.executionPlan.namespace);
|
|
189
|
+
deleteNamespaceEntries(traceOwners, prepared.executionPlan.namespace);
|
|
190
|
+
sessionPageState.clearNamespace(prepared.executionPlan.namespace);
|
|
191
|
+
const retainedSessionKey = nestedBatchRemainsActive ? sessionStateKey : undefined;
|
|
192
|
+
for (const [key, owner] of state.ownedManagedSessions) {
|
|
193
|
+
if (!isAgentBrowserSessionIdentityKeyInNamespace(key, prepared.executionPlan.namespace) || key === retainedSessionKey)
|
|
194
|
+
continue;
|
|
195
|
+
state.closedManagedSessionNames.add(key);
|
|
196
|
+
state.managedSessionRestoreState.clear(owner.sessionName, owner.namespace);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (nestedBatchClose && sessionStateKey) {
|
|
200
|
+
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
201
|
+
allowedDomainsBySession.delete(sessionStateKey);
|
|
202
|
+
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
203
|
+
networkRoutesBySession.delete(sessionStateKey);
|
|
204
|
+
sessionPageState.clearSession(sessionStateKey);
|
|
205
|
+
}
|
|
206
|
+
if (prepared.executionPlan.commandInfo.command === "batch" && Array.isArray(presentationEnvelope?.data)) {
|
|
207
|
+
for (const [index, row] of presentationEnvelope.data.entries()) {
|
|
208
|
+
if (!isRecord(row))
|
|
209
|
+
continue;
|
|
210
|
+
const rowCommand = Array.isArray(row.command) && row.command.every((token) => typeof token === "string")
|
|
211
|
+
? row.command
|
|
212
|
+
: batchCommandSteps[index];
|
|
213
|
+
if (!rowCommand)
|
|
214
|
+
continue;
|
|
215
|
+
const [command, subcommand] = extractUpstreamCommandTokens(rowCommand);
|
|
216
|
+
updateTraceOwnerState({ command, sessionName: sessionStateKey, subcommand, succeeded: row.success === true, traceOwners });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
updateTraceOwnerState({ command: prepared.executionPlan.commandInfo.command, sessionName: sessionStateKey, subcommand: prepared.executionPlan.commandInfo.subcommand, succeeded, traceOwners });
|
|
221
|
+
}
|
|
156
222
|
let clickDispatchDiagnostic;
|
|
157
223
|
if (succeeded && prepared.clickDispatchProbe) {
|
|
158
224
|
clickDispatchDiagnostic = await collectClickDispatchDiagnostic({ cwd, namespace: prepared.executionPlan.namespace, probe: prepared.clickDispatchProbe, sessionName: prepared.executionPlan.sessionName, signal });
|
|
@@ -183,7 +249,24 @@ export async function processBrowserOutput(input) {
|
|
|
183
249
|
shouldCaptureAllowedDomainNavigationSummary ||
|
|
184
250
|
managedSessionCommandRequiresLivePageVerification(prepared.executionPlan.effectiveArgs, prepared.runtimeToolStdin) ||
|
|
185
251
|
(prepared.executionPlan.commandInfo.command === "tab" && prepared.executionPlan.commandInfo.subcommand === "close"))) {
|
|
186
|
-
navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
|
|
252
|
+
navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
|
|
253
|
+
}
|
|
254
|
+
// A failed eval/back/forward/reload/state-load/tab would otherwise drop the page to unverified and force the
|
|
255
|
+
// agent through a manual get url round trip. Probe the live URL ourselves instead: an observed http(s) page
|
|
256
|
+
// stays verified, a file URL keeps the local-file gate closed through currentSessionTabTarget, and a failed
|
|
257
|
+
// probe preserves the existing unknown-target behavior.
|
|
258
|
+
if (succeeded === false &&
|
|
259
|
+
!navigationSummary &&
|
|
260
|
+
processResult.agentBrowserStarted &&
|
|
261
|
+
!processResult.aborted &&
|
|
262
|
+
!processResult.timedOut &&
|
|
263
|
+
prepared.executionPlan.commandInfo.command !== "batch" &&
|
|
264
|
+
isUnverifiedPageTransitionCommand(prepared.executionPlan.commandInfo.command, prepared.executionPlan.commandInfo.subcommand)) {
|
|
265
|
+
navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
|
|
266
|
+
// A failed transition command can still have mutated or replaced the document before throwing, so
|
|
267
|
+
// keeping the verified URL must not keep the prior snapshot refs (markTabTargetUnknown dropped
|
|
268
|
+
// them before this probe existed); invalidate so the next ref use requires a fresh snapshot.
|
|
269
|
+
failedTransitionReverification = navigationSummary !== undefined;
|
|
187
270
|
}
|
|
188
271
|
if (navigationSummary && presentationEnvelope && prepared.executionPlan.commandInfo.command !== "eval" && !Array.isArray(presentationEnvelope.data))
|
|
189
272
|
presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
|
|
@@ -207,15 +290,18 @@ export async function processBrowserOutput(input) {
|
|
|
207
290
|
presentationEnvelope = { error: observedPageValidationError, success: false };
|
|
208
291
|
}
|
|
209
292
|
const safeObservedSessionTabTarget = observedPageValidationError ? normalizeSessionTabTarget({ url: observedSessionTabTarget?.url }) : observedSessionTabTarget;
|
|
210
|
-
let currentSessionTabTarget = safeObservedSessionTabTarget
|
|
211
|
-
|
|
212
|
-
|
|
293
|
+
let currentSessionTabTarget = safeObservedSessionTabTarget;
|
|
294
|
+
if (!currentSessionTabTarget && nestedBatchClose === undefined) {
|
|
295
|
+
currentSessionTabTarget = resultingPageState.pageTargetMayHaveChanged
|
|
296
|
+
? succeeded ? normalizeSessionTabTarget({ url: resultingPageState.currentPageUrl }) : undefined
|
|
297
|
+
: deriveSessionTabTarget({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary, previousTarget: prepared.priorSessionTabTarget, subcommand: prepared.executionPlan.commandInfo.subcommand });
|
|
298
|
+
}
|
|
213
299
|
let aboutBlankSessionMismatch;
|
|
214
300
|
let electronPostCommandHealth;
|
|
215
301
|
let electronRefFreshnessDiagnostic;
|
|
216
302
|
let electronSessionMismatch;
|
|
217
303
|
let electronStatusAfterCommand;
|
|
218
|
-
const shouldTreatAboutBlankAsMismatch = succeeded && prepared.priorSessionTabTarget !== undefined && !isAboutBlankSessionTabTarget(prepared.priorSessionTabTarget) && isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget) && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens);
|
|
304
|
+
const shouldTreatAboutBlankAsMismatch = succeeded && nestedBatchClose === undefined && prepared.priorSessionTabTarget !== undefined && !isAboutBlankSessionTabTarget(prepared.priorSessionTabTarget) && isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget) && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens);
|
|
219
305
|
let sessionTabCorrection = prepared.sessionTabCorrection;
|
|
220
306
|
if (shouldTreatAboutBlankAsMismatch && prepared.priorSessionTabTarget) {
|
|
221
307
|
const aboutBlankObservedTarget = observedSessionTabTarget ?? currentSessionTabTarget;
|
|
@@ -228,7 +314,7 @@ export async function processBrowserOutput(input) {
|
|
|
228
314
|
else
|
|
229
315
|
currentSessionTabTarget = aboutBlankObservedTarget ?? normalizeSessionTabTarget({ url: "about:blank" });
|
|
230
316
|
aboutBlankSessionMismatch = { activeUrl: "about:blank", recoveryApplied: appliedAboutBlankRecovery !== undefined, recoveryHint: buildAboutBlankRecoveryHint(), targetTitle: prepared.priorSessionTabTarget.title, targetUrl: prepared.priorSessionTabTarget.url };
|
|
231
|
-
const electronRecord = findElectronLaunchRecordForSession(prepared.executionPlan.sessionName, electronLaunchRecords);
|
|
317
|
+
const electronRecord = findElectronLaunchRecordForSession(prepared.executionPlan.sessionName, electronLaunchRecords, prepared.executionPlan.namespace);
|
|
232
318
|
if (electronRecord && prepared.executionPlan.sessionName) {
|
|
233
319
|
electronStatusAfterCommand = await inspectElectronLaunchStatus(electronRecord);
|
|
234
320
|
electronSessionMismatch = buildElectronSessionMismatch({ managedSession: { sessionName: prepared.executionPlan.sessionName, title: aboutBlankObservedTarget?.title, url: aboutBlankObservedTarget?.url ?? "about:blank" }, record: electronRecord, statusTargets: electronStatusAfterCommand.targets });
|
|
@@ -254,7 +340,7 @@ export async function processBrowserOutput(input) {
|
|
|
254
340
|
succeeded = false;
|
|
255
341
|
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: allowedDomainsViolation.summary, success: false };
|
|
256
342
|
}
|
|
257
|
-
const electronRecordForCommand = findElectronLaunchRecordForSession(prepared.executionPlan.sessionName, electronLaunchRecords);
|
|
343
|
+
const electronRecordForCommand = findElectronLaunchRecordForSession(prepared.executionPlan.sessionName, electronLaunchRecords, prepared.executionPlan.namespace);
|
|
258
344
|
if (succeeded && electronRecordForCommand && shouldInspectElectronPostCommandHealth(prepared.executionPlan.commandInfo.command)) {
|
|
259
345
|
electronStatusAfterCommand ??= await inspectElectronLaunchStatus(electronRecordForCommand);
|
|
260
346
|
electronPostCommandHealth = buildElectronPostCommandHealthDiagnostic({ command: prepared.executionPlan.commandInfo.command, record: electronRecordForCommand, status: electronStatusAfterCommand, target: observedSessionTabTarget ?? currentSessionTabTarget });
|
|
@@ -288,7 +374,7 @@ export async function processBrowserOutput(input) {
|
|
|
288
374
|
if (succeeded) {
|
|
289
375
|
selectorTextVisibilityDiagnostics = await collectSelectorTextVisibilityDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, cwd, data: presentationEnvelope?.data, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
|
|
290
376
|
if (electronRecordForCommand)
|
|
291
|
-
electronBroadGetTextScopeDiagnostics = collectElectronBroadGetTextScopeDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, currentTarget: currentSessionTabTarget, data: presentationEnvelope?.data, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName });
|
|
377
|
+
electronBroadGetTextScopeDiagnostics = collectElectronBroadGetTextScopeDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, currentTarget: currentSessionTabTarget, data: presentationEnvelope?.data, electronLaunchRecords, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName });
|
|
292
378
|
}
|
|
293
379
|
const activeNetworkRoutes = sessionStateKey ? networkRoutesBySession.get(sessionStateKey) : undefined;
|
|
294
380
|
const networkRouteDiagnostics = succeeded && prepared.executionPlan.commandInfo.command === "network" && prepared.executionPlan.commandInfo.subcommand === "requests" && prepared.executionPlan.sessionName
|
|
@@ -298,11 +384,13 @@ export async function processBrowserOutput(input) {
|
|
|
298
384
|
const comboboxFocusDiagnostic = succeeded ? await collectComboboxFocusDiagnostic({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, cwd, namespace: prepared.executionPlan.namespace, semanticAction: prepared.compiledSemanticAction, sessionName: prepared.executionPlan.sessionName, signal }) : undefined;
|
|
299
385
|
const recordingDependencyWarning = await collectRecordingDependencyWarning({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, succeeded });
|
|
300
386
|
const scrollNoopDiagnostic = succeeded && prepared.shouldProbeScrollNoop ? buildScrollNoopDiagnostic(prepared.scrollPositionBefore, await collectScrollPositionSnapshot({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal })) : undefined;
|
|
387
|
+
const batchRefSnapshotState = prepared.executionPlan.commandInfo.command === "batch" ? extractLatestRefSnapshotStateFromBatchResults(presentationEnvelope?.data) : undefined;
|
|
301
388
|
let currentRefSnapshot;
|
|
302
389
|
let currentRefSnapshotInvalidation;
|
|
303
|
-
const batchRefSnapshotState = prepared.executionPlan.commandInfo.command === "batch" ? extractLatestRefSnapshotStateFromBatchResults(presentationEnvelope?.data) : undefined;
|
|
304
390
|
if (sessionStateKey) {
|
|
305
|
-
|
|
391
|
+
const sessionClosed = (isCloseCommand(prepared.executionPlan.commandInfo.command) && succeeded) || nestedBatchClosed;
|
|
392
|
+
if (sessionClosed) {
|
|
393
|
+
state.attachedSessionKeys.delete(sessionStateKey);
|
|
306
394
|
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
307
395
|
allowedDomainsBySession.delete(sessionStateKey);
|
|
308
396
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
@@ -310,24 +398,43 @@ export async function processBrowserOutput(input) {
|
|
|
310
398
|
sessionPageState.clearSession(sessionStateKey);
|
|
311
399
|
state.closedManagedSessionNames.add(sessionStateKey);
|
|
312
400
|
}
|
|
313
|
-
else if (currentSessionTabTarget) {
|
|
314
|
-
const tabUpdate = sessionPageState.applyTabTarget({ sessionName: sessionStateKey, target: currentSessionTabTarget, update: sessionPageStateUpdate });
|
|
315
|
-
if (!tabUpdate.applied && succeeded)
|
|
316
|
-
sessionPageState.markPinning(sessionStateKey, "drift");
|
|
317
|
-
}
|
|
318
|
-
else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged)) {
|
|
319
|
-
sessionPageState.markTabTargetUnknown({ sessionName: sessionStateKey, update: sessionPageStateUpdate });
|
|
320
|
-
}
|
|
321
|
-
const refSnapshot = prepared.executionPlan.commandInfo.command === "batch" ? batchRefSnapshotState?.snapshot : succeeded ? prepared.executionPlan.commandInfo.command === "snapshot" ? extractRefSnapshotFromData(presentationEnvelope?.data) : prepared.resolvedSemanticActionRefSnapshot ?? overlayBlockerDiagnostic?.snapshot : undefined;
|
|
322
|
-
if (refSnapshot) {
|
|
323
|
-
const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName: sessionStateKey, snapshot: refSnapshot, update: sessionPageStateUpdate });
|
|
324
|
-
currentRefSnapshot = refUpdate.refSnapshot;
|
|
325
|
-
currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
|
|
326
|
-
}
|
|
327
401
|
else {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
402
|
+
// A batch that times out or returns unparseable output yields no result rows, but the daemon
|
|
403
|
+
// may already have executed a recording page swap; fall back to the planned steps then. This
|
|
404
|
+
// also fires for wrapper-replaced envelopes and upstream pre-loop errors where nothing ran,
|
|
405
|
+
// which only over-invalidates (one extra snapshot), never under-invalidates.
|
|
406
|
+
const plannedBatchRecordSwap = !Array.isArray(presentationEnvelope?.data) && batchCommandSteps.some((step) => isRecordPageTransitionCommand(step));
|
|
407
|
+
const pageTransitionInvalidation = processResult.agentBrowserStarted && (isRecordPageTransitionCommand(prepared.commandTokens) || plannedBatchRecordSwap)
|
|
408
|
+
? buildPageTransitionRefSnapshotInvalidation()
|
|
409
|
+
: failedTransitionReverification
|
|
410
|
+
? buildPageTransitionRefSnapshotInvalidation("A failed eval/back/forward/reload/connect/state-load/tab command may still have changed the page, so the prior snapshot refs were invalidated. Run snapshot -i before using page-scoped refs.")
|
|
411
|
+
: batchRefSnapshotState?.invalidation?.reason === "page-transition"
|
|
412
|
+
? batchRefSnapshotState.invalidation
|
|
413
|
+
: undefined;
|
|
414
|
+
if (currentSessionTabTarget) {
|
|
415
|
+
const tabUpdate = sessionPageState.applyTabTarget({ sessionName: sessionStateKey, target: currentSessionTabTarget, update: sessionPageStateUpdate });
|
|
416
|
+
if (!tabUpdate.applied && succeeded)
|
|
417
|
+
sessionPageState.markPinning(sessionStateKey, "drift");
|
|
418
|
+
}
|
|
419
|
+
else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged)) {
|
|
420
|
+
sessionPageState.markTabTargetUnknown({ sessionName: sessionStateKey, update: sessionPageStateUpdate });
|
|
421
|
+
}
|
|
422
|
+
const refSnapshot = prepared.executionPlan.commandInfo.command === "batch" ? batchRefSnapshotState?.snapshot : succeeded ? prepared.executionPlan.commandInfo.command === "snapshot" ? extractRefSnapshotFromData(presentationEnvelope?.data) : prepared.resolvedSemanticActionRefSnapshot ?? overlayBlockerDiagnostic?.snapshot : undefined;
|
|
423
|
+
if (refSnapshot) {
|
|
424
|
+
const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName: sessionStateKey, snapshot: refSnapshot, update: sessionPageStateUpdate });
|
|
425
|
+
currentRefSnapshot = refUpdate.refSnapshot;
|
|
426
|
+
currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
|
|
427
|
+
}
|
|
428
|
+
else if (pageTransitionInvalidation) {
|
|
429
|
+
const refUpdate = sessionPageState.applyRefSnapshotInvalidation({ invalidation: pageTransitionInvalidation, sessionName: sessionStateKey, update: sessionPageStateUpdate });
|
|
430
|
+
currentRefSnapshot = refUpdate.refSnapshot;
|
|
431
|
+
currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
const stateView = sessionPageState.get(sessionStateKey);
|
|
435
|
+
currentRefSnapshot = stateView.refSnapshot;
|
|
436
|
+
currentRefSnapshotInvalidation = stateView.refSnapshotInvalidation;
|
|
437
|
+
}
|
|
331
438
|
}
|
|
332
439
|
}
|
|
333
440
|
const priorManagedSessionActive = managedSessionActive;
|
|
@@ -335,11 +442,22 @@ export async function processBrowserOutput(input) {
|
|
|
335
442
|
const priorManagedSessionHeadedAutosaveInterval = managedSessionHeadedAutosaveInterval;
|
|
336
443
|
const priorManagedSessionName = managedSessionName;
|
|
337
444
|
const priorManagedSessionNamespace = managedSessionNamespace;
|
|
338
|
-
const
|
|
445
|
+
const priorManagedSessionKey = getSessionContextKey(priorManagedSessionName, priorManagedSessionNamespace) ?? priorManagedSessionName;
|
|
446
|
+
const closeAllTargetsPriorManagedSession = closeAllApplied
|
|
447
|
+
&& priorManagedSessionActive
|
|
448
|
+
&& isAgentBrowserSessionIdentityKeyInNamespace(priorManagedSessionKey, prepared.executionPlan.namespace);
|
|
449
|
+
const closeAllRetainsPriorManagedSession = closeAllTargetsPriorManagedSession
|
|
450
|
+
&& nestedBatchRemainsActive
|
|
451
|
+
&& sessionStateKey === priorManagedSessionKey;
|
|
452
|
+
const closeAllClosesPriorManagedSession = closeAllTargetsPriorManagedSession && !closeAllRetainsPriorManagedSession;
|
|
453
|
+
const commandClosesSession = isCloseCommand(prepared.executionPlan.commandInfo.command) || nestedBatchClosed || closeAllClosesPriorManagedSession;
|
|
454
|
+
const closeCommandSucceeded = (isCloseCommand(prepared.executionPlan.commandInfo.command) && succeeded) || nestedBatchClosed || closeAllClosesPriorManagedSession;
|
|
339
455
|
const closeTargetsPriorManagedNamespace = prepared.executionPlan.namespace === priorManagedSessionNamespace;
|
|
340
|
-
const managedCloseSessionName =
|
|
341
|
-
?
|
|
342
|
-
: prepared.executionPlan.
|
|
456
|
+
const managedCloseSessionName = closeAllClosesPriorManagedSession
|
|
457
|
+
? priorManagedSessionName
|
|
458
|
+
: closeCommandSucceeded && prepared.executionPlan.sessionName === priorManagedSessionName && closeTargetsPriorManagedNamespace
|
|
459
|
+
? prepared.executionPlan.sessionName
|
|
460
|
+
: prepared.executionPlan.managedSessionName;
|
|
343
461
|
const policyBlockedFreshManagedSession = allowedDomainsViolation !== undefined && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
|
|
344
462
|
const postLaunchBatchFailure = !succeeded && processSucceeded && parseSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && batchStartedManagedBrowser(presentationEnvelope?.data);
|
|
345
463
|
const postLaunchTimeoutWithPage = !succeeded && processResult.timedOut && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && timeoutPartialProgress?.liveUrlRecovered === true;
|
|
@@ -361,8 +479,8 @@ export async function processBrowserOutput(input) {
|
|
|
361
479
|
}
|
|
362
480
|
// Only a confirmed inactive daemon proves that a started fresh command did not establish browser ownership.
|
|
363
481
|
const postLaunchFreshFailure = failedFreshDaemon !== undefined && failedFreshDaemon.status !== "inactive";
|
|
364
|
-
const managedTransitionSucceeded = succeeded || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage || postLaunchFreshFailure;
|
|
365
|
-
const managedSessionState = resolveManagedSessionState({ command: prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, managedSessionNamespace: prepared.executionPlan.namespace, priorActive: priorManagedSessionActive, priorNamespace: priorManagedSessionNamespace, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
|
|
482
|
+
const managedTransitionSucceeded = succeeded || nestedBatchClosed || nestedBatchRemainsActive || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage || postLaunchFreshFailure;
|
|
483
|
+
const managedSessionState = resolveManagedSessionState({ command: commandClosesSession ? "close" : prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, managedSessionNamespace: prepared.executionPlan.namespace, priorActive: priorManagedSessionActive, priorNamespace: priorManagedSessionNamespace, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
|
|
366
484
|
if (!managedTransitionSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName) {
|
|
367
485
|
state.managedSessionRestoreState.clear(prepared.executionPlan.managedSessionName, prepared.executionPlan.namespace);
|
|
368
486
|
}
|
|
@@ -383,7 +501,7 @@ export async function processBrowserOutput(input) {
|
|
|
383
501
|
managedSessionHeadedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true;
|
|
384
502
|
managedSessionHeadedAutosaveInterval = prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval;
|
|
385
503
|
}
|
|
386
|
-
if (
|
|
504
|
+
if (closeCommandSucceeded && managedCloseSessionName === priorManagedSessionName && !managedSessionActive) {
|
|
387
505
|
const daemonRestoreKey = state.managedSessionRestoreState.getDaemonRestoreKey(managedCloseSessionName, priorManagedSessionNamespace);
|
|
388
506
|
const ownedRestoreKey = !state.managedSessionRestoreState.isDisabled(managedCloseSessionName, priorManagedSessionNamespace)
|
|
389
507
|
&& isManagedSessionRestoreKey(daemonRestoreKey) ? daemonRestoreKey : null;
|
|
@@ -398,7 +516,7 @@ export async function processBrowserOutput(input) {
|
|
|
398
516
|
managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
|
|
399
517
|
managedSessionNamespace = undefined;
|
|
400
518
|
}
|
|
401
|
-
let managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: managedCloseSessionName, command: prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, currentSessionNamespace: managedSessionNamespace, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, replacedSessionNamespace: priorManagedSessionNamespace, sessionMode: prepared.sessionMode, succeeded: managedTransitionSucceeded });
|
|
519
|
+
let managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: managedCloseSessionName, command: commandClosesSession ? "close" : prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, currentSessionNamespace: managedSessionNamespace, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, replacedSessionNamespace: priorManagedSessionNamespace, sessionMode: prepared.sessionMode, succeeded: managedTransitionSucceeded });
|
|
402
520
|
if (prepared.executionPlan.managedSessionName && managedTransitionSucceeded && managedSessionActive) {
|
|
403
521
|
managedSessionCwd = cwd;
|
|
404
522
|
managedSessionNamespace = prepared.executionPlan.namespace;
|
|
@@ -439,7 +557,7 @@ export async function processBrowserOutput(input) {
|
|
|
439
557
|
if (succeeded && prepared.executionPlan.sessionName) {
|
|
440
558
|
const electronSessionName = prepared.executionPlan.sessionName;
|
|
441
559
|
const electronSessionStateKey = sessionStateKey ?? electronSessionName;
|
|
442
|
-
electronLaunchRecord = { ...prepared.electronLaunch.record, sessionName: electronSessionName };
|
|
560
|
+
electronLaunchRecord = { ...prepared.electronLaunch.record, namespace: prepared.executionPlan.namespace, sessionName: electronSessionName };
|
|
443
561
|
const electronHandoffMode = prepared.compiledElectron?.action === "launch" ? prepared.compiledElectron.handoff : "connect";
|
|
444
562
|
try {
|
|
445
563
|
electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, namespace: prepared.executionPlan.namespace, sessionName: electronSessionName, signal });
|
|
@@ -507,7 +625,7 @@ export async function processBrowserOutput(input) {
|
|
|
507
625
|
if (presentationEnvelope?.error !== undefined)
|
|
508
626
|
presentationEnvelope = { ...presentationEnvelope, error: redactClipboardPermissionErrorValue(prepared.executionPlan.commandInfo, presentationEnvelope.error, clipboardWritePayloadCandidates) };
|
|
509
627
|
}
|
|
510
|
-
const 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, 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 });
|
|
628
|
+
const 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 });
|
|
511
629
|
if (observedPageValidationError)
|
|
512
630
|
presentation.failureCategory = "validation-error";
|
|
513
631
|
if (electronHandoff?.error && electronHandoff.failureCategory)
|
|
@@ -518,13 +636,20 @@ export async function processBrowserOutput(input) {
|
|
|
518
636
|
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: presentation.summary, success: false };
|
|
519
637
|
}
|
|
520
638
|
if (scrollNoopDiagnostic) {
|
|
639
|
+
succeeded = false;
|
|
640
|
+
presentation.resultCategory = "failure";
|
|
641
|
+
presentation.failureCategory = "upstream-error";
|
|
642
|
+
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: "Scroll completed with no observed movement.", success: false };
|
|
521
643
|
presentation.summary = "Scroll completed with no observed movement.";
|
|
522
644
|
if (isRecord(presentation.data))
|
|
523
645
|
presentation.data = { ...presentation.data, noMovement: true, scrolled: false };
|
|
524
|
-
if (presentation.content[0]?.type === "text")
|
|
525
|
-
presentation.
|
|
526
|
-
|
|
646
|
+
if (presentation.content[0]?.type === "text") {
|
|
647
|
+
const details = isRecord(presentation.data) ? JSON.stringify(omitUpstreamLifecycle(presentation.data), null, 2) : presentation.content[0].text;
|
|
648
|
+
presentation.content[0] = { ...presentation.content[0], text: `Scroll completed with no observed movement.\n\n${details}` };
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
527
651
|
presentation.content.unshift({ type: "text", text: "Scroll completed with no observed movement." });
|
|
652
|
+
}
|
|
528
653
|
}
|
|
529
654
|
if (parseFailureOutput.artifactManifest) {
|
|
530
655
|
presentation.artifactManifest = parseFailureOutput.artifactManifest;
|
|
@@ -544,7 +669,7 @@ export async function processBrowserOutput(input) {
|
|
|
544
669
|
let qaAttachedTarget = prepared.compiledQaPreset?.checks.attached
|
|
545
670
|
? await collectQaAttachedTarget({ currentTarget: currentSessionTabTarget ?? prepared.priorSessionTabTarget, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal })
|
|
546
671
|
: undefined;
|
|
547
|
-
const sourceLookupElectronContext = prepared.compiledSourceLookup ? getSourceLookupElectronContext({ currentTarget: currentSessionTabTarget, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName }) : undefined;
|
|
672
|
+
const sourceLookupElectronContext = prepared.compiledSourceLookup ? getSourceLookupElectronContext({ currentTarget: currentSessionTabTarget, electronLaunchRecords, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName }) : undefined;
|
|
548
673
|
const sourceLookup = prepared.compiledSourceLookup ? await analyzeSourceLookupResults(presentationEnvelope?.data, prepared.compiledSourceLookup, cwd, { electronContext: sourceLookupElectronContext, workspaceRoot: cwd }) : undefined;
|
|
549
674
|
const networkSourceLookup = prepared.compiledNetworkSourceLookup ? redactNetworkSourceLookupAnalysis(await analyzeNetworkSourceLookupResults(presentationEnvelope?.data, prepared.compiledNetworkSourceLookup, cwd)) : undefined;
|
|
550
675
|
if (networkSourceLookup && presentation.content[0]?.type === "text")
|
|
@@ -555,14 +680,22 @@ export async function processBrowserOutput(input) {
|
|
|
555
680
|
presentation.content[0] = { ...presentation.content[0], text: `${sourceLookup.summary}\n\n${presentation.content[0].text}` };
|
|
556
681
|
else if (sourceLookup)
|
|
557
682
|
presentation.content.unshift({ type: "text", text: sourceLookup.summary });
|
|
558
|
-
if (qaPreset && !qaPreset.passed && presentation.failureCategory !== "artifact-missing") {
|
|
683
|
+
if (qaPreset && !qaPreset.passed && prepared.compiledQaPreset && presentation.failureCategory !== "artifact-missing") {
|
|
559
684
|
succeeded = false;
|
|
560
685
|
presentation.failureCategory = "qa-failure";
|
|
561
686
|
presentation.summary = qaPreset.summary;
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
687
|
+
const compactText = buildQaCompactFailureText({
|
|
688
|
+
batchStepCount: presentation.batchSteps?.length ?? prepared.compiledQaPreset.steps.length,
|
|
689
|
+
checks: prepared.compiledQaPreset.checks,
|
|
690
|
+
page: extractQaPageContext({
|
|
691
|
+
attachedTarget: qaAttachedTarget,
|
|
692
|
+
batchData: presentationEnvelope?.data,
|
|
693
|
+
compiled: prepared.compiledQaPreset,
|
|
694
|
+
}),
|
|
695
|
+
qaPreset,
|
|
696
|
+
});
|
|
697
|
+
const nonTextContent = presentation.content.filter((item) => item.type !== "text");
|
|
698
|
+
presentation.content = [{ type: "text", text: compactText }, ...nonTextContent];
|
|
566
699
|
}
|
|
567
700
|
else if (qaPreset?.passed && prepared.compiledQaPreset && succeeded) {
|
|
568
701
|
const compactText = buildQaCompactPassText({
|
|
@@ -617,8 +750,11 @@ export async function processBrowserOutput(input) {
|
|
|
617
750
|
? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
|
|
618
751
|
: undefined;
|
|
619
752
|
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, inspectionText, 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, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
753
|
+
const resultWithCloseAll = closeAllApplied
|
|
754
|
+
? { ...result, details: { ...(isRecord(result.details) ? result.details : {}), closeAllApplied: true } }
|
|
755
|
+
: result;
|
|
620
756
|
const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionHeadedAutosaveDisabled, managedSessionHeadedAutosaveInterval, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
|
|
621
|
-
return { result, statePatch };
|
|
757
|
+
return { result: resultWithCloseAll, statePatch };
|
|
622
758
|
}
|
|
623
759
|
finally {
|
|
624
760
|
if (processResult.stdoutSpillPath)
|