pi-agent-browser-native 0.2.71 → 0.2.74

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 (52) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +14 -12
  3. package/dist/extensions/agent-browser/index.js +104 -16
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +124 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +12 -1
  6. package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
  7. package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
  8. package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
  9. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
  10. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
  11. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
  12. package/dist/extensions/agent-browser/lib/managed-session-restore.js +367 -0
  13. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
  14. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
  15. package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
  16. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +625 -429
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +136 -56
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +28 -40
  29. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
  30. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
  31. package/dist/extensions/agent-browser/lib/playbook.js +10 -9
  32. package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
  33. package/dist/extensions/agent-browser/lib/process.js +270 -34
  34. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
  35. package/dist/extensions/agent-browser/lib/results/categories.js +21 -2
  36. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +80 -12
  38. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
  39. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
  40. package/dist/extensions/agent-browser/lib/runtime.js +85 -85
  41. package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
  42. package/dist/extensions/agent-browser/lib/temp.js +13 -25
  43. package/docs/ARCHITECTURE.md +9 -8
  44. package/docs/COMMAND_REFERENCE.md +97 -32
  45. package/docs/ELECTRON.md +10 -10
  46. package/docs/RELEASE.md +3 -2
  47. package/docs/SUPPORT_MATRIX.md +22 -19
  48. package/docs/TOOL_CONTRACT.md +31 -28
  49. package/docs/platform-smoke.md +5 -5
  50. package/package.json +1 -1
  51. package/platform-smoke.config.mjs +3 -1
  52. package/scripts/agent-browser-capability-baseline.mjs +45 -3
@@ -6,14 +6,17 @@
6
6
  import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
7
7
  import { discoverElectronApps } from "../../electron/discovery.js";
8
8
  import { boundElectronProbeString } from "../../electron/text.js";
9
+ import { buildOwnedManagedSessionRestoreContext, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
10
+ import { getManagedSessionStateAccessValidationError } from "../../managed-session-state-policy.js";
9
11
  import { isRecord } from "../../parsing.js";
10
12
  import { buildAgentBrowserNextActions, buildAgentBrowserResultCategoryDetails } from "../../results.js";
11
13
  import { appendUniqueAgentBrowserNextActions } from "../../results/next-actions.js";
12
- import { extractRefSnapshotFromData, isAboutBlankUrl, normalizeSessionTabTarget } from "../../session-page-state.js";
14
+ import { extractRefSnapshotFromData, getSessionPageStateKey, isAboutBlankUrl, normalizeSessionTabTarget } from "../../session-page-state.js";
13
15
  import { redactSensitiveText } from "../../runtime.js";
14
16
  import { collectElectronManagedSessionTarget } from "../browser-run/diagnostics.js";
15
17
  import { buildElectronHostFailureResult, formatElectronTargetLines, redactToolDetails } from "../browser-run/final-result.js";
16
- import { buildElectronIdentifiers, buildElectronMismatchNextActions, buildElectronSessionMismatch, closeManagedSession, extractStringResultField, findElectronLaunchRecordForSession, formatElectronSessionMismatchText, getActiveElectronRecords, getLiveElectronRendererTargets, runSessionCommandData, } from "../browser-run/session-state.js";
18
+ import { acquireOwnedManagedSessionDaemonPolicy, closeManagedSession } from "../browser-run/managed-session-daemon-policy.js";
19
+ import { buildElectronIdentifiers, buildElectronMismatchNextActions, buildElectronSessionMismatch, extractStringResultField, findElectronLaunchRecordForSession, formatElectronSessionMismatchText, getActiveElectronRecords, getLiveElectronRendererTargets, runSessionCommandData, } from "../browser-run/session-state.js";
17
20
  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.";
18
21
  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.";
19
22
  export const ELECTRON_PROFILE_ISOLATION_DETAILS = {
@@ -391,32 +394,87 @@ function getElectronProbeSummary(probe) {
391
394
  ].filter((item) => item !== undefined);
392
395
  return parts.length > 0 ? `Electron probe collected ${parts.join(", ")}.` : "Electron probe did not return current session state.";
393
396
  }
397
+ class ElectronManagedSessionPolicyError extends Error {
398
+ }
399
+ async function withOwnedElectronManagedSessionPolicy(options, run) {
400
+ const context = buildOwnedManagedSessionRestoreContext({
401
+ args: ["--namespace", options.namespace ?? "", "--session", options.sessionName, ...options.args],
402
+ cwd: options.cwd,
403
+ managedSessionName: options.sessionName,
404
+ namespace: options.namespace,
405
+ restoreState: options.restoreState,
406
+ });
407
+ if (!context)
408
+ throw new ElectronManagedSessionPolicyError("Electron helper could not establish wrapper ownership for its managed session.");
409
+ let policy;
410
+ try {
411
+ policy = await acquireOwnedManagedSessionDaemonPolicy({ context, signal: options.signal });
412
+ }
413
+ catch (error) {
414
+ throw new ElectronManagedSessionPolicyError(error instanceof Error ? error.message : String(error), { cause: error });
415
+ }
416
+ try {
417
+ if (policy.error)
418
+ throw new ElectronManagedSessionPolicyError(policy.error);
419
+ if (!policy.lock)
420
+ throw new ElectronManagedSessionPolicyError(options.signal?.aborted ? "Electron helper was aborted." : "Electron helper could not acquire managed-session policy coordination.");
421
+ return await withOwnedManagedSessionContext(context, run);
422
+ }
423
+ finally {
424
+ await policy.lock?.release();
425
+ }
426
+ }
427
+ async function collectOwnedElectronManagedSessionTarget(options) {
428
+ try {
429
+ return await withOwnedElectronManagedSessionPolicy({ ...options, args: ["get", "url"] }, async () => await collectElectronManagedSessionTarget({
430
+ allowManagedSessionTarget: true,
431
+ cwd: options.cwd,
432
+ namespace: options.namespace,
433
+ sessionName: options.sessionName,
434
+ signal: options.signal,
435
+ timeoutMs: options.timeoutMs,
436
+ })) ?? { sessionName: options.sessionName };
437
+ }
438
+ catch (error) {
439
+ return { error: error instanceof Error ? error.message : String(error), sessionName: options.sessionName };
440
+ }
441
+ }
394
442
  async function runElectronProbeCommandData(options) {
395
443
  try {
396
- return { data: await runSessionCommandData(options) };
444
+ return { data: await runSessionCommandData({ ...options, allowManagedSessionTarget: true, pinNamespace: true, throwOnFailure: true }) };
397
445
  }
398
446
  catch (error) {
399
447
  return { error: error instanceof Error ? error.message : String(error) };
400
448
  }
401
449
  }
402
450
  async function collectElectronProbe(options) {
403
- const titleResult = await runElectronProbeCommandData({ args: ["get", "title"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
404
- const urlResult = await runElectronProbeCommandData({ args: ["get", "url"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
405
- const focusedResult = await runElectronProbeCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: ELECTRON_FOCUSED_ELEMENT_EVAL, timeoutMs: options.timeoutMs });
406
- const tabsResult = await runElectronProbeCommandData({ args: ["tab", "list"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
407
- const snapshotResult = await runElectronProbeCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
451
+ const commandContext = { cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs };
452
+ const urlResult = await runElectronProbeCommandData({ ...commandContext, args: ["get", "url"] });
453
+ if (urlResult.error)
454
+ throw new Error(`get url: ${urlResult.error}`);
455
+ const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
456
+ if (!url)
457
+ throw new Error("get url returned no active page URL.");
458
+ const fileAccessError = getManagedSessionStateAccessValidationError({ args: ["snapshot", "-i"], currentPageUrl: url, cwd: options.cwd });
459
+ if (fileAccessError)
460
+ throw new ElectronManagedSessionPolicyError(fileAccessError);
461
+ const titleResult = await runElectronProbeCommandData({ ...commandContext, args: ["get", "title"] });
462
+ const focusedResult = await runElectronProbeCommandData({ ...commandContext, args: ["eval", "--stdin"], stdin: ELECTRON_FOCUSED_ELEMENT_EVAL });
463
+ const tabsResult = await runElectronProbeCommandData({ ...commandContext, args: ["tab", "list"] });
464
+ const snapshotResult = await runElectronProbeCommandData({ ...commandContext, args: ["snapshot", "-i"] });
408
465
  const errors = [
409
466
  titleResult.error ? `get title: ${titleResult.error}` : undefined,
410
- urlResult.error ? `get url: ${urlResult.error}` : undefined,
411
467
  focusedResult.error ? `focused element: ${focusedResult.error}` : undefined,
412
468
  tabsResult.error ? `tab list: ${tabsResult.error}` : undefined,
413
469
  snapshotResult.error ? `snapshot: ${snapshotResult.error}` : undefined,
414
470
  ].filter((item) => item !== undefined).map((error) => boundElectronProbeString(error, 240) ?? "probe command failed");
415
471
  const title = boundElectronProbeString(extractStringResultField(titleResult.data, "result") ?? extractStringResultField(titleResult.data, "title"), 160);
416
- const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
417
472
  const focusedElement = extractElectronFocusedElement(focusedResult.data);
418
473
  const { activeTab, tabs } = extractElectronProbeTabs(tabsResult.data);
419
474
  const { refSnapshot, snapshot } = summarizeElectronProbeSnapshot(snapshotResult.data);
475
+ if (errors.length > 0 && !(title || url || focusedElement || tabs || snapshot)) {
476
+ throw new Error(errors.join("; "));
477
+ }
420
478
  const probeWithoutSummary = {
421
479
  activeTab,
422
480
  focusedElement,
@@ -522,6 +580,8 @@ function buildElectronProbeResult(options) {
522
580
  },
523
581
  nextActions: nextActions.length > 0 ? nextActions : undefined,
524
582
  ...buildAgentBrowserResultCategoryDetails({ args: [], succeeded: true }),
583
+ namespace: options.namespace,
584
+ refSnapshot: options.probe.refSnapshot,
525
585
  sessionName: options.probe.sessionName,
526
586
  sessionTabTarget: options.sessionTabTarget,
527
587
  summary: options.mismatch?.summary ?? options.probe.summary,
@@ -537,7 +597,7 @@ export async function cleanupTrackedElectronHostLaunches(options) {
537
597
  const results = [];
538
598
  for (const record of options.records) {
539
599
  const managedSessionCloseError = record.sessionName
540
- ? await closeManagedSession({ cwd: options.cwd, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
600
+ ? await closeManagedSession({ cwd: options.cwd, restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
541
601
  : undefined;
542
602
  const managedSessionStep = record.sessionName
543
603
  ? managedSessionCloseError
@@ -580,7 +640,7 @@ export async function cleanupActiveElectronHostLaunches(options) {
580
640
  : [];
581
641
  }
582
642
  export async function handleElectronHostInput(options) {
583
- const { compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, redactedCompiledElectron, sessionPageState, signal, } = options;
643
+ const { compiledElectron, cwd, electronChildProcesses, electronLaunchRecords, implicitSessionCloseTimeoutMs, managedSessionActive, managedSessionName, managedSessionNamespace, managedSessionRestoreState, redactedCompiledElectron, sessionPageState, signal, } = options;
584
644
  if (compiledElectron?.action === "list") {
585
645
  try {
586
646
  const discovery = await discoverElectronApps({ maxResults: compiledElectron.maxResults, query: compiledElectron.query });
@@ -596,12 +656,15 @@ export async function handleElectronHostInput(options) {
596
656
  return buildElectronHostFailureResult({ compiledElectron: redactedCompiledElectron ?? compiledElectron, errorText: selection.error, failureCategory: "validation-error" });
597
657
  const records = selection.records ?? [];
598
658
  const statuses = await Promise.all(records.map((record) => inspectElectronLaunchStatus(record)));
599
- const managedSessions = (await Promise.all(records.map((record) => collectElectronManagedSessionTarget({
659
+ const managedSessions = await Promise.all(records
660
+ .filter((record) => typeof record.sessionName === "string")
661
+ .map((record) => collectOwnedElectronManagedSessionTarget({
600
662
  cwd,
663
+ restoreState: managedSessionRestoreState,
601
664
  sessionName: record.sessionName,
602
665
  signal,
603
666
  timeoutMs: compiledElectron.timeoutMs,
604
- })))).filter((managedSession) => managedSession !== undefined);
667
+ })));
605
668
  const mismatches = managedSessions
606
669
  .map((managedSession) => {
607
670
  const record = records.find((candidate) => candidate.sessionName === managedSession.sessionName);
@@ -652,7 +715,25 @@ export async function handleElectronHostInput(options) {
652
715
  }
653
716
  try {
654
717
  const status = launchRecord ? await inspectElectronLaunchStatus(launchRecord) : undefined;
655
- const probe = await collectElectronProbe({ cwd, sessionName: probeSessionName, signal, timeoutMs: compiledElectron.timeoutMs });
718
+ const probeNamespace = compiledElectron.launchId ? undefined : managedSessionNamespace;
719
+ const pageStateKey = getSessionPageStateKey(probeSessionName, probeNamespace) ?? probeSessionName;
720
+ const currentPageState = sessionPageState.get(pageStateKey);
721
+ const fileAccessError = getManagedSessionStateAccessValidationError({
722
+ args: ["snapshot", "-i"],
723
+ currentPageUrl: currentPageState.tabTarget?.url,
724
+ cwd,
725
+ pageUrlUnknown: currentPageState.tabTargetUnknown === true,
726
+ });
727
+ if (fileAccessError)
728
+ throw new ElectronManagedSessionPolicyError(fileAccessError);
729
+ const probe = await withOwnedElectronManagedSessionPolicy({
730
+ args: ["snapshot", "-i"],
731
+ cwd,
732
+ namespace: probeNamespace,
733
+ restoreState: managedSessionRestoreState,
734
+ sessionName: probeSessionName,
735
+ signal,
736
+ }, async () => await collectElectronProbe({ cwd, namespace: probeNamespace, sessionName: probeSessionName, signal, timeoutMs: compiledElectron.timeoutMs }));
656
737
  const managedSession = {
657
738
  sessionName: probe.sessionName,
658
739
  title: probe.title ?? probe.activeTab?.title,
@@ -677,13 +758,14 @@ export async function handleElectronHostInput(options) {
677
758
  url: probe.url ?? probe.activeTab?.url ?? probe.refSnapshot?.target?.url,
678
759
  });
679
760
  const pageStateUpdate = sessionPageState.beginUpdate();
761
+ const probePageStateKey = getSessionPageStateKey(probe.sessionName, probeNamespace) ?? probe.sessionName;
680
762
  if (sessionTabTarget) {
681
- sessionPageState.applyTabTarget({ sessionName: probe.sessionName, target: sessionTabTarget, update: pageStateUpdate });
763
+ sessionPageState.applyTabTarget({ sessionName: probePageStateKey, target: sessionTabTarget, update: pageStateUpdate });
682
764
  }
683
765
  if (probe.refSnapshot) {
684
766
  sessionPageState.applyRefSnapshot({
685
767
  fallbackTarget: sessionTabTarget,
686
- sessionName: probe.sessionName,
768
+ sessionName: probePageStateKey,
687
769
  snapshot: probe.refSnapshot,
688
770
  update: pageStateUpdate,
689
771
  });
@@ -691,6 +773,7 @@ export async function handleElectronHostInput(options) {
691
773
  return buildElectronProbeResult({
692
774
  compiledElectron: redactedCompiledElectron ?? compiledElectron,
693
775
  mismatch: sessionMismatch,
776
+ namespace: probeNamespace,
694
777
  probe,
695
778
  probeContext,
696
779
  record: launchRecord,
@@ -703,7 +786,7 @@ export async function handleElectronHostInput(options) {
703
786
  return buildElectronHostFailureResult({
704
787
  compiledElectron: redactedCompiledElectron ?? compiledElectron,
705
788
  errorText: `Electron probe failed: ${errorText}`,
706
- failureCategory: "upstream-error",
789
+ failureCategory: error instanceof ElectronManagedSessionPolicyError ? "validation-error" : "upstream-error",
707
790
  });
708
791
  }
709
792
  }
@@ -711,7 +794,7 @@ export async function handleElectronHostInput(options) {
711
794
  const selection = selectElectronRecords(compiledElectron, electronLaunchRecords);
712
795
  if (selection.error)
713
796
  return buildElectronHostFailureResult({ compiledElectron: redactedCompiledElectron ?? compiledElectron, errorText: selection.error, failureCategory: "validation-error" });
714
- const cleanupResults = await cleanupTrackedElectronHostLaunches({ cwd, electronChildProcesses, electronLaunchRecords, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
797
+ const cleanupResults = await cleanupTrackedElectronHostLaunches({ cwd, electronChildProcesses, electronLaunchRecords, managedSessionRestoreState, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
715
798
  return buildElectronCleanupResult(redactedCompiledElectron ?? compiledElectron, cleanupResults);
716
799
  }
717
800
  return undefined;
@@ -1,7 +1,8 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, resolve } from "node:path";
3
+ import { getAgentBrowserStoragePathValidationError } from "../managed-session-state-policy.js";
3
4
  import { isRecord } from "../parsing.js";
4
- function normalizeRequestedOutputPath(path) {
5
+ export function normalizeRequestedOutputPath(path) {
5
6
  return path.startsWith("@") ? path.slice(1) : path;
6
7
  }
7
8
  function getTextContent(result) {
@@ -27,9 +28,20 @@ function appendOutputFileNotice(result, message) {
27
28
  }
28
29
  return [{ type: "text", text: message }, ...content];
29
30
  }
31
+ export function getAgentBrowserOutputPathValidationError(outputPath, cwd) {
32
+ return outputPath ? getAgentBrowserStoragePathValidationError(normalizeRequestedOutputPath(outputPath), cwd) : undefined;
33
+ }
30
34
  export async function applyAgentBrowserOutputPath(options) {
31
35
  if (!options.outputPath)
32
36
  return options.result;
37
+ const validationError = getAgentBrowserOutputPathValidationError(options.outputPath, options.cwd);
38
+ if (validationError) {
39
+ return {
40
+ content: [{ type: "text", text: validationError }],
41
+ details: { failureCategory: "validation-error", resultCategory: "failure", validationError },
42
+ isError: true,
43
+ };
44
+ }
33
45
  if (options.result.isError || (isRecord(options.result.details) && options.result.details.resultCategory === "failure"))
34
46
  return options.result;
35
47
  const requestedPath = normalizeRequestedOutputPath(options.outputPath);
@@ -14,11 +14,11 @@ export function buildInstalledDocsGuideline(paths) {
14
14
  return `For detailed agent_browser docs, read targeted sections: ${paths.readmePath} (setup), ${paths.commandReferencePath} (commands), ${paths.toolContractPath} (result/details). Do not load the full command reference unless needed.`;
15
15
  }
16
16
  export const QUICK_START_GUIDELINES = [
17
- `Quick start mental model: use exactly one of args (exact agent-browser CLI args after the binary), semanticAction (a thin shorthand compiled to find argv for locator actions, direct selector/ref click/check/fill, or select argv for native dropdowns), job (a constrained short-workflow schema compiled to batch --bail by default; set failFast:false only when later diagnostics should continue after a failed step), qa (a lightweight fail-fast QA preset built on batch --bail with bounded visible expected-text checks, including qa.attached for current sessions), electron (desktop Electron list/launch/status/cleanup/probe), or the experimental sourceLookup / networkSourceLookup helpers (candidates only; each compiled to batch); stdin is only for batch, eval --stdin, auth save --password-stdin, and wrapper-generated batch stdin from job, qa, sourceLookup, or networkSourceLookup, and is rejected with electron; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}) to apply. Use outputPath for durable eval/get/snapshot captures. Do not pass --json in args; the wrapper injects it.`,
17
+ `Quick start mental model: use exactly one of args (exact agent-browser CLI args after the binary), semanticAction (a thin shorthand compiled to find argv for locator actions, direct selector/ref click/check/fill, or select argv for native dropdowns), job (a constrained short-workflow schema compiled to batch --bail by default; set failFast:false only when later diagnostics remain safe if an earlier navigation fails), qa (a lightweight fail-fast QA preset built on batch --bail with bounded visible expected-text checks, including qa.attached for current sessions), electron (desktop Electron list/launch/status/cleanup/probe), or the experimental sourceLookup / networkSourceLookup helpers (candidates only; each compiled to batch); stdin is only for batch, eval --stdin, auth save --password-stdin, and wrapper-generated batch stdin from job, qa, sourceLookup, or networkSourceLookup, and is rejected with electron; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}) to apply. Use outputPath for durable eval/get/snapshot captures. Do not pass --json in args; the wrapper injects it.`,
18
18
  "There is no first-class reusable named browser recipe runtime above top-level job, the qa preset, and raw batch stdin; keep recurring flows in documentation examples or those inputs (closed RQ-0068; see docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet).",
19
19
  "Common first calls (first-call recipe): { args: [\"open\", \"<url>\"] } → { args: [\"snapshot\", \"-i\"] } → { args: [\"click\", \"@eN\"] } or { args: [\"fill\", \"@eN\", \"<text>\"] } using @refs and visible labels from that snapshot, then { args: [\"snapshot\", \"-i\"] } after navigation or DOM changes. On https://example.com/ the main link label is Learn more (use exact snapshot text, not guessed link copy).",
20
20
  "Locator-first clicks/fills and native select changes without hand-building argv: { semanticAction: { action: \"click\", locator: \"text\", value: \"Close\" } }, { semanticAction: { action: \"fill\", locator: \"label\", value: \"Email\", text: \"user@example.com\" } }, direct current targets such as { semanticAction: { action: \"fill\", selector: \"@e1\", text: \"prompt\" } }, or { semanticAction: { action: \"select\", selector: \"#flavor\", value: \"chocolate\" } }; add semanticAction.session when targeting a named upstream browser session; details.compiledSemanticAction shows the semantic target, while details.effectiveArgs may show a resolved current @ref for active-session role/name click/check/fill actions to avoid hidden duplicate matches; semanticAction does not expose uncheck while upstream find ... uncheck is not runtime-supported, so use raw uncheck with a stable selector or current ref; selector-not-found failures may append bounded click try-*-candidate next actions or, for fill misses with current editable refs, details.richInputRecovery with focus/click actions that do not copy fill text; stale-ref failures can return retry-semantic-action-after-stale-ref for compiled find actions when retry safety is provable.",
21
- `Common advanced calls: { args: ["batch"], 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.`,
21
+ `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
22
  "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
23
  "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
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.",
@@ -36,17 +36,18 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
36
36
  "For authenticated or user-specific content explicitly requested by the user, such as feeds, inboxes, account pages, or private dashboards, use a real profile only when the user/config asks for it or profiles have been inspected; do not assume --profile Default exists on every machine. Do not use a real profile for public pages just because they are dashboards. Treat visible page content from real profiles as model-visible transcript data; use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser. If profile/user-data-dir resolution fails, stop retrying opens, run profiles and/or doctor through agent_browser, then report what the user needs to configure.",
37
37
  "Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.",
38
38
  `When using launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}), put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.`,
39
+ "Caller-owned explicit sessions are serialized per effective canonical namespace/session inside this extension while live URL checks, semantic-action snapshots, and the requested command run. For raw batches whose later content step depends on navigation, use exact batch --bail or split the calls; unsafe continue-after-navigation-failure shapes are rejected before the batch runs.",
39
40
  `If you already used the implicit session and now need launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}), retry with top-level sessionMode set to fresh or pass an explicit --session for the new launch; never pass --session-mode inside args. After a successful unnamed fresh launch, later auto calls follow that new session.`,
40
41
  "For WebGPU pages, use args [\"--webgpu\", \"open\", \"<url>\"] on a fresh local browser launch; use doctor --webgpu (or --headed on Linux/Windows capture paths) to prove rendering before trusting a non-black screenshot. WebGPU cannot be combined with --cdp, --auto-connect, or provider launches unless --webgpu false overrides an enabled config/environment default.",
41
42
  "For --allowed-domains, use a fresh local Chrome context. Upstream rejects CDP/auto-connect, profiles, restore/state replay, direct-page providers, iOS/Safari, and startup/profile Chrome args because they cannot guarantee containment; Chromium also disables RTCPeerConnection while the allowlist is active.",
42
43
  "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.",
43
44
  "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.",
44
- "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/clear/clear -a/clean for saved-state lifecycle cleanup, 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.",
45
+ "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.",
45
46
  "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.",
46
47
  "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.",
47
48
  "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.",
48
- "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, inspect tab list, select the stable tab id such as tab t2, 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.",
49
- "For provider or specialized app workflows, load version-matched upstream guidance with skills get agentcore|electron|slack|dogfood|vercel-sandbox through the native tool; add --full when you need references/templates, and use skills get --all only for broad skill audits. Hosted sandbox workflows should use upstream @agent-browser/sandbox helpers outside this wrapper. Provider launches such as -p ios, --provider browserbase/kernel/browseruse/browserless/agentcore, and iOS --device are upstream-owned setup paths; use sessionMode fresh when switching providers and expect external credentials or local Appium/Xcode setup to be required.",
49
+ "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.",
50
+ "For provider or specialized app workflows, load version-matched upstream guidance with skills get agentcore|electron|slack|dogfood|vercel-sandbox|derive-client through the native tool; add --full when you need references/templates, and use skills get --all only for broad skill audits. Use derive-client when recording HAR traffic to generate a standalone API client; prefer network har start (text bodies by default) or network har start --content all|none before multi-step capture. For accessibility audits use a11y or a11y --tags wcag2a,wcag2aa (CDP browsers only). Hosted sandbox workflows should use upstream @agent-browser/sandbox helpers outside this wrapper. Provider launches such as -p ios, --provider browserbase/kernel/browseruse/browserless/agentcore, and iOS --device are upstream-owned setup paths; use sessionMode fresh when switching providers and expect external credentials or local Appium/Xcode setup to be required.",
50
51
  "For dialogs and frames, use dialog status/accept/dismiss and frame <selector|main> through native args; dialog commands and eval snippets that look like alert/confirm/prompt/dialog triggers are shorter-bounded than normal browser calls, and timed-out dialog-like interactions may add inspect-dialog-after-timeout, dismiss-dialog-after-timeout, or recover-fresh-session-after-dialog-timeout nextActions. When --confirm-actions produces a pending confirmation, use details.nextActions or exact confirm <id> / deny <id> calls instead of inventing ids.",
51
52
  "If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. For headed demos, put --headed on the first launch with sessionMode=fresh and verify with screenshot/tab/get-url evidence because tool success cannot prove the OS window is visible to the user. For desktop readiness, prefer real conditions first: wait --text, wait --url, wait --fn, wait --load <state>, wait --download, or qa.attached; for disappearance checks, use wait --fn predicates instead of stale upstream-help examples like wait <selector> --state hidden. Use electron.probe/status for wrapper-owned launch health or target mismatch. Fixed waits are a last resort: use explicit --timeout or top-level timeoutMs for legitimately slow waits, and treat a successful payload like \"waited\":\"timeout\" as elapsed time only—verify completion with an observed condition, fresh snapshot, or screenshot.",
52
53
  "For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
@@ -54,7 +55,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
54
55
  "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.",
55
56
  "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.",
56
57
  "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.",
57
- "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. On file:// pages, when upstream JSON returns result: null for non-trivial stdin, details.evalResultWarning may append Eval result warning without failing the tool—treat that as inconclusive DOM verification. 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.",
58
+ "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.",
58
59
  "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.",
59
60
  "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.",
60
61
  "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.",
@@ -63,7 +64,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
63
64
  "Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary.",
64
65
  ];
65
66
  export const TOOL_PROMPT_GUIDELINES_SUFFIX = [
66
- "Prefer agent_browser over bash, osascript, AppleScript, or generic browser-driving shell for sites, docs, clicking, filling, screenshots, eval, and batch workflows.",
67
+ "Prefer agent_browser over bash, osascript, AppleScript, or generic browser shell for sites, docs, clicks, fills, screenshots, eval, and batch.",
67
68
  "Pass exact agent-browser CLI arguments in agent_browser args when you are not using semanticAction, job, or qa, excluding the binary name and --json (agent_browser injects --json automatically).",
68
69
  "Use agent_browser stdin only for eval --stdin, batch, auth save --password-stdin, or wrapper-generated job/qa batches instead of shell heredocs or password args; other command/stdin combinations are rejected before launch.",
69
70
  `Let the agent_browser extension-managed session handle the common path unless you explicitly need a fresh launch for launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}).`,
@@ -83,9 +84,9 @@ export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
83
84
  export const RUNTIME_PROMPT_GUIDELINES = [
84
85
  "Use agent_browser with one input mode: args, semanticAction, job, qa, sourceLookup/networkSourceLookup, or electron. stdin only for batch/eval/auth/wrapper batch; electron rejects stdin; never pass --json.",
85
86
  "For agent_browser, use open → snapshot -i → current @refs or semanticAction → re-snapshot after navigation/scroll/rerender. Batch same-snapshot forms; split before navigation/submits. Stop before order/post/purchase/submit.",
86
- "Use agent_browser sessionMode=fresh for launch-scoped flags, including --allowed-domains; never put --session-mode in args. Use requested/configured profiles only; on profile failures run profiles/doctor. Profile content is model-visible.",
87
+ "Use agent_browser sessionMode=fresh for launch-scoped flags incl. --allowed-domains; never put --session-mode in args. Use requested/configured profiles only; on profile failures run profiles/doctor. Profile content is model-visible. Restores project cookies; SSO may need --headed once.",
87
88
  "For agent_browser artifacts, use exact user paths and verify details.artifactVerification/details.artifacts before claiming success. Save details.promptGuard-required artifacts before close; record stop needs ffmpeg; close keeps files; waited:timeout is not proof.",
88
- "When agent_browser details.nextActions exists, use exact payloads over guessed selectors/prose. Dense snapshots: check Omitted high-value controls/highValueControlRefIds. Dashboards: verify scroll with screenshot/snapshot.",
89
+ "When agent_browser details.nextActions exists, use exact payloads over guessed selectors/prose. Dense snapshots: check Omitted high-value controls. Dashboards: verify scroll with screenshot/snapshot.",
89
90
  "For agent_browser extraction: read <url> for docs/text; read for active-tab DOM; get title/url; get text/html/value/count <selector>; get attr <selector> <name>; eval --stdin for targeted state. Batch 3+ getters; heed visibility warnings.",
90
91
  ];
91
92
  export function buildBrowserExecutablePathGuideline(executablePath) {
@@ -0,0 +1,82 @@
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
+ import { execFile } from "node:child_process";
7
+ import { win32 } from "node:path";
8
+ const WINDOWS_PROCESS_START_IDENTITY_PREFIX = "win32-powershell-ticks-v1:";
9
+ const PROCESS_START_IDENTITY_TIMEOUT_MS = 1_000;
10
+ const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
11
+ export function buildProcessStartIdentityCommand(pid, platform = process.platform) {
12
+ if (!Number.isSafeInteger(pid) || pid <= 0)
13
+ return undefined;
14
+ const configuredSystemRoot = process.env.SystemRoot;
15
+ const windowsSystemRoot = configuredSystemRoot && win32.isAbsolute(configuredSystemRoot)
16
+ ? configuredSystemRoot
17
+ : DEFAULT_WINDOWS_SYSTEM_ROOT;
18
+ return platform === "win32"
19
+ ? {
20
+ args: [
21
+ "-NoProfile",
22
+ "-NonInteractive",
23
+ "-Command",
24
+ `$p = Get-Process -Id ${pid} -ErrorAction Stop; Write-Output ("${WINDOWS_PROCESS_START_IDENTITY_PREFIX}" + $p.StartTime.ToUniversalTime().Ticks)`,
25
+ ],
26
+ file: win32.join(windowsSystemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
27
+ }
28
+ : {
29
+ args: ["-p", String(pid), "-o", "lstart="],
30
+ file: "/bin/ps",
31
+ };
32
+ }
33
+ export function buildProcessStartIdentityCommands(pid, platform = process.platform) {
34
+ const primary = buildProcessStartIdentityCommand(pid, platform);
35
+ if (!primary)
36
+ return [];
37
+ return platform === "win32"
38
+ ? [primary]
39
+ : [primary, { ...primary, file: "/usr/bin/ps" }];
40
+ }
41
+ export function normalizeProcessStartIdentity(stdout) {
42
+ return stdout.trim().replace(/\s+/g, " ") || undefined;
43
+ }
44
+ let currentProcessStartIdentityPromise;
45
+ async function executeProcessStartIdentityCommand(command) {
46
+ return await new Promise((resolve) => {
47
+ execFile(command.file, command.args, { timeout: PROCESS_START_IDENTITY_TIMEOUT_MS }, (error, stdout) => {
48
+ resolve(error ? undefined : normalizeProcessStartIdentity(stdout));
49
+ });
50
+ });
51
+ }
52
+ export async function resolveProcessStartIdentityFromCommands(commands, execute = executeProcessStartIdentityCommand) {
53
+ for (const command of commands) {
54
+ const identity = await execute(command);
55
+ if (identity)
56
+ return identity;
57
+ }
58
+ return undefined;
59
+ }
60
+ async function readUncachedProcessStartIdentity(pid, platform) {
61
+ return await resolveProcessStartIdentityFromCommands(buildProcessStartIdentityCommands(pid, platform));
62
+ }
63
+ export async function readProcessStartIdentity(pid, platform = process.platform) {
64
+ if (pid !== process.pid || platform !== process.platform)
65
+ return await readUncachedProcessStartIdentity(pid, platform);
66
+ currentProcessStartIdentityPromise ??= readUncachedProcessStartIdentity(pid, platform).then((identity) => {
67
+ if (!identity)
68
+ currentProcessStartIdentityPromise = undefined;
69
+ return identity;
70
+ });
71
+ return await currentProcessStartIdentityPromise;
72
+ }
73
+ /** Return undefined when a native-Windows marker predates the versioned PowerShell identity format. */
74
+ export function processStartIdentitiesMatch(recorded, current, platform = process.platform) {
75
+ if (platform === "win32") {
76
+ const recordedIsCurrentFormat = recorded.startsWith(WINDOWS_PROCESS_START_IDENTITY_PREFIX);
77
+ const currentIsCurrentFormat = current.startsWith(WINDOWS_PROCESS_START_IDENTITY_PREFIX);
78
+ if (recordedIsCurrentFormat !== currentIsCurrentFormat)
79
+ return undefined;
80
+ }
81
+ return recorded === current;
82
+ }