pi-agent-browser-native 0.2.72 → 0.2.75
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.
- package/CHANGELOG.md +33 -0
- package/README.md +14 -12
- package/dist/extensions/agent-browser/index.js +148 -16
- package/dist/extensions/agent-browser/lib/argv-grammar.js +122 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +11 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
- package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
- package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +374 -0
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +629 -430
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +148 -57
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +30 -40
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
- package/dist/extensions/agent-browser/lib/playbook.js +9 -8
- package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
- package/dist/extensions/agent-browser/lib/process.js +271 -34
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +35 -12
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
- package/dist/extensions/agent-browser/lib/runtime.js +114 -107
- package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
- package/dist/extensions/agent-browser/lib/temp.js +13 -25
- package/docs/ARCHITECTURE.md +10 -9
- package/docs/COMMAND_REFERENCE.md +43 -23
- package/docs/ELECTRON.md +10 -10
- package/docs/RELEASE.md +3 -2
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +19 -18
- package/docs/TOOL_CONTRACT.md +29 -26
- package/docs/platform-smoke.md +2 -2
- package/package.json +1 -1
- package/platform-smoke.config.mjs +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +11 -3
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Purpose: Build safe, deterministic agent-browser invocations and persisted session state for the pi-agent-browser extension.
|
|
3
3
|
* Responsibilities: Validate raw tool arguments, derive extension-managed session names from the pi session identity, restore managed-session state from persisted tool details, redact sensitive invocation text, classify browser-oriented prompts, and build the effective CLI argument list passed to the upstream agent-browser binary.
|
|
4
|
-
* Scope:
|
|
4
|
+
* Scope: Runtime-planning helpers only; no subprocess execution or filesystem access.
|
|
5
5
|
* Usage: Imported by the extension entrypoint and unit tests before spawning the upstream CLI.
|
|
6
6
|
* Invariants/Assumptions: The wrapper stays thin, preserves upstream command vocabulary, keeps plain-text inspection stateless,
|
|
7
|
-
* and only injects wrapper-owned flags: `--json`, an extension-managed `--session` when appropriate,
|
|
8
|
-
*
|
|
7
|
+
* and only injects wrapper-owned flags: `--json`, an extension-managed `--session` when appropriate, the narrow
|
|
8
|
+
* site-specific headless compatibility `--user-agent` when that workaround applies.
|
|
9
9
|
*/
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
11
|
import { basename } from "node:path";
|
|
12
12
|
import { findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
|
|
13
|
-
import { GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, PREVALIDATED_VALUE_FLAGS,
|
|
13
|
+
import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, isBooleanFlagEnabled, PREVALIDATED_VALUE_FLAGS, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
|
|
14
14
|
import { needsManagedSession } from "./command-policy.js";
|
|
15
|
+
import { isWrapperManagedSessionName, redactManagedSessionRestoreKeys } from "./managed-session-capabilities.js";
|
|
15
16
|
import { isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
16
|
-
import { LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL } from "./launch-scoped-flags.js";
|
|
17
|
+
import { hasLaunchScopedFlagToken, LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL, } from "./launch-scoped-flags.js";
|
|
18
|
+
import { MANAGED_SESSION_NAME_PREFIX, } from "./managed-session-restore.js";
|
|
17
19
|
export { extractCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo } from "./argv-descriptor.js";
|
|
18
20
|
import { isRecord } from "./parsing.js";
|
|
19
21
|
const OPENAI_HEADLESS_COMPAT_HOSTS = new Set(["chat.com", "chat.openai.com", "chatgpt.com"]);
|
|
22
|
+
const CLOUDFLARE_HEADLESS_COMPAT_HOST = "dash.cloudflare.com";
|
|
20
23
|
const AGENT_BROWSER_IDLE_TIMEOUT_ENV = "AGENT_BROWSER_IDLE_TIMEOUT_MS";
|
|
21
24
|
const IMPLICIT_SESSION_IDLE_TIMEOUT_ENV = "PI_AGENT_BROWSER_IMPLICIT_SESSION_IDLE_TIMEOUT_MS";
|
|
22
25
|
const IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV = "PI_AGENT_BROWSER_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS";
|
|
@@ -228,7 +231,7 @@ function redactEnvSecretAssignments(text) {
|
|
|
228
231
|
});
|
|
229
232
|
}
|
|
230
233
|
export function redactSensitiveText(text) {
|
|
231
|
-
return redactEmbeddedStructuredText(redactEnvSecretAssignments(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlUserinfo(redactLooseUrlMatches(text)))
|
|
234
|
+
return redactEmbeddedStructuredText(redactEnvSecretAssignments(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlUserinfo(redactLooseUrlMatches(redactManagedSessionRestoreKeys(text))))
|
|
232
235
|
.replace(/\b(Authorization\s*:\s*Basic)\s+[^\s",]+/gi, "$1 [REDACTED]")
|
|
233
236
|
.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n\r"]+/gi, "$1: [REDACTED]"))));
|
|
234
237
|
}
|
|
@@ -322,11 +325,30 @@ export function getImplicitSessionIdleTimeoutMs(env = process.env) {
|
|
|
322
325
|
parseTimeoutMs(env[AGENT_BROWSER_IDLE_TIMEOUT_ENV], 0) ??
|
|
323
326
|
DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS;
|
|
324
327
|
}
|
|
328
|
+
function countExplicitGlobalFlags(args, targetFlag) {
|
|
329
|
+
return scanUpstreamGlobalFlagOccurrences(args, targetFlag).length;
|
|
330
|
+
}
|
|
331
|
+
function getUnsupportedLeadingIdentityAssignment(args) {
|
|
332
|
+
const commandStartIndex = findCommandStartIndex(args) ?? args.length;
|
|
333
|
+
for (let index = 0; index < commandStartIndex; index += 1) {
|
|
334
|
+
const token = args[index];
|
|
335
|
+
if (token.startsWith("--session="))
|
|
336
|
+
return "--session";
|
|
337
|
+
if (token.startsWith("--namespace="))
|
|
338
|
+
return "--namespace";
|
|
339
|
+
const flag = token.split("=", 1)[0] ?? token;
|
|
340
|
+
if (!token.includes("=") && PREVALIDATED_VALUE_FLAGS.has(flag))
|
|
341
|
+
index += 1;
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
325
345
|
export function getImplicitSessionCloseTimeoutMs(env = process.env) {
|
|
326
346
|
return parseTimeoutMs(env[IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV], 0) ?? DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS;
|
|
327
347
|
}
|
|
328
348
|
export function resolveManagedSessionState(options) {
|
|
329
|
-
const { command, managedSessionName,
|
|
349
|
+
const { command, managedSessionName, priorActive, priorSessionName, succeeded } = options;
|
|
350
|
+
const managedSessionNamespace = canonicalizeAgentBrowserNamespace(options.managedSessionNamespace);
|
|
351
|
+
const priorNamespace = canonicalizeAgentBrowserNamespace(options.priorNamespace);
|
|
330
352
|
if (!managedSessionName) {
|
|
331
353
|
return { active: priorActive, ...(priorNamespace ? { namespace: priorNamespace } : {}), sessionName: priorSessionName };
|
|
332
354
|
}
|
|
@@ -391,6 +413,7 @@ function getElectronCleanupClosedManagedSessionNames(details, fallbackSessionNam
|
|
|
391
413
|
return [...closedSessionNames];
|
|
392
414
|
}
|
|
393
415
|
export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName) {
|
|
416
|
+
const restoreDisabledIdentities = new Map();
|
|
394
417
|
let restoredState = {
|
|
395
418
|
active: false,
|
|
396
419
|
sessionName: fallbackSessionName,
|
|
@@ -400,6 +423,7 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
400
423
|
let freshSessionOrdinal = 0;
|
|
401
424
|
const freshSessionRanks = new Map();
|
|
402
425
|
const applyManagedClose = (sessionName, namespace) => {
|
|
426
|
+
namespace = canonicalizeAgentBrowserNamespace(namespace);
|
|
403
427
|
const restoreRank = getManagedSessionRestoreRank({
|
|
404
428
|
fallbackSessionName,
|
|
405
429
|
freshSessionRanks,
|
|
@@ -431,7 +455,7 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
431
455
|
}
|
|
432
456
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
433
457
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
434
|
-
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
458
|
+
const namespace = canonicalizeAgentBrowserNamespace(typeof details.namespace === "string" ? details.namespace : undefined);
|
|
435
459
|
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
436
460
|
const usedImplicitSession = details.usedImplicitSession === true;
|
|
437
461
|
const command = typeof details.command === "string" ? details.command : parseCommandInfo(args).command;
|
|
@@ -447,6 +471,11 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
447
471
|
const explicitCloseSessionName = commandClosesSession && explicitSessionName && restorableDetailSessionName === explicitSessionName
|
|
448
472
|
? restorableDetailSessionName
|
|
449
473
|
: undefined;
|
|
474
|
+
// Sticky restore policy is session-identity state and must apply even for explicit
|
|
475
|
+
// `--session <current-managed>` rows that are not used for managed-session lifecycle replay.
|
|
476
|
+
if (details.managedSessionRestoreDisabled === true && typeof sessionName === "string") {
|
|
477
|
+
restoreDisabledIdentities.set(getAgentBrowserSessionIdentityKey(sessionName, namespace), { namespace, sessionName });
|
|
478
|
+
}
|
|
450
479
|
const managedSessionName = !explicitSessionName &&
|
|
451
480
|
restorableDetailSessionName &&
|
|
452
481
|
(usedImplicitSession || sessionMode === "fresh")
|
|
@@ -472,8 +501,10 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
472
501
|
const outcomeRepresentsActiveCurrentSession = outcomeActiveAfter && outcomeCurrentSessionName === managedSessionName && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged");
|
|
473
502
|
const succeeded = outcomeRepresentsActiveCurrentSession ? true : messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
|
|
474
503
|
if (commandClosesSession) {
|
|
475
|
-
if (succeeded)
|
|
504
|
+
if (succeeded) {
|
|
505
|
+
restoreDisabledIdentities.delete(getAgentBrowserSessionIdentityKey(managedSessionName, namespace));
|
|
476
506
|
applyManagedClose(managedSessionName, namespace);
|
|
507
|
+
}
|
|
477
508
|
continue;
|
|
478
509
|
}
|
|
479
510
|
const staleCompletion = succeeded && restoreRank < activeRestoreRank;
|
|
@@ -498,6 +529,7 @@ export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName
|
|
|
498
529
|
...restoredState,
|
|
499
530
|
...(closedSessionName ? { closedSessionName } : {}),
|
|
500
531
|
freshSessionOrdinal,
|
|
532
|
+
managedSessionRestoreDisabledIdentities: [...restoreDisabledIdentities.values()],
|
|
501
533
|
};
|
|
502
534
|
}
|
|
503
535
|
export function createEphemeralSessionSeed() {
|
|
@@ -515,13 +547,13 @@ export function createImplicitSessionName(sessionId, cwd, ephemeralSeed) {
|
|
|
515
547
|
const cwdHash = createCwdHash(cwd);
|
|
516
548
|
const stableSessionId = sessionId?.replace(/-/g, "").slice(0, SESSION_NAME_SESSION_ID_LENGTH);
|
|
517
549
|
if (stableSessionId && stableSessionId.length > 0) {
|
|
518
|
-
return
|
|
550
|
+
return `${MANAGED_SESSION_NAME_PREFIX}${slug}-${stableSessionId}-${cwdHash}`;
|
|
519
551
|
}
|
|
520
552
|
const digest = createHash("sha256")
|
|
521
553
|
.update(`ephemeral:${cwd}:${ephemeralSeed}`)
|
|
522
554
|
.digest("hex")
|
|
523
555
|
.slice(0, SESSION_NAME_SESSION_ID_LENGTH);
|
|
524
|
-
return
|
|
556
|
+
return `${MANAGED_SESSION_NAME_PREFIX}${slug}-${digest}-${cwdHash}`;
|
|
525
557
|
}
|
|
526
558
|
export function createFreshSessionName(baseSessionName, ephemeralSeed, ordinal) {
|
|
527
559
|
const suffix = createHash("sha256")
|
|
@@ -623,21 +655,6 @@ function getFlagValue(args, flag) {
|
|
|
623
655
|
}
|
|
624
656
|
return undefined;
|
|
625
657
|
}
|
|
626
|
-
function isBooleanFlagEnabled(args, flag) {
|
|
627
|
-
for (const [index, token] of args.entries()) {
|
|
628
|
-
if (token === flag) {
|
|
629
|
-
const nextToken = args[index + 1]?.trim().toLowerCase();
|
|
630
|
-
if (nextToken === "false") {
|
|
631
|
-
return false;
|
|
632
|
-
}
|
|
633
|
-
return true;
|
|
634
|
-
}
|
|
635
|
-
if (token.startsWith(`${flag}=`)) {
|
|
636
|
-
return token.slice(flag.length + 1).trim().toLowerCase() !== "false";
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return false;
|
|
640
|
-
}
|
|
641
658
|
function normalizeComparableUrl(url) {
|
|
642
659
|
const normalizedUrl = url.trim();
|
|
643
660
|
if (normalizedUrl.length === 0) {
|
|
@@ -683,90 +700,44 @@ function parseComparableNavigationUrl(url) {
|
|
|
683
700
|
}
|
|
684
701
|
}
|
|
685
702
|
}
|
|
686
|
-
function getDefaultHeadlessCompatUserAgent(platform = process.platform) {
|
|
703
|
+
export function getDefaultHeadlessCompatUserAgent(platform = process.platform) {
|
|
687
704
|
return DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM[platform] ?? FALLBACK_HEADLESS_COMPAT_USER_AGENT;
|
|
688
705
|
}
|
|
689
|
-
function
|
|
690
|
-
if (
|
|
691
|
-
return
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
return undefined;
|
|
695
|
-
}
|
|
696
|
-
if (isBooleanFlagEnabled(args, "--headed")) {
|
|
697
|
-
return undefined;
|
|
698
|
-
}
|
|
699
|
-
if (hasFlagToken(args, "--cdp") || hasFlagToken(args, "--provider") || hasFlagToken(args, "-p") || isBooleanFlagEnabled(args, "--auto-connect")) {
|
|
700
|
-
return undefined;
|
|
701
|
-
}
|
|
706
|
+
export function canUseHeadlessCompatibilityUserAgent(args) {
|
|
707
|
+
if (hasFlagToken(args, "--user-agent") || isBooleanFlagEnabled(args, "--headed"))
|
|
708
|
+
return false;
|
|
709
|
+
if (hasFlagToken(args, "--cdp") || hasFlagToken(args, "--provider") || hasFlagToken(args, "-p") || isBooleanFlagEnabled(args, "--auto-connect"))
|
|
710
|
+
return false;
|
|
702
711
|
const engine = getFlagValue(args, "--engine");
|
|
703
|
-
|
|
712
|
+
return !engine || engine === "chrome";
|
|
713
|
+
}
|
|
714
|
+
function getCompatibilityWorkaround(args, commandInfo) {
|
|
715
|
+
if (!commandInfo.command || !isOpenNavigationCommand(commandInfo.command) || !commandInfo.subcommand || !canUseHeadlessCompatibilityUserAgent(args))
|
|
704
716
|
return undefined;
|
|
705
|
-
}
|
|
706
717
|
const parsedTargetUrl = parseComparableNavigationUrl(commandInfo.subcommand);
|
|
707
|
-
if (!parsedTargetUrl || !["http:", "https:"].includes(parsedTargetUrl.protocol))
|
|
718
|
+
if (!parsedTargetUrl || !["http:", "https:"].includes(parsedTargetUrl.protocol))
|
|
708
719
|
return undefined;
|
|
709
|
-
}
|
|
710
720
|
const hostname = parsedTargetUrl.hostname.toLowerCase();
|
|
711
|
-
if (
|
|
712
|
-
return
|
|
721
|
+
if (hostname === CLOUDFLARE_HEADLESS_COMPAT_HOST) {
|
|
722
|
+
return {
|
|
723
|
+
id: "cloudflare-headless-user-agent",
|
|
724
|
+
reason: "Cloudflare Dashboard challenges the default headless Chrome user agent; inject a normal Chrome user agent so authenticated headless browsing reaches the dashboard instead of Turnstile.",
|
|
725
|
+
};
|
|
713
726
|
}
|
|
727
|
+
if (!OPENAI_HEADLESS_COMPAT_HOSTS.has(hostname))
|
|
728
|
+
return undefined;
|
|
714
729
|
return {
|
|
715
730
|
id: "chatgpt-headless-user-agent",
|
|
716
731
|
reason: "OpenAI web properties currently challenge the default headless Chrome user agent; inject a normal Chrome user agent to preserve the default headless workflow without requiring headed mode or auto-connect.",
|
|
717
732
|
};
|
|
718
733
|
}
|
|
719
|
-
export function extractExplicitSessionName(args) {
|
|
720
|
-
for (const [index, token] of args.entries()) {
|
|
721
|
-
if (token === "--session") {
|
|
722
|
-
return args[index + 1];
|
|
723
|
-
}
|
|
724
|
-
if (token.startsWith("--session=")) {
|
|
725
|
-
return token.slice("--session=".length);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return undefined;
|
|
729
|
-
}
|
|
730
|
-
export function extractExplicitNamespace(args) {
|
|
731
|
-
for (const [index, token] of args.entries()) {
|
|
732
|
-
if (token === "--namespace") {
|
|
733
|
-
return args[index + 1];
|
|
734
|
-
}
|
|
735
|
-
if (token.startsWith("--namespace=")) {
|
|
736
|
-
return token.slice("--namespace=".length);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
return undefined;
|
|
740
|
-
}
|
|
741
734
|
function stripExplicitNamespaceArgs(args) {
|
|
742
|
-
const
|
|
743
|
-
for (
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
index += 1;
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
if (token.startsWith("--namespace="))
|
|
750
|
-
continue;
|
|
751
|
-
stripped.push(token);
|
|
735
|
+
const namespaceTokenIndexes = new Set();
|
|
736
|
+
for (const occurrence of scanUpstreamGlobalFlagOccurrences(args, "--namespace")) {
|
|
737
|
+
namespaceTokenIndexes.add(occurrence.index);
|
|
738
|
+
namespaceTokenIndexes.add(occurrence.index + 1);
|
|
752
739
|
}
|
|
753
|
-
return
|
|
754
|
-
}
|
|
755
|
-
function hasLaunchScopedFlagToken(args, flag) {
|
|
756
|
-
const commandStartIndex = findCommandStartIndex(args);
|
|
757
|
-
const command = commandStartIndex === undefined ? undefined : args[commandStartIndex];
|
|
758
|
-
return args.some((token, index) => {
|
|
759
|
-
if (token !== flag && !token.startsWith(`${flag}=`))
|
|
760
|
-
return false;
|
|
761
|
-
if (flag === "--auto-connect")
|
|
762
|
-
return isBooleanFlagEnabled(args, flag);
|
|
763
|
-
if (flag === "--restore" && token === "--restore" && optionalGlobalValueFlagConsumesNext(flag, args[index + 1]))
|
|
764
|
-
return true;
|
|
765
|
-
if (flag === "--state" && command === "wait" && commandStartIndex !== undefined && index > commandStartIndex) {
|
|
766
|
-
return false;
|
|
767
|
-
}
|
|
768
|
-
return true;
|
|
769
|
-
});
|
|
740
|
+
return args.filter((_token, index) => !namespaceTokenIndexes.has(index));
|
|
770
741
|
}
|
|
771
742
|
export function getStartupScopedFlags(args) {
|
|
772
743
|
return LAUNCH_SCOPED_FLAG_DEFINITIONS
|
|
@@ -775,14 +746,27 @@ export function getStartupScopedFlags(args) {
|
|
|
775
746
|
}
|
|
776
747
|
export function buildExecutionPlan(args, options) {
|
|
777
748
|
const invalidValueFlag = getInvalidValueFlagDetails(args);
|
|
749
|
+
const unsupportedIdentityAssignment = getUnsupportedLeadingIdentityAssignment(args);
|
|
750
|
+
const explicitNamespacePresent = scanUpstreamGlobalFlagOccurrences(args, "--namespace").length > 0;
|
|
778
751
|
const explicitNamespace = extractExplicitNamespace(args);
|
|
779
|
-
const
|
|
752
|
+
const managedSessionNamespace = canonicalizeAgentBrowserNamespace(options.managedSessionNamespace);
|
|
753
|
+
const startupScopedFlags = getStartupScopedFlags(args).filter((flag) => !(flag === "--namespace" && explicitNamespacePresent && explicitNamespace === managedSessionNamespace));
|
|
780
754
|
const plainTextInspection = isPlainTextInspectionArgs(args);
|
|
781
755
|
const argvDescriptor = parseArgvDescriptor(args);
|
|
782
756
|
const commandInfo = argvDescriptor.commandInfo;
|
|
783
757
|
const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
|
|
784
758
|
const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
|
|
785
759
|
let namespace = explicitNamespace;
|
|
760
|
+
if (plainTextInspection) {
|
|
761
|
+
return {
|
|
762
|
+
commandInfo,
|
|
763
|
+
effectiveArgs,
|
|
764
|
+
namespace,
|
|
765
|
+
plainTextInspection,
|
|
766
|
+
startupScopedFlags,
|
|
767
|
+
usedImplicitSession: false,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
786
770
|
if (invalidValueFlag) {
|
|
787
771
|
return {
|
|
788
772
|
commandInfo: {},
|
|
@@ -794,22 +778,38 @@ export function buildExecutionPlan(args, options) {
|
|
|
794
778
|
validationError: formatInvalidValueFlagError(invalidValueFlag),
|
|
795
779
|
};
|
|
796
780
|
}
|
|
797
|
-
if (
|
|
781
|
+
if (unsupportedIdentityAssignment) {
|
|
798
782
|
return {
|
|
799
|
-
commandInfo,
|
|
783
|
+
commandInfo: {},
|
|
800
784
|
effectiveArgs,
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
startupScopedFlags,
|
|
785
|
+
plainTextInspection: false,
|
|
786
|
+
startupScopedFlags: [],
|
|
804
787
|
usedImplicitSession: false,
|
|
788
|
+
validationError: `${unsupportedIdentityAssignment}=... is not supported by agent-browser 0.33.2. Pass ${unsupportedIdentityAssignment} and its value as separate arguments.`,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
for (const flag of ["--session", "--namespace"]) {
|
|
792
|
+
if (countExplicitGlobalFlags(args, flag) <= 1)
|
|
793
|
+
continue;
|
|
794
|
+
return {
|
|
795
|
+
commandInfo: {},
|
|
796
|
+
effectiveArgs,
|
|
797
|
+
plainTextInspection: false,
|
|
798
|
+
startupScopedFlags: [],
|
|
799
|
+
usedImplicitSession: false,
|
|
800
|
+
validationError: `Multiple ${flag} flags are not supported. Pass a single ${flag} value; upstream uses the last occurrence while this wrapper would otherwise mis-attribute managed-session ownership.`,
|
|
805
801
|
};
|
|
806
802
|
}
|
|
807
803
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
804
|
+
if (explicitSessionName && !isWrapperManagedSessionName(explicitSessionName)) {
|
|
805
|
+
namespace = resolveAgentBrowserNamespace(args, process.env.AGENT_BROWSER_NAMESPACE);
|
|
806
|
+
}
|
|
808
807
|
const shouldCreateFreshManagedSession = !explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
|
|
809
808
|
let argsToAppend = args;
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
809
|
+
const requestedCompatibilityWorkaround = getCompatibilityWorkaround(args, commandInfo);
|
|
810
|
+
let compatibilityWorkaround = requestedCompatibilityWorkaround;
|
|
811
|
+
if (explicitSessionName && explicitNamespacePresent) {
|
|
812
|
+
effectiveArgs.push("--namespace", explicitNamespace ?? "");
|
|
813
813
|
argsToAppend = stripExplicitNamespaceArgs(args);
|
|
814
814
|
}
|
|
815
815
|
let managedSessionName;
|
|
@@ -831,11 +831,11 @@ export function buildExecutionPlan(args, options) {
|
|
|
831
831
|
].join(" ");
|
|
832
832
|
}
|
|
833
833
|
else {
|
|
834
|
-
namespace = explicitNamespace
|
|
834
|
+
namespace = explicitNamespacePresent ? explicitNamespace : managedSessionNamespace;
|
|
835
835
|
if (namespace)
|
|
836
836
|
effectiveArgs.push("--namespace", namespace);
|
|
837
837
|
effectiveArgs.push("--session", options.managedSessionName);
|
|
838
|
-
if (
|
|
838
|
+
if (explicitNamespacePresent)
|
|
839
839
|
argsToAppend = stripExplicitNamespaceArgs(args);
|
|
840
840
|
managedSessionName = options.managedSessionName;
|
|
841
841
|
sessionName = options.managedSessionName;
|
|
@@ -846,11 +846,18 @@ export function buildExecutionPlan(args, options) {
|
|
|
846
846
|
if (namespace)
|
|
847
847
|
effectiveArgs.push("--namespace", namespace);
|
|
848
848
|
effectiveArgs.push("--session", options.freshSessionName);
|
|
849
|
-
if (
|
|
849
|
+
if (explicitNamespacePresent)
|
|
850
850
|
argsToAppend = stripExplicitNamespaceArgs(args);
|
|
851
851
|
managedSessionName = options.freshSessionName;
|
|
852
852
|
sessionName = options.freshSessionName;
|
|
853
853
|
}
|
|
854
|
+
if (!compatibilityWorkaround
|
|
855
|
+
&& canUseHeadlessCompatibilityUserAgent(args)
|
|
856
|
+
&& options.managedSessionActive
|
|
857
|
+
&& sessionName
|
|
858
|
+
&& getAgentBrowserSessionIdentityKey(sessionName, namespace) === getAgentBrowserSessionIdentityKey(options.managedSessionName, options.managedSessionNamespace)) {
|
|
859
|
+
compatibilityWorkaround = options.managedSessionCompatibilityWorkaround;
|
|
860
|
+
}
|
|
854
861
|
if (compatibilityWorkaround) {
|
|
855
862
|
effectiveArgs.push("--user-agent", getDefaultHeadlessCompatUserAgent());
|
|
856
863
|
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Usage: `index.ts` creates one store per Pi session lifecycle and records observations through update tokens.
|
|
6
6
|
* Invariants/Assumptions: One tool-call update token must govern all page-state observations from that invocation; stale overlapping updates must not overwrite newer state.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { getAgentBrowserSessionIdentityKey } from "./argv-grammar.js";
|
|
9
|
+
import { isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
|
|
9
10
|
import { isRecord } from "./parsing.js";
|
|
10
11
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
11
12
|
import { enrichSnapshotRefEntries, getSnapshotRefEntries } from "./results/snapshot-refs.js";
|
|
@@ -82,6 +83,9 @@ function extractBatchResultCommand(item) {
|
|
|
82
83
|
}
|
|
83
84
|
export function extractSessionTabTargetFromCommandData(commandTokens, data) {
|
|
84
85
|
const [command, subcommand] = commandTokens;
|
|
86
|
+
if (command === "get" && subcommand === "url") {
|
|
87
|
+
return normalizeSessionTabTarget({ url: extractStringResultField(data, "url") ?? extractStringResultField(data, "result") });
|
|
88
|
+
}
|
|
85
89
|
return isReadOnlyDiagnosticSessionTargetCommand(command, subcommand) ? undefined : extractSessionTabTargetFromData(data);
|
|
86
90
|
}
|
|
87
91
|
export function extractSessionTabTargetFromBatchResults(data) {
|
|
@@ -124,10 +128,12 @@ export function deriveSessionTabTarget(options) {
|
|
|
124
128
|
const commandDataTarget = isReadOnlyDiagnosticSessionTargetCommand(options.command, options.subcommand)
|
|
125
129
|
? undefined
|
|
126
130
|
: extractSessionTabTargetFromData(options.data);
|
|
127
|
-
|
|
128
|
-
extractSessionTabTargetFromBatchResults(options.data)
|
|
129
|
-
commandDataTarget
|
|
130
|
-
|
|
131
|
+
const observedTarget = normalizeSessionTabTarget(options.navigationSummary)
|
|
132
|
+
?? extractSessionTabTargetFromBatchResults(options.data)
|
|
133
|
+
?? commandDataTarget;
|
|
134
|
+
if (observedTarget || !isUnverifiedPageTransitionCommand(options.command, options.subcommand))
|
|
135
|
+
return observedTarget ?? options.previousTarget;
|
|
136
|
+
return undefined;
|
|
131
137
|
}
|
|
132
138
|
function batchContainsOnlyReadOnlyDiagnosticTargets(data) {
|
|
133
139
|
if (!Array.isArray(data) || data.length === 0) {
|
|
@@ -263,11 +269,12 @@ function getRestoredRefSnapshot(details) {
|
|
|
263
269
|
: undefined,
|
|
264
270
|
};
|
|
265
271
|
}
|
|
266
|
-
function getLatestTabTargetOrder(targets) {
|
|
272
|
+
function getLatestTabTargetOrder(targets, unknownTargets) {
|
|
267
273
|
let latestOrder = 0;
|
|
268
|
-
for (const target of targets.values())
|
|
274
|
+
for (const target of targets.values())
|
|
269
275
|
latestOrder = Math.max(latestOrder, target.order);
|
|
270
|
-
|
|
276
|
+
for (const order of unknownTargets.values())
|
|
277
|
+
latestOrder = Math.max(latestOrder, order);
|
|
271
278
|
return latestOrder;
|
|
272
279
|
}
|
|
273
280
|
function getLatestRefStateOrder(snapshots, invalidations) {
|
|
@@ -278,8 +285,8 @@ function getLatestRefStateOrder(snapshots, invalidations) {
|
|
|
278
285
|
latestOrder = Math.max(latestOrder, invalidation.order);
|
|
279
286
|
return latestOrder;
|
|
280
287
|
}
|
|
281
|
-
function shouldApplyTabTargetUpdate(current, updateOrder) {
|
|
282
|
-
return
|
|
288
|
+
function shouldApplyTabTargetUpdate(current, unknownOrder, updateOrder) {
|
|
289
|
+
return updateOrder >= Math.max(current?.order ?? 0, unknownOrder ?? 0);
|
|
283
290
|
}
|
|
284
291
|
function shouldApplyRefStateUpdate(options) {
|
|
285
292
|
const currentOrder = Math.max(options.currentSnapshot?.order ?? 0, options.currentInvalidation?.order ?? 0);
|
|
@@ -292,14 +299,13 @@ function stripRefSnapshotInvalidationOrder(invalidation) {
|
|
|
292
299
|
return invalidation ? { reason: invalidation.reason, summary: invalidation.summary } : undefined;
|
|
293
300
|
}
|
|
294
301
|
export function getSessionPageStateKey(sessionName, namespace) {
|
|
295
|
-
|
|
296
|
-
return undefined;
|
|
297
|
-
return namespace ? `${namespace}\u0000${sessionName}` : sessionName;
|
|
302
|
+
return sessionName ? getAgentBrowserSessionIdentityKey(sessionName, namespace) : undefined;
|
|
298
303
|
}
|
|
299
304
|
export class SessionPageState {
|
|
300
305
|
refSnapshotInvalidations = new Map();
|
|
301
306
|
refSnapshots = new Map();
|
|
302
307
|
tabPinningReasons = new Map();
|
|
308
|
+
tabTargetUnknownOrders = new Map();
|
|
303
309
|
tabTargets = new Map();
|
|
304
310
|
updateOrder = 0;
|
|
305
311
|
static fromBranch(branch) {
|
|
@@ -327,13 +333,23 @@ export class SessionPageState {
|
|
|
327
333
|
continue;
|
|
328
334
|
}
|
|
329
335
|
const tabTarget = getRestoredSessionTabTarget(details, command, subcommand);
|
|
336
|
+
const tabTargetUnknown = details.sessionTabTargetUnknown === true;
|
|
330
337
|
const refSnapshotInvalidation = getRestoredRefSnapshotInvalidation(details, command);
|
|
331
338
|
const refSnapshot = refSnapshotInvalidation ? undefined : getRestoredRefSnapshot(details);
|
|
332
|
-
if (!tabTarget && !refSnapshotInvalidation && !refSnapshot)
|
|
339
|
+
if (!tabTarget && !tabTargetUnknown && !refSnapshotInvalidation && !refSnapshot)
|
|
333
340
|
continue;
|
|
334
341
|
restoredOrder += 1;
|
|
335
|
-
if (
|
|
342
|
+
if (tabTargetUnknown) {
|
|
343
|
+
state.refSnapshotInvalidations.delete(sessionKey);
|
|
344
|
+
state.refSnapshots.delete(sessionKey);
|
|
345
|
+
state.tabTargets.delete(sessionKey);
|
|
346
|
+
state.tabTargetUnknownOrders.set(sessionKey, restoredOrder);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (tabTarget) {
|
|
350
|
+
state.tabTargetUnknownOrders.delete(sessionKey);
|
|
336
351
|
state.tabTargets.set(sessionKey, { order: restoredOrder, target: tabTarget });
|
|
352
|
+
}
|
|
337
353
|
if (refSnapshotInvalidation) {
|
|
338
354
|
state.refSnapshots.delete(sessionKey);
|
|
339
355
|
state.refSnapshotInvalidations.set(sessionKey, { ...refSnapshotInvalidation, order: restoredOrder });
|
|
@@ -343,7 +359,7 @@ export class SessionPageState {
|
|
|
343
359
|
state.refSnapshots.set(sessionKey, { ...refSnapshot, order: restoredOrder });
|
|
344
360
|
}
|
|
345
361
|
}
|
|
346
|
-
state.updateOrder = Math.max(restoredOrder, getLatestTabTargetOrder(state.tabTargets), getLatestRefStateOrder(state.refSnapshots, state.refSnapshotInvalidations));
|
|
362
|
+
state.updateOrder = Math.max(restoredOrder, getLatestTabTargetOrder(state.tabTargets, state.tabTargetUnknownOrders), getLatestRefStateOrder(state.refSnapshots, state.refSnapshotInvalidations));
|
|
347
363
|
state.tabPinningReasons = new Map([...state.tabTargets.keys()].map((sessionName) => [sessionName, "restore"]));
|
|
348
364
|
return state;
|
|
349
365
|
}
|
|
@@ -355,6 +371,7 @@ export class SessionPageState {
|
|
|
355
371
|
this.refSnapshotInvalidations = new Map();
|
|
356
372
|
this.refSnapshots = new Map();
|
|
357
373
|
this.tabPinningReasons = new Map();
|
|
374
|
+
this.tabTargetUnknownOrders = new Map();
|
|
358
375
|
this.tabTargets = new Map();
|
|
359
376
|
this.updateOrder = 0;
|
|
360
377
|
}
|
|
@@ -365,14 +382,16 @@ export class SessionPageState {
|
|
|
365
382
|
pinningReason: this.tabPinningReasons.get(sessionName),
|
|
366
383
|
refSnapshot: stripRefSnapshotOrder(this.refSnapshots.get(sessionName)),
|
|
367
384
|
refSnapshotInvalidation: stripRefSnapshotInvalidationOrder(this.refSnapshotInvalidations.get(sessionName)),
|
|
385
|
+
...(this.tabTargetUnknownOrders.has(sessionName) ? { tabTargetUnknown: true } : {}),
|
|
368
386
|
tabTarget: this.tabTargets.get(sessionName)?.target,
|
|
369
387
|
};
|
|
370
388
|
}
|
|
371
389
|
applyTabTarget(options) {
|
|
372
390
|
const current = this.tabTargets.get(options.sessionName);
|
|
373
|
-
if (!shouldApplyTabTargetUpdate(current, options.update)) {
|
|
391
|
+
if (!shouldApplyTabTargetUpdate(current, this.tabTargetUnknownOrders.get(options.sessionName), options.update)) {
|
|
374
392
|
return { ...this.get(options.sessionName), applied: false, stale: true };
|
|
375
393
|
}
|
|
394
|
+
this.tabTargetUnknownOrders.delete(options.sessionName);
|
|
376
395
|
this.tabTargets.set(options.sessionName, { order: options.update, target: options.target });
|
|
377
396
|
return { ...this.get(options.sessionName), applied: true };
|
|
378
397
|
}
|
|
@@ -401,10 +420,22 @@ export class SessionPageState {
|
|
|
401
420
|
this.refSnapshotInvalidations.set(options.sessionName, { ...options.invalidation, order: options.update });
|
|
402
421
|
return { ...this.get(options.sessionName), applied: true };
|
|
403
422
|
}
|
|
423
|
+
markTabTargetUnknown(options) {
|
|
424
|
+
const current = this.tabTargets.get(options.sessionName);
|
|
425
|
+
if (!shouldApplyTabTargetUpdate(current, this.tabTargetUnknownOrders.get(options.sessionName), options.update))
|
|
426
|
+
return { ...this.get(options.sessionName), applied: false, stale: true };
|
|
427
|
+
this.refSnapshotInvalidations.delete(options.sessionName);
|
|
428
|
+
this.refSnapshots.delete(options.sessionName);
|
|
429
|
+
this.tabPinningReasons.delete(options.sessionName);
|
|
430
|
+
this.tabTargets.delete(options.sessionName);
|
|
431
|
+
this.tabTargetUnknownOrders.set(options.sessionName, options.update);
|
|
432
|
+
return { ...this.get(options.sessionName), applied: true };
|
|
433
|
+
}
|
|
404
434
|
clearSession(sessionName) {
|
|
405
435
|
this.refSnapshotInvalidations.delete(sessionName);
|
|
406
436
|
this.refSnapshots.delete(sessionName);
|
|
407
437
|
this.tabPinningReasons.delete(sessionName);
|
|
438
|
+
this.tabTargetUnknownOrders.delete(sessionName);
|
|
408
439
|
this.tabTargets.delete(sessionName);
|
|
409
440
|
}
|
|
410
441
|
markPinning(sessionName, reason) {
|
|
@@ -5,26 +5,24 @@
|
|
|
5
5
|
* Usage: Imported by result/process helpers when they need secure spill files instead of world-readable shared tmp paths.
|
|
6
6
|
* Invariants/Assumptions: Temp artifacts live under the OS temp directory, each active run uses a dedicated 0700 directory, files are created with exclusive 0600 permissions, session-scoped persisted artifacts stay under the pi session directory, and stale pruning only touches roots with an explicit pi-agent-browser ownership marker.
|
|
7
7
|
*/
|
|
8
|
-
import { execFile } from "node:child_process";
|
|
9
8
|
import { randomBytes } from "node:crypto";
|
|
10
9
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
11
10
|
import { chmod, mkdir, mkdtemp, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
12
11
|
import { tmpdir } from "node:os";
|
|
13
12
|
import { basename, dirname, join, resolve } from "node:path";
|
|
14
|
-
import { promisify } from "node:util";
|
|
15
13
|
import { isRecord, parsePositiveInteger } from "./parsing.js";
|
|
14
|
+
import { processStartIdentitiesMatch, readProcessStartIdentity } from "./process-identity.js";
|
|
16
15
|
const TEMP_ROOT_PREFIX = "pi-agent-browser-";
|
|
17
16
|
const TEMP_ROOT_MARKER_FILE_NAME = ".pi-agent-browser-owner.json";
|
|
18
17
|
const TEMP_ROOT_MARKER_KIND = "pi-agent-browser-temp-root";
|
|
19
|
-
const
|
|
18
|
+
const TEMP_ROOT_LEGACY_MARKER_VERSION = 1;
|
|
19
|
+
const TEMP_ROOT_MARKER_VERSION = 2;
|
|
20
20
|
const STALE_TEMP_ROOT_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
21
21
|
const TEMP_ROOT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_TEMP_ROOT_MAX_BYTES";
|
|
22
22
|
const DEFAULT_TEMP_ROOT_MAX_BYTES = 32 * 1_024 * 1_024;
|
|
23
23
|
const SESSION_ARTIFACT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES";
|
|
24
24
|
const DEFAULT_SESSION_ARTIFACT_MAX_BYTES = 32 * 1_024 * 1_024;
|
|
25
25
|
const SESSION_ARTIFACTS_ROOT_DIR_NAME = ".pi-agent-browser-artifacts";
|
|
26
|
-
const PROCESS_START_IDENTITY_TIMEOUT_MS = 1_000;
|
|
27
|
-
const execFileAsync = promisify(execFile);
|
|
28
26
|
let sessionTempRootPromise;
|
|
29
27
|
let exitCleanupRegistered = false;
|
|
30
28
|
let tempMutationQueue = Promise.resolve();
|
|
@@ -48,7 +46,7 @@ function isProtectedTempChildName(value) {
|
|
|
48
46
|
function isTempRootOwnershipRecord(value) {
|
|
49
47
|
if (!isRecord(value))
|
|
50
48
|
return false;
|
|
51
|
-
if (value.kind !== TEMP_ROOT_MARKER_KIND || value.version
|
|
49
|
+
if (value.kind !== TEMP_ROOT_MARKER_KIND || ![TEMP_ROOT_LEGACY_MARKER_VERSION, TEMP_ROOT_MARKER_VERSION].includes(value.version))
|
|
52
50
|
return false;
|
|
53
51
|
if (!isPositiveFiniteNumber(value.createdAtMs))
|
|
54
52
|
return false;
|
|
@@ -153,6 +151,7 @@ async function persistProtectedTempChildren(tempRoot, protectedChildren) {
|
|
|
153
151
|
...ownershipMarker,
|
|
154
152
|
leaseUpdatedAtMs: Date.now(),
|
|
155
153
|
protectedChildNames: childNames,
|
|
154
|
+
version: TEMP_ROOT_MARKER_VERSION,
|
|
156
155
|
});
|
|
157
156
|
}
|
|
158
157
|
async function getExistingProtectedChildren(tempRoot, protectedChildren) {
|
|
@@ -180,20 +179,7 @@ async function removeTempRootChildrenExcept(tempRoot, protectedChildren) {
|
|
|
180
179
|
}));
|
|
181
180
|
}
|
|
182
181
|
async function getProcessStartIdentity(pid) {
|
|
183
|
-
|
|
184
|
-
return undefined;
|
|
185
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
186
|
-
return undefined;
|
|
187
|
-
try {
|
|
188
|
-
const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
189
|
-
timeout: PROCESS_START_IDENTITY_TIMEOUT_MS,
|
|
190
|
-
});
|
|
191
|
-
const identity = stdout.trim().replace(/\s+/g, " ");
|
|
192
|
-
return identity || undefined;
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
182
|
+
return pid === undefined ? undefined : await readProcessStartIdentity(pid);
|
|
197
183
|
}
|
|
198
184
|
export async function writeSecureTempRootOwnershipMarker(tempRoot, options = {}) {
|
|
199
185
|
const createdAtMs = options.createdAtMs ?? Date.now();
|
|
@@ -219,10 +205,10 @@ async function refreshSecureTempRootLease(tempRoot) {
|
|
|
219
205
|
if (currentUid !== undefined && ownershipMarker.ownerUid !== undefined && ownershipMarker.ownerUid !== currentUid)
|
|
220
206
|
return;
|
|
221
207
|
const currentProcessStartIdentity = await getProcessStartIdentity(process.pid);
|
|
222
|
-
if (ownershipMarker.ownerProcessStartIdentity !== undefined &&
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
208
|
+
if (ownershipMarker.ownerProcessStartIdentity !== undefined && currentProcessStartIdentity !== undefined) {
|
|
209
|
+
const identitiesMatch = processStartIdentitiesMatch(ownershipMarker.ownerProcessStartIdentity, currentProcessStartIdentity);
|
|
210
|
+
if (identitiesMatch === false)
|
|
211
|
+
return;
|
|
226
212
|
}
|
|
227
213
|
const refreshedMarker = {
|
|
228
214
|
...ownershipMarker,
|
|
@@ -230,6 +216,7 @@ async function refreshSecureTempRootLease(tempRoot) {
|
|
|
230
216
|
ownerPid: process.pid,
|
|
231
217
|
ownerProcessStartIdentity: currentProcessStartIdentity ?? ownershipMarker.ownerProcessStartIdentity,
|
|
232
218
|
ownerUid: currentUid,
|
|
219
|
+
version: TEMP_ROOT_MARKER_VERSION,
|
|
233
220
|
};
|
|
234
221
|
await writeTempRootOwnershipMarkerRecord(tempRoot, refreshedMarker);
|
|
235
222
|
}
|
|
@@ -251,7 +238,8 @@ async function getMarkerOwnerLiveness(ownershipMarker) {
|
|
|
251
238
|
if (ownershipMarker.ownerProcessStartIdentity === undefined || currentProcessStartIdentity === undefined) {
|
|
252
239
|
return "unknown";
|
|
253
240
|
}
|
|
254
|
-
|
|
241
|
+
const identitiesMatch = processStartIdentitiesMatch(ownershipMarker.ownerProcessStartIdentity, currentProcessStartIdentity);
|
|
242
|
+
return identitiesMatch === undefined ? "unknown" : identitiesMatch ? "alive" : "dead";
|
|
255
243
|
}
|
|
256
244
|
async function pruneStaleTempRoots(currentTempRoot) {
|
|
257
245
|
const entries = await readdir(tmpdir(), { withFileTypes: true }).catch(() => []);
|