pi-agent-browser-native 0.2.76 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +10 -19
  3. package/dist/extensions/agent-browser/index.js +221 -23
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +0 -5
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +4 -5
  6. package/dist/extensions/agent-browser/lib/command-policy.js +0 -5
  7. package/dist/extensions/agent-browser/lib/command-taxonomy.js +0 -6
  8. package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
  9. package/dist/extensions/agent-browser/lib/config.js +0 -6
  10. package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
  11. package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
  12. package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
  13. package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
  14. package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
  15. package/dist/extensions/agent-browser/lib/input-modes/job.js +0 -5
  16. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +0 -5
  17. package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -6
  18. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +0 -5
  19. package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
  20. package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
  21. package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
  22. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +4 -5
  23. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
  24. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +0 -3
  25. package/dist/extensions/agent-browser/lib/managed-session-restore.js +30 -13
  26. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +0 -5
  27. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +1 -7
  28. package/dist/extensions/agent-browser/lib/managed-session-storage.js +0 -5
  29. package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +1 -1
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +41 -30
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +4 -1
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +20 -11
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +42 -20
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +40 -9
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +17 -1
  41. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +42 -18
  42. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +4 -1
  43. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
  44. package/dist/extensions/agent-browser/lib/parsing.js +0 -7
  45. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +4 -1
  46. package/dist/extensions/agent-browser/lib/playbook.js +4 -10
  47. package/dist/extensions/agent-browser/lib/process-identity.js +0 -5
  48. package/dist/extensions/agent-browser/lib/process.js +12 -9
  49. package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
  50. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -8
  51. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -7
  52. package/dist/extensions/agent-browser/lib/results/categories.js +1 -8
  53. package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
  54. package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
  55. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
  56. package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
  57. package/dist/extensions/agent-browser/lib/results/network.js +0 -7
  58. package/dist/extensions/agent-browser/lib/results/next-actions.js +0 -30
  59. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +1 -7
  60. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
  61. package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -5
  62. package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
  63. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +0 -5
  64. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
  65. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
  66. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +0 -5
  67. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +0 -5
  68. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
  69. package/dist/extensions/agent-browser/lib/results/presentation.js +0 -5
  70. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +0 -7
  71. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
  72. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +0 -7
  73. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +0 -7
  74. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
  75. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
  76. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
  77. package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
  78. package/dist/extensions/agent-browser/lib/results/text.js +0 -7
  79. package/dist/extensions/agent-browser/lib/runtime.js +4 -14
  80. package/dist/extensions/agent-browser/lib/session-page-state.js +0 -7
  81. package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
  82. package/dist/extensions/agent-browser/lib/temp.js +0 -7
  83. package/dist/extensions/agent-browser/lib/web-search.js +0 -5
  84. package/docs/ARCHITECTURE.md +17 -16
  85. package/docs/COMMAND_REFERENCE.md +10 -9
  86. package/docs/ELECTRON.md +3 -19
  87. package/docs/RELEASE.md +5 -17
  88. package/docs/REQUIREMENTS.md +4 -4
  89. package/docs/SUPPORT_MATRIX.md +18 -17
  90. package/docs/TOOL_CONTRACT.md +23 -19
  91. package/docs/platform-smoke.md +1 -1
  92. package/package.json +5 -6
  93. package/scripts/doctor.mjs +1 -1
  94. package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
  95. package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
  96. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
  97. package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
  98. package/dist/extensions/agent-browser/lib/results.js +0 -14
@@ -1,25 +1,26 @@
1
1
  import { copyFile, mkdir } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
+ import { getBooleanFlagValue, isUpstreamEnvFlagEnabled } from "../../argv-grammar.js";
3
4
  import { isCloseCommand } from "../../command-taxonomy.js";
4
5
  import { cleanupElectronLaunchResources } from "../../electron/cleanup.js";
5
6
  import { launchElectronApp } from "../../electron/launch.js";
6
7
  import { pathExists } from "../../fs-utils.js";
7
- import { getCompiledSemanticActionSessionPrefix } from "../../input-modes.js";
8
+ import { getCompiledSemanticActionSessionPrefix } from "../../input-modes/semantic-action.js";
8
9
  import { tryDirectAnchorDownload } from "./prepare/direct-anchor-download.js";
9
10
  import { tryNetworkRequestsPageFilter } from "./prepare/network-page-filter.js";
10
11
  import { tryContainerScroll, tryPageScrollTo } from "./prepare/scroll-shims.js";
11
12
  import { trySnapshotFilter } from "./prepare/snapshot-filter.js";
12
13
  import { commandTimeoutNeedsActivePageUrl, getCommandAwareProcessTimeoutMs } from "./prepare/wait-timeouts.js";
13
- import { getPersistentSessionArtifactStore } from "./session-artifacts.js";
14
- import { buildAgentBrowserResultCategoryDetails } from "../../results.js";
14
+ import { getPersistentSessionArtifactStore } from "./session-state.js";
15
+ import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
15
16
  import { applyNamespaceToNextActions } from "../../results/next-actions.js";
16
17
  import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions } from "../../results/recovery-next-actions.js";
17
18
  import { resolveVisibleRefActionFromSnapshot } from "../../results/selector-recovery.js";
18
19
  import { extractRefSnapshotFromData } from "../../session-page-state.js";
19
20
  import { buildExecutionPlan, createFreshSessionName, extractCommandTokens, getDefaultHeadlessCompatUserAgent, redactInvocationArgs, } from "../../runtime.js";
20
- import { buildOwnedManagedSessionRestoreContext, canonicalizeOwnedManagedSessionCloseArgs, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
21
+ import { buildOwnedManagedSessionRestoreContext, canonicalizeOwnedManagedSessionCloseArgs, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
21
22
  import { getCallerOwnedSessionLivePageVerificationRequirement, getManagedSessionStateAccessValidationError, getManagedSessionTargetAccessValidationError, } from "../../managed-session-state-policy.js";
22
- import { acquireOwnedManagedSessionDaemonPolicy } from "./managed-session-daemon-policy.js";
23
+ import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError } from "./managed-session-daemon-policy.js";
23
24
  import { applyOpenResultTabCorrection, buildManagedSessionOutcome, buildPinnedBatchPlan, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, extractStringResultField, collectAnySessionTabSelection, collectSessionTabSelection, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
24
25
  import { parseBatchStdinJsonArray } from "../batch-stdin.js";
25
26
  import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
@@ -397,11 +398,27 @@ export async function prepareBrowserRun(options) {
397
398
  if (!executionPlan.validationError && managedStateAccessError)
398
399
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: managedStateAccessError };
399
400
  const recordedOwnedSession = ownedSessionKey ? state.ownedManagedSessions.get(ownedSessionKey) : undefined;
401
+ const targetsCurrentManagedSession = state.managedSessionActive
402
+ && ownedSessionKey === getSessionContextKey(state.managedSessionName, state.managedSessionNamespace);
403
+ const retainedHeadedAutosaveDisabled = recordedOwnedSession?.headedManagedAutosaveDisabled === true
404
+ || (targetsCurrentManagedSession && state.managedSessionHeadedAutosaveDisabled === true);
405
+ const retainedHeadedAutosaveInterval = recordedOwnedSession?.headedManagedAutosaveInterval
406
+ ?? (targetsCurrentManagedSession ? state.managedSessionHeadedAutosaveInterval : undefined);
407
+ const explicitAutosaveInterval = resolveExplicitAutosaveInterval(process.env.AGENT_BROWSER_AUTOSAVE_INTERVAL_MS);
408
+ const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(retainedHeadedAutosaveInterval, isCloseCommand(executionPlan.commandInfo.command));
409
+ if (!executionPlan.validationError && autosavePolicyChangeError) {
410
+ executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: autosavePolicyChangeError };
411
+ }
412
+ const headedLaunch = getBooleanFlagValue(executionPlan.effectiveArgs, "--headed") ?? isUpstreamEnvFlagEnabled(process.env.AGENT_BROWSER_HEADED);
413
+ const headedManagedAutosaveDisabled = retainedHeadedAutosaveDisabled || (explicitAutosaveInterval === undefined && headedLaunch);
414
+ const headedManagedAutosaveInterval = retainedHeadedAutosaveInterval ?? (headedLaunch ? explicitAutosaveInterval ?? "0" : undefined);
400
415
  const ownedManagedSession = buildOwnedManagedSessionRestoreContext({
401
416
  args: executionPlan.effectiveArgs,
402
417
  cwd: recordedOwnedSession?.cwd ?? cwd,
403
418
  currentManagedSessionName: state.managedSessionName,
404
419
  currentManagedSessionNamespace: state.managedSessionNamespace,
420
+ headedManagedAutosaveDisabled,
421
+ headedManagedAutosaveInterval,
405
422
  managedSessionName: executionPlan.managedSessionName,
406
423
  namespace: executionPlan.namespace,
407
424
  recordedOwnedSession,
@@ -451,13 +468,18 @@ export async function prepareBrowserRun(options) {
451
468
  const priorRefSnapshotState = priorSessionPageState.refSnapshot;
452
469
  const priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
453
470
  let semanticActionVisibleRefResolution;
454
- let callerOwnedLivePageVerified = false;
471
+ let livePageVerified = false;
455
472
  const isCallerOwnedExplicitSession = () => executionPlan.sessionName !== undefined
456
473
  && executionPlan.usedImplicitSession === false
457
474
  && ownedManagedSession === undefined;
458
- const verifyCallerOwnedLivePage = async (options) => {
459
- if (!options.requirement || !executionPlan.sessionName)
475
+ const requiresLivePageVerification = () => isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true;
476
+ const verifyLivePage = async (request) => {
477
+ if (!request.requirement || !executionPlan.sessionName)
478
+ return;
479
+ if (options.establishAttachedBrowserSession) {
480
+ executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: request.requirement };
460
481
  return;
482
+ }
461
483
  let liveUrl;
462
484
  try {
463
485
  const liveUrlData = await runSessionCommandData({
@@ -475,27 +497,27 @@ export async function prepareBrowserRun(options) {
475
497
  throw signal.reason ?? error;
476
498
  }
477
499
  if (liveUrl === undefined) {
478
- executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: options.requirement };
500
+ executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: request.requirement };
479
501
  return;
480
502
  }
481
503
  const livePageValidationError = getManagedSessionStateAccessValidationError({
482
- args: options.args,
504
+ args: request.args,
483
505
  currentPageUrl: liveUrl,
484
506
  cwd,
485
507
  pageUrlUnknown: false,
486
- stdin: options.stdin,
508
+ stdin: request.stdin,
487
509
  });
488
510
  if (livePageValidationError) {
489
511
  executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: livePageValidationError };
490
512
  return;
491
513
  }
492
- callerOwnedLivePageVerified = true;
514
+ livePageVerified = true;
493
515
  priorSessionTabTarget ??= { url: liveUrl };
494
516
  priorSessionTabTargetUnknown = undefined;
495
517
  };
496
518
  const mayResolveSemanticVisibleRef = executionPlan.managedSessionName !== freshSessionName && canResolveSemanticVisibleRef(compiledSemanticAction);
497
- if (!executionPlan.validationError && mayResolveSemanticVisibleRef && isCallerOwnedExplicitSession()) {
498
- await verifyCallerOwnedLivePage({
519
+ if (!executionPlan.validationError && mayResolveSemanticVisibleRef && requiresLivePageVerification()) {
520
+ await verifyLivePage({
499
521
  args: ["snapshot", "-i"],
500
522
  requirement: getCallerOwnedSessionLivePageVerificationRequirement({ args: ["snapshot", "-i"], cwd }),
501
523
  });
@@ -530,21 +552,21 @@ export async function prepareBrowserRun(options) {
530
552
  refSnapshotInvalidation: resolvedSemanticActionRefSnapshot ? undefined : priorRefSnapshotInvalidation,
531
553
  stdin: runtimeToolStdin,
532
554
  });
533
- const callerOwnedPageAccessEligible = !executionPlan.validationError
555
+ const livePageAccessEligible = !executionPlan.validationError
534
556
  && preLiveStaleRefPreflight === undefined
535
557
  && validateStdinCommandContract({ command: executionPlan.commandInfo.command, commandTokens, stdin: runtimeToolStdin }) === undefined
536
- && isCallerOwnedExplicitSession();
537
- const callerOwnedLivePageRequirement = callerOwnedPageAccessEligible
538
- && !callerOwnedLivePageVerified
558
+ && requiresLivePageVerification();
559
+ const livePageRequirement = livePageAccessEligible
560
+ && !livePageVerified
539
561
  ? getCallerOwnedSessionLivePageVerificationRequirement({
540
562
  args: executionPlan.effectiveArgs,
541
563
  cwd,
542
564
  stdin: runtimeToolStdin,
543
565
  })
544
566
  : undefined;
545
- await verifyCallerOwnedLivePage({
567
+ await verifyLivePage({
546
568
  args: executionPlan.effectiveArgs,
547
- requirement: callerOwnedLivePageRequirement,
569
+ requirement: livePageRequirement,
548
570
  stdin: runtimeToolStdin,
549
571
  });
550
572
  const redactedEffectiveArgs = redactInvocationArgs(executionPlan.effectiveArgs);
@@ -5,8 +5,11 @@ import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
5
5
  import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
6
6
  import { getAllowedDomainsViolation, parseAllowedDomainsPolicyFromArgs } from "../../navigation-policy.js";
7
7
  import { getManagedSessionResultingPageState, getObservedBrowserPageValidationError, managedSessionCommandRequiresLivePageVerification } from "../../managed-session-state-policy.js";
8
- import { analyzeNetworkSourceLookupResults, analyzeQaPresetResults, analyzeQaPresetTimeout, analyzeSourceLookupResults, buildQaCompactPassText, extractQaPageContext, redactNetworkSourceLookupAnalysis, } from "../../input-modes.js";
9
- import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics, buildToolPresentation, getAgentBrowserErrorText, parseAgentBrowserEnvelope, } from "../../results.js";
8
+ import { analyzeNetworkSourceLookupResults, analyzeSourceLookupResults, redactNetworkSourceLookupAnalysis } from "../../input-modes/lookups.js";
9
+ import { analyzeQaPresetResults, analyzeQaPresetTimeout, buildQaCompactPassText, extractQaPageContext } from "../../input-modes/job.js";
10
+ import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../../results/network-routes.js";
11
+ import { buildToolPresentation } from "../../results/presentation.js";
12
+ import { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
10
13
  import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
11
14
  import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
12
15
  import { commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
@@ -19,7 +22,7 @@ import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBl
19
22
  import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
20
23
  import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
21
24
  import { repairScreenshotData } from "./prepare.js";
22
- import { getPersistentSessionArtifactStore } from "./session-artifacts.js";
25
+ import { getPersistentSessionArtifactStore } from "./session-state.js";
23
26
  import { buildFinalAgentBrowserToolResult, buildRedactedPresentationContent, buildWrapperRecoveryHint, prepareFinalResultRecoveryState, redactExactSensitiveValue, } from "./final-result.js";
24
27
  async function repairScreenshotArtifact(options) {
25
28
  const { cwd, envelope, request } = options;
@@ -105,6 +108,8 @@ export async function processBrowserOutput(input) {
105
108
  let freshSessionOrdinal = state.freshSessionOrdinal;
106
109
  let managedSessionActive = state.managedSessionActive;
107
110
  let managedSessionCompatibilityWorkaround = state.managedSessionCompatibilityWorkaround;
111
+ let managedSessionHeadedAutosaveDisabled = state.managedSessionHeadedAutosaveDisabled === true;
112
+ let managedSessionHeadedAutosaveInterval = state.managedSessionHeadedAutosaveInterval;
108
113
  let managedSessionCwd = state.managedSessionCwd;
109
114
  let managedSessionName = state.managedSessionName;
110
115
  let managedSessionNamespace = state.managedSessionNamespace;
@@ -327,6 +332,7 @@ export async function processBrowserOutput(input) {
327
332
  }
328
333
  const priorManagedSessionActive = managedSessionActive;
329
334
  const priorManagedSessionCwd = managedSessionCwd;
335
+ const priorManagedSessionHeadedAutosaveInterval = managedSessionHeadedAutosaveInterval;
330
336
  const priorManagedSessionName = managedSessionName;
331
337
  const priorManagedSessionNamespace = managedSessionNamespace;
332
338
  const commandClosesSession = isCloseCommand(prepared.executionPlan.commandInfo.command);
@@ -344,6 +350,7 @@ export async function processBrowserOutput(input) {
344
350
  const failedFreshDaemon = failedFreshSessionMayHaveStarted && prepared.executionPlan.sessionName
345
351
  ? await inspectManagedSessionDaemon({
346
352
  cwd,
353
+ headedManagedAutosaveInterval: prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval,
347
354
  namespace: prepared.executionPlan.namespace,
348
355
  sessionName: prepared.executionPlan.sessionName,
349
356
  timeoutMs: Math.min(implicitSessionCloseTimeoutMs, 2_000),
@@ -368,9 +375,13 @@ export async function processBrowserOutput(input) {
368
375
  === getAgentBrowserSessionIdentityKey(managedSessionName, managedSessionNamespace);
369
376
  if (!managedSessionActive) {
370
377
  managedSessionCompatibilityWorkaround = undefined;
378
+ managedSessionHeadedAutosaveDisabled = false;
379
+ managedSessionHeadedAutosaveInterval = undefined;
371
380
  }
372
381
  else if (managedTransitionSucceeded && executionTargetsManagedSession) {
373
382
  managedSessionCompatibilityWorkaround = prepared.compatibilityWorkaround;
383
+ managedSessionHeadedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true;
384
+ managedSessionHeadedAutosaveInterval = prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval;
374
385
  }
375
386
  if (commandClosesSession && succeeded && managedCloseSessionName === priorManagedSessionName && !managedSessionActive) {
376
387
  const daemonRestoreKey = state.managedSessionRestoreState.getDaemonRestoreKey(managedCloseSessionName, priorManagedSessionNamespace);
@@ -405,9 +416,21 @@ export async function processBrowserOutput(input) {
405
416
  networkRoutesBySession = new Map(networkRoutesBySession);
406
417
  networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
407
418
  sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
408
- const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, namespace: priorManagedSessionNamespace, restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
409
- if (!replacedCloseError)
410
- state.closedManagedSessionNames.add(replacedSessionStateKey ?? replacedManagedSessionName);
419
+ const replacedSessionKey = replacedSessionStateKey ?? replacedManagedSessionName;
420
+ const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, headedManagedAutosaveInterval: priorManagedSessionHeadedAutosaveInterval, namespace: priorManagedSessionNamespace, preserveAttachedBrowserSession: state.attachedSessionKeys.has(replacedSessionKey), restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
421
+ if (managedSessionOutcome) {
422
+ managedSessionOutcome = {
423
+ ...managedSessionOutcome,
424
+ replacedSessionClosed: !replacedCloseError,
425
+ summary: replacedCloseError
426
+ ? `${managedSessionOutcome.summary} Previous session ${replacedManagedSessionName} remains wrapper-owned because automatic close failed; retry an explicit close.`
427
+ : managedSessionOutcome.summary,
428
+ };
429
+ }
430
+ if (!replacedCloseError) {
431
+ state.attachedSessionKeys.delete(replacedSessionKey);
432
+ state.closedManagedSessionNames.add(replacedSessionKey);
433
+ }
411
434
  }
412
435
  let electronLaunchRecord;
413
436
  let electronFailedConnectCleanup = prepared.electronFailedConnectCleanup;
@@ -431,7 +454,7 @@ export async function processBrowserOutput(input) {
431
454
  if (electronHandoff.error) {
432
455
  succeeded = false;
433
456
  presentationEnvelope = { error: electronHandoff.error, success: false };
434
- const closeError = await closeManagedSession({ cwd, namespace: prepared.executionPlan.namespace, policyLock: prepared.managedSessionPolicyLock, restoreState: state.managedSessionRestoreState, sessionName: electronSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
457
+ const closeError = await closeManagedSession({ cwd, headedManagedAutosaveInterval: prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval, namespace: prepared.executionPlan.namespace, policyLock: prepared.managedSessionPolicyLock, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession, restoreState: state.managedSessionRestoreState, sessionName: electronSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
435
458
  electronFailedConnectCleanup = await cleanupElectronLaunchResources({ child: prepared.electronLaunch.child, record: electronLaunchRecord, timeoutMs: implicitSessionCloseTimeoutMs });
436
459
  electronLaunchRecord = electronFailedConnectCleanup.record;
437
460
  if (electronFailedConnectCleanup.partial) {
@@ -585,8 +608,16 @@ export async function processBrowserOutput(input) {
585
608
  if (sessionStateKey)
586
609
  currentSessionTabTarget = authoritativePageState?.tabTarget;
587
610
  const currentSessionTabTargetUnknown = authoritativePageState?.tabTargetUnknown === true ? true : undefined;
588
- const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, inspectionText, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
589
- const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
611
+ const resultRetainsPreparedManagedSession = !managedSessionOutcome || (managedSessionOutcome.activeAfter
612
+ && managedSessionOutcome.attemptedSessionName === managedSessionOutcome.currentSessionName);
613
+ const resultHeadedManagedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true
614
+ && resultRetainsPreparedManagedSession
615
+ && !(commandClosesSession && succeeded);
616
+ const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !(commandClosesSession && succeeded)
617
+ ? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
618
+ : undefined;
619
+ const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, inspectionText, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
620
+ const statePatch = { allowedDomainsBySession, artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionHeadedAutosaveDisabled, managedSessionHeadedAutosaveInterval, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
590
621
  return { result, statePatch };
591
622
  }
592
623
  finally {
@@ -1,6 +1,7 @@
1
1
  import { rm } from "node:fs/promises";
2
2
  import { runAgentBrowserProcess } from "../../process.js";
3
- import { buildAgentBrowserNextActions, parseAgentBrowserEnvelope } from "../../results.js";
3
+ import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
4
+ import { parseAgentBrowserEnvelope } from "../../results/envelope.js";
4
5
  import { buildNextToolAction, withOptionalNamespaceArgs, withOptionalSessionArgs } from "../../results/next-actions.js";
5
6
  import { getSessionPageStateKey, isAboutBlankUrl, normalizeComparableUrl, normalizeSessionTabTarget, targetsMatch, } from "../../session-page-state.js";
6
7
  import { isCloseCommand, isElectronPostCommandHealthCommand, isNavigationObservableCommandName, isRefGuardedCommand, isRefInvalidatingBatchCommand, isSessionTabPinningExcludedCommand, isSessionTabPostCommandCorrectionExcludedCommand, } from "../../command-taxonomy.js";
@@ -21,6 +22,10 @@ export function applyBrowserRunStatePatch(state, patch) {
21
22
  state.managedSessionActive = patch.managedSessionActive;
22
23
  if ("managedSessionCompatibilityWorkaround" in patch)
23
24
  state.managedSessionCompatibilityWorkaround = patch.managedSessionCompatibilityWorkaround;
25
+ if (patch.managedSessionHeadedAutosaveDisabled !== undefined)
26
+ state.managedSessionHeadedAutosaveDisabled = patch.managedSessionHeadedAutosaveDisabled;
27
+ if ("managedSessionHeadedAutosaveInterval" in patch)
28
+ state.managedSessionHeadedAutosaveInterval = patch.managedSessionHeadedAutosaveInterval;
24
29
  if (patch.managedSessionCwd !== undefined)
25
30
  state.managedSessionCwd = patch.managedSessionCwd;
26
31
  if (patch.managedSessionName !== undefined)
@@ -136,6 +141,12 @@ function formatManagedSessionOutcomeRecoveryGuidance(outcome) {
136
141
  export function formatManagedSessionOutcomeText(outcome) {
137
142
  if (!outcome)
138
143
  return undefined;
144
+ if (outcome.replacedSessionClosed === false) {
145
+ const cleanupWarning = "Cleanup warning: Automatic close of the previous wrapper-managed session failed, so it remains wrapper-owned. Use details.managedSessionOutcome for its exact identity and close it explicitly when safe.";
146
+ return outcome.succeeded
147
+ ? ["Managed session outcome: The fresh browser became current.", cleanupWarning].join("\n")
148
+ : [formatManagedSessionOutcomeHeadline(outcome), formatManagedSessionOutcomeRecoveryGuidance(outcome), cleanupWarning].join("\n");
149
+ }
139
150
  if (outcome.status === "closed" && outcome.succeeded) {
140
151
  return [
141
152
  "Managed session outcome: The current wrapper-managed browser session was closed.",
@@ -753,3 +764,8 @@ export function formatElectronRefFreshnessText(diagnostic) {
753
764
  return diagnostic?.summary;
754
765
  }
755
766
  export { extractBatchResultCommand };
767
+ export function getPersistentSessionArtifactStore(ctx) {
768
+ const sessionDir = typeof ctx.sessionManager.getSessionDir === "function" ? ctx.sessionManager.getSessionDir() : undefined;
769
+ const sessionId = ctx.sessionManager.getSessionId();
770
+ return sessionDir && sessionId ? { sessionDir, sessionId } : undefined;
771
+ }
@@ -1,21 +1,18 @@
1
- /**
2
- * Purpose: Own wrapper-side Electron host orchestration for agent_browser structured electron input.
3
- * Responsibilities: Discover Electron apps, inspect/probe/cleanup wrapper-tracked Electron launches, and build Pi-facing Electron host results.
4
- * Scope: Electron host actions that do not spawn the main upstream browser command; generic agent_browser execution stays in browser-run.
5
- */
6
1
  import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
7
2
  import { discoverElectronApps } from "../../electron/discovery.js";
8
- import { boundElectronProbeString } from "../../electron/text.js";
3
+ import { boundElectronProbeString } from "../../electron/cdp.js";
9
4
  import { buildOwnedManagedSessionRestoreContext, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
10
5
  import { getManagedSessionStateAccessValidationError } from "../../managed-session-state-policy.js";
11
6
  import { isRecord } from "../../parsing.js";
12
- import { buildAgentBrowserNextActions, buildAgentBrowserResultCategoryDetails } from "../../results.js";
7
+ import { withAttachedBrowserSessionContext } from "../../process.js";
8
+ import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
9
+ import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
13
10
  import { appendUniqueAgentBrowserNextActions } from "../../results/next-actions.js";
14
11
  import { extractRefSnapshotFromData, getSessionPageStateKey, isAboutBlankUrl, normalizeSessionTabTarget } from "../../session-page-state.js";
15
12
  import { redactSensitiveText } from "../../runtime.js";
16
13
  import { collectElectronManagedSessionTarget } from "../browser-run/diagnostics.js";
17
14
  import { buildElectronHostFailureResult, formatElectronTargetLines, redactToolDetails } from "../browser-run/final-result.js";
18
- import { acquireOwnedManagedSessionDaemonPolicy, closeManagedSession } from "../browser-run/managed-session-daemon-policy.js";
15
+ import { acquireOwnedManagedSessionDaemonPolicy, closeManagedSession, getRunningHeadedAutosavePolicyChangeError } from "../browser-run/managed-session-daemon-policy.js";
19
16
  import { buildElectronIdentifiers, buildElectronMismatchNextActions, buildElectronSessionMismatch, extractStringResultField, findElectronLaunchRecordForSession, formatElectronSessionMismatchText, getActiveElectronRecords, getLiveElectronRendererTargets, runSessionCommandData, } from "../browser-run/session-state.js";
20
17
  const ELECTRON_PROFILE_ISOLATION_NOTE = "Profile note: electron.launch starts an isolated temporary profile; it does not reuse the app's normal signed-in profile or attach to an already-running authenticated app.";
21
18
  const ELECTRON_EXISTING_AUTH_GUIDANCE = "For already-authenticated desktop app content, do not stop here: if host tools are allowed and the app is not running, launch the normal app with --remote-debugging-port=<port>, verify the port, then run agent_browser connect <port>; if it is already running without a debug port, ask before relaunching it.";
@@ -397,9 +394,14 @@ function getElectronProbeSummary(probe) {
397
394
  class ElectronManagedSessionPolicyError extends Error {
398
395
  }
399
396
  async function withOwnedElectronManagedSessionPolicy(options, run) {
397
+ const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(options.headedManagedAutosaveInterval);
398
+ if (autosavePolicyChangeError)
399
+ throw new ElectronManagedSessionPolicyError(autosavePolicyChangeError);
400
400
  const context = buildOwnedManagedSessionRestoreContext({
401
401
  args: ["--namespace", options.namespace ?? "", "--session", options.sessionName, ...options.args],
402
402
  cwd: options.cwd,
403
+ headedManagedAutosaveDisabled: options.headedManagedAutosaveDisabled,
404
+ headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
403
405
  managedSessionName: options.sessionName,
404
406
  namespace: options.namespace,
405
407
  restoreState: options.restoreState,
@@ -580,6 +582,8 @@ function buildElectronProbeResult(options) {
580
582
  },
581
583
  nextActions: nextActions.length > 0 ? nextActions : undefined,
582
584
  ...buildAgentBrowserResultCategoryDetails({ args: [], succeeded: true }),
585
+ managedSessionHeadedAutosaveDisabled: options.headedManagedAutosaveDisabled === true ? true : undefined,
586
+ managedSessionHeadedAutosaveInterval: options.headedManagedAutosaveInterval,
583
587
  namespace: options.namespace,
584
588
  refSnapshot: options.probe.refSnapshot,
585
589
  sessionName: options.probe.sessionName,
@@ -596,8 +600,10 @@ function buildElectronProbeResult(options) {
596
600
  export async function cleanupTrackedElectronHostLaunches(options) {
597
601
  const results = [];
598
602
  for (const record of options.records) {
603
+ const sessionKey = getSessionPageStateKey(record.sessionName) ?? record.sessionName;
604
+ const managedSessionOwner = sessionKey ? options.ownedManagedSessions.get(sessionKey) : undefined;
599
605
  const managedSessionCloseError = record.sessionName
600
- ? await closeManagedSession({ cwd: options.cwd, restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
606
+ ? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
601
607
  : undefined;
602
608
  const managedSessionStep = record.sessionName
603
609
  ? managedSessionCloseError
@@ -640,7 +646,13 @@ export async function cleanupActiveElectronHostLaunches(options) {
640
646
  : [];
641
647
  }
642
648
  export async function handleElectronHostInput(options) {
643
- const { compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, managedSessionNamespace, managedSessionRestoreState, redactedCompiledElectron, sessionPageState, signal, } = options;
649
+ const currentSessionKey = getSessionPageStateKey(options.managedSessionName, options.managedSessionNamespace) ?? options.managedSessionName;
650
+ const preserveAttachedBrowserSession = options.attachedSessionKeys.has(currentSessionKey)
651
+ || [...options.electronLaunchRecords.values()].some((record) => record.sessionName !== undefined && options.attachedSessionKeys.has(getSessionPageStateKey(record.sessionName) ?? record.sessionName));
652
+ return await withAttachedBrowserSessionContext(preserveAttachedBrowserSession, () => handleElectronHostInputInContext(options));
653
+ }
654
+ async function handleElectronHostInputInContext(options) {
655
+ const { attachedSessionKeys, compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, managedSessionNamespace, managedSessionRestoreState, ownedManagedSessions, redactedCompiledElectron, sessionPageState, signal, } = options;
644
656
  if (compiledElectron?.action === "list") {
645
657
  try {
646
658
  const discovery = await discoverElectronApps({ maxResults: compiledElectron.maxResults, query: compiledElectron.query });
@@ -658,13 +670,18 @@ export async function handleElectronHostInput(options) {
658
670
  const statuses = await Promise.all(records.map((record) => inspectElectronLaunchStatus(record)));
659
671
  const managedSessions = await Promise.all(records
660
672
  .filter((record) => typeof record.sessionName === "string")
661
- .map((record) => collectOwnedElectronManagedSessionTarget({
662
- cwd,
663
- restoreState: managedSessionRestoreState,
664
- sessionName: record.sessionName,
665
- signal,
666
- timeoutMs: compiledElectron.timeoutMs,
667
- })));
673
+ .map((record) => {
674
+ const sessionKey = getSessionPageStateKey(record.sessionName) ?? record.sessionName;
675
+ return collectOwnedElectronManagedSessionTarget({
676
+ cwd,
677
+ headedManagedAutosaveDisabled: ownedManagedSessions.get(sessionKey)?.headedManagedAutosaveDisabled,
678
+ headedManagedAutosaveInterval: ownedManagedSessions.get(sessionKey)?.headedManagedAutosaveInterval,
679
+ restoreState: managedSessionRestoreState,
680
+ sessionName: record.sessionName,
681
+ signal,
682
+ timeoutMs: compiledElectron.timeoutMs,
683
+ });
684
+ }));
668
685
  const mismatches = managedSessions
669
686
  .map((managedSession) => {
670
687
  const record = records.find((candidate) => candidate.sessionName === managedSession.sessionName);
@@ -726,9 +743,14 @@ export async function handleElectronHostInput(options) {
726
743
  });
727
744
  if (fileAccessError)
728
745
  throw new ElectronManagedSessionPolicyError(fileAccessError);
746
+ const managedSessionOwner = ownedManagedSessions.get(pageStateKey);
747
+ const headedManagedAutosaveDisabled = managedSessionOwner?.headedManagedAutosaveDisabled === true;
748
+ const headedManagedAutosaveInterval = managedSessionOwner?.headedManagedAutosaveInterval;
729
749
  const probe = await withOwnedElectronManagedSessionPolicy({
730
750
  args: ["snapshot", "-i"],
731
751
  cwd,
752
+ headedManagedAutosaveDisabled,
753
+ headedManagedAutosaveInterval,
732
754
  namespace: probeNamespace,
733
755
  restoreState: managedSessionRestoreState,
734
756
  sessionName: probeSessionName,
@@ -772,6 +794,8 @@ export async function handleElectronHostInput(options) {
772
794
  }
773
795
  return buildElectronProbeResult({
774
796
  compiledElectron: redactedCompiledElectron ?? compiledElectron,
797
+ headedManagedAutosaveDisabled,
798
+ headedManagedAutosaveInterval,
775
799
  mismatch: sessionMismatch,
776
800
  namespace: probeNamespace,
777
801
  probe,
@@ -794,7 +818,7 @@ export async function handleElectronHostInput(options) {
794
818
  const selection = selectElectronRecords(compiledElectron, electronLaunchRecords);
795
819
  if (selection.error)
796
820
  return buildElectronHostFailureResult({ compiledElectron: redactedCompiledElectron ?? compiledElectron, errorText: selection.error, failureCategory: "validation-error" });
797
- const cleanupResults = await cleanupTrackedElectronHostLaunches({ cwd, electronChildProcesses, electronLaunchRecords, managedSessionRestoreState, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
821
+ const cleanupResults = await cleanupTrackedElectronHostLaunches({ attachedSessionKeys, cwd, electronChildProcesses, electronLaunchRecords, managedSessionRestoreState, ownedManagedSessions, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
798
822
  return buildElectronCleanupResult(redactedCompiledElectron ?? compiledElectron, cleanupResults);
799
823
  }
800
824
  return undefined;
@@ -1,7 +1,10 @@
1
1
  import { parseArgvDescriptor } from "../argv-descriptor.js";
2
2
  import { validateToolArgs, redactInvocationArgs, redactSensitiveText } from "../runtime.js";
3
3
  import { buildAgentBrowserResultCategoryDetails } from "../results/categories.js";
4
- import { compileAgentBrowserElectron, compileAgentBrowserJob, compileAgentBrowserNetworkSourceLookup, compileAgentBrowserQaPreset, compileAgentBrowserSemanticAction, compileAgentBrowserSourceLookup, redactNetworkSourceLookupArgs, redactNetworkSourceLookupUrl, } from "../input-modes.js";
4
+ import { compileAgentBrowserElectron } from "../input-modes/electron.js";
5
+ import { compileAgentBrowserJob, compileAgentBrowserQaPreset } from "../input-modes/job.js";
6
+ import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup, redactNetworkSourceLookupArgs, redactNetworkSourceLookupUrl } from "../input-modes/lookups.js";
7
+ import { compileAgentBrowserSemanticAction } from "../input-modes/semantic-action.js";
5
8
  function redactCompiledElectron(compiled) {
6
9
  if (!compiled)
7
10
  return undefined;
@@ -1,4 +1,4 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
1
+ import { mkdir, realpath, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, resolve } from "node:path";
3
3
  import { getAgentBrowserStoragePathValidationError } from "../managed-session-state-policy.js";
4
4
  import { isRecord } from "../parsing.js";
@@ -31,6 +31,33 @@ function appendOutputFileNotice(result, message) {
31
31
  export function getAgentBrowserOutputPathValidationError(outputPath, cwd) {
32
32
  return outputPath ? getAgentBrowserStoragePathValidationError(normalizeRequestedOutputPath(outputPath), cwd) : undefined;
33
33
  }
34
+ function getArtifactPaths(result, cwd) {
35
+ const details = isRecord(result.details) ? result.details : undefined;
36
+ if (!details || !Array.isArray(details.artifacts))
37
+ return [];
38
+ return details.artifacts.flatMap((artifact) => {
39
+ if (!isRecord(artifact))
40
+ return [];
41
+ const path = typeof artifact.absolutePath === "string" ? artifact.absolutePath : typeof artifact.path === "string" ? artifact.path : undefined;
42
+ return path ? [isAbsolute(path) ? path : resolve(cwd, path)] : [];
43
+ });
44
+ }
45
+ async function pathsReferToSameFile(left, right) {
46
+ if (resolve(left) === resolve(right))
47
+ return true;
48
+ try {
49
+ if (await realpath(left) === await realpath(right))
50
+ return true;
51
+ }
52
+ catch { }
53
+ try {
54
+ const [leftStat, rightStat] = await Promise.all([stat(left), stat(right)]);
55
+ return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
34
61
  export async function applyAgentBrowserOutputPath(options) {
35
62
  if (!options.outputPath)
36
63
  return options.result;
@@ -47,6 +74,20 @@ export async function applyAgentBrowserOutputPath(options) {
47
74
  const requestedPath = normalizeRequestedOutputPath(options.outputPath);
48
75
  const absolutePath = isAbsolute(requestedPath) ? requestedPath : resolve(options.cwd, requestedPath);
49
76
  const payload = getOutputPayload(options.result);
77
+ for (const artifactPath of getArtifactPaths(options.result, options.cwd)) {
78
+ if (!await pathsReferToSameFile(absolutePath, artifactPath))
79
+ continue;
80
+ const message = "outputPath resolves to the same file as a browser artifact destination; choose a separate outputPath or omit it. The browser artifact was preserved.";
81
+ const outputFile = { absolutePath, error: message, path: requestedPath, source: payload.source, status: "failed" };
82
+ const details = isRecord(options.result.details) ? { ...options.result.details } : {};
83
+ delete details.successCategory;
84
+ return {
85
+ ...options.result,
86
+ content: appendOutputFileNotice(options.result, `Output file rejected: ${message}`),
87
+ details: { ...details, failureCategory: "validation-error", outputFile, resultCategory: "failure" },
88
+ isError: true,
89
+ };
90
+ }
50
91
  try {
51
92
  const serialized = serializeOutputPayload(payload.value);
52
93
  await mkdir(dirname(absolutePath), { recursive: true });
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Centralize low-level boundary parsing helpers shared by runtime planning, temp-artifact lifecycle, and result rendering.
3
- * Responsibilities: Identify non-null object records and normalize positive-integer string configuration values.
4
- * Scope: Tiny generic parsing predicates only; module-specific validation and error handling stay with their owning modules.
5
- * Usage: Imported by agent-browser wrapper modules that parse untyped JSON, persisted state, or environment variables.
6
- * Invariants/Assumptions: Arrays intentionally count as records to preserve existing object-boundary semantics, and positive integers must be safe base-10 integer strings greater than zero.
7
- */
8
1
  export function isRecord(value) {
9
2
  return typeof value === "object" && value !== null;
10
3
  }
@@ -1,5 +1,8 @@
1
1
  import { getKeybindings, Text, truncateToWidth } from "@earendil-works/pi-tui";
2
- import { compileAgentBrowserElectron, compileAgentBrowserJob, compileAgentBrowserNetworkSourceLookup, compileAgentBrowserQaPreset, compileAgentBrowserSemanticAction, compileAgentBrowserSourceLookup, } from "./input-modes.js";
2
+ import { compileAgentBrowserElectron } from "./input-modes/electron.js";
3
+ import { compileAgentBrowserJob, compileAgentBrowserQaPreset } from "./input-modes/job.js";
4
+ import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup } from "./input-modes/lookups.js";
5
+ import { compileAgentBrowserSemanticAction } from "./input-modes/semantic-action.js";
3
6
  import { isRecord } from "./parsing.js";
4
7
  import { redactInvocationArgs } from "./runtime.js";
5
8
  const TUI_INVOCATION_PREVIEW_MAX_CHARS = 160;