pi-agent-browser-native 0.2.77 → 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 (97) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +8 -17
  3. package/dist/extensions/agent-browser/index.js +127 -19
  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/managed-session-daemon-policy.js +17 -11
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +22 -5
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +35 -7
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +17 -1
  40. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +35 -18
  41. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +4 -1
  42. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
  43. package/dist/extensions/agent-browser/lib/parsing.js +0 -7
  44. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +4 -1
  45. package/dist/extensions/agent-browser/lib/playbook.js +3 -10
  46. package/dist/extensions/agent-browser/lib/process-identity.js +0 -5
  47. package/dist/extensions/agent-browser/lib/process.js +0 -7
  48. package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
  49. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -8
  50. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -7
  51. package/dist/extensions/agent-browser/lib/results/categories.js +1 -8
  52. package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
  53. package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
  54. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
  55. package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
  56. package/dist/extensions/agent-browser/lib/results/network.js +0 -7
  57. package/dist/extensions/agent-browser/lib/results/next-actions.js +0 -30
  58. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +1 -7
  59. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
  60. package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -5
  61. package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
  62. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +0 -5
  63. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
  64. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
  65. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +0 -5
  66. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +0 -5
  67. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
  68. package/dist/extensions/agent-browser/lib/results/presentation.js +0 -5
  69. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +0 -7
  70. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
  71. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +0 -7
  72. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +0 -7
  73. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
  74. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
  75. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
  76. package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
  77. package/dist/extensions/agent-browser/lib/results/text.js +0 -7
  78. package/dist/extensions/agent-browser/lib/runtime.js +4 -14
  79. package/dist/extensions/agent-browser/lib/session-page-state.js +0 -7
  80. package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
  81. package/dist/extensions/agent-browser/lib/temp.js +0 -7
  82. package/dist/extensions/agent-browser/lib/web-search.js +0 -5
  83. package/docs/ARCHITECTURE.md +16 -16
  84. package/docs/COMMAND_REFERENCE.md +8 -7
  85. package/docs/ELECTRON.md +3 -19
  86. package/docs/RELEASE.md +5 -17
  87. package/docs/REQUIREMENTS.md +4 -4
  88. package/docs/SUPPORT_MATRIX.md +17 -17
  89. package/docs/TOOL_CONTRACT.md +18 -16
  90. package/docs/platform-smoke.md +1 -1
  91. package/package.json +5 -6
  92. package/scripts/doctor.mjs +1 -1
  93. package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
  94. package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
  95. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
  96. package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
  97. package/dist/extensions/agent-browser/lib/results.js +0 -14
@@ -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);
@@ -406,7 +417,16 @@ export async function processBrowserOutput(input) {
406
417
  networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
407
418
  sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
408
419
  const replacedSessionKey = replacedSessionStateKey ?? replacedManagedSessionName;
409
- const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, namespace: priorManagedSessionNamespace, preserveAttachedBrowserSession: state.attachedSessionKeys.has(replacedSessionKey), restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
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
+ }
410
430
  if (!replacedCloseError) {
411
431
  state.attachedSessionKeys.delete(replacedSessionKey);
412
432
  state.closedManagedSessionNames.add(replacedSessionKey);
@@ -434,7 +454,7 @@ export async function processBrowserOutput(input) {
434
454
  if (electronHandoff.error) {
435
455
  succeeded = false;
436
456
  presentationEnvelope = { error: electronHandoff.error, success: false };
437
- const closeError = await closeManagedSession({ cwd, namespace: prepared.executionPlan.namespace, policyLock: prepared.managedSessionPolicyLock, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession, 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 });
438
458
  electronFailedConnectCleanup = await cleanupElectronLaunchResources({ child: prepared.electronLaunch.child, record: electronLaunchRecord, timeoutMs: implicitSessionCloseTimeoutMs });
439
459
  electronLaunchRecord = electronFailedConnectCleanup.record;
440
460
  if (electronFailedConnectCleanup.partial) {
@@ -588,8 +608,16 @@ export async function processBrowserOutput(input) {
588
608
  if (sessionStateKey)
589
609
  currentSessionTabTarget = authoritativePageState?.tabTarget;
590
610
  const currentSessionTabTargetUnknown = authoritativePageState?.tabTargetUnknown === true ? true : undefined;
591
- 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 });
592
- 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 };
593
621
  return { result, statePatch };
594
622
  }
595
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,22 +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
7
  import { withAttachedBrowserSessionContext } from "../../process.js";
13
- import { buildAgentBrowserNextActions, buildAgentBrowserResultCategoryDetails } from "../../results.js";
8
+ import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
9
+ import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
14
10
  import { appendUniqueAgentBrowserNextActions } from "../../results/next-actions.js";
15
11
  import { extractRefSnapshotFromData, getSessionPageStateKey, isAboutBlankUrl, normalizeSessionTabTarget } from "../../session-page-state.js";
16
12
  import { redactSensitiveText } from "../../runtime.js";
17
13
  import { collectElectronManagedSessionTarget } from "../browser-run/diagnostics.js";
18
14
  import { buildElectronHostFailureResult, formatElectronTargetLines, redactToolDetails } from "../browser-run/final-result.js";
19
- import { acquireOwnedManagedSessionDaemonPolicy, closeManagedSession } from "../browser-run/managed-session-daemon-policy.js";
15
+ import { acquireOwnedManagedSessionDaemonPolicy, closeManagedSession, getRunningHeadedAutosavePolicyChangeError } from "../browser-run/managed-session-daemon-policy.js";
20
16
  import { buildElectronIdentifiers, buildElectronMismatchNextActions, buildElectronSessionMismatch, extractStringResultField, findElectronLaunchRecordForSession, formatElectronSessionMismatchText, getActiveElectronRecords, getLiveElectronRendererTargets, runSessionCommandData, } from "../browser-run/session-state.js";
21
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.";
22
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.";
@@ -398,9 +394,14 @@ function getElectronProbeSummary(probe) {
398
394
  class ElectronManagedSessionPolicyError extends Error {
399
395
  }
400
396
  async function withOwnedElectronManagedSessionPolicy(options, run) {
397
+ const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(options.headedManagedAutosaveInterval);
398
+ if (autosavePolicyChangeError)
399
+ throw new ElectronManagedSessionPolicyError(autosavePolicyChangeError);
401
400
  const context = buildOwnedManagedSessionRestoreContext({
402
401
  args: ["--namespace", options.namespace ?? "", "--session", options.sessionName, ...options.args],
403
402
  cwd: options.cwd,
403
+ headedManagedAutosaveDisabled: options.headedManagedAutosaveDisabled,
404
+ headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
404
405
  managedSessionName: options.sessionName,
405
406
  namespace: options.namespace,
406
407
  restoreState: options.restoreState,
@@ -581,6 +582,8 @@ function buildElectronProbeResult(options) {
581
582
  },
582
583
  nextActions: nextActions.length > 0 ? nextActions : undefined,
583
584
  ...buildAgentBrowserResultCategoryDetails({ args: [], succeeded: true }),
585
+ managedSessionHeadedAutosaveDisabled: options.headedManagedAutosaveDisabled === true ? true : undefined,
586
+ managedSessionHeadedAutosaveInterval: options.headedManagedAutosaveInterval,
584
587
  namespace: options.namespace,
585
588
  refSnapshot: options.probe.refSnapshot,
586
589
  sessionName: options.probe.sessionName,
@@ -597,8 +600,10 @@ function buildElectronProbeResult(options) {
597
600
  export async function cleanupTrackedElectronHostLaunches(options) {
598
601
  const results = [];
599
602
  for (const record of options.records) {
603
+ const sessionKey = getSessionPageStateKey(record.sessionName) ?? record.sessionName;
604
+ const managedSessionOwner = sessionKey ? options.ownedManagedSessions.get(sessionKey) : undefined;
600
605
  const managedSessionCloseError = record.sessionName
601
- ? await closeManagedSession({ cwd: options.cwd, preserveAttachedBrowserSession: options.attachedSessionKeys.has(getSessionPageStateKey(record.sessionName) ?? record.sessionName), 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 })
602
607
  : undefined;
603
608
  const managedSessionStep = record.sessionName
604
609
  ? managedSessionCloseError
@@ -647,7 +652,7 @@ export async function handleElectronHostInput(options) {
647
652
  return await withAttachedBrowserSessionContext(preserveAttachedBrowserSession, () => handleElectronHostInputInContext(options));
648
653
  }
649
654
  async function handleElectronHostInputInContext(options) {
650
- const { attachedSessionKeys, compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, managedSessionNamespace, managedSessionRestoreState, redactedCompiledElectron, sessionPageState, signal, } = options;
655
+ const { attachedSessionKeys, compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, managedSessionNamespace, managedSessionRestoreState, ownedManagedSessions, redactedCompiledElectron, sessionPageState, signal, } = options;
651
656
  if (compiledElectron?.action === "list") {
652
657
  try {
653
658
  const discovery = await discoverElectronApps({ maxResults: compiledElectron.maxResults, query: compiledElectron.query });
@@ -665,13 +670,18 @@ async function handleElectronHostInputInContext(options) {
665
670
  const statuses = await Promise.all(records.map((record) => inspectElectronLaunchStatus(record)));
666
671
  const managedSessions = await Promise.all(records
667
672
  .filter((record) => typeof record.sessionName === "string")
668
- .map((record) => collectOwnedElectronManagedSessionTarget({
669
- cwd,
670
- restoreState: managedSessionRestoreState,
671
- sessionName: record.sessionName,
672
- signal,
673
- timeoutMs: compiledElectron.timeoutMs,
674
- })));
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
+ }));
675
685
  const mismatches = managedSessions
676
686
  .map((managedSession) => {
677
687
  const record = records.find((candidate) => candidate.sessionName === managedSession.sessionName);
@@ -733,9 +743,14 @@ async function handleElectronHostInputInContext(options) {
733
743
  });
734
744
  if (fileAccessError)
735
745
  throw new ElectronManagedSessionPolicyError(fileAccessError);
746
+ const managedSessionOwner = ownedManagedSessions.get(pageStateKey);
747
+ const headedManagedAutosaveDisabled = managedSessionOwner?.headedManagedAutosaveDisabled === true;
748
+ const headedManagedAutosaveInterval = managedSessionOwner?.headedManagedAutosaveInterval;
736
749
  const probe = await withOwnedElectronManagedSessionPolicy({
737
750
  args: ["snapshot", "-i"],
738
751
  cwd,
752
+ headedManagedAutosaveDisabled,
753
+ headedManagedAutosaveInterval,
739
754
  namespace: probeNamespace,
740
755
  restoreState: managedSessionRestoreState,
741
756
  sessionName: probeSessionName,
@@ -779,6 +794,8 @@ async function handleElectronHostInputInContext(options) {
779
794
  }
780
795
  return buildElectronProbeResult({
781
796
  compiledElectron: redactedCompiledElectron ?? compiledElectron,
797
+ headedManagedAutosaveDisabled,
798
+ headedManagedAutosaveInterval,
782
799
  mismatch: sessionMismatch,
783
800
  namespace: probeNamespace,
784
801
  probe,
@@ -801,7 +818,7 @@ async function handleElectronHostInputInContext(options) {
801
818
  const selection = selectElectronRecords(compiledElectron, electronLaunchRecords);
802
819
  if (selection.error)
803
820
  return buildElectronHostFailureResult({ compiledElectron: redactedCompiledElectron ?? compiledElectron, errorText: selection.error, failureCategory: "validation-error" });
804
- const cleanupResults = await cleanupTrackedElectronHostLaunches({ attachedSessionKeys, 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 });
805
822
  return buildElectronCleanupResult(redactedCompiledElectron ?? compiledElectron, cleanupResults);
806
823
  }
807
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;
@@ -1,11 +1,4 @@
1
1
  import { LAUNCH_SCOPED_FLAG_LABEL } from "./launch-scoped-flags.js";
2
- /**
3
- * Purpose: Provide the canonical agent_browser operating playbook shared by runtime prompt metadata and generated documentation fragments.
4
- * Responsibilities: Define stable guidance bullets, native tool-call examples, and wrapper-behavior notes without importing runtime/browser process code.
5
- * Scope: Agent-facing documentation and prompt-guidance text only; command execution and wrapper state behavior live in runtime modules.
6
- * Usage: Imported by the extension entrypoint for promptGuidelines and by the documentation drift-check script for generated Markdown blocks.
7
- * Invariants/Assumptions: The native pi tool receives args after the agent-browser binary, stdin is only for batch/eval --stdin/auth save --password-stdin, and wrapper behavior documented here must match implemented behavior.
8
- */
9
2
  export const PROJECT_RULE_PROMPT = "Project rule: when browser automation is needed, prefer the native `agent_browser` tool. Do not run direct `agent-browser` bash commands unless the user explicitly asks for a bash-oriented workflow or browser-integration debugging.";
10
3
  export const TOOL_PROMPT_GUIDELINES_PREFIX = [
11
4
  "Use agent_browser for real browser or live web content.",
@@ -21,7 +14,7 @@ export const QUICK_START_GUIDELINES = [
21
14
  `Common advanced calls: { args: ["batch", "--bail"], stdin: "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }, { job: { steps: [{ action: "open", url: "https://example.com" }, { action: "assertText", text: "Example Domain" }, { action: "screenshot", path: ".dogfood/example.png" }] } }, { qa: { url: "https://example.com", expectedText: "Example Domain", screenshotPath: ".dogfood/qa-example.png" } } (example.com smoke only; elsewhere match exact visible text from snapshot -i), { electron: { action: "list", query: "code" } }, { electron: { action: "launch", appName: "Visual Studio Code", handoff: "snapshot" } }, { electron: { action: "probe" } }, { qa: { attached: true, expectedText: "Explorer" } }, { args: ["eval", "--stdin"], stdin: "document.title", outputPath: "logs/page-title.json" }, { args: ["auth", "save", "name", "--password-stdin"], stdin: "<password from user-approved secret source>" }, { args: ["--profile", "Default", "open", "https://example.com/account"], sessionMode: "fresh" }, and { args: ["open", "--enable", "react-devtools", "https://example.com"], sessionMode: "fresh" }. For app pages with a native dropdown, job steps can include { action: "select", selector: "#flavor", value: "chocolate" } before the dependent assertion; for locator-friendly pages, job click/fill steps can use semantic locator fields such as { action: "fill", locator: "role", role: "searchbox", name: "Search", text: "agent browser" }; for human-paced input, job type steps can use { action: "type", selector: "#prompt", text: "hello", delayMs: 20, press: "Enter" }; delayed typing is capped at 200 characters per step, and generated per-character rows are compacted in visible batch prose while full rows remain in details.batchSteps.`,
22
15
  "Constrained job navigation is explicit only: click (and select/submit flows that may navigate) does not prove the next page loaded; add assertUrl and/or assertText after navigation-prone steps before screenshot or later interactions. Keep jobs short around navigation, click, and rerender boundaries on dynamic React/product apps; avoid a whole checkout in one job. If a long job times out and details.timeoutPartialProgress shows a mutating incomplete step, inspect current page state and continue with a shorter job or single action instead of blindly retrying the mutating step. Example: { job: { steps: [{ action: \"open\", url: \"https://shop.example/checkout\" }, { action: \"fill\", selector: \"#email\", text: \"user@example.com\" }, { action: \"click\", selector: \"#continue\" }, { action: \"assertUrl\", url: \"**/shipping\" }, { action: \"assertText\", text: \"Shipping address\" }, { action: \"screenshot\", path: \".dogfood/shipping.png\" }] } }. Top-level click may add pageChangeSummary hints, but job never auto-inserts post-click asserts.",
23
16
  "High-value command reference: click <selector> --new-tab opens link-like targets in a new tab; select <selector> <value...> changes native dropdown values; wrapper-handled scroll <dir> [px|percent] and scroll to end/top target document scrolling before upstream fallback, while scroll <selector> <dir> [px|percent] targets nested scrollers; download <selector> <path> saves a file triggered by a click; read [url] returns agent-readable text (explicit URLs prefer markdown without launching Chrome; omit the URL for rendered active-tab DOM); get title/url need no selector; get text/html/value/count <selector> and get attr <selector> <name> read elements/page state (use body for whole-page text/html); screenshot [selector] [path] captures a page or element image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab; react tree, react inspect <fiberId>, react renders start/stop, and react suspense introspect React after --enable react-devtools; vitals [url] measures Core Web Vitals; pushstate <url> performs SPA navigation; tap <selector> and swipe <direction> [distance] support iOS/provider touch flows.",
24
- "For artifact-producing commands, read the visible artifact block and details.artifactVerification before using files: check requested path, absolute path, existence, size bytes, artifact kind, optional mediaType, status, optional limitation, and verified/missing/pending/unverified counts. details.artifacts contains per-file metadata; record start rows are pending/openRecording until record stop writes the target. The wrapper creates parent directories for direct artifact paths and can save simple loopback HTTP(S) anchor downloads directly to the requested path before upstream download fallback. Browser close does not delete explicit saved files; if close reports details.artifactCleanup, use host file tools to remove paths listed in explicitArtifactPaths (when non-empty) after inspection. If close fails with details.promptGuard.reason=requested-artifacts-missing-before-close, save the exact required artifact path before closing. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step; if annotation labels crowd a dense page, use a scoped or non-annotated screenshot plus snapshot refs instead.",
17
+ "For artifact-producing commands, read the visible artifact block and details.artifactVerification before using files: check requested path, absolute path, existence, size bytes, artifact kind, optional mediaType, status, optional limitation, and verified/missing/pending/unverified counts. details.artifacts contains per-file metadata; record start rows are pending/openRecording until record stop writes the target. The wrapper creates parent directories for direct artifact paths and can save simple loopback HTTP(S) anchor downloads directly to the requested path before upstream download fallback. Browser close does not delete explicit saved files; if close reports details.artifactCleanup, use host file tools to remove paths listed in explicitArtifactPaths (when non-empty) after inspection. If close fails with details.promptGuard.reason=requested-artifacts-missing-before-close, save the exact required artifact path before closing. A bare inbound image/video path is not a requested output artifact and does not block close. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step; if annotation labels crowd a dense page, use a scoped or non-annotated screenshot plus snapshot refs instead.",
25
18
  "When details.nextActions is present, prefer those exact native agent_browser follow-up payloads over prose guidance; they may include args, stdin, sessionMode, networkSourceLookup, safety notes, or artifactPath for saved files.",
26
19
  ];
27
20
  export const WEB_SEARCH_PROMPT_GUIDELINE = "Use agent_browser_web_search for quick live search/URL discovery; prefer it over public search-engine forms that can hit anti-bot/CAPTCHA-gated pages. Use agent_browser after you have a target URL; one query, one follow-up max; stop on HTTP 429.";
@@ -44,7 +37,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
44
37
  "For React introspection, launch the page with --enable react-devtools before first navigation, then use react tree, react inspect <fiberId>, sourceLookup candidates for local UI source hints, react renders start/stop, or react suspense; sourceLookup is experimental and reports confidence/evidence instead of guaranteed DOM-to-file mappings. For failed fetches and APIs, networkSourceLookup (experimental) correlates failed network requests with initiator metadata and bounded workspace URL literals—candidates only, not definitive blame. Use vitals [url] for Core Web Vitals and hydration timing, and pushstate <url> for client-side SPA navigation.",
45
38
  "For first-navigation setup, use open without a URL plus network route --resource-type <csv>, cookies set --curl <file>, or --init-script/--enable before navigate/opening the target page.",
46
39
  "For stateful browser context work, prefer purpose-specific page actions before dumping browser data: use auth save --password-stdin with the tool stdin field for credentials, auth list/show/delete/remove for local auth-profile maintenance, auth login when you need the browser to fill a saved profile, state save/load for portable test state, state list/show/rename and targeted `state clear <caller-owned-name>` for saved-state lifecycle cleanup (the native wrapper blocks broad clear/clean and managed targets), cookies get/set/clear and storage local|session only when the task needs those values, and expect cookie/storage/auth/state summaries to redact credential-like fields while allowing benign primitive storage values when useful for local QA.",
47
- "Upstream restore sessions periodically autosave cookies and localStorage while the browser stays open, including page-driven background changes; AGENT_BROWSER_AUTOSAVE_INTERVAL_MS controls the interval (30000 by default; 0 disables periodic saves but keeps save-on-close), while the never value for --restore-save disables automatic saves for that restore session.",
40
+ "Upstream restore sessions periodically autosave cookies and localStorage while the browser stays open, including page-driven background changes; AGENT_BROWSER_AUTOSAVE_INTERVAL_MS controls the interval (30000 by default; 0 disables periodic saves but keeps save-on-close), while the never value for --restore-save disables automatic saves for that restore session. For wrapper-owned headed launches, the wrapper defaults the interval to 0 because upstream 0.33.2 collects multi-origin storage through visible temporary tabs, then records and reapplies the effective launch-time value to helpers and follow-ups so daemon configuration remains stable. Native close still saves, but direct window close can lose newer state because upstream exempts headed browsers from idle shutdown; set AGENT_BROWSER_AUTOSAVE_INTERVAL_MS before launch when periodic preservation matters, because changing it on a running wrapper-owned headed session requires close plus a fresh launch.",
48
41
  "For batch chains that touch cookies, storage, auth, or other secret-bearing commands, use details.batchSteps for per-step artifacts, categories, spill paths, and full structured errors; top-level details.data on batch is only a compact redacted step matrix (success, argv-redacted command, redacted result or scrubbed error text) built from the same presentation rules as standalone calls.",
49
42
  "For non-core families, pass current upstream commands through the native tool directly: network requests, network route <url>, network har start/stop [path], diff snapshot, diff screenshot --baseline <file>, diff url <u1> <u2>, trace start, trace stop [path], profiler start, profiler stop [path], record start <path>, record stop, console/errors [--clear], highlight <selector>, inspect, clipboard read, clipboard write <text>, clipboard copy/paste, stream enable/disable/status, dashboard start/stop, device list for iOS simulator inventory, and chat <message>. For compact network requests output, prefer details.nextActions for request detail, route-mock diagnostics, actionable failed-request networkSourceLookup, filtering, clearing the aggregate buffer before repro, or HAR capture follow-ups instead of guessing request-id syntax. Artifact-producing commands report details.artifacts and verification state; long-running starts such as stream, dashboard, trace/profiler, and record should be paired with the matching stop/disable command when the task is done; stream enable already-enabled outcomes are treated as idempotent success with status/disable follow-ups.",
50
43
  "For Electron desktop apps, prefer top-level electron for wrapper-owned discovery, isolated launch, status, compact probe, and cleanup: list first, treat likely-sensitive annotations as hints rather than enforcement, launch with the default snapshot handoff unless handoff: \"tabs\" is the safer diagnostic starting point, use electron.probe or snapshot -i/qa.attached for current-session state, and always cleanup the returned launchId when done. electron.launch uses an isolated temporary profile; it does not reuse the app's normal signed-in profile or attach to an already-running authenticated app. For signed-in local app state, host-launch the normal app with --remote-debugging-port when appropriate, then use raw args connect <port|url>; after connect, run get url to verify the active target before page-content reads, inspect tab list, select the stable tab id such as tab t2, verify it again with get url, then run a condition wait or snapshot -i before using refs. close commands (`close`, `quit`, or `exit`) only close the browser/CDP session; leave manually launched app shutdown, profile cleanup, and explicit artifacts to the host owner.",
@@ -56,7 +49,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
56
49
  "For downloads, prefer download <selector> <path> when an element click should save a file; simple loopback anchor downloads are saved to the requested path when the wrapper can resolve an HTTP(S) href. Do not rely on click alone when you need the downloaded file on disk.",
57
50
  "On dashboards with nested scroll containers, verify scroll with a screenshot or fresh snapshot -i; if the viewport did not move, details.data.scrolled may be false/noMovement true and you should prefer scrollintoview <@ref> or target the actual scrollable region with scroll <selector> <dir> [px|percent]. For native selects, use select <selector> <value...> (or semanticAction/job select) instead of clicking option refs; for custom comboboxes, a click/semanticAction may only focus the field, so re-snapshot and fall back to type, press Enter/arrow keys, or visible option refs.",
58
51
  "When using eval --stdin, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.",
59
- "When using eval --stdin for extraction, pass the JavaScript through the native tool stdin field, not as an extra args token after --stdin, and return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); use outputPath when the eval/get/snapshot data should be saved as a durable local file. If a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. The native wrapper blocks follow-up inspection, scripting, and interaction on file:// pages to protect authenticated local browser state; use a reachable HTTP(S) fixture instead. If get text on a broad CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
52
+ "When using eval --stdin for extraction, pass the JavaScript through the native tool stdin field, not as an extra args token after --stdin, and return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); use outputPath when the eval/get/snapshot data should be saved as a durable local file, but never reuse a screenshot, download, recording, or other browser artifact destination as outputPath. If a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. The native wrapper blocks follow-up inspection, scripting, and interaction on file:// pages to protect authenticated local browser state; use a reachable HTTP(S) fixture instead. If get text on a broad CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
60
53
  "When details.pageChangeSummary is present, use changeType and summary as a compact signal for navigation, DOM mutation, confirmations, or artifacts; when nextActionIds is set, match those ids to entries in details.nextActions (or per-step nextActions inside batch) for concrete follow-up payloads instead of inferring from prose alone. If details.clickDispatch reports a click-dispatch miss, refresh/inspect/retry the real click first; for static local fixtures only, an explicit eval --stdin programmatic .click() can exercise app handlers, but treat it as an untrusted scripted workaround and never use it to bypass stop-before-submit/order/purchase boundaries. If a no-navigation click surfaces details.overlayBlockers, inspect the fresh snapshot evidence before using a close/dismiss candidate nextAction; ordinary page chrome without dialog/alertdialog evidence should not trigger this diagnostic.",
61
54
  "When commands save or spill files (screenshots, downloads, PDFs, traces, recordings, HAR, large snapshot spills), use the user's exact requested paths when given and treat paths as provisional until details.artifactVerification shows every row verified: branch on missingCount, pendingCount, unverifiedCount, per-entry state, and optional limitation before downstream file use or PASS/FAIL reporting.",
62
55
  "For evidence-only screenshots, QA captures, or other audit artifacts, save to an explicit path and branch on details.artifactVerification plus details.artifacts before reporting PASS/FAIL; do not require vision review of inline image attachments unless the user asked for visual inspection.",
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Describe the native command used to read a process start identity for PID-reuse-safe ownership checks.
3
- * Responsibilities: Keep POSIX and native-Windows process identity probes aligned across policy locks and temp cleanup.
4
- * Scope: Command construction, output normalization, and cached async lookup for the current process.
5
- */
6
1
  import { execFile } from "node:child_process";
7
2
  import { win32 } from "node:path";
8
3
  const WINDOWS_PROCESS_START_IDENTITY_PREFIX = "win32-powershell-ticks-v1:";
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Execute the upstream agent-browser binary for the pi-agent-browser extension.
3
- * Responsibilities: Validate POSIX socket storage, spawn the agent-browser subprocess, forward parent environment variables plus wrapper overrides, stream optional stdin, bound in-memory output buffering, spill oversized stdout safely to a private temp file under a disk budget, and honor abort signals.
4
- * Scope: Process execution only; argument planning, output formatting, and pi tool registration live elsewhere.
5
- * Usage: Called by the extension tool after argument validation and session planning are complete.
6
- * Invariants/Assumptions: The binary name is always `agent-browser`; Windows routes through PowerShell to invoke npm launchers with escaped argv; callers handle semantic success/error interpretation.
7
- */
8
1
  import { AsyncLocalStorage } from "node:async_hooks";
9
2
  import { spawn } from "node:child_process";
10
3
  import { lstat, mkdir, readdir } from "node:fs/promises";