pi-agent-browser-native 0.9.6 → 0.9.8

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 (34) hide show
  1. package/CHANGELOG.md +108 -1
  2. package/README.md +14 -6
  3. package/dist/extensions/agent-browser/index.js +1276 -350
  4. package/dist/packaged/lib/browser-session-conversion.js +157 -21
  5. package/dist/packaged/lib/playbook.js +42 -8
  6. package/dist/packaged/lib/results/snapshot.js +140 -11
  7. package/dist/packaged/lib/runtime.js +58 -10
  8. package/docs/ARCHITECTURE.md +19 -10
  9. package/docs/CODE_QUALITY.md +21 -5
  10. package/docs/COMMAND_REFERENCE.md +154 -38
  11. package/docs/ELECTRON.md +1 -1
  12. package/docs/PERFORMANCE.md +12 -3
  13. package/docs/REFERENCE.md +14 -14
  14. package/docs/RELEASE.md +44 -19
  15. package/docs/REQUIREMENTS.md +6 -0
  16. package/docs/SESSION_STORAGE.md +3 -3
  17. package/docs/SUPPORT_MATRIX.md +25 -23
  18. package/docs/TOOL_CONTRACT.md +37 -14
  19. package/docs/platform-smoke.md +18 -14
  20. package/package.json +6 -3
  21. package/platform-smoke.config.mjs +3 -2
  22. package/scripts/agent-browser-capability-baseline.mjs +16 -2
  23. package/scripts/agent-browser-target.mjs +1 -1
  24. package/scripts/capability-options.mjs +20 -0
  25. package/scripts/capability-samples.mjs +23 -10
  26. package/scripts/platform-smoke/commands.mjs +30 -4
  27. package/scripts/platform-smoke/doctor-support.mjs +11 -2
  28. package/scripts/platform-smoke/doctor-windows.mjs +1 -1
  29. package/scripts/platform-smoke/doctor.mjs +26 -11
  30. package/scripts/platform-smoke/linux-image/Dockerfile +6 -3
  31. package/scripts/platform-smoke/platform-build-windows.ps1 +23 -3
  32. package/scripts/platform-smoke/suite-checks.mjs +11 -1
  33. package/scripts/platform-smoke/targets.mjs +4 -3
  34. package/scripts/platform-smoke.mjs +68 -25
@@ -855,6 +855,7 @@ var init_argv_grammar = __esm({
855
855
  "-q",
856
856
  "--verbose",
857
857
  "-v",
858
+ "--use-system-ca",
858
859
  "--webgpu"
859
860
  ]);
860
861
  }
@@ -1150,6 +1151,9 @@ var init_text = __esm({
1150
1151
  });
1151
1152
 
1152
1153
  // dist/extensions/agent-browser/lib/orchestration/batch-stdin.js
1154
+ function isNativeBatchOption(token) {
1155
+ return token === "--bail" || token === "--omit-command";
1156
+ }
1153
1157
  function normalizeUrlLessOpen(args, stdin, batchStep = false) {
1154
1158
  const { tokens, indices } = batchStep ? { tokens: args, indices: args.map((_, index) => index) } : projectUpstreamGlobalFlags(args);
1155
1159
  if (tokens[0] === "open" && !tokens.slice(1).some((token) => !token.startsWith("--"))) {
@@ -1159,7 +1163,7 @@ function normalizeUrlLessOpen(args, stdin, batchStep = false) {
1159
1163
  if (tokens[0] !== "batch") {
1160
1164
  return { args, stdin };
1161
1165
  }
1162
- if (tokens.slice(1).some((token) => token !== "--bail")) {
1166
+ if (tokens.slice(1).some((token) => !isNativeBatchOption(token))) {
1163
1167
  return { args: normalizeRawBatchRows(args, tokens, indices), stdin };
1164
1168
  }
1165
1169
  const steps = parseUserBatchStdin(stdin).steps;
@@ -1175,7 +1179,7 @@ function normalizeUrlLessOpen(args, stdin, batchStep = false) {
1175
1179
  function normalizeRawBatchRows(args, tokens, indices) {
1176
1180
  let normalized;
1177
1181
  for (const [offset, token] of tokens.slice(1).entries()) {
1178
- if (token === "--bail") {
1182
+ if (isNativeBatchOption(token)) {
1179
1183
  continue;
1180
1184
  }
1181
1185
  const step = parseBatchCommandArgument(token).step;
@@ -1251,20 +1255,37 @@ function parseUserBatchStdin(stdin) {
1251
1255
  return { steps };
1252
1256
  }
1253
1257
  function getUpstreamEffectiveBatchSteps(commandTokens, stdin) {
1258
+ return getUpstreamBatchInputSteps(commandTokens, stdin).filter((step) => step.length > 0);
1259
+ }
1260
+ function getUpstreamBatchInputSteps(commandTokens, stdin) {
1254
1261
  if (commandTokens[0] !== "batch") {
1255
1262
  return [];
1256
1263
  }
1257
1264
  const argumentSteps = commandTokens.slice(1).flatMap((command) => {
1258
- if (command === "--bail") {
1265
+ if (isNativeBatchOption(command)) {
1259
1266
  return [];
1260
1267
  }
1261
1268
  const step = parseBatchCommandArgument(command).step;
1262
1269
  return step ? [step] : [];
1263
1270
  });
1264
- if (commandTokens.slice(1).some((token) => token !== "--bail")) {
1271
+ if (commandTokens.slice(1).some((token) => !isNativeBatchOption(token))) {
1265
1272
  return argumentSteps;
1266
1273
  }
1267
- return parseUserBatchStdin(stdin).steps?.filter((step) => step.length > 0) ?? [];
1274
+ return parseUserBatchStdin(stdin).steps ?? [];
1275
+ }
1276
+ function resolveIndexedBatchCommands(envelope, commandTokens, stdin) {
1277
+ if (commandTokens[0] !== "batch" || !commandTokens.slice(1).includes("--omit-command") || !Array.isArray(envelope?.data) || !envelope.data.some((row) => isRecord(row) && row.command === void 0 && row.index !== void 0)) {
1278
+ return envelope;
1279
+ }
1280
+ const steps = getUpstreamBatchInputSteps(commandTokens, stdin);
1281
+ const data = envelope.data.map((row) => {
1282
+ if (!isRecord(row) || row.command !== void 0 || typeof row.index !== "number" || !Number.isSafeInteger(row.index) || row.index < 0) {
1283
+ return row;
1284
+ }
1285
+ const command = steps.at(row.index);
1286
+ return command !== void 0 && command.length > 0 ? { ...row, command } : row;
1287
+ });
1288
+ return { ...envelope, data };
1268
1289
  }
1269
1290
  var BATCH_STDIN_EXAMPLE, BatchWords;
1270
1291
  var init_batch_stdin = __esm({
@@ -1272,6 +1293,7 @@ var init_batch_stdin = __esm({
1272
1293
  "use strict";
1273
1294
  init_argv_grammar();
1274
1295
  init_shared();
1296
+ init_parsing();
1275
1297
  init_text();
1276
1298
  BATCH_STDIN_EXAMPLE = ' Example: { "args": ["batch"], "stdin": "[[\\"get\\",\\"title\\"],[\\"get\\",\\"url\\"]]" }';
1277
1299
  BatchWords = class {
@@ -1711,7 +1733,7 @@ function redactBatchRows(args) {
1711
1733
  return replacements;
1712
1734
  }
1713
1735
  for (let index = 1; index < batch.tokens.length; index++) {
1714
- if (batch.tokens[index] === "--bail") {
1736
+ if (isNativeBatchOption(batch.tokens[index])) {
1715
1737
  continue;
1716
1738
  }
1717
1739
  const step = parseBatchCommandArgument(batch.tokens[index]).step;
@@ -3895,15 +3917,30 @@ function snapshotDefinition(snapshot) {
3895
3917
  function snapshotMetadataValid(definition) {
3896
3918
  return (definition.target === void 0 || decodeBrowserTarget(definition.target) !== void 0) && (definition.generation === void 0 || typeof definition.generation === "string");
3897
3919
  }
3898
- function snapshotFromDefinition(definition) {
3899
- if (!isRecord(definition) || typeof definition.id !== "string" || definition.id.length === 0 || !isRecord(definition.refs) || !snapshotMetadataValid(definition) || !Object.entries(definition.refs).every(([id, ref]) => /^e\d+$/.test(id) && isBrowserSnapshotReference(ref))) {
3920
+ function snapshotReferencesValid(refs) {
3921
+ return Object.keys(refs).every((id) => SNAPSHOT_REF_ID.test(id) && isBrowserSnapshotReference(refs[id]));
3922
+ }
3923
+ function assertSnapshotDefinition(definition) {
3924
+ if (!isRecord(definition) || typeof definition.id !== "string" || definition.id.length === 0 || !isRecord(definition.refs) || !snapshotMetadataValid(definition) || !snapshotReferencesValid(definition.refs)) {
3900
3925
  throw new Error("Missing or invalid browser snapshot definition; take a new complete snapshot before using refs.");
3901
3926
  }
3902
- const refs = Object.fromEntries(Object.entries(definition.refs).flatMap(([id, ref]) => isNamedBrowserSnapshotReference(ref) ? [[id, ref]] : []));
3927
+ }
3928
+ function snapshotFromDefinition(definition) {
3929
+ assertSnapshotDefinition(definition);
3930
+ const refIds = Object.keys(definition.refs);
3931
+ const refs = {};
3932
+ let named = false;
3933
+ for (const id of refIds) {
3934
+ const ref = definition.refs[id];
3935
+ if (isNamedBrowserSnapshotReference(ref)) {
3936
+ refs[id] = ref;
3937
+ named = true;
3938
+ }
3939
+ }
3903
3940
  return {
3904
3941
  snapshotId: definition.id,
3905
- refIds: Object.keys(definition.refs),
3906
- ...Object.keys(refs).length > 0 ? { refs } : {},
3942
+ refIds,
3943
+ ...named ? { refs } : {},
3907
3944
  target: definition.target === void 0 ? void 0 : decodeBrowserTarget(definition.target),
3908
3945
  generation: typeof definition.generation === "string" ? definition.generation : void 0
3909
3946
  };
@@ -4005,7 +4042,7 @@ function applyArtifactChanges(previous, changes) {
4005
4042
  function appendBrowserTransition(pi, record) {
4006
4043
  pi.appendEntry(BROWSER_TRANSITION_ENTRY, record);
4007
4044
  }
4008
- var BROWSER_EVENT_VERSION;
4045
+ var BROWSER_EVENT_VERSION, SNAPSHOT_REF_ID;
4009
4046
  var init_browser_transcript = __esm({
4010
4047
  "dist/extensions/agent-browser/lib/browser-transcript.js"() {
4011
4048
  "use strict";
@@ -4018,6 +4055,7 @@ var init_browser_transcript = __esm({
4018
4055
  init_browser_transcript_contracts();
4019
4056
  init_browser_transcript_validation();
4020
4057
  BROWSER_EVENT_VERSION = 1;
4058
+ SNAPSHOT_REF_ID = /^e\d+$/;
4021
4059
  }
4022
4060
  });
4023
4061
 
@@ -4548,7 +4586,10 @@ function hasLaunchScopedFlagToken(args, flag) {
4548
4586
  const commandStartIndex = findCommandStartIndex(args);
4549
4587
  return commandStartIndex === void 0 || args[commandStartIndex] !== "wait" || args.indexOf(flag) < commandStartIndex;
4550
4588
  }
4551
- var LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAGS, LAUNCH_SCOPED_FLAG_LABEL, OPEN_RESULT_TAB_CORRECTION_FLAGS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS, MANAGED_RESTORE_INCOMPATIBLE_ENVS, MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS;
4589
+ function hasBrowserLaunchFlag(args) {
4590
+ return BROWSER_LAUNCH_FLAGS.some((flag) => hasLaunchScopedFlagToken(args, flag));
4591
+ }
4592
+ var LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAGS, LAUNCH_SCOPED_FLAG_LABEL, OPEN_RESULT_TAB_CORRECTION_FLAGS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS, MANAGED_RESTORE_INCOMPATIBLE_ENVS, MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS, HELPER_INHERITED_LAUNCH_FLAGS, BROWSER_LAUNCH_FLAGS;
4552
4593
  var init_launch_scoped_flags = __esm({
4553
4594
  "dist/extensions/agent-browser/lib/launch-scoped-flags.js"() {
4554
4595
  "use strict";
@@ -4739,6 +4780,10 @@ var init_launch_scoped_flags = __esm({
4739
4780
  "AGENT_BROWSER_ALLOW_FILE_ACCESS",
4740
4781
  "AGENT_BROWSER_WEBGPU"
4741
4782
  ];
4783
+ HELPER_INHERITED_LAUNCH_FLAGS = /* @__PURE__ */ new Set(["--namespace", "--idle-timeout"]);
4784
+ BROWSER_LAUNCH_FLAGS = [
4785
+ .../* @__PURE__ */ new Set([...LAUNCH_SCOPED_FLAGS, ...MANAGED_RESTORE_INCOMPATIBLE_FLAGS])
4786
+ ].filter((flag) => !HELPER_INHERITED_LAUNCH_FLAGS.has(flag));
4742
4787
  }
4743
4788
  });
4744
4789
 
@@ -4756,14 +4801,86 @@ var init_temp_mutation_queue = __esm({
4756
4801
  }
4757
4802
  });
4758
4803
 
4804
+ // dist/extensions/agent-browser/lib/browser-execution-policy-debug.js
4805
+ import { AsyncLocalStorage } from "node:async_hooks";
4806
+ import { debuglog } from "node:util";
4807
+ function debugPolicyFailure(stage, reason, error) {
4808
+ if (!policyDebugEnabled) {
4809
+ return;
4810
+ }
4811
+ const context2 = attempts?.getStore();
4812
+ const code = nativeField(error, "code");
4813
+ const syscall = nativeField(error, "syscall");
4814
+ const exit = isRecord(error) && typeof error.code === "number" && Number.isSafeInteger(error.code) ? error.code : "-";
4815
+ debug("attempt=%s phase=%s stage=%s reason=%s code=%s syscall=%s exit=%s", context2?.attempt ?? 0, context2?.phase ?? "standalone", stage, reason, code, syscall, exit);
4816
+ }
4817
+ function nativeField(error, field) {
4818
+ const value = isRecord(error) ? error[field] : void 0;
4819
+ if (typeof value !== "string") {
4820
+ return "-";
4821
+ }
4822
+ if (field === "code") {
4823
+ return /^(?:E[A-Z0-9]{1,24}|ABORT_ERR|ERR_[A-Z_]{1,48})$/.test(value) ? value : "unknown";
4824
+ }
4825
+ if (value.startsWith("spawn ")) {
4826
+ return "spawn";
4827
+ }
4828
+ return syscalls.has(value) ? value : "unknown";
4829
+ }
4830
+ function withPolicyDebugAttempt(run) {
4831
+ return attempts === void 0 ? run() : attempts.run({ attempt: ++nextAttempt, phase: "acquire" }, run);
4832
+ }
4833
+ function policyDebugPhase(phase, run) {
4834
+ const context2 = attempts?.getStore();
4835
+ return context2 === void 0 || attempts === void 0 ? run : () => attempts.run({ ...context2, phase }, run);
4836
+ }
4837
+ function policyDebugError(error) {
4838
+ const code = nativeField(error, "code");
4839
+ const syscall = nativeField(error, "syscall");
4840
+ let normalizedCode = code === "-" ? void 0 : code;
4841
+ if (isRecord(error) && typeof error.code === "number" && Number.isSafeInteger(error.code)) {
4842
+ normalizedCode = error.code;
4843
+ }
4844
+ return { code: normalizedCode, syscall: syscall === "-" ? void 0 : syscall };
4845
+ }
4846
+ var debug, policyDebugEnabled, attempts, nextAttempt, syscalls;
4847
+ var init_browser_execution_policy_debug = __esm({
4848
+ "dist/extensions/agent-browser/lib/browser-execution-policy-debug.js"() {
4849
+ "use strict";
4850
+ init_parsing();
4851
+ debug = debuglog("pi-agent-browser-policy");
4852
+ policyDebugEnabled = debug.enabled;
4853
+ attempts = policyDebugEnabled ? new AsyncLocalStorage() : void 0;
4854
+ nextAttempt = 0;
4855
+ syscalls = /* @__PURE__ */ new Set([
4856
+ "mkdir",
4857
+ "lstat",
4858
+ "stat",
4859
+ "scandir",
4860
+ "open",
4861
+ "read",
4862
+ "write",
4863
+ "rename",
4864
+ "rmdir",
4865
+ "unlink",
4866
+ "rm",
4867
+ "kill",
4868
+ "spawn",
4869
+ "connect",
4870
+ "listen"
4871
+ ]);
4872
+ }
4873
+ });
4874
+
4759
4875
  // dist/extensions/agent-browser/lib/process-identity.js
4760
4876
  import { execFile } from "node:child_process";
4877
+ import { readFile } from "node:fs/promises";
4761
4878
  import { dirname, join, win32 } from "node:path";
4762
4879
  function getCurrentProcessUid() {
4763
4880
  return typeof process.getuid === "function" ? process.getuid() : void 0;
4764
4881
  }
4765
4882
  function buildProcessStartIdentityCommand(pid, platform = process.platform) {
4766
- if (!Number.isSafeInteger(pid) || pid <= 0) {
4883
+ if (!Number.isSafeInteger(pid) || pid <= 0 || platform === "linux") {
4767
4884
  return void 0;
4768
4885
  }
4769
4886
  const configuredSystemRoot = process.env.SystemRoot;
@@ -4773,11 +4890,12 @@ function buildProcessStartIdentityCommand(pid, platform = process.platform) {
4773
4890
  "-NoProfile",
4774
4891
  "-NonInteractive",
4775
4892
  "-Command",
4776
- `$p = Get-Process -Id ${pid} -ErrorAction Stop; Write-Output ("${WINDOWS_PROCESS_START_IDENTITY_PREFIX}" + $p.StartTime.ToUniversalTime().Ticks)`
4893
+ `$ErrorActionPreference = 'Stop'; $p = $null; try { $p = [System.Diagnostics.Process]::GetProcessById(${pid}); [long]$ticks = $p.StartTime.ToUniversalTime().Ticks; if ($ticks -le 0) { throw 'Process start ticks unavailable' }; [Console]::Out.WriteLine("${WINDOWS_PROCESS_START_IDENTITY_PREFIX}" + $ticks.ToString([Globalization.CultureInfo]::InvariantCulture)) } finally { if ($null -ne $p) { $p.Dispose() } }`
4777
4894
  ],
4778
4895
  file: win32.join(windowsSystemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
4779
4896
  } : {
4780
4897
  args: ["-p", String(pid), "-o", "lstart="],
4898
+ env: { ...process.env, TZ: "UTC0", LC_ALL: "C" },
4781
4899
  file: platform === "android" ? join(dirname(process.execPath), "ps") : "/bin/ps"
4782
4900
  };
4783
4901
  }
@@ -4804,14 +4922,22 @@ function normalizeProcessStartIdentity(stdout) {
4804
4922
  }
4805
4923
  return trimmed.replace(/\s+/g, " ");
4806
4924
  }
4925
+ function debugIdentityBudget(budget) {
4926
+ debugPolicyFailure("probe.budget", budget?.signal?.aborted === true ? "cancelled" : "deadline");
4927
+ }
4807
4928
  async function executeProcessStartIdentityCommand(command, budget = {}) {
4808
- if (budget.signal?.aborted === true || budget.deadline !== void 0 && Date.now() >= budget.deadline) {
4929
+ if (budget.signal?.aborted === true || budget.deadline !== void 0 && performance.now() >= budget.deadline) {
4930
+ debugIdentityBudget(budget);
4809
4931
  return void 0;
4810
4932
  }
4811
- const timeout = Math.max(1, Math.min(PROCESS_START_IDENTITY_TIMEOUT_MS, (budget.deadline ?? Infinity) - Date.now()));
4933
+ const timeout = Math.max(1, Math.ceil(Math.min(PROCESS_START_IDENTITY_TIMEOUT_MS, (budget.deadline ?? Infinity) - performance.now())));
4812
4934
  return await new Promise((resolve19) => {
4813
- execFile(command.file, command.args, { timeout, signal: budget.signal }, (error, stdout) => {
4814
- resolve19(error ? void 0 : normalizeProcessStartIdentity(stdout));
4935
+ execFile(command.file, command.args, { env: command.env, timeout, signal: budget.signal }, (error, stdout) => {
4936
+ const identity = error ? void 0 : normalizeProcessStartIdentity(stdout);
4937
+ if (identity === void 0) {
4938
+ debugPolicyFailure("probe.execute", error ? "native-error" : "invalid-output", error);
4939
+ }
4940
+ resolve19(identity);
4815
4941
  });
4816
4942
  });
4817
4943
  }
@@ -4824,14 +4950,67 @@ async function resolveProcessStartIdentityFromCommands(commands, execute = execu
4824
4950
  }
4825
4951
  return void 0;
4826
4952
  }
4953
+ async function readLinuxProcfsText(path, budget) {
4954
+ if (identityBudgetExpired(budget)) {
4955
+ debugIdentityBudget(budget);
4956
+ return void 0;
4957
+ }
4958
+ try {
4959
+ return await readFile(path, { encoding: "latin1", signal: budget.signal });
4960
+ } catch (error) {
4961
+ debugPolicyFailure("probe.read", "native-error", error);
4962
+ return void 0;
4963
+ }
4964
+ }
4965
+ async function readLinuxBootId(budget) {
4966
+ const bootId = (await readLinuxProcfsText(LINUX_BOOT_ID_PATH, budget))?.replace(/\n$/, "");
4967
+ if (bootId === void 0 || !LINUX_BOOT_ID_PATTERN.test(bootId)) {
4968
+ debugPolicyFailure("probe.boot-id", bootId === void 0 ? "unavailable" : "invalid-output");
4969
+ return void 0;
4970
+ }
4971
+ return bootId;
4972
+ }
4973
+ function parseLinuxStartTicks(stat16, pid) {
4974
+ const header = `${String(pid)} (`;
4975
+ const commEnd = stat16.lastIndexOf(")");
4976
+ if (!stat16.startsWith(header) || commEnd < header.length || stat16[commEnd + 1] !== " ") {
4977
+ return void 0;
4978
+ }
4979
+ const ticks = stat16.slice(commEnd + 2).replace(/\n$/, "").split(" ").at(LINUX_STAT_START_TICKS_INDEX);
4980
+ return ticks !== void 0 && /^[0-9]+$/.test(ticks) ? ticks : void 0;
4981
+ }
4982
+ async function readLinuxProcessStartIdentity(pid, budget = {}) {
4983
+ if (!Number.isSafeInteger(pid) || pid <= 0) {
4984
+ return void 0;
4985
+ }
4986
+ const bootId = await readLinuxBootId(budget);
4987
+ if (bootId === void 0) {
4988
+ return void 0;
4989
+ }
4990
+ const stat16 = await readLinuxProcfsText(`/proc/${String(pid)}/stat`, budget);
4991
+ if (stat16 === void 0) {
4992
+ return void 0;
4993
+ }
4994
+ const ticks = parseLinuxStartTicks(stat16, pid);
4995
+ if (ticks === void 0) {
4996
+ debugPolicyFailure("probe.stat", "invalid-output");
4997
+ return void 0;
4998
+ }
4999
+ return `${LINUX_PROCESS_START_IDENTITY_PREFIX}${bootId}:${ticks}`;
5000
+ }
4827
5001
  async function readUncachedProcessStartIdentity(pid, platform, budget) {
4828
- return await resolveProcessStartIdentityFromCommands(buildProcessStartIdentityCommands(pid, platform), (command) => executeProcessStartIdentityCommand(command, budget));
5002
+ if (platform === "linux") {
5003
+ return await readLinuxProcessStartIdentity(pid, budget);
5004
+ }
5005
+ const identity = await resolveProcessStartIdentityFromCommands(buildProcessStartIdentityCommands(pid, platform), (command) => executeProcessStartIdentityCommand(command, budget));
5006
+ return identity === void 0 || platform === "win32" ? identity : `${POSIX_PROCESS_START_IDENTITY_PREFIX}${identity}`;
4829
5007
  }
4830
5008
  function identityBudgetExpired(budget) {
4831
- return budget?.signal?.aborted === true || budget?.deadline !== void 0 && Date.now() >= budget.deadline;
5009
+ return budget?.signal?.aborted === true || budget?.deadline !== void 0 && performance.now() >= budget.deadline;
4832
5010
  }
4833
5011
  async function readProcessStartIdentity(pid, platform = process.platform, budget) {
4834
5012
  if (identityBudgetExpired(budget)) {
5013
+ debugIdentityBudget(budget);
4835
5014
  return void 0;
4836
5015
  }
4837
5016
  if (pid !== process.pid || platform !== process.platform) {
@@ -4861,20 +5040,35 @@ async function readCachedProcessStartIdentity(pid, platform) {
4861
5040
  return await currentProcessStartIdentityPromise;
4862
5041
  }
4863
5042
  function processStartIdentitiesMatch(recorded, current) {
5043
+ const format4 = PROCESS_START_IDENTITY_FORMATS.find((prefix) => current.startsWith(prefix));
5044
+ if (format4 === void 0 || !recorded.startsWith(format4)) {
5045
+ return void 0;
5046
+ }
4864
5047
  return recorded === current;
4865
5048
  }
4866
- var WINDOWS_PROCESS_START_IDENTITY_PREFIX, PROCESS_START_IDENTITY_TIMEOUT_MS, DEFAULT_WINDOWS_SYSTEM_ROOT, currentProcessStartIdentityPromise, currentProcessStartIdentity;
5049
+ var WINDOWS_PROCESS_START_IDENTITY_PREFIX, POSIX_PROCESS_START_IDENTITY_PREFIX, LINUX_PROCESS_START_IDENTITY_PREFIX, PROCESS_START_IDENTITY_FORMATS, PROCESS_START_IDENTITY_TIMEOUT_MS, DEFAULT_WINDOWS_SYSTEM_ROOT, LINUX_BOOT_ID_PATH, LINUX_BOOT_ID_PATTERN, LINUX_STAT_START_TICKS_INDEX, currentProcessStartIdentityPromise, currentProcessStartIdentity;
4867
5050
  var init_process_identity = __esm({
4868
5051
  "dist/extensions/agent-browser/lib/process-identity.js"() {
4869
5052
  "use strict";
5053
+ init_browser_execution_policy_debug();
4870
5054
  WINDOWS_PROCESS_START_IDENTITY_PREFIX = "win32-powershell-ticks-v1:";
5055
+ POSIX_PROCESS_START_IDENTITY_PREFIX = "posix-ps-utc-v1:";
5056
+ LINUX_PROCESS_START_IDENTITY_PREFIX = "linux-proc-boot-ticks-v1:";
5057
+ PROCESS_START_IDENTITY_FORMATS = [
5058
+ WINDOWS_PROCESS_START_IDENTITY_PREFIX,
5059
+ POSIX_PROCESS_START_IDENTITY_PREFIX,
5060
+ LINUX_PROCESS_START_IDENTITY_PREFIX
5061
+ ];
4871
5062
  PROCESS_START_IDENTITY_TIMEOUT_MS = process.platform === "win32" ? 1e4 : 5e3;
4872
5063
  DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
5064
+ LINUX_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id";
5065
+ LINUX_BOOT_ID_PATTERN = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
5066
+ LINUX_STAT_START_TICKS_INDEX = 22 - 3;
4873
5067
  }
4874
5068
  });
4875
5069
 
4876
5070
  // dist/extensions/agent-browser/lib/temp-root-ownership.js
4877
- import { chmod, readFile, readdir, rm, stat as stat3, writeFile } from "node:fs/promises";
5071
+ import { chmod, readFile as readFile2, readdir, rm, stat as stat3, writeFile } from "node:fs/promises";
4878
5072
  import { basename, dirname as dirname2, join as join2, resolve } from "node:path";
4879
5073
  function getCurrentProcessUid2() {
4880
5074
  return typeof process.getuid === "function" ? process.getuid() : void 0;
@@ -4930,7 +5124,7 @@ function validOwnershipAndChildren(value) {
4930
5124
  }
4931
5125
  async function readTempRootOwnershipMarker(tempRoot) {
4932
5126
  try {
4933
- const markerText = await readFile(join2(tempRoot, TEMP_ROOT_MARKER_FILE_NAME), "utf8");
5127
+ const markerText = await readFile2(join2(tempRoot, TEMP_ROOT_MARKER_FILE_NAME), "utf8");
4934
5128
  const parsed = JSON.parse(markerText);
4935
5129
  return isTempRootOwnershipRecord(parsed) ? parsed : void 0;
4936
5130
  } catch {
@@ -5048,7 +5242,7 @@ async function refreshSecureTempRootLease(tempRoot) {
5048
5242
  const currentProcessStartIdentity2 = await getProcessStartIdentity(process.pid);
5049
5243
  if (ownershipMarker.ownerProcessStartIdentity !== void 0 && currentProcessStartIdentity2 !== void 0) {
5050
5244
  const identitiesMatch = processStartIdentitiesMatch(ownershipMarker.ownerProcessStartIdentity, currentProcessStartIdentity2);
5051
- if (!identitiesMatch) {
5245
+ if (identitiesMatch !== true) {
5052
5246
  return;
5053
5247
  }
5054
5248
  }
@@ -5083,6 +5277,9 @@ async function getMarkerOwnerLiveness(ownershipMarker) {
5083
5277
  return "unknown";
5084
5278
  }
5085
5279
  const identitiesMatch = processStartIdentitiesMatch(ownershipMarker.ownerProcessStartIdentity, currentProcessStartIdentity2);
5280
+ if (identitiesMatch === void 0) {
5281
+ return "unknown";
5282
+ }
5086
5283
  return identitiesMatch ? "alive" : "dead";
5087
5284
  }
5088
5285
  var TEMP_ROOT_MARKER_FILE_NAME, TEMP_ROOT_MARKER_KIND, TEMP_ROOT_MARKER_VERSION;
@@ -5470,7 +5667,7 @@ var init_temp = __esm({
5470
5667
  });
5471
5668
 
5472
5669
  // dist/extensions/agent-browser/lib/process-environment.js
5473
- import { AsyncLocalStorage } from "node:async_hooks";
5670
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
5474
5671
  import { rm as rm5 } from "node:fs/promises";
5475
5672
  function getAgentBrowserProcessEnvironment(baseEnv = process.env) {
5476
5673
  const isolatedConfig = isolatedAgentBrowserEnvironment.getStore();
@@ -5518,8 +5715,8 @@ var init_process_environment = __esm({
5518
5715
  "use strict";
5519
5716
  init_argv_grammar();
5520
5717
  init_temp();
5521
- isolatedAgentBrowserEnvironment = new AsyncLocalStorage();
5522
- agentBrowserProcessEnvironment = new AsyncLocalStorage();
5718
+ isolatedAgentBrowserEnvironment = new AsyncLocalStorage2();
5719
+ agentBrowserProcessEnvironment = new AsyncLocalStorage2();
5523
5720
  PROXY_ENV_NAMES = /* @__PURE__ */ new Set(["ALL_PROXY", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY"]);
5524
5721
  }
5525
5722
  });
@@ -5546,7 +5743,7 @@ var TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL, MINIMUM_AG
5546
5743
  var init_agent_browser_target = __esm({
5547
5744
  "dist/scripts/agent-browser-target.mjs"() {
5548
5745
  "use strict";
5549
- TARGET_AGENT_BROWSER_VERSION = "0.38.1";
5746
+ TARGET_AGENT_BROWSER_VERSION = "0.41.0";
5550
5747
  TARGET_AGENT_BROWSER_VERSION_LABEL = `agent-browser ${TARGET_AGENT_BROWSER_VERSION}`;
5551
5748
  MINIMUM_AGENT_BROWSER_VERSION = "0.35.0";
5552
5749
  MINIMUM_AGENT_BROWSER_VERSION_LABEL = `agent-browser ${MINIMUM_AGENT_BROWSER_VERSION}`;
@@ -6323,7 +6520,7 @@ var init_managed_session_storage = __esm({
6323
6520
  });
6324
6521
 
6325
6522
  // dist/extensions/agent-browser/lib/owned-managed-session-context.js
6326
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
6523
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
6327
6524
  async function withOwnedManagedSessionContext(context2, run) {
6328
6525
  return await ownedManagedSessionStorage.run(context2, run);
6329
6526
  }
@@ -6372,7 +6569,7 @@ var init_owned_managed_session_context = __esm({
6372
6569
  "dist/extensions/agent-browser/lib/owned-managed-session-context.js"() {
6373
6570
  "use strict";
6374
6571
  init_argv_grammar();
6375
- ownedManagedSessionStorage = new AsyncLocalStorage2();
6572
+ ownedManagedSessionStorage = new AsyncLocalStorage3();
6376
6573
  }
6377
6574
  });
6378
6575
 
@@ -6407,7 +6604,7 @@ function batchHasManagedSessionRestoreConflict(args, stdin) {
6407
6604
  if (command !== "batch") {
6408
6605
  return false;
6409
6606
  }
6410
- if (commandArgs.some((token) => token !== "--bail")) {
6607
+ if (commandArgs.some((token) => !isNativeBatchOption(token))) {
6411
6608
  return true;
6412
6609
  }
6413
6610
  const parsed = parseUserBatchStdin(stdin);
@@ -8954,7 +9151,7 @@ __export(lookup_workspace_exports, {
8954
9151
  scanWorkspaceComponent: () => scanWorkspaceComponent,
8955
9152
  scanWorkspaceRequests: () => scanWorkspaceRequests
8956
9153
  });
8957
- import { readFile as readFile2, readdir as readdir4 } from "node:fs/promises";
9154
+ import { readFile as readFile3, readdir as readdir4 } from "node:fs/promises";
8958
9155
  import { extname, join as join11 } from "node:path";
8959
9156
  async function walkWorkspaceSourceFiles(root, maxFiles) {
8960
9157
  const files = [];
@@ -8986,7 +9183,7 @@ async function walkWorkspaceSourceFiles(root, maxFiles) {
8986
9183
  }
8987
9184
  async function readWorkspaceText(file) {
8988
9185
  try {
8989
- return await readFile2(file, "utf8");
9186
+ return await readFile3(file, "utf8");
8990
9187
  } catch {
8991
9188
  return void 0;
8992
9189
  }
@@ -9575,6 +9772,7 @@ var init_content = __esm({
9575
9772
  "failures",
9576
9773
  "artifacts",
9577
9774
  "artifactVerification",
9775
+ "outputFile",
9578
9776
  "imageObservations",
9579
9777
  "fullOutputPath",
9580
9778
  "fullOutputPaths",
@@ -10319,7 +10517,7 @@ var init_artifact_format = __esm({
10319
10517
  });
10320
10518
 
10321
10519
  // dist/extensions/agent-browser/lib/results/presentation/artifact-images.js
10322
- import { readFile as readFile3, stat as stat7 } from "node:fs/promises";
10520
+ import { readFile as readFile4, stat as stat7 } from "node:fs/promises";
10323
10521
  import { resolve as resolve6 } from "node:path";
10324
10522
  import { inspect as inspect2 } from "node:util";
10325
10523
  import { getImageDimensions } from "@earendil-works/pi-tui";
@@ -10386,7 +10584,7 @@ async function attachVisibleImage(presentation, options) {
10386
10584
  appendPresentationNotice(presentation, `Image attachment skipped: ${formatByteCount(options.size)} exceeds the inline limit of ${formatByteCount(limit)}.`);
10387
10585
  return;
10388
10586
  }
10389
- const file = await readFile3(options.imagePath);
10587
+ const file = await readFile4(options.imagePath);
10390
10588
  presentation.content.push({
10391
10589
  type: "image",
10392
10590
  data: file.toString("base64"),
@@ -10956,6 +11154,8 @@ function buildCompactObservation(observation2, persisted) {
10956
11154
  "sessionName",
10957
11155
  "namespace",
10958
11156
  "codeRun",
11157
+ // A requested outputPath receipt is small and confirms the caller's side effect; keep it ahead of bulky fields.
11158
+ "outputFile",
10959
11159
  "error",
10960
11160
  "failures",
10961
11161
  "nextActions",
@@ -10998,7 +11198,7 @@ async function renderAgentBrowserObservation(options) {
10998
11198
  let structuredContent = observation2;
10999
11199
  const images = options.content.filter((part) => part.type === "image");
11000
11200
  const prose = options.content.filter((part) => part.type === "text").map((part) => part.text).join("\n\n");
11001
- const { data: _data, error: _error, summary: _summary, ...metadata } = observation2;
11201
+ const { data: _data, error: _error, summary: _summary, outputFile: _outputFile, ...metadata } = observation2;
11002
11202
  let text3 = options.json ? JSON.stringify(observation2, null, 2) : `${prose}
11003
11203
 
11004
11204
  Observation: ${JSON.stringify(metadata)}`;
@@ -11984,6 +12184,7 @@ var init_process_stdout = __esm({
11984
12184
  // dist/extensions/agent-browser/lib/process-child.js
11985
12185
  import { spawn } from "node:child_process";
11986
12186
  import { platform as processPlatform } from "node:process";
12187
+ import { constants as constants3 } from "node:os";
11987
12188
  import { spawn as crossSpawn } from "cross-spawn";
11988
12189
  async function terminateSpawnedChild(child, signal) {
11989
12190
  if (processPlatform === "win32" && child.pid !== void 0 && child.pid !== 0) {
@@ -12010,6 +12211,9 @@ function resolveSpawnedChildExitCode(input) {
12010
12211
  if (input.useExitFallback && input.exitCode !== null && input.exitCode !== void 0) {
12011
12212
  return input.exitCode;
12012
12213
  }
12214
+ if (input.exitSignal !== null && input.exitSignal !== void 0) {
12215
+ return 128 + constants3.signals[input.exitSignal];
12216
+ }
12013
12217
  return input.spawnError ? 127 : 0;
12014
12218
  }
12015
12219
  function destroySpawnedChildStreams(child) {
@@ -12049,21 +12253,24 @@ var init_process_child = __esm({
12049
12253
  context;
12050
12254
  exited = false;
12051
12255
  exitCode = null;
12256
+ exitSignal = null;
12052
12257
  timer;
12053
12258
  completed = false;
12054
12259
  constructor(child, complete, context2) {
12055
12260
  this.complete = complete;
12056
12261
  this.context = context2;
12057
- child.once("exit", (code) => {
12262
+ child.once("exit", (code, signal) => {
12058
12263
  this.exited = true;
12059
12264
  this.exitCode = code;
12265
+ this.exitSignal = signal;
12060
12266
  this.timer = setTimeout(() => {
12061
12267
  destroySpawnedChildStreams(child);
12062
12268
  this.finish();
12063
12269
  }, EXIT_STDIO_GRACE_MS);
12064
12270
  this.timer.unref();
12065
12271
  });
12066
- child.once("close", (code) => {
12272
+ child.once("close", (code, signal) => {
12273
+ this.exitSignal = signal ?? this.exitSignal;
12067
12274
  this.finish(code);
12068
12275
  });
12069
12276
  }
@@ -12077,10 +12284,11 @@ var init_process_child = __esm({
12077
12284
  this.complete(resolveSpawnedChildExitCode({
12078
12285
  closeCode,
12079
12286
  exitCode: this.exitCode,
12287
+ exitSignal: this.exitSignal,
12080
12288
  useExitFallback: this.exited,
12081
12289
  timedOut: context2.timedOut,
12082
12290
  spawnError: context2.spawnError
12083
- }));
12291
+ }), this.exitSignal ?? void 0);
12084
12292
  }
12085
12293
  clear() {
12086
12294
  if (this.timer) {
@@ -12096,6 +12304,7 @@ var init_process_child = __esm({
12096
12304
  agentBrowserStarted = false;
12097
12305
  settled = false;
12098
12306
  spawnError;
12307
+ exitSignal;
12099
12308
  stderr = "";
12100
12309
  stdout = new ProcessStdout();
12101
12310
  pendingTermination;
@@ -12142,8 +12351,8 @@ var init_process_child = __esm({
12142
12351
  spawnError: this.spawnError
12143
12352
  }));
12144
12353
  });
12145
- this.completion = new ChildCompletion(child, (code) => {
12146
- this.finish(code);
12354
+ this.completion = new ChildCompletion(child, (code, signal) => {
12355
+ this.finish(code, signal);
12147
12356
  }, () => ({ timedOut: this.timedOut, spawnError: this.spawnError }));
12148
12357
  child.stdout.on("data", (chunk) => {
12149
12358
  this.stdout.queue(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -12212,11 +12421,12 @@ var init_process_child = __esm({
12212
12421
  this.child.stdin.destroy();
12213
12422
  }
12214
12423
  }
12215
- finish(exitCode) {
12424
+ finish(exitCode, exitSignal) {
12216
12425
  if (this.settled) {
12217
12426
  return;
12218
12427
  }
12219
12428
  this.settled = true;
12429
+ this.exitSignal = exitSignal;
12220
12430
  if (this.abortListener) {
12221
12431
  this.options.signal?.removeEventListener("abort", this.abortListener);
12222
12432
  this.abortListener = void 0;
@@ -12250,6 +12460,7 @@ var init_process_child = __esm({
12250
12460
  aborted: this.aborted,
12251
12461
  agentBrowserStarted: this.agentBrowserStarted,
12252
12462
  exitCode,
12463
+ exitSignal: this.exitSignal,
12253
12464
  spawnError: this.spawnError,
12254
12465
  stderr: this.stderr,
12255
12466
  stdout,
@@ -12337,7 +12548,7 @@ function getBatchCommandSteps(args, stdin) {
12337
12548
  if (descriptor.commandInfo.command !== "batch") {
12338
12549
  return { steps: [] };
12339
12550
  }
12340
- const argumentsToParse = descriptor.upstreamCommandTokens.slice(1).filter((command) => command !== "--bail");
12551
+ const argumentsToParse = descriptor.upstreamCommandTokens.slice(1).filter((command) => !isNativeBatchOption(command));
12341
12552
  if (argumentsToParse.length > 0) {
12342
12553
  return inspectBatchArguments(argumentsToParse);
12343
12554
  }
@@ -12839,7 +13050,7 @@ var init_process_socket_storage = __esm({
12839
13050
  });
12840
13051
 
12841
13052
  // dist/extensions/agent-browser/lib/process.js
12842
- import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
13053
+ import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
12843
13054
  import { env as processEnv2 } from "node:process";
12844
13055
  function withChromeStartupArgs(args, run) {
12845
13056
  return chromeStartupArgsContext.run(args ?? chromeStartupArgsContext.getStore(), run);
@@ -13037,8 +13248,8 @@ var init_process = __esm({
13037
13248
  PI_AGENT_BROWSER_PROCESS_TIMEOUT_ENV = "PI_AGENT_BROWSER_PROCESS_TIMEOUT_MS";
13038
13249
  SAFE_AGENT_BROWSER_OPERATION_TIMEOUT_MS = 25e3;
13039
13250
  DEFAULT_AGENT_BROWSER_PROCESS_TIMEOUT_MS = 35e3;
13040
- attachedBrowserSessionContext = new AsyncLocalStorage4();
13041
- chromeStartupArgsContext = new AsyncLocalStorage4();
13251
+ attachedBrowserSessionContext = new AsyncLocalStorage5();
13252
+ chromeStartupArgsContext = new AsyncLocalStorage5();
13042
13253
  }
13043
13254
  });
13044
13255
 
@@ -13098,7 +13309,7 @@ var init_envelope_decoding = __esm({
13098
13309
  });
13099
13310
 
13100
13311
  // dist/extensions/agent-browser/lib/results/envelope.js
13101
- import { readFile as readFile5 } from "node:fs/promises";
13312
+ import { readFile as readFile6 } from "node:fs/promises";
13102
13313
  function hasStructuredBatchStepFailure(data) {
13103
13314
  return Array.isArray(data) && data.some((item) => isRecord(item) && item.success === false);
13104
13315
  }
@@ -13107,7 +13318,7 @@ async function readEnvelopeSource(options) {
13107
13318
  return options.stdout;
13108
13319
  }
13109
13320
  try {
13110
- return await readFile5(options.stdoutPath, "utf8");
13321
+ return await readFile6(options.stdoutPath, "utf8");
13111
13322
  } catch (error) {
13112
13323
  const message = error instanceof Error ? error.message : stringifyUnknown(error);
13113
13324
  throw new Error(`agent-browser output spill file could not be read: ${message}`, {
@@ -13232,10 +13443,24 @@ function getTransportErrorText(options) {
13232
13443
  return options.spawnError.message;
13233
13444
  }
13234
13445
  if ((options.parseError ?? "") !== "") {
13235
- return options.exitCode !== 0 && options.stderr.trim().length > 0 ? options.stderr.trim() : options.parseError;
13446
+ return malformedProcessErrorText(options);
13236
13447
  }
13237
13448
  return void 0;
13238
13449
  }
13450
+ function malformedProcessErrorText(options) {
13451
+ if (options.exitCode === 0 && options.exitSignal === void 0) {
13452
+ return options.parseError;
13453
+ }
13454
+ const signalText = options.exitSignal === void 0 ? void 0 : `Signal: ${options.exitSignal}.`;
13455
+ const oomHint = options.exitSignal === "SIGKILL" || options.exitCode === 137 ? "SIGKILL may indicate an OOM kill or an external stop. Inspect the host kernel log and cgroup memory.events before retrying; the command may have executed." : void 0;
13456
+ return [
13457
+ buildExitCodeFallback(options),
13458
+ signalText,
13459
+ options.stderr.trim(),
13460
+ options.stderr.trim().length > 0 ? void 0 : options.parseError,
13461
+ oomHint
13462
+ ].filter(Boolean).join("\n");
13463
+ }
13239
13464
  function getEnvelopeDataError(data) {
13240
13465
  if (typeof data === "string") {
13241
13466
  return data;
@@ -13286,7 +13511,7 @@ var init_envelope = __esm({
13286
13511
  });
13287
13512
 
13288
13513
  // dist/extensions/agent-browser/lib/webmcp-observation.js
13289
- import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
13514
+ import { AsyncLocalStorage as AsyncLocalStorage6 } from "node:async_hooks";
13290
13515
  function getNativeWebMcpCatalog(data) {
13291
13516
  if (Array.isArray(data)) {
13292
13517
  for (let index = data.length - 1; index >= 0; index -= 1) {
@@ -13321,12 +13546,12 @@ var init_webmcp_observation = __esm({
13321
13546
  "dist/extensions/agent-browser/lib/webmcp-observation.js"() {
13322
13547
  "use strict";
13323
13548
  init_parsing();
13324
- observation = new AsyncLocalStorage5();
13549
+ observation = new AsyncLocalStorage6();
13325
13550
  }
13326
13551
  });
13327
13552
 
13328
13553
  // dist/extensions/agent-browser/lib/orchestration/browser-run/session-state-commands.js
13329
- import { AsyncLocalStorage as AsyncLocalStorage6 } from "node:async_hooks";
13554
+ import { AsyncLocalStorage as AsyncLocalStorage7 } from "node:async_hooks";
13330
13555
  import { rm as rm6 } from "node:fs/promises";
13331
13556
  function withSessionCommandObservation(observer, run) {
13332
13557
  return sessionCommandObservation.run(observer, run);
@@ -13441,7 +13666,7 @@ var init_session_state_commands = __esm({
13441
13666
  init_confirmation();
13442
13667
  init_envelope();
13443
13668
  init_webmcp_observation();
13444
- sessionCommandObservation = new AsyncLocalStorage6();
13669
+ sessionCommandObservation = new AsyncLocalStorage7();
13445
13670
  }
13446
13671
  });
13447
13672
 
@@ -15016,7 +15241,7 @@ function hasProcessExited(child, pid) {
15016
15241
  return child ? child.exitCode !== null || child.signalCode !== null : isPidAlive(pid) === false;
15017
15242
  }
15018
15243
  async function waitForProcessExit(child, pid, deadlineMs) {
15019
- while (Date.now() <= deadlineMs) {
15244
+ while (performance.now() <= deadlineMs) {
15020
15245
  if (hasProcessExited(child, pid)) {
15021
15246
  return true;
15022
15247
  }
@@ -15032,7 +15257,7 @@ async function getRestoredProcessVerificationError(record) {
15032
15257
  if (currentIdentity === void 0) {
15033
15258
  return `PID ${String(record.pid)} is alive, but its current start identity could not be inspected; refusing to signal a restored PID that may have been reused.`;
15034
15259
  }
15035
- if (!processStartIdentitiesMatch(record.startIdentity, currentIdentity)) {
15260
+ if (processStartIdentitiesMatch(record.startIdentity, currentIdentity) !== true) {
15036
15261
  return `PID ${String(record.pid)} is alive, but its start identity does not match the wrapper launch record; refusing to signal a restored PID that may have been reused.`;
15037
15262
  }
15038
15263
  return void 0;
@@ -15091,7 +15316,7 @@ async function cleanupRestoredProcess(record, deadlineMs) {
15091
15316
  return { error: killVerificationError, resource: "process", state: "failed" };
15092
15317
  }
15093
15318
  signalRestoredLaunchProcess(record, "SIGKILL");
15094
- if (await waitForProcessExit(void 0, record.pid, Date.now() + 1e3)) {
15319
+ if (await waitForProcessExit(void 0, record.pid, performance.now() + 1e3)) {
15095
15320
  return { resource: "process", state: "removed" };
15096
15321
  }
15097
15322
  return {
@@ -15114,7 +15339,7 @@ async function cleanupTrackedProcess(record, child, deadlineMs) {
15114
15339
  } else {
15115
15340
  killPid(record.pid, "SIGKILL");
15116
15341
  }
15117
- if (await waitForProcessExit(child, record.pid, Date.now() + 1e3)) {
15342
+ if (await waitForProcessExit(child, record.pid, performance.now() + 1e3)) {
15118
15343
  return { resource: "process", state: "removed" };
15119
15344
  }
15120
15345
  return {
@@ -15176,7 +15401,7 @@ function summarizeCleanup(launchId, steps) {
15176
15401
  }
15177
15402
  async function cleanupElectronLaunchResources(options) {
15178
15403
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isSafeInteger(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : ELECTRON_CLEANUP_DEFAULT_TIMEOUT_MS;
15179
- const deadlineMs = Date.now() + timeoutMs;
15404
+ const deadlineMs = performance.now() + timeoutMs;
15180
15405
  const processStep = await cleanupProcess(options.record, options.child, deadlineMs);
15181
15406
  const debugPortStep = await cleanupDebugPort(options.record);
15182
15407
  const userDataDirSkipReason = shouldSkipUserDataDirCleanup(processStep, debugPortStep);
@@ -15298,17 +15523,25 @@ function directoryOwnerTrusted(entry, platform) {
15298
15523
  return uid !== void 0 && entry.uid === uid && (entry.mode & 63) === 0;
15299
15524
  }
15300
15525
  async function ensureCoordinationDirectory(path, platform) {
15526
+ let stage = "prepare.directory-mkdir";
15301
15527
  try {
15302
15528
  try {
15303
15529
  await mkdir3(path, { mode: 448 });
15304
15530
  } catch (error) {
15305
15531
  if (getErrorCode(error) !== "EEXIST") {
15532
+ debugPolicyFailure(stage, "native-error", error);
15306
15533
  return false;
15307
15534
  }
15308
15535
  }
15536
+ stage = "prepare.directory-stat";
15309
15537
  const entry = await lstat3(path);
15310
- return !entry.isSymbolicLink() && entry.isDirectory() && directoryOwnerTrusted(entry, platform);
15311
- } catch {
15538
+ const trusted = !entry.isSymbolicLink() && entry.isDirectory() && directoryOwnerTrusted(entry, platform);
15539
+ if (!trusted) {
15540
+ debugPolicyFailure(stage, "unsafe-metadata");
15541
+ }
15542
+ return trusted;
15543
+ } catch (error) {
15544
+ debugPolicyFailure(stage, "native-error", error);
15312
15545
  return false;
15313
15546
  }
15314
15547
  }
@@ -15344,13 +15577,143 @@ var init_browser_execution_identity = __esm({
15344
15577
  init_process_socket_storage();
15345
15578
  init_process_identity();
15346
15579
  init_process_errors();
15580
+ init_browser_execution_policy_debug();
15581
+ }
15582
+ });
15583
+
15584
+ // dist/extensions/agent-browser/lib/browser-execution-claim-endpoint.js
15585
+ import { unlink } from "node:fs/promises";
15586
+ import { createConnection, createServer } from "node:net";
15587
+ import { dirname as dirname11, join as join19 } from "node:path";
15588
+ function claimEndpointPath(claimPath, token) {
15589
+ return join19(dirname11(claimPath), `.l-${token}`);
15590
+ }
15591
+ function openClaimEndpoint(claimPath, token) {
15592
+ if (!endpointsSupported) {
15593
+ return void 0;
15594
+ }
15595
+ const server = createServer();
15596
+ const subscribers = /* @__PURE__ */ new Set();
15597
+ let closed = false;
15598
+ const close = () => {
15599
+ if (closed) {
15600
+ return;
15601
+ }
15602
+ closed = true;
15603
+ server.close();
15604
+ for (const subscriber of subscribers) {
15605
+ subscriber.destroy();
15606
+ }
15607
+ subscribers.clear();
15608
+ };
15609
+ server.on("connection", (subscriber) => {
15610
+ subscriber.unref();
15611
+ subscribers.add(subscriber);
15612
+ subscriber.on("error", () => {
15613
+ });
15614
+ subscriber.on("close", () => {
15615
+ subscribers.delete(subscriber);
15616
+ });
15617
+ subscriber.resume();
15618
+ });
15619
+ server.on("error", (error) => {
15620
+ debugPolicyFailure("endpoint.listen", "native-error", error);
15621
+ close();
15622
+ });
15623
+ server.unref();
15624
+ server.listen(claimEndpointPath(claimPath, token));
15625
+ return { close };
15626
+ }
15627
+ async function removeClaimEndpoint(claimPath, token) {
15628
+ if (!endpointsSupported) {
15629
+ return;
15630
+ }
15631
+ try {
15632
+ await unlink(claimEndpointPath(claimPath, token));
15633
+ } catch (error) {
15634
+ if (getErrorCode(error) !== "ENOENT") {
15635
+ debugPolicyFailure("endpoint.unlink", "native-error", error);
15636
+ }
15637
+ }
15638
+ }
15639
+ function sleep2(signal, deadline, setWake) {
15640
+ return new Promise((resolve19) => {
15641
+ const timer = setTimeout(done, Math.max(0, Math.min(deadline - performance.now(), MAX_TIMER_MS)));
15642
+ function done() {
15643
+ clearTimeout(timer);
15644
+ signal?.removeEventListener("abort", done);
15645
+ setWake(void 0);
15646
+ resolve19();
15647
+ }
15648
+ setWake(done);
15649
+ signal?.addEventListener("abort", done, { once: true });
15650
+ if (signal?.aborted === true) {
15651
+ done();
15652
+ }
15653
+ });
15654
+ }
15655
+ async function subscribeClaimEndpoint(claimPath, token, signal, deadline) {
15656
+ if (!endpointsSupported) {
15657
+ return void 0;
15658
+ }
15659
+ let connected = false;
15660
+ let disconnected = false;
15661
+ let wake;
15662
+ const open6 = () => connected && !disconnected;
15663
+ const setWake = (next) => {
15664
+ wake = next;
15665
+ };
15666
+ const socket = createConnection(claimEndpointPath(claimPath, token));
15667
+ socket.on("error", (error) => {
15668
+ if (!connected) {
15669
+ debugPolicyFailure("endpoint.connect", "unavailable", error);
15670
+ }
15671
+ disconnected = true;
15672
+ wake?.();
15673
+ });
15674
+ socket.on("close", () => {
15675
+ disconnected = true;
15676
+ wake?.();
15677
+ });
15678
+ socket.once("connect", () => {
15679
+ connected = true;
15680
+ wake?.();
15681
+ });
15682
+ await sleep2(signal, deadline, setWake);
15683
+ if (!open6()) {
15684
+ socket.destroy();
15685
+ return void 0;
15686
+ }
15687
+ socket.resume();
15688
+ return {
15689
+ token,
15690
+ connected: open6,
15691
+ wait: async (waitSignal, waitDeadline) => {
15692
+ if (!disconnected) {
15693
+ await sleep2(waitSignal, waitDeadline, setWake);
15694
+ }
15695
+ },
15696
+ close: () => {
15697
+ wake = void 0;
15698
+ socket.destroy();
15699
+ }
15700
+ };
15701
+ }
15702
+ var endpointsSupported, MAX_TIMER_MS;
15703
+ var init_browser_execution_claim_endpoint = __esm({
15704
+ "dist/extensions/agent-browser/lib/browser-execution-claim-endpoint.js"() {
15705
+ "use strict";
15706
+ init_browser_execution_policy_debug();
15707
+ init_process_errors();
15708
+ endpointsSupported = process.platform !== "win32";
15709
+ MAX_TIMER_MS = 2147483647;
15347
15710
  }
15348
15711
  });
15349
15712
 
15350
15713
  // dist/extensions/agent-browser/lib/browser-execution-claim-storage.js
15351
15714
  import { randomInt as randomInt2, randomUUID as randomUUID6 } from "node:crypto";
15352
- import { lstat as lstat4, readFile as readFile6, readdir as readdir6, rename, rm as rm8 } from "node:fs/promises";
15353
- import { basename as basename5, dirname as dirname11, join as join19 } from "node:path";
15715
+ import { lstat as lstat4, readFile as readFile7, readdir as readdir6, rename, rm as rm8 } from "node:fs/promises";
15716
+ import { basename as basename5, dirname as dirname12, join as join20 } from "node:path";
15354
15717
  function secureEntry(entry, directory) {
15355
15718
  if (entry.isSymbolicLink() || (directory ? !entry.isDirectory() : !entry.isFile())) {
15356
15719
  return false;
@@ -15367,60 +15730,99 @@ function secureEntryOwnership(entry, mask) {
15367
15730
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
15368
15731
  return uid !== void 0 && entry.uid === uid && (entry.mode & mask) === 0;
15369
15732
  }
15370
- async function readClaimTicket(path, owner) {
15371
- const ticketPath = join19(path, LOCK_TICKET_FILE);
15733
+ async function readClaimTicket(path, owner, report) {
15734
+ const ticketPath = join20(path, LOCK_TICKET_FILE);
15735
+ let stage = "claim.ticket-stat";
15372
15736
  try {
15373
15737
  if (!secureEntry(await lstat4(ticketPath), false)) {
15738
+ report?.(stage, "unsafe-metadata");
15374
15739
  return void 0;
15375
15740
  }
15376
- const ticket = parseTicket(await readFile6(ticketPath, "utf8"), owner.token);
15741
+ stage = "claim.ticket-read";
15742
+ const ticket = parseTicket(await readFile7(ticketPath, "utf8"), owner.token);
15743
+ if (ticket === void 0) {
15744
+ report?.(stage, "invalid-ticket-or-token");
15745
+ }
15377
15746
  return ticket === void 0 ? void 0 : { owner, path, ticket };
15378
15747
  } catch (error) {
15379
- return getErrorCode(error) === "ENOENT" ? { owner, path, ticket: null } : void 0;
15748
+ const code = getErrorCode(error);
15749
+ if (code !== "ENOENT") {
15750
+ report?.(stage, "native-error", error);
15751
+ }
15752
+ return code === "ENOENT" ? { owner, path, ticket: null } : void 0;
15380
15753
  }
15381
15754
  }
15382
- async function readClaim(path) {
15755
+ async function inspectClaim(path, report) {
15756
+ let stage = "claim.directory-stat";
15383
15757
  try {
15384
15758
  const directory = await lstat4(path);
15385
- const ownerPath = join19(path, LOCK_OWNER_FILE);
15759
+ const ownerPath = join20(path, LOCK_OWNER_FILE);
15760
+ stage = "claim.owner-stat";
15386
15761
  const ownerEntry = await lstat4(ownerPath);
15387
15762
  if (!secureEntry(directory, true) || !secureEntry(ownerEntry, false)) {
15763
+ report?.("claim.metadata", "unsafe-directory-or-owner");
15388
15764
  return void 0;
15389
15765
  }
15390
- const owner = parseOwner(await readFile6(ownerPath, "utf8"));
15391
- return owner ? await readClaimTicket(path, owner) : void 0;
15392
- } catch {
15766
+ stage = "claim.owner-read";
15767
+ const owner = parseOwner(await readFile7(ownerPath, "utf8"));
15768
+ if (!owner) {
15769
+ report?.(stage, "invalid-owner");
15770
+ }
15771
+ return owner ? await readClaimTicket(path, owner, report) : void 0;
15772
+ } catch (error) {
15773
+ report?.(stage, "native-error", error);
15393
15774
  return void 0;
15394
15775
  }
15395
15776
  }
15777
+ function reportDirectClaimFailure(stage, reason, error) {
15778
+ if (getErrorCode(error) !== "ENOENT") {
15779
+ debugPolicyFailure(stage, reason, error);
15780
+ }
15781
+ }
15782
+ function readClaim(path) {
15783
+ return inspectClaim(path, policyDebugEnabled ? reportDirectClaimFailure : void 0);
15784
+ }
15396
15785
  async function claimDisappeared(path) {
15397
15786
  try {
15398
15787
  await lstat4(path);
15399
15788
  return false;
15400
15789
  } catch (error) {
15401
- return getErrorCode(error) === "ENOENT";
15790
+ const code = getErrorCode(error);
15791
+ if (code !== "ENOENT") {
15792
+ debugPolicyFailure("claims.absence-stat", "absence-not-confirmed", error);
15793
+ }
15794
+ return code === "ENOENT";
15402
15795
  }
15403
15796
  }
15404
15797
  async function readClaims(basePath) {
15405
- const directory = dirname11(basePath);
15798
+ const directory = dirname12(basePath);
15406
15799
  const prefix = `${basename5(basePath)}.claim-`;
15407
15800
  let names;
15408
15801
  try {
15409
15802
  names = await readdir6(directory);
15410
- } catch {
15803
+ } catch (error) {
15804
+ debugPolicyFailure("claims.readdir", "native-error", error);
15411
15805
  return void 0;
15412
15806
  }
15413
15807
  const claims = [];
15414
15808
  for (const name of names.filter((candidate) => candidate.startsWith(prefix))) {
15415
- const path = join19(directory, name);
15416
- const claim = await readClaim(path);
15809
+ const path = join20(directory, name);
15810
+ let failure;
15811
+ const claim = await inspectClaim(path, policyDebugEnabled ? (stage, reason, error) => {
15812
+ failure = { stage, reason, error: policyDebugError(error) };
15813
+ } : void 0);
15417
15814
  if (!claim) {
15418
15815
  if (await claimDisappeared(path)) {
15419
15816
  continue;
15420
15817
  }
15818
+ if (failure !== void 0) {
15819
+ debugPolicyFailure(failure.stage, failure.reason, failure.error);
15820
+ }
15821
+ debugPolicyFailure("claims.inspect", "absence-not-confirmed");
15421
15822
  return void 0;
15422
15823
  }
15423
15824
  if (name !== `${prefix}${claim.owner.token}`) {
15825
+ debugPolicyFailure("claims.inspect", "published-owner-token-mismatch");
15424
15826
  return void 0;
15425
15827
  }
15426
15828
  claims.push(claim);
@@ -15436,6 +15838,7 @@ async function ownerAlive(owner, budget) {
15436
15838
  return false;
15437
15839
  }
15438
15840
  if (code !== "EPERM") {
15841
+ debugPolicyFailure("owner.liveness", "native-error", error);
15439
15842
  return void 0;
15440
15843
  }
15441
15844
  }
@@ -15460,6 +15863,7 @@ function waitForRetry(signal) {
15460
15863
  async function moveOwnedClaim(path, movedPath, token, deadline) {
15461
15864
  while (true) {
15462
15865
  if ((await readClaim(path))?.owner.token !== token) {
15866
+ debugPolicyFailure("remove.verify", "owner-missing-or-token-mismatch");
15463
15867
  return void 0;
15464
15868
  }
15465
15869
  try {
@@ -15470,18 +15874,20 @@ async function moveOwnedClaim(path, movedPath, token, deadline) {
15470
15874
  if (code === "ENOENT") {
15471
15875
  return "absent";
15472
15876
  }
15473
- if (process.platform !== "win32" || code !== "EPERM" || Date.now() >= deadline) {
15877
+ if (process.platform !== "win32" || code !== "EPERM" || performance.now() >= deadline) {
15878
+ debugPolicyFailure("remove.rename", "native-error", error);
15474
15879
  return void 0;
15475
15880
  }
15476
15881
  await waitForRetry();
15477
- if (Date.now() >= deadline) {
15882
+ if (performance.now() >= deadline) {
15883
+ debugPolicyFailure("remove.rename", "retry-deadline", error);
15478
15884
  return void 0;
15479
15885
  }
15480
15886
  }
15481
15887
  }
15482
15888
  }
15483
15889
  async function removeClaimOwnedBy(path, token, deadline = 0) {
15484
- const movedPath = join19(dirname11(path), `.pi-agent-browser-policy-remove-${token}-${randomUUID6()}`);
15890
+ const movedPath = join20(dirname12(path), `.pi-agent-browser-policy-remove-${token}-${randomUUID6()}`);
15485
15891
  const movement = await moveOwnedClaim(path, movedPath, token, deadline);
15486
15892
  if (movement === "absent") {
15487
15893
  return true;
@@ -15491,13 +15897,21 @@ async function removeClaimOwnedBy(path, token, deadline = 0) {
15491
15897
  }
15492
15898
  const moved = await readClaim(movedPath);
15493
15899
  if (moved?.owner.token !== token) {
15900
+ debugPolicyFailure("remove.moved-verify", "owner-missing-or-token-mismatch");
15494
15901
  try {
15495
15902
  await rename(movedPath, path);
15496
- } catch {
15903
+ } catch (error) {
15904
+ debugPolicyFailure("remove.restore-rename", "native-error", error);
15497
15905
  }
15498
15906
  return false;
15499
15907
  }
15500
- await rm8(movedPath, { force: true, recursive: true });
15908
+ try {
15909
+ await rm8(movedPath, { force: true, recursive: true });
15910
+ } catch (error) {
15911
+ debugPolicyFailure("remove.rm", "native-error", error);
15912
+ throw error;
15913
+ }
15914
+ await removeClaimEndpoint(path, token);
15501
15915
  return true;
15502
15916
  }
15503
15917
  function gcBatch(names) {
@@ -15512,7 +15926,7 @@ function gcBatch(names) {
15512
15926
  return batch;
15513
15927
  }
15514
15928
  async function cleanDeadClaim(directory, name, budget) {
15515
- const path = join19(directory, name);
15929
+ const path = join20(directory, name);
15516
15930
  const published = name.includes(".lock-v4.claim-");
15517
15931
  if (published) {
15518
15932
  const entry = await lstat4(path).catch(() => void 0);
@@ -15536,7 +15950,7 @@ async function cleanDeadPolicyArtifacts(directory, budget) {
15536
15950
  return;
15537
15951
  }
15538
15952
  for (const name of gcBatch(names)) {
15539
- if (budget.signal?.aborted === true || Date.now() >= (budget.deadline ?? Infinity)) {
15953
+ if (budget.signal?.aborted === true || performance.now() >= (budget.deadline ?? Infinity)) {
15540
15954
  return;
15541
15955
  }
15542
15956
  await cleanDeadClaim(directory, name, budget);
@@ -15548,6 +15962,8 @@ var init_browser_execution_claim_storage = __esm({
15548
15962
  "use strict";
15549
15963
  init_process_identity();
15550
15964
  init_process_errors();
15965
+ init_browser_execution_claim_endpoint();
15966
+ init_browser_execution_policy_debug();
15551
15967
  init_browser_execution_claim_codec();
15552
15968
  POLICY_LOCK_WAIT_MS = 1e3;
15553
15969
  LOCK_OWNER_FILE = "owner.json";
@@ -15560,7 +15976,7 @@ var init_browser_execution_claim_storage = __esm({
15560
15976
  // dist/extensions/agent-browser/lib/browser-execution-claims.js
15561
15977
  import { randomUUID as randomUUID7 } from "node:crypto";
15562
15978
  import { lstat as lstat5, mkdir as mkdir4, rename as rename2, rm as rm9, writeFile as writeFile2 } from "node:fs/promises";
15563
- import { join as join20 } from "node:path";
15979
+ import { join as join21 } from "node:path";
15564
15980
  function claimPrecedes(left, right) {
15565
15981
  if (left.ticket === null) {
15566
15982
  return true;
@@ -15585,45 +16001,55 @@ function relevantPredecessor(claim, ownClaim) {
15585
16001
  const sessions = claim.owner.sessionNames;
15586
16002
  return ownSessions === null || sessions === null || ownSessions.some((session) => sessions.includes(session));
15587
16003
  }
15588
- async function claimBlocks(claim, ownClaim, budget) {
16004
+ async function blockingPredecessor(claim, ownClaim, budget) {
15589
16005
  if (!relevantPredecessor(claim, ownClaim) || await hasPublishedLaterTicket(claim, ownClaim)) {
15590
- return false;
16006
+ return void 0;
16007
+ }
16008
+ if (!await claimStillExists(claim.path)) {
16009
+ return void 0;
15591
16010
  }
15592
16011
  const alive = await ownerAlive(claim.owner, budget);
15593
16012
  if (alive === false) {
15594
16013
  await removeClaimOwnedBy(claim.path, claim.owner.token);
15595
- return false;
16014
+ return void 0;
15596
16015
  }
15597
16016
  if (!await claimStillExists(claim.path)) {
15598
- return false;
16017
+ return void 0;
15599
16018
  }
15600
- return !await hasPublishedLaterTicket(claim, ownClaim);
16019
+ return await hasPublishedLaterTicket(claim, ownClaim) ? void 0 : { claim, ownerConfirmed: alive === true };
15601
16020
  }
15602
16021
  async function claimStillExists(path) {
15603
16022
  try {
15604
16023
  await lstat5(path);
15605
16024
  return true;
15606
16025
  } catch (error) {
15607
- return getErrorCode(error) !== "ENOENT";
16026
+ const code = getErrorCode(error);
16027
+ if (code !== "ENOENT") {
16028
+ debugPolicyFailure("acquire.predecessor-stat", "presence-unresolved", error);
16029
+ }
16030
+ return code !== "ENOENT";
15608
16031
  }
15609
16032
  }
15610
- async function anyPredecessorBlocks(claims, own, budget) {
16033
+ async function firstBlockingPredecessor(claims, own, budget) {
15611
16034
  for (const claim of claims) {
15612
- if (await claimBlocks(claim, own, budget)) {
15613
- return true;
16035
+ const blocking = await blockingPredecessor(claim, own, budget);
16036
+ if (blocking) {
16037
+ return blocking;
15614
16038
  }
15615
16039
  }
15616
- return false;
16040
+ return void 0;
15617
16041
  }
15618
16042
  async function prepareClaim(identity, budget) {
15619
16043
  const directory = getCoordinationDirectory(process.platform);
15620
16044
  if (!await ensureCoordinationDirectory(directory, process.platform)) {
16045
+ debugPolicyFailure("acquire.prepare", "directory-unavailable");
15621
16046
  return void 0;
15622
16047
  }
15623
16048
  const basePath = getBrowserExecutionLockPath(identity);
15624
16049
  const token = randomUUID7();
15625
16050
  const startIdentity = await readProcessStartIdentity(process.pid, process.platform, budget);
15626
16051
  if (startIdentity === void 0 || startIdentity.length === 0) {
16052
+ debugPolicyFailure("acquire.prepare", "start-identity-unavailable");
15627
16053
  return void 0;
15628
16054
  }
15629
16055
  const owner = {
@@ -15635,6 +16061,7 @@ async function prepareClaim(identity, budget) {
15635
16061
  };
15636
16062
  const content = JSON.stringify(owner);
15637
16063
  if (Buffer.byteLength(content) > POLICY_LOCK_MAX_BYTES) {
16064
+ debugPolicyFailure("acquire.prepare", "owner-too-large");
15638
16065
  return void 0;
15639
16066
  }
15640
16067
  return {
@@ -15646,59 +16073,164 @@ async function prepareClaim(identity, budget) {
15646
16073
  path: `${basePath}.claim-${token}`
15647
16074
  };
15648
16075
  }
15649
- async function publishClaim(claim) {
15650
- await mkdir4(claim.candidatePath, { mode: 448 });
15651
- await writeFile2(join20(claim.candidatePath, LOCK_OWNER_FILE), claim.content, {
15652
- encoding: "utf8",
15653
- flag: "wx",
15654
- mode: 384
15655
- });
15656
- await rename2(claim.candidatePath, claim.path);
16076
+ function publicationRetryAvailable(budget) {
16077
+ return budget.deadline !== void 0 && budget.signal?.aborted !== true && performance.now() < budget.deadline;
16078
+ }
16079
+ function candidateMatchesPreparedClaim(candidate, claim) {
16080
+ if (!candidate || candidate.ticket !== null) {
16081
+ return false;
16082
+ }
16083
+ const { owner } = candidate;
16084
+ const expected = claim.owner;
16085
+ const sessions = owner.sessionNames;
16086
+ const expectedSessions = expected.sessionNames;
16087
+ const identityFields = ["token", "pid", "startIdentity", "version"];
16088
+ return identityFields.every((field) => owner[field] === expected[field]) && (expectedSessions === null ? sessions === null : sessions !== null && sessions.length === expectedSessions.length && sessions.every((session, index) => session === expectedSessions[index]));
16089
+ }
16090
+ async function publishCandidate(claim, budget) {
16091
+ while (true) {
16092
+ try {
16093
+ await rename2(claim.candidatePath, claim.path);
16094
+ return true;
16095
+ } catch (error) {
16096
+ if (process.platform !== "win32" || getErrorCode(error) !== "EPERM" || !publicationRetryAvailable(budget)) {
16097
+ throw error;
16098
+ }
16099
+ await waitForRetry(budget.signal);
16100
+ if (!publicationRetryAvailable(budget)) {
16101
+ throw error;
16102
+ }
16103
+ const candidate = await readClaim(claim.candidatePath);
16104
+ if (!candidateMatchesPreparedClaim(candidate, claim)) {
16105
+ debugPolicyFailure("publish.verify", "candidate-unconfirmed", error);
16106
+ return false;
16107
+ }
16108
+ if (!publicationRetryAvailable(budget)) {
16109
+ throw error;
16110
+ }
16111
+ }
16112
+ }
16113
+ }
16114
+ async function publishClaim(claim, budget) {
16115
+ let stage = "publish.mkdir";
16116
+ try {
16117
+ await mkdir4(claim.candidatePath, { mode: 448 });
16118
+ stage = "publish.owner-write";
16119
+ await writeFile2(join21(claim.candidatePath, LOCK_OWNER_FILE), claim.content, {
16120
+ encoding: "utf8",
16121
+ flag: "wx",
16122
+ mode: 384
16123
+ });
16124
+ stage = "publish.rename";
16125
+ return await publishCandidate(claim, budget);
16126
+ } catch (error) {
16127
+ debugPolicyFailure(stage, "native-error", error);
16128
+ throw error;
16129
+ }
15657
16130
  }
15658
16131
  async function publishTicket(claim) {
15659
16132
  const claims = await readClaims(claim.basePath);
15660
16133
  if (!claims) {
16134
+ debugPolicyFailure("ticket.scan", "claims-unavailable");
15661
16135
  return void 0;
15662
16136
  }
15663
16137
  const maxTicket = claims.reduce((max, entry) => entry.ticket === null ? max : Math.max(max, entry.ticket), 0);
15664
16138
  if (!Number.isSafeInteger(maxTicket + 1)) {
16139
+ debugPolicyFailure("ticket.number", "unsafe-ticket-number");
15665
16140
  return void 0;
15666
16141
  }
15667
16142
  const ticket = { ticket: maxTicket + 1, token: claim.owner.token, version: 4 };
15668
- const candidate = join20(claim.path, `.ticket-${claim.owner.token}.tmp`);
15669
- await writeFile2(candidate, JSON.stringify(ticket), { encoding: "utf8", flag: "wx", mode: 384 });
15670
- await rename2(candidate, join20(claim.path, LOCK_TICKET_FILE));
16143
+ const candidate = join21(claim.path, `.ticket-${claim.owner.token}.tmp`);
16144
+ let stage = "ticket.write";
16145
+ try {
16146
+ await writeFile2(candidate, JSON.stringify(ticket), {
16147
+ encoding: "utf8",
16148
+ flag: "wx",
16149
+ mode: 384
16150
+ });
16151
+ stage = "ticket.rename";
16152
+ await rename2(candidate, join21(claim.path, LOCK_TICKET_FILE));
16153
+ } catch (error) {
16154
+ debugPolicyFailure(stage, "native-error", error);
16155
+ throw error;
16156
+ }
15671
16157
  return ticket.ticket;
15672
16158
  }
15673
16159
  function acquisitionCancelled(budget) {
15674
16160
  return budget.signal?.aborted === true;
15675
16161
  }
15676
- async function waitForTurn(claim, ticket, budget, deadline) {
15677
- while (budget.signal?.aborted !== true) {
15678
- const claims = await readClaims(claim.basePath);
15679
- if (!claims) {
15680
- return false;
15681
- }
15682
- const own = claims.find((entry) => entry.owner.token === claim.owner.token);
15683
- if (!own || own.ticket !== ticket) {
15684
- return false;
15685
- }
15686
- if (!await anyPredecessorBlocks(claims, own, budget)) {
15687
- await cleanDeadPolicyArtifacts(claim.directory, budget);
15688
- return !acquisitionCancelled(budget);
16162
+ function createPredecessorWait() {
16163
+ let subscription;
16164
+ const attempted = /* @__PURE__ */ new Set();
16165
+ const close = () => {
16166
+ subscription?.close();
16167
+ subscription = void 0;
16168
+ };
16169
+ return {
16170
+ close,
16171
+ wait: async (blocking, budget, deadline) => {
16172
+ const { token } = blocking.claim.owner;
16173
+ if (subscription?.token === token && subscription.connected()) {
16174
+ await subscription.wait(budget.signal, deadline);
16175
+ return;
16176
+ }
16177
+ close();
16178
+ if (blocking.ownerConfirmed && blocking.claim.ticket !== null && !attempted.has(token)) {
16179
+ attempted.add(token);
16180
+ subscription = await subscribeClaimEndpoint(blocking.claim.path, token, budget.signal, deadline);
16181
+ return;
16182
+ }
16183
+ await waitForRetry(budget.signal);
15689
16184
  }
15690
- if (Date.now() >= deadline) {
15691
- return false;
16185
+ };
16186
+ }
16187
+ async function waitForTurn(claim, ticket, budget, deadline) {
16188
+ const predecessorWait = createPredecessorWait();
16189
+ try {
16190
+ while (budget.signal?.aborted !== true) {
16191
+ const claims = await readClaims(claim.basePath);
16192
+ if (!claims) {
16193
+ debugPolicyFailure("acquire.wait-scan", "claims-unavailable");
16194
+ return false;
16195
+ }
16196
+ const own = claims.find((entry) => entry.owner.token === claim.owner.token);
16197
+ if (!own || own.ticket !== ticket) {
16198
+ debugPolicyFailure("acquire.own-claim", !own ? "missing" : "ticket-mismatch");
16199
+ return false;
16200
+ }
16201
+ const blocking = await firstBlockingPredecessor(claims, own, budget);
16202
+ if (!blocking) {
16203
+ return await acquireTurn(claim, budget);
16204
+ }
16205
+ if (performance.now() >= deadline) {
16206
+ debugPolicyFailure("acquire.wait", "deadline");
16207
+ return false;
16208
+ }
16209
+ await predecessorWait.wait(blocking, budget, deadline);
15692
16210
  }
15693
- await waitForRetry(budget.signal);
16211
+ debugPolicyFailure("acquire.wait", "cancelled");
16212
+ return false;
16213
+ } finally {
16214
+ predecessorWait.close();
15694
16215
  }
15695
- return false;
16216
+ }
16217
+ async function acquireTurn(claim, budget) {
16218
+ await cleanDeadPolicyArtifacts(claim.directory, budget);
16219
+ const cancelled = acquisitionCancelled(budget);
16220
+ if (cancelled) {
16221
+ debugPolicyFailure("acquire.turn", "cancelled");
16222
+ }
16223
+ return !cancelled;
15696
16224
  }
15697
16225
  function acquisitionDeadline(options) {
15698
- return options.deadline ?? Date.now() + (options.timeoutMs ?? POLICY_LOCK_WAIT_MS);
16226
+ return options.deadline ?? performance.now() + (options.timeoutMs ?? POLICY_LOCK_WAIT_MS);
15699
16227
  }
15700
- async function acquireExecutionClaim(options) {
15701
- if (options.signal?.aborted === true) {
16228
+ function acquireExecutionClaim(options) {
16229
+ return policyDebugEnabled ? withPolicyDebugAttempt(() => acquireClaim(options)) : acquireClaim(options);
16230
+ }
16231
+ async function acquireClaim(options) {
16232
+ if (acquisitionCancelled(options)) {
16233
+ debugPolicyFailure("acquire.begin", "cancelled");
15702
16234
  return void 0;
15703
16235
  }
15704
16236
  const deadline = acquisitionDeadline(options);
@@ -15712,41 +16244,68 @@ async function acquireExecutionClaim(options) {
15712
16244
  }
15713
16245
  let published = false;
15714
16246
  let acquired = false;
16247
+ let cleanupCandidate = true;
16248
+ let endpoint;
15715
16249
  try {
15716
- await publishClaim(claim);
16250
+ if (!await publishClaim(claim, budget)) {
16251
+ cleanupCandidate = false;
16252
+ return void 0;
16253
+ }
15717
16254
  published = true;
16255
+ endpoint = openClaimEndpoint(claim.path, claim.owner.token);
15718
16256
  const ticket = await publishTicket(claim);
15719
16257
  if (ticket === void 0 || !await waitForTurn(claim, ticket, budget, deadline)) {
15720
16258
  return void 0;
15721
16259
  }
15722
16260
  acquired = true;
15723
16261
  return {
15724
- release: async () => {
15725
- await removeClaimOwnedBy(claim.path, claim.owner.token, Date.now() + POLICY_LOCK_WAIT_MS);
15726
- }
16262
+ release: policyDebugPhase("release", async () => removeOwnClaim(claim, endpoint, "release.complete"))
15727
16263
  };
15728
- } catch {
16264
+ } catch (error) {
16265
+ debugPolicyFailure("acquire.complete", "native-error", error);
15729
16266
  return void 0;
15730
16267
  } finally {
15731
- await rm9(claim.candidatePath, { force: true, recursive: true }).catch(() => void 0);
16268
+ await cleanupUnpublishedCandidate(claim, cleanupCandidate);
15732
16269
  if (published && !acquired) {
15733
- await removeClaimOwnedBy(claim.path, claim.owner.token, Date.now() + POLICY_LOCK_WAIT_MS);
16270
+ await removeOwnClaim(claim, endpoint, "cleanup.complete");
15734
16271
  }
15735
16272
  }
15736
16273
  }
16274
+ async function removeOwnClaim(claim, endpoint, stage) {
16275
+ try {
16276
+ const removed = await removeClaimOwnedBy(claim.path, claim.owner.token, performance.now() + POLICY_LOCK_WAIT_MS);
16277
+ debugRemovalResult(removed, stage);
16278
+ } finally {
16279
+ endpoint?.close();
16280
+ }
16281
+ }
16282
+ async function cleanupUnpublishedCandidate(claim, permitted) {
16283
+ if (permitted) {
16284
+ await rm9(claim.candidatePath, { force: true, recursive: true }).catch((error) => {
16285
+ debugPolicyFailure("cleanup.candidate-rm", "native-error", error);
16286
+ });
16287
+ }
16288
+ }
16289
+ function debugRemovalResult(removed, stage) {
16290
+ if (!removed) {
16291
+ debugPolicyFailure(stage, "claim-not-removed");
16292
+ }
16293
+ }
15737
16294
  var init_browser_execution_claims = __esm({
15738
16295
  "dist/extensions/agent-browser/lib/browser-execution-claims.js"() {
15739
16296
  "use strict";
15740
16297
  init_process_errors();
16298
+ init_browser_execution_policy_debug();
15741
16299
  init_process_identity();
15742
16300
  init_browser_execution_claim_codec();
15743
16301
  init_browser_execution_identity();
16302
+ init_browser_execution_claim_endpoint();
15744
16303
  init_browser_execution_claim_storage();
15745
16304
  }
15746
16305
  });
15747
16306
 
15748
16307
  // dist/extensions/agent-browser/lib/managed-session-policy-lock.js
15749
- import { AsyncLocalStorage as AsyncLocalStorage7 } from "node:async_hooks";
16308
+ import { AsyncLocalStorage as AsyncLocalStorage8 } from "node:async_hooks";
15750
16309
  function groupExecutionIdentities(identities) {
15751
16310
  if (identities.length === 0) {
15752
16311
  throw new Error("Browser execution coordination requires at least one identity.");
@@ -15821,10 +16380,10 @@ async function withBrowserExecutionLocks(options, run) {
15821
16380
  const signals = [controller.signal, options.signal, parent?.signal].filter((signal2) => signal2 !== void 0);
15822
16381
  const signal = AbortSignal.any(signals);
15823
16382
  const expire = () => controller.abort(new DOMException("Browser execution deadline exceeded.", "TimeoutError"));
15824
- const remaining = options.deadline - Date.now();
16383
+ const remaining = options.deadline - performance.now();
15825
16384
  const timer = setTimeout(expire, Math.max(0, Math.min(remaining, 2147483647)));
15826
16385
  const checkBudget = () => {
15827
- if (Date.now() >= options.deadline) {
16386
+ if (performance.now() >= options.deadline) {
15828
16387
  expire();
15829
16388
  }
15830
16389
  signal.throwIfAborted();
@@ -15900,7 +16459,7 @@ var init_managed_session_policy_lock = __esm({
15900
16459
  init_browser_execution_claims();
15901
16460
  init_browser_execution_identity();
15902
16461
  init_browser_execution_identity();
15903
- executionScope = new AsyncLocalStorage7();
16462
+ executionScope = new AsyncLocalStorage8();
15904
16463
  }
15905
16464
  });
15906
16465
 
@@ -15913,13 +16472,31 @@ function getRunningHeadedAutosavePolicyChangeError(recordedInterval, closeComman
15913
16472
  function getHeadedManagedAutosaveEnv(interval) {
15914
16473
  return interval !== void 0 ? { AGENT_BROWSER_AUTOSAVE_INTERVAL_MS: interval } : void 0;
15915
16474
  }
16475
+ function isDaemonGeneration(value) {
16476
+ return isRecord(value) && typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0 && typeof value.start === "string" && value.start !== "" && typeof value.socketDir === "string";
16477
+ }
16478
+ function managedSessionDaemonGenerationsMatch(recorded, current) {
16479
+ if (recorded === void 0 || current === void 0) {
16480
+ return void 0;
16481
+ }
16482
+ try {
16483
+ const previous = JSON.parse(recorded);
16484
+ const observed = JSON.parse(current);
16485
+ if (!isDaemonGeneration(previous) || !isDaemonGeneration(observed)) {
16486
+ return void 0;
16487
+ }
16488
+ return previous.pid !== observed.pid || previous.socketDir !== observed.socketDir ? false : processStartIdentitiesMatch(previous.start, observed.start);
16489
+ } catch {
16490
+ return void 0;
16491
+ }
16492
+ }
15916
16493
  async function readDaemonGeneration(runtime, options) {
15917
16494
  if (options.includeGeneration !== true || typeof runtime.backgroundPid !== "number" || !Number.isSafeInteger(runtime.backgroundPid) || runtime.backgroundPid <= 0 || typeof runtime.socketDir !== "string" || runtime.browserLaunched === false) {
15918
16495
  return void 0;
15919
16496
  }
15920
16497
  const start = await readProcessStartIdentity(runtime.backgroundPid, process.platform, {
15921
16498
  signal: options.signal,
15922
- deadline: Date.now() + (options.timeoutMs ?? 5e3)
16499
+ deadline: performance.now() + (options.timeoutMs ?? 5e3)
15923
16500
  });
15924
16501
  return start !== void 0 && start !== "" ? JSON.stringify({
15925
16502
  pid: runtime.backgroundPid,
@@ -16089,17 +16666,17 @@ async function closeUnderPolicy(options, lifecycle) {
16089
16666
  async function closeWithDeadline(options) {
16090
16667
  const controller = new AbortController();
16091
16668
  let phase = "policy coordination";
16092
- let phaseStartedAt = Date.now();
16669
+ let phaseStartedAt = performance.now();
16093
16670
  let timeoutError2;
16094
16671
  const timer = setTimeout(() => {
16095
- timeoutError2 = `Managed-session cleanup timed out after ${options.timeoutMs} ms during ${phase} (${Date.now() - phaseStartedAt} ms in this phase).`;
16672
+ timeoutError2 = `Managed-session cleanup timed out after ${options.timeoutMs} ms during ${phase} (${Math.round(performance.now() - phaseStartedAt)} ms in this phase).`;
16096
16673
  controller.abort();
16097
16674
  }, options.timeoutMs);
16098
16675
  const lifecycle = {
16099
16676
  signal: controller.signal,
16100
16677
  enterPhase: (nextPhase) => {
16101
16678
  phase = nextPhase;
16102
- phaseStartedAt = Date.now();
16679
+ phaseStartedAt = performance.now();
16103
16680
  },
16104
16681
  clearTimer: () => {
16105
16682
  clearTimeout(timer);
@@ -16205,7 +16782,7 @@ function reconcileDaemonReceipt(context2, daemon, receipt) {
16205
16782
  if (daemon.status !== "active" || receipt === void 0 || context2.restoreState.hasDaemonRestoreKey(context2.sessionName, context2.namespace)) {
16206
16783
  return;
16207
16784
  }
16208
- if (daemon.generation === receipt.generation && daemon.restoreKey === receipt.restoreKey) {
16785
+ if (managedSessionDaemonGenerationsMatch(receipt.generation, daemon.generation) === true && daemon.restoreKey === receipt.restoreKey) {
16209
16786
  context2.restoreState.recordDaemonRestoreKey(context2.sessionName, context2.namespace, daemon.restoreKey, daemon.generation);
16210
16787
  } else {
16211
16788
  context2.restoreState.forgetDaemonRestoreKey(context2.sessionName, context2.namespace);
@@ -16403,7 +16980,7 @@ var init_branch = __esm({
16403
16980
  // dist/extensions/agent-browser/lib/electron/discovery-evidence.js
16404
16981
  import { constants as fsConstants } from "node:fs";
16405
16982
  import { access as access2, readdir as readdir7, realpath as realpath2, stat as stat9 } from "node:fs/promises";
16406
- import { basename as basename6, dirname as dirname12, extname as extname4, join as join21 } from "node:path";
16983
+ import { basename as basename6, dirname as dirname13, extname as extname4, join as join22 } from "node:path";
16407
16984
  async function isDirectory(path) {
16408
16985
  try {
16409
16986
  return (await stat9(path)).isDirectory();
@@ -16445,19 +17022,19 @@ async function directoryContainsChromePak(directory) {
16445
17022
  }
16446
17023
  }
16447
17024
  async function hasElectronAppPayload(resourcesDirectory) {
16448
- return await pathExists(join21(resourcesDirectory, "app.asar")) || await pathExists(join21(resourcesDirectory, "default_app.asar")) || await isDirectory(join21(resourcesDirectory, "app"));
17025
+ return await pathExists(join22(resourcesDirectory, "app.asar")) || await pathExists(join22(resourcesDirectory, "default_app.asar")) || await isDirectory(join22(resourcesDirectory, "app"));
16449
17026
  }
16450
17027
  async function hasLinuxElectronEvidence(executablePath) {
16451
17028
  const resolved = await resolveRealPath(executablePath);
16452
17029
  if (!await isExecutableFile(resolved)) {
16453
17030
  return false;
16454
17031
  }
16455
- const directory = dirname12(resolved);
17032
+ const directory = dirname13(resolved);
16456
17033
  if (!await directoryContainsChromePak(directory)) {
16457
17034
  return false;
16458
17035
  }
16459
- for (const base of [directory, dirname12(directory)]) {
16460
- if (await hasElectronAppPayload(join21(base, "resources"))) {
17036
+ for (const base of [directory, dirname13(directory)]) {
17037
+ if (await hasElectronAppPayload(join22(base, "resources"))) {
16461
17038
  return true;
16462
17039
  }
16463
17040
  }
@@ -16468,9 +17045,9 @@ async function inspectWin32Executable(executablePath) {
16468
17045
  if (!await isExecutableFile(resolved)) {
16469
17046
  return void 0;
16470
17047
  }
16471
- const directory = dirname12(resolved);
16472
- const hasAppPayload = await hasElectronAppPayload(join21(directory, "resources"));
16473
- const hasPak = await directoryContainsChromePak(directory) || await pathExists(join21(directory, "resources.pak"));
17048
+ const directory = dirname13(resolved);
17049
+ const hasAppPayload = await hasElectronAppPayload(join22(directory, "resources"));
17050
+ const hasPak = await directoryContainsChromePak(directory) || await pathExists(join22(directory, "resources.pak"));
16474
17051
  if (!hasAppPayload || !hasPak) {
16475
17052
  return void 0;
16476
17053
  }
@@ -16488,8 +17065,8 @@ var init_discovery_evidence = __esm({
16488
17065
  });
16489
17066
 
16490
17067
  // dist/extensions/agent-browser/lib/electron/discovery-darwin.js
16491
- import { readdir as readdir8, readFile as readFile7 } from "node:fs/promises";
16492
- import { basename as basename7, join as join22 } from "node:path";
17068
+ import { readdir as readdir8, readFile as readFile8 } from "node:fs/promises";
17069
+ import { basename as basename7, join as join23 } from "node:path";
16493
17070
  function readPlistString(plist, key) {
16494
17071
  const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16495
17072
  const match = new RegExp(`<key>\\s*${escapedKey}\\s*</key>\\s*<string>([\\s\\S]*?)</string>`, "i").exec(plist);
@@ -16497,7 +17074,7 @@ function readPlistString(plist, key) {
16497
17074
  }
16498
17075
  async function readMacInfoPlist(appPath) {
16499
17076
  try {
16500
- const plist = await readFile7(join22(appPath, "Contents", "Info.plist"), "utf8");
17077
+ const plist = await readFile8(join23(appPath, "Contents", "Info.plist"), "utf8");
16501
17078
  return {
16502
17079
  CFBundleDisplayName: readPlistString(plist, "CFBundleDisplayName") ?? "",
16503
17080
  CFBundleExecutable: readPlistString(plist, "CFBundleExecutable") ?? "",
@@ -16509,15 +17086,15 @@ async function readMacInfoPlist(appPath) {
16509
17086
  }
16510
17087
  }
16511
17088
  async function resolveMacExecutablePath(appPath, executableName, fallbackName) {
16512
- const directory = join22(appPath, "Contents", "MacOS");
17089
+ const directory = join23(appPath, "Contents", "MacOS");
16513
17090
  if (executableName !== void 0 && executableName.trim().length > 0) {
16514
- const path = join22(directory, executableName);
17091
+ const path = join23(directory, executableName);
16515
17092
  return await pathExists(path) ? path : void 0;
16516
17093
  }
16517
17094
  try {
16518
17095
  const entries = await readdir8(directory, { withFileTypes: true });
16519
17096
  const candidate = entries.find((entry) => entry.isFile() && entry.name === fallbackName) ?? entries.find((entry) => entry.isFile());
16520
- return candidate ? join22(directory, candidate.name) : void 0;
17097
+ return candidate ? join23(directory, candidate.name) : void 0;
16521
17098
  } catch {
16522
17099
  return void 0;
16523
17100
  }
@@ -16526,11 +17103,11 @@ function firstNonempty(values) {
16526
17103
  return values.find((value) => value !== void 0 && value.length > 0);
16527
17104
  }
16528
17105
  async function inspectDarwinApp(appPath) {
16529
- const framework = join22(appPath, "Contents", "Frameworks", "Electron Framework.framework");
17106
+ const framework = join23(appPath, "Contents", "Frameworks", "Electron Framework.framework");
16530
17107
  if (!await isDirectory(framework)) {
16531
17108
  return void 0;
16532
17109
  }
16533
- if (!await hasElectronAppPayload(join22(appPath, "Contents", "Resources"))) {
17110
+ if (!await hasElectronAppPayload(join23(appPath, "Contents", "Resources"))) {
16534
17111
  return void 0;
16535
17112
  }
16536
17113
  const info = await readMacInfoPlist(appPath);
@@ -16561,7 +17138,7 @@ async function inspectDirectory(directory) {
16561
17138
  continue;
16562
17139
  }
16563
17140
  try {
16564
- const app = await inspectDarwinApp(join22(directory, entry.name));
17141
+ const app = await inspectDarwinApp(join23(directory, entry.name));
16565
17142
  if (app) {
16566
17143
  apps.push(app);
16567
17144
  }
@@ -16726,7 +17303,7 @@ var init_discovery_desktop = __esm({
16726
17303
 
16727
17304
  // dist/extensions/agent-browser/lib/electron/discovery-packages.js
16728
17305
  import { readdir as readdir9 } from "node:fs/promises";
16729
- import { basename as basename9, extname as extname5, isAbsolute as isAbsolute6, join as join23, relative, resolve as resolve9 } from "node:path";
17306
+ import { basename as basename9, extname as extname5, isAbsolute as isAbsolute6, join as join24, relative, resolve as resolve9 } from "node:path";
16730
17307
  function pathIsWithin(path, parent) {
16731
17308
  const relativePath = relative(resolve9(parent), resolve9(path));
16732
17309
  return relativePath.length === 0 || !relativePath.startsWith("..") && !isAbsolute6(relativePath);
@@ -16739,7 +17316,7 @@ async function findExecutableInPath(command, pathEnv) {
16739
17316
  if (directory.length === 0) {
16740
17317
  continue;
16741
17318
  }
16742
- const candidate = join23(directory, command);
17319
+ const candidate = join24(directory, command);
16743
17320
  if (await isFile(candidate)) {
16744
17321
  return candidate;
16745
17322
  }
@@ -16750,7 +17327,7 @@ async function resolveSnapExecutable(commandPath, entry, locations) {
16750
17327
  const commandName = basename9(commandPath);
16751
17328
  const names = [commandName, commandName.split(".")[0] ?? "", entry.desktopId.split(".")[0] ?? ""];
16752
17329
  for (const snapName of new Set(names.filter((name) => name.length > 0))) {
16753
- const root = join23(locations.snapMountDirectory, snapName, "current");
17330
+ const root = join24(locations.snapMountDirectory, snapName, "current");
16754
17331
  const candidate = await new ElectronBinarySearch([
16755
17332
  commandName,
16756
17333
  commandName.split(".").at(-1) ?? commandName
@@ -16783,7 +17360,7 @@ function getFlatpakCommandName(tokens) {
16783
17360
  return void 0;
16784
17361
  }
16785
17362
  function getFlatpakRoots(entry, locations) {
16786
- const userExport = join23(locations.homeDir, ".local", "share", "flatpak", "exports", "share", "applications");
17363
+ const userExport = join24(locations.homeDir, ".local", "share", "flatpak", "exports", "share", "applications");
16787
17364
  if (pathIsWithin(entry.filePath, userExport)) {
16788
17365
  return [locations.flatpakUserAppDirectory, locations.flatpakSystemAppDirectory];
16789
17366
  }
@@ -16796,7 +17373,7 @@ async function resolveFlatpakExecutable(tokens, entry, locations) {
16796
17373
  const appId = getFlatpakAppId(tokens, entry.desktopId);
16797
17374
  const commandName = getFlatpakCommandName(tokens);
16798
17375
  for (const root of getFlatpakRoots(entry, locations)) {
16799
- const candidate = await new ElectronBinarySearch(commandName === void 0 ? [] : [commandName]).find(join23(root, appId, "current", "active", "files"));
17376
+ const candidate = await new ElectronBinarySearch(commandName === void 0 ? [] : [commandName]).find(join24(root, appId, "current", "active", "files"));
16800
17377
  if (candidate !== void 0) {
16801
17378
  return candidate;
16802
17379
  }
@@ -16866,7 +17443,7 @@ var init_discovery_packages = __esm({
16866
17443
  return void 0;
16867
17444
  }
16868
17445
  async inspectEntry(directory, entry, depth) {
16869
- const path = join23(directory, entry.name);
17446
+ const path = join24(directory, entry.name);
16870
17447
  if (entry.isDirectory()) {
16871
17448
  return this.visit(path, depth + 1);
16872
17449
  }
@@ -16884,8 +17461,8 @@ var init_discovery_packages = __esm({
16884
17461
  });
16885
17462
 
16886
17463
  // dist/extensions/agent-browser/lib/electron/discovery-linux.js
16887
- import { readdir as readdir10, readFile as readFile8 } from "node:fs/promises";
16888
- import { basename as basename10, extname as extname6, isAbsolute as isAbsolute7, join as join24 } from "node:path";
17464
+ import { readdir as readdir10, readFile as readFile9 } from "node:fs/promises";
17465
+ import { basename as basename10, extname as extname6, isAbsolute as isAbsolute7, join as join25 } from "node:path";
16889
17466
  async function resolveLinuxExecutable(entry, locations) {
16890
17467
  const tokens = stripEnvLauncher(tokenizeDesktopExec(entry.exec));
16891
17468
  const token = tokens.at(0);
@@ -16909,7 +17486,7 @@ async function resolveLinuxExecutable(entry, locations) {
16909
17486
  async function inspectLinuxDesktopFile(filePath, locations) {
16910
17487
  let text3;
16911
17488
  try {
16912
- text3 = await readFile8(filePath, "utf8");
17489
+ text3 = await readFile9(filePath, "utf8");
16913
17490
  } catch {
16914
17491
  return void 0;
16915
17492
  }
@@ -16945,7 +17522,7 @@ async function inspectDirectory2(directory, locations) {
16945
17522
  continue;
16946
17523
  }
16947
17524
  try {
16948
- const app = await inspectLinuxDesktopFile(join24(directory, entry.name), locations);
17525
+ const app = await inspectLinuxDesktopFile(join25(directory, entry.name), locations);
16949
17526
  if (app) {
16950
17527
  apps.push(app);
16951
17528
  }
@@ -16987,7 +17564,7 @@ __export(discovery_exports, {
16987
17564
  inspectElectronExecutablePath: () => inspectElectronExecutablePath
16988
17565
  });
16989
17566
  import { homedir as homedir3 } from "node:os";
16990
- import { basename as basename11, join as join25, resolve as resolve10 } from "node:path";
17567
+ import { basename as basename11, join as join26, resolve as resolve10, sep } from "node:path";
16991
17568
  function normalizeSensitivityValue(value) {
16992
17569
  const normalized = value?.trim().replace(/[_./\\-]+/g, " ").replace(/\s+/g, " ");
16993
17570
  return normalized === void 0 || normalized.length === 0 ? void 0 : normalized;
@@ -17021,17 +17598,17 @@ function normalizeMaxResults(value) {
17021
17598
  }
17022
17599
  function linuxDesktopDirectories(locations, homeDir) {
17023
17600
  return locations?.linuxDesktopDirectories ?? [
17024
- join25(homeDir, ".local", "share", "applications"),
17601
+ join26(homeDir, ".local", "share", "applications"),
17025
17602
  "/usr/share/applications",
17026
17603
  "/var/lib/snapd/desktop/applications",
17027
- join25(homeDir, ".local", "share", "flatpak", "exports", "share", "applications"),
17604
+ join26(homeDir, ".local", "share", "flatpak", "exports", "share", "applications"),
17028
17605
  "/var/lib/flatpak/exports/share/applications"
17029
17606
  ];
17030
17607
  }
17031
17608
  function resolvePackageLocations(locations, homeDir) {
17032
17609
  return {
17033
17610
  flatpakSystemAppDirectory: locations?.flatpakSystemAppDirectory ?? "/var/lib/flatpak/app",
17034
- flatpakUserAppDirectory: locations?.flatpakUserAppDirectory ?? join25(homeDir, ".local", "share", "flatpak", "app"),
17611
+ flatpakUserAppDirectory: locations?.flatpakUserAppDirectory ?? join26(homeDir, ".local", "share", "flatpak", "app"),
17035
17612
  snapBinDirectory: locations?.snapBinDirectory ?? "/snap/bin",
17036
17613
  snapMountDirectory: locations?.snapMountDirectory ?? "/snap"
17037
17614
  };
@@ -17043,7 +17620,7 @@ function resolveLocations(locations) {
17043
17620
  homeDir,
17044
17621
  darwinApplicationDirectories: locations?.darwinApplicationDirectories ?? [
17045
17622
  "/Applications",
17046
- join25(homeDir, "Applications")
17623
+ join26(homeDir, "Applications")
17047
17624
  ],
17048
17625
  linuxDesktopDirectories: linuxDesktopDirectories(locations, homeDir),
17049
17626
  pathEnv: locations?.pathEnv ?? process.env.PATH ?? ""
@@ -17083,12 +17660,12 @@ function sortApps(apps) {
17083
17660
  });
17084
17661
  }
17085
17662
  function findMacAppBundleAncestor(path) {
17086
- const parts = resolve10(path).split(/[\\/]+/);
17663
+ const parts = resolve10(path).split(sep);
17087
17664
  const index = parts.findIndex((part) => part.endsWith(".app"));
17088
17665
  if (index < 0) {
17089
17666
  return void 0;
17090
17667
  }
17091
- const ancestor = parts.slice(0, index + 1).join("/");
17668
+ const ancestor = parts.slice(0, index + 1).join(sep);
17092
17669
  return ancestor.length > 0 ? ancestor : "/";
17093
17670
  }
17094
17671
  async function inspectElectronExecutablePath(executablePath, platform = process.platform) {
@@ -19466,7 +20043,7 @@ var init_timeout_diagnostics = __esm({
19466
20043
  // dist/extensions/agent-browser/lib/executable-path.js
19467
20044
  import { constants as fsConstants2 } from "node:fs";
19468
20045
  import { access as access3, stat as stat11 } from "node:fs/promises";
19469
- import { delimiter, join as join26 } from "node:path";
20046
+ import { delimiter, join as join27 } from "node:path";
19470
20047
  async function isExecutableFile2(candidate) {
19471
20048
  try {
19472
20049
  await access3(candidate, fsConstants2.X_OK);
@@ -19477,7 +20054,7 @@ async function isExecutableFile2(candidate) {
19477
20054
  }
19478
20055
  async function executableExistsOnPath(command) {
19479
20056
  const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
19480
- const candidates = (process.env.PATH ?? "").split(delimiter).filter(Boolean).flatMap((directory) => extensions.map((extension) => join26(directory, `${command}${extension}`)));
20057
+ const candidates = (process.env.PATH ?? "").split(delimiter).filter(Boolean).flatMap((directory) => extensions.map((extension) => join27(directory, `${command}${extension}`)));
19481
20058
  for (const candidate of candidates) {
19482
20059
  if (await isExecutableFile2(candidate)) {
19483
20060
  return true;
@@ -20102,6 +20679,7 @@ function nativeObservationDetails(options, lifecycle, browserWindow) {
20102
20679
  recordingRecovery: options.presentation.recordingRecovery,
20103
20680
  readConfirmation: options.presentation.readConfirmation,
20104
20681
  exitCode: options.processResult.exitCode,
20682
+ exitSignal: options.processResult.exitSignal,
20105
20683
  parseError: options.plainTextInspection ? void 0 : options.parseError,
20106
20684
  stderr: options.processResult.stderr,
20107
20685
  stdout: options.plainTextInspection ? options.inspectionText ?? "" : void 0,
@@ -22745,11 +23323,12 @@ var init_batch_redaction = __esm({
22745
23323
  function createStepIdentity(item, index) {
22746
23324
  const command = isStringArray2(item.command) ? item.command : void 0;
22747
23325
  const redactedCommand = command ? redactInvocationArgs(command) : void 0;
23326
+ const visibleCommand = hasModelFacingArgRedaction(redactedCommand) ? redactedCommand : command;
22748
23327
  return {
22749
23328
  command,
22750
23329
  redactedCommand,
22751
- index,
22752
- commandText: formatBatchStepCommand(hasModelFacingArgRedaction(redactedCommand) ? redactedCommand : command, index),
23330
+ index: item.index ?? index,
23331
+ commandText: formatBatchStepCommand(item.index === void 0 ? visibleCommand : void 0, item.index ?? index),
22753
23332
  lifecycle: extractAgentBrowserLifecycle(item.result)
22754
23333
  };
22755
23334
  }
@@ -22930,12 +23509,16 @@ async function buildBatchStepPresentation(options) {
22930
23509
  const identity = createStepIdentity(options.item, options.index);
22931
23510
  const scope = { namespace: options.namespace, sessionName: options.sessionName };
22932
23511
  if (options.item.success === false && identity.command?.[0] !== "record") {
22933
- return buildFailedStep(options.item, identity, scope);
23512
+ return {
23513
+ ...buildFailedStep(options.item, identity, scope),
23514
+ nativeIndex: options.item.index
23515
+ };
22934
23516
  }
22935
23517
  const request = buildNestedRequest(options, identity.command);
22936
23518
  const presentation = await options.buildNestedToolPresentation(request);
22937
23519
  return {
22938
23520
  details: buildStepDetails(presentation, identity, request.commandInfo, scope),
23521
+ nativeIndex: options.item.index,
22939
23522
  presentation
22940
23523
  };
22941
23524
  }
@@ -23121,6 +23704,9 @@ function scanTypingRun(steps, start) {
23121
23704
  return typedStepCount >= 2 ? { nextIndex: index, typedCharCount, delayMs } : void 0;
23122
23705
  }
23123
23706
  function getTypingStart(steps, index) {
23707
+ if (steps.at(index)?.nativeIndex !== void 0) {
23708
+ return void 0;
23709
+ }
23124
23710
  const details = steps.at(index)?.details;
23125
23711
  if (!details || !details.success) {
23126
23712
  return void 0;
@@ -23243,13 +23829,20 @@ function formatBatchStatusText(steps, status, modelVisible) {
23243
23829
  ].filter((line) => line !== void 0).join("\n\n");
23244
23830
  }
23245
23831
  function serializeBatchData(steps) {
23246
- return steps.map(({ details }) => details.success ? { command: details.command, result: details.data, success: true } : {
23247
- command: details.command,
23832
+ return steps.map(({ details, nativeIndex }) => details.success ? {
23833
+ ...batchResultIdentity(details.command, nativeIndex),
23834
+ result: details.data,
23835
+ success: true
23836
+ } : {
23837
+ ...batchResultIdentity(details.command, nativeIndex),
23248
23838
  error: details.text,
23249
23839
  ...details.command?.[0] === "record" ? { result: details.data } : {},
23250
23840
  success: false
23251
23841
  });
23252
23842
  }
23843
+ function batchResultIdentity(command, index) {
23844
+ return index === void 0 ? { command } : { index };
23845
+ }
23253
23846
  function formatBatchChangeText(observed, unverified) {
23254
23847
  if (observed === 0) {
23255
23848
  return `batch \u2192 ${unverified} action${unverified === 1 ? "" : "s"} dispatched \u2192 application change unverified`;
@@ -23289,7 +23882,7 @@ var init_batch_summary = __esm({
23289
23882
 
23290
23883
  // dist/extensions/agent-browser/lib/results/presentation/batch.js
23291
23884
  function isAgentBrowserBatchResultArray(value) {
23292
- return Array.isArray(value) && value.every((row) => isRecord(row) && (row.command === void 0 || isStringArray2(row.command)) && (row.success === void 0 || typeof row.success === "boolean"));
23885
+ return Array.isArray(value) && value.every((row) => isRecord(row) && (row.command === void 0 || isStringArray2(row.command)) && (row.index === void 0 || typeof row.index === "number" && Number.isSafeInteger(row.index) && row.index >= 0) && (row.success === void 0 || typeof row.success === "boolean"));
23293
23886
  }
23294
23887
  async function collectPresentedSteps(options) {
23295
23888
  const steps = [];
@@ -23678,6 +24271,12 @@ function stateShowText(data) {
23678
24271
  }
23679
24272
  return lines.join("\n");
23680
24273
  }
24274
+ function stateLoadText(data) {
24275
+ const loaded = `State loaded: ${redactModelFacingText(getStringField(data, "path") ?? getStringField(data, "name") ?? "ok")}`;
24276
+ const skipped = (getArrayField(data, "skippedOrigins") ?? []).filter((origin) => typeof origin === "string");
24277
+ return skipped.length === 0 ? loaded : `${loaded}
24278
+ Skipped storage for origins (page did not stay on that origin): ${redactModelFacingText(skipped.join(", "))}`;
24279
+ }
23681
24280
  function formatStateText(data, subcommand) {
23682
24281
  if (subcommand === "show") {
23683
24282
  return stateShowText(data);
@@ -23687,7 +24286,7 @@ function formatStateText(data, subcommand) {
23687
24286
  return states.length === 0 ? "No saved states." : states.map(stateLine).join("\n");
23688
24287
  }
23689
24288
  if (data.loaded === true) {
23690
- return `State loaded: ${redactModelFacingText(getStringField(data, "path") ?? getStringField(data, "name") ?? "ok")}`;
24289
+ return stateLoadText(data);
23691
24290
  }
23692
24291
  return data.cleared === true || data.clear === true ? "State cleared." : void 0;
23693
24292
  }
@@ -23730,6 +24329,7 @@ var init_diagnostic_identity = __esm({
23730
24329
  "compatibilityStatus",
23731
24330
  "restoreKey",
23732
24331
  "restoreStatus",
24332
+ "restoreSkippedOrigins",
23733
24333
  "restoreLoadedPath",
23734
24334
  "restoreValidationPending",
23735
24335
  "restoreSave",
@@ -25214,7 +25814,7 @@ function redactBatchSpillData(data) {
25214
25814
  const command = isStringArray2(row.command) ? row.command : void 0;
25215
25815
  return {
25216
25816
  ...row,
25217
- command: command ? redactInvocationArgs(command) : row.command,
25817
+ command: row.index === void 0 && command ? redactInvocationArgs(command) : void 0,
25218
25818
  error: row.error === void 0 ? void 0 : redactBatchStepErrorData(command, row.error),
25219
25819
  result: row.result === void 0 ? void 0 : redactPresentationData(parseCommandInfo(command ?? []), row.result)
25220
25820
  };
@@ -25734,7 +26334,7 @@ var init_prepare_failure = __esm({
25734
26334
  });
25735
26335
 
25736
26336
  // dist/extensions/agent-browser/lib/electron/launch-monitor.js
25737
- import { readFile as readFile11 } from "node:fs/promises";
26337
+ import { readFile as readFile12 } from "node:fs/promises";
25738
26338
  function isElectronLaunchAborted(signal) {
25739
26339
  return signal?.aborted ?? false;
25740
26340
  }
@@ -25759,7 +26359,7 @@ function activePortReadError(error) {
25759
26359
  async function readDevToolsActivePort(userDataDir) {
25760
26360
  const path = `${userDataDir}/DevToolsActivePort`;
25761
26361
  try {
25762
- const text3 = await readFile11(path, "utf8");
26362
+ const text3 = await readFile12(path, "utf8");
25763
26363
  const port = Number(text3.split(/\r?\n/)[0].trim());
25764
26364
  const valid = Number.isSafeInteger(port) && port > 0 && port <= 65535;
25765
26365
  return {
@@ -25774,7 +26374,7 @@ async function readDevToolsActivePort(userDataDir) {
25774
26374
  }
25775
26375
  async function pollDevToolsActivePort(options) {
25776
26376
  let devToolsActivePort;
25777
- while (Date.now() <= options.deadlineMs) {
26377
+ while (performance.now() <= options.deadlineMs) {
25778
26378
  if (isElectronLaunchAborted(options.signal)) {
25779
26379
  return { devToolsActivePort, failure: "aborted" };
25780
26380
  }
@@ -25798,7 +26398,7 @@ async function pollDevToolsActivePort(options) {
25798
26398
  return { devToolsActivePort, failure: "timeout" };
25799
26399
  }
25800
26400
  async function pollCdpMetadata(port, deadlineMs, signal) {
25801
- while (Date.now() <= deadlineMs) {
26401
+ while (performance.now() <= deadlineMs) {
25802
26402
  if (isElectronLaunchAborted(signal)) {
25803
26403
  return { aborted: true };
25804
26404
  }
@@ -25830,7 +26430,7 @@ var init_launch_monitor = __esm({
25830
26430
  // dist/extensions/agent-browser/lib/electron/launch-process.js
25831
26431
  import { spawn as spawn3 } from "node:child_process";
25832
26432
  import { open as open5 } from "node:fs/promises";
25833
- import { dirname as dirname16, join as join30 } from "node:path";
26433
+ import { dirname as dirname17, join as join31 } from "node:path";
25834
26434
  async function closeOutputHandle(file) {
25835
26435
  try {
25836
26436
  await file.close();
@@ -25925,11 +26525,11 @@ var init_launch_process = __esm({
25925
26525
  async spawn(executablePath, args, signal) {
25926
26526
  const handles = [];
25927
26527
  try {
25928
- handles.push(await open5(join30(this.userDataDir, "stdout.log"), "wx", 384));
25929
- handles.push(await open5(join30(this.userDataDir, "stderr.log"), "wx", 384));
26528
+ handles.push(await open5(join31(this.userDataDir, "stdout.log"), "wx", 384));
26529
+ handles.push(await open5(join31(this.userDataDir, "stderr.log"), "wx", 384));
25930
26530
  signal?.throwIfAborted();
25931
26531
  this.process = spawn3(executablePath, args, {
25932
- cwd: dirname16(executablePath),
26532
+ cwd: dirname17(executablePath),
25933
26533
  detached: process.platform !== "win32",
25934
26534
  stdio: ["ignore", handles[0].fd, handles[1].fd]
25935
26535
  });
@@ -25960,12 +26560,13 @@ var init_launch_process = __esm({
25960
26560
  return isRecord(error) && error.code === "EPERM";
25961
26561
  }
25962
26562
  }
26563
+ /** Waits for exit until deadlineMs, a performance.now() timestamp unaffected by wall-clock steps. */
25963
26564
  async waitForExit(deadlineMs) {
25964
26565
  const child = this.process;
25965
26566
  if (child === void 0) {
25966
26567
  return true;
25967
26568
  }
25968
- while (Date.now() <= deadlineMs) {
26569
+ while (performance.now() <= deadlineMs) {
25969
26570
  if (child.exitCode !== null || child.signalCode !== null) {
25970
26571
  return true;
25971
26572
  }
@@ -25990,14 +26591,14 @@ var init_launch_process = __esm({
25990
26591
  if (terminateError !== void 0) {
25991
26592
  return terminateError;
25992
26593
  }
25993
- if (await this.waitForExit(Date.now() + 1e3)) {
26594
+ if (await this.waitForExit(performance.now() + 1e3)) {
25994
26595
  return void 0;
25995
26596
  }
25996
26597
  const killError = this.signalChild("SIGKILL");
25997
26598
  if (killError !== void 0) {
25998
26599
  return killError;
25999
26600
  }
26000
- if (await this.waitForExit(Date.now() + 1e3)) {
26601
+ if (await this.waitForExit(performance.now() + 1e3)) {
26001
26602
  return void 0;
26002
26603
  }
26003
26604
  return `PID ${child.pid} remained alive after failed Electron launch cleanup.`;
@@ -26007,8 +26608,8 @@ var init_launch_process = __esm({
26007
26608
  return { evidence: {}, lines: [] };
26008
26609
  }
26009
26610
  const [stdout, stderr] = await Promise.all([
26010
- readOutputTail(join30(this.userDataDir, "stdout.log")),
26011
- readOutputTail(join30(this.userDataDir, "stderr.log"))
26611
+ readOutputTail(join31(this.userDataDir, "stdout.log")),
26612
+ readOutputTail(join31(this.userDataDir, "stderr.log"))
26012
26613
  ]);
26013
26614
  return {
26014
26615
  evidence: {
@@ -26186,7 +26787,7 @@ async function launchElectronApp(options) {
26186
26787
  };
26187
26788
  }
26188
26789
  const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
26189
- const startedAtMs = Date.now();
26790
+ const startedAtMs = performance.now();
26190
26791
  const userDataDir = await createSecureTempDirectory(ELECTRON_PROFILE_DIR_PREFIX);
26191
26792
  if (isElectronLaunchAborted(options.signal)) {
26192
26793
  return abortedProfileResult(userDataDir, options, target);
@@ -26231,7 +26832,7 @@ var init_launch = __esm({
26231
26832
  diagnostics(observation2) {
26232
26833
  return {
26233
26834
  ...observation2,
26234
- elapsedMs: Math.max(0, Date.now() - this.startedAtMs),
26835
+ elapsedMs: Math.round(Math.max(0, performance.now() - this.startedAtMs)),
26235
26836
  exitCode: this.process.exitCode,
26236
26837
  exitSignal: this.process.exitSignal,
26237
26838
  outputCaptured: this.process.outputCaptured,
@@ -26365,7 +26966,7 @@ var init_launch = __esm({
26365
26966
 
26366
26967
  // dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-artifacts.js
26367
26968
  import { copyFile, mkdir as mkdir6 } from "node:fs/promises";
26368
- import { dirname as dirname17, resolve as resolve16 } from "node:path";
26969
+ import { dirname as dirname18, resolve as resolve16 } from "node:path";
26369
26970
  function getArtifactParentPathTokenIndex(commandTokens) {
26370
26971
  if (commandTokens[0] === "download" && commandTokens.length >= 3) {
26371
26972
  return 2;
@@ -26390,7 +26991,7 @@ async function ensureArtifactParentDirectory(commandTokens, cwd) {
26390
26991
  if (requestedPath === void 0 || requestedPath === "") {
26391
26992
  return;
26392
26993
  }
26393
- await mkdir6(dirname17(resolve16(cwd, requestedPath)), { recursive: true });
26994
+ await mkdir6(dirname18(resolve16(cwd, requestedPath)), { recursive: true });
26394
26995
  }
26395
26996
  function screenshotPathIndex(commandTokens, batchStep) {
26396
26997
  if (batchStep) {
@@ -26407,7 +27008,7 @@ async function normalizeScreenshotPathInTokens(commandTokens, cwd, batchStep = f
26407
27008
  }
26408
27009
  const requestedPath = commandTokens[pathIndex];
26409
27010
  const absolutePath = resolve16(cwd, requestedPath);
26410
- await mkdir6(dirname17(absolutePath), { recursive: true });
27011
+ await mkdir6(dirname18(absolutePath), { recursive: true });
26411
27012
  const tokens = [...commandTokens];
26412
27013
  tokens[pathIndex] = absolutePath;
26413
27014
  const terminatorIndex = batchStep ? -1 : tokens.indexOf("--");
@@ -26502,7 +27103,7 @@ async function repairScreenshotData(options) {
26502
27103
  if (reportedAbsolutePath !== void 0 && reportedAbsolutePath !== request.absolutePath) {
26503
27104
  tempPath = reportedAbsolutePath;
26504
27105
  if (status === "missing" && await pathExists(reportedAbsolutePath)) {
26505
- await mkdir6(dirname17(request.absolutePath), { recursive: true });
27106
+ await mkdir6(dirname18(request.absolutePath), { recursive: true });
26506
27107
  await copyFile(reportedAbsolutePath, request.absolutePath);
26507
27108
  status = "repaired-from-temp";
26508
27109
  }
@@ -26834,6 +27435,31 @@ var init_prepare_session_plan = __esm({
26834
27435
  });
26835
27436
 
26836
27437
  // dist/extensions/agent-browser/lib/orchestration/browser-run/prepare-daemon.js
27438
+ async function notCallerOwned() {
27439
+ return false;
27440
+ }
27441
+ function observeCallerDaemonInactive(facts, { plan, session }) {
27442
+ if (session.ownedManagedSession) {
27443
+ return notCallerOwned;
27444
+ }
27445
+ let observation2;
27446
+ const observe = async () => {
27447
+ const initial = facts.initialDaemonObservation;
27448
+ if (initial?.identity === getAgentBrowserSessionIdentityKey(plan.sessionName ?? "", plan.namespace)) {
27449
+ return initial.daemon.status === "inactive";
27450
+ }
27451
+ return plan.sessionName !== void 0 && (await inspectManagedSessionDaemon({
27452
+ cwd: facts.cwd,
27453
+ signal: facts.signal,
27454
+ sessionName: plan.sessionName,
27455
+ namespace: plan.namespace
27456
+ })).status === "inactive";
27457
+ };
27458
+ return () => {
27459
+ observation2 ??= observe();
27460
+ return observation2;
27461
+ };
27462
+ }
26837
27463
  function daemonReuseError(plan, session, policy) {
26838
27464
  if (policy.error !== void 0 && policy.error !== "") {
26839
27465
  return policy.error;
@@ -26865,7 +27491,12 @@ async function prepareDaemonPolicy(process2, electronLaunchRecord, planning) {
26865
27491
  const { plan, session } = planning;
26866
27492
  const context2 = session.ownedManagedSession;
26867
27493
  if (session.browserIndependent || planHasValidationError(plan) || !context2) {
26868
- return { plan, session, policy: { inactive: false } };
27494
+ return {
27495
+ plan,
27496
+ session,
27497
+ policy: { inactive: false },
27498
+ callerDaemonInactive: observeCallerDaemonInactive(process2, planning)
27499
+ };
26869
27500
  }
26870
27501
  const closeCommand = isCloseCommand(plan.commandInfo.command);
26871
27502
  const policy = await acquireOwnedManagedSessionDaemonPolicy({
@@ -26886,7 +27517,8 @@ async function prepareDaemonPolicy(process2, electronLaunchRecord, planning) {
26886
27517
  inactive: policy.daemonStatus === "inactive",
26887
27518
  generationInspection: policy.generationInspection,
26888
27519
  cleanupOnlyReason: policy.error !== void 0 && policy.error !== "" ? policy.cleanupOnlyReason : void 0
26889
- }
27520
+ },
27521
+ callerDaemonInactive: notCallerOwned
26890
27522
  };
26891
27523
  }
26892
27524
  function chromeStartupEligible(facts, plan, request) {
@@ -26899,16 +27531,7 @@ async function prepareChromeStartupArgs(facts, planning, request) {
26899
27531
  })) {
26900
27532
  return void 0;
26901
27533
  }
26902
- const { plan, session, policy } = planning;
26903
- let inactive = policy.inactive;
26904
- if (!session.ownedManagedSession) {
26905
- inactive = (facts.initialDaemonObservation?.identity === getAgentBrowserSessionIdentityKey(plan.sessionName ?? "", plan.namespace) ? facts.initialDaemonObservation.daemon.status === "inactive" : void 0) ?? (plan.sessionName !== void 0 && (await inspectManagedSessionDaemon({
26906
- cwd: facts.cwd,
26907
- signal: facts.signal,
26908
- sessionName: plan.sessionName,
26909
- namespace: plan.namespace
26910
- })).status === "inactive");
26911
- }
27534
+ const inactive = planning.session.ownedManagedSession ? planning.policy.inactive : await planning.callerDaemonInactive();
26912
27535
  return inactive || facts.configuredChromeLaunch === true ? facts.chromeStartupArgs : void 0;
26913
27536
  }
26914
27537
  var init_prepare_daemon = __esm({
@@ -26964,11 +27587,10 @@ async function reconcileGeneration(pageState, process2, request) {
26964
27587
  owned: session.ownedManagedSession
26965
27588
  }, request.generationInspection);
26966
27589
  pageState.bindSnapshotGeneration(key, daemon.status === "active" ? daemon.generation : void 0);
26967
- const generationKnown = daemon.status === "active" && daemon.generation !== void 0;
26968
- if (generationKnown && snapshot.generation === daemon.generation) {
27590
+ const generationMatch = managedSessionDaemonGenerationsMatch(snapshot.generation, daemon.status === "active" ? daemon.generation : void 0);
27591
+ if (generationMatch === true) {
26969
27592
  return { ...page, nativeGenerationChanged: false };
26970
27593
  }
26971
- const nativeGenerationChanged = generationKnown && snapshot.generation !== void 0;
26972
27594
  const invalidation = buildPageTransitionRefSnapshotInvalidation("The native browser generation changed or could not be verified. Take a new complete snapshot before using refs, even when the URL is unchanged.");
26973
27595
  pageState.applyRefSnapshotInvalidation({
26974
27596
  invalidation,
@@ -26977,7 +27599,7 @@ async function reconcileGeneration(pageState, process2, request) {
26977
27599
  });
26978
27600
  return {
26979
27601
  ...page,
26980
- nativeGenerationChanged,
27602
+ nativeGenerationChanged: generationMatch === false,
26981
27603
  priorRefSnapshotState: void 0,
26982
27604
  priorRefSnapshotInvalidation: invalidation
26983
27605
  };
@@ -27034,6 +27656,7 @@ var init_prepare_page_state = __esm({
27034
27656
  init_session_page_state();
27035
27657
  init_managed_session_daemon_policy();
27036
27658
  init_session_state();
27659
+ init_managed_session_daemon_inspection();
27037
27660
  }
27038
27661
  });
27039
27662
 
@@ -27157,7 +27780,7 @@ async function preparePageSelection(pageState, process2, hasReadConfirmation, re
27157
27780
  executionPlan: request.plan,
27158
27781
  livePageVerified: false
27159
27782
  };
27160
- if (!shouldSelectRememberedTab(request.plan, hasReadConfirmation, request)) {
27783
+ if (!shouldSelectRememberedTab(request.plan, hasReadConfirmation, request) || await probeWouldDropLaunchFlags(process2)) {
27161
27784
  return base;
27162
27785
  }
27163
27786
  process2.signal?.throwIfAborted();
@@ -27208,6 +27831,9 @@ function verifiedLivePage(selection, url) {
27208
27831
  }
27209
27832
  };
27210
27833
  }
27834
+ async function probeWouldDropLaunchFlags(process2) {
27835
+ return process2.preserveAttachedBrowserSession !== true && await process2.callerColdLaunch?.() === true;
27836
+ }
27211
27837
  async function verifyPreparationLivePage(process2, selection, request) {
27212
27838
  const plan = selection.executionPlan;
27213
27839
  if (request.requirement === void 0 || request.requirement === "" || plan.sessionName === void 0 || plan.sessionName === "") {
@@ -27216,6 +27842,9 @@ async function verifyPreparationLivePage(process2, selection, request) {
27216
27842
  if (process2.establishAttachedBrowserSession === true) {
27217
27843
  return { ...selection, executionPlan: withPlanValidationError(plan, request.requirement) };
27218
27844
  }
27845
+ if (await probeWouldDropLaunchFlags(process2)) {
27846
+ return selection;
27847
+ }
27219
27848
  const url = await readLiveUrl(process2, plan);
27220
27849
  if (url === void 0) {
27221
27850
  return { ...selection, executionPlan: withPlanValidationError(plan, request.requirement) };
@@ -28863,9 +29492,11 @@ async function prepareNativeCommand(facts, context2, evidence) {
28863
29492
  const plan = context2.semantic.selection.executionPlan;
28864
29493
  const page = context2.semantic.selection.page;
28865
29494
  const input = context2.input;
29495
+ const callerColdLaunch = !context2.session.browserIndependent && needsManagedSession(parseArgvDescriptor(plan.effectiveArgs), native.processStdin) && facts.preserveAttachedBrowserSession !== true && await facts.callerColdLaunch?.() === true;
28866
29496
  return {
28867
29497
  kind: "ready",
28868
29498
  prepared: {
29499
+ callerColdLaunch: callerColdLaunch ? true : void 0,
28869
29500
  compiledElectron: input.compiledElectron,
28870
29501
  compiledJob: input.compiledJob,
28871
29502
  compiledNetworkSourceLookup: input.compiledNetworkSourceLookup,
@@ -28914,6 +29545,8 @@ var init_prepare_command = __esm({
28914
29545
  init_process();
28915
29546
  init_runtime_redaction();
28916
29547
  init_argv_grammar();
29548
+ init_argv_descriptor();
29549
+ init_command_policy();
28917
29550
  init_click_dispatch();
28918
29551
  init_diagnostics();
28919
29552
  init_prepare_failure();
@@ -28983,6 +29616,10 @@ async function prepareCommandPhases(state, controls, context2) {
28983
29616
  }
28984
29617
  async function preparePagePhases(state, controls, planning, request) {
28985
29618
  const { plan, session, policy } = planning;
29619
+ const pageControls = {
29620
+ ...controls,
29621
+ callerColdLaunch: hasBrowserLaunchFlag(request.preparedArgs.args) ? planning.callerDaemonInactive : void 0
29622
+ };
28986
29623
  const initial = controls.initialDaemonObservation;
28987
29624
  const page = await preparePageState(state, controls, {
28988
29625
  plan,
@@ -28992,7 +29629,7 @@ async function preparePagePhases(state, controls, planning, request) {
28992
29629
  preserveAttachedBrowserSession: controls.preserveAttachedBrowserSession,
28993
29630
  generationInspection: policy.generationInspection ?? (!session.ownedManagedSession && initial?.generationInspected === true && initial.identity === getAgentBrowserSessionIdentityKey(plan.sessionName ?? "", plan.namespace) ? initial.daemon : void 0)
28994
29631
  });
28995
- const selection = await preparePageSelection(state.sessionPageState, controls, session.readConfirmation !== void 0, {
29632
+ const selection = await preparePageSelection(state.sessionPageState, pageControls, session.readConfirmation !== void 0, {
28996
29633
  ...request,
28997
29634
  plan,
28998
29635
  page,
@@ -29001,14 +29638,14 @@ async function preparePagePhases(state, controls, planning, request) {
29001
29638
  if ("earlyResult" in selection) {
29002
29639
  return selection.earlyResult;
29003
29640
  }
29004
- const semantic = await prepareSemanticAction(controls, request.sessionFacts, {
29641
+ const semantic = await prepareSemanticAction(pageControls, request.sessionFacts, {
29005
29642
  hasReadConfirmation: session.readConfirmation !== void 0,
29006
29643
  ownsSession: session.ownedManagedSession !== void 0
29007
29644
  }, {
29008
29645
  ...request,
29009
29646
  selection
29010
29647
  });
29011
- return prepareCommandPhases(state, controls, { ...request, semantic, session, policy });
29648
+ return prepareCommandPhases(state, pageControls, { ...request, semantic, session, policy });
29012
29649
  }
29013
29650
  async function prepareRequest(identity, input, settings) {
29014
29651
  const freshSessionName = createFreshSessionName(identity.managedSessionBaseName, identity.ephemeralSessionSeed, identity.freshSessionOrdinal + 1);
@@ -29121,6 +29758,7 @@ var init_prepare = __esm({
29121
29758
  "dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js"() {
29122
29759
  "use strict";
29123
29760
  init_runtime();
29761
+ init_launch_scoped_flags();
29124
29762
  init_page_target_validation();
29125
29763
  init_categories();
29126
29764
  init_process();
@@ -29242,20 +29880,20 @@ function planRecordingRecovery(options) {
29242
29880
  return void 0;
29243
29881
  }
29244
29882
  const { batch, steps, rows } = recoverySource(options);
29245
- const stopIndex = steps.reduce((last, step, index) => isRecordingStop(step) && stopNeedsRecovery(options, rows?.at(index)) ? index : last, -1);
29246
- if (stopIndex < 0) {
29883
+ const stopPosition = steps.reduce((last, step, position) => isRecordingStop(step) && stopNeedsRecovery(options, rows?.at(position)) ? position : last, -1);
29884
+ if (stopPosition < 0) {
29247
29885
  return void 0;
29248
29886
  }
29249
29887
  let expected = options.reservation;
29250
- for (const [index, step] of steps.slice(0, stopIndex).entries()) {
29251
- expected = reservationForStart(options, step, rows?.at(index)) ?? expected;
29888
+ for (const [position, step] of steps.slice(0, stopPosition).entries()) {
29889
+ expected = reservationForStart(options, step, rows?.at(position)) ?? expected;
29252
29890
  }
29253
29891
  return {
29254
29892
  batch,
29255
29893
  rows,
29256
- stopIndex,
29894
+ stopPosition,
29257
29895
  expected,
29258
- attempt: originalAttempt(options, rows?.at(stopIndex))
29896
+ attempt: originalAttempt(options, rows?.at(stopPosition))
29259
29897
  };
29260
29898
  }
29261
29899
  function receiptMatches(receipt, expected, nowMs) {
@@ -29410,7 +30048,7 @@ function classifyRecordingRecovery(evidence, plan, verified) {
29410
30048
  }
29411
30049
  function repairRecordingEnvelope(options, plan, data, verified) {
29412
30050
  if (plan.rows) {
29413
- const rows = plan.rows.map((row, index) => index === plan.stopIndex && isRecord(row) ? { ...row, result: data, success: verified, error: verified ? void 0 : row.error } : row);
30051
+ const rows = plan.rows.map((row, position) => position === plan.stopPosition && isRecord(row) ? { ...row, result: data, success: verified, error: verified ? void 0 : row.error } : row);
29414
30052
  const healed = verified && !options.processResult.timedOut && rows.every((row) => isRecord(row) && row.success === true);
29415
30053
  return {
29416
30054
  healed,
@@ -29523,13 +30161,13 @@ async function recoverRecordingStop(options) {
29523
30161
  batch: plan.batch,
29524
30162
  envelope: repaired.envelope,
29525
30163
  partialBatch: plan.batch && plan.rows === void 0,
29526
- stopIndex: plan.stopIndex,
30164
+ stopPosition: plan.stopPosition,
29527
30165
  presentation: finishRecordingPresentation(options, presentation, evidence, recovery),
29528
30166
  recovery
29529
30167
  };
29530
30168
  }
29531
30169
  function mergeRecoveryNextActions(base, result) {
29532
- const laterRecording = base.batchSteps?.slice(result.stopIndex + 1).some((step) => step.artifacts?.some(isPendingRecordingArtifact) === true) === true;
30170
+ const laterRecording = base.batchSteps?.slice(result.stopPosition + 1).some((step) => step.artifacts?.some(isPendingRecordingArtifact) === true) === true;
29533
30171
  const unrelatedFailure = base.batchFailure !== void 0 && !isRecordingStop(base.batchFailure.failedStep.command ?? []);
29534
30172
  const nextActions = (base.nextActions ?? []).filter((action) => action.id === "stop-pending-recording" ? laterRecording : unrelatedFailure);
29535
30173
  appendUniqueAgentBrowserNextActions(nextActions, result.presentation.nextActions);
@@ -29544,7 +30182,7 @@ function mergeRecordingRecoveryPresentation(base, result) {
29544
30182
  ...base,
29545
30183
  artifacts,
29546
30184
  artifactVerification: buildArtifactVerificationSummary(artifacts ?? []),
29547
- batchSteps: base.batchSteps?.map((step) => step.index === result.stopIndex ? {
30185
+ batchSteps: base.batchSteps?.map((step, position) => position === result.stopPosition ? {
29548
30186
  ...step,
29549
30187
  artifacts: result.presentation.artifacts,
29550
30188
  artifactVerification: result.presentation.artifactVerification
@@ -29583,6 +30221,10 @@ async function parseNativeOutput(draft) {
29583
30221
  plainText: draft.plainTextUpgrade,
29584
30222
  textOutput: draft.textOutput
29585
30223
  });
30224
+ draft.parsed = {
30225
+ ...draft.parsed,
30226
+ envelope: resolveIndexedBatchCommands(draft.parsed.envelope, draft.input.prepared.commandTokens, draft.input.prepared.runtimeToolStdin)
30227
+ };
29586
30228
  observeNativeWebMcp(draft.parsed.envelope?.data);
29587
30229
  draft.parseError = draft.parsed.parseError;
29588
30230
  draft.recordingStopRecovery = await recoverRecordingStop({
@@ -30213,7 +30855,9 @@ function mergeObservedNavigation(draft) {
30213
30855
  async function observeNavigationTarget(draft) {
30214
30856
  resolveResultingPageState(draft);
30215
30857
  const { prepared, cwd, signal } = draft.input;
30216
- if (canObserveSuccessfulNavigation(draft) && navigationObservationRequested(draft)) {
30858
+ const navigationRequested = navigationObservationRequested(draft);
30859
+ const coldTargetObservation = prepared.callerColdLaunch === true && !isOpenNavigationCommand(prepared.executionPlan.commandInfo.command);
30860
+ if (canObserveSuccessfulNavigation(draft) && (navigationRequested || coldTargetObservation)) {
30217
30861
  draft.navigationSummary = await collectNavigationSummary({
30218
30862
  cwd,
30219
30863
  namespace: prepared.executionPlan.namespace,
@@ -30233,7 +30877,9 @@ async function observeNavigationTarget(draft) {
30233
30877
  });
30234
30878
  draft.failedTransitionReverification = draft.navigationSummary !== void 0;
30235
30879
  }
30236
- mergeObservedNavigation(draft);
30880
+ if (navigationRequested || draft.failedTransitionReverification) {
30881
+ mergeObservedNavigation(draft);
30882
+ }
30237
30883
  }
30238
30884
  async function correctRestoredOpenTab(draft) {
30239
30885
  const { prepared, cwd, signal } = draft.input;
@@ -30730,6 +31376,9 @@ function getTransitionInvalidation(draft) {
30730
31376
  if (draft.unsettledWebMcpMutation) {
30731
31377
  return buildPageTransitionRefSnapshotInvalidation("A detached WebMCP invocation is still pending or failed to settle and can mutate, rerender, or navigate the page, so prior snapshot refs remain invalid after URL verification. Run webmcp result or cancel, then take a fresh snapshot before using page-scoped refs.");
30732
31378
  }
31379
+ if (draft.input.prepared.callerColdLaunch === true && draft.input.processResult.agentBrowserStarted) {
31380
+ return buildPageTransitionRefSnapshotInvalidation("The previous caller-owned browser stopped. Take a fresh complete snapshot before using page-scoped refs.");
31381
+ }
30733
31382
  return observedTransitionInvalidation(draft, direct ?? planned);
30734
31383
  }
30735
31384
  function observedTransitionInvalidation(draft, commandInvalidation) {
@@ -31426,6 +32075,7 @@ function resolveNativeErrorText(draft) {
31426
32075
  effectiveArgs: draft.input.prepared.redactedProcessArgs,
31427
32076
  envelope: draft.presentationEnvelope,
31428
32077
  exitCode: draft.input.processResult.exitCode,
32078
+ exitSignal: draft.input.processResult.exitSignal,
31429
32079
  parseError: draft.parseError,
31430
32080
  plainTextInspection: draft.plainTextInspection,
31431
32081
  staleRefArgs: getStaleRefArgs(draft.input.prepared.commandTokens, draft.input.prepared.runtimeToolStdin),
@@ -31470,7 +32120,7 @@ function observeResultLaunchPolicy(draft) {
31470
32120
  draft.resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !(draft.commandClosesSession && draft.succeeded) ? draft.input.prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval : void 0;
31471
32121
  }
31472
32122
  function classifyMalformedNativeFailure(draft) {
31473
- if (draft.parseError !== void 0 && draft.parseError.length > 0 && draft.input.processResult.exitCode !== 0 && draft.input.processResult.stderr.trim().length > 0 && !draft.input.processResult.timedOut && !draft.input.processResult.aborted && !draft.input.processResult.spawnError) {
32123
+ if (draft.parseError !== void 0 && draft.parseError.length > 0 && draft.input.processResult.exitCode !== 0 && !draft.input.processResult.timedOut && !draft.input.processResult.aborted && !draft.input.processResult.spawnError) {
31474
32124
  draft.presentation.failureCategory = "upstream-error";
31475
32125
  }
31476
32126
  }
@@ -32432,7 +33082,23 @@ __export(process_output_exports, {
32432
33082
  processBrowserOutput: () => processBrowserOutput
32433
33083
  });
32434
33084
  import { rm as rm14 } from "node:fs/promises";
32435
- async function processBrowserOutput(input) {
33085
+ function coldLaunchOutput(input) {
33086
+ if (input.prepared.callerColdLaunch !== true || !input.processResult.agentBrowserStarted) {
33087
+ return input;
33088
+ }
33089
+ return {
33090
+ ...input,
33091
+ prepared: {
33092
+ ...input.prepared,
33093
+ priorSessionTabTarget: void 0,
33094
+ priorSessionTabTargetUnknown: true,
33095
+ priorRefSnapshotState: void 0,
33096
+ resolvedSemanticActionRefSnapshot: void 0
33097
+ }
33098
+ };
33099
+ }
33100
+ async function processBrowserOutput(invocation) {
33101
+ const input = coldLaunchOutput(invocation);
32436
33102
  const ownership = captureBrowserOutputOwnership(input);
32437
33103
  try {
32438
33104
  const native = await observeNativeEffects(input, ownership);
@@ -32473,6 +33139,11 @@ import { open as open2, stat as stat2 } from "node:fs/promises";
32473
33139
  // dist/extensions/agent-browser/lib/browser-journal-projection.js
32474
33140
  init_parsing();
32475
33141
  var ENVELOPE_MAX_BYTES = 16 * 1024 * 1024;
33142
+ var MAX_NESTING = 1024;
33143
+ var RESERVE_PER_BYTE = 16;
33144
+ var NATIVE_RECORD_BYTES = ENVELOPE_MAX_BYTES / RESERVE_PER_BYTE;
33145
+ var NATIVE_STRUCTURE_LIMIT = 64 * 1024;
33146
+ var JOURNAL_CHUNK_BYTES = 64 * 1024;
32476
33147
  function matches(field, key) {
32477
33148
  return field === key || field === "*" && typeof key === "number";
32478
33149
  }
@@ -32611,7 +33282,7 @@ var JsonProjection = class {
32611
33282
  }
32612
33283
  }
32613
33284
  startContainer(name) {
32614
- if (this.stack.length >= 1024) {
33285
+ if (this.stack.length >= MAX_NESTING) {
32615
33286
  throw new Error("Journal JSON nesting exceeds 1024 levels.");
32616
33287
  }
32617
33288
  const currentPath = this.excluded() ? [] : this.path();
@@ -32676,14 +33347,110 @@ async function projectJson(input, fields, maxBytes = ENVELOPE_MAX_BYTES) {
32676
33347
  consume(chain.none);
32677
33348
  return projection.result();
32678
33349
  }
33350
+ function stringEnd(bytes, open6) {
33351
+ for (let quote = bytes.indexOf(34, open6 + 1); quote !== -1; quote = bytes.indexOf(34, quote + 1)) {
33352
+ let escapes = 0;
33353
+ while (bytes[quote - 1 - escapes] === 92) {
33354
+ escapes++;
33355
+ }
33356
+ if (escapes % 2 === 0) {
33357
+ return quote;
33358
+ }
33359
+ }
33360
+ return -1;
33361
+ }
33362
+ var DEPTH_STEP = new Int8Array(256);
33363
+ var STRUCTURE_STEP = new Uint8Array(256);
33364
+ for (const byte of [91, 123]) {
33365
+ DEPTH_STEP[byte] = 1;
33366
+ STRUCTURE_STEP[byte] = 1;
33367
+ }
33368
+ for (const byte of [93, 125]) {
33369
+ DEPTH_STEP[byte] = -1;
33370
+ }
33371
+ for (const byte of [44, 58]) {
33372
+ STRUCTURE_STEP[byte] = 1;
33373
+ }
33374
+ function fitsNativeParse(bytes) {
33375
+ let depth = 0;
33376
+ let structure = 0;
33377
+ for (let index = 0; index < bytes.length; index++) {
33378
+ const byte = bytes[index];
33379
+ if (byte === 34) {
33380
+ index = stringEnd(bytes, index);
33381
+ if (index === -1) {
33382
+ return false;
33383
+ }
33384
+ continue;
33385
+ }
33386
+ depth += DEPTH_STEP[byte];
33387
+ structure += STRUCTURE_STEP[byte];
33388
+ if (depth > MAX_NESTING || structure > NATIVE_STRUCTURE_LIMIT) {
33389
+ return false;
33390
+ }
33391
+ }
33392
+ return true;
33393
+ }
33394
+ function selectValue(value, path, fields) {
33395
+ if (typeof value !== "object" || value === null || fields.some((field) => field.every((key, index) => matches(key, path[index])))) {
33396
+ return value;
33397
+ }
33398
+ if (Array.isArray(value)) {
33399
+ const selected2 = [];
33400
+ for (const [index, item] of value.entries()) {
33401
+ const itemPath = [...path, index];
33402
+ if (selectedPath(itemPath, fields)) {
33403
+ selected2[index] = selectValue(item, itemPath, fields);
33404
+ }
33405
+ }
33406
+ return selected2;
33407
+ }
33408
+ const selected = {};
33409
+ for (const [key, item] of Object.entries(value)) {
33410
+ const itemPath = [...path, key];
33411
+ if (selectedPath(itemPath, fields)) {
33412
+ Object.defineProperty(selected, key, {
33413
+ value: selectValue(item, itemPath, fields),
33414
+ writable: true,
33415
+ enumerable: true,
33416
+ configurable: true
33417
+ });
33418
+ }
33419
+ }
33420
+ return selected;
33421
+ }
33422
+ function nativeProjection(bytes, fields) {
33423
+ if (!fitsNativeParse(bytes)) {
33424
+ return void 0;
33425
+ }
33426
+ let value;
33427
+ try {
33428
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
33429
+ } catch {
33430
+ return void 0;
33431
+ }
33432
+ if (!isRecord(value)) {
33433
+ return void 0;
33434
+ }
33435
+ const selected = selectValue(value, [], fields);
33436
+ return isRecord(selected) ? selected : void 0;
33437
+ }
33438
+ async function* chunks(bytes) {
33439
+ for (let start = 0; start < bytes.length; start += JOURNAL_CHUNK_BYTES) {
33440
+ yield bytes.subarray(start, start + JOURNAL_CHUNK_BYTES);
33441
+ }
33442
+ }
33443
+ async function projectJsonBytes(bytes, fields, maxBytes = ENVELOPE_MAX_BYTES) {
33444
+ const native = bytes.length * RESERVE_PER_BYTE <= maxBytes ? nativeProjection(bytes, fields) : void 0;
33445
+ return native ?? projectJson(chunks(bytes), fields, maxBytes);
33446
+ }
32679
33447
 
32680
33448
  // dist/extensions/agent-browser/lib/browser-journal-reader.js
32681
- var CHUNK_BYTES = 64 * 1024;
32682
33449
  async function* readRange(file, range) {
32683
33450
  let position = range.offset;
32684
33451
  const end = range.offset + range.length;
32685
33452
  while (position < end) {
32686
- const buffer = Buffer.allocUnsafe(Math.min(CHUNK_BYTES, end - position));
33453
+ const buffer = Buffer.allocUnsafe(Math.min(JOURNAL_CHUNK_BYTES, end - position));
32687
33454
  const { bytesRead } = await file.read(buffer, 0, buffer.length, position);
32688
33455
  if (bytesRead === 0) {
32689
33456
  throw new Error("Journal changed or truncated during read.");
@@ -32692,35 +33459,69 @@ async function* readRange(file, range) {
32692
33459
  yield buffer.subarray(0, bytesRead);
32693
33460
  }
32694
33461
  }
33462
+ function hasContent(buffer, start, end) {
33463
+ for (let index = start; index < end; index++) {
33464
+ const byte = buffer[index];
33465
+ if (byte !== 9 && byte !== 13 && byte !== 32) {
33466
+ return true;
33467
+ }
33468
+ }
33469
+ return false;
33470
+ }
32695
33471
  var JournalLineFramer = class {
32696
33472
  start;
32697
33473
  nonblank = false;
33474
+ parts = [];
33475
+ held = 0;
32698
33476
  constructor(offset) {
32699
33477
  this.start = offset;
32700
33478
  }
32701
33479
  *accept(buffer, position) {
32702
- for (let index = 0; index < buffer.length; index++) {
32703
- const byte = buffer[index];
32704
- if (byte === 10) {
32705
- if (this.nonblank) {
32706
- yield { offset: this.start, length: position + index + 1 - this.start };
32707
- }
32708
- this.start = position + index + 1;
32709
- this.nonblank = false;
32710
- } else if (![9, 13, 32].includes(byte)) {
32711
- this.nonblank = true;
33480
+ let index = 0;
33481
+ while (index < buffer.length) {
33482
+ const newline = buffer.indexOf(10, index);
33483
+ const end = newline === -1 ? buffer.length : newline;
33484
+ this.nonblank ||= hasContent(buffer, index, end);
33485
+ this.hold(buffer.subarray(index, newline === -1 ? end : end + 1));
33486
+ if (newline === -1) {
33487
+ return;
33488
+ }
33489
+ const record = this.take(position + newline + 1);
33490
+ if (record) {
33491
+ yield record;
32712
33492
  }
33493
+ index = newline + 1;
32713
33494
  }
32714
33495
  }
32715
33496
  final(end, sealed) {
32716
- return this.nonblank && sealed ? { offset: this.start, length: end - this.start } : void 0;
33497
+ return sealed ? this.take(end) : void 0;
33498
+ }
33499
+ hold(part) {
33500
+ this.held += part.length;
33501
+ if (this.held > NATIVE_RECORD_BYTES) {
33502
+ this.parts = void 0;
33503
+ } else {
33504
+ this.parts?.push(part);
33505
+ }
33506
+ }
33507
+ take(next) {
33508
+ let record;
33509
+ if (this.nonblank) {
33510
+ const range = { offset: this.start, length: next - this.start };
33511
+ record = this.parts ? { ...range, bytes: Buffer.concat(this.parts) } : range;
33512
+ }
33513
+ this.start = next;
33514
+ this.nonblank = false;
33515
+ this.parts = [];
33516
+ this.held = 0;
33517
+ return record;
32717
33518
  }
32718
33519
  };
32719
33520
  async function* journalRanges(file, end, sealed = false, offset = 0) {
32720
33521
  let position = offset;
32721
33522
  const framer = new JournalLineFramer(offset);
32722
33523
  while (position < end) {
32723
- const buffer = Buffer.allocUnsafe(Math.min(CHUNK_BYTES, end - position));
33524
+ const buffer = Buffer.allocUnsafe(Math.min(JOURNAL_CHUNK_BYTES, end - position));
32724
33525
  const { bytesRead } = await file.read(buffer, 0, buffer.length, position);
32725
33526
  if (bytesRead === 0) {
32726
33527
  throw new Error("Journal truncated during read.");
@@ -32748,14 +33549,14 @@ var STRUCTURAL_FIELDS = [
32748
33549
  ["message", "details", "artifactManifest", "version"],
32749
33550
  ["message", "details", "electron", "action"]
32750
33551
  ];
32751
- async function scanJournalMetadata(file, end, sealed = false) {
33552
+ async function scanJournalMetadata(file, end, sealed = false, offset = 0) {
32752
33553
  const entries = [];
32753
- for await (const range of journalRanges(file, end, sealed)) {
32754
- const value = await projectJson(readRange(file, range), STRUCTURAL_FIELDS);
33554
+ for await (const range of journalRanges(file, end, sealed, offset)) {
33555
+ const value = range.bytes ? await projectJsonBytes(range.bytes, STRUCTURAL_FIELDS) : await projectJson(readRange(file, range), STRUCTURAL_FIELDS);
32755
33556
  if (!value) {
32756
33557
  throw new Error(`Expected a journal object at byte ${range.offset}.`);
32757
33558
  }
32758
- entries.push({ ...range, value });
33559
+ entries.push({ offset: range.offset, length: range.length, value });
32759
33560
  }
32760
33561
  return entries;
32761
33562
  }
@@ -33331,6 +34132,20 @@ var REPLAY_CUSTOM_TYPES = /* @__PURE__ */ new Set([
33331
34132
  "agent-browser-recording-reservation",
33332
34133
  "agent-browser-script-session"
33333
34134
  ]);
34135
+ var journalIndexes = /* @__PURE__ */ new WeakMap();
34136
+ function appendCompatible(previous, current) {
34137
+ return previous.dev === current.dev && previous.ino === current.ino && current.size >= previous.size && (current.size > previous.size || current.mtimeMs === previous.mtimeMs && current.ctimeMs === previous.ctimeMs);
34138
+ }
34139
+ async function readJournalIndex(manager, file, path, captured) {
34140
+ const previous = journalIndexes.get(manager);
34141
+ journalIndexes.delete(manager);
34142
+ const reusable = previous?.path === path && appendCompatible(previous.captured, captured) && previous.header === manager.getHeader();
34143
+ const prefix = reusable ? previous.metadata : [];
34144
+ const last = prefix.at(-1);
34145
+ const offset = last ? last.offset + last.length : 0;
34146
+ const suffix = reusable && captured.size === previous.captured.size ? [] : await scanJournalMetadata(file, captured.size, false, offset);
34147
+ return { path, header: manager.getHeader(), captured, metadata: [...prefix, ...suffix] };
34148
+ }
33334
34149
  function branchEntries(entries, selectedLeaf) {
33335
34150
  const byId = /* @__PURE__ */ new Map();
33336
34151
  for (const entry of entries) {
@@ -33429,6 +34244,16 @@ function winningSnapshot(body, source) {
33429
34244
  const value = body?.[source.root];
33430
34245
  return isRecord(value) && isRecord(value.details) ? legacySnapshot(value.details.refSnapshot, source.id) : void 0;
33431
34246
  }
34247
+ async function projectEntryBody(file, entry, fields, maxBytes) {
34248
+ if (entry.length > NATIVE_RECORD_BYTES) {
34249
+ return projectJson(readRange(file, entry), fields, maxBytes);
34250
+ }
34251
+ const parts = [];
34252
+ for await (const part of readRange(file, entry)) {
34253
+ parts.push(part);
34254
+ }
34255
+ return projectJsonBytes(Buffer.concat(parts), fields, maxBytes);
34256
+ }
33432
34257
  var PublishedJournalReplay = class {
33433
34258
  file;
33434
34259
  ownerSessionId;
@@ -33458,7 +34283,7 @@ var PublishedJournalReplay = class {
33458
34283
  this.advance(value, parent);
33459
34284
  return;
33460
34285
  }
33461
- const body = await projectJson(readRange(this.file, entry), replayFields(value, legacy), legacy ? Infinity : void 0);
34286
+ const body = await projectEntryBody(this.file, entry, replayFields(value, legacy), legacy ? Infinity : void 0);
33462
34287
  const candidate = { ...value, ...body };
33463
34288
  const record = this.normalizeRecord(candidate, parent);
33464
34289
  this.rememberSnapshot(entry, legacy, record?.snapshot);
@@ -33502,7 +34327,7 @@ var PublishedJournalReplay = class {
33502
34327
  }
33503
34328
  const { entry, legacy } = definition;
33504
34329
  const root = entry.value.type === "message" ? "message" : "data";
33505
- const body = await projectJson(readRange(this.file, entry), legacy ? [[root, "details", "refSnapshot"]] : [["data", "snapshot"]], Infinity);
34330
+ const body = await projectEntryBody(this.file, entry, legacy ? [[root, "details", "refSnapshot"]] : [["data", "snapshot"]], Infinity);
33506
34331
  const resolved = winningSnapshot(body, { root, legacy, id });
33507
34332
  const destination = this.projected.find((candidate) => isRecord(candidate) && candidate.id === entry.value.id);
33508
34333
  if (!isRecord(destination) || !isRecord(destination.data)) {
@@ -33512,7 +34337,7 @@ var PublishedJournalReplay = class {
33512
34337
  if (!isRecord(resolved) || resolved.id !== id) {
33513
34338
  throw new Error("Winning browser snapshot identity is invalid.");
33514
34339
  }
33515
- snapshotFromDefinition(resolved);
34340
+ assertSnapshotDefinition(resolved);
33516
34341
  }
33517
34342
  };
33518
34343
  function replayRelevant(value) {
@@ -33525,9 +34350,9 @@ async function verifyCapturedFile(file, path, captured) {
33525
34350
  throw new Error("Pi journal replaced or truncated during replay.");
33526
34351
  }
33527
34352
  }
33528
- function validateJournalHeader(metadata, sessionId) {
33529
- const header = metadata.find((entry) => entry.value.type === "session");
33530
- if (header?.value.id !== sessionId) {
34353
+ function validateJournalHeader(index, manager) {
34354
+ const header = index.metadata.find((entry) => entry.value.type === "session");
34355
+ if (header?.value.id !== (index.header?.id ?? manager.getSessionId())) {
33531
34356
  throw new Error("Pi session journal identity does not match the active session.");
33532
34357
  }
33533
34358
  }
@@ -33544,29 +34369,37 @@ async function openPublishedJournal(path) {
33544
34369
  async function readBrowserEntries(manager, physical = false) {
33545
34370
  const path = manager.getSessionFile();
33546
34371
  if (path === void 0 || path.length === 0) {
34372
+ journalIndexes.delete(manager);
33547
34373
  return inMemoryEntries(manager, physical);
33548
34374
  }
33549
34375
  const leaf = physical ? null : manager.getLeafId();
33550
- const file = await openPublishedJournal(path);
33551
- if (!file) {
33552
- return inMemoryEntries(manager, physical);
33553
- }
34376
+ let file;
33554
34377
  try {
34378
+ file = await openPublishedJournal(path);
34379
+ if (!file) {
34380
+ journalIndexes.delete(manager);
34381
+ return inMemoryEntries(manager, physical);
34382
+ }
33555
34383
  const captured = await file.stat();
33556
34384
  if (!captured.isFile()) {
33557
34385
  throw new Error("Pi session journal is not a regular file.");
33558
34386
  }
33559
- const metadata = await scanJournalMetadata(file, captured.size);
33560
- validateJournalHeader(metadata, manager.getHeader()?.id ?? manager.getSessionId());
34387
+ const index = await readJournalIndex(manager, file, path, captured);
34388
+ const { metadata } = index;
34389
+ validateJournalHeader(index, manager);
33561
34390
  const records = metadata.filter((entry) => entry.value.type !== "session");
33562
34391
  const selected = physical ? records : branchEntries(records, leaf);
33563
34392
  const replay = new PublishedJournalReplay(file, selected, legacyOwner(manager));
33564
34393
  await replay.project(selected);
33565
34394
  const entries = await replay.resolveWinningDefinitions(physical);
33566
34395
  await verifyCapturedFile(file, path, captured);
34396
+ journalIndexes.set(manager, index);
33567
34397
  return entries;
34398
+ } catch (error) {
34399
+ journalIndexes.delete(manager);
34400
+ throw error;
33568
34401
  } finally {
33569
- await file.close();
34402
+ await file?.close();
33570
34403
  }
33571
34404
  }
33572
34405
 
@@ -33791,10 +34624,10 @@ init_managed_session_restore();
33791
34624
 
33792
34625
  // dist/extensions/agent-browser/lib/orchestration/execution-queue.js
33793
34626
  init_argv_grammar();
33794
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
34627
+ import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
33795
34628
  var AsyncExecutionQueue = class {
33796
34629
  tail = Promise.resolve();
33797
- active = new AsyncLocalStorage3();
34630
+ active = new AsyncLocalStorage4();
33798
34631
  isCurrent() {
33799
34632
  return this.active.getStore()?.active === true;
33800
34633
  }
@@ -33897,14 +34730,14 @@ function buildInstalledDocsGuideline(paths) {
33897
34730
  }
33898
34731
  var QUICK_START_GUIDELINES = [
33899
34732
  "Choose agent_browser for one native command, native batch --bail with JSON-array stdin for a known fixed sequence, or agent_browser_code for loops, branches, and aggregation. Return to the model when a fresh observation needs judgment. Neither tool is a named recipe registry.",
33900
- 'agent_browser takes args (argv after the binary), optional stdin, outputPath, timeoutMs, and sessionMode. Start with { args: ["open", "<url>"] }, then { args: ["snapshot", "-i"] }; act on current @refs and re-snapshot after navigation or DOM changes. Native commands, providers, plugins, profiles, auth, file access, and caller paths remain available through args.',
34733
+ 'agent_browser takes args (argv after the binary), optional stdin, outputPath, timeoutMs, and sessionMode; sessionMode:fresh intentionally launches a separate managed browser. Start with { args: ["open", "<url>"] }, then { args: ["snapshot", "-i"] }; act on current @refs and re-snapshot after navigation or DOM changes. Native commands, providers, plugins, profiles, auth, file access, and caller paths remain available through args; native config and environment pass through. Use requested/configured profiles and verify authentication after restart.',
33901
34734
  'Use { args: ["batch", "--bail"], stdin: "[[\\"fill\\",\\"@e1\\",\\"text\\"],[\\"click\\",\\"@e2\\"]]" } for same-snapshot form fields followed by submit. Split if a fill rerenders later fields. Add native condition waits after navigation-prone actions; a click alone does not prove the destination loaded. Avoid blind mutation retries after timeout.',
33902
34735
  "agent_browser_code takes { code, session?, namespace?, timeoutMs?, outputPath? }. JavaScript globals are fresh for every call; the selected browser persists and is shared with agent_browser. Use await browser({ args, stdin?, timeoutMs? }), inspect result.success, and emit selected JSON instead of whole envelopes. Use emitImage(result.imageObservations[0]) only when image inspection is needed; image handles belong to the current code call. No imports or host filesystem/network/process APIs are exposed.",
33903
34736
  `Example agent_browser_code: { code: 'const result = await browser({args:["get","title"]}); if (!result.success) { emit(result); } else { emit({title:result.data}); }' }. Native batch still avoids extra CLI processes for fixed segments inside code. Explicit session/namespace controls select the browser for this cell without changing the default browser.`,
33904
34737
  'Call agent_browser_tools with no fields for inventory, or { enable: ["action", "qa", "electron", "source", "network"] } for advanced native tools. It adds agent_browser_action, agent_browser_qa, agent_browser_electron, agent_browser_source, and agent_browser_network_source without disabling other tools. The browser instructions cover these capabilities; activation reveals their selected tool declarations.',
33905
34738
  "Use read <url> for readable web text; get title/url, get text/html/value/count <selector>, or get attr <selector> <name> for targeted state. For eval --stdin, put JavaScript in stdin and return the desired value. outputPath saves result data and must differ from screenshot/download/recording paths. Use --json only when JSON text is needed.",
33906
- "For artifacts, verify artifactVerification and artifacts before reporting success. A pending recording is not verified output; install ffmpeg before starting and pair start with stop. Native download owns the click and saves Blob/redirect downloads. Close keeps explicitly saved files. Save promptGuard-required paths before close. Screenshot pixels are not browser CSS coordinates: use imageObservations geometry (dimensions, viewport, crop/origin, scroll and DPR) before any visual-coordinate action; full-page and element captures are not current-viewport coordinates, and Pi resizing only maps sent pixels to original image pixels. Lightpanda images are text-rendered, not graphical layout; rendering:text never supports CSS/mouse-coordinate input.",
33907
- "Follow model-visible nextActions when present, including identity and stdin, instead of guessing commands. Treat native pending confirmations and unknown mutation outcomes as inspection boundaries. For dense snapshots, inspect Omitted high-value controls before reading a full spill."
34739
+ "For artifacts, verify artifactVerification and artifacts before reporting success. A pending recording is not verified output; install ffmpeg before starting and pair start with stop. Native download owns the click and saves Blob/redirect downloads. Close keeps explicitly saved files. Save promptGuard-required paths before close. Screenshot pixels are not browser CSS coordinates: use imageObservations geometry (dimensions, viewport, crop/origin, scroll and DPR) before any visual-coordinate action; full-page and element captures are not current-viewport coordinates, and Pi resizing only maps sent pixels to original image pixels. Lightpanda images are text-rendered, not graphical layout; rendering:text never supports CSS/mouse-coordinate input. Prefer refs; inspect screenshots only when the task needs vision.",
34740
+ "Follow model-visible nextActions when present, including identity and stdin, instead of guessing commands. Treat native pending confirmations and unknown mutation outcomes as inspection boundaries. Native outcome_unknown means a command may have run and was not retried; inspect before any repeat. For dense snapshots, inspect Omitted high-value controls before reading a full spill."
33908
34741
  ];
33909
34742
  var ADVANCED_TOOL_PROMPT_GUIDELINES = {
33910
34743
  action: [
@@ -33946,11 +34779,11 @@ var SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
33946
34779
  "Do not assume Playwright selector dialects such as text=Close or button:has-text('Close') are supported wrapper syntax unless current upstream agent-browser behavior has been verified.",
33947
34780
  "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. On macOS, copied Chrome profiles may omit encrypted cookies, so profile selection alone is not proof of authentication; verify the target page and use a user-approved headed login once when needed. 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.",
33948
34781
  "Ordinary bare calls share one native browser within your root Pi parent/subagent group; unrelated roots browse independently. Coordinate multi-call navigation inside your group with existing subagent/intercom tools. Explicit native session defaults still win. Pi quit does not close group browsers; never close an unrelated browser.",
33949
- `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.`,
34782
+ `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. If you already used the implicit session and now need launch-scoped flags, 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.`,
33950
34783
  "Cooperating Pi processes serialize each complete browser operation and code cell by native socket context, namespace, and session, including target/ref helpers and state updates. Different identities remain concurrent; namespace-wide close waits for matching operations. All participating Pi instances must load this release; humans, third-party clients, and older extensions are outside that coordination. It is not a transaction or rollback. For raw batches whose later content depends on navigation, use exact batch --bail or split the calls.",
33951
34784
  "After a successful `connect`, `--cdp`, or enabled `--auto-connect` call, verify with get url and keep using the resulting session without repeating the attach flag. The wrapper remembers that attachment across active-branch reload/resume and live-checks the URL before later page reads/interactions because an attached browser can drift externally; caller config, file access, launch arguments, and environment pass through unchanged. A successful close clears the marker. When several named sessions share one Chrome, pass --pin-tab once (AGENT_BROWSER_PIN_TAB) so a closed bound tab fails as tab_gone instead of acting on a neighbor; recover with tab new or tab list. --no-pin-tab turns the sticky pin off. tab list includes each tab's CDP targetId, accepted as a tab ref.",
33952
- `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.`,
33953
- "Chrome is the default. Use separately installed Lightpanda only when explicitly selected with --engine lightpanda, environment or native config; --engine is launch-scoped, so use sessionMode:fresh to change a managed browser. Lightpanda text-rendered screenshots do not prove graphical layout/visibility or mouse coordinates. Chrome profiles, headed mode, multi-tab, WebGPU and native WebMCP are not parity capabilities; choose Chrome explicitly when needed, never assume automatic fallback.",
34785
+ "Chrome is the default. Select separately installed Lightpanda or experimental Obscura explicitly with --engine lightpanda|obscura, environment or native config; use sessionMode:fresh to change a managed engine. Lightpanda text-rendered screenshots never establish graphical layout or mouse coordinates. Obscura has accessibility, iframe and screenshot gaps; read its command-reference limits first. Profiles, headed mode, multi-tab, WebGPU and WebMCP are not parity capabilities; choose Chrome when needed, never assume automatic fallback.",
34786
+ "For CLI HTTPS trust on read/install/upgrade/doctor, use --ca-cert <path> or --use-system-ca; --use-system-ca false disables OS roots and SSL_CERT_FILE is a fallback. These do not disable certificate verification or configure remote CDP trust. Browser CA trust remains Linux-Chromium-only and launch-scoped.",
33954
34787
  '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.',
33955
34788
  "For experimental WebMCP page tools, use webmcp list, then webmcp invoke <tool> with --params and optional --frame/--detach/--timeout; use webmcp result or cancel for detached calls. Locally managed Chrome enables WebMCP by default; a positive navigation hint means the page has tools to list. --no-webmcp is launch-scoped and requires a fresh session; invoke/result/cancel can mutate or navigate, so refresh snapshot refs afterward.",
33956
34789
  "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.",
@@ -33958,7 +34791,7 @@ var SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
33958
34791
  "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.",
33959
34792
  "For stateful browser context work, 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, and state save/load/list/show/rename/clear/clean for upstream saved-state lifecycle. State paths, restore identifiers, wrapper-prefixed sessions, and all upstream list rows remain available; credential values inside cookie/storage/auth payloads are still redacted from presentation.",
33960
34793
  "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.",
33961
- "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.",
34794
+ "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 or native --omit-command index, redacted result or scrubbed error text) built from the same presentation rules as standalone calls. Indexed receipts preserve original input positions, including skipped empty rows, without echoing commands.",
33962
34795
  "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 agent_browser_network_source, 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.",
33963
34796
  'For Electron desktop apps, enable agent_browser_electron through agent_browser_tools 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 agent_browser_electron probe or snapshot -i/agent_browser_qa attached:true for current-session state, and always cleanup the returned launchId when done. agent_browser_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.',
33964
34797
  "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.",
@@ -33976,6 +34809,7 @@ var SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
33976
34809
  "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.",
33977
34810
  "Respect explicit user stop boundaries yourself. When the surrounding authenticated employee or automation context is explicitly unattended/auto-approved, ordinary non-destructive form submissions within the requested flow may proceed without separate confirmation. Still require explicit authorization for purchases, production-control actions, destructive or irreversible actions, and account, security, or privacy changes. The wrapper does not infer broad business intent from prompt text; details.promptGuard is reserved for concrete artifact-before-close checks.",
33978
34811
  "Recording needs ffmpeg on PATH before start. Current upstream checks it at startup; older natives may defer failure. A pending recording is not verified output.",
34812
+ "Native 0.41 mcp call and stdin-driven MCP can finish normally, but this wrapper currently rejects all non-help mcp invocations. Session/namespace coordination and credential/image presentation are not integrated; use ordinary agent_browser commands.",
33979
34813
  "Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary."
33980
34814
  ];
33981
34815
  var TOOL_PROMPT_GUIDELINES_SUFFIX = [
@@ -34020,7 +34854,7 @@ function buildToolPromptGuidelines(options) {
34020
34854
  return [
34021
34855
  ...options.includeWebSearch ? ["Prefer agent_browser_web_search for facts; agent_browser for pages."] : TOOL_PROMPT_GUIDELINES_PREFIX,
34022
34856
  ...options.docs ? [buildInstalledDocsGuideline(options.docs)] : [],
34023
- ...RUNTIME_PROMPT_GUIDELINES,
34857
+ ...options.compact ? RUNTIME_PROMPT_GUIDELINES : [],
34024
34858
  ...browserExecutablePathGuideline !== void 0 ? [browserExecutablePathGuideline] : [],
34025
34859
  ...browserDefaultProfileGuideline !== void 0 ? [browserDefaultProfileGuideline] : [],
34026
34860
  TOOL_PROMPT_GUIDELINES_SUFFIX[0]
@@ -36376,11 +37210,13 @@ function sleepWithAbort(ms, signal) {
36376
37210
  });
36377
37211
  }
36378
37212
  var WebSearchRequestGate = class {
37213
+ // A performance.now() timestamp (0 before the first request), so a system clock step neither
37214
+ // skips nor extends the spacing between requests.
36379
37215
  lastRequestStartedAt = 0;
36380
37216
  tail = Promise.resolve();
36381
37217
  run(signal, task) {
36382
37218
  const runTask = async () => {
36383
- const elapsedMs = this.lastRequestStartedAt === 0 ? WEB_SEARCH_MIN_REQUEST_INTERVAL_MS : Date.now() - this.lastRequestStartedAt;
37219
+ const elapsedMs = this.lastRequestStartedAt === 0 ? WEB_SEARCH_MIN_REQUEST_INTERVAL_MS : performance.now() - this.lastRequestStartedAt;
36384
37220
  const waitMs = Math.max(0, WEB_SEARCH_MIN_REQUEST_INTERVAL_MS - elapsedMs);
36385
37221
  if (waitMs > 0) {
36386
37222
  await sleepWithAbort(waitMs, signal);
@@ -36388,7 +37224,7 @@ var WebSearchRequestGate = class {
36388
37224
  if (signal?.aborted === true) {
36389
37225
  signal.throwIfAborted();
36390
37226
  }
36391
- this.lastRequestStartedAt = Date.now();
37227
+ this.lastRequestStartedAt = performance.now();
36392
37228
  return task();
36393
37229
  };
36394
37230
  const result = this.tail.then(runTask, runTask);
@@ -36761,7 +37597,7 @@ function createAgentBrowserWebSearchTool(configState, options = {}) {
36761
37597
 
36762
37598
  // dist/extensions/agent-browser/lib/bash-guard.js
36763
37599
  init_parsing();
36764
- import { readFile as readFile4 } from "node:fs/promises";
37600
+ import { readFile as readFile5 } from "node:fs/promises";
36765
37601
  import { join as join14 } from "node:path";
36766
37602
  var DIRECT_AGENT_BROWSER_BASH_BYPASS_ENV = "PI_AGENT_BROWSER_ALLOW_DIRECT_BASH";
36767
37603
  var DIRECT_AGENT_BROWSER_EXECUTABLE_PATTERN = /^(?:[.~]|\.\.?|\/)?(?:[^\s;&|]+\/)?agent-browser$/;
@@ -36934,7 +37770,7 @@ function isTruthyEnvValue(value) {
36934
37770
  }
36935
37771
  async function isPackageDevelopmentCwd(cwd) {
36936
37772
  try {
36937
- const packageJson = JSON.parse(await readFile4(join14(cwd, "package.json"), "utf8"));
37773
+ const packageJson = JSON.parse(await readFile5(join14(cwd, "package.json"), "utf8"));
36938
37774
  return isRecord(packageJson) && packageJson.name === PACKAGE_NAME;
36939
37775
  } catch {
36940
37776
  return false;
@@ -36945,6 +37781,16 @@ async function isDirectAgentBrowserBashAllowed(cwd) {
36945
37781
  }
36946
37782
 
36947
37783
  // dist/extensions/agent-browser/lib/orchestration/extension-prompt.js
37784
+ var COMPACT_FALLBACK_ENV = "AGENT_BROWSER_COMPACT_FALLBACK";
37785
+ var PLAYBOOK_MODE_ENV = "AGENT_BROWSER_PLAYBOOK";
37786
+ function isCompactFallbackEnabled() {
37787
+ const compactEnv = process.env[COMPACT_FALLBACK_ENV]?.trim().toLowerCase();
37788
+ if (compactEnv === "1" || compactEnv === "true" || compactEnv === "yes") {
37789
+ return true;
37790
+ }
37791
+ const playbookEnv = process.env[PLAYBOOK_MODE_ENV]?.trim().toLowerCase();
37792
+ return playbookEnv === "compact" || playbookEnv === "minimal" || playbookEnv === "0" || playbookEnv === "false";
37793
+ }
36948
37794
  function isBashToolCallEvent(event) {
36949
37795
  return isRecord(event) && event.toolName === "bash" && isRecord(event.input) && typeof event.input.command === "string";
36950
37796
  }
@@ -36981,6 +37827,7 @@ function shouldIncludeProjectConfig(ctx, argv = process.argv) {
36981
37827
  }
36982
37828
  var BrowserPrompt = class {
36983
37829
  pi;
37830
+ docsPaths;
36984
37831
  state;
36985
37832
  webSearchToolRegistered = false;
36986
37833
  constructor(pi) {
@@ -37018,7 +37865,7 @@ var BrowserPrompt = class {
37018
37865
  });
37019
37866
  this.webSearchToolRegistered = true;
37020
37867
  }
37021
- instructions(ctx) {
37868
+ instructions(ctx, options) {
37022
37869
  const config = loadAgentBrowserConfigSync({
37023
37870
  cwd: ctx.cwd,
37024
37871
  includeProjectConfig: shouldIncludeProjectConfig(ctx)
@@ -37031,18 +37878,22 @@ var BrowserPrompt = class {
37031
37878
 
37032
37879
  Project agent_browser config guidance:
37033
37880
  ${guidance.map((line) => `- ${line}`).join("\n")}` : "";
37881
+ const compact = !(options?.forceFull ?? false) && isCompactFallbackEnabled();
37034
37882
  const guidelines = [
37035
37883
  ...buildToolPromptGuidelines({
37884
+ compact,
37036
37885
  browserDefaultProfile: config.trustedBrowserDefaultProfile,
37037
37886
  browserExecutablePath: config.trustedBrowserExecutablePath,
37038
37887
  includeWebSearch: this.webSearchToolRegistered,
37039
- docs: getInstalledDocsPaths()
37888
+ docs: this.docsPaths ??= getInstalledDocsPaths()
37040
37889
  }),
37041
- ...QUICK_START_GUIDELINES,
37042
- ...SHARED_BROWSER_PLAYBOOK_GUIDELINES,
37043
- ...WRAPPER_TAB_RECOVERY_BEHAVIOR,
37044
- ...Object.values(ADVANCED_TOOL_PROMPT_GUIDELINES).flat(),
37045
- ...this.webSearchToolRegistered ? WEB_SEARCH_TOOL_PROMPT_GUIDELINES : []
37890
+ ...compact ? [] : [
37891
+ ...QUICK_START_GUIDELINES,
37892
+ ...SHARED_BROWSER_PLAYBOOK_GUIDELINES,
37893
+ ...WRAPPER_TAB_RECOVERY_BEHAVIOR,
37894
+ ...Object.values(ADVANCED_TOOL_PROMPT_GUIDELINES).flat(),
37895
+ ...this.webSearchToolRegistered ? WEB_SEARCH_TOOL_PROMPT_GUIDELINES : []
37896
+ ]
37046
37897
  ];
37047
37898
  return `${PROJECT_RULE_PROMPT}
37048
37899
 
@@ -37064,7 +37915,7 @@ ${[...new Set(guidelines)].map((line) => `- ${line}`).join("\n")}${configPrompt}
37064
37915
  "agent_browser_web_search",
37065
37916
  ...Object.values(AGENT_BROWSER_TOOL_INVENTORY).map(({ name }) => name)
37066
37917
  ],
37067
- instructions: (ctx) => this.instructions(ctx)
37918
+ instructions: (ctx) => this.instructions(ctx, { forceFull: true })
37068
37919
  }
37069
37920
  ]);
37070
37921
  setManaged(() => {
@@ -37237,10 +38088,9 @@ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
37237
38088
  retireOutcomeOwner(sessions, outcome, details, current);
37238
38089
  }
37239
38090
  async function closeOwnedManagedSessionsExcept(sessions, pages, settings, options) {
37240
- const keepKey = getSessionContextKey(options.keepSessionName, options.keepNamespace);
37241
38091
  const owners = Array.from(sessions);
37242
38092
  for (const [key, owner] of owners) {
37243
- if (key === keepKey) {
38093
+ if (options.keepKeys.has(key)) {
37244
38094
  continue;
37245
38095
  }
37246
38096
  const error = await closeManagedSession({
@@ -38421,8 +39271,8 @@ init_managed_session_daemon_policy();
38421
39271
 
38422
39272
  // dist/extensions/agent-browser/lib/input-modes/script.js
38423
39273
  init_types();
38424
- import { existsSync as existsSync4 } from "node:fs";
38425
- import { dirname as dirname13, join as join27 } from "node:path";
39274
+ import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
39275
+ import { dirname as dirname14, join as join28 } from "node:path";
38426
39276
  import { fileURLToPath as fileURLToPath2 } from "node:url";
38427
39277
  import which2 from "which";
38428
39278
 
@@ -38955,19 +39805,19 @@ function resolveScriptChildNodePath(options) {
38955
39805
  return nodePath;
38956
39806
  }
38957
39807
  function resolveScriptWorkerPath() {
38958
- let currentDir = dirname13(fileURLToPath2(import.meta.url));
38959
- while (!existsSync4(join27(currentDir, "package.json"))) {
38960
- const parentDir = dirname13(currentDir);
39808
+ let currentDir = dirname14(fileURLToPath2(import.meta.url));
39809
+ while (!existsSync4(join28(currentDir, "package.json"))) {
39810
+ const parentDir = dirname14(currentDir);
38961
39811
  if (parentDir === currentDir) {
38962
39812
  throw new Error("Unable to resolve the pi-agent-browser-native package root.");
38963
39813
  }
38964
39814
  currentDir = parentDir;
38965
39815
  }
38966
- const workerPath = join27(currentDir, "dist", "extensions", "agent-browser", "script-worker.js");
39816
+ const workerPath = join28(currentDir, "dist", "extensions", "agent-browser", "script-worker.js");
38967
39817
  if (!existsSync4(workerPath)) {
38968
39818
  throw new Error("Compiled script worker is missing; run npm run build or reinstall pi-agent-browser-native.");
38969
39819
  }
38970
- return workerPath;
39820
+ return realpathSync5(workerPath);
38971
39821
  }
38972
39822
  function createAgentBrowserScriptCloseArgs(sessionName) {
38973
39823
  return ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, "close"];
@@ -39821,6 +40671,21 @@ function restoreAttachedSessionKeysFromBranch(branch) {
39821
40671
  }
39822
40672
 
39823
40673
  // dist/extensions/agent-browser/lib/orchestration/extension-branch-restore.js
40674
+ function isActiveOnBranch(events, key) {
40675
+ const active = events.managedSessionActiveRanks.get(key);
40676
+ const closed = events.managedSessionCloseRanks.get(key);
40677
+ return active !== void 0 && (closed === void 0 || closed < active);
40678
+ }
40679
+ function* restorableBranchManagedIdentities(events, baseName) {
40680
+ for (const [key, identity] of events.managedSessionActiveIdentities) {
40681
+ if (isActiveOnBranch(events, key) && isRestorableManagedSessionName(identity.sessionName, baseName)) {
40682
+ yield [key, identity];
40683
+ }
40684
+ }
40685
+ }
40686
+ function getOwnerKey(sessionName, namespace) {
40687
+ return getSessionContextKey(sessionName, typeof namespace === "string" ? namespace : void 0) ?? sessionName;
40688
+ }
39824
40689
  async function readBranchForRestore(runtime, ctx, resetRuntimeOwnership) {
39825
40690
  runtime.branch.beginRestore(ctx.sessionManager.getSessionId());
39826
40691
  runtime.prompt.restore(ctx);
@@ -39924,7 +40789,7 @@ var BrowserBranchRestoration = class {
39924
40789
  }
39925
40790
  }
39926
40791
  restoreManagedOwners() {
39927
- for (const [sessionKey, identity] of this.branchResourceEvents.managedSessionActiveIdentities) {
40792
+ for (const [sessionKey, identity] of restorableBranchManagedIdentities(this.branchResourceEvents, this.runtime.managed.baseName)) {
39928
40793
  this.restoreBranchManagedOwner(sessionKey, identity);
39929
40794
  }
39930
40795
  if (this.restoredState.active) {
@@ -39939,14 +40804,9 @@ var BrowserBranchRestoration = class {
39939
40804
  }
39940
40805
  }
39941
40806
  sessionActiveOnBranch(key) {
39942
- const active = this.branchResourceEvents.managedSessionActiveRanks.get(key);
39943
- const closed = this.branchResourceEvents.managedSessionCloseRanks.get(key);
39944
- return active !== void 0 && (closed === void 0 || closed < active);
40807
+ return isActiveOnBranch(this.branchResourceEvents, key);
39945
40808
  }
39946
40809
  restoreBranchManagedOwner(key, identity) {
39947
- if (!this.sessionActiveOnBranch(key) || !isRestorableManagedSessionName(identity.sessionName, this.runtime.managed.baseName)) {
39948
- return;
39949
- }
39950
40810
  const launch = this.branchResourceEvents.managedSessionLaunchState.get(key);
39951
40811
  trackOwnedManagedSession(this.runtime.managed.owned, identity.sessionName, launch?.cwd ?? this.ctx.cwd, {
39952
40812
  branchOwned: true,
@@ -40047,6 +40907,24 @@ async function restoreBranchBackedState(runtime, ctx, options) {
40047
40907
  const input = await readBranchForRestore(runtime, ctx, options.resetRuntimeOwnership);
40048
40908
  return new BrowserBranchRestoration(runtime, ctx, input).run();
40049
40909
  }
40910
+ async function readTransferableManagedSessionKeys(runtime, ctx) {
40911
+ const branch = await readBrowserEntries(ctx.sessionManager);
40912
+ const { baseName } = runtime.managed;
40913
+ const keys = /* @__PURE__ */ new Set();
40914
+ for (const [key] of restorableBranchManagedIdentities(collectBranchManagedResourceEvents(branch), baseName)) {
40915
+ keys.add(key);
40916
+ }
40917
+ const current = restoreManagedSessionStateFromBranch([...branch], baseName);
40918
+ if (current.active) {
40919
+ keys.add(getOwnerKey(current.sessionName, current.namespace));
40920
+ }
40921
+ for (const begin of collectPendingOwnedBrowserIntents(branch, runtime.branch.ownerSessionId)) {
40922
+ if (typeof begin.state.sessionName === "string") {
40923
+ keys.add(getOwnerKey(begin.state.sessionName, begin.state.namespace));
40924
+ }
40925
+ }
40926
+ return keys;
40927
+ }
40050
40928
 
40051
40929
  // dist/extensions/agent-browser/lib/orchestration/extension-lifecycle.js
40052
40930
  var BrowserLifecycle = class {
@@ -40076,8 +40954,22 @@ var BrowserLifecycle = class {
40076
40954
  await this.runtime.code.settle();
40077
40955
  await this.runtime.managedSessionExecutionQueue.run(() => this.runtime.artifacts.queue.run(() => this.restore(ctx, false)));
40078
40956
  }
40079
- async cleanup(ctx, quitting) {
40957
+ async retainedManagedSessionKeys(ctx, scope) {
40958
+ const { managed } = this.runtime;
40959
+ if (scope === "quit") {
40960
+ return /* @__PURE__ */ new Set();
40961
+ }
40962
+ if (scope === "restart" && ctx) {
40963
+ try {
40964
+ return await readTransferableManagedSessionKeys(this.runtime, ctx);
40965
+ } catch {
40966
+ }
40967
+ }
40968
+ return managed.active ? /* @__PURE__ */ new Set([getSessionContextKey(managed.name, managed.namespace) ?? managed.name]) : /* @__PURE__ */ new Set();
40969
+ }
40970
+ async cleanup(ctx, scope) {
40080
40971
  const { runtime } = this;
40972
+ const quitting = scope === "quit";
40081
40973
  const active = getActiveElectronRecords(runtime.electron.records);
40082
40974
  const preserved = quitting ? active.filter((record) => !runtime.electron.ownedRecords.has(record.launchId)).map((record) => record.userDataDir) : active.map((record) => record.userDataDir);
40083
40975
  const records = quitting ? runtime.electron.ownedRecords : getOffBranchOwnedElectronLaunchRecords(runtime.electron.ownedRecords, runtime.electron.records);
@@ -40102,8 +40994,7 @@ var BrowserLifecycle = class {
40102
40994
  restore: runtime.managed.restore,
40103
40995
  timeoutMs: runtime.implicitSessionCloseTimeoutMs
40104
40996
  }, {
40105
- keepSessionName: !quitting && runtime.managed.active ? runtime.managed.name : void 0,
40106
- keepNamespace: !quitting && runtime.managed.active ? runtime.managed.namespace : void 0,
40997
+ keepKeys: await this.retainedManagedSessionKeys(ctx, scope),
40107
40998
  onClosed: (owner) => runtime.recordings.retire(owner.sessionName, owner.namespace)
40108
40999
  });
40109
41000
  runtime.recordings.flush();
@@ -40118,22 +41009,25 @@ var BrowserLifecycle = class {
40118
41009
  runtime.recordings.resetIfClean();
40119
41010
  runtime.electron.reset();
40120
41011
  }
40121
- async shutdown(ctx, quitting) {
41012
+ async shutdown(ctx, scope) {
40122
41013
  await this.runtime.code.settle();
40123
41014
  this.runtime.branch.invalidate();
40124
- const preservePaths = await this.runtime.managedSessionExecutionQueue.run(() => this.runtime.artifacts.queue.run(() => this.cleanup(ctx, quitting)));
41015
+ const preservePaths = await this.runtime.managedSessionExecutionQueue.run(() => this.runtime.artifacts.queue.run(() => this.cleanup(ctx, scope)));
40125
41016
  this.reset();
40126
41017
  await cleanupSecureTempArtifacts({ preservePaths });
40127
41018
  }
40128
41019
  };
40129
- function isQuitting(event) {
40130
- return event?.reason === "quit";
41020
+ function getShutdownScope(event) {
41021
+ if (event?.reason !== "quit") {
41022
+ return "replace";
41023
+ }
41024
+ return event.disposition === "handoff" || event.disposition === "provisional" ? "restart" : "quit";
40131
41025
  }
40132
41026
  function registerBrowserLifecycle(runtime) {
40133
41027
  const lifecycle = new BrowserLifecycle(runtime);
40134
41028
  runtime.pi.on("session_start", (_event, ctx) => lifecycle.start(ctx));
40135
41029
  runtime.pi.on("session_tree", (_event, ctx) => lifecycle.tree(ctx));
40136
- runtime.pi.on("session_shutdown", (event, ctx) => lifecycle.shutdown(ctx, isQuitting(event)));
41030
+ runtime.pi.on("session_shutdown", (event, ctx) => lifecycle.shutdown(ctx, getShutdownScope(event)));
40137
41031
  }
40138
41032
 
40139
41033
  // dist/extensions/agent-browser/lib/orchestration/extension-tool-boundary.js
@@ -40264,14 +41158,14 @@ init_process_environment();
40264
41158
  init_runtime();
40265
41159
  import { createHash as createHash6 } from "node:crypto";
40266
41160
  import { homedir as homedir4 } from "node:os";
40267
- import { join as join28, resolve as resolve13 } from "node:path";
41161
+ import { join as join29, resolve as resolve13 } from "node:path";
40268
41162
 
40269
41163
  // dist/extensions/agent-browser/lib/orchestration/native-config.js
40270
41164
  init_parsing();
40271
41165
  init_argv_grammar();
40272
41166
  init_process();
40273
41167
  init_envelope();
40274
- import { readFile as readFile9, rm as rm12 } from "node:fs/promises";
41168
+ import { readFile as readFile10, rm as rm12 } from "node:fs/promises";
40275
41169
  var VALUE_DEFAULTS = [
40276
41170
  "executablePath",
40277
41171
  "profile",
@@ -40326,7 +41220,7 @@ function requestsLaunch(config, options) {
40326
41220
  }
40327
41221
  async function readConfigFile(path) {
40328
41222
  try {
40329
- const config = JSON.parse(await readFile9(path, "utf8"));
41223
+ const config = JSON.parse(await readFile10(path, "utf8"));
40330
41224
  if (!isRecord(config)) {
40331
41225
  return void 0;
40332
41226
  }
@@ -40819,7 +41713,16 @@ function cliValue2(args, flag) {
40819
41713
  function requestsConnection(tokens, stdin) {
40820
41714
  return tokens[0] === "connect" || getUpstreamEffectiveBatchSteps(tokens, stdin).some((step) => requestsConnection(step));
40821
41715
  }
40822
- function rootBrowserSessionName(rootSessionId) {
41716
+ function rootPiSessionId(sessionId) {
41717
+ const root = process.env.PI_INTERCOM_ROOT_SESSION_ID;
41718
+ if (root !== void 0 && root !== "" && root === root.trim() && root === process.env.PI_INTERCOM_PARENT_SESSION_ID) {
41719
+ return root;
41720
+ }
41721
+ const legacyRoot = process.env.PI_SUBAGENT_ROOT_SESSION_ID;
41722
+ return process.env.PI_SUBAGENT_CHILD === "1" && legacyRoot !== void 0 && legacyRoot !== "" ? legacyRoot : sessionId;
41723
+ }
41724
+ function rootBrowserSessionName(sessionId) {
41725
+ const rootSessionId = rootPiSessionId(sessionId);
40823
41726
  return `pi-root-${createHash6("sha256").update(rootSessionId).digest("hex").slice(0, 24)}`;
40824
41727
  }
40825
41728
  function nativeBooleanOverrides(args) {
@@ -40850,10 +41753,10 @@ var NativeSessionCall = class {
40850
41753
  const configArg = scanUpstreamGlobalFlagOccurrences([...input.toolArgs], "--config").at(0);
40851
41754
  this.configPath = configArg?.value ?? this.env.AGENT_BROWSER_CONFIG;
40852
41755
  this.configPaths = this.configPath !== void 0 ? [resolve13(context2.cwd, this.configPath)] : [
40853
- join28(homedir4(), ".agent-browser", "config.json"),
40854
- join28(context2.cwd, "agent-browser.json")
41756
+ join29(homedir4(), ".agent-browser", "config.json"),
41757
+ join29(context2.cwd, "agent-browser.json")
40855
41758
  ];
40856
- this.rootName = context2.root === void 0 ? void 0 : rootBrowserSessionName(context2.root.id);
41759
+ this.rootName = context2.root === void 0 ? void 0 : rootBrowserSessionName(context2.root.sessionId);
40857
41760
  this.browserCommand = input.kind !== "electron" && needsManagedSession(parseArgvDescriptor([...input.toolArgs]), input.toolStdin);
40858
41761
  const session = extractExplicitSessionName([...input.toolArgs]);
40859
41762
  this.rootFallback = context2.root !== void 0 && this.env.AGENT_BROWSER_SESSION === void 0 && (session === void 0 || session === this.rootName) && this.browserCommand;
@@ -40922,10 +41825,10 @@ var NativeSessionCall = class {
40922
41825
  };
40923
41826
  }
40924
41827
  booleanEnvironment(args) {
40925
- const debug = getBooleanFlagValue([...args], "--debug");
41828
+ const debug2 = getBooleanFlagValue([...args], "--debug");
40926
41829
  const noAutoDialog = getBooleanFlagValue([...args], "--no-auto-dialog");
40927
41830
  return {
40928
- ...debug !== void 0 ? { AGENT_BROWSER_DEBUG: debug ? "1" : void 0 } : {},
41831
+ ...debug2 !== void 0 ? { AGENT_BROWSER_DEBUG: debug2 ? "1" : void 0 } : {},
40929
41832
  ...noAutoDialog !== void 0 ? { AGENT_BROWSER_NO_AUTO_DIALOG: noAutoDialog ? "1" : "0" } : {}
40930
41833
  };
40931
41834
  }
@@ -40988,18 +41891,18 @@ async function withNativeSessionDefaults(input, context2, run) {
40988
41891
  // dist/extensions/agent-browser/lib/execution-cwd.js
40989
41892
  init_parsing();
40990
41893
  import { readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
40991
- import { dirname as dirname14, isAbsolute as isAbsolute11, join as join29 } from "node:path";
41894
+ import { dirname as dirname15, isAbsolute as isAbsolute11, join as join30 } from "node:path";
40992
41895
  function isDirectoryOwner(source) {
40993
41896
  if (/^(?:npm:pi-change-working-dir|git:github\.com\/fitchmultz\/pi-change-working-dir(?:\.git)?)(?:@.+)?$/.test(source.source)) {
40994
41897
  return true;
40995
41898
  }
40996
- const directories = [source.baseDir, isAbsolute11(source.path) ? dirname14(source.path) : void 0];
41899
+ const directories = [source.baseDir, isAbsolute11(source.path) ? dirname15(source.path) : void 0];
40997
41900
  return directories.some((directory) => {
40998
41901
  if (directory === void 0 || directory.length === 0 || !isAbsolute11(directory)) {
40999
41902
  return false;
41000
41903
  }
41001
41904
  try {
41002
- const manifest = JSON.parse(readFileSync6(join29(directory, "package.json"), "utf8"));
41905
+ const manifest = JSON.parse(readFileSync6(join30(directory, "package.json"), "utf8"));
41003
41906
  return isRecord(manifest) && manifest.name === "pi-change-working-dir";
41004
41907
  } catch {
41005
41908
  return false;
@@ -41053,8 +41956,8 @@ init_argv_grammar();
41053
41956
  init_artifact_manifest();
41054
41957
  init_runtime();
41055
41958
  init_text();
41056
- import { mkdir as mkdir5, readFile as readFile10, realpath as realpath3, stat as stat14, writeFile as writeFile3 } from "node:fs/promises";
41057
- import { dirname as dirname15, extname as extname7, isAbsolute as isAbsolute12, resolve as resolve14 } from "node:path";
41959
+ import { mkdir as mkdir5, readFile as readFile11, realpath as realpath3, stat as stat14, writeFile as writeFile3 } from "node:fs/promises";
41960
+ import { dirname as dirname16, extname as extname7, isAbsolute as isAbsolute12, resolve as resolve14 } from "node:path";
41058
41961
  function normalizeRequestedOutputPath(path) {
41059
41962
  return path.startsWith("@") ? path.slice(1) : path;
41060
41963
  }
@@ -41090,7 +41993,7 @@ async function readCompactedSpill(path, manifest) {
41090
41993
  if (path === void 0 || path.length === 0 || live !== true) {
41091
41994
  throw new Error("Full compacted output is unavailable from the wrapper-managed spill; outputPath was not written.");
41092
41995
  }
41093
- const text3 = await readFile10(path, "utf8");
41996
+ const text3 = await readFile11(path, "utf8");
41094
41997
  if (extname7(path) !== ".json") {
41095
41998
  return text3;
41096
41999
  }
@@ -41247,7 +42150,7 @@ async function saveOutput(result, destination, preserveText) {
41247
42150
  const payload = await getOutputPayload(result);
41248
42151
  const serialized = typeof payload.value === "string" ? payload.value : `${JSON.stringify(payload.value, null, 2)}
41249
42152
  `;
41250
- await mkdir5(dirname15(destination.absolutePath), { recursive: true });
42153
+ await mkdir5(dirname16(destination.absolutePath), { recursive: true });
41251
42154
  await writeFile3(destination.absolutePath, serialized, "utf8");
41252
42155
  const bytes = Buffer.byteLength(serialized, "utf8");
41253
42156
  const outputFile = {
@@ -41298,7 +42201,7 @@ init_batch_stdin();
41298
42201
  function parseRawArtifactSteps(commands) {
41299
42202
  const steps = [];
41300
42203
  for (const command of commands) {
41301
- if (command === "--bail") {
42204
+ if (isNativeBatchOption(command)) {
41302
42205
  continue;
41303
42206
  }
41304
42207
  const parsed = parseBatchCommandArgument(command);
@@ -41559,7 +42462,7 @@ var OperationPathBinding = class {
41559
42462
  }
41560
42463
  bindRawBatchRows() {
41561
42464
  for (let index = 1; index < this.tokens.length; index++) {
41562
- if (this.tokens[index] === "--bail") {
42465
+ if (isNativeBatchOption(this.tokens[index])) {
41563
42466
  continue;
41564
42467
  }
41565
42468
  const row = parseBatchCommandArgument(this.tokens[index]).step;
@@ -41592,7 +42495,7 @@ var OperationPathBinding = class {
41592
42495
  this.bindWebMcpParams();
41593
42496
  }
41594
42497
  bindBatch(stdin) {
41595
- if (this.tokens.slice(1).some((token) => token !== "--bail")) {
42498
+ if (this.tokens.slice(1).some((token) => !isNativeBatchOption(token))) {
41596
42499
  this.bindRawBatchRows();
41597
42500
  return { args: this.result, stdin };
41598
42501
  }
@@ -42062,7 +42965,7 @@ var ElectronHostCoordinator = class {
42062
42965
  return await withBrowserExecutionLocks({
42063
42966
  identities,
42064
42967
  signal: this.call.signal,
42065
- deadline: Date.now() + this.call.executionTimeoutMs,
42968
+ deadline: performance.now() + this.call.executionTimeoutMs,
42066
42969
  waitOnly: true
42067
42970
  }, (signal) => this.execute(signal));
42068
42971
  } catch (error) {
@@ -42636,6 +43539,11 @@ init_command_taxonomy();
42636
43539
  init_session_state();
42637
43540
  init_artifact_manifest();
42638
43541
  init_read_confirmation();
43542
+ function withoutCommand(step) {
43543
+ const visible = { ...step };
43544
+ delete visible.command;
43545
+ return visible;
43546
+ }
42639
43547
  var BrowserCommandResult = class {
42640
43548
  runtime;
42641
43549
  call;
@@ -42892,12 +43800,28 @@ var BrowserCommandResult = class {
42892
43800
  throw new Error("The selected Pi branch changed before browser-state confirmation.");
42893
43801
  }
42894
43802
  } catch (error) {
42895
- return this.persistenceFailure(error);
43803
+ return this.omitIndexedBatchCommands(this.persistenceFailure(error));
42896
43804
  }
42897
43805
  this.runtime.sessions.pages.applyBrowserRecord(finish);
42898
43806
  this.runtime.artifacts.manifest = applyArtifactChanges(this.runtime.artifacts.manifest, finish.event.artifacts);
42899
43807
  return void 0;
42900
43808
  }
43809
+ omitIndexedBatchCommands(result) {
43810
+ const details = result.details;
43811
+ const tokens = extractUpstreamCommandTokens(this.call.toolArgs);
43812
+ if (!isRecord(details) || tokens[0] !== "batch" || !tokens.includes("--omit-command") || !Array.isArray(details.batchSteps)) {
43813
+ return result;
43814
+ }
43815
+ const failure = details.batchFailure;
43816
+ return {
43817
+ ...result,
43818
+ details: {
43819
+ ...details,
43820
+ batchSteps: details.batchSteps.map((step) => isRecord(step) ? withoutCommand(step) : step),
43821
+ ...isRecord(failure) && isRecord(failure.failedStep) ? { batchFailure: { ...failure, failedStep: withoutCommand(failure.failedStep) } } : {}
43822
+ }
43823
+ };
43824
+ }
42901
43825
  async complete() {
42902
43826
  if (isRecord(this.result.details)) {
42903
43827
  this.result.details.browserEventVersion = 1;
@@ -42906,12 +43830,13 @@ var BrowserCommandResult = class {
42906
43830
  }
42907
43831
  this.result.details.artifactManifest = invocationArtifactManifest(this.call.branch.isCurrent() ? this.runtime.artifacts.manifest : this.prepared.browserRunState.artifactManifest, this.dispatch.initialArtifactManifest, this.result.details);
42908
43832
  }
42909
- return applyAgentBrowserOutputPath({
43833
+ const result = await applyAgentBrowserOutputPath({
42910
43834
  cwd: this.call.operationCwd,
42911
43835
  outputPath: this.call.outputPath,
42912
43836
  preserveTextContent: Array.isArray(this.call.params.args) && isBooleanFlagEnabled(this.call.params.args, "--json"),
42913
43837
  result: warnRecordingPersistence(this.runtime.recordings.dirty, this.result)
42914
43838
  });
43839
+ return this.omitIndexedBatchCommands(result);
42915
43840
  }
42916
43841
  };
42917
43842
 
@@ -43205,7 +44130,7 @@ var BrowserCommandCoordinator = class {
43205
44130
  }
43206
44131
  deadline() {
43207
44132
  const url = this.runtime.sessions.pages.get(this.key).tabTarget?.url;
43208
- return Date.now() + (this.call.params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(this.call.toolArgs), this.call.resolvedInput.toolStdin, url) ?? getAgentBrowserProcessTimeoutMs());
44133
+ return performance.now() + (this.call.params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(this.call.toolArgs), this.call.resolvedInput.toolStdin, url) ?? getAgentBrowserProcessTimeoutMs());
43209
44134
  }
43210
44135
  async runLocked() {
43211
44136
  if (!this.needsIdentityLock()) {
@@ -43378,11 +44303,9 @@ var BrowserAdmission = class {
43378
44303
  if (!this.canSelectNativeRoot(call)) {
43379
44304
  return void 0;
43380
44305
  }
43381
- const root = process.env.PI_SUBAGENT_ROOT_SESSION_ID;
43382
- const id = process.env.PI_SUBAGENT_CHILD === "1" && root !== void 0 && root !== "" ? root : call.ctx.sessionManager.getSessionId();
43383
44306
  const profile = config.trustedBrowserDefaultProfile;
43384
44307
  return {
43385
- id,
44308
+ sessionId: call.ctx.sessionManager.getSessionId(),
43386
44309
  profile: profile?.policy === "always" && !/[\\/~]/.test(profile.name) ? profile.name : void 0,
43387
44310
  executablePath: config.trustedBrowserExecutablePath
43388
44311
  };
@@ -43522,6 +44445,7 @@ var BrowserCodeExecution = class {
43522
44445
  runtime;
43523
44446
  invocation;
43524
44447
  operationCwd;
44448
+ /** A performance.now() timestamp bounding the whole cell, unaffected by wall-clock steps. */
43525
44449
  deadline;
43526
44450
  output = createBrowserCodeOutput();
43527
44451
  nativeEnvironment = getAgentBrowserProcessEnvironment();
@@ -43535,7 +44459,7 @@ var BrowserCodeExecution = class {
43535
44459
  this.runtime = runtime;
43536
44460
  this.invocation = invocation;
43537
44461
  this.operationCwd = operationCwd;
43538
- this.deadline = Date.now() + (invocation.params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS);
44462
+ this.deadline = performance.now() + (invocation.params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS);
43539
44463
  }
43540
44464
  start() {
43541
44465
  const { runtime, invocation } = this;
@@ -43579,10 +44503,8 @@ var BrowserCodeExecution = class {
43579
44503
  includeProjectConfig: shouldIncludeProjectConfig(invocation.ctx)
43580
44504
  });
43581
44505
  const profile = config.trustedBrowserDefaultProfile;
43582
- const root = process.env.PI_SUBAGENT_ROOT_SESSION_ID;
43583
- const id = process.env.PI_SUBAGENT_CHILD === "1" && root !== void 0 && root !== "" ? root : invocation.ctx.sessionManager.getSessionId();
43584
44506
  return {
43585
- id,
44507
+ sessionId: invocation.ctx.sessionManager.getSessionId(),
43586
44508
  profile: profile?.policy === "always" && !/[\\/~]/.test(profile.name) ? profile.name : void 0,
43587
44509
  executablePath: config.trustedBrowserExecutablePath
43588
44510
  };
@@ -43652,7 +44574,7 @@ var BrowserCodeExecution = class {
43652
44574
  params: {
43653
44575
  args,
43654
44576
  stdin: inner.stdin,
43655
- timeoutMs: Math.min(inner.timeoutMs ?? this.deadline - Date.now(), Math.max(1, this.deadline - Date.now()))
44577
+ timeoutMs: Math.min(inner.timeoutMs ?? this.remainingMs(), Math.max(1, this.remainingMs()))
43656
44578
  },
43657
44579
  signal: innerSignal,
43658
44580
  ctx,
@@ -43668,11 +44590,15 @@ var BrowserCodeExecution = class {
43668
44590
  }
43669
44591
  return this.output.observe(result);
43670
44592
  }
44593
+ /** Whole milliseconds left in the cell: inner calls and the script child take integer timeouts. */
44594
+ remainingMs() {
44595
+ return Math.ceil(this.deadline - performance.now());
44596
+ }
43671
44597
  async runCell(signal, identity) {
43672
44598
  const run = await runAgentBrowserScript({
43673
44599
  code: this.invocation.params.code,
43674
44600
  signal,
43675
- timeoutMs: Math.max(1, this.deadline - Date.now()),
44601
+ timeoutMs: Math.max(1, this.remainingMs()),
43676
44602
  emitImage: (image) => this.output.emitImage(image),
43677
44603
  dispatch: (inner, innerSignal) => this.dispatch(inner, innerSignal, identity)
43678
44604
  });
@@ -43724,7 +44650,7 @@ var BrowserCodeExecution = class {
43724
44650
  }
43725
44651
  async run() {
43726
44652
  this.start();
43727
- const timer = setTimeout(() => this.controller.abort(new DOMException("Browser code deadline exceeded.", "TimeoutError")), Math.max(0, this.deadline - Date.now()));
44653
+ const timer = setTimeout(() => this.controller.abort(new DOMException("Browser code deadline exceeded.", "TimeoutError")), Math.max(0, this.remainingMs()));
43728
44654
  try {
43729
44655
  const input = this.prepare();
43730
44656
  if (input.status === "invalid") {