pi-agent-browser-native 0.2.77 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -0
- package/README.md +69 -35
- package/dist/extensions/agent-browser/index.js +911 -121
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
- package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/config.js +0 -6
- package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
- package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +27 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +22 -14
- 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 +148 -34
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/parsing.js +0 -7
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
- package/dist/extensions/agent-browser/lib/playbook.js +20 -24
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
- package/dist/extensions/agent-browser/lib/process.js +131 -48
- package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
- package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
- package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
- package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
- package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
- package/dist/extensions/agent-browser/lib/results/network.js +0 -7
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
- package/dist/extensions/agent-browser/lib/results/text.js +0 -7
- package/dist/extensions/agent-browser/lib/runtime.js +119 -52
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/temp.js +0 -7
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +0 -5
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +50 -31
- package/docs/COMMAND_REFERENCE.md +97 -41
- package/docs/ELECTRON.md +3 -19
- package/docs/RELEASE.md +8 -20
- package/docs/REQUIREMENTS.md +8 -6
- package/docs/SUPPORT_MATRIX.md +32 -25
- package/docs/TOOL_CONTRACT.md +107 -64
- package/docs/platform-smoke.md +1 -1
- package/package.json +7 -6
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +8 -7
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
- package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
- package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
- package/dist/extensions/agent-browser/lib/results.js +0 -14
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Register the native agent_browser tool for pi so agents can invoke agent-browser without going through bash.
|
|
3
|
-
* Responsibilities: Define the tool schema, inject thin wrapper behavior around the upstream CLI, manage extension-owned browser session convenience, and return pi-friendly content/details.
|
|
4
|
-
* Scope: Native tool registration and orchestration only; the wrapper intentionally stays close to the upstream agent-browser CLI.
|
|
5
|
-
* Usage: Loaded by pi through the package manifest in this package, or explicitly via `pi --no-extensions -e .` during local checkout development.
|
|
6
|
-
* Invariants/Assumptions: agent-browser is installed separately on PATH, the wrapper targets the current locally installed upstream version only, and no backward-compatibility shims are provided.
|
|
7
|
-
*/
|
|
8
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
2
|
import { dirname, join } from "node:path";
|
|
10
3
|
import { fileURLToPath } from "node:url";
|
|
11
4
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
+
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./lib/batch-lifecycle.js";
|
|
12
6
|
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
13
7
|
import { SessionPageState } from "./lib/session-page-state.js";
|
|
14
|
-
import { canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName,
|
|
15
|
-
import { extractExplicitNamespace, extractExplicitSessionName, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
|
|
8
|
+
import { canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, isPlainTextInspectionArgs, } from "./lib/runtime.js";
|
|
9
|
+
import { extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
|
|
10
|
+
import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
|
|
11
|
+
import { needsManagedSession } from "./lib/command-policy.js";
|
|
16
12
|
import { cleanupManagedSessionRestoreConfig, ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
|
|
17
13
|
import { isRecord } from "./lib/parsing.js";
|
|
14
|
+
import { runAgentBrowserProcess } from "./lib/process.js";
|
|
15
|
+
import { getAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
|
|
16
|
+
import { TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
|
|
18
17
|
import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
|
|
19
|
-
import { isCloseCommand } from "./lib/command-taxonomy.js";
|
|
18
|
+
import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
|
|
20
19
|
import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
|
|
21
20
|
import { cleanupSecureTempArtifacts } from "./lib/temp.js";
|
|
22
|
-
import { AGENT_BROWSER_PARAMS
|
|
21
|
+
import { AGENT_BROWSER_PARAMS } from "./lib/input-modes/params.js";
|
|
22
|
+
import { AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS, AGENT_BROWSER_SCRIPT_NAMESPACE, createAgentBrowserScriptSessionName, isAgentBrowserScriptSessionName, runAgentBrowserScript, } from "./lib/input-modes/script.js";
|
|
23
23
|
import { parseAllowedDomainsPolicyFromArgs } from "./lib/navigation-policy.js";
|
|
24
24
|
import { closeManagedSession, getSessionContextKey, runAgentBrowserTool } from "./lib/orchestration/browser-run/index.js";
|
|
25
|
+
import { canonicalizeExplicitArtifactDestination, getExplicitArtifactDestination } from "./lib/orchestration/browser-run/artifact-paths.js";
|
|
25
26
|
import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./lib/orchestration/browser-run/session-state.js";
|
|
26
|
-
import {
|
|
27
|
+
import { parseBatchCommandArgument, parseUserBatchStdin } from "./lib/orchestration/batch-stdin.js";
|
|
27
28
|
import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
|
|
28
29
|
import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
|
|
29
|
-
import { applyAgentBrowserOutputPath, getAgentBrowserOutputPathValidationError } from "./lib/orchestration/output-file.js";
|
|
30
|
-
import {
|
|
30
|
+
import { applyAgentBrowserOutputPath, getAgentBrowserOutputPathValidationError, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
|
|
31
|
+
import { appendScriptSessionLease, buildScriptBrowserEnvelope, buildScriptToolResult, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
|
|
32
|
+
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingCommand, isSessionArtifactManifest, mergeSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
|
|
33
|
+
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
|
|
31
34
|
import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
|
|
35
|
+
import { appendRecordingReservationTransition, applyRecordingArtifactsToReservations, restoreRecordingReservationStateFromBranch, retireRecordingReservation, } from "./lib/recording-reservations.js";
|
|
32
36
|
import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
|
|
33
37
|
import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
|
|
34
38
|
import { AgentBrowserResultComponent, buildAgentBrowserToolResultPatch, formatAgentBrowserRenderCall, formatAgentBrowserRenderResult, } from "./lib/pi-tool-rendering.js";
|
|
@@ -37,22 +41,94 @@ function isBashToolCallEvent(event) {
|
|
|
37
41
|
return false;
|
|
38
42
|
return typeof event.input.command === "string";
|
|
39
43
|
}
|
|
40
|
-
function
|
|
41
|
-
const commandTokens =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
function getArtifactCommandSteps(args, stdin) {
|
|
45
|
+
const commandTokens = extractUpstreamCommandTokens(args);
|
|
46
|
+
const batch = commandTokens[0] === "batch";
|
|
47
|
+
if (!batch)
|
|
48
|
+
return { batch, steps: commandTokens.length > 0 ? [commandTokens] : [] };
|
|
49
|
+
const steps = [];
|
|
50
|
+
for (const command of commandTokens.slice(1)) {
|
|
51
|
+
if (command === "--bail")
|
|
52
|
+
continue;
|
|
53
|
+
const parsed = parseBatchCommandArgument(command);
|
|
54
|
+
if (parsed.error || !parsed.step)
|
|
55
|
+
return { batch, error: `Unsupported batch step ${steps.length + 1}: ${parsed.error ?? "command could not be parsed safely"}`, steps };
|
|
56
|
+
steps.push(parsed.step);
|
|
44
57
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
// Upstream executes raw argument steps exclusively when any exist, so ignored
|
|
59
|
+
// stdin must not add artifact/lifecycle steps or fail this preflight.
|
|
60
|
+
if (steps.length > 0)
|
|
61
|
+
return { batch, steps };
|
|
62
|
+
const parsed = parseUserBatchStdin(stdin);
|
|
63
|
+
return parsed.error ? { batch, error: parsed.error, steps } : { batch, steps: parsed.steps ?? [] };
|
|
64
|
+
}
|
|
65
|
+
function getArtifactPreflightValidationError(options) {
|
|
66
|
+
const { batch, error, steps } = getArtifactCommandSteps(options.args, options.stdin);
|
|
67
|
+
if (error)
|
|
68
|
+
return error;
|
|
69
|
+
const activeRecordingDestinations = new Set();
|
|
70
|
+
const cleanupOnly = steps.length > 0 && steps.every((step) => {
|
|
71
|
+
const [command, subcommand] = extractUpstreamCommandTokens(step);
|
|
72
|
+
return isCloseCommand(command) || (command === "record" && subcommand === "stop");
|
|
73
|
+
});
|
|
74
|
+
for (const reservation of options.activeRecordingReservations ?? []) {
|
|
75
|
+
try {
|
|
76
|
+
activeRecordingDestinations.add(canonicalizeExplicitArtifactDestination(reservation.cwd, reservation.absolutePath));
|
|
77
|
+
}
|
|
78
|
+
catch (canonicalizationError) {
|
|
79
|
+
if (!cleanupOnly)
|
|
80
|
+
return canonicalizationError instanceof Error ? canonicalizationError.message : "An active recording destination could not be resolved safely.";
|
|
81
|
+
}
|
|
48
82
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
83
|
+
let canonicalOutputPath;
|
|
84
|
+
if (options.outputPath) {
|
|
85
|
+
try {
|
|
86
|
+
canonicalOutputPath = canonicalizeExplicitArtifactDestination(options.cwd, normalizeRequestedOutputPath(options.outputPath));
|
|
87
|
+
if (activeRecordingDestinations.has(canonicalOutputPath)) {
|
|
88
|
+
return `Unsupported outputPath: ${options.outputPath} is reserved by an active recording. Stop that recording first or use a distinct path.`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (canonicalizationError) {
|
|
92
|
+
return canonicalizationError instanceof Error ? canonicalizationError.message : `outputPath ${options.outputPath} could not be resolved safely.`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const artifactDestinations = new Map();
|
|
96
|
+
let sawBatchClose = false;
|
|
97
|
+
for (const [index, step] of steps.entries()) {
|
|
98
|
+
const commandStep = extractUpstreamCommandTokens(step);
|
|
99
|
+
if (batch) {
|
|
100
|
+
const stepValidationError = validateToolArgs(step);
|
|
101
|
+
if (stepValidationError)
|
|
102
|
+
return `Unsupported batch step ${index + 1}: ${stepValidationError}`;
|
|
103
|
+
if (sawBatchClose && commandStep[0] === "record" && (commandStep[1] === "start" || commandStep[1] === "restart")) {
|
|
104
|
+
return `Unsupported batch step ${index + 1}: record ${commandStep[1]} cannot follow close, quit, or exit in one upstream batch because upstream can report success without starting a recording. Split the close and recording into separate agent_browser calls.`;
|
|
105
|
+
}
|
|
106
|
+
if (isCloseCommand(commandStep[0]))
|
|
107
|
+
sawBatchClose = true;
|
|
108
|
+
}
|
|
109
|
+
const artifactDestination = getExplicitArtifactDestination(commandStep);
|
|
110
|
+
if (artifactDestination) {
|
|
111
|
+
let canonicalDestination;
|
|
112
|
+
try {
|
|
113
|
+
canonicalDestination = canonicalizeExplicitArtifactDestination(options.cwd, artifactDestination);
|
|
114
|
+
}
|
|
115
|
+
catch (canonicalizationError) {
|
|
116
|
+
return canonicalizationError instanceof Error ? canonicalizationError.message : `Artifact destination ${artifactDestination} could not be resolved safely.`;
|
|
117
|
+
}
|
|
118
|
+
if (canonicalOutputPath === canonicalDestination) {
|
|
119
|
+
return `Unsupported outputPath: ${options.outputPath} resolves to the same destination as artifact path ${artifactDestination}. Use distinct paths so the tool-result JSON cannot overwrite the browser artifact.`;
|
|
120
|
+
}
|
|
121
|
+
if (activeRecordingDestinations.has(canonicalDestination)) {
|
|
122
|
+
const prefix = batch ? `Unsupported batch artifact destination in step ${index + 1}` : "Unsupported artifact destination";
|
|
123
|
+
return `${prefix}: ${artifactDestination} is reserved by an active recording. Stop that recording first or use a distinct path.`;
|
|
124
|
+
}
|
|
125
|
+
const priorStep = artifactDestinations.get(canonicalDestination);
|
|
126
|
+
if (priorStep !== undefined) {
|
|
127
|
+
return `Unsupported batch artifact destination in step ${index + 1}: ${artifactDestination} is already written by step ${priorStep + 1}. Use distinct paths or split the batch so each artifact can be verified independently.`;
|
|
128
|
+
}
|
|
129
|
+
artifactDestinations.set(canonicalDestination, index);
|
|
130
|
+
}
|
|
131
|
+
if (batch && commandStep[0] === "screenshot" && step.includes("--annotate")) {
|
|
56
132
|
return [
|
|
57
133
|
`Unsupported batch screenshot annotation in step ${index + 1}: put --annotate in top-level args, not inside the batch step.`,
|
|
58
134
|
`Use: { "args": ["--annotate", "batch"], "stdin": "[[\\"screenshot\\",\\"/path/to/image.png\\"]]" }`,
|
|
@@ -61,6 +137,51 @@ function getBatchPreflightValidationError(args, stdin) {
|
|
|
61
137
|
}
|
|
62
138
|
return undefined;
|
|
63
139
|
}
|
|
140
|
+
function commandClosesAllSessions(args, stdin) {
|
|
141
|
+
const parsed = getArtifactCommandSteps(args, stdin);
|
|
142
|
+
return !parsed.error && parsed.steps.some((step) => isCloseAllCommand(extractUpstreamCommandTokens(step)));
|
|
143
|
+
}
|
|
144
|
+
function commandTouchesArtifactLifecycle(args, stdin, outputPath) {
|
|
145
|
+
if (outputPath)
|
|
146
|
+
return true;
|
|
147
|
+
const parsed = getArtifactCommandSteps(args, stdin);
|
|
148
|
+
if (parsed.error)
|
|
149
|
+
return true;
|
|
150
|
+
return parsed.steps.some((step) => {
|
|
151
|
+
const commandStep = extractUpstreamCommandTokens(step);
|
|
152
|
+
return getExplicitArtifactDestination(commandStep) !== undefined || commandStep[0] === "record" || commandStep[0] === "screenshot" || isCloseCommand(commandStep[0]);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function isResultFileArtifact(artifact) {
|
|
156
|
+
return isRecord(artifact)
|
|
157
|
+
&& typeof artifact.absolutePath === "string"
|
|
158
|
+
&& typeof artifact.kind === "string"
|
|
159
|
+
&& typeof artifact.path === "string";
|
|
160
|
+
}
|
|
161
|
+
function getResultFileArtifacts(result) {
|
|
162
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
163
|
+
return Array.isArray(details?.artifacts) ? details.artifacts.filter(isResultFileArtifact) : [];
|
|
164
|
+
}
|
|
165
|
+
function reportsNoRecordingInProgress(value) {
|
|
166
|
+
try {
|
|
167
|
+
return /no recording in progress/i.test(JSON.stringify(value));
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function batchStepReportsNoRecordingInProgress(step) {
|
|
174
|
+
if (!isRecord(step) || step.success !== false)
|
|
175
|
+
return false;
|
|
176
|
+
const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? extractUpstreamCommandTokens(step.command) : [];
|
|
177
|
+
return command[0] === "record" && command[1] === "stop" && reportsNoRecordingInProgress(step);
|
|
178
|
+
}
|
|
179
|
+
function resultReportsNoRecordingInProgress(result) {
|
|
180
|
+
if (result.isError !== true)
|
|
181
|
+
return false;
|
|
182
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
183
|
+
return details?.command === "record" && details.subcommand === "stop" && reportsNoRecordingInProgress(result.content);
|
|
184
|
+
}
|
|
64
185
|
function restoreArtifactManifestFromBranch(branch) {
|
|
65
186
|
let restoredManifest;
|
|
66
187
|
for (const entry of branch) {
|
|
@@ -76,6 +197,12 @@ function restoreArtifactManifestFromBranch(branch) {
|
|
|
76
197
|
}
|
|
77
198
|
return restoredManifest;
|
|
78
199
|
}
|
|
200
|
+
function getRecognizedCompatibilityWorkaround(value) {
|
|
201
|
+
const workaround = isRecord(value) ? value : undefined;
|
|
202
|
+
return (workaround?.id === "chatgpt-headless-user-agent" || workaround?.id === "cloudflare-headless-user-agent") && typeof workaround.reason === "string"
|
|
203
|
+
? { id: workaround.id, reason: workaround.reason }
|
|
204
|
+
: undefined;
|
|
205
|
+
}
|
|
79
206
|
function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionName, namespace) {
|
|
80
207
|
let restored;
|
|
81
208
|
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
@@ -88,12 +215,9 @@ function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionN
|
|
|
88
215
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
89
216
|
if (!details)
|
|
90
217
|
continue;
|
|
91
|
-
const workaround = isRecord(details.compatibilityWorkaround) ? details.compatibilityWorkaround : undefined;
|
|
92
218
|
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
93
219
|
continue;
|
|
94
|
-
const recognizedWorkaround = (
|
|
95
|
-
? { id: workaround.id, reason: workaround.reason }
|
|
96
|
-
: undefined;
|
|
220
|
+
const recognizedWorkaround = getRecognizedCompatibilityWorkaround(details.compatibilityWorkaround);
|
|
97
221
|
const succeeded = getSuccessfulToolResult(details, message);
|
|
98
222
|
const outcome = getManagedSessionOutcome(details);
|
|
99
223
|
const activeAfterFailure = recognizedWorkaround
|
|
@@ -112,6 +236,57 @@ function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionN
|
|
|
112
236
|
}
|
|
113
237
|
return restored;
|
|
114
238
|
}
|
|
239
|
+
function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionName, namespace) {
|
|
240
|
+
let restored = false;
|
|
241
|
+
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
242
|
+
for (const entry of branch) {
|
|
243
|
+
if (!isRecord(entry) || entry.type !== "message")
|
|
244
|
+
continue;
|
|
245
|
+
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
246
|
+
if (!message || message.toolName !== "agent_browser")
|
|
247
|
+
continue;
|
|
248
|
+
const details = isRecord(message.details) ? message.details : undefined;
|
|
249
|
+
if (!details)
|
|
250
|
+
continue;
|
|
251
|
+
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
252
|
+
continue;
|
|
253
|
+
const outcome = getManagedSessionOutcome(details);
|
|
254
|
+
const activeAfterFailure = outcome?.activeAfter === true
|
|
255
|
+
&& typeof outcome.currentSessionName === "string"
|
|
256
|
+
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
|
|
257
|
+
if ((getSuccessfulToolResult(details, message) || activeAfterFailure) && typeof details.managedSessionHeadedAutosaveDisabled === "boolean") {
|
|
258
|
+
restored = details.managedSessionHeadedAutosaveDisabled;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return restored;
|
|
262
|
+
}
|
|
263
|
+
function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionName, namespace) {
|
|
264
|
+
let restored;
|
|
265
|
+
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
266
|
+
for (const entry of branch) {
|
|
267
|
+
if (!isRecord(entry) || entry.type !== "message")
|
|
268
|
+
continue;
|
|
269
|
+
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
270
|
+
if (!message || message.toolName !== "agent_browser")
|
|
271
|
+
continue;
|
|
272
|
+
const details = isRecord(message.details) ? message.details : undefined;
|
|
273
|
+
if (!details)
|
|
274
|
+
continue;
|
|
275
|
+
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
276
|
+
continue;
|
|
277
|
+
const outcome = getManagedSessionOutcome(details);
|
|
278
|
+
const activeAfterFailure = outcome?.activeAfter === true
|
|
279
|
+
&& typeof outcome.currentSessionName === "string"
|
|
280
|
+
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
|
|
281
|
+
if (!getSuccessfulToolResult(details, message) && !activeAfterFailure)
|
|
282
|
+
continue;
|
|
283
|
+
if (typeof details.managedSessionHeadedAutosaveInterval === "string")
|
|
284
|
+
restored = details.managedSessionHeadedAutosaveInterval;
|
|
285
|
+
else if (details.managedSessionHeadedAutosaveDisabled === true)
|
|
286
|
+
restored = "0";
|
|
287
|
+
}
|
|
288
|
+
return restored;
|
|
289
|
+
}
|
|
115
290
|
function getToolResultArgs(details) {
|
|
116
291
|
if (Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string"))
|
|
117
292
|
return details.args;
|
|
@@ -119,13 +294,25 @@ function getToolResultArgs(details) {
|
|
|
119
294
|
return details.effectiveArgs;
|
|
120
295
|
return [];
|
|
121
296
|
}
|
|
122
|
-
function
|
|
297
|
+
function detailsReportCloseAllApplied(details, succeeded) {
|
|
298
|
+
const args = getToolResultArgs(details);
|
|
299
|
+
return details.closeAllApplied === true
|
|
300
|
+
|| (succeeded && isCloseAllCommand(extractUpstreamCommandTokens(args)))
|
|
301
|
+
|| batchHasSuccessfulCloseAll(details.batchSteps);
|
|
302
|
+
}
|
|
303
|
+
function deleteIdentityKeysInNamespace(entries, namespace) {
|
|
304
|
+
for (const key of entries.keys()) {
|
|
305
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
306
|
+
entries.delete(key);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function isAttachedBrowserInvocation(args, env = getAgentBrowserProcessEnvironment()) {
|
|
123
310
|
const autoConnectEnv = env.AGENT_BROWSER_AUTO_CONNECT;
|
|
124
|
-
return
|
|
311
|
+
return extractUpstreamCommandTokens(args)[0] === "connect"
|
|
125
312
|
|| hasLaunchScopedFlagToken(args, "--cdp")
|
|
126
313
|
|| hasLaunchScopedFlagToken(args, "--auto-connect")
|
|
127
314
|
|| env.AGENT_BROWSER_CDP !== undefined
|
|
128
|
-
|| (autoConnectEnv
|
|
315
|
+
|| isUpstreamEnvFlagEnabled(autoConnectEnv);
|
|
129
316
|
}
|
|
130
317
|
function restoreAttachedSessionKeysFromBranch(branch) {
|
|
131
318
|
const attachedSessionKeys = new Set();
|
|
@@ -140,15 +327,32 @@ function restoreAttachedSessionKeysFromBranch(branch) {
|
|
|
140
327
|
continue;
|
|
141
328
|
const managedSessionOutcome = isRecord(details.managedSessionOutcome) ? details.managedSessionOutcome : undefined;
|
|
142
329
|
const retainedFailedAttachment = details.attachedBrowserSession === true && managedSessionOutcome?.activeAfter === true;
|
|
143
|
-
|
|
144
|
-
|
|
330
|
+
const succeeded = getSuccessfulToolResult(details, message);
|
|
331
|
+
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
332
|
+
const terminalBatchClose = batchCloseLifecycle?.endsClosed === true;
|
|
145
333
|
const args = getToolResultArgs(details);
|
|
334
|
+
const namespace = typeof details.namespace === "string" ? details.namespace : extractExplicitNamespace(args);
|
|
146
335
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : extractExplicitSessionName(args);
|
|
336
|
+
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
337
|
+
const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
|
|
338
|
+
for (const cleanupResult of Array.isArray(cleanup?.results) ? cleanup.results : []) {
|
|
339
|
+
for (const identity of getCleanupResultClosedManagedSessionIdentities(cleanupResult, namespace)) {
|
|
340
|
+
attachedSessionKeys.delete(getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (detailsReportCloseAllApplied(details, succeeded)) {
|
|
344
|
+
deleteIdentityKeysInNamespace(attachedSessionKeys, namespace);
|
|
345
|
+
if (sessionName && details.attachedBrowserSession === true && batchCloseLifecycle?.endsClosed === false) {
|
|
346
|
+
attachedSessionKeys.add(getSessionContextKey(sessionName, namespace) ?? sessionName);
|
|
347
|
+
}
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (!succeeded && !retainedFailedAttachment && !terminalBatchClose)
|
|
351
|
+
continue;
|
|
147
352
|
if (!sessionName)
|
|
148
353
|
continue;
|
|
149
|
-
const namespace = typeof details.namespace === "string" ? details.namespace : extractExplicitNamespace(args);
|
|
150
354
|
const sessionKey = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
151
|
-
if (isCloseCommand(
|
|
355
|
+
if ((succeeded && isCloseCommand(extractUpstreamCommandTokens(args)[0])) || terminalBatchClose)
|
|
152
356
|
attachedSessionKeys.delete(sessionKey);
|
|
153
357
|
else if (details.attachedBrowserSession === true || isAttachedBrowserInvocation(args, {}))
|
|
154
358
|
attachedSessionKeys.add(sessionKey);
|
|
@@ -167,12 +371,15 @@ function restoreAllowedDomainsBySessionFromBranch(branch) {
|
|
|
167
371
|
if (!details)
|
|
168
372
|
continue;
|
|
169
373
|
const succeeded = getSuccessfulToolResult(details, message);
|
|
374
|
+
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
170
375
|
const args = getToolResultArgs(details);
|
|
171
|
-
const command = typeof details.command === "string" ? details.command :
|
|
376
|
+
const command = typeof details.command === "string" ? details.command : extractUpstreamCommandTokens(args)[0];
|
|
172
377
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
173
378
|
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
174
379
|
const sessionKey = getSessionContextKey(sessionName, namespace);
|
|
175
380
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
381
|
+
if (detailsReportCloseAllApplied(details, succeeded))
|
|
382
|
+
deleteIdentityKeysInNamespace(restoredPolicies, namespace);
|
|
176
383
|
const outcome = getManagedSessionOutcome(details);
|
|
177
384
|
const outcomeSucceeded = outcome?.succeeded === true;
|
|
178
385
|
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
@@ -189,7 +396,7 @@ function restoreAllowedDomainsBySessionFromBranch(branch) {
|
|
|
189
396
|
if (replacedSessionName)
|
|
190
397
|
restoredPolicies.delete(getSessionContextKey(replacedSessionName, replacedSessionNamespace) ?? replacedSessionName);
|
|
191
398
|
}
|
|
192
|
-
if (succeeded && isCloseCommand(command)) {
|
|
399
|
+
if ((succeeded && isCloseCommand(command)) || batchCloseLifecycle) {
|
|
193
400
|
const closedSessionName = explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName;
|
|
194
401
|
if (closedSessionName)
|
|
195
402
|
restoredPolicies.delete(getSessionContextKey(closedSessionName, namespace) ?? closedSessionName);
|
|
@@ -198,13 +405,14 @@ function restoreAllowedDomainsBySessionFromBranch(branch) {
|
|
|
198
405
|
const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
|
|
199
406
|
const cleanupResults = Array.isArray(cleanup?.results) ? cleanup.results : [];
|
|
200
407
|
for (const cleanupResult of cleanupResults) {
|
|
201
|
-
for (const
|
|
202
|
-
restoredPolicies.delete(
|
|
408
|
+
for (const identity of getCleanupResultClosedManagedSessionIdentities(cleanupResult, namespace)) {
|
|
409
|
+
restoredPolicies.delete(getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName);
|
|
410
|
+
}
|
|
203
411
|
}
|
|
204
412
|
const outcomeKeepsSessionCurrent = outcome?.activeAfter === true
|
|
205
413
|
&& (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")
|
|
206
414
|
&& outcomeCurrentSessionName === sessionName;
|
|
207
|
-
const policy = (succeeded || outcomeKeepsSessionCurrent) && sessionKey && !isCloseCommand(command) ? parseAllowedDomainsPolicyFromArgs(args) : undefined;
|
|
415
|
+
const policy = (succeeded || outcomeKeepsSessionCurrent) && sessionKey && !isCloseCommand(command) && batchCloseLifecycle?.endsClosed !== true ? parseAllowedDomainsPolicyFromArgs(args) : undefined;
|
|
208
416
|
if (policy && sessionKey)
|
|
209
417
|
restoredPolicies.set(sessionKey, policy);
|
|
210
418
|
}
|
|
@@ -216,7 +424,12 @@ function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
|
|
|
216
424
|
const key = getSessionContextKey(sessionName, options.namespace) ?? sessionName;
|
|
217
425
|
const existing = sessions.get(key);
|
|
218
426
|
const branchOwned = existing && !existing.branchOwned ? false : options.branchOwned === true;
|
|
219
|
-
|
|
427
|
+
const compatibilityWorkaround = Object.hasOwn(options, "compatibilityWorkaround")
|
|
428
|
+
? options.compatibilityWorkaround
|
|
429
|
+
: existing?.compatibilityWorkaround;
|
|
430
|
+
const headedManagedAutosaveDisabled = options.headedManagedAutosaveDisabled ?? existing?.headedManagedAutosaveDisabled;
|
|
431
|
+
const headedManagedAutosaveInterval = options.headedManagedAutosaveInterval ?? existing?.headedManagedAutosaveInterval;
|
|
432
|
+
sessions.set(key, { branchOwned, compatibilityWorkaround, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName });
|
|
220
433
|
}
|
|
221
434
|
function untrackOwnedManagedSession(sessions, sessionName, namespace) {
|
|
222
435
|
if (!sessionName)
|
|
@@ -245,16 +458,21 @@ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
|
|
|
245
458
|
const status = typeof outcome.status === "string" ? outcome.status : undefined;
|
|
246
459
|
const currentSessionName = typeof outcome.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
247
460
|
const attemptedSessionName = typeof outcome.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
|
|
461
|
+
const namespace = isRecord(details) && typeof details.namespace === "string" ? details.namespace : undefined;
|
|
248
462
|
if (outcome.activeAfter === true && (status === "created" || status === "replaced" || status === "unchanged")) {
|
|
249
|
-
|
|
250
|
-
|
|
463
|
+
trackOwnedManagedSession(sessions, currentSessionName, cwd, {
|
|
464
|
+
compatibilityWorkaround: getRecognizedCompatibilityWorkaround(details?.compatibilityWorkaround),
|
|
465
|
+
headedManagedAutosaveDisabled: details?.managedSessionHeadedAutosaveDisabled === true,
|
|
466
|
+
headedManagedAutosaveInterval: typeof details?.managedSessionHeadedAutosaveInterval === "string" ? details.managedSessionHeadedAutosaveInterval : undefined,
|
|
467
|
+
namespace,
|
|
468
|
+
});
|
|
251
469
|
}
|
|
252
470
|
if (succeeded && status === "closed") {
|
|
253
|
-
untrackOwnedManagedSession(sessions, attemptedSessionName ?? currentSessionName);
|
|
471
|
+
untrackOwnedManagedSession(sessions, attemptedSessionName ?? currentSessionName, namespace);
|
|
254
472
|
}
|
|
255
473
|
}
|
|
256
|
-
function getTouchedElectronLaunchIds(sessionName, records) {
|
|
257
|
-
const record = findElectronLaunchRecordForSession(sessionName, records);
|
|
474
|
+
function getTouchedElectronLaunchIds(sessionName, records, namespace) {
|
|
475
|
+
const record = findElectronLaunchRecordForSession(sessionName, records, namespace);
|
|
258
476
|
return record ? new Set([record.launchId]) : undefined;
|
|
259
477
|
}
|
|
260
478
|
function mergeActiveElectronLaunchRecords(target, source, options = {}) {
|
|
@@ -322,10 +540,10 @@ function getElectronHostLaunchRecordsForInput(options) {
|
|
|
322
540
|
}
|
|
323
541
|
return options.branchRecords;
|
|
324
542
|
}
|
|
325
|
-
function
|
|
543
|
+
function getCleanupResultClosedManagedSessionIdentities(result, fallbackNamespace) {
|
|
326
544
|
if (!isRecord(result) || !Array.isArray(result.steps))
|
|
327
545
|
return [];
|
|
328
|
-
const
|
|
546
|
+
const identities = new Map();
|
|
329
547
|
const record = isRecord(result.record) ? result.record : undefined;
|
|
330
548
|
for (const step of result.steps) {
|
|
331
549
|
if (!isRecord(step) || step.resource !== "managed-session")
|
|
@@ -335,24 +553,29 @@ function getCleanupResultClosedManagedSessionNames(result) {
|
|
|
335
553
|
const sessionName = typeof step.sessionName === "string"
|
|
336
554
|
? step.sessionName
|
|
337
555
|
: typeof record?.sessionName === "string" ? record.sessionName : undefined;
|
|
556
|
+
const namespace = typeof step.namespace === "string"
|
|
557
|
+
? step.namespace
|
|
558
|
+
: typeof record?.namespace === "string" ? record.namespace : fallbackNamespace;
|
|
338
559
|
if (sessionName)
|
|
339
|
-
|
|
560
|
+
identities.set(getSessionContextKey(sessionName, namespace) ?? sessionName, { namespace, sessionName });
|
|
340
561
|
}
|
|
341
|
-
return [...
|
|
562
|
+
return [...identities.values()];
|
|
342
563
|
}
|
|
343
|
-
function
|
|
344
|
-
const
|
|
564
|
+
function getCleanupResultsClosedManagedSessionIdentities(cleanupResults, fallbackNamespace) {
|
|
565
|
+
const identities = new Map();
|
|
345
566
|
for (const result of cleanupResults) {
|
|
346
|
-
for (const
|
|
347
|
-
|
|
567
|
+
for (const identity of getCleanupResultClosedManagedSessionIdentities(result, fallbackNamespace)) {
|
|
568
|
+
identities.set(getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName, identity);
|
|
569
|
+
}
|
|
348
570
|
}
|
|
349
|
-
return [...
|
|
571
|
+
return [...identities.values()];
|
|
350
572
|
}
|
|
351
573
|
function isElectronLaunchRecord(value) {
|
|
352
574
|
if (!isRecord(value))
|
|
353
575
|
return false;
|
|
354
576
|
return value.version === 1
|
|
355
577
|
&& value.launchedByWrapper === true
|
|
578
|
+
&& (value.namespace === undefined || typeof value.namespace === "string")
|
|
356
579
|
&& typeof value.launchId === "string"
|
|
357
580
|
&& typeof value.appName === "string"
|
|
358
581
|
&& typeof value.executablePath === "string"
|
|
@@ -382,10 +605,18 @@ function setBranchRankForString(map, value, rank) {
|
|
|
382
605
|
if (typeof value === "string" && value.length > 0)
|
|
383
606
|
map.set(value, rank);
|
|
384
607
|
}
|
|
608
|
+
function setBranchManagedSessionActive(events, sessionName, namespace, rank) {
|
|
609
|
+
if (typeof sessionName !== "string" || sessionName.length === 0)
|
|
610
|
+
return;
|
|
611
|
+
const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
612
|
+
events.managedSessionActiveIdentities.set(key, { namespace, sessionName });
|
|
613
|
+
events.managedSessionActiveRanks.set(key, rank);
|
|
614
|
+
}
|
|
385
615
|
function collectBranchManagedResourceEvents(branch) {
|
|
386
616
|
const events = {
|
|
387
617
|
electronLaunchActiveRanks: new Map(),
|
|
388
618
|
electronLaunchCleanupRanks: new Map(),
|
|
619
|
+
managedSessionActiveIdentities: new Map(),
|
|
389
620
|
managedSessionActiveRanks: new Map(),
|
|
390
621
|
managedSessionCloseRanks: new Map(),
|
|
391
622
|
};
|
|
@@ -402,33 +633,43 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
402
633
|
eventRank += 1;
|
|
403
634
|
const succeeded = getSuccessfulToolResult(details, message);
|
|
404
635
|
const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
|
|
405
|
-
const command = typeof details.command === "string" ? details.command :
|
|
636
|
+
const command = typeof details.command === "string" ? details.command : extractUpstreamCommandTokens(args)[0];
|
|
406
637
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
407
638
|
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
408
639
|
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
409
640
|
const usedImplicitSession = details.usedImplicitSession === true;
|
|
410
641
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
642
|
+
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
643
|
+
const closeAllApplied = detailsReportCloseAllApplied(details, succeeded);
|
|
411
644
|
const outcome = getManagedSessionOutcome(details);
|
|
412
645
|
const outcomeSucceeded = outcome?.succeeded === true;
|
|
413
646
|
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
414
647
|
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
415
648
|
const outcomeAttemptedSessionName = typeof outcome?.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
|
|
416
|
-
if (
|
|
417
|
-
|
|
649
|
+
if (outcome?.activeAfter === true && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")) {
|
|
650
|
+
setBranchManagedSessionActive(events, outcomeCurrentSessionName, namespace, eventRank);
|
|
418
651
|
}
|
|
419
652
|
if (outcomeSucceeded && outcomeStatus === "closed") {
|
|
420
653
|
setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName, namespace), eventRank);
|
|
421
654
|
}
|
|
422
|
-
if (
|
|
655
|
+
if (outcome && outcomeStatus === "replaced" && outcome.replacedSessionClosed !== false) {
|
|
423
656
|
const replacedSessionNamespace = typeof outcome.replacedSessionNamespace === "string" ? outcome.replacedSessionNamespace : namespace;
|
|
424
657
|
setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(typeof outcome.replacedSessionName === "string" ? outcome.replacedSessionName : undefined, replacedSessionNamespace), eventRank);
|
|
425
658
|
}
|
|
426
|
-
if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh")) {
|
|
427
|
-
events
|
|
659
|
+
if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh" || details.managedSessionHeadedAutosaveDisabled === true || typeof details.managedSessionHeadedAutosaveInterval === "string")) {
|
|
660
|
+
setBranchManagedSessionActive(events, sessionName, namespace, eventRank);
|
|
428
661
|
}
|
|
429
662
|
if (succeeded && isCloseCommand(command)) {
|
|
430
663
|
setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName, namespace), eventRank);
|
|
431
664
|
}
|
|
665
|
+
if (closeAllApplied) {
|
|
666
|
+
const retainedSessionKey = batchCloseLifecycle?.endsClosed === false ? getSessionContextKey(sessionName, namespace) : undefined;
|
|
667
|
+
for (const sessionKey of events.managedSessionActiveIdentities.keys()) {
|
|
668
|
+
if (sessionKey !== retainedSessionKey && isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, namespace)) {
|
|
669
|
+
events.managedSessionCloseRanks.set(sessionKey, eventRank);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
432
673
|
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
433
674
|
const launch = electron && isElectronLaunchRecord(electron.launch) ? electron.launch : undefined;
|
|
434
675
|
if (launch && getActiveElectronRecords(new Map([[launch.launchId, launch]])).length > 0) {
|
|
@@ -445,8 +686,8 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
445
686
|
if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
|
|
446
687
|
events.electronLaunchCleanupRanks.set(cleanupResult.record.launchId, eventRank);
|
|
447
688
|
}
|
|
448
|
-
for (const
|
|
449
|
-
events.managedSessionCloseRanks.set(
|
|
689
|
+
for (const identity of getCleanupResultClosedManagedSessionIdentities(cleanupResult, namespace)) {
|
|
690
|
+
events.managedSessionCloseRanks.set(getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName, eventRank);
|
|
450
691
|
}
|
|
451
692
|
}
|
|
452
693
|
}
|
|
@@ -465,23 +706,25 @@ function getCleanupResultsPreservedUserDataDirs(cleanupResults) {
|
|
|
465
706
|
}
|
|
466
707
|
return [...userDataDirs];
|
|
467
708
|
}
|
|
468
|
-
function syncElectronCleanupManagedSessions(sessions, cleanupResults) {
|
|
469
|
-
for (const
|
|
470
|
-
untrackOwnedManagedSession(sessions, sessionName);
|
|
709
|
+
function syncElectronCleanupManagedSessions(sessions, cleanupResults, fallbackNamespace) {
|
|
710
|
+
for (const identity of getCleanupResultsClosedManagedSessionIdentities(cleanupResults, fallbackNamespace)) {
|
|
711
|
+
untrackOwnedManagedSession(sessions, identity.sessionName, identity.namespace);
|
|
471
712
|
}
|
|
472
713
|
}
|
|
473
|
-
async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessionName, timeoutMs, attachedSessionKeys, keepNamespace) {
|
|
714
|
+
async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessionName, timeoutMs, attachedSessionKeys, keepNamespace, onClosed) {
|
|
474
715
|
const keepKey = getSessionContextKey(keepSessionName, keepNamespace);
|
|
475
716
|
for (const [key, owner] of [...sessions]) {
|
|
476
717
|
if (key === keepKey)
|
|
477
718
|
continue;
|
|
478
|
-
const error = await closeManagedSession({ cwd: owner.cwd, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, timeoutMs });
|
|
479
|
-
if (!error)
|
|
719
|
+
const error = await closeManagedSession({ cwd: owner.cwd, headedManagedAutosaveInterval: owner.headedManagedAutosaveInterval, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, timeoutMs });
|
|
720
|
+
if (!error) {
|
|
480
721
|
sessions.delete(key);
|
|
722
|
+
onClosed?.(owner);
|
|
723
|
+
}
|
|
481
724
|
}
|
|
482
725
|
}
|
|
483
|
-
async function closeOwnedManagedSessions(sessions, restoreState, timeoutMs, attachedSessionKeys) {
|
|
484
|
-
await closeOwnedManagedSessionsExcept(sessions, restoreState, undefined, timeoutMs, attachedSessionKeys);
|
|
726
|
+
async function closeOwnedManagedSessions(sessions, restoreState, timeoutMs, attachedSessionKeys, onClosed) {
|
|
727
|
+
await closeOwnedManagedSessionsExcept(sessions, restoreState, undefined, timeoutMs, attachedSessionKeys, undefined, onClosed);
|
|
485
728
|
}
|
|
486
729
|
function getOffBranchOwnedElectronLaunchRecords(ownedRecords, branchRecords) {
|
|
487
730
|
const activeBranchLaunchIds = new Set(getActiveElectronRecords(branchRecords).map((record) => record.launchId));
|
|
@@ -521,14 +764,19 @@ class AsyncExecutionQueue {
|
|
|
521
764
|
})();
|
|
522
765
|
}
|
|
523
766
|
}
|
|
524
|
-
class KeyedAsyncExecutionQueue {
|
|
767
|
+
export class KeyedAsyncExecutionQueue {
|
|
768
|
+
barriers = new Map();
|
|
525
769
|
entries = new Map();
|
|
526
|
-
async run(key, work) {
|
|
770
|
+
async run(key, namespace, work) {
|
|
527
771
|
const entry = this.entries.get(key) ?? { queue: new AsyncExecutionQueue(), users: 0 };
|
|
772
|
+
const barrier = this.barriers.get(getAgentBrowserSessionIdentityKey("", namespace)) ?? Promise.resolve();
|
|
528
773
|
entry.users += 1;
|
|
529
774
|
this.entries.set(key, entry);
|
|
530
775
|
try {
|
|
531
|
-
return await entry.queue.run(
|
|
776
|
+
return await entry.queue.run(async () => {
|
|
777
|
+
await barrier;
|
|
778
|
+
return await work();
|
|
779
|
+
});
|
|
532
780
|
}
|
|
533
781
|
finally {
|
|
534
782
|
entry.users -= 1;
|
|
@@ -536,6 +784,29 @@ class KeyedAsyncExecutionQueue {
|
|
|
536
784
|
this.entries.delete(key);
|
|
537
785
|
}
|
|
538
786
|
}
|
|
787
|
+
async runExclusive(namespace, work) {
|
|
788
|
+
const namespaceKey = getAgentBrowserSessionIdentityKey("", namespace);
|
|
789
|
+
const previous = this.barriers.get(namespaceKey) ?? Promise.resolve();
|
|
790
|
+
let release;
|
|
791
|
+
const blocked = new Promise((resolve) => {
|
|
792
|
+
release = resolve;
|
|
793
|
+
});
|
|
794
|
+
const barrier = previous.then(() => blocked);
|
|
795
|
+
this.barriers.set(namespaceKey, barrier);
|
|
796
|
+
const drains = [...this.entries]
|
|
797
|
+
.filter(([key]) => isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
798
|
+
.map(([, { queue }]) => queue.run(async () => undefined));
|
|
799
|
+
await previous;
|
|
800
|
+
await Promise.all(drains);
|
|
801
|
+
try {
|
|
802
|
+
return await work();
|
|
803
|
+
}
|
|
804
|
+
finally {
|
|
805
|
+
release();
|
|
806
|
+
if (this.barriers.get(namespaceKey) === barrier)
|
|
807
|
+
this.barriers.delete(namespaceKey);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
539
810
|
}
|
|
540
811
|
function mergeBrowserRunMap(current, initial, updated) {
|
|
541
812
|
if (updated === initial)
|
|
@@ -551,11 +822,19 @@ function mergeBrowserRunMap(current, initial, updated) {
|
|
|
551
822
|
}
|
|
552
823
|
return merged;
|
|
553
824
|
}
|
|
554
|
-
function mergeBrowserRunArtifactManifest(current, initial, updated) {
|
|
825
|
+
export function mergeBrowserRunArtifactManifest(current, initial, updated) {
|
|
555
826
|
if (!updated || updated === initial)
|
|
556
827
|
return current;
|
|
828
|
+
if (current === initial)
|
|
829
|
+
return updated;
|
|
557
830
|
const initialEntries = new Map((initial?.entries ?? []).map((entry) => [getSessionArtifactManifestEntryKey(entry), entry]));
|
|
558
|
-
const changedEntries = updated.entries
|
|
831
|
+
const changedEntries = updated.entries
|
|
832
|
+
.map((entry, index) => ({ entry, index }))
|
|
833
|
+
.filter(({ entry }) => initialEntries.get(getSessionArtifactManifestEntryKey(entry)) !== entry)
|
|
834
|
+
.sort((left, right) => left.entry.createdAtMs - right.entry.createdAtMs
|
|
835
|
+
|| Number(isPendingRecordingCommand(left.entry.command, left.entry.subcommand, left.entry.kind)) - Number(isPendingRecordingCommand(right.entry.command, right.entry.subcommand, right.entry.kind))
|
|
836
|
+
|| left.index - right.index)
|
|
837
|
+
.map(({ entry }) => entry);
|
|
559
838
|
return changedEntries.length === 0
|
|
560
839
|
? current
|
|
561
840
|
: mergeSessionArtifactManifest({
|
|
@@ -614,6 +893,8 @@ export default function agentBrowserExtension(pi) {
|
|
|
614
893
|
let managedSessionActive = false;
|
|
615
894
|
let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
|
|
616
895
|
let managedSessionCompatibilityWorkaround;
|
|
896
|
+
let managedSessionHeadedAutosaveDisabled = false;
|
|
897
|
+
let managedSessionHeadedAutosaveInterval;
|
|
617
898
|
let managedSessionName = managedSessionBaseName;
|
|
618
899
|
let managedSessionCwd = process.cwd();
|
|
619
900
|
let managedSessionNamespace;
|
|
@@ -621,6 +902,9 @@ export default function agentBrowserExtension(pi) {
|
|
|
621
902
|
let sessionPageState = new SessionPageState();
|
|
622
903
|
let traceOwners = new Map();
|
|
623
904
|
let artifactManifest;
|
|
905
|
+
let activeRecordingReservations = new Map();
|
|
906
|
+
let recordingSessionTombstones = new Map();
|
|
907
|
+
let recordingSessionTombstonesToPersist = new Map();
|
|
624
908
|
let allowedDomainsBySession = new Map();
|
|
625
909
|
let attachedSessionKeys = new Set();
|
|
626
910
|
let networkRoutesBySession = new Map();
|
|
@@ -631,9 +915,160 @@ export default function agentBrowserExtension(pi) {
|
|
|
631
915
|
const managedSessionRestoreState = new ManagedSessionRestoreState();
|
|
632
916
|
const ownedManagedSessions = new Map();
|
|
633
917
|
const managedSessionExecutionQueue = new AsyncExecutionQueue();
|
|
918
|
+
const artifactExecutionQueue = new AsyncExecutionQueue();
|
|
634
919
|
const callerOwnedSessionExecutionQueues = new KeyedAsyncExecutionQueue();
|
|
920
|
+
const activeScriptControllers = new Set();
|
|
921
|
+
const activeScriptExecutions = new Set();
|
|
635
922
|
let branchRestoreGeneration = 0;
|
|
636
923
|
let branchStateGeneration = 0;
|
|
924
|
+
const validatedUpstreamPathKeys = new Set();
|
|
925
|
+
const appendRecordingTransitions = (transitions) => {
|
|
926
|
+
for (const transition of transitions) {
|
|
927
|
+
const key = getAgentBrowserSessionIdentityKey(transition.reservation.sessionName, transition.reservation.namespace);
|
|
928
|
+
if (transition.state === "active") {
|
|
929
|
+
recordingSessionTombstones.delete(key);
|
|
930
|
+
recordingSessionTombstonesToPersist.delete(key);
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
recordingSessionTombstones.set(key, transition.reservation);
|
|
934
|
+
}
|
|
935
|
+
try {
|
|
936
|
+
appendRecordingReservationTransition(pi, transition);
|
|
937
|
+
recordingSessionTombstonesToPersist.delete(key);
|
|
938
|
+
}
|
|
939
|
+
catch {
|
|
940
|
+
if (transition.state === "closed")
|
|
941
|
+
recordingSessionTombstonesToPersist.set(key, transition.reservation);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
const appendActiveRecordingCleanupAction = (result, reservation) => {
|
|
946
|
+
if (result.isError !== true)
|
|
947
|
+
return result;
|
|
948
|
+
const details = isRecord(result.details) ? result.details : {};
|
|
949
|
+
const nextActions = Array.isArray(details.nextActions) ? [...details.nextActions] : [];
|
|
950
|
+
if (nextActions.some((action) => action.id === "stop-pending-recording"))
|
|
951
|
+
return result;
|
|
952
|
+
const stopActions = applyNamespaceToNextActions(applySessionToNextActions([
|
|
953
|
+
buildNextToolAction({
|
|
954
|
+
args: ["record", "stop"],
|
|
955
|
+
id: "stop-pending-recording",
|
|
956
|
+
reason: "Stop the active recording so the requested video can be finalized and verified on disk.",
|
|
957
|
+
safety: "The file remains pending until record stop succeeds; verify details.artifactVerification afterward.",
|
|
958
|
+
}),
|
|
959
|
+
], reservation.sessionName), reservation.namespace);
|
|
960
|
+
appendUniqueAgentBrowserNextActions(nextActions, stopActions);
|
|
961
|
+
const cleanupNotice = "An active recording remains open. Use the exact stop-pending-recording payload in details.nextActions before leaving this session.";
|
|
962
|
+
let noticeAppended = false;
|
|
963
|
+
const content = result.content.map((item) => {
|
|
964
|
+
if (noticeAppended || item.type !== "text")
|
|
965
|
+
return item;
|
|
966
|
+
noticeAppended = true;
|
|
967
|
+
return { ...item, text: `${item.text}\n\n${cleanupNotice}` };
|
|
968
|
+
});
|
|
969
|
+
if (!noticeAppended)
|
|
970
|
+
content.push({ type: "text", text: cleanupNotice });
|
|
971
|
+
return { ...result, content, details: { ...details, nextActions } };
|
|
972
|
+
};
|
|
973
|
+
const retireRecordingSession = (sessionName, namespace, retireManifest = true) => {
|
|
974
|
+
const reservation = retireRecordingReservation(activeRecordingReservations, sessionName, namespace);
|
|
975
|
+
const previousManifest = artifactManifest;
|
|
976
|
+
if (retireManifest && artifactManifest)
|
|
977
|
+
artifactManifest = retirePendingRecordingManifestEntries(artifactManifest, sessionName, namespace);
|
|
978
|
+
if (!reservation && artifactManifest === previousManifest)
|
|
979
|
+
return;
|
|
980
|
+
const terminalReservation = reservation ?? { absolutePath: "", cwd: managedSessionCwd, namespace, path: "", sessionName };
|
|
981
|
+
const terminalKey = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
982
|
+
recordingSessionTombstones.set(terminalKey, terminalReservation);
|
|
983
|
+
try {
|
|
984
|
+
appendRecordingReservationTransition(pi, {
|
|
985
|
+
reservation: terminalReservation,
|
|
986
|
+
state: "closed",
|
|
987
|
+
});
|
|
988
|
+
recordingSessionTombstonesToPersist.delete(terminalKey);
|
|
989
|
+
}
|
|
990
|
+
catch {
|
|
991
|
+
recordingSessionTombstonesToPersist.set(terminalKey, terminalReservation);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
const syncRecordingReservationsFromResult = (result) => {
|
|
995
|
+
const handledClosedSessionKeys = new Set();
|
|
996
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
997
|
+
const batchSteps = Array.isArray(details?.batchSteps) ? details.batchSteps : undefined;
|
|
998
|
+
const resultSessionName = typeof details?.sessionName === "string" ? details.sessionName : undefined;
|
|
999
|
+
const resultNamespace = typeof details?.namespace === "string" ? details.namespace : undefined;
|
|
1000
|
+
if (!batchSteps) {
|
|
1001
|
+
appendRecordingTransitions(applyRecordingArtifactsToReservations(activeRecordingReservations, getResultFileArtifacts(result)));
|
|
1002
|
+
return handledClosedSessionKeys;
|
|
1003
|
+
}
|
|
1004
|
+
let sessionClosed = false;
|
|
1005
|
+
for (const step of batchSteps) {
|
|
1006
|
+
if (!isRecord(step))
|
|
1007
|
+
continue;
|
|
1008
|
+
const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? step.command : undefined;
|
|
1009
|
+
const commandTokens = command ? extractUpstreamCommandTokens(command) : [];
|
|
1010
|
+
const commandName = commandTokens[0];
|
|
1011
|
+
if (resultSessionName && batchStepReportsNoRecordingInProgress(step)) {
|
|
1012
|
+
const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
|
|
1013
|
+
retireRecordingSession(resultSessionName, resultNamespace, false);
|
|
1014
|
+
handledClosedSessionKeys.add(sessionKey);
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (step.success === true && commandName && isCloseCommand(commandName) && resultSessionName) {
|
|
1018
|
+
const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
|
|
1019
|
+
retireRecordingSession(resultSessionName, resultNamespace, false);
|
|
1020
|
+
handledClosedSessionKeys.add(sessionKey);
|
|
1021
|
+
sessionClosed = true;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (sessionClosed && commandName === "record")
|
|
1025
|
+
continue;
|
|
1026
|
+
if (step.success === true && sessionClosed)
|
|
1027
|
+
sessionClosed = false;
|
|
1028
|
+
const artifacts = Array.isArray(step.artifacts) ? step.artifacts.filter(isResultFileArtifact) : [];
|
|
1029
|
+
appendRecordingTransitions(applyRecordingArtifactsToReservations(activeRecordingReservations, artifacts));
|
|
1030
|
+
}
|
|
1031
|
+
for (const sessionKey of handledClosedSessionKeys) {
|
|
1032
|
+
if (!activeRecordingReservations.has(sessionKey) && artifactManifest && resultSessionName) {
|
|
1033
|
+
artifactManifest = retirePendingRecordingManifestEntries(artifactManifest, resultSessionName, resultNamespace);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return handledClosedSessionKeys;
|
|
1037
|
+
};
|
|
1038
|
+
const validateUpstreamVersion = async (cwd, signal) => {
|
|
1039
|
+
const processEnvironment = getAgentBrowserProcessEnvironment();
|
|
1040
|
+
const pathKey = `${cwd}\0${processEnvironment.PATH ?? processEnvironment.Path ?? ""}`;
|
|
1041
|
+
if (validatedUpstreamPathKeys.has(pathKey))
|
|
1042
|
+
return undefined;
|
|
1043
|
+
const probe = await runAgentBrowserProcess({ args: ["--version"], cwd, signal, timeoutMs: 5_000 });
|
|
1044
|
+
if (probe.spawnError?.code === "ENOENT" || probe.exitCode === 127 || probe.aborted)
|
|
1045
|
+
return undefined;
|
|
1046
|
+
let error;
|
|
1047
|
+
let observedVersion;
|
|
1048
|
+
if (probe.spawnError || probe.exitCode !== 0) {
|
|
1049
|
+
const detail = redactSensitiveText(probe.spawnError?.message ?? (probe.stderr.trim() || `exit ${probe.exitCode}`));
|
|
1050
|
+
error = `agent-browser --version could not be validated (${detail}). Run pi-agent-browser-doctor before browser-backed calls.`;
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
observedVersion = parseAgentBrowserVersionOutput(probe.stdout);
|
|
1054
|
+
error = getAgentBrowserVersionValidationError(probe.stdout);
|
|
1055
|
+
}
|
|
1056
|
+
if (!error) {
|
|
1057
|
+
validatedUpstreamPathKeys.add(pathKey);
|
|
1058
|
+
return undefined;
|
|
1059
|
+
}
|
|
1060
|
+
return {
|
|
1061
|
+
content: [{ type: "text", text: error }],
|
|
1062
|
+
details: {
|
|
1063
|
+
expectedVersion: TARGET_AGENT_BROWSER_VERSION,
|
|
1064
|
+
failureCategory: "validation-error",
|
|
1065
|
+
observedVersion,
|
|
1066
|
+
resultCategory: "failure",
|
|
1067
|
+
versionValidation: { expected: TARGET_AGENT_BROWSER_VERSION_LABEL, observed: observedVersion },
|
|
1068
|
+
},
|
|
1069
|
+
isError: true,
|
|
1070
|
+
};
|
|
1071
|
+
};
|
|
637
1072
|
const clearSessionScopedBrowserState = (sessionName, namespace) => {
|
|
638
1073
|
const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
639
1074
|
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
@@ -641,8 +1076,51 @@ export default function agentBrowserExtension(pi) {
|
|
|
641
1076
|
attachedSessionKeys.delete(key);
|
|
642
1077
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
643
1078
|
networkRoutesBySession.delete(key);
|
|
1079
|
+
traceOwners.delete(key);
|
|
644
1080
|
sessionPageState.clearSession(key);
|
|
645
1081
|
};
|
|
1082
|
+
const closeScriptSessionLeaseWithinQueue = async (sessionName, cwd) => {
|
|
1083
|
+
const closeError = await withIsolatedAgentBrowserEnvironment(() => closeManagedSession({
|
|
1084
|
+
cwd,
|
|
1085
|
+
namespace: AGENT_BROWSER_SCRIPT_NAMESPACE,
|
|
1086
|
+
restoreState: managedSessionRestoreState,
|
|
1087
|
+
sessionName,
|
|
1088
|
+
timeoutMs: implicitSessionCloseTimeoutMs,
|
|
1089
|
+
}));
|
|
1090
|
+
if (closeError) {
|
|
1091
|
+
try {
|
|
1092
|
+
appendScriptSessionLease(pi, sessionName, "failed");
|
|
1093
|
+
}
|
|
1094
|
+
catch { }
|
|
1095
|
+
return redactSensitiveText(closeError);
|
|
1096
|
+
}
|
|
1097
|
+
try {
|
|
1098
|
+
appendScriptSessionLease(pi, sessionName, "closed");
|
|
1099
|
+
}
|
|
1100
|
+
catch {
|
|
1101
|
+
managedSessionRestoreState.disable(sessionName);
|
|
1102
|
+
return "The isolated session closed, but its durable cleanup record could not be saved.";
|
|
1103
|
+
}
|
|
1104
|
+
untrackOwnedManagedSession(ownedManagedSessions, sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1105
|
+
managedSessionRestoreState.clear(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1106
|
+
retireRecordingSession(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1107
|
+
clearSessionScopedBrowserState(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1108
|
+
return undefined;
|
|
1109
|
+
};
|
|
1110
|
+
const recoverScriptSessionLeasesWithinQueue = async (ctx) => {
|
|
1111
|
+
const pendingSessionNames = new Set([...ownedManagedSessions.values()]
|
|
1112
|
+
.map((session) => session.sessionName)
|
|
1113
|
+
.filter(isAgentBrowserScriptSessionName));
|
|
1114
|
+
for (const lease of getScriptSessionLeasesFromBranch(ctx.sessionManager.getBranch()).values()) {
|
|
1115
|
+
if (lease.cleanup !== "closed")
|
|
1116
|
+
pendingSessionNames.add(lease.sessionName);
|
|
1117
|
+
}
|
|
1118
|
+
for (const sessionName of pendingSessionNames) {
|
|
1119
|
+
trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { branchOwned: true, namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
|
|
1120
|
+
managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1121
|
+
await closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
646
1124
|
const restoreBranchBackedState = (ctx, options) => {
|
|
647
1125
|
branchRestoreGeneration += 1;
|
|
648
1126
|
branchStateGeneration += 1;
|
|
@@ -680,18 +1158,39 @@ export default function agentBrowserExtension(pi) {
|
|
|
680
1158
|
managedSessionCompatibilityWorkaround = managedSessionActive
|
|
681
1159
|
? restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, managedSessionName, managedSessionNamespace)
|
|
682
1160
|
: undefined;
|
|
1161
|
+
managedSessionHeadedAutosaveDisabled = managedSessionActive
|
|
1162
|
+
&& restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, managedSessionName, managedSessionNamespace);
|
|
1163
|
+
managedSessionHeadedAutosaveInterval = managedSessionActive
|
|
1164
|
+
? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
|
|
1165
|
+
: undefined;
|
|
683
1166
|
managedSessionCwd = ctx.cwd;
|
|
684
1167
|
freshSessionOrdinal = nextFreshSessionOrdinal;
|
|
685
1168
|
sessionPageState = SessionPageState.fromBranch(branch);
|
|
686
1169
|
traceOwners = new Map();
|
|
687
1170
|
artifactManifest = restoreArtifactManifestFromBranch(branch);
|
|
1171
|
+
const restoredRecordingState = restoreRecordingReservationStateFromBranch(branch);
|
|
1172
|
+
for (const [key, reservation] of recordingSessionTombstones) {
|
|
1173
|
+
if (restoredRecordingState.terminal.has(key))
|
|
1174
|
+
recordingSessionTombstonesToPersist.delete(key);
|
|
1175
|
+
else
|
|
1176
|
+
recordingSessionTombstonesToPersist.set(key, reservation);
|
|
1177
|
+
}
|
|
1178
|
+
for (const [key, reservation] of restoredRecordingState.terminal) {
|
|
1179
|
+
if (!activeRecordingReservations.has(key))
|
|
1180
|
+
recordingSessionTombstones.set(key, reservation);
|
|
1181
|
+
}
|
|
1182
|
+
for (const key of recordingSessionTombstones.keys())
|
|
1183
|
+
restoredRecordingState.active.delete(key);
|
|
1184
|
+
for (const [key, reservation] of activeRecordingReservations)
|
|
1185
|
+
restoredRecordingState.active.set(key, reservation);
|
|
1186
|
+
activeRecordingReservations = restoredRecordingState.active;
|
|
688
1187
|
allowedDomainsBySession = restoreAllowedDomainsBySessionFromBranch(branch);
|
|
689
1188
|
attachedSessionKeys = restoreAttachedSessionKeysFromBranch(branch);
|
|
690
1189
|
networkRoutesBySession = new Map();
|
|
691
1190
|
electronLaunchRecords = restoreElectronLaunchRecordsFromBranch(branch);
|
|
692
1191
|
for (const record of getActiveElectronRecords(electronLaunchRecords)) {
|
|
693
1192
|
if (record.sessionName)
|
|
694
|
-
attachedSessionKeys.add(getSessionContextKey(record.sessionName) ?? record.sessionName);
|
|
1193
|
+
attachedSessionKeys.add(getSessionContextKey(record.sessionName, record.namespace) ?? record.sessionName);
|
|
695
1194
|
}
|
|
696
1195
|
if (options.resetRuntimeOwnership) {
|
|
697
1196
|
ownedManagedSessions.clear();
|
|
@@ -704,8 +1203,43 @@ export default function agentBrowserExtension(pi) {
|
|
|
704
1203
|
}
|
|
705
1204
|
removeInactiveOwnedElectronLaunchRecords(ownedElectronLaunchRecords, branchOwnedElectronLaunchIds, electronLaunchRecords, branchResourceEvents.electronLaunchActiveRanks, branchResourceEvents.electronLaunchCleanupRanks);
|
|
706
1205
|
}
|
|
1206
|
+
for (const [sessionKey, identity] of branchResourceEvents.managedSessionActiveIdentities) {
|
|
1207
|
+
const activeRank = branchResourceEvents.managedSessionActiveRanks.get(sessionKey);
|
|
1208
|
+
const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
|
|
1209
|
+
if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
|
|
1210
|
+
continue;
|
|
1211
|
+
if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
|
|
1212
|
+
continue;
|
|
1213
|
+
trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, ctx.cwd, {
|
|
1214
|
+
branchOwned: true,
|
|
1215
|
+
compatibilityWorkaround: restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, identity.sessionName, identity.namespace),
|
|
1216
|
+
headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, identity.sessionName, identity.namespace),
|
|
1217
|
+
headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, identity.sessionName, identity.namespace),
|
|
1218
|
+
namespace: identity.namespace,
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
707
1221
|
if (restoredState.active) {
|
|
708
|
-
trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, {
|
|
1222
|
+
trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, {
|
|
1223
|
+
branchOwned: true,
|
|
1224
|
+
compatibilityWorkaround: managedSessionCompatibilityWorkaround,
|
|
1225
|
+
headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
|
|
1226
|
+
headedManagedAutosaveInterval: managedSessionHeadedAutosaveInterval,
|
|
1227
|
+
namespace: restoredState.namespace,
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
for (const record of getActiveElectronRecords(electronLaunchRecords)) {
|
|
1231
|
+
if (!record.sessionName || !isRestorableManagedSessionName(record.sessionName, managedSessionBaseName))
|
|
1232
|
+
continue;
|
|
1233
|
+
const sessionKey = getSessionContextKey(record.sessionName) ?? record.sessionName;
|
|
1234
|
+
const activeRank = branchResourceEvents.managedSessionActiveRanks.get(sessionKey);
|
|
1235
|
+
const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
|
|
1236
|
+
if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
|
|
1237
|
+
continue;
|
|
1238
|
+
trackOwnedManagedSession(ownedManagedSessions, record.sessionName, ctx.cwd, {
|
|
1239
|
+
branchOwned: true,
|
|
1240
|
+
headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, record.sessionName),
|
|
1241
|
+
headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, record.sessionName),
|
|
1242
|
+
});
|
|
709
1243
|
}
|
|
710
1244
|
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
711
1245
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
@@ -743,17 +1277,25 @@ export default function agentBrowserExtension(pi) {
|
|
|
743
1277
|
cwd: ctx.cwd,
|
|
744
1278
|
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
745
1279
|
}));
|
|
1280
|
+
await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => recoverScriptSessionLeasesWithinQueue(ctx)));
|
|
746
1281
|
});
|
|
747
1282
|
pi.on("session_tree", async (_event, ctx) => {
|
|
748
|
-
|
|
1283
|
+
for (const controller of activeScriptControllers)
|
|
1284
|
+
controller.abort();
|
|
1285
|
+
await Promise.allSettled([...activeScriptExecutions]);
|
|
1286
|
+
await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(async () => {
|
|
749
1287
|
restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
750
|
-
|
|
1288
|
+
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1289
|
+
}));
|
|
751
1290
|
});
|
|
752
1291
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
1292
|
+
for (const controller of activeScriptControllers)
|
|
1293
|
+
controller.abort();
|
|
1294
|
+
await Promise.allSettled([...activeScriptExecutions]);
|
|
753
1295
|
branchRestoreGeneration += 1;
|
|
754
1296
|
branchStateGeneration += 1;
|
|
755
1297
|
let preservedElectronProfileDirs = [];
|
|
756
|
-
await managedSessionExecutionQueue.run(async () => {
|
|
1298
|
+
await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(async () => {
|
|
757
1299
|
const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
|
|
758
1300
|
const quitting = event?.reason === "quit";
|
|
759
1301
|
preservedElectronProfileDirs = quitting
|
|
@@ -768,6 +1310,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
768
1310
|
electronChildProcesses,
|
|
769
1311
|
electronLaunchRecords: electronRecordsToCleanup,
|
|
770
1312
|
managedSessionRestoreState,
|
|
1313
|
+
ownedManagedSessions,
|
|
771
1314
|
timeoutMs: implicitSessionCloseTimeoutMs,
|
|
772
1315
|
});
|
|
773
1316
|
preservedElectronProfileDirs = [...new Set([
|
|
@@ -775,19 +1318,38 @@ export default function agentBrowserExtension(pi) {
|
|
|
775
1318
|
...getCleanupResultsPreservedUserDataDirs(electronCleanupResults),
|
|
776
1319
|
])];
|
|
777
1320
|
syncElectronCleanupManagedSessions(ownedManagedSessions, electronCleanupResults);
|
|
1321
|
+
for (const identity of getCleanupResultsClosedManagedSessionIdentities(electronCleanupResults))
|
|
1322
|
+
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
778
1323
|
if (quitting) {
|
|
779
|
-
await closeOwnedManagedSessions(ownedManagedSessions, managedSessionRestoreState, implicitSessionCloseTimeoutMs, attachedSessionKeys);
|
|
1324
|
+
await closeOwnedManagedSessions(ownedManagedSessions, managedSessionRestoreState, implicitSessionCloseTimeoutMs, attachedSessionKeys, (owner) => retireRecordingSession(owner.sessionName, owner.namespace));
|
|
780
1325
|
}
|
|
781
1326
|
else {
|
|
782
|
-
await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, attachedSessionKeys, managedSessionActive ? managedSessionNamespace : undefined);
|
|
1327
|
+
await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, attachedSessionKeys, managedSessionActive ? managedSessionNamespace : undefined, (owner) => retireRecordingSession(owner.sessionName, owner.namespace));
|
|
783
1328
|
}
|
|
784
|
-
});
|
|
1329
|
+
}));
|
|
785
1330
|
managedSessionActive = false;
|
|
786
1331
|
managedSessionCompatibilityWorkaround = undefined;
|
|
1332
|
+
managedSessionHeadedAutosaveDisabled = false;
|
|
1333
|
+
managedSessionHeadedAutosaveInterval = undefined;
|
|
787
1334
|
managedSessionNamespace = undefined;
|
|
1335
|
+
for (const reservation of recordingSessionTombstonesToPersist.values()) {
|
|
1336
|
+
try {
|
|
1337
|
+
appendRecordingReservationTransition(pi, { reservation, state: "closed" });
|
|
1338
|
+
}
|
|
1339
|
+
catch { }
|
|
1340
|
+
}
|
|
1341
|
+
for (const reservation of activeRecordingReservations.values()) {
|
|
1342
|
+
try {
|
|
1343
|
+
appendRecordingReservationTransition(pi, { reservation, state: "active" });
|
|
1344
|
+
}
|
|
1345
|
+
catch { }
|
|
1346
|
+
}
|
|
788
1347
|
sessionPageState.reset();
|
|
789
1348
|
traceOwners = new Map();
|
|
790
1349
|
artifactManifest = undefined;
|
|
1350
|
+
activeRecordingReservations = new Map();
|
|
1351
|
+
recordingSessionTombstones = new Map();
|
|
1352
|
+
recordingSessionTombstonesToPersist = new Map();
|
|
791
1353
|
allowedDomainsBySession = new Map();
|
|
792
1354
|
attachedSessionKeys = new Set();
|
|
793
1355
|
networkRoutesBySession = new Map();
|
|
@@ -836,16 +1398,16 @@ export default function agentBrowserExtension(pi) {
|
|
|
836
1398
|
}
|
|
837
1399
|
});
|
|
838
1400
|
pi.on("tool_result", async (event) => buildAgentBrowserToolResultPatch(event));
|
|
839
|
-
|
|
1401
|
+
const agentBrowserTool = {
|
|
840
1402
|
name: "agent_browser",
|
|
841
1403
|
label: "Agent Browser",
|
|
842
|
-
description: "Browse and interact with websites using agent-browser. Use this for web research, reading live docs, opening pages, taking snapshots or screenshots, clicking links, filling forms, extracting page content, and authenticated/profile-based browser work. Input choice: default `args` for open → snapshot -i → click/fill @refs; `semanticAction` for stable role/text/label targets; `job` or `qa` for multi-step checks; `electron` only for desktop apps; experimental `sourceLookup` / `networkSourceLookup` for candidates only.",
|
|
1404
|
+
description: "Browse and interact with websites using agent-browser. Use this for web research, reading live docs, opening pages, taking snapshots or screenshots, clicking links, filling forms, extracting page content, and authenticated/profile-based browser work. Input choice: `script` for one-shot JavaScript orchestration; default `args` for open → snapshot -i → click/fill @refs; `semanticAction` for stable role/text/label targets; `job` or `qa` for multi-step checks; `electron` only for desktop apps; experimental `sourceLookup` / `networkSourceLookup` for candidates only.",
|
|
843
1405
|
promptSnippet: "Browse websites, read live docs, click and fill pages, extract browser content, take screenshots, and automate real web workflows.",
|
|
844
1406
|
promptGuidelines: toolPromptGuidelines,
|
|
845
1407
|
parameters: AGENT_BROWSER_PARAMS,
|
|
846
1408
|
renderCall(args, theme, context) {
|
|
847
1409
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
848
|
-
text.setText(formatAgentBrowserRenderCall(args, theme));
|
|
1410
|
+
text.setText(formatAgentBrowserRenderCall(args, theme, context.expanded));
|
|
849
1411
|
return text;
|
|
850
1412
|
},
|
|
851
1413
|
renderResult(result, options, theme, context) {
|
|
@@ -855,11 +1417,11 @@ export default function agentBrowserExtension(pi) {
|
|
|
855
1417
|
component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
|
|
856
1418
|
return component;
|
|
857
1419
|
},
|
|
858
|
-
async execute(
|
|
1420
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
859
1421
|
const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
|
|
860
1422
|
const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
|
|
861
1423
|
const resolvedInput = resolveAgentBrowserInput({
|
|
862
|
-
getBatchPreflightValidationError,
|
|
1424
|
+
getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: ctx.cwd, outputPath, stdin }),
|
|
863
1425
|
managedSessionActive,
|
|
864
1426
|
params,
|
|
865
1427
|
});
|
|
@@ -870,6 +1432,126 @@ export default function agentBrowserExtension(pi) {
|
|
|
870
1432
|
if (outputPathValidationError) {
|
|
871
1433
|
return buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: outputPathValidationError });
|
|
872
1434
|
}
|
|
1435
|
+
const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
|
|
1436
|
+
if (!outputPath || result.isError === true || (isRecord(result.details) && result.details.resultCategory === "failure"))
|
|
1437
|
+
return result;
|
|
1438
|
+
return artifactExecutionQueue.run(async () => {
|
|
1439
|
+
const reservationError = getArtifactPreflightValidationError({
|
|
1440
|
+
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1441
|
+
args: [],
|
|
1442
|
+
cwd: ctx.cwd,
|
|
1443
|
+
outputPath,
|
|
1444
|
+
});
|
|
1445
|
+
if (reservationError) {
|
|
1446
|
+
return buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError });
|
|
1447
|
+
}
|
|
1448
|
+
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result });
|
|
1449
|
+
});
|
|
1450
|
+
};
|
|
1451
|
+
const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
|
|
1452
|
+
const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
|
|
1453
|
+
const browserBackedVersionCheck = needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs));
|
|
1454
|
+
if (!electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
|
|
1455
|
+
const versionFailure = resolvedInput.kind === "script"
|
|
1456
|
+
? await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(ctx.cwd, signal))
|
|
1457
|
+
: await validateUpstreamVersion(ctx.cwd, signal);
|
|
1458
|
+
if (versionFailure)
|
|
1459
|
+
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
|
|
1460
|
+
}
|
|
1461
|
+
if (resolvedInput.kind === "script") {
|
|
1462
|
+
if (!ctx.sessionManager.getSessionFile()) {
|
|
1463
|
+
return buildValidationFailureResult({
|
|
1464
|
+
attemptedKind: "script",
|
|
1465
|
+
kind: "invalid",
|
|
1466
|
+
redactedArgs: [],
|
|
1467
|
+
status: "invalid",
|
|
1468
|
+
toolArgs: [],
|
|
1469
|
+
validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
const sessionName = createAgentBrowserScriptSessionName();
|
|
1473
|
+
const innerResults = [];
|
|
1474
|
+
const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
|
|
1475
|
+
const deadline = Date.now() + scriptTimeoutMs;
|
|
1476
|
+
let leased = false;
|
|
1477
|
+
let cleanupError;
|
|
1478
|
+
let run = {
|
|
1479
|
+
callCount: 0,
|
|
1480
|
+
emitCount: 0,
|
|
1481
|
+
error: "Script sandbox execution failed.",
|
|
1482
|
+
failureCategory: "upstream-error",
|
|
1483
|
+
ok: false,
|
|
1484
|
+
rejectedCallCount: 0,
|
|
1485
|
+
steps: [],
|
|
1486
|
+
};
|
|
1487
|
+
const scriptController = new AbortController();
|
|
1488
|
+
const abortScript = () => scriptController.abort();
|
|
1489
|
+
signal?.addEventListener("abort", abortScript, { once: true });
|
|
1490
|
+
if (signal?.aborted)
|
|
1491
|
+
scriptController.abort();
|
|
1492
|
+
activeScriptControllers.add(scriptController);
|
|
1493
|
+
let finishScriptExecution;
|
|
1494
|
+
const scriptExecution = new Promise((resolve) => {
|
|
1495
|
+
finishScriptExecution = resolve;
|
|
1496
|
+
});
|
|
1497
|
+
activeScriptExecutions.add(scriptExecution);
|
|
1498
|
+
try {
|
|
1499
|
+
const pendingRun = runAgentBrowserScript({
|
|
1500
|
+
beforeFirstCall() {
|
|
1501
|
+
appendScriptSessionLease(pi, sessionName, "active");
|
|
1502
|
+
trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
|
|
1503
|
+
managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1504
|
+
leased = true;
|
|
1505
|
+
},
|
|
1506
|
+
code: resolvedInput.compiledScript.code,
|
|
1507
|
+
dispatch: async (innerParams, innerSignal) => {
|
|
1508
|
+
const remainingMs = Math.max(1, deadline - Date.now());
|
|
1509
|
+
const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
|
|
1510
|
+
const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
|
|
1511
|
+
args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
|
|
1512
|
+
stdin: innerParams.stdin,
|
|
1513
|
+
timeoutMs: innerTimeoutMs,
|
|
1514
|
+
}, innerSignal, undefined, ctx));
|
|
1515
|
+
innerResults.push(innerResult);
|
|
1516
|
+
return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
|
|
1517
|
+
},
|
|
1518
|
+
signal: scriptController.signal,
|
|
1519
|
+
timeoutMs: scriptTimeoutMs,
|
|
1520
|
+
});
|
|
1521
|
+
run = await pendingRun;
|
|
1522
|
+
}
|
|
1523
|
+
catch { }
|
|
1524
|
+
finally {
|
|
1525
|
+
activeScriptControllers.delete(scriptController);
|
|
1526
|
+
signal?.removeEventListener("abort", abortScript);
|
|
1527
|
+
if (leased) {
|
|
1528
|
+
try {
|
|
1529
|
+
cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd)));
|
|
1530
|
+
}
|
|
1531
|
+
catch {
|
|
1532
|
+
cleanupError = "The isolated script session cleanup operation failed.";
|
|
1533
|
+
try {
|
|
1534
|
+
appendScriptSessionLease(pi, sessionName, "failed");
|
|
1535
|
+
}
|
|
1536
|
+
catch { }
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
activeScriptExecutions.delete(scriptExecution);
|
|
1540
|
+
finishScriptExecution();
|
|
1541
|
+
}
|
|
1542
|
+
let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
|
|
1543
|
+
if (artifactManifest) {
|
|
1544
|
+
scriptResult = {
|
|
1545
|
+
...scriptResult,
|
|
1546
|
+
details: {
|
|
1547
|
+
...(isRecord(scriptResult.details) ? scriptResult.details : {}),
|
|
1548
|
+
artifactManifest,
|
|
1549
|
+
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1550
|
+
},
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
return applyUnserializedOutputPath(scriptResult);
|
|
1554
|
+
}
|
|
873
1555
|
const { toolArgs } = resolvedInput;
|
|
874
1556
|
const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
|
|
875
1557
|
const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
|
|
@@ -879,7 +1561,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
879
1561
|
compiledElectron,
|
|
880
1562
|
ownedRecords: ownedElectronLaunchRecords,
|
|
881
1563
|
});
|
|
882
|
-
|
|
1564
|
+
let electronHostResult = await handleElectronHostInput({
|
|
883
1565
|
attachedSessionKeys,
|
|
884
1566
|
compiledElectron,
|
|
885
1567
|
cwd: ctx.cwd,
|
|
@@ -890,6 +1572,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
890
1572
|
managedSessionName,
|
|
891
1573
|
managedSessionNamespace,
|
|
892
1574
|
managedSessionRestoreState,
|
|
1575
|
+
ownedManagedSessions,
|
|
893
1576
|
redactedCompiledElectron,
|
|
894
1577
|
sessionPageState,
|
|
895
1578
|
signal,
|
|
@@ -910,26 +1593,58 @@ export default function agentBrowserExtension(pi) {
|
|
|
910
1593
|
}
|
|
911
1594
|
replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
|
|
912
1595
|
mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1596
|
+
const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
|
|
1597
|
+
? electronHostResult.details.namespace
|
|
1598
|
+
: undefined;
|
|
1599
|
+
const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
|
|
1600
|
+
syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
|
|
1601
|
+
for (const identity of closedSessionIdentities) {
|
|
1602
|
+
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1603
|
+
const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
|
|
1604
|
+
clearSessionScopedBrowserState(closedSessionKey);
|
|
1605
|
+
if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
|
|
918
1606
|
managedSessionActive = false;
|
|
919
1607
|
managedSessionCompatibilityWorkaround = undefined;
|
|
1608
|
+
managedSessionHeadedAutosaveDisabled = false;
|
|
1609
|
+
managedSessionHeadedAutosaveInterval = undefined;
|
|
920
1610
|
managedSessionNamespace = undefined;
|
|
921
1611
|
freshSessionOrdinal += 1;
|
|
922
1612
|
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
923
1613
|
}
|
|
924
1614
|
}
|
|
1615
|
+
if (artifactManifest) {
|
|
1616
|
+
electronHostResult = {
|
|
1617
|
+
...electronHostResult,
|
|
1618
|
+
details: {
|
|
1619
|
+
...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
|
|
1620
|
+
artifactManifest,
|
|
1621
|
+
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1622
|
+
},
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
925
1625
|
}
|
|
926
1626
|
return electronHostResult;
|
|
927
1627
|
};
|
|
928
|
-
const
|
|
929
|
-
?
|
|
930
|
-
:
|
|
1628
|
+
const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
|
|
1629
|
+
? managedSessionExecutionQueue.run(runElectronHostInput)
|
|
1630
|
+
: runElectronHostInput();
|
|
1631
|
+
const electronHostResult = compiledElectron?.action === "cleanup"
|
|
1632
|
+
? await artifactExecutionQueue.run(async () => {
|
|
1633
|
+
const reservationError = outputPath ? getArtifactPreflightValidationError({
|
|
1634
|
+
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1635
|
+
args: [],
|
|
1636
|
+
cwd: ctx.cwd,
|
|
1637
|
+
outputPath,
|
|
1638
|
+
}) : undefined;
|
|
1639
|
+
if (reservationError) {
|
|
1640
|
+
return buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError });
|
|
1641
|
+
}
|
|
1642
|
+
const result = await runSerializedElectronHostInput();
|
|
1643
|
+
return result ? applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result }) : result;
|
|
1644
|
+
})
|
|
1645
|
+
: await runSerializedElectronHostInput();
|
|
931
1646
|
if (electronHostResult) {
|
|
932
|
-
return
|
|
1647
|
+
return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
|
|
933
1648
|
}
|
|
934
1649
|
const explicitSessionName = extractExplicitSessionName(toolArgs);
|
|
935
1650
|
const explicitNamespace = extractExplicitNamespace(toolArgs);
|
|
@@ -940,8 +1655,11 @@ export default function agentBrowserExtension(pi) {
|
|
|
940
1655
|
ownedElectronLaunchRecords,
|
|
941
1656
|
ownedManagedSessions,
|
|
942
1657
|
});
|
|
1658
|
+
const callerOwnedSessionNamespace = explicitSessionName
|
|
1659
|
+
? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
|
|
1660
|
+
: undefined;
|
|
943
1661
|
const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
|
|
944
|
-
? getSessionContextKey(explicitSessionName,
|
|
1662
|
+
? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
|
|
945
1663
|
: undefined;
|
|
946
1664
|
const runBrowserCommand = async () => {
|
|
947
1665
|
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
@@ -959,6 +1677,8 @@ export default function agentBrowserExtension(pi) {
|
|
|
959
1677
|
managedSessionActive,
|
|
960
1678
|
managedSessionBaseName,
|
|
961
1679
|
managedSessionCompatibilityWorkaround,
|
|
1680
|
+
managedSessionHeadedAutosaveDisabled,
|
|
1681
|
+
managedSessionHeadedAutosaveInterval,
|
|
962
1682
|
managedSessionCwd,
|
|
963
1683
|
managedSessionName,
|
|
964
1684
|
managedSessionNamespace,
|
|
@@ -997,20 +1717,30 @@ export default function agentBrowserExtension(pi) {
|
|
|
997
1717
|
state: browserRunState,
|
|
998
1718
|
});
|
|
999
1719
|
const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
|
|
1720
|
+
const resultDetails = isRecord(result.details) ? result.details : undefined;
|
|
1721
|
+
const resultSessionName = typeof resultDetails?.sessionName === "string"
|
|
1722
|
+
? resultDetails.sessionName
|
|
1723
|
+
: extractExplicitSessionName(toolArgs);
|
|
1724
|
+
const resultNamespace = typeof resultDetails?.namespace === "string"
|
|
1725
|
+
? resultDetails.namespace
|
|
1726
|
+
: resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
|
|
1000
1727
|
if (branchRestoreStillCurrent) {
|
|
1001
|
-
const
|
|
1002
|
-
const resultSessionName = typeof resultDetails?.sessionName === "string"
|
|
1003
|
-
? resultDetails.sessionName
|
|
1004
|
-
: extractExplicitSessionName(toolArgs);
|
|
1005
|
-
const resultNamespace = typeof resultDetails?.namespace === "string"
|
|
1006
|
-
? resultDetails.namespace
|
|
1007
|
-
: resolveAgentBrowserNamespace(toolArgs, process.env.AGENT_BROWSER_NAMESPACE);
|
|
1728
|
+
const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
|
|
1008
1729
|
const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
|
|
1009
1730
|
const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
|
|
1731
|
+
const closeAllApplied = resultDetails?.closeAllApplied === true;
|
|
1010
1732
|
const attachedSessionRemainsActive = result.isError !== true
|
|
1011
|
-
|| (attachedSessionRequested && managedSessionOutcome?.activeAfter === true);
|
|
1012
|
-
const closesAttachedSession = result.isError !== true && isCloseCommand(
|
|
1013
|
-
|
|
1733
|
+
|| ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
|
|
1734
|
+
const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
|
|
1735
|
+
|| resultBatchCloseLifecycle?.endsClosed === true;
|
|
1736
|
+
if (closeAllApplied) {
|
|
1737
|
+
deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
|
|
1738
|
+
if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
|
|
1739
|
+
attachedSessionKeys.add(resultSessionKey);
|
|
1740
|
+
result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
else if (resultSessionKey && closesAttachedSession)
|
|
1014
1744
|
attachedSessionKeys.delete(resultSessionKey);
|
|
1015
1745
|
else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
|
|
1016
1746
|
attachedSessionKeys.add(resultSessionKey);
|
|
@@ -1021,6 +1751,29 @@ export default function agentBrowserExtension(pi) {
|
|
|
1021
1751
|
allowedDomainsBySession = mergeBrowserRunMap(allowedDomainsBySession, initialAllowedDomainsBySession, browserRunState.allowedDomainsBySession);
|
|
1022
1752
|
networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
|
|
1023
1753
|
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
|
|
1754
|
+
const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
|
|
1755
|
+
if (resultDetails?.closeAllApplied === true) {
|
|
1756
|
+
for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
|
|
1757
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
|
|
1758
|
+
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
|
|
1763
|
+
if (handledBatchCloseKeys.has(closedSessionKey))
|
|
1764
|
+
continue;
|
|
1765
|
+
const reservation = activeRecordingReservations.get(closedSessionKey);
|
|
1766
|
+
if (reservation)
|
|
1767
|
+
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1768
|
+
}
|
|
1769
|
+
if (resultSessionName && resultReportsNoRecordingInProgress(result)) {
|
|
1770
|
+
retireRecordingSession(resultSessionName, resultNamespace);
|
|
1771
|
+
}
|
|
1772
|
+
if (resultSessionName) {
|
|
1773
|
+
const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
|
|
1774
|
+
if (reservation)
|
|
1775
|
+
result = appendActiveRecordingCleanupAction(result, reservation);
|
|
1776
|
+
}
|
|
1024
1777
|
if (artifactManifest) {
|
|
1025
1778
|
result = {
|
|
1026
1779
|
...result,
|
|
@@ -1037,6 +1790,8 @@ export default function agentBrowserExtension(pi) {
|
|
|
1037
1790
|
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1038
1791
|
managedSessionActive = browserRunState.managedSessionActive;
|
|
1039
1792
|
managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
|
|
1793
|
+
managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
|
|
1794
|
+
managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
|
|
1040
1795
|
managedSessionCwd = browserRunState.managedSessionCwd;
|
|
1041
1796
|
managedSessionName = browserRunState.managedSessionName;
|
|
1042
1797
|
managedSessionNamespace = browserRunState.managedSessionNamespace;
|
|
@@ -1047,7 +1802,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
1047
1802
|
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
1048
1803
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1049
1804
|
touchedLaunchIds: !result.isError
|
|
1050
|
-
? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords)
|
|
1805
|
+
? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, explicitSessionName ? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE) : browserRunState.managedSessionNamespace)
|
|
1051
1806
|
: undefined,
|
|
1052
1807
|
});
|
|
1053
1808
|
if (serializeBrowserCommand)
|
|
@@ -1055,12 +1810,47 @@ export default function agentBrowserExtension(pi) {
|
|
|
1055
1810
|
}
|
|
1056
1811
|
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result });
|
|
1057
1812
|
};
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1813
|
+
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
1814
|
+
const closeAllNamespace = closesAllSessions
|
|
1815
|
+
? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
|
|
1816
|
+
: undefined;
|
|
1817
|
+
const runWithinSessionQueue = () => {
|
|
1818
|
+
if (closesAllSessions)
|
|
1819
|
+
return managedSessionExecutionQueue.run(() => callerOwnedSessionExecutionQueues.runExclusive(closeAllNamespace, runBrowserCommand));
|
|
1820
|
+
if (serializeBrowserCommand)
|
|
1821
|
+
return managedSessionExecutionQueue.run(runBrowserCommand);
|
|
1822
|
+
return callerOwnedSessionQueueKey
|
|
1823
|
+
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runBrowserCommand)
|
|
1824
|
+
: runBrowserCommand();
|
|
1825
|
+
};
|
|
1826
|
+
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1827
|
+
return runWithinSessionQueue();
|
|
1828
|
+
return artifactExecutionQueue.run(async () => {
|
|
1829
|
+
const artifactValidationError = getArtifactPreflightValidationError({
|
|
1830
|
+
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1831
|
+
args: toolArgs,
|
|
1832
|
+
cwd: ctx.cwd,
|
|
1833
|
+
outputPath,
|
|
1834
|
+
stdin: resolvedInput.toolStdin,
|
|
1835
|
+
});
|
|
1836
|
+
if (!artifactValidationError)
|
|
1837
|
+
return runWithinSessionQueue();
|
|
1838
|
+
return applyAgentBrowserOutputPath({
|
|
1839
|
+
cwd: ctx.cwd,
|
|
1840
|
+
outputPath,
|
|
1841
|
+
result: buildValidationFailureResult({
|
|
1842
|
+
attemptedKind: resolvedInput.kind,
|
|
1843
|
+
kind: "invalid",
|
|
1844
|
+
redactedArgs: resolvedInput.redactedArgs,
|
|
1845
|
+
status: "invalid",
|
|
1846
|
+
toolArgs: resolvedInput.toolArgs,
|
|
1847
|
+
toolStdin: resolvedInput.toolStdin,
|
|
1848
|
+
validationError: artifactValidationError,
|
|
1849
|
+
}),
|
|
1850
|
+
});
|
|
1851
|
+
});
|
|
1063
1852
|
},
|
|
1064
|
-
}
|
|
1853
|
+
};
|
|
1854
|
+
pi.registerTool(agentBrowserTool);
|
|
1065
1855
|
registerWebSearchToolIfAvailable(agentBrowserConfig);
|
|
1066
1856
|
}
|