pi-agent-browser-native 0.3.0 → 0.6.5
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 +265 -0
- package/README.md +130 -54
- package/dist/extensions/agent-browser/index.js +781 -169
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +50 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +5 -8
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +53 -12
- package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
- package/dist/extensions/agent-browser/lib/config.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -13
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -24
- 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 +26 -4
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +6 -139
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -116
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +54 -25
- 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 +54 -48
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +71 -5
- 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 -7
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +152 -64
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +244 -102
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +63 -37
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +20 -21
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
- package/dist/extensions/agent-browser/lib/playbook.js +29 -25
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +5 -12
- package/dist/extensions/agent-browser/lib/process.js +130 -104
- package/dist/extensions/agent-browser/lib/recording-reservations.js +116 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -6
- 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 +86 -18
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +38 -2
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +18 -17
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +38 -20
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +60 -15
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
- package/dist/extensions/agent-browser/lib/results/presentation.js +36 -6
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +3 -1
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +54 -11
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
- package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
- package/dist/extensions/agent-browser/lib/runtime.js +186 -108
- package/dist/extensions/agent-browser/lib/session-page-state.js +71 -10
- package/dist/extensions/agent-browser/lib/temp.js +1 -2
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +108 -24
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +21 -0
- package/docs/ARCHITECTURE.md +57 -34
- package/docs/COMMAND_REFERENCE.md +255 -68
- package/docs/ELECTRON.md +2 -2
- package/docs/RELEASE.md +12 -10
- package/docs/REQUIREMENTS.md +11 -8
- package/docs/SUPPORT_MATRIX.md +36 -24
- package/docs/TOOL_CONTRACT.md +169 -95
- package/package.json +3 -1
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +87 -9
- package/scripts/agent-browser-target.mjs +21 -0
- package/scripts/build.mjs +41 -0
- package/scripts/config.mjs +1 -0
- package/scripts/doctor.mjs +16 -9
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -583
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
|
@@ -3,9 +3,10 @@ import { getCompiledSemanticActionCommandIndex, getCompiledSemanticActionSession
|
|
|
3
3
|
import { redactNetworkSourceLookupSurface } from "../../input-modes/lookups.js";
|
|
4
4
|
import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
|
|
5
5
|
import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
|
|
6
|
+
import { extractAgentBrowserLifecycle } from "../../results/presentation/common.js";
|
|
6
7
|
import { formatSessionArtifactRetentionSummary } from "../../results/artifact-manifest.js";
|
|
7
8
|
import { alignPageChangeSummaryNextActionIds, appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, isStandaloneSnapshotNextAction, withOptionalSessionArgs, } from "../../results/next-actions.js";
|
|
8
|
-
import { buildConnectedSessionNextActions, buildNoActivePageNextActions, buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions, } from "../../results/recovery-next-actions.js";
|
|
9
|
+
import { buildConnectedSessionNextActions, buildNoActivePageNextActions, buildPendingWebMcpNextActions, buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions, } from "../../results/recovery-next-actions.js";
|
|
9
10
|
import { buildRichInputRecoveryDiagnostic, buildRichInputRecoveryNextActions, buildVisibleRefFallbackNextActions, formatRichInputRecoveryText, formatVisibleRefFallbackText, sanitizeVisibleRefFallbackDiagnostic, } from "../../results/selector-recovery.js";
|
|
10
11
|
import { buildNoActivePageRefSnapshotInvalidation, isNoActivePageSnapshotFailure, } from "../../session-page-state.js";
|
|
11
12
|
import { extractExplicitSessionName } from "../../argv-grammar.js";
|
|
@@ -195,7 +196,7 @@ export async function prepareFinalResultRecoveryState(options) {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
const richInputRecoveryDiagnostic = buildRichInputRecoveryDiagnostic(visibleRefFallbackDiagnostic);
|
|
198
|
-
const noActivePageSnapshotFailure = categoryDetails.resultCategory === "failure" && (isNoActivePageSnapshotFailure(options.executionPlan.commandInfo.command, options.errorText ?? options.presentation.summary) || options.batchRefSnapshotState?.invalidation
|
|
199
|
+
const noActivePageSnapshotFailure = categoryDetails.resultCategory === "failure" && (isNoActivePageSnapshotFailure(options.executionPlan.commandInfo.command, options.errorText ?? options.presentation.summary) || options.batchRefSnapshotState?.invalidation?.reason === "no-active-page");
|
|
199
200
|
const executionSessionKey = getSessionContextKey(options.executionPlan.sessionName, options.executionPlan.namespace);
|
|
200
201
|
if (noActivePageSnapshotFailure && executionSessionKey) {
|
|
201
202
|
const refUpdate = options.sessionPageState.applyRefSnapshotInvalidation({ invalidation: buildNoActivePageRefSnapshotInvalidation(), sessionName: executionSessionKey, update: options.sessionPageStateUpdate });
|
|
@@ -208,6 +209,18 @@ function buildTimeoutPartialProgressNextActions(options) {
|
|
|
208
209
|
const retryArgs = options.timeoutPartialProgress?.retryStep?.retry?.args;
|
|
209
210
|
const stepIndex = options.timeoutPartialProgress?.retryStep?.index;
|
|
210
211
|
const freshSessionAbandoned = options.sessionMode === "fresh" && options.timeoutPartialProgress?.liveUrlRecovered !== true;
|
|
212
|
+
if (options.currentSessionTabTargetUnknown && !freshSessionAbandoned && options.executionPlan.sessionName) {
|
|
213
|
+
return [{
|
|
214
|
+
id: "verify-page-target-after-timeout",
|
|
215
|
+
params: {
|
|
216
|
+
args: withOptionalSessionArgs(options.executionPlan.sessionName, ["batch", "--bail"]),
|
|
217
|
+
stdin: JSON.stringify([["get", "url"], ["snapshot", "-i"]]),
|
|
218
|
+
},
|
|
219
|
+
reason: `Verify the current URL, then inspect the page after timeout${stepIndex === undefined ? "" : ` before resuming from incomplete step ${stepIndex}`}.`,
|
|
220
|
+
safety: "Fail-fast read-only recovery: snapshot runs only after get url succeeds, satisfying the wrapper page-target guard without trusting the planned URL.",
|
|
221
|
+
tool: "agent_browser",
|
|
222
|
+
}];
|
|
223
|
+
}
|
|
211
224
|
if (retryArgs) {
|
|
212
225
|
return [{
|
|
213
226
|
id: "retry-timeout-step",
|
|
@@ -267,6 +280,10 @@ function buildResultNextActions(options) {
|
|
|
267
280
|
const appendUnique = (actions) => {
|
|
268
281
|
appendUniqueAgentBrowserNextActions(nextActions, actions);
|
|
269
282
|
};
|
|
283
|
+
if (options.unsettledWebMcpMutation && options.currentSessionTabTargetUnknown) {
|
|
284
|
+
nextActions = nextActions.filter((action) => !isStandaloneSnapshotNextAction(action));
|
|
285
|
+
appendUnique(buildPendingWebMcpNextActions(options.executionPlan.sessionName));
|
|
286
|
+
}
|
|
270
287
|
if (options.categoryDetails.resultCategory === "success" && options.executionPlan.commandInfo.command === "connect" && !options.electronLaunchRecord)
|
|
271
288
|
appendUnique(buildConnectedSessionNextActions(options.executionPlan.sessionName));
|
|
272
289
|
if (options.noActivePageSnapshotFailure)
|
|
@@ -320,6 +337,8 @@ function buildResultNextActions(options) {
|
|
|
320
337
|
if (options.managedSessionOutcome)
|
|
321
338
|
appendUnique(buildManagedSessionFreshFailureNextActions(options.managedSessionOutcome));
|
|
322
339
|
if (options.categoryDetails.failureCategory === "timeout" && options.processResult.timedOut) {
|
|
340
|
+
if (options.currentSessionTabTargetUnknown)
|
|
341
|
+
nextActions = nextActions.filter((action) => !isStandaloneSnapshotNextAction(action));
|
|
323
342
|
appendUnique(buildTimeoutPartialProgressNextActions(options));
|
|
324
343
|
appendUnique(buildDialogTimeoutNextActions({ command: options.executionPlan.commandInfo.command, sessionName: options.executionPlan.sessionName }));
|
|
325
344
|
}
|
|
@@ -329,7 +348,45 @@ function buildResultNextActions(options) {
|
|
|
329
348
|
append(buildAgentBrowserNextActions({ electron: { launchId: options.electronLaunchRecord.launchId, sessionName: options.electronLaunchRecord.sessionName, status: options.electronLaunchRecord.cleanupState }, failureCategory: options.categoryDetails.failureCategory, resultCategory: options.categoryDetails.resultCategory, successCategory: options.categoryDetails.successCategory }));
|
|
330
349
|
return nextActions.length > 0 ? nextActions : undefined;
|
|
331
350
|
}
|
|
351
|
+
export function formatAgentBrowserNextActionsText(nextActions) {
|
|
352
|
+
if (!nextActions || nextActions.length === 0)
|
|
353
|
+
return undefined;
|
|
354
|
+
const lines = nextActions.slice(0, 6).map((action) => {
|
|
355
|
+
const params = action.params
|
|
356
|
+
? { ...action.params, ...(action.params.stdin !== undefined && action.params.stdin.length > 500 ? { stdin: "[omitted; use details.nextActions]" } : {}) }
|
|
357
|
+
: undefined;
|
|
358
|
+
const payload = action.artifactPath ? { artifactPath: action.artifactPath } : params;
|
|
359
|
+
return `- ${action.id}${payload ? ` ${redactSensitiveText(JSON.stringify(payload))}` : ""}: ${redactSensitiveText(action.reason)}`;
|
|
360
|
+
});
|
|
361
|
+
return ["Next actions:", ...lines, "The same redacted payloads are available in details.nextActions."].join("\n");
|
|
362
|
+
}
|
|
363
|
+
function formatFailureNextActionsText(options, nextActions) {
|
|
364
|
+
return options.categoryDetails.resultCategory === "failure" ? formatAgentBrowserNextActionsText(nextActions) : undefined;
|
|
365
|
+
}
|
|
366
|
+
function getReadSource(options) {
|
|
367
|
+
return options.executionPlan.commandInfo.command === "read" && isRecord(options.presentationEnvelope?.data) && typeof options.presentationEnvelope.data.source === "string"
|
|
368
|
+
? options.presentationEnvelope.data.source
|
|
369
|
+
: undefined;
|
|
370
|
+
}
|
|
371
|
+
function formatReadExecutionText(options, lifecycle) {
|
|
372
|
+
const source = getReadSource(options);
|
|
373
|
+
if (!source)
|
|
374
|
+
return undefined;
|
|
375
|
+
return `Read execution: source ${source}; CLI started: ${options.processResult.agentBrowserStarted ? "yes" : "no"}; managed browser lifecycle active: ${lifecycle?.effectiveLaunch.browserLaunched === true ? "yes" : "no"}; managed session outcome: ${options.managedSessionOutcome?.status ?? "not managed"}.`;
|
|
376
|
+
}
|
|
377
|
+
function buildBrowserWindowStatus(options, lifecycle) {
|
|
378
|
+
if (!options.headedLaunch || options.preserveAttachedBrowserSession || options.providerLaunch || !options.succeeded || lifecycle?.effectiveLaunch.browserLaunched !== true || !options.executionPlan.managedSessionName || !options.managedSessionOutcome || !["created", "replaced"].includes(options.managedSessionOutcome.status))
|
|
379
|
+
return undefined;
|
|
380
|
+
return { mode: "headed", ownership: "wrapper-managed", sessionName: options.executionPlan.managedSessionName, visibility: "unverified" };
|
|
381
|
+
}
|
|
382
|
+
function formatBrowserWindowText(browserWindow) {
|
|
383
|
+
if (!browserWindow)
|
|
384
|
+
return undefined;
|
|
385
|
+
return "Headed browser handoff: wrapper-managed headed window requested; desktop visibility unverified. If login is needed, ask the user to confirm they can see the window and finish signing in there, then continue with sessionMode auto.";
|
|
386
|
+
}
|
|
332
387
|
function buildAgentBrowserResultDetails(options, nextActions) {
|
|
388
|
+
const lifecycle = extractAgentBrowserLifecycle(options.presentationEnvelope?.data);
|
|
389
|
+
const browserWindow = buildBrowserWindowStatus(options, lifecycle);
|
|
333
390
|
const publicVisibleRefFallbackDiagnostic = options.visibleRefFallbackDiagnostic ? sanitizeVisibleRefFallbackDiagnostic(options.visibleRefFallbackDiagnostic) : undefined;
|
|
334
391
|
const rawPageChangeSummary = (options.scrollNoopDiagnostic || options.comboboxFocusDiagnostic) && options.presentation.pageChangeSummary ? { ...options.presentation.pageChangeSummary, nextActionIds: nextActions?.map((action) => action.id) } : options.presentation.pageChangeSummary;
|
|
335
392
|
const pageChangeSummary = alignPageChangeSummaryNextActionIds(rawPageChangeSummary, nextActions);
|
|
@@ -357,6 +414,10 @@ function buildAgentBrowserResultDetails(options, nextActions) {
|
|
|
357
414
|
navigationSummary: options.navigationSummary,
|
|
358
415
|
electron: options.electronLaunchRecord ? { action: "launch", cleanup: options.electronFailedConnectCleanup, handoff: options.electronHandoff, identifiers: buildElectronIdentifiers(options.electronLaunchRecord), launch: options.electronLaunchRecord, profileIsolation: options.electronProfileIsolationDetails, status: options.succeeded ? "succeeded" : "failed", targets: options.electronLaunch?.targets, version: options.electronLaunch?.version } : undefined,
|
|
359
416
|
...options.categoryDetails,
|
|
417
|
+
agentBrowserStarted: options.processResult.agentBrowserStarted,
|
|
418
|
+
browserWindow,
|
|
419
|
+
lifecycle,
|
|
420
|
+
readSource: getReadSource(options),
|
|
360
421
|
aboutBlankSessionMismatch: options.aboutBlankSessionMismatch,
|
|
361
422
|
electronPostCommandHealth: options.electronPostCommandHealth,
|
|
362
423
|
electronRefFreshness: options.electronRefFreshnessDiagnostic,
|
|
@@ -416,6 +477,8 @@ function buildAgentBrowserResultDetails(options, nextActions) {
|
|
|
416
477
|
}
|
|
417
478
|
export function buildFinalAgentBrowserToolResult(options) {
|
|
418
479
|
const nextActions = applyNamespaceToNextActions(buildResultNextActions(options), options.executionPlan.namespace);
|
|
480
|
+
const lifecycle = extractAgentBrowserLifecycle(options.presentationEnvelope?.data);
|
|
481
|
+
const browserWindow = buildBrowserWindowStatus(options, lifecycle);
|
|
419
482
|
const details = buildAgentBrowserResultDetails(options, nextActions);
|
|
420
483
|
const visibleRefFallbackText = formatVisibleRefFallbackText(options.visibleRefFallbackDiagnostic);
|
|
421
484
|
const richInputRecoveryText = formatRichInputRecoveryText(options.richInputRecoveryDiagnostic);
|
|
@@ -432,9 +495,12 @@ export function buildFinalAgentBrowserToolResult(options) {
|
|
|
432
495
|
const evalStdinHintText = formatEvalStdinHintText(options.evalStdinHint);
|
|
433
496
|
const evalResultWarningText = formatEvalResultWarningText(options.evalResultWarning);
|
|
434
497
|
const artifactCleanupText = formatArtifactCleanupGuidanceText(options.artifactCleanup);
|
|
435
|
-
const timeoutPartialProgressText = options.timeoutPartialProgress ? formatTimeoutPartialProgressText(options.timeoutPartialProgress) : undefined;
|
|
498
|
+
const timeoutPartialProgressText = options.timeoutPartialProgress ? formatTimeoutPartialProgressText(options.timeoutPartialProgress, options.currentSessionTabTargetUnknown === true && !(options.sessionMode === "fresh" && options.timeoutPartialProgress.liveUrlRecovered !== true)) : undefined;
|
|
436
499
|
const managedSessionOutcomeText = formatManagedSessionOutcomeText(options.managedSessionOutcome);
|
|
437
|
-
const
|
|
500
|
+
const readExecutionText = formatReadExecutionText(options, lifecycle);
|
|
501
|
+
const browserWindowText = formatBrowserWindowText(browserWindow);
|
|
502
|
+
const failureNextActionsText = formatFailureNextActionsText(options, nextActions);
|
|
503
|
+
const rawAppendedDiagnosticText = [visibleRefFallbackText, richInputRecoveryText, semanticActionCandidateText, clickDispatchText, overlayBlockerText, fillVerificationText, electronRefFreshnessText, selectorTextVisibilityText, electronBroadGetTextScopeText, scrollNoopDiagnosticText, comboboxFocusDiagnosticText, recordingDependencyWarningText, evalStdinHintText, evalResultWarningText, artifactCleanupText, timeoutPartialProgressText, managedSessionOutcomeText, readExecutionText, browserWindowText, failureNextActionsText].filter((item) => item !== undefined).join("\n\n");
|
|
438
504
|
const appendedDiagnosticText = redactSensitiveText(redactExactSensitiveText(rawAppendedDiagnosticText, options.exactSensitiveValues));
|
|
439
505
|
const shouldAppendDiagnosticText = appendedDiagnosticText.length > 0 && (!options.userRequestedJson || options.plainTextInspection);
|
|
440
506
|
let content = shouldAppendDiagnosticText && options.redactedContent[0]?.type === "text" ? [{ ...options.redactedContent[0], text: `${options.redactedContent[0].text}\n\n${appendedDiagnosticText}` }, ...options.redactedContent.slice(1)] : options.redactedContent;
|
|
@@ -462,5 +528,5 @@ export async function buildMissingBinaryFailureResult(options) {
|
|
|
462
528
|
missingBinaryElectronRecord = missingBinaryElectronCleanup.record;
|
|
463
529
|
}
|
|
464
530
|
const textParts = [errorText, managedSessionOutcomeText, missingBinaryElectronCleanup ? `Electron cleanup after failed attach: ${missingBinaryElectronCleanup.summary}` : undefined].filter((part) => part !== undefined && part.length > 0);
|
|
465
|
-
return { content: [{ type: "text", text: textParts.join("\n\n") }], details: { args: options.redactedArgs, compatibilityWorkaround: options.compatibilityWorkaround, effectiveArgs: options.redactedProcessArgs, electron: missingBinaryElectronRecord ? { action: "launch", cleanup: missingBinaryElectronCleanup, launch: missingBinaryElectronRecord, status: "failed", targets: options.electronLaunch?.targets, version: options.electronLaunch?.version } : undefined, managedSessionOutcome, namespace: options.executionPlan.namespace, nextActions: managedSessionRecoveryNextActions.length > 0 ? managedSessionRecoveryNextActions : undefined, sessionMode: options.sessionMode, sessionTabCorrection: options.sessionTabCorrection, ...buildAgentBrowserResultCategoryDetails({ args: options.redactedProcessArgs, command: options.executionPlan.commandInfo.command, errorText, failureCategory: "missing-binary", spawnError, succeeded: false }), spawnError }, isError: true };
|
|
531
|
+
return { content: [{ type: "text", text: textParts.join("\n\n") }], details: { agentBrowserStarted: options.processResult.agentBrowserStarted, args: options.redactedArgs, compatibilityWorkaround: options.compatibilityWorkaround, effectiveArgs: options.redactedProcessArgs, electron: missingBinaryElectronRecord ? { action: "launch", cleanup: missingBinaryElectronCleanup, launch: missingBinaryElectronRecord, status: "failed", targets: options.electronLaunch?.targets, version: options.electronLaunch?.version } : undefined, managedSessionOutcome, namespace: options.executionPlan.namespace, nextActions: managedSessionRecoveryNextActions.length > 0 ? managedSessionRecoveryNextActions : undefined, sessionMode: options.sessionMode, sessionTabCorrection: options.sessionTabCorrection, ...buildAgentBrowserResultCategoryDetails({ args: options.redactedProcessArgs, command: options.executionPlan.commandInfo.command, errorText, failureCategory: "missing-binary", spawnError, succeeded: false }), spawnError }, isError: true };
|
|
466
532
|
}
|
|
@@ -20,6 +20,7 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
20
20
|
const ownedManagedSession = prepared.ownedManagedSessionContext;
|
|
21
21
|
return await withOwnedManagedSessionContext(ownedManagedSession, async () => {
|
|
22
22
|
try {
|
|
23
|
+
const artifactRunStartedAtMs = Date.now();
|
|
23
24
|
const processResult = await runAgentBrowserProcess({
|
|
24
25
|
args: prepared.processArgs,
|
|
25
26
|
cwd: options.cwd,
|
|
@@ -51,7 +52,7 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
51
52
|
});
|
|
52
53
|
if (missingBinaryResult)
|
|
53
54
|
return missingBinaryResult;
|
|
54
|
-
const output = await processBrowserOutput({ ...options, prepared, processResult });
|
|
55
|
+
const output = await processBrowserOutput({ ...options, artifactRunStartedAtMs, prepared, processResult });
|
|
55
56
|
applyBrowserRunStatePatch(options.state, output.statePatch);
|
|
56
57
|
return output.result;
|
|
57
58
|
}
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js
CHANGED
|
@@ -3,13 +3,14 @@ import { acquireManagedSessionPolicyLock } from "../../managed-session-policy-lo
|
|
|
3
3
|
import { pruneOwnedManagedSessionRestoreSnapshots, resolveExplicitAutosaveInterval, } from "../../managed-session-restore.js";
|
|
4
4
|
import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
|
|
5
5
|
import { isRecord } from "../../parsing.js";
|
|
6
|
+
import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
|
|
6
7
|
import { runAgentBrowserProcess } from "../../process.js";
|
|
7
8
|
import { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
8
9
|
import { redactInvocationArgs } from "../../runtime.js";
|
|
9
10
|
const MANAGED_SESSION_DAEMON_INSPECTION_TIMEOUT_MS = 35_000;
|
|
10
11
|
const RUNNING_HEADED_AUTOSAVE_POLICY_CHANGE_ERROR = "AGENT_BROWSER_AUTOSAVE_INTERVAL_MS cannot change a running wrapper-owned headed session's launch-time periodic autosave interval. Close that session first, then retry with sessionMode: \"fresh\" so the new daemon starts with the requested interval.";
|
|
11
12
|
export function getRunningHeadedAutosavePolicyChangeError(recordedInterval, closeCommand = false) {
|
|
12
|
-
const explicitInterval = resolveExplicitAutosaveInterval(
|
|
13
|
+
const explicitInterval = resolveExplicitAutosaveInterval(getAgentBrowserProcessEnvironment().AGENT_BROWSER_AUTOSAVE_INTERVAL_MS);
|
|
13
14
|
return recordedInterval !== undefined && !closeCommand && explicitInterval !== undefined && explicitInterval !== recordedInterval
|
|
14
15
|
? RUNNING_HEADED_AUTOSAVE_POLICY_CHANGE_ERROR
|
|
15
16
|
: undefined;
|
|
@@ -19,7 +20,6 @@ function getHeadedManagedAutosaveEnv(interval) {
|
|
|
19
20
|
}
|
|
20
21
|
export async function inspectManagedSessionDaemon(options) {
|
|
21
22
|
const processResult = await runAgentBrowserProcess({
|
|
22
|
-
allowManagedSessionTarget: options.allowManagedSessionTarget,
|
|
23
23
|
args: ["--json", "--namespace", options.namespace ?? "", "--session", options.sessionName, "session", "info"],
|
|
24
24
|
cwd: options.cwd,
|
|
25
25
|
env: getHeadedManagedAutosaveEnv(options.headedManagedAutosaveInterval),
|
|
@@ -67,7 +67,6 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
67
67
|
}
|
|
68
68
|
try {
|
|
69
69
|
const daemon = await inspectManagedSessionDaemon({
|
|
70
|
-
allowManagedSessionTarget: true,
|
|
71
70
|
cwd: context.cwd,
|
|
72
71
|
headedManagedAutosaveInterval: context.headedManagedAutosaveInterval,
|
|
73
72
|
namespace: context.namespace,
|
|
@@ -79,7 +78,7 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
79
78
|
if (options.mode === "close") {
|
|
80
79
|
if (daemon.status === "active")
|
|
81
80
|
context.restoreState.recordDaemonRestoreKey(context.sessionName, context.namespace, daemon.restoreKey);
|
|
82
|
-
return { lock };
|
|
81
|
+
return { daemonStatus: daemon.status, lock };
|
|
83
82
|
}
|
|
84
83
|
const stickyDisabled = context.restoreState.isDisabled(context.sessionName, context.namespace);
|
|
85
84
|
const hasKnownDaemonRestoreKey = context.restoreState.hasDaemonRestoreKey(context.sessionName, context.namespace);
|
|
@@ -101,13 +100,14 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
101
100
|
context.restoreState.recordDaemonRestoreKey(context.sessionName, context.namespace, daemon.restoreKey);
|
|
102
101
|
return !["inactive", "missing-binary"].includes(daemon.status) && !activePolicyMatches
|
|
103
102
|
? {
|
|
103
|
+
daemonStatus: daemon.status,
|
|
104
104
|
error: [
|
|
105
105
|
"This wrapper-owned session's live daemon does not match the requested managed-restore policy.",
|
|
106
106
|
"Close that session first, retry with sessionMode: \"fresh\", or use a distinct explicit --session.",
|
|
107
107
|
].join(" "),
|
|
108
108
|
lock,
|
|
109
109
|
}
|
|
110
|
-
: { lock };
|
|
110
|
+
: { daemonStatus: daemon.status, lock };
|
|
111
111
|
}
|
|
112
112
|
catch (error) {
|
|
113
113
|
await lock.release();
|
|
@@ -118,7 +118,7 @@ export async function closeManagedSession(options) {
|
|
|
118
118
|
const controller = new AbortController();
|
|
119
119
|
const timer = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
120
120
|
let stdoutSpillPath;
|
|
121
|
-
const closeArgs = [...(options.namespace ? ["--namespace", options.namespace] : []), "--session", options.sessionName, "close"];
|
|
121
|
+
const closeArgs = [...(options.namespace !== undefined ? ["--namespace", options.namespace] : []), "--session", options.sessionName, "close"];
|
|
122
122
|
const policyLock = options.policyLock ?? await acquireManagedSessionPolicyLock({
|
|
123
123
|
namespace: options.namespace,
|
|
124
124
|
sessionName: options.sessionName,
|
|
@@ -131,7 +131,6 @@ export async function closeManagedSession(options) {
|
|
|
131
131
|
}
|
|
132
132
|
try {
|
|
133
133
|
const daemon = await inspectManagedSessionDaemon({
|
|
134
|
-
allowManagedSessionTarget: true,
|
|
135
134
|
cwd: options.cwd,
|
|
136
135
|
headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
|
|
137
136
|
namespace: options.namespace,
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isRecord } from "../../../parsing.js";
|
|
|
2
2
|
import { buildAgentBrowserResultCategoryDetails } from "../../../results/categories.js";
|
|
3
3
|
import { buildSnapshotPresentation } from "../../../results/snapshot.js";
|
|
4
4
|
import { extractRefSnapshotFromData } from "../../../session-page-state.js";
|
|
5
|
+
import { redactSensitiveText } from "../../../runtime.js";
|
|
5
6
|
import { collectScrollPositionSnapshot } from "../diagnostics.js";
|
|
6
7
|
import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
|
|
7
8
|
function parseSnapshotFilterRequest(commandTokens) {
|
|
@@ -42,6 +43,116 @@ function parseSnapshotFilterRequest(commandTokens) {
|
|
|
42
43
|
return undefined;
|
|
43
44
|
return { cleanArgs, diff, role, search, viewport };
|
|
44
45
|
}
|
|
46
|
+
const RENDERED_TEXT_SEARCH_MAX_MATCHES = 8;
|
|
47
|
+
function buildRenderedTextSearchEval(search) {
|
|
48
|
+
return `(() => {
|
|
49
|
+
const query = ${JSON.stringify(search.trim().toLowerCase())};
|
|
50
|
+
const limit = ${RENDERED_TEXT_SEARCH_MAX_MATCHES};
|
|
51
|
+
const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
|
|
52
|
+
const attributeTextOf = (element) => [element.getAttribute("aria-label"), element.getAttribute("title"), element.getAttribute("placeholder")].filter(Boolean).join(" ");
|
|
53
|
+
const searchableTextOf = (element) => normalize([attributeTextOf(element), element.textContent].filter(Boolean).join(" "));
|
|
54
|
+
const renderedTextOf = (element) => normalize([attributeTextOf(element), element instanceof HTMLElement ? element.innerText : element.textContent].filter(Boolean).join(" "));
|
|
55
|
+
const isRendered = (element) => {
|
|
56
|
+
if (!(element instanceof Element) || element.getClientRects().length === 0) return false;
|
|
57
|
+
const visibility = getComputedStyle(element).visibility;
|
|
58
|
+
if (visibility === "hidden" || visibility === "collapse") return false;
|
|
59
|
+
for (let current = element; current; current = current.parentElement) {
|
|
60
|
+
if (Number(getComputedStyle(current).opacity) === 0) return false;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
const validationPattern = /(?:^|[-_\\s])(?:error|invalid|validation|warning)(?:$|[-_\\s])/i;
|
|
65
|
+
const validationAncestor = (element) => {
|
|
66
|
+
for (let current = element; current && current !== document.body; current = current.parentElement) {
|
|
67
|
+
const role = current.getAttribute("role");
|
|
68
|
+
const live = current.getAttribute("aria-live");
|
|
69
|
+
const descriptor = [current.id, current.className, current.getAttribute("data-testid")].map(normalize).join(" ");
|
|
70
|
+
if (["alert", "alertdialog", "status"].includes(role || "") || current.getAttribute("aria-invalid") === "true" || (live && live !== "off") || validationPattern.test(descriptor)) return current;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
const candidates = Array.from(document.querySelectorAll("body *")).filter((element) => searchableTextOf(element).toLowerCase().includes(query));
|
|
75
|
+
const candidateSet = new Set(candidates);
|
|
76
|
+
const minimal = candidates.filter((element) => !Array.from(element.children).some((child) => candidateSet.has(child)));
|
|
77
|
+
const rendered = minimal.filter((element) => isRendered(element) && renderedTextOf(element).toLowerCase().includes(query));
|
|
78
|
+
const matches = rendered.map((element, index) => {
|
|
79
|
+
const semantic = validationAncestor(element);
|
|
80
|
+
const source = semantic || element;
|
|
81
|
+
const text = renderedTextOf(element);
|
|
82
|
+
const matchIndex = text.toLowerCase().indexOf(query);
|
|
83
|
+
const start = Math.max(0, matchIndex - 80);
|
|
84
|
+
const snippet = text.slice(start, start + 240);
|
|
85
|
+
const rect = element.getBoundingClientRect();
|
|
86
|
+
const name = normalize(element.getAttribute("aria-label") || element.getAttribute("title") || element.getAttribute("placeholder")) || undefined;
|
|
87
|
+
return {
|
|
88
|
+
index,
|
|
89
|
+
kind: semantic ? "validation" : "text",
|
|
90
|
+
name,
|
|
91
|
+
offscreen: rect.bottom < 0 || rect.right < 0 || rect.top > innerHeight || rect.left > innerWidth,
|
|
92
|
+
role: source.getAttribute("role") || undefined,
|
|
93
|
+
tagName: element.tagName.toLowerCase(),
|
|
94
|
+
text: snippet,
|
|
95
|
+
};
|
|
96
|
+
}).sort((left, right) => (left.kind === right.kind ? left.index - right.index : left.kind === "validation" ? -1 : 1));
|
|
97
|
+
const unique = [];
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
for (const match of matches) {
|
|
100
|
+
const key = [match.kind, match.role, match.name, match.text].join("\\n");
|
|
101
|
+
if (seen.has(key)) continue;
|
|
102
|
+
seen.add(key);
|
|
103
|
+
const { index, ...visible } = match;
|
|
104
|
+
unique.push(visible);
|
|
105
|
+
}
|
|
106
|
+
return { matches: unique.slice(0, limit), totalMatches: unique.length, truncated: unique.length > limit };
|
|
107
|
+
})()`;
|
|
108
|
+
}
|
|
109
|
+
function extractRenderedTextSearchResult(data) {
|
|
110
|
+
const result = isRecord(data) && isRecord(data.result) ? data.result : data;
|
|
111
|
+
if (!isRecord(result) || !Array.isArray(result.matches))
|
|
112
|
+
return undefined;
|
|
113
|
+
const matches = result.matches.flatMap((value) => {
|
|
114
|
+
if (!isRecord(value) || (value.kind !== "text" && value.kind !== "validation") || typeof value.offscreen !== "boolean" || typeof value.tagName !== "string" || typeof value.text !== "string")
|
|
115
|
+
return [];
|
|
116
|
+
return [{
|
|
117
|
+
kind: value.kind,
|
|
118
|
+
...(typeof value.name === "string" ? { name: redactSensitiveText(value.name) } : {}),
|
|
119
|
+
offscreen: value.offscreen,
|
|
120
|
+
...(typeof value.role === "string" ? { role: value.role } : {}),
|
|
121
|
+
tagName: value.tagName,
|
|
122
|
+
text: redactSensitiveText(value.text),
|
|
123
|
+
}];
|
|
124
|
+
}).slice(0, RENDERED_TEXT_SEARCH_MAX_MATCHES);
|
|
125
|
+
const totalMatches = typeof result.totalMatches === "number" && Number.isInteger(result.totalMatches) && result.totalMatches >= matches.length ? result.totalMatches : matches.length;
|
|
126
|
+
return { matches, totalMatches, truncated: result.truncated === true || totalMatches > matches.length };
|
|
127
|
+
}
|
|
128
|
+
function attachRenderedTextMatchRefs(matches, snapshotData) {
|
|
129
|
+
if (!isRecord(snapshotData) || !isRecord(snapshotData.refs))
|
|
130
|
+
return matches;
|
|
131
|
+
const refs = snapshotData.refs;
|
|
132
|
+
return matches.map((match) => {
|
|
133
|
+
if (!match.name)
|
|
134
|
+
return match;
|
|
135
|
+
const normalizedName = match.name.replace(/\s+/g, " ").trim().toLowerCase();
|
|
136
|
+
const candidates = Object.entries(refs).filter(([, value]) => {
|
|
137
|
+
if (!isRecord(value) || typeof value.name !== "string")
|
|
138
|
+
return false;
|
|
139
|
+
const nameMatches = value.name.replace(/\s+/g, " ").trim().toLowerCase() === normalizedName;
|
|
140
|
+
return nameMatches && (!match.role || typeof value.role !== "string" || value.role.toLowerCase() === match.role.toLowerCase());
|
|
141
|
+
});
|
|
142
|
+
return candidates.length === 1 ? { ...match, ref: candidates[0][0] } : match;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function formatRenderedTextSearchMatches(result) {
|
|
146
|
+
if (!result || result.matches.length === 0)
|
|
147
|
+
return undefined;
|
|
148
|
+
const lines = result.matches.map((match) => {
|
|
149
|
+
const context = [match.kind === "validation" ? "validation" : undefined, match.offscreen ? "outside viewport" : undefined, match.role, match.tagName, match.ref ? `@${match.ref}` : undefined].filter(Boolean).join(", ");
|
|
150
|
+
return `- ${JSON.stringify(match.text)}${context ? ` (${context})` : ""}`;
|
|
151
|
+
});
|
|
152
|
+
if (result.truncated)
|
|
153
|
+
lines.push(`- ... (${result.totalMatches - result.matches.length} additional rendered-text matches omitted)`);
|
|
154
|
+
return ["Rendered page text matches:", ...lines].join("\n");
|
|
155
|
+
}
|
|
45
156
|
function buildSnapshotDiff(previous, current) {
|
|
46
157
|
if (!current)
|
|
47
158
|
return undefined;
|
|
@@ -113,6 +224,11 @@ export async function trySnapshotFilter(options) {
|
|
|
113
224
|
const filtered = request.role || request.search ? filterSnapshotData(snapshotData, request) : isRecord(snapshotData) ? { data: snapshotData, matchedRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, totalLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0, totalRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, visibleLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0 } : undefined;
|
|
114
225
|
if (!filtered)
|
|
115
226
|
return undefined;
|
|
227
|
+
const renderedTextSearch = request.search
|
|
228
|
+
? extractRenderedTextSearchResult(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildRenderedTextSearchEval(request.search) }))
|
|
229
|
+
: undefined;
|
|
230
|
+
if (renderedTextSearch)
|
|
231
|
+
renderedTextSearch.matches = attachRenderedTextMatchRefs(renderedTextSearch.matches, snapshotData);
|
|
116
232
|
const viewport = request.viewport ? await collectScrollPositionSnapshot({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }) : undefined;
|
|
117
233
|
const fullSnapshot = extractRefSnapshotFromData(snapshotData);
|
|
118
234
|
const diff = request.diff ? buildSnapshotDiff(options.previousRefSnapshot, fullSnapshot) : undefined;
|
|
@@ -126,7 +242,8 @@ export async function trySnapshotFilter(options) {
|
|
|
126
242
|
: "Snapshot viewport metadata collected.";
|
|
127
243
|
const viewportText = viewport ? `Viewport: ${viewport.innerWidth}×${viewport.innerHeight}, scroll ${viewport.scrollX},${viewport.scrollY}, document ${viewport.scrollWidth}×${viewport.scrollHeight}, sampled scroll containers ${viewport.containers.length}/${viewport.containerCount}.` : undefined;
|
|
128
244
|
const diffText = diff && (request.role || request.search) ? diff.summary : undefined;
|
|
129
|
-
const
|
|
245
|
+
const renderedTextSearchText = formatRenderedTextSearchMatches(renderedTextSearch);
|
|
246
|
+
const prefix = [summary, renderedTextSearchText, diffText, viewportText].filter((line) => line !== undefined).join("\n\n");
|
|
130
247
|
if (presentation.content[0]?.type === "text")
|
|
131
248
|
presentation.content[0] = { ...presentation.content[0], text: `${prefix}\n\n${presentation.content[0].text}` };
|
|
132
249
|
return {
|
|
@@ -146,7 +263,7 @@ export async function trySnapshotFilter(options) {
|
|
|
146
263
|
refSnapshot: fullSnapshot,
|
|
147
264
|
sessionMode: options.sessionMode,
|
|
148
265
|
snapshotDiff: diff,
|
|
149
|
-
snapshotFilter: request.role || request.search ? { cleanArgs: request.cleanArgs, matchedRefs: filtered.matchedRefs, role: request.role, search: request.search, totalLines: filtered.totalLines, totalRefs: filtered.totalRefs, visibleLines: filtered.visibleLines } : undefined,
|
|
266
|
+
snapshotFilter: request.role || request.search ? { cleanArgs: request.cleanArgs, matchedRefs: filtered.matchedRefs, renderedTextMatches: renderedTextSearch?.matches, renderedTextTotalMatches: renderedTextSearch?.totalMatches, renderedTextTruncated: renderedTextSearch?.truncated, role: request.role, search: request.search, totalLines: filtered.totalLines, totalRefs: filtered.totalRefs, visibleLines: filtered.visibleLines } : undefined,
|
|
150
267
|
snapshotViewport: viewport,
|
|
151
268
|
...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: "snapshot", succeeded: true }),
|
|
152
269
|
...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace, options.managedSessionRestoreDisabled()),
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS } from "../../../argv-grammar.js";
|
|
2
2
|
import { isOpenNavigationCommand } from "../../../command-taxonomy.js";
|
|
3
3
|
import { getAgentBrowserProcessTimeoutMs } from "../../../process.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getUpstreamEffectiveBatchSteps } from "../../batch-stdin.js";
|
|
5
5
|
const POSITIONAL_VALUE_FLAGS = new Set([...VALUE_FLAGS, "--llms"]);
|
|
6
6
|
const COMMAND_PROCESS_TIMEOUT_GRACE_MS = 5_000;
|
|
7
7
|
function parseMillisecondsToken(token) {
|
|
@@ -12,7 +12,8 @@ function parseMillisecondsToken(token) {
|
|
|
12
12
|
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
13
13
|
}
|
|
14
14
|
function findCommandTimeoutMs(commandTokens) {
|
|
15
|
-
|
|
15
|
+
const [command, subcommand] = commandTokens;
|
|
16
|
+
if (command !== "wait" && command !== "read" && !(command === "webmcp" && ["invoke", "result"].includes(subcommand ?? "")))
|
|
16
17
|
return undefined;
|
|
17
18
|
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
18
19
|
const token = commandTokens[index];
|
|
@@ -73,11 +74,11 @@ function findCommandTimeoutBudgetMs(commandTokens, stdin, activePageUrl) {
|
|
|
73
74
|
const directTimeout = commandTimeoutBudgetMs(commandTokens, activePageUrl);
|
|
74
75
|
if (directTimeout !== undefined)
|
|
75
76
|
return directTimeout;
|
|
76
|
-
if (commandTokens[0] !== "batch"
|
|
77
|
+
if (commandTokens[0] !== "batch")
|
|
77
78
|
return undefined;
|
|
78
79
|
let batchTimeoutTotal = 0;
|
|
79
80
|
let batchPageUrl = activePageUrl;
|
|
80
|
-
for (const
|
|
81
|
+
for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
|
|
81
82
|
batchTimeoutTotal += commandTimeoutBudgetMs(step, batchPageUrl) ?? 0;
|
|
82
83
|
if (isOpenNavigationCommand(step[0]))
|
|
83
84
|
batchPageUrl = findFirstPositionalArgument(step);
|
|
@@ -87,10 +88,10 @@ function findCommandTimeoutBudgetMs(commandTokens, stdin, activePageUrl) {
|
|
|
87
88
|
export function commandTimeoutNeedsActivePageUrl(commandTokens, stdin) {
|
|
88
89
|
if (commandTokens[0] === "read")
|
|
89
90
|
return findCommandTimeoutMs(commandTokens) !== undefined && readUsesActivePageUrl(commandTokens);
|
|
90
|
-
if (commandTokens[0] !== "batch"
|
|
91
|
+
if (commandTokens[0] !== "batch")
|
|
91
92
|
return false;
|
|
92
93
|
let hasKnownPageUrl = false;
|
|
93
|
-
for (const
|
|
94
|
+
for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
|
|
94
95
|
if (isOpenNavigationCommand(step[0]) && findFirstPositionalArgument(step))
|
|
95
96
|
hasKnownPageUrl = true;
|
|
96
97
|
if (!hasKnownPageUrl && findCommandTimeoutMs(step) !== undefined && step[0] === "read" && readUsesActivePageUrl(step))
|