pi-agent-browser-native 0.6.16 → 0.7.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 +18 -1
- package/README.md +59 -165
- package/dist/extensions/agent-browser/index.js +634 -501
- package/dist/extensions/agent-browser/lib/browser-transcript.js +33 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +8 -4
- package/dist/extensions/agent-browser/lib/execution-cwd.js +66 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +2 -213
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/params.js +146 -142
- package/dist/extensions/agent-browser/lib/input-modes/script.js +64 -92
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +170 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +27 -20
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +26 -13
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +21 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +1 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +41 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +3 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +79 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +3 -4
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +19 -52
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/operation-paths.js +76 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +91 -278
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +9 -9
- package/dist/extensions/agent-browser/lib/playbook.js +46 -25
- package/dist/extensions/agent-browser/lib/process-identity.js +22 -6
- package/dist/extensions/agent-browser/lib/process.js +16 -13
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +2 -2
- package/dist/extensions/agent-browser/lib/results/next-actions.js +1 -2
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +25 -4
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +6 -2
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +39 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +3 -3
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +41 -1
- package/dist/extensions/agent-browser/lib/results/presentation.js +16 -11
- package/dist/extensions/agent-browser/lib/runtime.js +18 -6
- package/dist/extensions/agent-browser/lib/session-page-state.js +3 -4
- package/dist/extensions/agent-browser/lib/tool-surface.js +131 -0
- package/dist/extensions/agent-browser/script-worker.js +4 -1
- package/docs/ARCHITECTURE.md +47 -61
- package/docs/COMMAND_REFERENCE.md +78 -137
- package/docs/ELECTRON.md +51 -49
- package/docs/RELEASE.md +31 -15
- package/docs/REQUIREMENTS.md +18 -14
- package/docs/SUPPORT_MATRIX.md +33 -15
- package/docs/TOOL_CONTRACT.md +218 -401
- package/package.json +4 -4
- package/scripts/doctor.mjs +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -5,30 +6,35 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
5
6
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./lib/batch-lifecycle.js";
|
|
6
7
|
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
7
8
|
import { SessionPageState } from "./lib/session-page-state.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { appendBrowserTransition, getBrowserResultMessage } from "./lib/browser-transcript.js";
|
|
10
|
+
import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactInvocationArgs, redactSensitiveText, redactSensitiveValue, isPlainTextInspectionArgs, } from "./lib/runtime.js";
|
|
11
|
+
import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences } from "./lib/argv-grammar.js";
|
|
10
12
|
import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
|
|
11
13
|
import { needsManagedSession } from "./lib/command-policy.js";
|
|
12
14
|
import { ManagedSessionRestoreState, resolveOwnedManagedSessionContext, withOwnedManagedSessionContext } from "./lib/managed-session-restore.js";
|
|
13
15
|
import { isRecord } from "./lib/parsing.js";
|
|
14
|
-
import { runAgentBrowserProcess } from "./lib/process.js";
|
|
15
|
-
import {
|
|
16
|
+
import { getAgentBrowserProcessTimeoutMs, runAgentBrowserProcess } from "./lib/process.js";
|
|
17
|
+
import { getCommandAwareProcessTimeoutMs } from "./lib/orchestration/browser-run/prepare/wait-timeouts.js";
|
|
18
|
+
import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
|
|
16
19
|
import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
|
|
20
|
+
import { getBrowserCwdError, resolveExecutionCwd, restoreManagedSessionCwd } from "./lib/execution-cwd.js";
|
|
21
|
+
import { resolveOperationPaths } from "./lib/orchestration/operation-paths.js";
|
|
17
22
|
import { closeManagedSession, inspectManagedSessionDaemon } from "./lib/orchestration/browser-run/managed-session-daemon-policy.js";
|
|
18
23
|
import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
|
|
19
24
|
import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
|
|
20
25
|
import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
|
|
21
26
|
import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
|
|
22
27
|
import { cleanupSecureTempArtifacts } from "./lib/temp.js";
|
|
23
|
-
import {
|
|
24
|
-
import { AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS, AGENT_BROWSER_SCRIPT_NAMESPACE, createAgentBrowserScriptSessionName, isAgentBrowserScriptSessionName, runAgentBrowserScript, } from "./lib/input-modes/script.js";
|
|
28
|
+
import { AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS, AGENT_BROWSER_SCRIPT_NAMESPACE, bindBrowserCodeCall, isAgentBrowserScriptSessionName, runAgentBrowserScript, } from "./lib/input-modes/script.js";
|
|
25
29
|
import { canonicalizeExplicitArtifactDestination, getExplicitArtifactDestination, getRecordContactSheetDestination } from "./lib/orchestration/browser-run/artifact-paths.js";
|
|
26
|
-
import { findElectronLaunchRecordForSession, getActiveElectronRecords, getSessionContextKey } from "./lib/orchestration/browser-run/session-state.js";
|
|
30
|
+
import { findElectronLaunchRecordForSession, getActiveElectronRecords, getPersistentSessionArtifactStore, getSessionContextKey } from "./lib/orchestration/browser-run/session-state.js";
|
|
27
31
|
import { parseBatchCommandArgument, parseUserBatchStdin } from "./lib/orchestration/batch-stdin.js";
|
|
28
32
|
import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
|
|
29
33
|
import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
|
|
30
34
|
import { applyAgentBrowserOutputPath, canWriteAgentBrowserOutput, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
|
|
31
|
-
import { appendScriptSessionLease,
|
|
35
|
+
import { appendScriptSessionLease, createBrowserCodeOutput, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
|
|
36
|
+
import { resolveBrowserExecutionIdentity, withBrowserExecutionLock, withBrowserExecutionLocks } from "./lib/managed-session-policy-lock.js";
|
|
37
|
+
import { registerAgentBrowserToolSurface } from "./lib/tool-surface.js";
|
|
32
38
|
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingCommand, isSessionArtifactManifest, mergeSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
|
|
33
39
|
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
|
|
34
40
|
import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
|
|
@@ -37,6 +43,12 @@ import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
|
|
|
37
43
|
import { scopeReadConfirmationArgs } from "./lib/read-confirmation.js";
|
|
38
44
|
import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
|
|
39
45
|
import { AgentBrowserResultComponent, buildAgentBrowserToolResultPatch, formatAgentBrowserRenderCall, formatAgentBrowserRenderResult, } from "./lib/pi-tool-rendering.js";
|
|
46
|
+
function browserExecutionFailure(error, signal, fallbackCategory = "upstream-error") {
|
|
47
|
+
const timeout = error instanceof Error && error.name === "TimeoutError";
|
|
48
|
+
const summary = redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
49
|
+
const failureCategory = timeout ? "timeout" : signal?.aborted ? "aborted" : fallbackCategory;
|
|
50
|
+
return { content: [{ type: "text", text: summary }], details: { error: summary, summary, resultCategory: "failure", failureCategory }, isError: true };
|
|
51
|
+
}
|
|
40
52
|
function isBashToolCallEvent(event) {
|
|
41
53
|
if (!isRecord(event) || event.toolName !== "bash" || !isRecord(event.input))
|
|
42
54
|
return false;
|
|
@@ -165,10 +177,8 @@ function getResultFileArtifacts(result) {
|
|
|
165
177
|
function restoreArtifactManifestFromBranch(branch) {
|
|
166
178
|
let restoredManifest;
|
|
167
179
|
for (const entry of branch) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
171
|
-
if (!message || message.toolName !== "agent_browser")
|
|
180
|
+
const message = getBrowserResultMessage(entry);
|
|
181
|
+
if (!message)
|
|
172
182
|
continue;
|
|
173
183
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
174
184
|
if (isSessionArtifactManifest(details?.artifactManifest) && (!restoredManifest || details.artifactManifest.updatedAtMs >= restoredManifest.updatedAtMs)) {
|
|
@@ -187,10 +197,8 @@ function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionN
|
|
|
187
197
|
let restored;
|
|
188
198
|
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
189
199
|
for (const entry of branch) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
193
|
-
if (!message || message.toolName !== "agent_browser")
|
|
200
|
+
const message = getBrowserResultMessage(entry);
|
|
201
|
+
if (!message)
|
|
194
202
|
continue;
|
|
195
203
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
196
204
|
if (!details)
|
|
@@ -220,10 +228,8 @@ function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionNa
|
|
|
220
228
|
let restored = false;
|
|
221
229
|
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
222
230
|
for (const entry of branch) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
226
|
-
if (!message || message.toolName !== "agent_browser")
|
|
231
|
+
const message = getBrowserResultMessage(entry);
|
|
232
|
+
if (!message)
|
|
227
233
|
continue;
|
|
228
234
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
229
235
|
if (!details)
|
|
@@ -244,10 +250,8 @@ function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionNa
|
|
|
244
250
|
let restored;
|
|
245
251
|
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
246
252
|
for (const entry of branch) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
250
|
-
if (!message || message.toolName !== "agent_browser")
|
|
253
|
+
const message = getBrowserResultMessage(entry);
|
|
254
|
+
if (!message)
|
|
251
255
|
continue;
|
|
252
256
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
253
257
|
if (!details)
|
|
@@ -291,10 +295,8 @@ function isAttachedBrowserInvocation(args, env = getAgentBrowserProcessEnvironme
|
|
|
291
295
|
function restoreAttachedSessionKeysFromBranch(branch) {
|
|
292
296
|
const attachedSessionKeys = new Set();
|
|
293
297
|
for (const entry of branch) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
297
|
-
if (!message || message.toolName !== "agent_browser")
|
|
298
|
+
const message = getBrowserResultMessage(entry);
|
|
299
|
+
if (!message)
|
|
298
300
|
continue;
|
|
299
301
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
300
302
|
if (!details)
|
|
@@ -537,10 +539,8 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
537
539
|
};
|
|
538
540
|
let eventRank = 0;
|
|
539
541
|
for (const entry of branch) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
543
|
-
if (!message || message.toolName !== "agent_browser")
|
|
542
|
+
const message = getBrowserResultMessage(entry);
|
|
543
|
+
if (!message)
|
|
544
544
|
continue;
|
|
545
545
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
546
546
|
if (!details)
|
|
@@ -659,36 +659,56 @@ function shouldSerializeBrowserCommand(options) {
|
|
|
659
659
|
// Serializes managed-session read/modify/write work so overlapping tool calls cannot promote stale state or close an in-use session.
|
|
660
660
|
class AsyncExecutionQueue {
|
|
661
661
|
tail = Promise.resolve();
|
|
662
|
-
|
|
662
|
+
active = new AsyncLocalStorage();
|
|
663
|
+
isCurrent() { return this.active.getStore()?.active === true; }
|
|
664
|
+
run(work, signal, barrier = Promise.resolve()) {
|
|
665
|
+
if (this.isCurrent()) {
|
|
666
|
+
signal?.throwIfAborted();
|
|
667
|
+
return work();
|
|
668
|
+
}
|
|
663
669
|
const previous = this.tail;
|
|
664
670
|
let release;
|
|
665
671
|
this.tail = new Promise((resolve) => {
|
|
666
672
|
release = resolve;
|
|
667
673
|
});
|
|
668
|
-
|
|
669
|
-
|
|
674
|
+
let rejectWaiting;
|
|
675
|
+
const cancelled = new Promise((_resolve, reject) => { rejectWaiting = reject; });
|
|
676
|
+
const abortWaiting = () => rejectWaiting(signal?.reason);
|
|
677
|
+
signal?.addEventListener("abort", abortWaiting, { once: true });
|
|
678
|
+
if (signal?.aborted)
|
|
679
|
+
abortWaiting();
|
|
680
|
+
const execution = (async () => {
|
|
681
|
+
await Promise.all([previous, barrier]);
|
|
682
|
+
signal?.removeEventListener("abort", abortWaiting);
|
|
670
683
|
try {
|
|
671
|
-
|
|
684
|
+
signal?.throwIfAborted();
|
|
685
|
+
const scope = { active: true };
|
|
686
|
+
try {
|
|
687
|
+
return await this.active.run(scope, work);
|
|
688
|
+
}
|
|
689
|
+
finally {
|
|
690
|
+
scope.active = false;
|
|
691
|
+
}
|
|
672
692
|
}
|
|
673
693
|
finally {
|
|
674
694
|
release();
|
|
675
695
|
}
|
|
676
696
|
})();
|
|
697
|
+
return signal ? Promise.race([execution, cancelled]) : execution;
|
|
677
698
|
}
|
|
678
699
|
}
|
|
679
700
|
export class KeyedAsyncExecutionQueue {
|
|
680
701
|
barriers = new Map();
|
|
681
702
|
entries = new Map();
|
|
682
|
-
async run(key, namespace, work) {
|
|
703
|
+
async run(key, namespace, work, signal) {
|
|
683
704
|
const entry = this.entries.get(key) ?? { queue: new AsyncExecutionQueue(), users: 0 };
|
|
705
|
+
if (entry.queue.isCurrent())
|
|
706
|
+
return work();
|
|
684
707
|
const barrier = this.barriers.get(getAgentBrowserSessionIdentityKey("", namespace)) ?? Promise.resolve();
|
|
685
708
|
entry.users += 1;
|
|
686
709
|
this.entries.set(key, entry);
|
|
687
710
|
try {
|
|
688
|
-
return await entry.queue.run(
|
|
689
|
-
await barrier;
|
|
690
|
-
return await work();
|
|
691
|
-
});
|
|
711
|
+
return await entry.queue.run(work, signal, barrier);
|
|
692
712
|
}
|
|
693
713
|
finally {
|
|
694
714
|
entry.users -= 1;
|
|
@@ -1108,7 +1128,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1108
1128
|
managedSessionHeadedAutosaveInterval = managedSessionActive
|
|
1109
1129
|
? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
|
|
1110
1130
|
: undefined;
|
|
1111
|
-
managedSessionCwd = ctx.cwd;
|
|
1131
|
+
managedSessionCwd = restoreManagedSessionCwd(branch, managedSessionName, managedSessionNamespace, ctx.cwd);
|
|
1112
1132
|
freshSessionOrdinal = nextFreshSessionOrdinal;
|
|
1113
1133
|
sessionPageState = SessionPageState.fromBranch(branch);
|
|
1114
1134
|
traceOwners = new Map();
|
|
@@ -1159,7 +1179,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1159
1179
|
continue;
|
|
1160
1180
|
if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
|
|
1161
1181
|
continue;
|
|
1162
|
-
trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, ctx.cwd, {
|
|
1182
|
+
trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, restoreManagedSessionCwd(branch, identity.sessionName, identity.namespace, ctx.cwd), {
|
|
1163
1183
|
branchOwned: true,
|
|
1164
1184
|
compatibilityWorkaround: restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, identity.sessionName, identity.namespace),
|
|
1165
1185
|
headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, identity.sessionName, identity.namespace),
|
|
@@ -1168,7 +1188,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1168
1188
|
});
|
|
1169
1189
|
}
|
|
1170
1190
|
if (restoredState.active) {
|
|
1171
|
-
trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName,
|
|
1191
|
+
trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, managedSessionCwd, {
|
|
1172
1192
|
branchOwned: true,
|
|
1173
1193
|
compatibilityWorkaround: managedSessionCompatibilityWorkaround,
|
|
1174
1194
|
headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
|
|
@@ -1184,7 +1204,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1184
1204
|
const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
|
|
1185
1205
|
if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
|
|
1186
1206
|
continue;
|
|
1187
|
-
trackOwnedManagedSession(ownedManagedSessions, record.sessionName, ctx.cwd, {
|
|
1207
|
+
trackOwnedManagedSession(ownedManagedSessions, record.sessionName, restoreManagedSessionCwd(branch, record.sessionName, undefined, ctx.cwd), {
|
|
1188
1208
|
branchOwned: true,
|
|
1189
1209
|
headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, record.sessionName),
|
|
1190
1210
|
headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, record.sessionName),
|
|
@@ -1226,31 +1246,32 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1226
1246
|
cwd: ctx.cwd,
|
|
1227
1247
|
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
1228
1248
|
}));
|
|
1229
|
-
await
|
|
1249
|
+
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1230
1250
|
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1231
1251
|
flushRecordingReservations();
|
|
1232
1252
|
notifyRecordingPersistence(ctx);
|
|
1233
1253
|
}));
|
|
1234
1254
|
});
|
|
1235
1255
|
pi.on("session_tree", async (_event, ctx) => {
|
|
1256
|
+
branchRestoreGeneration += 1;
|
|
1236
1257
|
for (const controller of activeScriptControllers)
|
|
1237
1258
|
controller.abort();
|
|
1238
1259
|
await Promise.allSettled([...activeScriptExecutions]);
|
|
1239
|
-
await
|
|
1260
|
+
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1240
1261
|
restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1241
1262
|
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1242
1263
|
flushRecordingReservations();
|
|
1243
1264
|
notifyRecordingPersistence(ctx);
|
|
1244
1265
|
}));
|
|
1245
1266
|
});
|
|
1246
|
-
//
|
|
1247
|
-
// Pi owns awaited tools/events (including their execution queues).
|
|
1248
|
-
// children and retained browser/recording resources need extension-side checks.
|
|
1267
|
+
// Fork-only checkpoint event; official Pi validation types do not declare it.
|
|
1268
|
+
// Pi owns awaited tools/events (including their execution queues). Detached code
|
|
1269
|
+
// children and retained browser/recording resources still need extension-side checks.
|
|
1249
1270
|
const checkpointPi = pi;
|
|
1250
1271
|
checkpointPi.on("session_checkpoint", async (event, ctx) => {
|
|
1251
1272
|
const blocked = (reason) => ({ sleepReady: false, reason });
|
|
1252
1273
|
if (activeScriptControllers.size || activeScriptExecutions.size)
|
|
1253
|
-
return blocked("Browser
|
|
1274
|
+
return blocked("Browser code execution is pending");
|
|
1254
1275
|
flushRecordingReservations();
|
|
1255
1276
|
if (recordingReservationsDirty)
|
|
1256
1277
|
return blocked("Browser recording journal persistence is dirty");
|
|
@@ -1276,9 +1297,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1276
1297
|
if (managedSessionActive)
|
|
1277
1298
|
trackOwnedManagedSession(sessions, managedSessionName, managedSessionCwd, { namespace: managedSessionNamespace });
|
|
1278
1299
|
for (const entry of entries) {
|
|
1279
|
-
|
|
1300
|
+
const message = getBrowserResultMessage(entry);
|
|
1301
|
+
if (!message)
|
|
1280
1302
|
continue;
|
|
1281
|
-
const details = isRecord(
|
|
1303
|
+
const details = isRecord(message.details) ? message.details : undefined;
|
|
1282
1304
|
// Helpers can launch before a main-process failure. Conversely, native
|
|
1283
1305
|
// launch flags on sessionless reads can open the unnamed default browser.
|
|
1284
1306
|
const sessionName = typeof details?.sessionName === "string" ? details.sessionName
|
|
@@ -1322,7 +1344,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1322
1344
|
branchRestoreGeneration += 1;
|
|
1323
1345
|
branchStateGeneration += 1;
|
|
1324
1346
|
let preservedElectronProfileDirs = [];
|
|
1325
|
-
await
|
|
1347
|
+
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1326
1348
|
const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
|
|
1327
1349
|
const quitting = event?.reason === "quit";
|
|
1328
1350
|
preservedElectronProfileDirs = quitting
|
|
@@ -1396,9 +1418,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1396
1418
|
const runtimeConfigPrompt = browserGuidance.length > 0
|
|
1397
1419
|
? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
|
|
1398
1420
|
: "";
|
|
1399
|
-
|
|
1400
|
-
systemPrompt: `${event.systemPrompt}\n\n${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`,
|
|
1401
|
-
};
|
|
1421
|
+
event.systemPromptOptions.sections.agent_browser = `${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`;
|
|
1402
1422
|
});
|
|
1403
1423
|
pi.on("tool_call", async (event, ctx) => {
|
|
1404
1424
|
const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
|
|
@@ -1415,12 +1435,6 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1415
1435
|
});
|
|
1416
1436
|
pi.on("tool_result", async (event) => buildAgentBrowserToolResultPatch(event));
|
|
1417
1437
|
const agentBrowserTool = {
|
|
1418
|
-
name: "agent_browser",
|
|
1419
|
-
label: "Agent Browser",
|
|
1420
|
-
description: "Browse and interact with websites using agent-browser. Use this for reading live pages, opening known URLs, 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.",
|
|
1421
|
-
promptSnippet: "Browse websites, read live docs, click and fill pages, extract browser content, take screenshots, and automate real web workflows.",
|
|
1422
|
-
promptGuidelines: toolPromptGuidelines,
|
|
1423
|
-
parameters: AGENT_BROWSER_PARAMS,
|
|
1424
1438
|
renderCall(args, theme, context) {
|
|
1425
1439
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1426
1440
|
text.setText(formatAgentBrowserRenderCall(args, theme, context.expanded));
|
|
@@ -1433,479 +1447,598 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1433
1447
|
component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
|
|
1434
1448
|
return component;
|
|
1435
1449
|
},
|
|
1436
|
-
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId) {
|
|
1450
|
+
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true) {
|
|
1451
|
+
let operationCwd;
|
|
1452
|
+
try {
|
|
1453
|
+
operationCwd = capturedCwd ?? resolveExecutionCwd(pi, ctx);
|
|
1454
|
+
}
|
|
1455
|
+
catch (error) {
|
|
1456
|
+
return buildValidationFailureResult({ kind: "invalid", status: "invalid", redactedArgs: [], toolArgs: [], validationError: error instanceof Error ? error.message : String(error) });
|
|
1457
|
+
}
|
|
1437
1458
|
const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
|
|
1438
1459
|
const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
|
|
1439
|
-
const
|
|
1440
|
-
getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd:
|
|
1460
|
+
const admittedInput = resolveAgentBrowserInput({
|
|
1461
|
+
getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: operationCwd, outputPath, stdin }),
|
|
1441
1462
|
params,
|
|
1442
1463
|
});
|
|
1443
|
-
if (
|
|
1444
|
-
return buildValidationFailureResult(
|
|
1464
|
+
if (admittedInput.status === "invalid") {
|
|
1465
|
+
return buildValidationFailureResult(admittedInput);
|
|
1445
1466
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1467
|
+
const explicitConfig = scanUpstreamGlobalFlagOccurrences(admittedInput.toolArgs, "--config").length > 0 || getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIG !== undefined;
|
|
1468
|
+
// Managed selection must follow earlier queued fresh launches, while file paths keep their admission snapshot.
|
|
1469
|
+
const managedAtAdmission = admittedInput.kind !== "electron"
|
|
1470
|
+
&& !extractExplicitSessionName(admittedInput.toolArgs) && !explicitConfig;
|
|
1471
|
+
const executeAdmitted = async () => {
|
|
1472
|
+
let resolvedInput = admittedInput;
|
|
1473
|
+
const requestedSession = extractExplicitSessionName(resolvedInput.toolArgs);
|
|
1474
|
+
const requestedNamespace = resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
|
|
1475
|
+
const requestedOwner = requestedSession ? ownedManagedSessions.get(getSessionContextKey(requestedSession, requestedNamespace) ?? requestedSession) : undefined;
|
|
1476
|
+
const browserCwd = (params.sessionMode === "fresh" && !requestedSession) || explicitConfig ? operationCwd
|
|
1477
|
+
: requestedSession ? requestedOwner?.cwd ?? ctx.cwd : managedSessionActive ? managedSessionCwd : ctx.cwd;
|
|
1478
|
+
const browserCwdError = resolvedInput.kind === "electron" ? undefined : getBrowserCwdError(browserCwd);
|
|
1479
|
+
if (browserCwdError)
|
|
1480
|
+
return buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: browserCwdError });
|
|
1481
|
+
if (!isPlainTextInspectionArgs(resolvedInput.toolArgs) && (operationCwd !== browserCwd || operationCwd !== ctx.cwd)) {
|
|
1482
|
+
const bound = resolveOperationPaths(resolvedInput.toolArgs, resolvedInput.toolStdin, operationCwd);
|
|
1483
|
+
resolvedInput = { ...resolvedInput, toolArgs: bound.args, toolStdin: bound.stdin };
|
|
1459
1484
|
}
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
|
|
1475
|
-
});
|
|
1476
|
-
};
|
|
1477
|
-
const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
|
|
1478
|
-
const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
|
|
1479
|
-
const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
|
|
1480
|
-
if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
|
|
1481
|
-
const versionFailure = await validateUpstreamVersion(ctx.cwd, signal);
|
|
1482
|
-
if (versionFailure)
|
|
1483
|
-
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
|
|
1484
|
-
}
|
|
1485
|
-
if (resolvedInput.kind === "script") {
|
|
1486
|
-
if (!ctx.sessionManager.getSessionFile()) {
|
|
1487
|
-
return buildValidationFailureResult({
|
|
1488
|
-
attemptedKind: "script",
|
|
1489
|
-
kind: "invalid",
|
|
1490
|
-
redactedArgs: [],
|
|
1491
|
-
status: "invalid",
|
|
1492
|
-
toolArgs: [],
|
|
1493
|
-
validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
|
|
1494
|
-
});
|
|
1485
|
+
const executionTimeoutMs = (resolvedInput.kind === "electron" && "timeoutMs" in resolvedInput.compiledElectron ? resolvedInput.compiledElectron.timeoutMs : undefined) ?? params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(resolvedInput.toolArgs), resolvedInput.toolStdin) ?? getAgentBrowserProcessTimeoutMs();
|
|
1486
|
+
await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
|
|
1487
|
+
const runtimeBrowserConfig = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
|
|
1488
|
+
const rootProfile = runtimeBrowserConfig.trustedBrowserDefaultProfile;
|
|
1489
|
+
const pendingReadConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
1490
|
+
const rootSessionId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
|
|
1491
|
+
? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
|
|
1492
|
+
return withNativeSessionDefaults(resolvedInput, browserCwd, signal, async (resolvedInput, withLaunchDefaults) => {
|
|
1493
|
+
const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
1494
|
+
if (readConfirmation)
|
|
1495
|
+
resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
|
|
1496
|
+
if (resolvedInput.kind === "qa" && resolvedInput.compiledQaPreset.checks.attached && !managedSessionActive && !extractExplicitSessionName(resolvedInput.toolArgs)) {
|
|
1497
|
+
return buildValidationFailureResult({ ...resolvedInput, attemptedKind: "qa", kind: "invalid", status: "invalid", validationError: "qa.attached requires an active attached session. Run electron.launch or connect to an Electron debug port first, or configure a native shared session." });
|
|
1495
1498
|
}
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1499
|
+
const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
|
|
1500
|
+
if (!outputPath || !canWriteAgentBrowserOutput(result))
|
|
1501
|
+
return warnRecordingPersistence(result);
|
|
1502
|
+
return artifactExecutionQueue.run(async () => {
|
|
1503
|
+
flushRecordingReservations();
|
|
1504
|
+
const reservationError = getArtifactPreflightValidationError({
|
|
1505
|
+
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1506
|
+
args: [],
|
|
1507
|
+
cwd: operationCwd,
|
|
1508
|
+
outputPath,
|
|
1509
|
+
});
|
|
1510
|
+
if (reservationError) {
|
|
1511
|
+
return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
|
|
1512
|
+
}
|
|
1513
|
+
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
|
|
1514
|
+
});
|
|
1510
1515
|
};
|
|
1511
|
-
const
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1514
|
-
if (signal?.aborted)
|
|
1515
|
-
|
|
1516
|
-
activeScriptControllers.add(scriptController);
|
|
1517
|
-
let finishScriptExecution;
|
|
1518
|
-
const scriptExecution = new Promise((resolve) => {
|
|
1519
|
-
finishScriptExecution = resolve;
|
|
1520
|
-
});
|
|
1521
|
-
activeScriptExecutions.add(scriptExecution);
|
|
1522
|
-
try {
|
|
1523
|
-
// Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
|
|
1524
|
-
const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(ctx.cwd, scriptController.signal));
|
|
1516
|
+
const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
|
|
1517
|
+
const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
|
|
1518
|
+
const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
|
|
1519
|
+
if (!electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
|
|
1520
|
+
const versionFailure = await validateUpstreamVersion(browserCwd, signal);
|
|
1525
1521
|
if (versionFailure)
|
|
1526
|
-
return applyAgentBrowserOutputPath({ cwd:
|
|
1527
|
-
const pendingRun = runAgentBrowserScript({
|
|
1528
|
-
beforeFirstCall() {
|
|
1529
|
-
appendScriptSessionLease(pi, sessionName, "active");
|
|
1530
|
-
trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
|
|
1531
|
-
managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1532
|
-
// This fresh, unselectable lease owns any daemon its first read starts.
|
|
1533
|
-
managedSessionRestoreState.recordDaemonRestoreKey(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE, null);
|
|
1534
|
-
leased = true;
|
|
1535
|
-
},
|
|
1536
|
-
code: resolvedInput.compiledScript.code,
|
|
1537
|
-
dispatch: async (innerParams, innerSignal) => {
|
|
1538
|
-
const remainingMs = Math.max(1, deadline - Date.now());
|
|
1539
|
-
const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
|
|
1540
|
-
const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
|
|
1541
|
-
args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
|
|
1542
|
-
stdin: innerParams.stdin,
|
|
1543
|
-
timeoutMs: innerTimeoutMs,
|
|
1544
|
-
}, innerSignal, undefined, ctx, nativeToolCallId));
|
|
1545
|
-
innerResults.push(innerResult);
|
|
1546
|
-
return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
|
|
1547
|
-
},
|
|
1548
|
-
signal: scriptController.signal,
|
|
1549
|
-
timeoutMs: scriptTimeoutMs,
|
|
1550
|
-
});
|
|
1551
|
-
run = await pendingRun;
|
|
1522
|
+
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: versionFailure });
|
|
1552
1523
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1524
|
+
const { toolArgs } = resolvedInput;
|
|
1525
|
+
const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
|
|
1526
|
+
const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
|
|
1527
|
+
const runElectronHostInput = async (executionSignal = signal) => {
|
|
1528
|
+
const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
|
|
1529
|
+
branchRecords: electronLaunchRecords,
|
|
1530
|
+
compiledElectron,
|
|
1531
|
+
ownedRecords: ownedElectronLaunchRecords,
|
|
1532
|
+
});
|
|
1533
|
+
let electronHostResult = await handleElectronHostInput({
|
|
1534
|
+
attachedSessionKeys,
|
|
1535
|
+
compiledElectron,
|
|
1536
|
+
cwd: ctx.cwd,
|
|
1537
|
+
electronChildProcesses,
|
|
1538
|
+
electronLaunchRecords: electronHostLaunchRecords,
|
|
1539
|
+
implicitSessionCloseTimeoutMs,
|
|
1540
|
+
managedSessionActive,
|
|
1541
|
+
managedSessionName,
|
|
1542
|
+
managedSessionNamespace,
|
|
1543
|
+
managedSessionRestoreState,
|
|
1544
|
+
ownedManagedSessions,
|
|
1545
|
+
redactedCompiledElectron,
|
|
1546
|
+
sessionPageState,
|
|
1547
|
+
signal: executionSignal,
|
|
1548
|
+
});
|
|
1549
|
+
if (electronHostResult && compiledElectron?.action === "cleanup") {
|
|
1550
|
+
branchStateGeneration += 1;
|
|
1551
|
+
const cleanupRecords = isRecord(electronHostResult.details)
|
|
1552
|
+
&& isRecord(electronHostResult.details.electron)
|
|
1553
|
+
&& isRecord(electronHostResult.details.electron.cleanup)
|
|
1554
|
+
&& Array.isArray(electronHostResult.details.electron.cleanup.results)
|
|
1555
|
+
? electronHostResult.details.electron.cleanup.results
|
|
1556
|
+
: [];
|
|
1557
|
+
const cleanedLaunchIds = new Set();
|
|
1558
|
+
for (const cleanupResult of cleanupRecords) {
|
|
1559
|
+
if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
|
|
1560
|
+
cleanedLaunchIds.add(cleanupResult.record.launchId);
|
|
1561
|
+
}
|
|
1563
1562
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1563
|
+
replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
|
|
1564
|
+
mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
|
|
1565
|
+
const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
|
|
1566
|
+
? electronHostResult.details.namespace
|
|
1567
|
+
: undefined;
|
|
1568
|
+
const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
|
|
1569
|
+
syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
|
|
1570
|
+
for (const identity of closedSessionIdentities) {
|
|
1571
|
+
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1572
|
+
const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
|
|
1573
|
+
clearSessionScopedBrowserState(closedSessionKey);
|
|
1574
|
+
if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
|
|
1575
|
+
managedSessionActive = false;
|
|
1576
|
+
managedSessionCompatibilityWorkaround = undefined;
|
|
1577
|
+
managedSessionHeadedAutosaveDisabled = false;
|
|
1578
|
+
managedSessionHeadedAutosaveInterval = undefined;
|
|
1579
|
+
managedSessionNamespace = undefined;
|
|
1580
|
+
freshSessionOrdinal += 1;
|
|
1581
|
+
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
1568
1582
|
}
|
|
1569
|
-
catch { }
|
|
1570
1583
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
...(isRecord(scriptResult.details) ? scriptResult.details : {}),
|
|
1581
|
-
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1582
|
-
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1583
|
-
},
|
|
1584
|
-
};
|
|
1585
|
-
}
|
|
1586
|
-
return applyUnserializedOutputPath(scriptResult);
|
|
1587
|
-
}
|
|
1588
|
-
const { toolArgs } = resolvedInput;
|
|
1589
|
-
const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
|
|
1590
|
-
const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
|
|
1591
|
-
const runElectronHostInput = async () => {
|
|
1592
|
-
const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
|
|
1593
|
-
branchRecords: electronLaunchRecords,
|
|
1594
|
-
compiledElectron,
|
|
1595
|
-
ownedRecords: ownedElectronLaunchRecords,
|
|
1596
|
-
});
|
|
1597
|
-
let electronHostResult = await handleElectronHostInput({
|
|
1598
|
-
attachedSessionKeys,
|
|
1599
|
-
compiledElectron,
|
|
1600
|
-
cwd: ctx.cwd,
|
|
1601
|
-
electronChildProcesses,
|
|
1602
|
-
electronLaunchRecords: electronHostLaunchRecords,
|
|
1603
|
-
implicitSessionCloseTimeoutMs,
|
|
1604
|
-
managedSessionActive,
|
|
1605
|
-
managedSessionName,
|
|
1606
|
-
managedSessionNamespace,
|
|
1607
|
-
managedSessionRestoreState,
|
|
1608
|
-
ownedManagedSessions,
|
|
1609
|
-
redactedCompiledElectron,
|
|
1610
|
-
sessionPageState,
|
|
1611
|
-
signal,
|
|
1612
|
-
});
|
|
1613
|
-
if (electronHostResult && compiledElectron?.action === "cleanup") {
|
|
1614
|
-
branchStateGeneration += 1;
|
|
1615
|
-
const cleanupRecords = isRecord(electronHostResult.details)
|
|
1616
|
-
&& isRecord(electronHostResult.details.electron)
|
|
1617
|
-
&& isRecord(electronHostResult.details.electron.cleanup)
|
|
1618
|
-
&& Array.isArray(electronHostResult.details.electron.cleanup.results)
|
|
1619
|
-
? electronHostResult.details.electron.cleanup.results
|
|
1620
|
-
: [];
|
|
1621
|
-
const cleanedLaunchIds = new Set();
|
|
1622
|
-
for (const cleanupResult of cleanupRecords) {
|
|
1623
|
-
if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
|
|
1624
|
-
cleanedLaunchIds.add(cleanupResult.record.launchId);
|
|
1584
|
+
if (artifactManifest) {
|
|
1585
|
+
electronHostResult = {
|
|
1586
|
+
...electronHostResult,
|
|
1587
|
+
details: {
|
|
1588
|
+
...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
|
|
1589
|
+
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1590
|
+
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1625
1593
|
}
|
|
1626
1594
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
? electronHostResult.details.namespace
|
|
1631
|
-
: undefined;
|
|
1632
|
-
const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
|
|
1633
|
-
syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
|
|
1634
|
-
for (const identity of closedSessionIdentities) {
|
|
1635
|
-
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1636
|
-
const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
|
|
1637
|
-
clearSessionScopedBrowserState(closedSessionKey);
|
|
1638
|
-
if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
|
|
1639
|
-
managedSessionActive = false;
|
|
1640
|
-
managedSessionCompatibilityWorkaround = undefined;
|
|
1641
|
-
managedSessionHeadedAutosaveDisabled = false;
|
|
1642
|
-
managedSessionHeadedAutosaveInterval = undefined;
|
|
1643
|
-
managedSessionNamespace = undefined;
|
|
1644
|
-
freshSessionOrdinal += 1;
|
|
1645
|
-
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
1646
|
-
}
|
|
1595
|
+
if (electronHostResult && executionSignal?.aborted) {
|
|
1596
|
+
const failure = browserExecutionFailure(executionSignal.reason, executionSignal);
|
|
1597
|
+
return { ...electronHostResult, ...failure, details: { ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}), ...(isRecord(failure.details) ? failure.details : {}) } };
|
|
1647
1598
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1599
|
+
return electronHostResult;
|
|
1600
|
+
};
|
|
1601
|
+
const runCoordinatedElectronHostInput = async () => {
|
|
1602
|
+
const execute = (executionSignal = signal) => compiledElectron?.action === "cleanup"
|
|
1603
|
+
? artifactExecutionQueue.run(async () => {
|
|
1604
|
+
flushRecordingReservations();
|
|
1605
|
+
const error = outputPath ? getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: [], cwd: operationCwd, outputPath }) : undefined;
|
|
1606
|
+
if (error)
|
|
1607
|
+
return warnRecordingPersistence(buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: error }));
|
|
1608
|
+
const result = await runElectronHostInput(executionSignal);
|
|
1609
|
+
return result ? applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: warnRecordingPersistence(result) }) : result;
|
|
1610
|
+
}) : runElectronHostInput(executionSignal);
|
|
1611
|
+
if (!shouldSerializeElectronHostInput(compiledElectron))
|
|
1612
|
+
return execute();
|
|
1613
|
+
const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords });
|
|
1614
|
+
const launchId = compiledElectron && "launchId" in compiledElectron ? compiledElectron.launchId : undefined;
|
|
1615
|
+
const selected = launchId ? [records.get(launchId)].filter((record) => record !== undefined) : getActiveElectronRecords(records);
|
|
1616
|
+
const sessions = selected.flatMap(record => record.sessionName ? [{ sessionName: record.sessionName, namespace: record.namespace }] : []);
|
|
1617
|
+
if (compiledElectron?.action === "probe" && !launchId && managedSessionActive)
|
|
1618
|
+
sessions.push({ sessionName: managedSessionName, namespace: managedSessionNamespace });
|
|
1619
|
+
if (sessions.length === 0)
|
|
1620
|
+
return execute();
|
|
1621
|
+
try {
|
|
1622
|
+
const identities = await Promise.all(sessions.map(session => resolveBrowserExecutionIdentity({ ...session, ownedManagedSession: true })));
|
|
1623
|
+
return await withBrowserExecutionLocks({ identities, signal, deadline: Date.now() + executionTimeoutMs, waitOnly: true }, execute);
|
|
1657
1624
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
};
|
|
1661
|
-
const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
|
|
1662
|
-
? managedSessionExecutionQueue.run(runElectronHostInput)
|
|
1663
|
-
: runElectronHostInput();
|
|
1664
|
-
const electronHostResult = compiledElectron?.action === "cleanup"
|
|
1665
|
-
? await artifactExecutionQueue.run(async () => {
|
|
1666
|
-
flushRecordingReservations();
|
|
1667
|
-
const reservationError = outputPath ? getArtifactPreflightValidationError({
|
|
1668
|
-
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1669
|
-
args: [],
|
|
1670
|
-
cwd: ctx.cwd,
|
|
1671
|
-
outputPath,
|
|
1672
|
-
}) : undefined;
|
|
1673
|
-
if (reservationError) {
|
|
1674
|
-
return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
return browserExecutionFailure(error, signal);
|
|
1675
1627
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
managedSessionName,
|
|
1691
|
-
ownedElectronLaunchRecords,
|
|
1692
|
-
ownedManagedSessions,
|
|
1693
|
-
});
|
|
1694
|
-
const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
|
|
1695
|
-
? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
|
|
1696
|
-
: undefined;
|
|
1697
|
-
const runBrowserCommand = async (daemonInactive) => {
|
|
1698
|
-
// Load execution-only preparation/output code inside the existing session queue,
|
|
1699
|
-
// before capturing state. Registration, rendering, and restore stay synchronous/eager.
|
|
1700
|
-
const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
|
|
1701
|
-
flushRecordingReservations();
|
|
1702
|
-
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1703
|
-
const generationAtStart = branchStateGeneration;
|
|
1704
|
-
const sessionPageStateUpdate = sessionPageState.beginUpdate();
|
|
1705
|
-
const browserRunState = {
|
|
1706
|
-
activeRecordingReservations,
|
|
1707
|
-
artifactManifest,
|
|
1708
|
-
attachedSessionKeys,
|
|
1709
|
-
closedManagedSessionNames: new Set(),
|
|
1710
|
-
electronChildProcesses,
|
|
1711
|
-
electronLaunchRecords,
|
|
1712
|
-
ephemeralSessionSeed,
|
|
1713
|
-
freshSessionOrdinal,
|
|
1714
|
-
managedSessionActive,
|
|
1715
|
-
managedSessionBaseName,
|
|
1716
|
-
managedSessionCompatibilityWorkaround,
|
|
1717
|
-
managedSessionHeadedAutosaveDisabled,
|
|
1718
|
-
managedSessionHeadedAutosaveInterval,
|
|
1719
|
-
managedSessionCwd,
|
|
1628
|
+
};
|
|
1629
|
+
const electronHostResult = await (shouldSerializeElectronHostInput(compiledElectron)
|
|
1630
|
+
? managedSessionExecutionQueue.run(runCoordinatedElectronHostInput, signal)
|
|
1631
|
+
: runCoordinatedElectronHostInput());
|
|
1632
|
+
if (electronHostResult) {
|
|
1633
|
+
return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
|
|
1634
|
+
}
|
|
1635
|
+
const explicitSessionName = extractExplicitSessionName(toolArgs);
|
|
1636
|
+
const callerOwnedSessionNamespace = explicitSessionName
|
|
1637
|
+
? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
|
|
1638
|
+
: undefined;
|
|
1639
|
+
const serializeBrowserCommand = shouldSerializeBrowserCommand({
|
|
1640
|
+
namespace: callerOwnedSessionNamespace,
|
|
1641
|
+
explicitSessionName,
|
|
1720
1642
|
managedSessionName,
|
|
1721
|
-
|
|
1722
|
-
managedSessionRestoreState,
|
|
1723
|
-
networkRoutesBySession,
|
|
1643
|
+
ownedElectronLaunchRecords,
|
|
1724
1644
|
ownedManagedSessions,
|
|
1725
|
-
sessionPageState,
|
|
1726
|
-
traceOwners,
|
|
1727
|
-
};
|
|
1728
|
-
const selectedPlan = buildExecutionPlan(toolArgs, {
|
|
1729
|
-
freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
|
|
1730
|
-
managedSessionActive: browserRunState.managedSessionActive,
|
|
1731
|
-
managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
|
|
1732
|
-
managedSessionName: browserRunState.managedSessionName,
|
|
1733
|
-
managedSessionNamespace: browserRunState.managedSessionNamespace,
|
|
1734
|
-
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1735
|
-
stdin: resolvedInput.toolStdin,
|
|
1736
|
-
browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
|
|
1737
|
-
});
|
|
1738
|
-
const initialArtifactManifest = browserRunState.artifactManifest;
|
|
1739
|
-
const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
|
|
1740
|
-
const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
|
|
1741
|
-
|| (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
|
|
1742
|
-
const allocatesFreshManagedSession = explicitSessionName === undefined
|
|
1743
|
-
&& (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
|
|
1744
|
-
const reusableSessionKey = allocatesFreshManagedSession
|
|
1745
|
-
? undefined
|
|
1746
|
-
: getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
|
|
1747
|
-
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1748
|
-
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1749
|
-
let result = await runAgentBrowserTool({
|
|
1750
|
-
daemonInactive,
|
|
1751
|
-
ctx,
|
|
1752
|
-
cwd: ctx.cwd,
|
|
1753
|
-
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
1754
|
-
electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
|
|
1755
|
-
implicitSessionCloseTimeoutMs,
|
|
1756
|
-
implicitSessionIdleTimeoutMs,
|
|
1757
|
-
input: resolvedInput,
|
|
1758
|
-
onUpdate,
|
|
1759
|
-
params,
|
|
1760
|
-
establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
|
|
1761
|
-
preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
|
|
1762
|
-
promptPolicy,
|
|
1763
|
-
sessionPageStateUpdate,
|
|
1764
|
-
signal,
|
|
1765
|
-
state: browserRunState,
|
|
1766
1645
|
});
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const
|
|
1777
|
-
const
|
|
1778
|
-
const
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1646
|
+
const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
|
|
1647
|
+
? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
|
|
1648
|
+
: undefined;
|
|
1649
|
+
const runBrowserCommand = async (daemonInactive, executionSignal = signal) => {
|
|
1650
|
+
// Load execution-only preparation/output code inside the existing session queue,
|
|
1651
|
+
// before capturing state. Registration, rendering, and restore stay synchronous/eager.
|
|
1652
|
+
const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
|
|
1653
|
+
flushRecordingReservations();
|
|
1654
|
+
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1655
|
+
const generationAtStart = branchStateGeneration;
|
|
1656
|
+
const sessionPageStateUpdate = sessionPageState.beginUpdate();
|
|
1657
|
+
const browserRunState = {
|
|
1658
|
+
activeRecordingReservations,
|
|
1659
|
+
artifactManifest,
|
|
1660
|
+
attachedSessionKeys,
|
|
1661
|
+
closedManagedSessionNames: new Set(),
|
|
1662
|
+
electronChildProcesses,
|
|
1663
|
+
electronLaunchRecords,
|
|
1664
|
+
ephemeralSessionSeed,
|
|
1665
|
+
freshSessionOrdinal,
|
|
1666
|
+
managedSessionActive,
|
|
1667
|
+
managedSessionBaseName,
|
|
1668
|
+
managedSessionCompatibilityWorkaround,
|
|
1669
|
+
managedSessionHeadedAutosaveDisabled,
|
|
1670
|
+
managedSessionHeadedAutosaveInterval,
|
|
1671
|
+
managedSessionCwd,
|
|
1672
|
+
managedSessionName,
|
|
1673
|
+
managedSessionNamespace,
|
|
1674
|
+
managedSessionRestoreState,
|
|
1675
|
+
networkRoutesBySession,
|
|
1676
|
+
ownedManagedSessions,
|
|
1677
|
+
sessionPageState,
|
|
1678
|
+
traceOwners,
|
|
1679
|
+
};
|
|
1680
|
+
const selectedPlan = buildExecutionPlan(toolArgs, {
|
|
1681
|
+
freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
|
|
1682
|
+
managedSessionActive: browserRunState.managedSessionActive,
|
|
1683
|
+
managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
|
|
1684
|
+
managedSessionName: browserRunState.managedSessionName,
|
|
1685
|
+
managedSessionNamespace: browserRunState.managedSessionNamespace,
|
|
1686
|
+
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1687
|
+
stdin: resolvedInput.toolStdin,
|
|
1688
|
+
browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
|
|
1689
|
+
});
|
|
1690
|
+
const initialArtifactManifest = browserRunState.artifactManifest;
|
|
1691
|
+
const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
|
|
1692
|
+
const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
|
|
1693
|
+
|| (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
|
|
1694
|
+
const allocatesFreshManagedSession = explicitSessionName === undefined
|
|
1695
|
+
&& (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
|
|
1696
|
+
const reusableSessionKey = allocatesFreshManagedSession
|
|
1697
|
+
? undefined
|
|
1698
|
+
: getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
|
|
1699
|
+
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1700
|
+
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1701
|
+
let result = await runAgentBrowserTool({
|
|
1702
|
+
daemonInactive,
|
|
1703
|
+
ctx,
|
|
1704
|
+
cwd: browserCwd,
|
|
1705
|
+
operationCwd,
|
|
1706
|
+
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
1707
|
+
electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
|
|
1708
|
+
implicitSessionCloseTimeoutMs,
|
|
1709
|
+
implicitSessionIdleTimeoutMs,
|
|
1710
|
+
input: resolvedInput,
|
|
1711
|
+
modelVisible,
|
|
1712
|
+
onUpdate,
|
|
1713
|
+
params,
|
|
1714
|
+
establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
|
|
1715
|
+
preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
|
|
1716
|
+
promptPolicy,
|
|
1717
|
+
sessionPageStateUpdate,
|
|
1718
|
+
signal: executionSignal,
|
|
1719
|
+
state: browserRunState,
|
|
1720
|
+
});
|
|
1721
|
+
const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
|
|
1722
|
+
const resultDetails = isRecord(result.details) ? result.details : undefined;
|
|
1723
|
+
if (typeof resultDetails?.sessionName === "string" && browserRunState.managedSessionActive
|
|
1724
|
+
&& getAgentBrowserSessionIdentityKey(resultDetails.sessionName, typeof resultDetails.namespace === "string" ? resultDetails.namespace : undefined) === getAgentBrowserSessionIdentityKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace)) {
|
|
1725
|
+
resultDetails.managedSessionCwd = browserRunState.managedSessionCwd;
|
|
1726
|
+
}
|
|
1727
|
+
const resultSessionName = typeof resultDetails?.sessionName === "string"
|
|
1728
|
+
? resultDetails.sessionName
|
|
1729
|
+
: extractExplicitSessionName(toolArgs);
|
|
1730
|
+
const resultNamespace = typeof resultDetails?.namespace === "string"
|
|
1731
|
+
? resultDetails.namespace
|
|
1732
|
+
: selectedPlan.namespace;
|
|
1733
|
+
if (branchRestoreStillCurrent) {
|
|
1734
|
+
const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
|
|
1735
|
+
const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
|
|
1736
|
+
const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
|
|
1737
|
+
const closeAllApplied = resultDetails?.closeAllApplied === true;
|
|
1738
|
+
const attachedSessionRemainsActive = result.isError !== true
|
|
1739
|
+
|| ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
|
|
1740
|
+
const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
|
|
1741
|
+
|| resultBatchCloseLifecycle?.endsClosed === true;
|
|
1742
|
+
if (closeAllApplied) {
|
|
1743
|
+
deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
|
|
1744
|
+
if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
|
|
1745
|
+
attachedSessionKeys.add(resultSessionKey);
|
|
1746
|
+
result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
else if (resultSessionKey && closesAttachedSession)
|
|
1750
|
+
attachedSessionKeys.delete(resultSessionKey);
|
|
1751
|
+
else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
|
|
1787
1752
|
attachedSessionKeys.add(resultSessionKey);
|
|
1788
1753
|
result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
|
|
1789
1754
|
}
|
|
1790
1755
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
|
|
1801
|
-
const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
|
|
1802
|
-
if (resultDetails?.closeAllApplied === true) {
|
|
1803
|
-
for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
|
|
1804
|
-
if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
|
|
1805
|
-
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1756
|
+
if (branchRestoreStillCurrent) {
|
|
1757
|
+
networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
|
|
1758
|
+
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
|
|
1759
|
+
const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
|
|
1760
|
+
if (resultDetails?.closeAllApplied === true) {
|
|
1761
|
+
for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
|
|
1762
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
|
|
1763
|
+
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1764
|
+
}
|
|
1806
1765
|
}
|
|
1807
1766
|
}
|
|
1767
|
+
for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
|
|
1768
|
+
if (handledBatchCloseKeys.has(closedSessionKey))
|
|
1769
|
+
continue;
|
|
1770
|
+
const reservation = activeRecordingReservations.get(closedSessionKey);
|
|
1771
|
+
if (reservation)
|
|
1772
|
+
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1773
|
+
}
|
|
1774
|
+
if (resultSessionName) {
|
|
1775
|
+
const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
|
|
1776
|
+
if (reservation)
|
|
1777
|
+
result = appendActiveRecordingCleanupAction(result, reservation);
|
|
1778
|
+
}
|
|
1779
|
+
if (artifactManifest) {
|
|
1780
|
+
result = {
|
|
1781
|
+
...result,
|
|
1782
|
+
details: {
|
|
1783
|
+
...(isRecord(result.details) ? result.details : {}),
|
|
1784
|
+
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1785
|
+
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1786
|
+
},
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1808
1789
|
}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1790
|
+
const branchStateStillCurrent = generationAtStart === branchStateGeneration;
|
|
1791
|
+
if (serializeBrowserCommand || branchStateStillCurrent) {
|
|
1792
|
+
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1793
|
+
managedSessionActive = browserRunState.managedSessionActive;
|
|
1794
|
+
managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
|
|
1795
|
+
managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
|
|
1796
|
+
managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
|
|
1797
|
+
managedSessionCwd = browserRunState.managedSessionCwd;
|
|
1798
|
+
managedSessionName = browserRunState.managedSessionName;
|
|
1799
|
+
managedSessionNamespace = browserRunState.managedSessionNamespace;
|
|
1800
|
+
for (const closedSessionName of browserRunState.closedManagedSessionNames) {
|
|
1801
|
+
untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
|
|
1802
|
+
}
|
|
1803
|
+
syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
|
|
1804
|
+
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
1805
|
+
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1806
|
+
touchedLaunchIds: !result.isError
|
|
1807
|
+
? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
|
|
1808
|
+
: undefined,
|
|
1809
|
+
});
|
|
1810
|
+
if (serializeBrowserCommand)
|
|
1811
|
+
branchStateGeneration += 1;
|
|
1820
1812
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1813
|
+
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1814
|
+
};
|
|
1815
|
+
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
1816
|
+
const runWithLaunchDefaults = async () => {
|
|
1817
|
+
// Electron's browser identity is known before its host process supplies the CDP port.
|
|
1818
|
+
const plan = buildExecutionPlan(compiledElectron?.action === "launch" ? ["connect"] : toolArgs, {
|
|
1819
|
+
freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
|
|
1820
|
+
managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionName, managedSessionNamespace,
|
|
1821
|
+
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1822
|
+
stdin: resolvedInput.toolStdin,
|
|
1823
|
+
});
|
|
1824
|
+
const run = (executionSignal = signal) => {
|
|
1825
|
+
const execute = () => withLaunchDefaults
|
|
1826
|
+
? withLaunchDefaults(inactive => runBrowserCommand(inactive, executionSignal), executionSignal)
|
|
1827
|
+
: runBrowserCommand(undefined, executionSignal);
|
|
1828
|
+
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1829
|
+
return execute();
|
|
1830
|
+
return artifactExecutionQueue.run(async () => {
|
|
1831
|
+
const error = getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: toolArgs, cwd: operationCwd, outputPath, stdin: resolvedInput.toolStdin });
|
|
1832
|
+
if (!error)
|
|
1833
|
+
return execute();
|
|
1834
|
+
flushRecordingReservations();
|
|
1835
|
+
return warnRecordingPersistence(buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: error }));
|
|
1836
|
+
});
|
|
1837
|
+
};
|
|
1838
|
+
if (plan.validationError || (!plan.sessionName && plan.commandInfo.command !== "read" && !closesAllSessions))
|
|
1839
|
+
return run();
|
|
1840
|
+
try {
|
|
1841
|
+
const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace));
|
|
1842
|
+
const identities = [await resolveBrowserExecutionIdentity({ namespace: plan.namespace, ownedManagedSession: owned, sessionName: closesAllSessions ? undefined : plan.sessionName ?? "default" })];
|
|
1843
|
+
if (managedSessionActive && (params.sessionMode === "fresh" || compiledElectron?.action === "launch") && !explicitSessionName) {
|
|
1844
|
+
identities.push(await resolveBrowserExecutionIdentity({ namespace: managedSessionNamespace, ownedManagedSession: true, sessionName: managedSessionName }));
|
|
1845
|
+
}
|
|
1846
|
+
const activePageUrl = sessionPageState.get(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace)).tabTarget?.url;
|
|
1847
|
+
const deadline = Date.now() + (params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(toolArgs), resolvedInput.toolStdin, activePageUrl) ?? getAgentBrowserProcessTimeoutMs());
|
|
1848
|
+
return await withBrowserExecutionLocks({ identities, signal, deadline, waitOnly: true }, run);
|
|
1830
1849
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
if (serializeBrowserCommand || branchStateStillCurrent) {
|
|
1834
|
-
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1835
|
-
managedSessionActive = browserRunState.managedSessionActive;
|
|
1836
|
-
managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
|
|
1837
|
-
managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
|
|
1838
|
-
managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
|
|
1839
|
-
managedSessionCwd = browserRunState.managedSessionCwd;
|
|
1840
|
-
managedSessionName = browserRunState.managedSessionName;
|
|
1841
|
-
managedSessionNamespace = browserRunState.managedSessionNamespace;
|
|
1842
|
-
for (const closedSessionName of browserRunState.closedManagedSessionNames) {
|
|
1843
|
-
untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
|
|
1850
|
+
catch (error) {
|
|
1851
|
+
return browserExecutionFailure(error, signal);
|
|
1844
1852
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
const runWithinSessionQueue = () => {
|
|
1860
|
-
if (closesAllSessions)
|
|
1861
|
-
return managedSessionExecutionQueue.run(() => {
|
|
1862
|
-
const plan = buildExecutionPlan(toolArgs, {
|
|
1863
|
-
freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
|
|
1864
|
-
managedSessionActive,
|
|
1865
|
-
managedSessionCompatibilityWorkaround,
|
|
1866
|
-
managedSessionName,
|
|
1867
|
-
managedSessionNamespace,
|
|
1868
|
-
sessionMode: params.sessionMode ?? "auto",
|
|
1869
|
-
stdin: resolvedInput.toolStdin,
|
|
1853
|
+
};
|
|
1854
|
+
const runWithinSessionQueue = () => {
|
|
1855
|
+
if (closesAllSessions)
|
|
1856
|
+
return managedSessionExecutionQueue.run(() => {
|
|
1857
|
+
const plan = buildExecutionPlan(toolArgs, {
|
|
1858
|
+
freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
|
|
1859
|
+
managedSessionActive,
|
|
1860
|
+
managedSessionCompatibilityWorkaround,
|
|
1861
|
+
managedSessionName,
|
|
1862
|
+
managedSessionNamespace,
|
|
1863
|
+
sessionMode: params.sessionMode ?? "auto",
|
|
1864
|
+
stdin: resolvedInput.toolStdin,
|
|
1865
|
+
});
|
|
1866
|
+
return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
|
|
1870
1867
|
});
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
: runWithLaunchDefaults();
|
|
1878
|
-
};
|
|
1879
|
-
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1868
|
+
if (serializeBrowserCommand)
|
|
1869
|
+
return managedSessionExecutionQueue.run(runWithLaunchDefaults, signal);
|
|
1870
|
+
return callerOwnedSessionQueueKey
|
|
1871
|
+
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults, signal)
|
|
1872
|
+
: runWithLaunchDefaults();
|
|
1873
|
+
};
|
|
1880
1874
|
return runWithinSessionQueue();
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
cwd: ctx.cwd,
|
|
1886
|
-
outputPath,
|
|
1887
|
-
stdin: resolvedInput.toolStdin,
|
|
1888
|
-
});
|
|
1889
|
-
if (!artifactValidationError)
|
|
1890
|
-
return runWithinSessionQueue();
|
|
1891
|
-
flushRecordingReservations();
|
|
1892
|
-
return warnRecordingPersistence(buildValidationFailureResult({
|
|
1893
|
-
attemptedKind: resolvedInput.kind,
|
|
1894
|
-
kind: "invalid",
|
|
1895
|
-
redactedArgs: resolvedInput.redactedArgs,
|
|
1896
|
-
status: "invalid",
|
|
1897
|
-
toolArgs: resolvedInput.toolArgs,
|
|
1898
|
-
toolStdin: resolvedInput.toolStdin,
|
|
1899
|
-
validationError: artifactValidationError,
|
|
1900
|
-
}));
|
|
1875
|
+
}, managedSessionActive || freshSessionOrdinal > 0 || params.sessionMode === "fresh" || pendingReadConfirmation ? undefined : {
|
|
1876
|
+
id: rootSessionId,
|
|
1877
|
+
profile: rootProfile?.policy === "always" && !/[\\/~]/.test(rootProfile.name) ? rootProfile.name : undefined,
|
|
1878
|
+
executablePath: runtimeBrowserConfig.trustedBrowserExecutablePath,
|
|
1901
1879
|
});
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
});
|
|
1880
|
+
};
|
|
1881
|
+
if (!managedAtAdmission)
|
|
1882
|
+
return executeAdmitted();
|
|
1883
|
+
return managedSessionExecutionQueue.run(executeAdmitted, signal);
|
|
1907
1884
|
},
|
|
1908
1885
|
};
|
|
1909
|
-
|
|
1886
|
+
const executeCode = async (toolCallId, params, signal, _onUpdate, ctx) => {
|
|
1887
|
+
if (!ctx.sessionManager.getSessionFile())
|
|
1888
|
+
return browserExecutionFailure(new Error("agent_browser_code requires a persisted Pi session for ordered browser-state recovery; relaunch without --no-session."), signal, "validation-error");
|
|
1889
|
+
let operationCwd;
|
|
1890
|
+
try {
|
|
1891
|
+
operationCwd = resolveExecutionCwd(pi, ctx);
|
|
1892
|
+
}
|
|
1893
|
+
catch (error) {
|
|
1894
|
+
return browserExecutionFailure(error, signal, "validation-error");
|
|
1895
|
+
}
|
|
1896
|
+
const controller = new AbortController();
|
|
1897
|
+
const abort = () => controller.abort(signal?.reason);
|
|
1898
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1899
|
+
if (signal?.aborted)
|
|
1900
|
+
abort();
|
|
1901
|
+
activeScriptControllers.add(controller);
|
|
1902
|
+
let finish;
|
|
1903
|
+
const execution = new Promise(resolve => { finish = resolve; });
|
|
1904
|
+
activeScriptExecutions.add(execution);
|
|
1905
|
+
const deadline = Date.now() + (params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS);
|
|
1906
|
+
const deadlineTimer = setTimeout(() => controller.abort(new DOMException("Browser code deadline exceeded.", "TimeoutError")), Math.max(0, deadline - Date.now()));
|
|
1907
|
+
const generation = branchRestoreGeneration;
|
|
1908
|
+
const output = createBrowserCodeOutput();
|
|
1909
|
+
const nativeEnvironment = getAgentBrowserProcessEnvironment();
|
|
1910
|
+
try {
|
|
1911
|
+
const args = [
|
|
1912
|
+
...(params.namespace !== undefined ? ["--namespace", params.namespace] : []),
|
|
1913
|
+
...(params.session !== undefined ? ["--session", params.session] : []),
|
|
1914
|
+
"get", "url",
|
|
1915
|
+
];
|
|
1916
|
+
const input = resolveAgentBrowserInput({ params: { args }, getBatchPreflightValidationError: () => undefined });
|
|
1917
|
+
if (input.status === "invalid")
|
|
1918
|
+
return buildValidationFailureResult(input);
|
|
1919
|
+
const config = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
|
|
1920
|
+
const profile = config.trustedBrowserDefaultProfile;
|
|
1921
|
+
const rootId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
|
|
1922
|
+
? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
|
|
1923
|
+
const runAdmitted = async () => {
|
|
1924
|
+
const owner = params.session ? ownedManagedSessions.get(getAgentBrowserSessionIdentityKey(params.session, params.namespace ?? nativeEnvironment.AGENT_BROWSER_NAMESPACE)) : undefined;
|
|
1925
|
+
const browserCwd = nativeEnvironment.AGENT_BROWSER_CONFIG !== undefined ? operationCwd
|
|
1926
|
+
: params.session ? owner?.cwd ?? ctx.cwd : managedSessionActive ? managedSessionCwd : ctx.cwd;
|
|
1927
|
+
const cwdError = getBrowserCwdError(browserCwd);
|
|
1928
|
+
if (cwdError)
|
|
1929
|
+
return browserExecutionFailure(new Error(cwdError), controller.signal, "validation-error");
|
|
1930
|
+
return withNativeSessionDefaults(input, browserCwd, controller.signal, async (input) => {
|
|
1931
|
+
const plan = buildExecutionPlan(input.toolArgs, { freshSessionName: managedSessionName, managedSessionActive, managedSessionName, managedSessionNamespace, sessionMode: "auto" });
|
|
1932
|
+
const sessionName = plan.sessionName ?? "default";
|
|
1933
|
+
const namespace = plan.namespace || undefined;
|
|
1934
|
+
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
1935
|
+
const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(key);
|
|
1936
|
+
const run = async () => {
|
|
1937
|
+
const identity = await resolveBrowserExecutionIdentity({ sessionName, namespace, ownedManagedSession: owned });
|
|
1938
|
+
return withBrowserExecutionLock({ identity, deadline, signal: controller.signal }, async (executionSignal) => {
|
|
1939
|
+
let journalFailed = false;
|
|
1940
|
+
const run = await runAgentBrowserScript({
|
|
1941
|
+
code: params.code, signal: executionSignal, timeoutMs: Math.max(1, deadline - Date.now()),
|
|
1942
|
+
emitImage: output.emitImage,
|
|
1943
|
+
dispatch: async (inner, innerSignal) => {
|
|
1944
|
+
if (journalFailed)
|
|
1945
|
+
throw new Error("Browser-state persistence failed. Inspect the current page before continuing in another tool call.");
|
|
1946
|
+
const { args } = bindBrowserCodeCall(inner, { sessionName, namespace });
|
|
1947
|
+
if (plan.usedImplicitSession && !managedSessionActive && managedSessionName === sessionName && extractExplicitSessionName(inner.args) === undefined)
|
|
1948
|
+
args.splice(args.indexOf("--session"), 2);
|
|
1949
|
+
const pending = { args: redactInvocationArgs(args), sessionName, namespace, resultCategory: "failure", sessionTabTargetUnknown: true };
|
|
1950
|
+
try {
|
|
1951
|
+
appendBrowserTransition(pi, toolCallId, pending, true);
|
|
1952
|
+
}
|
|
1953
|
+
catch {
|
|
1954
|
+
journalFailed = true;
|
|
1955
|
+
throw new Error("Could not persist browser-state intent; the browser command was not run.");
|
|
1956
|
+
}
|
|
1957
|
+
// Resolve launch defaults from the real command, never the routing-only get-url probe.
|
|
1958
|
+
const result = await withAgentBrowserProcessEnvironment({ AGENT_BROWSER_SESSION: nativeEnvironment.AGENT_BROWSER_SESSION }, () => agentBrowserTool.execute(toolCallId, { args, stdin: inner.stdin, timeoutMs: Math.min(inner.timeoutMs ?? deadline - Date.now(), Math.max(1, deadline - Date.now())) }, innerSignal, undefined, ctx, toolCallId, operationCwd, false));
|
|
1959
|
+
const page = sessionPageState.get(key);
|
|
1960
|
+
const details = { ...(isRecord(result.details) ? result.details : {}), sessionName, namespace, sessionTabTarget: page.tabTarget, sessionTabTargetUnknown: page.tabTargetUnknown, sessionTabReopenPending: page.tabReopenPending, refSnapshot: page.refSnapshot, refSnapshotInvalidation: page.refSnapshotInvalidation };
|
|
1961
|
+
if (generation === branchRestoreGeneration) {
|
|
1962
|
+
try {
|
|
1963
|
+
appendBrowserTransition(pi, toolCallId, details, result.isError === true);
|
|
1964
|
+
}
|
|
1965
|
+
catch {
|
|
1966
|
+
journalFailed = true;
|
|
1967
|
+
throw new Error("The browser command finished, but its state could not be persisted. Inspect before retrying; changes may already have happened.");
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return output.observe(result);
|
|
1971
|
+
},
|
|
1972
|
+
});
|
|
1973
|
+
const result = await output.finish(run, sessionName, namespace);
|
|
1974
|
+
if (artifactManifest && isRecord(result.details))
|
|
1975
|
+
result.details.artifactManifest = redactSensitiveValue(artifactManifest);
|
|
1976
|
+
if (!params.outputPath)
|
|
1977
|
+
return result;
|
|
1978
|
+
return artifactExecutionQueue.run(async () => {
|
|
1979
|
+
const error = getArtifactPreflightValidationError({ args: [], cwd: operationCwd, outputPath: params.outputPath, activeRecordingReservations: activeRecordingReservations.values() });
|
|
1980
|
+
if (error) {
|
|
1981
|
+
const failure = browserExecutionFailure(new Error(error), undefined, "validation-error");
|
|
1982
|
+
return { ...result, isError: true, content: [...failure.content, ...result.content], details: { ...(isRecord(result.details) ? result.details : {}), ...(isRecord(failure.details) ? failure.details : {}) } };
|
|
1983
|
+
}
|
|
1984
|
+
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath: params.outputPath, preserveTextContent: true, result });
|
|
1985
|
+
});
|
|
1986
|
+
});
|
|
1987
|
+
};
|
|
1988
|
+
return shouldSerializeBrowserCommand({ namespace, explicitSessionName: sessionName, managedSessionName, ownedElectronLaunchRecords, ownedManagedSessions })
|
|
1989
|
+
? managedSessionExecutionQueue.run(run, controller.signal)
|
|
1990
|
+
: callerOwnedSessionExecutionQueues.run(key, namespace, run, controller.signal);
|
|
1991
|
+
}, managedSessionActive || freshSessionOrdinal > 0 ? undefined : { id: rootId, profile: profile?.policy === "always" && !/[\\/~]/.test(profile.name) ? profile.name : undefined, executablePath: config.trustedBrowserExecutablePath });
|
|
1992
|
+
};
|
|
1993
|
+
return await (!params.session && nativeEnvironment.AGENT_BROWSER_CONFIG === undefined
|
|
1994
|
+
? managedSessionExecutionQueue.run(runAdmitted, controller.signal) : runAdmitted());
|
|
1995
|
+
}
|
|
1996
|
+
catch (error) {
|
|
1997
|
+
return browserExecutionFailure(error, controller.signal);
|
|
1998
|
+
}
|
|
1999
|
+
finally {
|
|
2000
|
+
clearTimeout(deadlineTimer);
|
|
2001
|
+
activeScriptControllers.delete(controller);
|
|
2002
|
+
signal?.removeEventListener("abort", abort);
|
|
2003
|
+
activeScriptExecutions.delete(execution);
|
|
2004
|
+
finish();
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
const finalizeObservation = async (result, params, ctx) => {
|
|
2008
|
+
if ("args" in params && params.args && isPlainTextInspectionArgs(params.args))
|
|
2009
|
+
return result;
|
|
2010
|
+
const { renderAgentBrowserObservation } = await import("./lib/results/presentation/large-output.js");
|
|
2011
|
+
const details = isRecord(result.details) ? result.details : {};
|
|
2012
|
+
if (result.isError && details.error === undefined)
|
|
2013
|
+
details.error = details.validationError ?? details.summary ?? result.content.filter(item => item.type === "text").map(item => item.text).join("\n");
|
|
2014
|
+
const priorManifest = isSessionArtifactManifest(details.artifactManifest) ? details.artifactManifest : undefined;
|
|
2015
|
+
const rendered = await renderAgentBrowserObservation({ content: result.content, details, json: "code" in params || params.args?.includes("--json") === true, succeeded: result.isError !== true, persistentArtifactStore: getPersistentSessionArtifactStore(ctx) });
|
|
2016
|
+
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
|
|
2017
|
+
return { ...result, content: rendered.content, details: { ...details, ...(artifactManifest ? { artifactManifest: redactSensitiveValue(artifactManifest) } : {}) } };
|
|
2018
|
+
};
|
|
2019
|
+
registerAgentBrowserToolSurface(pi, {
|
|
2020
|
+
async execute(id, params, signal, onUpdate, ctx) {
|
|
2021
|
+
let result;
|
|
2022
|
+
try {
|
|
2023
|
+
result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx);
|
|
2024
|
+
}
|
|
2025
|
+
catch (error) {
|
|
2026
|
+
result = browserExecutionFailure(error, signal);
|
|
2027
|
+
}
|
|
2028
|
+
return finalizeObservation(result, params, ctx);
|
|
2029
|
+
},
|
|
2030
|
+
async executeCode(id, params, signal, onUpdate, ctx) {
|
|
2031
|
+
const result = await executeCode(id, params, signal, onUpdate, ctx);
|
|
2032
|
+
return finalizeObservation(result, params, ctx);
|
|
2033
|
+
},
|
|
2034
|
+
executionMode: beforeExecute ? "sequential" : undefined,
|
|
2035
|
+
renderCodeCall(args, theme, context) {
|
|
2036
|
+
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
2037
|
+
text.setText(formatAgentBrowserRenderCall(args, theme, context.expanded));
|
|
2038
|
+
return text;
|
|
2039
|
+
},
|
|
2040
|
+
promptGuidelines: toolPromptGuidelines,
|
|
2041
|
+
renderCall: agentBrowserTool.renderCall, renderResult: agentBrowserTool.renderResult,
|
|
2042
|
+
});
|
|
1910
2043
|
registerWebSearchToolIfAvailable(agentBrowserConfig);
|
|
1911
2044
|
}
|