pi-agent-browser-native 0.2.61 → 0.2.62

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +10 -10
  3. package/dist/extensions/agent-browser/index.js +51 -30
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -3
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +29 -1
  6. package/dist/extensions/agent-browser/lib/command-policy.js +10 -1
  7. package/dist/extensions/agent-browser/lib/command-taxonomy.js +7 -0
  8. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +5 -2
  9. package/dist/extensions/agent-browser/lib/input-modes/params.js +2 -1
  10. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +25 -1
  11. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +4 -3
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +25 -17
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +14 -11
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +3 -2
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +2 -1
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +3 -3
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +5 -4
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -4
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +43 -27
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +66 -54
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +24 -15
  22. package/dist/extensions/agent-browser/lib/playbook.js +4 -4
  23. package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -1
  24. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +8 -7
  25. package/dist/extensions/agent-browser/lib/results/presentation.js +2 -1
  26. package/dist/extensions/agent-browser/lib/runtime.js +62 -10
  27. package/dist/extensions/agent-browser/lib/session-page-state.js +14 -7
  28. package/docs/ARCHITECTURE.md +5 -5
  29. package/docs/COMMAND_REFERENCE.md +61 -23
  30. package/docs/SUPPORT_MATRIX.md +9 -8
  31. package/docs/TOOL_CONTRACT.md +20 -20
  32. package/package.json +1 -1
  33. package/scripts/agent-browser-capability-baseline.mjs +36 -4
@@ -43,7 +43,7 @@ function getClickDispatchProbeTarget(commandTokens, refSnapshot) {
43
43
  }
44
44
  if (selector.startsWith("xpath="))
45
45
  return { kind: "xpath", selector: selector.slice("xpath=".length) };
46
- return { kind: "selector", selector };
46
+ return undefined;
47
47
  }
48
48
  function getEvalResultRecord(data) {
49
49
  return isRecord(data) && isRecord(data.result) ? data.result : undefined;
@@ -219,7 +219,7 @@ export async function prepareClickDispatchProbe(options) {
219
219
  if (!target)
220
220
  return undefined;
221
221
  const probe = { marker: `${CLICK_DISPATCH_MARKER_PREFIX}${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`, target };
222
- const installData = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildClickDispatchProbeInstallScript(probe) });
222
+ const installData = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildClickDispatchProbeInstallScript(probe) });
223
223
  const installResult = getEvalResultRecord(installData);
224
224
  return installResult?.status === "installed" ? probe : undefined;
225
225
  }
@@ -241,7 +241,7 @@ function getClickDispatchScrollContainerDiagnostic(result) {
241
241
  export async function collectClickDispatchDiagnostic(options) {
242
242
  if (!options.probe || !options.sessionName)
243
243
  return undefined;
244
- const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildClickDispatchProbeCheckScript(options.probe) });
244
+ const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildClickDispatchProbeCheckScript(options.probe) });
245
245
  const result = getEvalResultRecord(data);
246
246
  if (!result)
247
247
  return undefined;
@@ -269,6 +269,7 @@ export async function cleanupClickDispatchProbe(options) {
269
269
  await runSessionCommandData({
270
270
  args: ["eval", "--stdin"],
271
271
  cwd: options.cwd,
272
+ namespace: options.namespace,
272
273
  sessionName: options.sessionName,
273
274
  stdin: buildClickDispatchProbeCleanupScript(options.probe),
274
275
  timeoutMs: CLICK_DISPATCH_CLEANUP_TIMEOUT_MS,
@@ -18,9 +18,14 @@ export function sleepMs(ms) {
18
18
  return new Promise((resolve) => setTimeout(resolve, ms));
19
19
  }
20
20
  export async function collectNavigationSummary(options) {
21
+ const url = extractStringResultField(await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "url");
22
+ const title = extractStringResultField(await runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "title");
23
+ if (url && /^[a-z][a-z0-9+.-]*:/i.test(url))
24
+ return { title, url };
21
25
  return extractNavigationSummaryFromData(await runSessionCommandData({
22
26
  args: ["eval", "--stdin"],
23
27
  cwd: options.cwd,
28
+ namespace: options.namespace,
24
29
  sessionName: options.sessionName,
25
30
  signal: options.signal,
26
31
  stdin: `({ title: document.title, url: location.href })`,
@@ -74,7 +79,7 @@ const SCROLL_POSITION_EVAL = `(() => {
74
79
  return { ...viewport, containerCount: containers.length, containers };
75
80
  })()`;
76
81
  export async function collectScrollPositionSnapshot(options) {
77
- return extractScrollPositionSnapshot(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: SCROLL_POSITION_EVAL }));
82
+ return extractScrollPositionSnapshot(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: SCROLL_POSITION_EVAL }));
78
83
  }
79
84
  function sameScrollPositionSnapshot(left, right) {
80
85
  return left.scrollX === right.scrollX && left.scrollY === right.scrollY && left.scrollHeight === right.scrollHeight && left.scrollWidth === right.scrollWidth && left.containers.length === right.containers.length && left.containers.every((container, index) => {
@@ -173,7 +178,7 @@ function isComboboxFocusDiagnosticSemanticAction(compiled) {
173
178
  export async function collectComboboxFocusDiagnostic(options) {
174
179
  if (!isComboboxFocusDiagnosticCommand(options.command, options.commandTokens) && !isComboboxFocusDiagnosticSemanticAction(options.semanticAction))
175
180
  return undefined;
176
- return extractComboboxFocusDiagnostic(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: COMBOBOX_FOCUS_EVAL }));
181
+ return extractComboboxFocusDiagnostic(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: COMBOBOX_FOCUS_EVAL }));
177
182
  }
178
183
  export function buildComboboxFocusNextActions(sessionName) {
179
184
  return [
@@ -257,11 +262,13 @@ export function collectSnapshotOverlayBlockerDiagnostic(data) {
257
262
  export async function collectOverlayBlockerDiagnostic(options) {
258
263
  if (options.command !== "click" || !isRecord(options.data) || typeof options.data.clicked !== "string")
259
264
  return undefined;
265
+ if (!options.data.clicked.startsWith("@") && !options.data.clicked.startsWith("ref="))
266
+ return undefined;
260
267
  const priorUrl = normalizeComparableUrl(options.priorTarget?.url);
261
268
  const currentUrl = normalizeComparableUrl(options.navigationSummary?.url);
262
- if (!priorUrl || !currentUrl || priorUrl !== currentUrl)
269
+ if (!priorUrl || !currentUrl || currentUrl !== priorUrl)
263
270
  return undefined;
264
- const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
271
+ const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
265
272
  const diagnostic = collectSnapshotOverlayBlockerDiagnostic(snapshotData);
266
273
  if (!diagnostic)
267
274
  return undefined;
@@ -314,9 +321,9 @@ function selectorMayExposeSensitiveLiteral(selector) {
314
321
  }
315
322
  async function collectSelectorTextVisibilityDiagnosticForSelector(options) {
316
323
  const { selector } = options;
317
- if (!selector || /^@e\d+$/.test(selector) || selectorMayExposeSensitiveLiteral(selector))
324
+ if (!selector || /^@e\d+$/.test(selector) || /^#[A-Za-z_][\w-]*$/.test(selector) || selectorMayExposeSensitiveLiteral(selector))
318
325
  return undefined;
319
- const probe = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildVisibleTextProbeScript(selector) });
326
+ const probe = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildVisibleTextProbeScript(selector) });
320
327
  const parsed = parseSelectorTextVisibilityProbe(probe, selector);
321
328
  if (!parsed || parsed.matchCount <= 1 && parsed.firstMatchVisible !== false)
322
329
  return undefined;
@@ -348,7 +355,7 @@ export async function collectSelectorTextVisibilityDiagnostics(options) {
348
355
  const selectors = getSuccessfulGetTextSelectors(options);
349
356
  const diagnostics = [];
350
357
  for (const selector of selectors) {
351
- const diagnostic = await collectSelectorTextVisibilityDiagnosticForSelector({ cwd: options.cwd, selector, sessionName: options.sessionName, signal: options.signal });
358
+ const diagnostic = await collectSelectorTextVisibilityDiagnosticForSelector({ cwd: options.cwd, namespace: options.namespace, selector, sessionName: options.sessionName, signal: options.signal });
352
359
  if (diagnostic)
353
360
  diagnostics.push(diagnostic);
354
361
  }
@@ -510,6 +517,7 @@ async function collectElectronManagedSessionUrl(options) {
510
517
  const urlResult = await collectManagedSessionCommandData({
511
518
  args: ["get", "url"],
512
519
  cwd: options.cwd,
520
+ namespace: options.namespace,
513
521
  sessionName: options.sessionName,
514
522
  signal: options.signal,
515
523
  timeoutMs: options.timeoutMs,
@@ -521,8 +529,8 @@ export async function collectElectronManagedSessionTarget(options) {
521
529
  if (!options.sessionName)
522
530
  return undefined;
523
531
  const [titleResult, urlResult] = await Promise.all([
524
- collectManagedSessionCommandData({ args: ["get", "title"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs }),
525
- collectManagedSessionCommandData({ args: ["get", "url"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs }),
532
+ collectManagedSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs }),
533
+ collectManagedSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs }),
526
534
  ]);
527
535
  const title = boundElectronProbeString(extractStringResultField(titleResult.data, "result") ?? extractStringResultField(titleResult.data, "title"), 160);
528
536
  const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
@@ -534,7 +542,7 @@ export async function collectQaAttachedTarget(options) {
534
542
  return undefined;
535
543
  if (options.currentTarget?.title || options.currentTarget?.url)
536
544
  return { sessionName: options.sessionName, title: options.currentTarget.title, url: options.currentTarget.url };
537
- return collectElectronManagedSessionTarget({ cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
545
+ return collectElectronManagedSessionTarget({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
538
546
  }
539
547
  export function formatQaAttachedTargetText(target) {
540
548
  if (!target)
@@ -565,7 +573,7 @@ export async function validateQaAttachedPrecondition(options) {
565
573
  nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
566
574
  };
567
575
  }
568
- const urlProbe = await collectElectronManagedSessionUrl({ cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
576
+ const urlProbe = await collectElectronManagedSessionUrl({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
569
577
  if (urlProbe.error) {
570
578
  return {
571
579
  error: `qa.attached could not read the attached session URL: ${urlProbe.error}. Run tab list or snapshot -i before retrying qa.attached.`,
@@ -630,7 +638,7 @@ export async function collectFillVerificationDiagnostic(options) {
630
638
  const method = contenteditable ? "text" : "value";
631
639
  let valueData;
632
640
  try {
633
- valueData = await runSessionCommandData({ args: ["get", method, fill.selector], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, timeoutMs: ELECTRON_FILL_VERIFICATION_TIMEOUT_MS });
641
+ valueData = await runSessionCommandData({ args: ["get", method, fill.selector], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: ELECTRON_FILL_VERIFICATION_TIMEOUT_MS });
634
642
  }
635
643
  catch {
636
644
  return undefined;
@@ -659,22 +667,22 @@ export async function collectVisibleRefFallbackDiagnostic(options) {
659
667
  const target = getVisibleRefFallbackTarget({ commandTokens: options.commandTokens, compiledSemanticAction: options.compiledSemanticAction });
660
668
  if (!target)
661
669
  return undefined;
662
- const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
670
+ const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
663
671
  return buildVisibleRefFallbackDiagnosticFromSnapshot({ snapshotData, target });
664
672
  }
665
673
  export async function collectElectronHandoff(options) {
666
674
  if (options.handoff === "connect")
667
675
  return { handoff: "connect" };
668
- const tabs = await runSessionCommandData({ args: ["tab", "list"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
676
+ const tabs = await runSessionCommandData({ args: ["tab", "list"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
669
677
  if (options.handoff === "tabs")
670
678
  return { handoff: "tabs", tabs };
671
- let snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
679
+ let snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
672
680
  let refSnapshot = extractRefSnapshotFromData(snapshot);
673
681
  let snapshotRetryCount = 0;
674
682
  while ((!refSnapshot || refSnapshot.refIds.length === 0) && snapshotRetryCount < 2) {
675
683
  snapshotRetryCount += 1;
676
684
  await sleepMs(250);
677
- snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
685
+ snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
678
686
  refSnapshot = extractRefSnapshotFromData(snapshot);
679
687
  }
680
688
  return { handoff: "snapshot", refSnapshot, snapshot, ...(snapshotRetryCount > 0 ? { snapshotRetryCount } : {}), tabs };
@@ -849,7 +857,7 @@ function buildTimeoutProgressSteps(options) {
849
857
  export async function collectTimeoutPartialProgress(options) {
850
858
  const rawSteps = getTimeoutProgressSteps(options.compiledJob, options.command, options.stdin);
851
859
  const artifacts = await collectTimeoutArtifactEvidence(options.cwd, rawSteps);
852
- const [urlData, titleData] = await Promise.all([runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, sessionName: options.sessionName }), runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, sessionName: options.sessionName })]);
860
+ const [urlData, titleData] = await Promise.all([runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName }), runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName })]);
853
861
  const recoveredUrl = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
854
862
  const title = extractStringResultField(titleData, "result") ?? extractStringResultField(titleData, "title");
855
863
  const plannedUrl = recoveredUrl ? undefined : getPlannedCurrentPageUrl(rawSteps);
@@ -2,7 +2,7 @@ import { cleanupElectronLaunchResources } from "../../electron/cleanup.js";
2
2
  import { getCompiledSemanticActionCommandIndex, getCompiledSemanticActionSessionPrefix, isCompiledSemanticActionFindCommand, redactNetworkSourceLookupSurface, } from "../../input-modes.js";
3
3
  import { buildAgentBrowserNextActions, buildAgentBrowserResultCategoryDetails, } from "../../results.js";
4
4
  import { formatSessionArtifactRetentionSummary } from "../../results/artifact-manifest.js";
5
- import { AgentBrowserNextActionCollector, alignPageChangeSummaryNextActionIds, isStandaloneSnapshotNextAction, withOptionalSessionArgs, } from "../../results/next-actions.js";
5
+ import { AgentBrowserNextActionCollector, alignPageChangeSummaryNextActionIds, applyNamespaceToNextActions, isStandaloneSnapshotNextAction, withOptionalSessionArgs, } from "../../results/next-actions.js";
6
6
  import { buildConnectedSessionNextActions, buildNoActivePageNextActions, buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions, } from "../../results/recovery-next-actions.js";
7
7
  import { buildRichInputRecoveryDiagnostic, buildRichInputRecoveryNextActions, buildVisibleRefFallbackNextActions, formatRichInputRecoveryText, formatVisibleRefFallbackText, sanitizeVisibleRefFallbackDiagnostic, } from "../../results/selector-recovery.js";
8
8
  import { buildNoActivePageRefSnapshotInvalidation, isNoActivePageSnapshotFailure, } from "../../session-page-state.js";
@@ -10,7 +10,7 @@ import { extractExplicitSessionName, redactInvocationArgs, redactSensitiveText,
10
10
  import { isRecord } from "../../parsing.js";
11
11
  import { buildClickDispatchNextActions, formatClickDispatchDiagnosticText } from "./click-dispatch.js";
12
12
  import { buildComboboxFocusNextActions, buildElectronBroadGetTextScopeNextActions, buildFillVerificationNextActions, buildOverlayBlockerNextActions, buildScrollNoopNextActions, buildSelectorTextVisibilityNextActions, buildSourceLookupElectronNextActions, collectVisibleRefFallbackDiagnostic, formatArtifactCleanupGuidanceText, formatComboboxFocusDiagnosticText, formatElectronBroadGetTextScopeText, formatEvalResultWarningText, formatEvalStdinHintText, formatFillVerificationText, formatOverlayBlockerText, formatRecordingDependencyWarningText, formatScrollNoopDiagnosticText, formatSelectorTextVisibilityText, formatTimeoutPartialProgressText, } from "./diagnostics.js";
13
- import { buildElectronIdentifiers, buildElectronLifecycleNextActions, buildElectronMismatchNextActions, buildElectronRefFreshnessNextActions, buildManagedSessionFreshFailureNextActions, buildManagedSessionOutcome, buildSessionDetailFields, formatElectronRefFreshnessText, formatManagedSessionOutcomeText, } from "./session-state.js";
13
+ import { buildElectronIdentifiers, buildElectronLifecycleNextActions, buildElectronMismatchNextActions, buildElectronRefFreshnessNextActions, buildManagedSessionFreshFailureNextActions, buildManagedSessionOutcome, buildSessionDetailFields, getSessionContextKey, formatElectronRefFreshnessText, formatManagedSessionOutcomeText, } from "./session-state.js";
14
14
  export function buildMissingBinaryMessage() {
15
15
  return [
16
16
  "agent-browser is required but was not found on PATH.",
@@ -180,17 +180,19 @@ export async function prepareFinalResultRecoveryState(options) {
180
180
  const visibleRefFallbackSessionName = options.executionPlan.sessionName ?? extractExplicitSessionName(options.runtimeToolArgs);
181
181
  if (categoryDetails.failureCategory === "selector-not-found") {
182
182
  const selectorRecoveryCommandTokens = options.presentation.batchFailure?.failedStep.command ?? options.commandTokens;
183
- visibleRefFallbackDiagnostic = await collectVisibleRefFallbackDiagnostic({ commandTokens: selectorRecoveryCommandTokens, compiledSemanticAction: options.compiledSemanticAction, cwd: options.cwd, sessionName: visibleRefFallbackSessionName, signal: options.signal });
184
- if (visibleRefFallbackDiagnostic && visibleRefFallbackSessionName) {
185
- const refUpdate = options.sessionPageState.applyRefSnapshot({ fallbackTarget: options.currentSessionTabTarget, sessionName: visibleRefFallbackSessionName, snapshot: visibleRefFallbackDiagnostic.snapshot, update: options.sessionPageStateUpdate });
183
+ visibleRefFallbackDiagnostic = await collectVisibleRefFallbackDiagnostic({ commandTokens: selectorRecoveryCommandTokens, compiledSemanticAction: options.compiledSemanticAction, cwd: options.cwd, namespace: options.executionPlan.namespace, sessionName: visibleRefFallbackSessionName, signal: options.signal });
184
+ const visibleRefFallbackSessionKey = getSessionContextKey(visibleRefFallbackSessionName, options.executionPlan.namespace);
185
+ if (visibleRefFallbackDiagnostic && visibleRefFallbackSessionKey) {
186
+ const refUpdate = options.sessionPageState.applyRefSnapshot({ fallbackTarget: options.currentSessionTabTarget, sessionName: visibleRefFallbackSessionKey, snapshot: visibleRefFallbackDiagnostic.snapshot, update: options.sessionPageStateUpdate });
186
187
  currentRefSnapshot = refUpdate.refSnapshot;
187
188
  currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
188
189
  }
189
190
  }
190
191
  const richInputRecoveryDiagnostic = buildRichInputRecoveryDiagnostic(visibleRefFallbackDiagnostic);
191
192
  const noActivePageSnapshotFailure = categoryDetails.resultCategory === "failure" && (isNoActivePageSnapshotFailure(options.executionPlan.commandInfo.command, options.errorText ?? options.presentation.summary) || options.batchRefSnapshotState?.invalidation !== undefined);
192
- if (noActivePageSnapshotFailure && options.executionPlan.sessionName) {
193
- const refUpdate = options.sessionPageState.applyRefSnapshotInvalidation({ invalidation: buildNoActivePageRefSnapshotInvalidation(), sessionName: options.executionPlan.sessionName, update: options.sessionPageStateUpdate });
193
+ const executionSessionKey = getSessionContextKey(options.executionPlan.sessionName, options.executionPlan.namespace);
194
+ if (noActivePageSnapshotFailure && executionSessionKey) {
195
+ const refUpdate = options.sessionPageState.applyRefSnapshotInvalidation({ invalidation: buildNoActivePageRefSnapshotInvalidation(), sessionName: executionSessionKey, update: options.sessionPageStateUpdate });
194
196
  currentRefSnapshot = refUpdate.refSnapshot;
195
197
  currentRefSnapshotInvalidation = refUpdate.refSnapshotInvalidation;
196
198
  }
@@ -385,6 +387,7 @@ function buildAgentBrowserResultDetails(options, nextActions) {
385
387
  sessionTabTarget: options.currentSessionTabTarget,
386
388
  refSnapshot: options.currentRefSnapshot,
387
389
  refSnapshotInvalidation: options.currentRefSnapshotInvalidation,
390
+ namespace: options.executionPlan.namespace,
388
391
  ...buildSessionDetailFields(options.executionPlan.sessionName, options.executionPlan.usedImplicitSession),
389
392
  sessionRecoveryHint: options.redactedRecoveryHint,
390
393
  startupScopedFlags: options.executionPlan.startupScopedFlags,
@@ -396,7 +399,7 @@ function buildAgentBrowserResultDetails(options, nextActions) {
396
399
  };
397
400
  }
398
401
  export function buildFinalAgentBrowserToolResult(options) {
399
- const nextActions = buildResultNextActions(options);
402
+ const nextActions = applyNamespaceToNextActions(buildResultNextActions(options), options.executionPlan.namespace);
400
403
  const details = buildAgentBrowserResultDetails(options, nextActions);
401
404
  const visibleRefFallbackText = formatVisibleRefFallbackText(options.visibleRefFallbackDiagnostic);
402
405
  const richInputRecoveryText = formatRichInputRecoveryText(options.richInputRecoveryDiagnostic);
@@ -429,9 +432,9 @@ export async function buildMissingBinaryFailureResult(options) {
429
432
  if (!options.processResult.spawnError?.message.includes("ENOENT"))
430
433
  return undefined;
431
434
  const errorText = buildMissingBinaryMessage();
432
- const managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: options.managedSessionActive, activeBefore: options.managedSessionActive, attemptedSessionName: options.executionPlan.managedSessionName, command: options.executionPlan.commandInfo.command, currentSessionName: options.managedSessionName, previousSessionName: options.managedSessionName, sessionMode: options.sessionMode, succeeded: false });
435
+ const managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: options.managedSessionActive, activeBefore: options.managedSessionActive, attemptedSessionName: options.executionPlan.managedSessionName, command: options.executionPlan.commandInfo.command, currentSessionName: options.managedSessionName, currentSessionNamespace: options.managedSessionNamespace, previousSessionName: options.managedSessionName, sessionMode: options.sessionMode, succeeded: false });
433
436
  const managedSessionOutcomeText = formatManagedSessionOutcomeText(managedSessionOutcome);
434
- const managedSessionRecoveryNextActions = buildManagedSessionFreshFailureNextActions(managedSessionOutcome);
437
+ const managedSessionRecoveryNextActions = applyNamespaceToNextActions(buildManagedSessionFreshFailureNextActions(managedSessionOutcome), options.executionPlan.namespace) ?? [];
435
438
  let missingBinaryElectronCleanup;
436
439
  let missingBinaryElectronRecord;
437
440
  if (options.electronLaunch) {
@@ -439,5 +442,5 @@ export async function buildMissingBinaryFailureResult(options) {
439
442
  missingBinaryElectronRecord = missingBinaryElectronCleanup.record;
440
443
  }
441
444
  const textParts = [errorText, managedSessionOutcomeText, missingBinaryElectronCleanup ? `Electron cleanup after failed attach: ${missingBinaryElectronCleanup.summary}` : undefined].filter((part) => part !== undefined && part.length > 0);
442
- 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, 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: options.processResult.spawnError.message, succeeded: false }), spawnError: options.processResult.spawnError.message }, isError: true };
445
+ 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: options.processResult.spawnError.message, succeeded: false }), spawnError: options.processResult.spawnError.message }, isError: true };
443
446
  }
@@ -4,7 +4,7 @@ import { applyBrowserRunStatePatch } from "./session-state.js";
4
4
  import { buildMissingBinaryFailureResult } from "./final-result.js";
5
5
  import { prepareBrowserRun } from "./prepare.js";
6
6
  import { processBrowserOutput } from "./process-output.js";
7
- export { closeManagedSession } from "./session-state.js";
7
+ export { closeManagedSession, getSessionContextKey } from "./session-state.js";
8
8
  export async function runAgentBrowserTool(options) {
9
9
  const preparedResult = await prepareBrowserRun(options);
10
10
  applyBrowserRunStatePatch(options.state, preparedResult.kind === "ready" ? preparedResult.prepared.statePatch : preparedResult.statePatch);
@@ -28,6 +28,7 @@ export async function runAgentBrowserTool(options) {
28
28
  implicitSessionCloseTimeoutMs: options.implicitSessionCloseTimeoutMs,
29
29
  managedSessionActive: options.state.managedSessionActive,
30
30
  managedSessionName: options.state.managedSessionName,
31
+ managedSessionNamespace: options.state.managedSessionNamespace,
31
32
  processResult,
32
33
  redactedArgs: prepared.redactedArgs,
33
34
  redactedProcessArgs: prepared.redactedProcessArgs,
@@ -42,6 +43,6 @@ export async function runAgentBrowserTool(options) {
42
43
  return output.result;
43
44
  }
44
45
  finally {
45
- await cleanupClickDispatchProbe({ cwd: options.cwd, probe: prepared.clickDispatchProbe, sessionName: prepared.executionPlan.sessionName });
46
+ await cleanupClickDispatchProbe({ cwd: options.cwd, namespace: prepared.executionPlan.namespace, probe: prepared.clickDispatchProbe, sessionName: prepared.executionPlan.sessionName });
46
47
  }
47
48
  }
@@ -29,6 +29,7 @@ export async function tryDirectAnchorDownload(options) {
29
29
  const probeData = await runSessionCommandData({
30
30
  args: ["eval", "--stdin"],
31
31
  cwd: options.cwd,
32
+ namespace: options.namespace,
32
33
  sessionName: options.sessionName,
33
34
  signal: options.signal,
34
35
  stdin: buildAnchorDownloadProbe(request.selector),
@@ -128,7 +129,7 @@ export async function tryDirectAnchorDownload(options) {
128
129
  savedFilePath: absolutePath,
129
130
  sessionMode: options.sessionMode,
130
131
  ...buildAgentBrowserResultCategoryDetails({ artifacts: [artifact], args: options.effectiveArgs, command: "download", savedFile, succeeded: true }),
131
- ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
132
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace),
132
133
  summary: `Download completed: ${absolutePath}`,
133
134
  },
134
135
  isError: false,
@@ -79,10 +79,10 @@ export async function tryNetworkRequestsPageFilter(options) {
79
79
  const request = parseNetworkRequestsPageFilterRequest(options.commandTokens);
80
80
  if (!request || !options.sessionName)
81
81
  return undefined;
82
- const currentUrl = extractCurrentUrl(await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal }));
82
+ const currentUrl = extractCurrentUrl(await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }));
83
83
  if (!currentUrl)
84
84
  return undefined;
85
- const networkData = await runSessionCommandData({ args: request.cleanArgs, cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
85
+ const networkData = await runSessionCommandData({ args: request.cleanArgs, cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
86
86
  const filtered = filterNetworkRequestsData(networkData, currentUrl, request);
87
87
  if (!filtered)
88
88
  return undefined;
@@ -100,7 +100,7 @@ export async function tryNetworkRequestsPageFilter(options) {
100
100
  networkRequestsPageFilter: { cleanArgs: request.cleanArgs, currentUrl: redactSensitiveText(currentUrl), matchedRows: filtered.matchedRows, mode: request.mode, totalRows: filtered.totalRows },
101
101
  sessionMode: options.sessionMode,
102
102
  ...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: "network", succeeded: true }),
103
- ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
103
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace),
104
104
  summary,
105
105
  },
106
106
  isError: false,
@@ -1,5 +1,6 @@
1
1
  import { isRecord } from "../../../parsing.js";
2
2
  import { buildAgentBrowserResultCategoryDetails } from "../../../results.js";
3
+ import { applyNamespaceToNextActions } from "../../../results/next-actions.js";
3
4
  import { buildScrollNoopNextActions } from "../diagnostics.js";
4
5
  import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
5
6
  const SCROLL_CONTAINER_DIRECTIONS = new Set(["down", "left", "right", "up"]);
@@ -52,11 +53,11 @@ function buildScrollResult(options) {
52
53
  compatibilityWorkaround: options.compatibilityWorkaround,
53
54
  data: options.result,
54
55
  effectiveArgs: options.effectiveArgs,
55
- nextActions: options.succeeded ? undefined : buildScrollNoopNextActions(options.sessionName),
56
+ nextActions: options.succeeded ? undefined : applyNamespaceToNextActions(buildScrollNoopNextActions(options.sessionName), options.namespace),
56
57
  [options.scrollField]: options.scrollValue,
57
58
  sessionMode: options.sessionMode,
58
59
  ...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: options.command, errorText: options.succeeded ? undefined : options.message, succeeded: options.succeeded, validationError: options.succeeded ? undefined : options.message }),
59
- ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
60
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace),
60
61
  summary: options.message,
61
62
  validationError: options.succeeded ? undefined : options.message,
62
63
  },
@@ -67,7 +68,7 @@ export async function tryContainerScroll(options) {
67
68
  const request = getContainerScrollRequest(options.commandTokens);
68
69
  if (!request || !options.sessionName)
69
70
  return undefined;
70
- const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildContainerScrollScript(request) });
71
+ const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildContainerScrollScript(request) });
71
72
  const result = isRecord(data) && isRecord(data.result) ? data.result : data;
72
73
  if (!isRecord(result) || typeof result.status !== "string")
73
74
  return undefined;
@@ -102,7 +103,7 @@ export async function tryPageScrollTo(options) {
102
103
  const request = getPageScrollToRequest(options.commandTokens);
103
104
  if (!request || !options.sessionName)
104
105
  return undefined;
105
- const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildPageScrollToScript(request) });
106
+ const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildPageScrollToScript(request) });
106
107
  const result = isRecord(data) && isRecord(data.result) ? data.result : data;
107
108
  if (!isRecord(result) || typeof result.status !== "string")
108
109
  return undefined;
@@ -109,15 +109,15 @@ export async function trySnapshotFilter(options) {
109
109
  const request = parseSnapshotFilterRequest(options.commandTokens);
110
110
  if (!request || !options.sessionName)
111
111
  return undefined;
112
- const snapshotData = await runSessionCommandData({ args: request.cleanArgs, cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
112
+ const snapshotData = await runSessionCommandData({ args: request.cleanArgs, cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
113
113
  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
114
  if (!filtered)
115
115
  return undefined;
116
- const viewport = request.viewport ? await collectScrollPositionSnapshot({ cwd: options.cwd, sessionName: options.sessionName, signal: options.signal }) : undefined;
116
+ const viewport = request.viewport ? await collectScrollPositionSnapshot({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }) : undefined;
117
117
  const fullSnapshot = extractRefSnapshotFromData(snapshotData);
118
118
  const diff = request.diff ? buildSnapshotDiff(options.previousRefSnapshot, fullSnapshot) : undefined;
119
119
  if (fullSnapshot)
120
- options.sessionPageState.applyRefSnapshot({ sessionName: options.sessionName, snapshot: fullSnapshot, update: options.sessionPageStateUpdate });
120
+ options.sessionPageState.applyRefSnapshot({ sessionName: options.sessionStateKey ?? options.sessionName, snapshot: fullSnapshot, update: options.sessionPageStateUpdate });
121
121
  const presentation = await buildSnapshotPresentation(filtered.data, options.persistentArtifactStore, options.artifactManifest);
122
122
  const summary = request.role || request.search
123
123
  ? `Snapshot filter: ${filtered.matchedRefs}/${filtered.totalRefs} direct refs matched${request.role ? ` role=${request.role}` : ""}${request.search ? ` search ${JSON.stringify(request.search)}` : ""}; ${filtered.visibleLines} surrounding snapshot line${filtered.visibleLines === 1 ? "" : "s"} shown.`
@@ -149,7 +149,7 @@ export async function trySnapshotFilter(options) {
149
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,
150
150
  snapshotViewport: viewport,
151
151
  ...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: "snapshot", succeeded: true }),
152
- ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession),
152
+ ...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace),
153
153
  summary,
154
154
  },
155
155
  isError: false,