pi-agent-browser-native 0.2.61 → 0.2.63
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 +34 -0
- package/README.md +10 -10
- package/dist/extensions/agent-browser/index.js +51 -30
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +29 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +10 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +7 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +5 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +2 -1
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +25 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +4 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +25 -17
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +14 -11
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +3 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +5 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +43 -27
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +66 -54
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +24 -15
- package/dist/extensions/agent-browser/lib/playbook.js +4 -4
- package/dist/extensions/agent-browser/lib/process.js +3 -3
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +8 -7
- package/dist/extensions/agent-browser/lib/results/presentation.js +2 -1
- package/dist/extensions/agent-browser/lib/runtime.js +62 -10
- package/dist/extensions/agent-browser/lib/session-page-state.js +14 -7
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/COMMAND_REFERENCE.md +65 -23
- package/docs/SUPPORT_MATRIX.md +15 -13
- package/docs/TOOL_CONTRACT.md +20 -20
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +36 -4
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +53 -10
- package/scripts/platform-smoke/doctor.mjs +67 -0
- package/scripts/platform-smoke/targets.mjs +2 -0
- package/scripts/platform-smoke.mjs +2 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, rm } from "node:fs/promises";
|
|
2
|
-
import { isCloseCommand, isOpenNavigationCommand } from "../../command-taxonomy.js";
|
|
2
|
+
import { isCloseCommand, isNavigationObservableCommandName, isOpenNavigationCommand } from "../../command-taxonomy.js";
|
|
3
3
|
import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
|
|
4
4
|
import { getAllowedDomainsViolation, parseAllowedDomainsPolicyFromArgs } from "../../navigation-policy.js";
|
|
5
5
|
import { analyzeNetworkSourceLookupResults, analyzeQaPresetResults, analyzeQaPresetTimeout, analyzeSourceLookupResults, buildQaCompactPassText, extractQaPageContext, redactNetworkSourceLookupAnalysis, } from "../../input-modes.js";
|
|
@@ -11,9 +11,9 @@ import { commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLate
|
|
|
11
11
|
import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../../temp.js";
|
|
12
12
|
import { isRecord } from "../../parsing.js";
|
|
13
13
|
import { createFreshSessionName, extractCommandTokens, hasLaunchScopedTabCorrectionFlag, resolveManagedSessionState } from "../../runtime.js";
|
|
14
|
-
import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, closeManagedSession, collectOpenResultTabCorrection, collectSessionTabSelection, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, unwrapPinnedSessionBatchEnvelope, updateTraceOwnerState, } from "./session-state.js";
|
|
14
|
+
import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, closeManagedSession, collectOpenResultTabCorrection, collectSessionTabSelection, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, unwrapPinnedSessionBatchEnvelope, updateTraceOwnerState, } from "./session-state.js";
|
|
15
15
|
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
16
|
-
import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext,
|
|
16
|
+
import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
|
|
17
17
|
import { repairScreenshotData } from "./prepare.js";
|
|
18
18
|
import { getPersistentSessionArtifactStore } from "./session-artifacts.js";
|
|
19
19
|
import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveText, redactExactSensitiveValue, } from "./final-result.js";
|
|
@@ -133,6 +133,7 @@ export async function processBrowserOutput(input) {
|
|
|
133
133
|
let managedSessionActive = state.managedSessionActive;
|
|
134
134
|
let managedSessionCwd = state.managedSessionCwd;
|
|
135
135
|
let managedSessionName = state.managedSessionName;
|
|
136
|
+
let managedSessionNamespace = state.managedSessionNamespace;
|
|
136
137
|
let networkRoutesBySession = state.networkRoutesBySession;
|
|
137
138
|
try {
|
|
138
139
|
const persistentArtifactStore = getPersistentSessionArtifactStore(ctx);
|
|
@@ -164,26 +165,30 @@ export async function processBrowserOutput(input) {
|
|
|
164
165
|
const envelopeSuccess = plainTextInspection ? true : presentationEnvelope?.success !== false;
|
|
165
166
|
let succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
|
|
166
167
|
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
167
|
-
|
|
168
|
+
const sessionStateKey = getSessionContextKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
|
|
169
|
+
updateTraceOwnerState({ command: prepared.executionPlan.commandInfo.command, sessionName: sessionStateKey, subcommand: prepared.executionPlan.commandInfo.subcommand, succeeded, traceOwners });
|
|
168
170
|
let clickDispatchDiagnostic;
|
|
169
171
|
if (succeeded && prepared.clickDispatchProbe) {
|
|
170
|
-
clickDispatchDiagnostic = await collectClickDispatchDiagnostic({ cwd, probe: prepared.clickDispatchProbe, sessionName: prepared.executionPlan.sessionName, signal });
|
|
172
|
+
clickDispatchDiagnostic = await collectClickDispatchDiagnostic({ cwd, namespace: prepared.executionPlan.namespace, probe: prepared.clickDispatchProbe, sessionName: prepared.executionPlan.sessionName, signal });
|
|
171
173
|
if (clickDispatchDiagnostic) {
|
|
172
174
|
succeeded = false;
|
|
173
175
|
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: clickDispatchDiagnostic.summary, success: false };
|
|
174
176
|
}
|
|
175
177
|
}
|
|
178
|
+
const presentationDataRecord = isRecord(presentationEnvelope?.data) ? presentationEnvelope.data : undefined;
|
|
179
|
+
const dataClicked = typeof presentationDataRecord?.clicked === "string" ? presentationDataRecord.clicked : undefined;
|
|
180
|
+
const cssClickWithoutHref = prepared.executionPlan.commandInfo.command === "click" && dataClicked !== undefined && !dataClicked.startsWith("@") && !dataClicked.startsWith("ref=") && typeof presentationDataRecord?.href !== "string";
|
|
176
181
|
const parsedAllowedDomainsPolicy = parseAllowedDomainsPolicyFromArgs(prepared.runtimeToolArgs);
|
|
177
|
-
const sessionAllowedDomainsPolicy =
|
|
178
|
-
? parsedAllowedDomainsPolicy ?? allowedDomainsBySession.get(
|
|
182
|
+
const sessionAllowedDomainsPolicy = sessionStateKey
|
|
183
|
+
? parsedAllowedDomainsPolicy ?? allowedDomainsBySession.get(sessionStateKey)
|
|
179
184
|
: parsedAllowedDomainsPolicy;
|
|
180
|
-
const shouldCaptureAllowedDomainNavigationSummary = prepared.executionPlan.commandInfo.command === "batch"
|
|
185
|
+
const shouldCaptureAllowedDomainNavigationSummary = sessionAllowedDomainsPolicy !== undefined && !cssClickWithoutHref && (prepared.executionPlan.commandInfo.command === "batch" || isNavigationObservableCommandName(prepared.executionPlan.commandInfo.command));
|
|
181
186
|
if (succeeded &&
|
|
182
187
|
!navigationSummary &&
|
|
183
188
|
(shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data) ||
|
|
184
189
|
shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data) ||
|
|
185
190
|
shouldCaptureAllowedDomainNavigationSummary)) {
|
|
186
|
-
navigationSummary = await collectNavigationSummary({ cwd, sessionName: prepared.executionPlan.sessionName, signal });
|
|
191
|
+
navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
|
|
187
192
|
}
|
|
188
193
|
if (navigationSummary && presentationEnvelope && !Array.isArray(presentationEnvelope.data))
|
|
189
194
|
presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
|
|
@@ -192,9 +197,9 @@ export async function processBrowserOutput(input) {
|
|
|
192
197
|
if (succeeded && prepared.executionPlan.sessionName && hasLaunchScopedTabCorrectionFlag(prepared.runtimeToolArgs) && isOpenNavigationCommand(prepared.executionPlan.commandInfo.command)) {
|
|
193
198
|
const targetTitle = extractStringResultField(presentationEnvelope?.data, "title");
|
|
194
199
|
const targetUrl = extractStringResultField(presentationEnvelope?.data, "url");
|
|
195
|
-
const plannedTabCorrection = await collectOpenResultTabCorrection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, targetTitle, targetUrl });
|
|
200
|
+
const plannedTabCorrection = await collectOpenResultTabCorrection({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal, targetTitle, targetUrl });
|
|
196
201
|
if (plannedTabCorrection)
|
|
197
|
-
openResultTabCorrection = await applyOpenResultTabCorrection({ correction: plannedTabCorrection, cwd, sessionName: prepared.executionPlan.sessionName, signal });
|
|
202
|
+
openResultTabCorrection = await applyOpenResultTabCorrection({ correction: plannedTabCorrection, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
|
|
198
203
|
}
|
|
199
204
|
const observedSessionTabTarget = normalizeSessionTabTarget(navigationSummary) ?? extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) ?? extractSessionTabTargetFromCommandData(prepared.commandTokens, presentationEnvelope?.data);
|
|
200
205
|
let currentSessionTabTarget = deriveSessionTabTarget({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary, previousTarget: prepared.priorSessionTabTarget, subcommand: prepared.executionPlan.commandInfo.subcommand });
|
|
@@ -207,8 +212,8 @@ export async function processBrowserOutput(input) {
|
|
|
207
212
|
let sessionTabCorrection = prepared.sessionTabCorrection;
|
|
208
213
|
if (shouldTreatAboutBlankAsMismatch && prepared.priorSessionTabTarget) {
|
|
209
214
|
const aboutBlankObservedTarget = observedSessionTabTarget ?? currentSessionTabTarget;
|
|
210
|
-
const aboutBlankRecovery = await collectSessionTabSelection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, target: prepared.priorSessionTabTarget });
|
|
211
|
-
const appliedAboutBlankRecovery = aboutBlankRecovery ? await applyOpenResultTabCorrection({ correction: aboutBlankRecovery, cwd, sessionName: prepared.executionPlan.sessionName, signal }) : undefined;
|
|
215
|
+
const aboutBlankRecovery = await collectSessionTabSelection({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal, target: prepared.priorSessionTabTarget });
|
|
216
|
+
const appliedAboutBlankRecovery = aboutBlankRecovery ? await applyOpenResultTabCorrection({ correction: aboutBlankRecovery, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal }) : undefined;
|
|
212
217
|
if (appliedAboutBlankRecovery) {
|
|
213
218
|
sessionTabCorrection = appliedAboutBlankRecovery;
|
|
214
219
|
currentSessionTabTarget = prepared.priorSessionTabTarget;
|
|
@@ -223,16 +228,16 @@ export async function processBrowserOutput(input) {
|
|
|
223
228
|
}
|
|
224
229
|
}
|
|
225
230
|
if (succeeded && prepared.priorSessionTabTarget && !sessionTabCorrection && !aboutBlankSessionMismatch && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens) && observedSessionTabTarget && shouldCorrectSessionTabAfterCommand({ command: prepared.executionPlan.commandInfo.command, pinningRequired: prepared.sessionTabPinningReason !== undefined, sessionName: prepared.executionPlan.sessionName })) {
|
|
226
|
-
const postCommandTabCorrection = await collectSessionTabSelection({ cwd, sessionName: prepared.executionPlan.sessionName, signal, target: observedSessionTabTarget });
|
|
231
|
+
const postCommandTabCorrection = await collectSessionTabSelection({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal, target: observedSessionTabTarget });
|
|
227
232
|
if (postCommandTabCorrection) {
|
|
228
|
-
const appliedPostCommandCorrection = await applyOpenResultTabCorrection({ correction: postCommandTabCorrection, cwd, sessionName: prepared.executionPlan.sessionName, signal });
|
|
233
|
+
const appliedPostCommandCorrection = await applyOpenResultTabCorrection({ correction: postCommandTabCorrection, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
|
|
229
234
|
if (appliedPostCommandCorrection && !sessionTabCorrection)
|
|
230
235
|
sessionTabCorrection = appliedPostCommandCorrection;
|
|
231
236
|
}
|
|
232
237
|
}
|
|
233
|
-
if (succeeded &&
|
|
238
|
+
if (succeeded && sessionStateKey && parsedAllowedDomainsPolicy) {
|
|
234
239
|
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
235
|
-
allowedDomainsBySession.set(
|
|
240
|
+
allowedDomainsBySession.set(sessionStateKey, parsedAllowedDomainsPolicy);
|
|
236
241
|
}
|
|
237
242
|
const allowedDomainsViolation = succeeded ? getAllowedDomainsViolation({
|
|
238
243
|
policy: sessionAllowedDomainsPolicy,
|
|
@@ -257,10 +262,10 @@ export async function processBrowserOutput(input) {
|
|
|
257
262
|
let fillVerificationDiagnostic;
|
|
258
263
|
let selectorTextVisibilityDiagnostics = [];
|
|
259
264
|
let electronBroadGetTextScopeDiagnostics = [];
|
|
260
|
-
const timeoutPartialProgress = processResult.timedOut ? await collectTimeoutPartialProgress({ command: prepared.executionPlan.commandInfo.command, compiledJob: prepared.compiledJob, cwd, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
|
|
265
|
+
const timeoutPartialProgress = processResult.timedOut ? await collectTimeoutPartialProgress({ command: prepared.executionPlan.commandInfo.command, compiledJob: prepared.compiledJob, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin }) : undefined;
|
|
261
266
|
if (succeeded) {
|
|
262
267
|
const fillRefSnapshot = prepared.resolvedSemanticActionRefSnapshot ?? prepared.priorRefSnapshotState;
|
|
263
|
-
fillVerificationDiagnostic = await collectFillVerificationDiagnostic({ commandTokens: prepared.commandTokens, cwd, forceValueVerification: electronRecordForCommand !== undefined, refSnapshot: fillRefSnapshot, sessionName: prepared.executionPlan.sessionName, signal });
|
|
268
|
+
fillVerificationDiagnostic = await collectFillVerificationDiagnostic({ commandTokens: prepared.commandTokens, cwd, forceValueVerification: electronRecordForCommand !== undefined, namespace: prepared.executionPlan.namespace, refSnapshot: fillRefSnapshot, sessionName: prepared.executionPlan.sessionName, signal });
|
|
264
269
|
}
|
|
265
270
|
if (succeeded && electronRecordForCommand) {
|
|
266
271
|
electronRefFreshnessDiagnostic = buildElectronRefFreshnessDiagnostic({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, record: electronRecordForCommand, sessionName: prepared.executionPlan.sessionName, stdin: prepared.runtimeToolStdin });
|
|
@@ -269,45 +274,45 @@ export async function processBrowserOutput(input) {
|
|
|
269
274
|
overlayBlockerDiagnostic = collectSnapshotOverlayBlockerDiagnostic(presentationEnvelope?.data);
|
|
270
275
|
}
|
|
271
276
|
if (succeeded && !overlayBlockerDiagnostic && !sessionTabCorrection && !aboutBlankSessionMismatch && !electronRecordForCommand && !clickDispatchDiagnostic)
|
|
272
|
-
overlayBlockerDiagnostic = await collectOverlayBlockerDiagnostic({ command: prepared.executionPlan.commandInfo.command, cwd, data: presentationEnvelope?.data, navigationSummary, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
|
|
277
|
+
overlayBlockerDiagnostic = await collectOverlayBlockerDiagnostic({ command: prepared.executionPlan.commandInfo.command, cwd, data: presentationEnvelope?.data, namespace: prepared.executionPlan.namespace, navigationSummary, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
|
|
273
278
|
if (succeeded) {
|
|
274
|
-
selectorTextVisibilityDiagnostics = await collectSelectorTextVisibilityDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, cwd, data: presentationEnvelope?.data, sessionName: prepared.executionPlan.sessionName, signal });
|
|
279
|
+
selectorTextVisibilityDiagnostics = await collectSelectorTextVisibilityDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, cwd, data: presentationEnvelope?.data, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
|
|
275
280
|
if (electronRecordForCommand)
|
|
276
281
|
electronBroadGetTextScopeDiagnostics = collectElectronBroadGetTextScopeDiagnostics({ commandInfo: prepared.executionPlan.commandInfo, commandTokens: prepared.commandTokens, currentTarget: currentSessionTabTarget, data: presentationEnvelope?.data, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName });
|
|
277
282
|
}
|
|
278
|
-
const activeNetworkRoutes =
|
|
283
|
+
const activeNetworkRoutes = sessionStateKey ? networkRoutesBySession.get(sessionStateKey) : undefined;
|
|
279
284
|
const networkRouteDiagnostics = succeeded && prepared.executionPlan.commandInfo.command === "network" && prepared.executionPlan.commandInfo.subcommand === "requests" && prepared.executionPlan.sessionName
|
|
280
285
|
? buildNetworkRouteDiagnostics(presentationEnvelope?.data, activeNetworkRoutes)
|
|
281
286
|
: undefined;
|
|
282
|
-
networkRoutesBySession = applyNetworkRouteState({ commandTokens: prepared.commandTokens, routesBySession: networkRoutesBySession, sessionName:
|
|
283
|
-
const comboboxFocusDiagnostic = succeeded ? await collectComboboxFocusDiagnostic({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, cwd, semanticAction: prepared.compiledSemanticAction, sessionName: prepared.executionPlan.sessionName, signal }) : undefined;
|
|
287
|
+
networkRoutesBySession = applyNetworkRouteState({ commandTokens: prepared.commandTokens, routesBySession: networkRoutesBySession, sessionName: sessionStateKey, succeeded });
|
|
288
|
+
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;
|
|
284
289
|
const recordingDependencyWarning = await collectRecordingDependencyWarning({ command: prepared.executionPlan.commandInfo.command, commandTokens: prepared.commandTokens, succeeded });
|
|
285
|
-
const scrollNoopDiagnostic = succeeded && prepared.shouldProbeScrollNoop ? buildScrollNoopDiagnostic(prepared.scrollPositionBefore, await collectScrollPositionSnapshot({ cwd, sessionName: prepared.executionPlan.sessionName, signal })) : undefined;
|
|
290
|
+
const scrollNoopDiagnostic = succeeded && prepared.shouldProbeScrollNoop ? buildScrollNoopDiagnostic(prepared.scrollPositionBefore, await collectScrollPositionSnapshot({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal })) : undefined;
|
|
286
291
|
let currentRefSnapshot;
|
|
287
292
|
let currentRefSnapshotInvalidation;
|
|
288
293
|
const batchRefSnapshotState = prepared.executionPlan.commandInfo.command === "batch" ? extractLatestRefSnapshotStateFromBatchResults(presentationEnvelope?.data) : undefined;
|
|
289
|
-
if (
|
|
294
|
+
if (sessionStateKey) {
|
|
290
295
|
if (isCloseCommand(prepared.executionPlan.commandInfo.command) && succeeded) {
|
|
291
296
|
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
292
|
-
allowedDomainsBySession.delete(
|
|
297
|
+
allowedDomainsBySession.delete(sessionStateKey);
|
|
293
298
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
294
|
-
networkRoutesBySession.delete(
|
|
295
|
-
sessionPageState.clearSession(
|
|
296
|
-
state.closedManagedSessionNames.add(
|
|
299
|
+
networkRoutesBySession.delete(sessionStateKey);
|
|
300
|
+
sessionPageState.clearSession(sessionStateKey);
|
|
301
|
+
state.closedManagedSessionNames.add(sessionStateKey);
|
|
297
302
|
}
|
|
298
303
|
else if (currentSessionTabTarget) {
|
|
299
|
-
const tabUpdate = sessionPageState.applyTabTarget({ sessionName:
|
|
304
|
+
const tabUpdate = sessionPageState.applyTabTarget({ sessionName: sessionStateKey, target: currentSessionTabTarget, update: sessionPageStateUpdate });
|
|
300
305
|
if (!tabUpdate.applied && succeeded)
|
|
301
|
-
sessionPageState.markPinning(
|
|
306
|
+
sessionPageState.markPinning(sessionStateKey, "drift");
|
|
302
307
|
}
|
|
303
308
|
const refSnapshot = prepared.executionPlan.commandInfo.command === "batch" ? batchRefSnapshotState?.snapshot : succeeded ? prepared.executionPlan.commandInfo.command === "snapshot" ? extractRefSnapshotFromData(presentationEnvelope?.data) : prepared.resolvedSemanticActionRefSnapshot ?? overlayBlockerDiagnostic?.snapshot : undefined;
|
|
304
309
|
if (refSnapshot) {
|
|
305
|
-
const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName:
|
|
310
|
+
const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName: sessionStateKey, snapshot: refSnapshot, update: sessionPageStateUpdate });
|
|
306
311
|
currentRefSnapshot = refUpdate.refSnapshot;
|
|
307
312
|
currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
|
|
308
313
|
}
|
|
309
314
|
else {
|
|
310
|
-
const stateView = sessionPageState.get(
|
|
315
|
+
const stateView = sessionPageState.get(sessionStateKey);
|
|
311
316
|
currentRefSnapshot = stateView.refSnapshot;
|
|
312
317
|
currentRefSnapshotInvalidation = stateView.refSnapshotInvalidation;
|
|
313
318
|
}
|
|
@@ -315,40 +320,47 @@ export async function processBrowserOutput(input) {
|
|
|
315
320
|
const priorManagedSessionActive = managedSessionActive;
|
|
316
321
|
const priorManagedSessionCwd = managedSessionCwd;
|
|
317
322
|
const priorManagedSessionName = managedSessionName;
|
|
323
|
+
const priorManagedSessionNamespace = managedSessionNamespace;
|
|
318
324
|
const commandClosesSession = isCloseCommand(prepared.executionPlan.commandInfo.command);
|
|
319
|
-
const
|
|
325
|
+
const closeTargetsPriorManagedNamespace = prepared.executionPlan.namespace === priorManagedSessionNamespace;
|
|
326
|
+
const managedCloseSessionName = commandClosesSession && succeeded && prepared.executionPlan.sessionName === priorManagedSessionName && closeTargetsPriorManagedNamespace
|
|
320
327
|
? prepared.executionPlan.sessionName
|
|
321
328
|
: prepared.executionPlan.managedSessionName;
|
|
322
329
|
const policyBlockedFreshManagedSession = allowedDomainsViolation !== undefined && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
|
|
323
330
|
const postLaunchBatchFailure = !succeeded && processSucceeded && parseSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && batchStartedManagedBrowser(presentationEnvelope?.data);
|
|
324
331
|
const postLaunchTimeoutWithPage = !succeeded && processResult.timedOut && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && timeoutPartialProgress?.liveUrlRecovered === true;
|
|
325
332
|
const managedTransitionSucceeded = succeeded || policyBlockedFreshManagedSession || postLaunchBatchFailure || postLaunchTimeoutWithPage;
|
|
326
|
-
const managedSessionState = resolveManagedSessionState({ command: prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, priorActive: priorManagedSessionActive, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
|
|
333
|
+
const managedSessionState = resolveManagedSessionState({ command: prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, managedSessionNamespace: prepared.executionPlan.namespace, priorActive: priorManagedSessionActive, priorNamespace: priorManagedSessionNamespace, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
|
|
327
334
|
const replacedManagedSessionName = managedSessionState.replacedSessionName;
|
|
328
335
|
managedSessionActive = managedSessionState.active;
|
|
329
336
|
managedSessionName = managedSessionState.sessionName;
|
|
337
|
+
managedSessionNamespace = managedSessionState.namespace;
|
|
330
338
|
if (commandClosesSession && succeeded && managedCloseSessionName === priorManagedSessionName && !managedSessionActive) {
|
|
331
339
|
freshSessionOrdinal += 1;
|
|
332
340
|
managedSessionName = createFreshSessionName(state.managedSessionBaseName, state.ephemeralSessionSeed, freshSessionOrdinal);
|
|
341
|
+
managedSessionNamespace = undefined;
|
|
333
342
|
}
|
|
334
|
-
let managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: managedSessionActive, activeBefore: priorManagedSessionActive, attemptedSessionName: managedCloseSessionName, command: prepared.executionPlan.commandInfo.command, currentSessionName: managedSessionName, previousSessionName: priorManagedSessionName, replacedSessionName: replacedManagedSessionName, sessionMode: prepared.sessionMode, succeeded: managedTransitionSucceeded });
|
|
335
|
-
if (prepared.executionPlan.managedSessionName && succeeded)
|
|
343
|
+
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 });
|
|
344
|
+
if (prepared.executionPlan.managedSessionName && succeeded && managedSessionActive) {
|
|
336
345
|
managedSessionCwd = cwd;
|
|
337
|
-
|
|
346
|
+
managedSessionNamespace = prepared.executionPlan.namespace;
|
|
347
|
+
}
|
|
348
|
+
if (sessionStateKey && succeeded) {
|
|
338
349
|
if (openResultTabCorrection || sessionTabCorrection || aboutBlankSessionMismatch?.recoveryApplied)
|
|
339
|
-
sessionPageState.markPinning(
|
|
350
|
+
sessionPageState.markPinning(sessionStateKey, "drift");
|
|
340
351
|
else if (prepared.sessionTabPinningReason === "restore")
|
|
341
|
-
sessionPageState.clearRestorePinning(
|
|
352
|
+
sessionPageState.clearRestorePinning(sessionStateKey);
|
|
342
353
|
}
|
|
343
354
|
if (replacedManagedSessionName) {
|
|
344
355
|
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
345
|
-
|
|
356
|
+
const replacedSessionStateKey = getSessionContextKey(replacedManagedSessionName, priorManagedSessionNamespace);
|
|
357
|
+
allowedDomainsBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
346
358
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
347
|
-
networkRoutesBySession.delete(replacedManagedSessionName);
|
|
348
|
-
sessionPageState.clearSession(replacedManagedSessionName);
|
|
349
|
-
const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
359
|
+
networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
360
|
+
sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
361
|
+
const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, namespace: priorManagedSessionNamespace, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
350
362
|
if (!replacedCloseError)
|
|
351
|
-
state.closedManagedSessionNames.add(replacedManagedSessionName);
|
|
363
|
+
state.closedManagedSessionNames.add(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
352
364
|
}
|
|
353
365
|
let electronLaunchRecord;
|
|
354
366
|
let electronFailedConnectCleanup = prepared.electronFailedConnectCleanup;
|
|
@@ -360,18 +372,18 @@ export async function processBrowserOutput(input) {
|
|
|
360
372
|
electronChildProcesses.set(electronLaunchRecord.launchId, prepared.electronLaunch.child);
|
|
361
373
|
const electronHandoffMode = prepared.compiledElectron?.action === "launch" ? prepared.compiledElectron.handoff : "connect";
|
|
362
374
|
try {
|
|
363
|
-
electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, sessionName: prepared.executionPlan.sessionName, signal });
|
|
375
|
+
electronHandoff = await collectElectronHandoff({ cwd, handoff: electronHandoffMode, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal });
|
|
364
376
|
}
|
|
365
377
|
catch (error) {
|
|
366
378
|
electronHandoff = { error: error instanceof Error ? error.message : String(error), handoff: electronHandoffMode };
|
|
367
379
|
}
|
|
368
380
|
if (electronHandoff?.refSnapshot) {
|
|
369
|
-
const refUpdate = sessionPageState.applyRefSnapshot({ sessionName: prepared.executionPlan.sessionName, snapshot: electronHandoff.refSnapshot, update: sessionPageStateUpdate });
|
|
381
|
+
const refUpdate = sessionPageState.applyRefSnapshot({ sessionName: sessionStateKey ?? prepared.executionPlan.sessionName ?? "", snapshot: electronHandoff.refSnapshot, update: sessionPageStateUpdate });
|
|
370
382
|
currentRefSnapshot = refUpdate.refSnapshot;
|
|
371
383
|
currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
|
|
372
384
|
if (electronHandoff.refSnapshot.target) {
|
|
373
385
|
currentSessionTabTarget = electronHandoff.refSnapshot.target;
|
|
374
|
-
sessionPageState.applyTabTarget({ sessionName: prepared.executionPlan.sessionName, target: electronHandoff.refSnapshot.target, update: sessionPageStateUpdate });
|
|
386
|
+
sessionPageState.applyTabTarget({ sessionName: sessionStateKey ?? prepared.executionPlan.sessionName ?? "", target: electronHandoff.refSnapshot.target, update: sessionPageStateUpdate });
|
|
375
387
|
}
|
|
376
388
|
}
|
|
377
389
|
}
|
|
@@ -387,8 +399,8 @@ export async function processBrowserOutput(input) {
|
|
|
387
399
|
if (presentationEnvelope?.error !== undefined)
|
|
388
400
|
presentationEnvelope = { ...presentationEnvelope, error: redactClipboardPermissionErrorValue(prepared.executionPlan.commandInfo, presentationEnvelope.error, clipboardWritePayloadCandidates) };
|
|
389
401
|
}
|
|
390
|
-
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, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
|
|
391
|
-
networkRoutesBySession = applyBatchNetworkRouteState({ data: presentationEnvelope?.data, routesBySession: networkRoutesBySession, sessionName:
|
|
402
|
+
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 });
|
|
403
|
+
networkRoutesBySession = applyBatchNetworkRouteState({ data: presentationEnvelope?.data, routesBySession: networkRoutesBySession, sessionName: sessionStateKey, succeeded });
|
|
392
404
|
if (presentation.resultCategory === "failure" && succeeded) {
|
|
393
405
|
succeeded = false;
|
|
394
406
|
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: presentation.summary, success: false };
|
|
@@ -418,7 +430,7 @@ export async function processBrowserOutput(input) {
|
|
|
418
430
|
? (processResult.timedOut ? analyzeQaPresetTimeout(prepared.compiledQaPreset) ?? analyzeQaPresetResults(presentationEnvelope?.data, prepared.compiledQaPreset) : analyzeQaPresetResults(presentationEnvelope?.data, prepared.compiledQaPreset))
|
|
419
431
|
: undefined;
|
|
420
432
|
let qaAttachedTarget = prepared.compiledQaPreset?.checks.attached
|
|
421
|
-
? await collectQaAttachedTarget({ currentTarget: currentSessionTabTarget ?? prepared.priorSessionTabTarget, cwd, sessionName: prepared.executionPlan.sessionName, signal })
|
|
433
|
+
? await collectQaAttachedTarget({ currentTarget: currentSessionTabTarget ?? prepared.priorSessionTabTarget, cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal })
|
|
422
434
|
: undefined;
|
|
423
435
|
const sourceLookupElectronContext = prepared.compiledSourceLookup ? getSourceLookupElectronContext({ currentTarget: currentSessionTabTarget, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName }) : undefined;
|
|
424
436
|
const sourceLookup = prepared.compiledSourceLookup ? await analyzeSourceLookupResults(presentationEnvelope?.data, prepared.compiledSourceLookup, cwd, { electronContext: sourceLookupElectronContext, workspaceRoot: cwd }) : undefined;
|
|
@@ -469,7 +481,7 @@ export async function processBrowserOutput(input) {
|
|
|
469
481
|
if (managedSessionOutcome && managedSessionOutcome.succeeded !== succeeded)
|
|
470
482
|
managedSessionOutcome = { ...managedSessionOutcome, succeeded };
|
|
471
483
|
const evalNavigationSummary = navigationSummary ?? extractNavigationSummaryFromData(presentationEnvelope?.data);
|
|
472
|
-
const evalSessionTabUrl =
|
|
484
|
+
const evalSessionTabUrl = sessionStateKey ? sessionPageState.get(sessionStateKey).tabTarget?.url : undefined;
|
|
473
485
|
const evalPageUrl = evalNavigationSummary?.url ?? currentSessionTabTarget?.url ?? prepared.priorSessionTabTarget?.url ?? evalSessionTabUrl;
|
|
474
486
|
const evalStdinHint = getEvalStdinHint({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, stdin: prepared.runtimeToolStdin });
|
|
475
487
|
const evalResultWarning = getEvalResultWarning({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, navigationSummary: evalNavigationSummary, pageUrl: evalPageUrl, stdin: prepared.runtimeToolStdin });
|
|
@@ -481,7 +493,7 @@ export async function processBrowserOutput(input) {
|
|
|
481
493
|
currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
|
|
482
494
|
currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
|
|
483
495
|
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, 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, managedSessionOutcome, 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 });
|
|
484
|
-
const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCwd, managedSessionName, networkRoutesBySession };
|
|
496
|
+
const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
|
|
485
497
|
return { result, statePatch };
|
|
486
498
|
}
|
|
487
499
|
finally {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
2
|
import { runAgentBrowserProcess } from "../../process.js";
|
|
3
3
|
import { buildAgentBrowserNextActions, getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results.js";
|
|
4
|
-
import { buildNextToolAction, withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
5
|
-
import { isAboutBlankUrl, normalizeComparableUrl, normalizeSessionTabTarget, targetsMatch, } from "../../session-page-state.js";
|
|
4
|
+
import { buildNextToolAction, withOptionalNamespaceArgs, withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
5
|
+
import { getSessionPageStateKey, isAboutBlankUrl, normalizeComparableUrl, normalizeSessionTabTarget, targetsMatch, } from "../../session-page-state.js";
|
|
6
6
|
import { isCloseCommand, isElectronPostCommandHealthCommand, isNavigationObservableCommandName, isRefGuardedCommand, isRefInvalidatingBatchCommand, isSessionTabPinningExcludedCommand, isSessionTabPostCommandCorrectionExcludedCommand, } from "../../command-taxonomy.js";
|
|
7
7
|
import { chooseOpenResultTabCorrection, redactInvocationArgs } from "../../runtime.js";
|
|
8
8
|
import { isRecord } from "../../parsing.js";
|
|
@@ -23,14 +23,17 @@ export function applyBrowserRunStatePatch(state, patch) {
|
|
|
23
23
|
state.managedSessionCwd = patch.managedSessionCwd;
|
|
24
24
|
if (patch.managedSessionName !== undefined)
|
|
25
25
|
state.managedSessionName = patch.managedSessionName;
|
|
26
|
+
if ("managedSessionNamespace" in patch)
|
|
27
|
+
state.managedSessionNamespace = patch.managedSessionNamespace;
|
|
26
28
|
if (patch.networkRoutesBySession)
|
|
27
29
|
state.networkRoutesBySession = patch.networkRoutesBySession;
|
|
28
30
|
}
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
+
export const getSessionContextKey = getSessionPageStateKey;
|
|
32
|
+
export function buildSessionDetailFields(sessionName, usedImplicitSession, namespace) {
|
|
33
|
+
return { ...(namespace ? { namespace } : {}), ...(sessionName ? { sessionName, usedImplicitSession } : {}) };
|
|
31
34
|
}
|
|
32
35
|
export function buildManagedSessionOutcome(options) {
|
|
33
|
-
const { activeAfter, activeBefore, attemptedSessionName, command, currentSessionName, previousSessionName, replacedSessionName, sessionMode, succeeded } = options;
|
|
36
|
+
const { activeAfter, activeBefore, attemptedSessionName, command, currentSessionName, currentSessionNamespace, previousSessionName, replacedSessionName, replacedSessionNamespace, sessionMode, succeeded } = options;
|
|
34
37
|
if (!attemptedSessionName)
|
|
35
38
|
return undefined;
|
|
36
39
|
let status;
|
|
@@ -74,8 +77,10 @@ export function buildManagedSessionOutcome(options) {
|
|
|
74
77
|
activeBefore,
|
|
75
78
|
attemptedSessionName,
|
|
76
79
|
currentSessionName,
|
|
80
|
+
...(currentSessionNamespace ? { currentSessionNamespace } : {}),
|
|
77
81
|
previousSessionName,
|
|
78
82
|
replacedSessionName,
|
|
83
|
+
replacedSessionNamespace,
|
|
79
84
|
sessionMode,
|
|
80
85
|
status,
|
|
81
86
|
succeeded,
|
|
@@ -145,13 +150,14 @@ export function buildManagedSessionFreshFailureNextActions(outcome) {
|
|
|
145
150
|
}
|
|
146
151
|
if ((outcome.status === "preserved" || isFreshPostLaunchFailure(outcome)) && outcome.activeAfter && outcome.currentSessionName) {
|
|
147
152
|
const sessionLabel = isFreshPostLaunchFailure(outcome) ? "current managed session" : "preserved managed session";
|
|
153
|
+
const currentSessionArgs = (args) => withOptionalNamespaceArgs(outcome.currentSessionNamespace, withOptionalSessionArgs(outcome.currentSessionName, args));
|
|
148
154
|
actions.push(buildNextToolAction({
|
|
149
|
-
args:
|
|
155
|
+
args: currentSessionArgs(["get", "url"]),
|
|
150
156
|
id: "verify-current-managed-session",
|
|
151
157
|
reason: `Confirm the ${sessionLabel} before continuing with sessionMode auto.`,
|
|
152
158
|
safety: `Read-only URL check on the ${sessionLabel}.`,
|
|
153
159
|
}), buildNextToolAction({
|
|
154
|
-
args:
|
|
160
|
+
args: currentSessionArgs(["snapshot", "-i"]),
|
|
155
161
|
id: "snapshot-current-managed-session",
|
|
156
162
|
reason: `Refresh interactive refs on the ${sessionLabel} before retrying the workflow.`,
|
|
157
163
|
safety: "Read-only snapshot; no navigation.",
|
|
@@ -218,6 +224,8 @@ export function extractNavigationSummaryFromData(data) {
|
|
|
218
224
|
return title || url ? { title, url } : undefined;
|
|
219
225
|
}
|
|
220
226
|
export function shouldCaptureNavigationSummary(command, data) {
|
|
227
|
+
if (isRecord(data) && typeof data.clicked === "string" && !data.clicked.startsWith("@") && !data.clicked.startsWith("ref=") && typeof data.href !== "string")
|
|
228
|
+
return false;
|
|
221
229
|
return (isNavigationObservableCommandName(command) &&
|
|
222
230
|
(!isRecord(data) || (typeof data.title !== "string" && typeof data.url !== "string")));
|
|
223
231
|
}
|
|
@@ -456,11 +464,11 @@ export function unwrapPinnedSessionBatchEnvelope(options) {
|
|
|
456
464
|
};
|
|
457
465
|
}
|
|
458
466
|
export async function runSessionCommandData(options) {
|
|
459
|
-
const { args, cwd, sessionName, signal, stdin, timeoutMs } = options;
|
|
467
|
+
const { args, cwd, namespace, sessionName, signal, stdin, timeoutMs } = options;
|
|
460
468
|
if (!sessionName)
|
|
461
469
|
return undefined;
|
|
462
470
|
const processResult = await runAgentBrowserProcess({
|
|
463
|
-
args: ["--json", "--session", sessionName, ...args],
|
|
471
|
+
args: ["--json", ...(namespace ? ["--namespace", namespace] : []), "--session", sessionName, ...args],
|
|
464
472
|
cwd,
|
|
465
473
|
signal,
|
|
466
474
|
stdin,
|
|
@@ -486,8 +494,8 @@ export async function runSessionCommandData(options) {
|
|
|
486
494
|
}
|
|
487
495
|
}
|
|
488
496
|
export async function collectOpenResultTabCorrection(options) {
|
|
489
|
-
const { cwd, sessionName, signal, targetTitle, targetUrl } = options;
|
|
490
|
-
const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, sessionName, signal });
|
|
497
|
+
const { cwd, namespace, sessionName, signal, targetTitle, targetUrl } = options;
|
|
498
|
+
const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, namespace, sessionName, signal });
|
|
491
499
|
if (!isRecord(tabData) || !Array.isArray(tabData.tabs)) {
|
|
492
500
|
return undefined;
|
|
493
501
|
}
|
|
@@ -502,8 +510,8 @@ export async function collectOpenResultTabCorrection(options) {
|
|
|
502
510
|
return chooseOpenResultTabCorrection({ tabs, targetTitle, targetUrl });
|
|
503
511
|
}
|
|
504
512
|
export async function collectSessionTabSelection(options) {
|
|
505
|
-
const { cwd, sessionName, signal, target } = options;
|
|
506
|
-
const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, sessionName, signal });
|
|
513
|
+
const { cwd, namespace, sessionName, signal, target } = options;
|
|
514
|
+
const tabData = await runSessionCommandData({ args: ["tab", "list"], cwd, namespace, sessionName, signal });
|
|
507
515
|
if (!isRecord(tabData) || !Array.isArray(tabData.tabs)) {
|
|
508
516
|
return undefined;
|
|
509
517
|
}
|
|
@@ -518,10 +526,11 @@ export async function collectSessionTabSelection(options) {
|
|
|
518
526
|
return selectSessionTargetTab({ tabs, target });
|
|
519
527
|
}
|
|
520
528
|
export async function applyOpenResultTabCorrection(options) {
|
|
521
|
-
const { correction, cwd, sessionName, signal } = options;
|
|
529
|
+
const { correction, cwd, namespace, sessionName, signal } = options;
|
|
522
530
|
const result = await runSessionCommandData({
|
|
523
531
|
args: ["tab", correction.selectedTab],
|
|
524
532
|
cwd,
|
|
533
|
+
namespace,
|
|
525
534
|
sessionName,
|
|
526
535
|
signal,
|
|
527
536
|
});
|
|
@@ -698,7 +707,7 @@ export async function closeManagedSession(options) {
|
|
|
698
707
|
const controller = new AbortController();
|
|
699
708
|
const timer = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
700
709
|
let stdoutSpillPath;
|
|
701
|
-
const closeArgs = ["--session", options.sessionName, "close"];
|
|
710
|
+
const closeArgs = [...(options.namespace ? ["--namespace", options.namespace] : []), "--session", options.sessionName, "close"];
|
|
702
711
|
try {
|
|
703
712
|
const processResult = await runAgentBrowserProcess({
|
|
704
713
|
args: closeArgs,
|
|
@@ -19,7 +19,7 @@ export const QUICK_START_GUIDELINES = [
|
|
|
19
19
|
"Common first calls (first-call recipe): { args: [\"open\", \"<url>\"] } → { args: [\"snapshot\", \"-i\"] } → { args: [\"click\", \"@eN\"] } or { args: [\"fill\", \"@eN\", \"<text>\"] } using @refs and visible labels from that snapshot, then { args: [\"snapshot\", \"-i\"] } after navigation or DOM changes. On https://example.com/ the main link label is Learn more (use exact snapshot text, not guessed link copy).",
|
|
20
20
|
"Locator-first clicks/fills and native select changes without hand-building argv: { semanticAction: { action: \"click\", locator: \"text\", value: \"Close\" } }, { semanticAction: { action: \"fill\", locator: \"label\", value: \"Email\", text: \"user@example.com\" } }, direct current targets such as { semanticAction: { action: \"fill\", selector: \"@e1\", text: \"prompt\" } }, or { semanticAction: { action: \"select\", selector: \"#flavor\", value: \"chocolate\" } }; add semanticAction.session when targeting a named upstream browser session; details.compiledSemanticAction shows the semantic target, while details.effectiveArgs may show a resolved current @ref for active-session role/name click/check/fill actions to avoid hidden duplicate matches; semanticAction does not expose uncheck while upstream find ... uncheck is not runtime-supported, so use raw uncheck with a stable selector or current ref; selector-not-found failures may append bounded click try-*-candidate next actions or, for fill misses with current editable refs, details.richInputRecovery with focus/click actions that do not copy fill text; stale-ref failures can return retry-semantic-action-after-stale-ref for compiled find actions when retry safety is provable.",
|
|
21
21
|
`Common advanced calls: { args: ["batch"], stdin: "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }, { job: { steps: [{ action: "open", url: "https://example.com" }, { action: "assertText", text: "Example Domain" }, { action: "screenshot", path: ".dogfood/example.png" }] } }, { qa: { url: "https://example.com", expectedText: "Example Domain", screenshotPath: ".dogfood/qa-example.png" } } (example.com smoke only; elsewhere match exact visible text from snapshot -i), { electron: { action: "list", query: "code" } }, { electron: { action: "launch", appName: "Visual Studio Code", handoff: "snapshot" } }, { electron: { action: "probe" } }, { qa: { attached: true, expectedText: "Explorer" } }, { args: ["eval", "--stdin"], stdin: "document.title", outputPath: "logs/page-title.json" }, { args: ["auth", "save", "name", "--password-stdin"], stdin: "<password from user-approved secret source>" }, { args: ["--profile", "Default", "open", "https://example.com/account"], sessionMode: "fresh" }, and { args: ["open", "--enable", "react-devtools", "https://example.com"], sessionMode: "fresh" }. For app pages with a native dropdown, job steps can include { action: "select", selector: "#flavor", value: "chocolate" } before the dependent assertion; for locator-friendly pages, job click/fill steps can use semantic locator fields such as { action: "fill", locator: "role", role: "searchbox", name: "Search", text: "agent browser" }; for human-paced input, job type steps can use { action: "type", selector: "#prompt", text: "hello", delayMs: 20, press: "Enter" }; delayed typing is capped at 200 characters per step, and generated per-character rows are compacted in visible batch prose while full rows remain in details.batchSteps.`,
|
|
22
|
-
"Constrained job navigation is explicit only: click (and select/submit flows that may navigate) does not prove the next page loaded; add assertUrl and/or assertText after navigation-prone steps before screenshot or later interactions. Keep jobs short around navigation, click, and rerender boundaries on dynamic React/product apps; avoid a whole checkout in one job. If a long job times out and details.timeoutPartialProgress shows a mutating incomplete step, inspect current page state and continue with a shorter job or single action instead of blindly retrying the mutating step. Example: { job: { steps: [{ action: \"open\", url: \"https://shop.example/checkout\" }, { action: \"fill\", selector: \"#email\", text: \"user@example.com\" }, { action: \"click\", selector: \"#continue\" }, { action: \"assertUrl\", url: \"**/shipping\" }, { action: \"assertText\", text: \"Shipping address\" }, { action: \"screenshot\", path: \".dogfood/shipping.png\" }] } }. Top-level click may add
|
|
22
|
+
"Constrained job navigation is explicit only: click (and select/submit flows that may navigate) does not prove the next page loaded; add assertUrl and/or assertText after navigation-prone steps before screenshot or later interactions. Keep jobs short around navigation, click, and rerender boundaries on dynamic React/product apps; avoid a whole checkout in one job. If a long job times out and details.timeoutPartialProgress shows a mutating incomplete step, inspect current page state and continue with a shorter job or single action instead of blindly retrying the mutating step. Example: { job: { steps: [{ action: \"open\", url: \"https://shop.example/checkout\" }, { action: \"fill\", selector: \"#email\", text: \"user@example.com\" }, { action: \"click\", selector: \"#continue\" }, { action: \"assertUrl\", url: \"**/shipping\" }, { action: \"assertText\", text: \"Shipping address\" }, { action: \"screenshot\", path: \".dogfood/shipping.png\" }] } }. Top-level click may add pageChangeSummary hints, but job never auto-inserts post-click asserts.",
|
|
23
23
|
"High-value command reference: click <selector> --new-tab opens link-like targets in a new tab; select <selector> <value...> changes native dropdown values; scroll <dir> [px] --selector <sel>, wrapper-handled scroll <selector> <dir> [px|percent] targets nested scrollers, and wrapper-handled scroll to end/top targets document scrolling; download <selector> <path> saves a file triggered by a click; get title/url/text/html/value/attr/count reads page state; screenshot [selector] [path] captures a page or element image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab; react tree/inspect/renders/suspense introspect React after --enable react-devtools; vitals [url] measures Core Web Vitals; pushstate <url> performs SPA navigation; tap <selector> and swipe <direction> [distance] support iOS/provider touch flows.",
|
|
24
24
|
"For artifact-producing commands, read the visible artifact block and details.artifactVerification before using files: check requested path, absolute path, existence, size bytes, artifact kind, optional mediaType, status, optional limitation, and verified/missing/pending/unverified counts. details.artifacts contains per-file metadata; record start rows are pending/openRecording until record stop writes the target. The wrapper creates parent directories for direct artifact paths and can save simple loopback HTTP(S) anchor downloads directly to the requested path before upstream download fallback. Browser close does not delete explicit saved files; if close reports details.artifactCleanup, use host file tools to remove paths listed in explicitArtifactPaths (when non-empty) after inspection. If close fails with details.promptGuard.reason=requested-artifacts-missing-before-close, save the exact required artifact path before closing. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step; if annotation labels crowd a dense page, use a scoped or non-annotated screenshot plus snapshot refs instead.",
|
|
25
25
|
"When details.nextActions is present, prefer those exact native agent_browser follow-up payloads over prose guidance; they may include args, stdin, sessionMode, networkSourceLookup, safety notes, or artifactPath for saved files.",
|
|
@@ -45,13 +45,13 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
|
45
45
|
"For Electron desktop apps, prefer top-level electron for wrapper-owned discovery, isolated launch, status, compact probe, and cleanup: list first, treat likely-sensitive annotations as hints rather than enforcement, launch with the default snapshot handoff unless handoff: \"tabs\" is the safer diagnostic starting point, use electron.probe or snapshot -i/qa.attached for current-session state, and always cleanup the returned launchId when done. electron.launch uses an isolated temporary profile; it does not reuse the app's normal signed-in profile or attach to an already-running authenticated app. For signed-in local app state, host-launch the normal app with --remote-debugging-port when appropriate, then use raw args connect <port|url>; after connect, inspect tab list, select the stable tab id such as tab t2, then run a condition wait or snapshot -i before using refs. close commands (`close`, `quit`, or `exit`) only close the browser/CDP session; leave manually launched app shutdown, profile cleanup, and explicit artifacts to the host owner.",
|
|
46
46
|
"For provider or specialized app workflows, load version-matched upstream guidance with skills get agentcore|electron|slack|dogfood|vercel-sandbox through the native tool; add --full when you need references/templates, and use skills get --all only for broad skill audits. Hosted sandbox workflows should use upstream @agent-browser/sandbox helpers outside this wrapper. Provider launches such as -p ios, --provider browserbase/kernel/browseruse/browserless/agentcore, and iOS --device are upstream-owned setup paths; use sessionMode fresh when switching providers and expect external credentials or local Appium/Xcode setup to be required.",
|
|
47
47
|
"For dialogs and frames, use dialog status/accept/dismiss and frame <selector|main> through native args; dialog commands and eval snippets that look like alert/confirm/prompt/dialog triggers are shorter-bounded than normal browser calls, and timed-out dialog-like interactions may add inspect-dialog-after-timeout, dismiss-dialog-after-timeout, or recover-fresh-session-after-dialog-timeout nextActions. When --confirm-actions produces a pending confirmation, use details.nextActions or exact confirm <id> / deny <id> calls instead of inventing ids.",
|
|
48
|
-
"If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. For headed demos, put --headed on the first launch with sessionMode=fresh and verify with screenshot/tab/get-url evidence because tool success cannot prove the OS window is visible to the user. For desktop readiness, prefer real conditions first: wait --text, wait --url, wait --fn, wait --load <state>, wait --download, or qa.attached; for disappearance checks
|
|
48
|
+
"If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. For headed demos, put --headed on the first launch with sessionMode=fresh and verify with screenshot/tab/get-url evidence because tool success cannot prove the OS window is visible to the user. For desktop readiness, prefer real conditions first: wait --text, wait --url, wait --fn, wait --load <state>, wait --download, or qa.attached; for disappearance checks, use wait --fn predicates instead of stale upstream-help examples like wait <selector> --state hidden. Use electron.probe/status for wrapper-owned launch health or target mismatch. Fixed waits are a last resort: use explicit --timeout or top-level timeoutMs for legitimately slow waits, and treat a successful payload like \"waited\":\"timeout\" as elapsed time only—verify completion with an observed condition, fresh snapshot, or screenshot.",
|
|
49
49
|
"For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
|
|
50
50
|
"For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or eval --stdin on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.",
|
|
51
51
|
"For downloads, prefer download <selector> <path> when an element click should save a file; simple loopback anchor downloads are saved to the requested path when the wrapper can resolve an HTTP(S) href. Do not rely on click alone when you need the downloaded file on disk.",
|
|
52
52
|
"On dashboards with nested scroll containers, verify scroll with a screenshot or fresh snapshot -i; if the viewport did not move, details.data.scrolled may be false/noMovement true and you should prefer scrollintoview <@ref> or target the actual scrollable region with scroll <selector> <dir> [px|percent]. For native selects, use select <selector> <value...> (or semanticAction/job select) instead of clicking option refs; for custom comboboxes, a click/semanticAction may only focus the field, so re-snapshot and fall back to type, press Enter/arrow keys, or visible option refs.",
|
|
53
53
|
"When using eval --stdin, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.",
|
|
54
|
-
"When using eval --stdin for extraction, pass the JavaScript through the native tool stdin field, not as an extra args token after --stdin, and return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); use outputPath when the eval/get/snapshot data should be saved as a durable local file. If a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. On file:// pages, when upstream JSON returns result: null for non-trivial stdin, details.evalResultWarning may append Eval result warning without failing the tool—treat that as inconclusive DOM verification. If get text on a CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
|
|
54
|
+
"When using eval --stdin for extraction, pass the JavaScript through the native tool stdin field, not as an extra args token after --stdin, and return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); use outputPath when the eval/get/snapshot data should be saved as a durable local file. If a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. On file:// pages, when upstream JSON returns result: null for non-trivial stdin, details.evalResultWarning may append Eval result warning without failing the tool—treat that as inconclusive DOM verification. If get text on a broad CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
|
|
55
55
|
"When details.pageChangeSummary is present, use changeType and summary as a compact signal for navigation, DOM mutation, confirmations, or artifacts; when nextActionIds is set, match those ids to entries in details.nextActions (or per-step nextActions inside batch) for concrete follow-up payloads instead of inferring from prose alone. If details.clickDispatch reports a click-dispatch miss, refresh/inspect/retry the real click first; for static local fixtures only, an explicit eval --stdin programmatic .click() can exercise app handlers, but treat it as an untrusted scripted workaround and never use it to bypass stop-before-submit/order/purchase boundaries. If a no-navigation click surfaces details.overlayBlockers, inspect the fresh snapshot evidence before using a close/dismiss candidate nextAction; ordinary page chrome without dialog/alertdialog evidence should not trigger this diagnostic.",
|
|
56
56
|
"When commands save or spill files (screenshots, downloads, PDFs, traces, recordings, HAR, large snapshot spills), use the user's exact requested paths when given and treat paths as provisional until details.artifactVerification shows every row verified: branch on missingCount, pendingCount, unverifiedCount, per-entry state, and optional limitation before downstream file use or PASS/FAIL reporting.",
|
|
57
57
|
"For evidence-only screenshots, QA captures, or other audit artifacts, save to an explicit path and branch on details.artifactVerification plus details.artifacts before reporting PASS/FAIL; do not require vision review of inline image attachments unless the user asked for visual inspection.",
|
|
@@ -71,7 +71,7 @@ export const INSPECTION_TOOL_CALL_EXAMPLES = [
|
|
|
71
71
|
'{ "args": ["--version"] }',
|
|
72
72
|
];
|
|
73
73
|
export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
|
|
74
|
-
"After launch-scoped open/goto/navigate calls that can restore existing tabs (for example --profile, --session-name, or --state), agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch.",
|
|
74
|
+
"After launch-scoped open/goto/navigate calls that can restore existing tabs (for example --profile, --restore, --session-name, or --state), agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch.",
|
|
75
75
|
"After the wrapper observes tab-drift risk for a session (for example profile restore correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab is known.",
|
|
76
76
|
"For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.",
|
|
77
77
|
"If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.",
|
|
@@ -53,11 +53,11 @@ export function reorderWindowsLeadingGlobalArgs(args) {
|
|
|
53
53
|
return args;
|
|
54
54
|
return [args[index], ...leadingGlobals, ...args.slice(index + 1)];
|
|
55
55
|
}
|
|
56
|
-
function buildAgentBrowserSpawnCommand(args) {
|
|
57
|
-
if (
|
|
56
|
+
export function buildAgentBrowserSpawnCommand(args, platform = processPlatform) {
|
|
57
|
+
if (platform !== "win32") {
|
|
58
58
|
return { command: "agent-browser", args };
|
|
59
59
|
}
|
|
60
|
-
const commandLine = ["&", "agent-browser", ...reorderWindowsLeadingGlobalArgs(args).map(quoteWindowsPowerShellArg)].join(" ");
|
|
60
|
+
const commandLine = ["&", "agent-browser.cmd", ...reorderWindowsLeadingGlobalArgs(args).map(quoteWindowsPowerShellArg)].join(" ");
|
|
61
61
|
return { command: "powershell.exe", args: ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", commandLine] };
|
|
62
62
|
}
|
|
63
63
|
function terminateSpawnedChild(child, signal) {
|
|
@@ -5,8 +5,26 @@
|
|
|
5
5
|
* Usage: Imported by result presentation helpers and the extension entrypoint when attaching details.nextActions.
|
|
6
6
|
* Invariants/Assumptions: Action ids are stable machine-readable contracts; dedupe preserves first occurrence order.
|
|
7
7
|
*/
|
|
8
|
+
export function withOptionalNamespaceArgs(namespace, args) {
|
|
9
|
+
return namespace && args[0] !== "--namespace" ? ["--namespace", namespace, ...args] : args;
|
|
10
|
+
}
|
|
8
11
|
export function withOptionalSessionArgs(sessionName, args) {
|
|
9
|
-
|
|
12
|
+
if (!sessionName || args[0] === "--session")
|
|
13
|
+
return args;
|
|
14
|
+
if (args[0] === "--namespace" && args[1] && args[2] !== "--session")
|
|
15
|
+
return [args[0], args[1], "--session", sessionName, ...args.slice(2)];
|
|
16
|
+
return ["--session", sessionName, ...args];
|
|
17
|
+
}
|
|
18
|
+
export function applyNamespaceToNextActions(actions, namespace) {
|
|
19
|
+
if (!namespace || !actions)
|
|
20
|
+
return actions;
|
|
21
|
+
return actions.map((action) => {
|
|
22
|
+
const args = action.params?.args;
|
|
23
|
+
if (args)
|
|
24
|
+
return { ...action, params: { ...action.params, args: withOptionalNamespaceArgs(namespace, args) } };
|
|
25
|
+
const networkSourceLookup = action.params?.networkSourceLookup;
|
|
26
|
+
return networkSourceLookup ? { ...action, params: { ...action.params, networkSourceLookup: { ...networkSourceLookup, namespace } } } : action;
|
|
27
|
+
});
|
|
10
28
|
}
|
|
11
29
|
export function buildNextToolAction(options) {
|
|
12
30
|
return {
|