pi-agent-browser-native 0.9.2 → 0.9.3

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 (39) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +18 -6
  3. package/dist/extensions/agent-browser/index.js +39 -16
  4. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +6 -3
  5. package/dist/extensions/agent-browser/lib/browser-transcript.js +2 -1
  6. package/dist/extensions/agent-browser/lib/native-output.js +2 -1
  7. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +2 -8
  8. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +12 -2
  9. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +58 -7
  10. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +18 -12
  11. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +33 -24
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +111 -53
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +32 -20
  14. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +37 -32
  15. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +26 -8
  16. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -1
  17. package/dist/extensions/agent-browser/lib/page-target-validation.js +10 -3
  18. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +2 -1
  19. package/dist/extensions/agent-browser/lib/playbook.js +2 -2
  20. package/dist/extensions/agent-browser/lib/process-environment.js +12 -3
  21. package/dist/extensions/agent-browser/lib/process.js +6 -6
  22. package/dist/extensions/agent-browser/lib/read-confirmation.js +56 -16
  23. package/dist/extensions/agent-browser/lib/results/confirmation.js +3 -0
  24. package/dist/extensions/agent-browser/lib/results/envelope.js +5 -1
  25. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +3 -0
  26. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +34 -1
  27. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +5 -1
  28. package/dist/extensions/agent-browser/lib/results/presentation.js +15 -2
  29. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +5 -5
  30. package/dist/extensions/agent-browser/lib/runtime.js +5 -1
  31. package/dist/extensions/agent-browser/lib/session-page-state.js +35 -16
  32. package/docs/ARCHITECTURE.md +5 -5
  33. package/docs/COMMAND_REFERENCE.md +13 -9
  34. package/docs/RELEASE.md +6 -5
  35. package/docs/REQUIREMENTS.md +1 -0
  36. package/docs/SESSION_STORAGE.md +1 -1
  37. package/docs/SUPPORT_MATRIX.md +9 -3
  38. package/docs/TOOL_CONTRACT.md +20 -14
  39. package/package.json +4 -4
@@ -9,6 +9,7 @@ import { readProcessStartIdentity } from "../../process-identity.js";
9
9
  import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment } from "../../process-environment.js";
10
10
  import { runAgentBrowserProcess, withAttachedBrowserSessionContext } from "../../process.js";
11
11
  import { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
12
+ import { detectConfirmationRequired } from "../../results/confirmation.js";
12
13
  import { redactInvocationArgs } from "../../runtime.js";
13
14
  import { runSessionCommandData } from "./session-state.js";
14
15
  const MANAGED_SESSION_DAEMON_INSPECTION_TIMEOUT_MS = 35_000;
@@ -158,7 +159,7 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
158
159
  }
159
160
  }
160
161
  export async function closeManagedSession(options) {
161
- return withAgentBrowserProcessEnvironment(options.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: options.socketDir } : {}, async () => {
162
+ return withAgentBrowserProcessEnvironment({ AGENT_BROWSER_CONFIRM_ACTIONS: options.confirmActions, ...(options.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: options.socketDir } : {}) }, async () => {
162
163
  const controller = new AbortController();
163
164
  let phase = "policy coordination";
164
165
  let phaseStartedAt = Date.now();
@@ -209,28 +210,33 @@ export async function closeManagedSession(options) {
209
210
  });
210
211
  clearTimeout(timer);
211
212
  stdoutSpillPath = processResult.stdoutSpillPath;
213
+ const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath });
212
214
  if (!processResult.aborted && !processResult.spawnError && processResult.exitCode === 0) {
213
- const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath });
214
- const data = parsed.envelope?.success === true && isRecord(parsed.envelope.data) ? parsed.envelope.data : undefined;
215
- options.restoreState.clear(options.sessionName, options.namespace);
216
- pruneOwnedManagedSessionRestoreSnapshots({
217
- cwd: options.cwd,
218
- namespace: options.namespace,
219
- restoreKey: ownedRestoreKey,
220
- statePath: typeof data?.statePath === "string" ? data.statePath : undefined,
221
- });
215
+ const data = parsed.envelope?.success === true && isRecord(parsed.envelope.data) && parsed.envelope.data.closed === true ? parsed.envelope.data : undefined;
216
+ if (data) {
217
+ options.restoreState.clear(options.sessionName, options.namespace);
218
+ pruneOwnedManagedSessionRestoreSnapshots({
219
+ cwd: options.cwd,
220
+ namespace: options.namespace,
221
+ restoreKey: ownedRestoreKey,
222
+ statePath: typeof data.statePath === "string" ? data.statePath : undefined,
223
+ });
224
+ }
222
225
  }
223
- return timeoutError ?? getAgentBrowserErrorText({
226
+ const pending = detectConfirmationRequired(parsed.envelope?.data);
227
+ return timeoutError ?? (pending ? `Native close requires confirmation (${pending.id}); the session remains wrapper-owned.` : getAgentBrowserErrorText({
224
228
  aborted: processResult.aborted,
225
229
  command: "close",
226
230
  effectiveArgs: redactInvocationArgs(closeArgs),
231
+ envelope: parsed.envelope,
227
232
  exitCode: processResult.exitCode,
233
+ parseError: parsed.parseError,
228
234
  plainTextInspection: false,
229
235
  spawnError: processResult.spawnError,
230
236
  stderr: processResult.stderr,
231
237
  timedOut: processResult.timedOut,
232
238
  timeoutMs: processResult.timeoutMs,
233
- });
239
+ }));
234
240
  }
235
241
  catch (error) {
236
242
  return timeoutError ?? (error instanceof Error ? error.message : String(error));
@@ -15,6 +15,7 @@ import { tryContainerScroll, tryPageScrollTo } from "./prepare/scroll-shims.js";
15
15
  import { trySnapshotFilter } from "./prepare/snapshot-filter.js";
16
16
  import { commandTimeoutNeedsActivePageUrl, getCommandAwareProcessTimeoutMs } from "./prepare/wait-timeouts.js";
17
17
  import { getPersistentSessionArtifactStore } from "./session-state.js";
18
+ import { isBrowserIndependentConfirmation, suppressConfirmationPageHelpers } from "../../read-confirmation.js";
18
19
  import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
19
20
  import { applyNamespaceToNextActions } from "../../results/next-actions.js";
20
21
  import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions } from "../../results/recovery-next-actions.js";
@@ -145,7 +146,7 @@ async function prepareBatchScreenshotPaths(args, stdin, cwd) {
145
146
  return changed
146
147
  ? {
147
148
  args,
148
- batchScreenshotPathRequests,
149
+ batchScreenshotPathRequests: parsed.steps.flatMap((step, index) => Array.isArray(step) && step.length === 0 ? [] : [batchScreenshotPathRequests[index]]),
149
150
  stdin: JSON.stringify(preparedSteps),
150
151
  }
151
152
  : undefined;
@@ -325,19 +326,12 @@ function canResolveSemanticVisibleRef(compiled) {
325
326
  function requiresResolvedSemanticVisibleRef(compiled) {
326
327
  return compiled?.action === "select" && compiled.locator !== undefined;
327
328
  }
328
- function resolveSemanticActionVisibleRefArgsFromSnapshot(compiled, snapshotData) {
329
- if (!canResolveSemanticVisibleRef(compiled))
330
- return undefined;
331
- const resolution = resolveVisibleRefActionFromSnapshot({ allowFill: true, compiledAction: compiled, snapshotData });
332
- if (!resolution)
333
- return undefined;
334
- return { args: [...getCompiledSemanticActionSessionPrefix(compiled), ...resolution.args], snapshot: resolution.snapshot };
335
- }
336
329
  async function resolveSemanticActionVisibleRefArgs(options) {
337
- if (!options.compiled || !options.sessionName)
330
+ if (!canResolveSemanticVisibleRef(options.compiled) || !options.sessionName)
338
331
  return undefined;
339
- const snapshotData = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
340
- return resolveSemanticActionVisibleRefArgsFromSnapshot(options.compiled, snapshotData);
332
+ const snapshotData = options.refSnapshot ? undefined : await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
333
+ const resolution = resolveVisibleRefActionFromSnapshot({ allowFill: true, compiledAction: options.compiled, refSnapshot: options.refSnapshot, snapshotData });
334
+ return resolution ? { args: [...getCompiledSemanticActionSessionPrefix(options.compiled), ...resolution.args], snapshot: resolution.snapshot } : undefined;
341
335
  }
342
336
  export async function prepareBrowserRun(options) {
343
337
  const { cwd, onUpdate, params, signal, state } = options;
@@ -423,9 +417,10 @@ export async function prepareBrowserRun(options) {
423
417
  isError: true,
424
418
  } };
425
419
  }
426
- const userRequestedJson = runtimeToolArgs.includes("--json");
427
- const routedReadConfirmation = state.sessionPageState.findReadConfirmation(preparedArgs.args, resolveAgentBrowserNamespace(preparedArgs.args, agentBrowserProcessEnv.AGENT_BROWSER_NAMESPACE));
428
- const readConfirmation = routedReadConfirmation?.capabilities?.readRequiresConfirmation === true ? routedReadConfirmation : undefined;
420
+ const userRequestedJson = getBooleanFlagValue(runtimeToolArgs, "--json") === true;
421
+ const routedReadConfirmation = state.sessionPageState.findReadConfirmation(preparedArgs.args, resolveAgentBrowserNamespace(preparedArgs.args, agentBrowserProcessEnv.AGENT_BROWSER_NAMESPACE), runtimeToolStdin);
422
+ const readConfirmation = suppressConfirmationPageHelpers(routedReadConfirmation) ? routedReadConfirmation : undefined;
423
+ const browserIndependentConfirmation = isBrowserIndependentConfirmation(readConfirmation);
429
424
  let executionPlan = buildExecutionPlan(preparedArgs.args, {
430
425
  freshSessionName,
431
426
  managedSessionActive: state.managedSessionActive,
@@ -434,13 +429,14 @@ export async function prepareBrowserRun(options) {
434
429
  managedSessionNamespace: state.managedSessionNamespace,
435
430
  sessionMode,
436
431
  stdin: runtimeToolStdin,
437
- browserIndependentReadConfirmation: readConfirmation !== undefined,
432
+ browserIndependentReadConfirmation: browserIndependentConfirmation,
438
433
  });
439
- const browserIndependent = readConfirmation !== undefined || isBrowserIndependentRead(extractUpstreamCommandTokens(preparedArgs.args), runtimeToolStdin)
434
+ const browserIndependent = browserIndependentConfirmation || isBrowserIndependentRead(extractUpstreamCommandTokens(preparedArgs.args), runtimeToolStdin)
440
435
  || (executionPlan.commandInfo.command === "session" && executionPlan.commandInfo.subcommand === "info");
441
436
  const ownedSessionKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
442
437
  const plannedSessionPageState = sessionPageState.get(ownedSessionKey);
443
- const pageTargetError = readConfirmation ? undefined : getPageTargetValidationError({
438
+ const pageTargetError = readConfirmation && executionPlan.commandInfo.command !== "batch" ? undefined : getPageTargetValidationError({
439
+ allowFirstBatchConfirmation: readConfirmation !== undefined,
444
440
  args: executionPlan.effectiveArgs,
445
441
  currentPageUrl: plannedSessionPageState.tabTarget?.url,
446
442
  pageUrlUnknown: plannedSessionPageState.tabTargetUnknown === true,
@@ -571,6 +567,15 @@ export async function prepareBrowserRun(options) {
571
567
  const sessionTabPinningReason = priorSessionPageState.pinningReason;
572
568
  let priorRefSnapshotState = priorSessionPageState.refSnapshot;
573
569
  let priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
570
+ const confirmedCapture = sessionStateKey ? sessionPageState.getReadConfirmation(sessionStateKey) : undefined;
571
+ // ponytail: reuse the approved capture for one call; DOM-only renames after that sample are not resampled on this use.
572
+ const reuseConfirmedCapture = confirmedCapture?.refSnapshotFresh === true;
573
+ if (reuseConfirmedCapture && confirmedCapture) {
574
+ const consumed = { ...confirmedCapture };
575
+ delete consumed.refSnapshotFresh;
576
+ sessionPageState.applyReadConfirmation(consumed, options.sessionPageStateUpdate);
577
+ state.observedBrowserEffects = { ...state.observedBrowserEffects, readConfirmation: consumed };
578
+ }
574
579
  let nativeGenerationChanged = false;
575
580
  if (!browserIndependent && !isCloseCommand(executionPlan.commandInfo.command) && priorRefSnapshotState?.refIds.length && sessionStateKey && executionPlan.sessionName) {
576
581
  const daemon = await inspectManagedSessionDaemon({ cwd, namespace: executionPlan.namespace, sessionName: executionPlan.sessionName, signal, includeGeneration: true, headedManagedAutosaveInterval: ownedManagedSession?.headedManagedAutosaveInterval ?? (ownedManagedSession?.headedManagedAutosaveDisabled ? "0" : undefined), timeoutMs: params.timeoutMs });
@@ -651,7 +656,7 @@ export async function prepareBrowserRun(options) {
651
656
  const isCallerOwnedExplicitSession = () => executionPlan.sessionName !== undefined
652
657
  && executionPlan.usedImplicitSession === false
653
658
  && ownedManagedSession === undefined;
654
- const requiresLivePageVerification = () => !readConfirmation && (isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true);
659
+ const requiresLivePageVerification = () => !readConfirmation && (reuseConfirmedCapture || isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true);
655
660
  const verifyLivePage = async (request) => {
656
661
  if (!request.requirement || !executionPlan.sessionName)
657
662
  return;
@@ -690,7 +695,9 @@ export async function prepareBrowserRun(options) {
690
695
  return;
691
696
  }
692
697
  livePageVerified = true;
693
- priorSessionTabTarget ??= { url: liveUrl };
698
+ priorSessionTabTarget = priorSessionTabTarget?.url === liveUrl
699
+ ? { ...priorSessionTabTarget, url: liveUrl }
700
+ : { url: liveUrl };
694
701
  priorSessionTabTargetUnknown = undefined;
695
702
  };
696
703
  const hasPotentialLiveSemanticSession = state.managedSessionActive || priorSessionTabTarget !== undefined || isCallerOwnedExplicitSession() || options.preserveAttachedBrowserSession === true;
@@ -706,6 +713,7 @@ export async function prepareBrowserRun(options) {
706
713
  compiled: compiledSemanticAction,
707
714
  cwd,
708
715
  namespace: executionPlan.namespace,
716
+ refSnapshot: reuseConfirmedCapture && !priorSessionTabTargetUnknown && !priorRefSnapshotInvalidation ? priorRefSnapshotState : undefined,
709
717
  sessionName: executionPlan.sessionName,
710
718
  signal,
711
719
  });
@@ -872,6 +880,7 @@ export async function prepareBrowserRun(options) {
872
880
  const staleRefPreflight = buildStaleRefPreflight({
873
881
  commandTokens,
874
882
  currentTarget: priorSessionTabTarget,
883
+ requireExactTargetUrl: reuseConfirmedCapture,
875
884
  refSnapshot: resolvedSemanticActionRefSnapshot ?? priorRefSnapshotState,
876
885
  refSnapshotInvalidation: resolvedSemanticActionRefSnapshot ? undefined : priorRefSnapshotInvalidation,
877
886
  stdin: runtimeToolStdin,
@@ -899,7 +908,7 @@ export async function prepareBrowserRun(options) {
899
908
  commandTokens,
900
909
  cwd,
901
910
  currentTarget: priorSessionTabTarget,
902
- previousSnapshot: resolvedSemanticActionRefSnapshot ? undefined : priorRefSnapshotState,
911
+ previousSnapshot: resolvedSemanticActionRefSnapshot || reuseConfirmedCapture ? undefined : priorRefSnapshotState,
903
912
  stdin: runtimeToolStdin,
904
913
  namespace: executionPlan.namespace,
905
914
  sessionName: executionPlan.sessionName,
@@ -1032,9 +1041,6 @@ export async function prepareBrowserRun(options) {
1032
1041
  }
1033
1042
  const processArgs = executionPlan.effectiveArgs;
1034
1043
  const processStdin = preparedArgs.stdin ?? runtimeToolStdin;
1035
- const clickDispatchProbe = compiledElectron === undefined
1036
- ? await prepareClickDispatchProbe({ commandTokens, cwd, namespace: executionPlan.namespace, refSnapshot: promptRefSnapshot, sessionName: executionPlan.sessionName, signal })
1037
- : undefined;
1038
1044
  let readTimeoutPageUrl = priorSessionTabTarget?.url;
1039
1045
  if (options.params.timeoutMs === undefined && readTimeoutPageUrl === undefined && executionPlan.sessionName && commandTimeoutNeedsActivePageUrl(commandTokens, processStdin)) {
1040
1046
  try {
@@ -1044,6 +1050,9 @@ export async function prepareBrowserRun(options) {
1044
1050
  catch { }
1045
1051
  }
1046
1052
  const processTimeoutMs = options.params.timeoutMs ?? getDialogAwareProcessTimeoutMs(commandTokens, promptRefSnapshot, processStdin) ?? getCommandAwareProcessTimeoutMs(commandTokens, processStdin, readTimeoutPageUrl);
1053
+ const clickDispatchProbe = compiledElectron === undefined
1054
+ ? await prepareClickDispatchProbe({ commandTokens, cwd, namespace: executionPlan.namespace, refSnapshot: promptRefSnapshot, sessionName: executionPlan.sessionName, signal, timeoutMs: processTimeoutMs })
1055
+ : undefined;
1047
1056
  const redactedProcessArgs = redactInvocationArgs(prepareAgentBrowserSpawnArgs(processArgs, ownedManagedSession?.compatibilityUserAgent, options.preserveAttachedBrowserSession, chromeStartupArgs));
1048
1057
  const scrollAmount = Number(commandTokens.find((token) => /^\d+(?:\.\d+)?$/.test(token)));
1049
1058
  const shouldProbeScrollNoop = executionPlan.commandInfo.command === "scroll" && executionPlan.startupScopedFlags.length === 0 && (state.managedSessionActive || sessionMode === "fresh") && (!Number.isFinite(scrollAmount) || scrollAmount >= 500);