pi-agent-browser-native 0.6.10 → 0.6.11
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 +32 -0
- package/README.md +49 -6
- package/dist/extensions/agent-browser/index.js +416 -430
- package/dist/extensions/agent-browser/lib/argv-grammar.js +1 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +41 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/script.js +3 -2
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +40 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +3 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +8 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +3 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +25 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +56 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +161 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +2 -4
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +68 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -6
- package/dist/extensions/agent-browser/lib/page-target-validation.js +9 -5
- package/dist/extensions/agent-browser/lib/playbook.js +10 -9
- package/dist/extensions/agent-browser/lib/process-environment.js +26 -8
- package/dist/extensions/agent-browser/lib/process.js +8 -5
- package/dist/extensions/agent-browser/lib/read-confirmation.js +59 -0
- package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -1
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +8 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -5
- package/dist/extensions/agent-browser/lib/results/categories.js +4 -2
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +60 -29
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +19 -8
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -20
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +40 -38
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +3 -3
- package/dist/extensions/agent-browser/lib/results/presentation.js +38 -9
- package/dist/extensions/agent-browser/lib/results/recording.js +50 -0
- package/dist/extensions/agent-browser/lib/runtime.js +72 -20
- package/dist/extensions/agent-browser/lib/session-page-state.js +23 -7
- package/dist/extensions/agent-browser/lib/temp.js +4 -0
- package/docs/ARCHITECTURE.md +26 -10
- package/docs/COMMAND_REFERENCE.md +35 -15
- package/docs/SUPPORT_MATRIX.md +7 -3
- package/docs/TOOL_CONTRACT.md +72 -20
- package/package.json +1 -1
- package/scripts/prepare.mjs +2 -4
|
@@ -5,7 +5,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
5
5
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./lib/batch-lifecycle.js";
|
|
6
6
|
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
7
7
|
import { SessionPageState } from "./lib/session-page-state.js";
|
|
8
|
-
import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, isPlainTextInspectionArgs, } from "./lib/runtime.js";
|
|
8
|
+
import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, redactSensitiveValue, isPlainTextInspectionArgs, } from "./lib/runtime.js";
|
|
9
9
|
import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
|
|
10
10
|
import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
|
|
11
11
|
import { needsManagedSession } from "./lib/command-policy.js";
|
|
@@ -13,6 +13,7 @@ import { ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
|
|
|
13
13
|
import { isRecord } from "./lib/parsing.js";
|
|
14
14
|
import { runAgentBrowserProcess } from "./lib/process.js";
|
|
15
15
|
import { getAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
|
|
16
|
+
import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
|
|
16
17
|
import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
|
|
17
18
|
import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
|
|
18
19
|
import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
|
|
@@ -26,13 +27,14 @@ import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./
|
|
|
26
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, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
|
|
30
|
+
import { applyAgentBrowserOutputPath, canWriteAgentBrowserOutput, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
|
|
30
31
|
import { appendScriptSessionLease, buildScriptBrowserEnvelope, buildScriptToolResult, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
|
|
31
32
|
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingCommand, isSessionArtifactManifest, mergeSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
|
|
32
33
|
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
|
|
33
34
|
import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
|
|
34
35
|
import { appendRecordingReservationTransition, applyRecordingArtifactsToReservations, restoreRecordingReservationStateFromBranch, retireRecordingReservation, } from "./lib/recording-reservations.js";
|
|
35
36
|
import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
|
|
37
|
+
import { scopeReadConfirmationArgs } from "./lib/read-confirmation.js";
|
|
36
38
|
import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
|
|
37
39
|
import { AgentBrowserResultComponent, buildAgentBrowserToolResultPatch, formatAgentBrowserRenderCall, formatAgentBrowserRenderResult, } from "./lib/pi-tool-rendering.js";
|
|
38
40
|
function isBashToolCallEvent(event) {
|
|
@@ -157,26 +159,6 @@ function getResultFileArtifacts(result) {
|
|
|
157
159
|
const details = isRecord(result.details) ? result.details : undefined;
|
|
158
160
|
return Array.isArray(details?.artifacts) ? details.artifacts.filter(isResultFileArtifact) : [];
|
|
159
161
|
}
|
|
160
|
-
function reportsNoRecordingInProgress(value) {
|
|
161
|
-
try {
|
|
162
|
-
return /no recording in progress/i.test(JSON.stringify(value));
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function batchStepReportsNoRecordingInProgress(step) {
|
|
169
|
-
if (!isRecord(step) || step.success !== false)
|
|
170
|
-
return false;
|
|
171
|
-
const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? extractUpstreamCommandTokens(step.command) : [];
|
|
172
|
-
return command[0] === "record" && command[1] === "stop" && reportsNoRecordingInProgress(step);
|
|
173
|
-
}
|
|
174
|
-
function resultReportsNoRecordingInProgress(result) {
|
|
175
|
-
if (result.isError !== true)
|
|
176
|
-
return false;
|
|
177
|
-
const details = isRecord(result.details) ? result.details : undefined;
|
|
178
|
-
return details?.command === "record" && details.subcommand === "stop" && reportsNoRecordingInProgress(result.content);
|
|
179
|
-
}
|
|
180
162
|
function restoreArtifactManifestFromBranch(branch) {
|
|
181
163
|
let restoredManifest;
|
|
182
164
|
for (const entry of branch) {
|
|
@@ -801,7 +783,7 @@ function shouldIncludeProjectConfig(ctx, argv = process.argv) {
|
|
|
801
783
|
return false;
|
|
802
784
|
return ctx?.isProjectTrusted?.() ?? true;
|
|
803
785
|
}
|
|
804
|
-
export default function agentBrowserExtension(pi) {
|
|
786
|
+
export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
805
787
|
const ephemeralSessionSeed = createEphemeralSessionSeed();
|
|
806
788
|
const agentBrowserConfig = loadAgentBrowserConfigSync({
|
|
807
789
|
cwd: process.cwd(),
|
|
@@ -913,6 +895,8 @@ export default function agentBrowserExtension(pi) {
|
|
|
913
895
|
if (result.isError !== true)
|
|
914
896
|
return result;
|
|
915
897
|
const details = isRecord(result.details) ? result.details : {};
|
|
898
|
+
if (details.recordingRecovery)
|
|
899
|
+
return result;
|
|
916
900
|
const cleanupOnly = details.managedSessionCleanupOnlyReason === "restore-disabled-daemon-without-provenance";
|
|
917
901
|
const nextActions = (Array.isArray(details.nextActions) ? details.nextActions : [])
|
|
918
902
|
.filter((action) => !cleanupOnly || action.id !== "stop-pending-recording");
|
|
@@ -973,12 +957,6 @@ export default function agentBrowserExtension(pi) {
|
|
|
973
957
|
const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? step.command : undefined;
|
|
974
958
|
const commandTokens = command ? extractUpstreamCommandTokens(command) : [];
|
|
975
959
|
const commandName = commandTokens[0];
|
|
976
|
-
if (resultSessionName && batchStepReportsNoRecordingInProgress(step)) {
|
|
977
|
-
const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
|
|
978
|
-
retireRecordingSession(resultSessionName, resultNamespace, false);
|
|
979
|
-
handledClosedSessionKeys.add(sessionKey);
|
|
980
|
-
continue;
|
|
981
|
-
}
|
|
982
960
|
if (step.success === true && commandName && isCloseCommand(commandName) && resultSessionName) {
|
|
983
961
|
const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
|
|
984
962
|
retireRecordingSession(resultSessionName, resultNamespace, false);
|
|
@@ -1148,7 +1126,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
1148
1126
|
}
|
|
1149
1127
|
for (const [key, reservation] of activeRecordingReservations) {
|
|
1150
1128
|
const restored = restoredRecordingState.active.get(key);
|
|
1151
|
-
if (restored?.absolutePath !== reservation.absolutePath || restored.cwd !== reservation.cwd)
|
|
1129
|
+
if (restored?.absolutePath !== reservation.absolutePath || restored.cwd !== reservation.cwd || restored.recordingId !== reservation.recordingId || restored.startedAtMs !== reservation.startedAtMs)
|
|
1152
1130
|
recordingReservationsDirty = true;
|
|
1153
1131
|
restoredRecordingState.active.set(key, reservation);
|
|
1154
1132
|
}
|
|
@@ -1380,445 +1358,453 @@ export default function agentBrowserExtension(pi) {
|
|
|
1380
1358
|
component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
|
|
1381
1359
|
return component;
|
|
1382
1360
|
},
|
|
1383
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1361
|
+
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId) {
|
|
1384
1362
|
const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
|
|
1385
1363
|
const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
|
|
1386
1364
|
const resolvedInput = resolveAgentBrowserInput({
|
|
1387
1365
|
getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: ctx.cwd, outputPath, stdin }),
|
|
1388
|
-
managedSessionActive,
|
|
1389
1366
|
params,
|
|
1390
1367
|
});
|
|
1391
1368
|
if (resolvedInput.status === "invalid") {
|
|
1392
1369
|
return buildValidationFailureResult(resolvedInput);
|
|
1393
1370
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
cwd: ctx.cwd,
|
|
1403
|
-
outputPath,
|
|
1404
|
-
});
|
|
1405
|
-
if (reservationError) {
|
|
1406
|
-
return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
|
|
1407
|
-
}
|
|
1408
|
-
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
|
|
1409
|
-
});
|
|
1410
|
-
};
|
|
1411
|
-
const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
|
|
1412
|
-
const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
|
|
1413
|
-
const browserBackedVersionCheck = needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs));
|
|
1414
|
-
if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
|
|
1415
|
-
const versionFailure = await validateUpstreamVersion(ctx.cwd, signal);
|
|
1416
|
-
if (versionFailure)
|
|
1417
|
-
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
|
|
1418
|
-
}
|
|
1419
|
-
if (resolvedInput.kind === "script") {
|
|
1420
|
-
if (!ctx.sessionManager.getSessionFile()) {
|
|
1421
|
-
return buildValidationFailureResult({
|
|
1422
|
-
attemptedKind: "script",
|
|
1423
|
-
kind: "invalid",
|
|
1424
|
-
redactedArgs: [],
|
|
1425
|
-
status: "invalid",
|
|
1426
|
-
toolArgs: [],
|
|
1427
|
-
validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
|
|
1428
|
-
});
|
|
1371
|
+
if (resolvedInput.kind !== "script")
|
|
1372
|
+
await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
|
|
1373
|
+
return withNativeSessionDefaults(resolvedInput, ctx.cwd, signal, async (resolvedInput) => {
|
|
1374
|
+
const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
1375
|
+
if (readConfirmation)
|
|
1376
|
+
resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
|
|
1377
|
+
if (resolvedInput.kind === "qa" && resolvedInput.compiledQaPreset.checks.attached && !managedSessionActive && !extractExplicitSessionName(resolvedInput.toolArgs)) {
|
|
1378
|
+
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." });
|
|
1429
1379
|
}
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1380
|
+
const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
|
|
1381
|
+
if (!outputPath || !canWriteAgentBrowserOutput(result))
|
|
1382
|
+
return warnRecordingPersistence(result);
|
|
1383
|
+
return artifactExecutionQueue.run(async () => {
|
|
1384
|
+
flushRecordingReservations();
|
|
1385
|
+
const reservationError = getArtifactPreflightValidationError({
|
|
1386
|
+
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1387
|
+
args: [],
|
|
1388
|
+
cwd: ctx.cwd,
|
|
1389
|
+
outputPath,
|
|
1390
|
+
});
|
|
1391
|
+
if (reservationError) {
|
|
1392
|
+
return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
|
|
1393
|
+
}
|
|
1394
|
+
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
|
|
1395
|
+
});
|
|
1444
1396
|
};
|
|
1445
|
-
const
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1448
|
-
if (signal?.aborted)
|
|
1449
|
-
|
|
1450
|
-
activeScriptControllers.add(scriptController);
|
|
1451
|
-
let finishScriptExecution;
|
|
1452
|
-
const scriptExecution = new Promise((resolve) => {
|
|
1453
|
-
finishScriptExecution = resolve;
|
|
1454
|
-
});
|
|
1455
|
-
activeScriptExecutions.add(scriptExecution);
|
|
1456
|
-
try {
|
|
1457
|
-
// Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
|
|
1458
|
-
const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(ctx.cwd, scriptController.signal));
|
|
1397
|
+
const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
|
|
1398
|
+
const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
|
|
1399
|
+
const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
|
|
1400
|
+
if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
|
|
1401
|
+
const versionFailure = await validateUpstreamVersion(ctx.cwd, signal);
|
|
1459
1402
|
if (versionFailure)
|
|
1460
1403
|
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
|
|
1461
|
-
const pendingRun = runAgentBrowserScript({
|
|
1462
|
-
beforeFirstCall() {
|
|
1463
|
-
appendScriptSessionLease(pi, sessionName, "active");
|
|
1464
|
-
trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
|
|
1465
|
-
managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1466
|
-
leased = true;
|
|
1467
|
-
},
|
|
1468
|
-
code: resolvedInput.compiledScript.code,
|
|
1469
|
-
dispatch: async (innerParams, innerSignal) => {
|
|
1470
|
-
const remainingMs = Math.max(1, deadline - Date.now());
|
|
1471
|
-
const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
|
|
1472
|
-
const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
|
|
1473
|
-
args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
|
|
1474
|
-
stdin: innerParams.stdin,
|
|
1475
|
-
timeoutMs: innerTimeoutMs,
|
|
1476
|
-
}, innerSignal, undefined, ctx));
|
|
1477
|
-
innerResults.push(innerResult);
|
|
1478
|
-
return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
|
|
1479
|
-
},
|
|
1480
|
-
signal: scriptController.signal,
|
|
1481
|
-
timeoutMs: scriptTimeoutMs,
|
|
1482
|
-
});
|
|
1483
|
-
run = await pendingRun;
|
|
1484
1404
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
}
|
|
1496
|
-
catch {
|
|
1497
|
-
cleanupError = "The isolated script session cleanup operation failed.";
|
|
1498
|
-
try {
|
|
1499
|
-
appendScriptSessionLease(pi, sessionName, "failed");
|
|
1500
|
-
}
|
|
1501
|
-
catch { }
|
|
1502
|
-
}
|
|
1405
|
+
if (resolvedInput.kind === "script") {
|
|
1406
|
+
if (!ctx.sessionManager.getSessionFile()) {
|
|
1407
|
+
return buildValidationFailureResult({
|
|
1408
|
+
attemptedKind: "script",
|
|
1409
|
+
kind: "invalid",
|
|
1410
|
+
redactedArgs: [],
|
|
1411
|
+
status: "invalid",
|
|
1412
|
+
toolArgs: [],
|
|
1413
|
+
validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
|
|
1414
|
+
});
|
|
1503
1415
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1416
|
+
const sessionName = createAgentBrowserScriptSessionName();
|
|
1417
|
+
const innerResults = [];
|
|
1418
|
+
const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
|
|
1419
|
+
const deadline = Date.now() + scriptTimeoutMs;
|
|
1420
|
+
let leased = false;
|
|
1421
|
+
let cleanupError;
|
|
1422
|
+
let run = {
|
|
1423
|
+
callCount: 0,
|
|
1424
|
+
emitCount: 0,
|
|
1425
|
+
error: "Script sandbox execution failed.",
|
|
1426
|
+
failureCategory: "upstream-error",
|
|
1427
|
+
ok: false,
|
|
1428
|
+
rejectedCallCount: 0,
|
|
1429
|
+
steps: [],
|
|
1516
1430
|
};
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1431
|
+
const scriptController = new AbortController();
|
|
1432
|
+
const abortScript = () => scriptController.abort();
|
|
1433
|
+
signal?.addEventListener("abort", abortScript, { once: true });
|
|
1434
|
+
if (signal?.aborted)
|
|
1435
|
+
scriptController.abort();
|
|
1436
|
+
activeScriptControllers.add(scriptController);
|
|
1437
|
+
let finishScriptExecution;
|
|
1438
|
+
const scriptExecution = new Promise((resolve) => {
|
|
1439
|
+
finishScriptExecution = resolve;
|
|
1440
|
+
});
|
|
1441
|
+
activeScriptExecutions.add(scriptExecution);
|
|
1442
|
+
try {
|
|
1443
|
+
// Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
|
|
1444
|
+
const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(ctx.cwd, scriptController.signal));
|
|
1445
|
+
if (versionFailure)
|
|
1446
|
+
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
|
|
1447
|
+
const pendingRun = runAgentBrowserScript({
|
|
1448
|
+
beforeFirstCall() {
|
|
1449
|
+
appendScriptSessionLease(pi, sessionName, "active");
|
|
1450
|
+
trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
|
|
1451
|
+
managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1452
|
+
// This fresh, unselectable lease owns any daemon its first read starts.
|
|
1453
|
+
managedSessionRestoreState.recordDaemonRestoreKey(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE, null);
|
|
1454
|
+
leased = true;
|
|
1455
|
+
},
|
|
1456
|
+
code: resolvedInput.compiledScript.code,
|
|
1457
|
+
dispatch: async (innerParams, innerSignal) => {
|
|
1458
|
+
const remainingMs = Math.max(1, deadline - Date.now());
|
|
1459
|
+
const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
|
|
1460
|
+
const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
|
|
1461
|
+
args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
|
|
1462
|
+
stdin: innerParams.stdin,
|
|
1463
|
+
timeoutMs: innerTimeoutMs,
|
|
1464
|
+
}, innerSignal, undefined, ctx, nativeToolCallId));
|
|
1465
|
+
innerResults.push(innerResult);
|
|
1466
|
+
return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
|
|
1467
|
+
},
|
|
1468
|
+
signal: scriptController.signal,
|
|
1469
|
+
timeoutMs: scriptTimeoutMs,
|
|
1470
|
+
});
|
|
1471
|
+
run = await pendingRun;
|
|
1558
1472
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
1473
|
+
catch { }
|
|
1474
|
+
finally {
|
|
1475
|
+
activeScriptControllers.delete(scriptController);
|
|
1476
|
+
signal?.removeEventListener("abort", abortScript);
|
|
1477
|
+
if (leased) {
|
|
1478
|
+
try {
|
|
1479
|
+
cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => {
|
|
1480
|
+
flushRecordingReservations();
|
|
1481
|
+
return closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
|
|
1482
|
+
}));
|
|
1483
|
+
}
|
|
1484
|
+
catch {
|
|
1485
|
+
cleanupError = "The isolated script session cleanup operation failed.";
|
|
1486
|
+
try {
|
|
1487
|
+
appendScriptSessionLease(pi, sessionName, "failed");
|
|
1488
|
+
}
|
|
1489
|
+
catch { }
|
|
1490
|
+
}
|
|
1578
1491
|
}
|
|
1492
|
+
activeScriptExecutions.delete(scriptExecution);
|
|
1493
|
+
finishScriptExecution();
|
|
1579
1494
|
}
|
|
1495
|
+
let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
|
|
1580
1496
|
if (artifactManifest) {
|
|
1581
|
-
|
|
1582
|
-
...
|
|
1497
|
+
scriptResult = {
|
|
1498
|
+
...scriptResult,
|
|
1583
1499
|
details: {
|
|
1584
|
-
...(isRecord(
|
|
1585
|
-
artifactManifest,
|
|
1500
|
+
...(isRecord(scriptResult.details) ? scriptResult.details : {}),
|
|
1501
|
+
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1586
1502
|
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1587
1503
|
},
|
|
1588
1504
|
};
|
|
1589
1505
|
}
|
|
1506
|
+
return applyUnserializedOutputPath(scriptResult);
|
|
1590
1507
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1508
|
+
const { toolArgs } = resolvedInput;
|
|
1509
|
+
const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
|
|
1510
|
+
const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
|
|
1511
|
+
const runElectronHostInput = async () => {
|
|
1512
|
+
const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
|
|
1513
|
+
branchRecords: electronLaunchRecords,
|
|
1514
|
+
compiledElectron,
|
|
1515
|
+
ownedRecords: ownedElectronLaunchRecords,
|
|
1516
|
+
});
|
|
1517
|
+
let electronHostResult = await handleElectronHostInput({
|
|
1518
|
+
attachedSessionKeys,
|
|
1519
|
+
compiledElectron,
|
|
1602
1520
|
cwd: ctx.cwd,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1521
|
+
electronChildProcesses,
|
|
1522
|
+
electronLaunchRecords: electronHostLaunchRecords,
|
|
1523
|
+
implicitSessionCloseTimeoutMs,
|
|
1524
|
+
managedSessionActive,
|
|
1525
|
+
managedSessionName,
|
|
1526
|
+
managedSessionNamespace,
|
|
1527
|
+
managedSessionRestoreState,
|
|
1528
|
+
ownedManagedSessions,
|
|
1529
|
+
redactedCompiledElectron,
|
|
1530
|
+
sessionPageState,
|
|
1531
|
+
signal,
|
|
1532
|
+
});
|
|
1533
|
+
if (electronHostResult && compiledElectron?.action === "cleanup") {
|
|
1534
|
+
branchStateGeneration += 1;
|
|
1535
|
+
const cleanupRecords = isRecord(electronHostResult.details)
|
|
1536
|
+
&& isRecord(electronHostResult.details.electron)
|
|
1537
|
+
&& isRecord(electronHostResult.details.electron.cleanup)
|
|
1538
|
+
&& Array.isArray(electronHostResult.details.electron.cleanup.results)
|
|
1539
|
+
? electronHostResult.details.electron.cleanup.results
|
|
1540
|
+
: [];
|
|
1541
|
+
const cleanedLaunchIds = new Set();
|
|
1542
|
+
for (const cleanupResult of cleanupRecords) {
|
|
1543
|
+
if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
|
|
1544
|
+
cleanedLaunchIds.add(cleanupResult.record.launchId);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
|
|
1548
|
+
mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
|
|
1549
|
+
const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
|
|
1550
|
+
? electronHostResult.details.namespace
|
|
1551
|
+
: undefined;
|
|
1552
|
+
const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
|
|
1553
|
+
syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
|
|
1554
|
+
for (const identity of closedSessionIdentities) {
|
|
1555
|
+
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1556
|
+
const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
|
|
1557
|
+
clearSessionScopedBrowserState(closedSessionKey);
|
|
1558
|
+
if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
|
|
1559
|
+
managedSessionActive = false;
|
|
1560
|
+
managedSessionCompatibilityWorkaround = undefined;
|
|
1561
|
+
managedSessionHeadedAutosaveDisabled = false;
|
|
1562
|
+
managedSessionHeadedAutosaveInterval = undefined;
|
|
1563
|
+
managedSessionNamespace = undefined;
|
|
1564
|
+
freshSessionOrdinal += 1;
|
|
1565
|
+
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (artifactManifest) {
|
|
1569
|
+
electronHostResult = {
|
|
1570
|
+
...electronHostResult,
|
|
1571
|
+
details: {
|
|
1572
|
+
...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
|
|
1573
|
+
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1574
|
+
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1575
|
+
},
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1607
1578
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
const
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
electronLaunchRecords,
|
|
1640
|
-
ephemeralSessionSeed,
|
|
1641
|
-
freshSessionOrdinal,
|
|
1642
|
-
managedSessionActive,
|
|
1643
|
-
managedSessionBaseName,
|
|
1644
|
-
managedSessionCompatibilityWorkaround,
|
|
1645
|
-
managedSessionHeadedAutosaveDisabled,
|
|
1646
|
-
managedSessionHeadedAutosaveInterval,
|
|
1647
|
-
managedSessionCwd,
|
|
1579
|
+
return electronHostResult;
|
|
1580
|
+
};
|
|
1581
|
+
const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
|
|
1582
|
+
? managedSessionExecutionQueue.run(runElectronHostInput)
|
|
1583
|
+
: runElectronHostInput();
|
|
1584
|
+
const electronHostResult = compiledElectron?.action === "cleanup"
|
|
1585
|
+
? await artifactExecutionQueue.run(async () => {
|
|
1586
|
+
flushRecordingReservations();
|
|
1587
|
+
const reservationError = outputPath ? getArtifactPreflightValidationError({
|
|
1588
|
+
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1589
|
+
args: [],
|
|
1590
|
+
cwd: ctx.cwd,
|
|
1591
|
+
outputPath,
|
|
1592
|
+
}) : undefined;
|
|
1593
|
+
if (reservationError) {
|
|
1594
|
+
return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
|
|
1595
|
+
}
|
|
1596
|
+
const result = await runSerializedElectronHostInput();
|
|
1597
|
+
return result ? applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: warnRecordingPersistence(result) }) : result;
|
|
1598
|
+
})
|
|
1599
|
+
: await runSerializedElectronHostInput();
|
|
1600
|
+
if (electronHostResult) {
|
|
1601
|
+
return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
|
|
1602
|
+
}
|
|
1603
|
+
const explicitSessionName = extractExplicitSessionName(toolArgs);
|
|
1604
|
+
const callerOwnedSessionNamespace = explicitSessionName
|
|
1605
|
+
? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
|
|
1606
|
+
: undefined;
|
|
1607
|
+
const serializeBrowserCommand = shouldSerializeBrowserCommand({
|
|
1608
|
+
namespace: callerOwnedSessionNamespace,
|
|
1609
|
+
explicitSessionName,
|
|
1648
1610
|
managedSessionName,
|
|
1649
|
-
|
|
1650
|
-
managedSessionRestoreState,
|
|
1651
|
-
networkRoutesBySession,
|
|
1611
|
+
ownedElectronLaunchRecords,
|
|
1652
1612
|
ownedManagedSessions,
|
|
1653
|
-
sessionPageState,
|
|
1654
|
-
traceOwners,
|
|
1655
|
-
};
|
|
1656
|
-
const selectedPlan = buildExecutionPlan(toolArgs, {
|
|
1657
|
-
freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
|
|
1658
|
-
managedSessionActive: browserRunState.managedSessionActive,
|
|
1659
|
-
managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
|
|
1660
|
-
managedSessionName: browserRunState.managedSessionName,
|
|
1661
|
-
managedSessionNamespace: browserRunState.managedSessionNamespace,
|
|
1662
|
-
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1663
|
-
});
|
|
1664
|
-
const initialArtifactManifest = browserRunState.artifactManifest;
|
|
1665
|
-
const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
|
|
1666
|
-
const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
|
|
1667
|
-
|| (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
|
|
1668
|
-
const allocatesFreshManagedSession = explicitSessionName === undefined
|
|
1669
|
-
&& (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
|
|
1670
|
-
const reusableSessionKey = allocatesFreshManagedSession
|
|
1671
|
-
? undefined
|
|
1672
|
-
: getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
|
|
1673
|
-
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1674
|
-
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1675
|
-
let result = await runAgentBrowserTool({
|
|
1676
|
-
ctx,
|
|
1677
|
-
cwd: ctx.cwd,
|
|
1678
|
-
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
1679
|
-
electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
|
|
1680
|
-
implicitSessionCloseTimeoutMs,
|
|
1681
|
-
implicitSessionIdleTimeoutMs,
|
|
1682
|
-
input: resolvedInput,
|
|
1683
|
-
onUpdate,
|
|
1684
|
-
params,
|
|
1685
|
-
establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
|
|
1686
|
-
preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
|
|
1687
|
-
promptPolicy,
|
|
1688
|
-
sessionPageStateUpdate,
|
|
1689
|
-
signal,
|
|
1690
|
-
state: browserRunState,
|
|
1691
1613
|
});
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1614
|
+
const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
|
|
1615
|
+
? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
|
|
1616
|
+
: undefined;
|
|
1617
|
+
const runBrowserCommand = async () => {
|
|
1618
|
+
flushRecordingReservations();
|
|
1619
|
+
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1620
|
+
const generationAtStart = branchStateGeneration;
|
|
1621
|
+
const sessionPageStateUpdate = sessionPageState.beginUpdate();
|
|
1622
|
+
const browserRunState = {
|
|
1623
|
+
activeRecordingReservations,
|
|
1624
|
+
artifactManifest,
|
|
1625
|
+
attachedSessionKeys,
|
|
1626
|
+
closedManagedSessionNames: new Set(),
|
|
1627
|
+
electronChildProcesses,
|
|
1628
|
+
electronLaunchRecords,
|
|
1629
|
+
ephemeralSessionSeed,
|
|
1630
|
+
freshSessionOrdinal,
|
|
1631
|
+
managedSessionActive,
|
|
1632
|
+
managedSessionBaseName,
|
|
1633
|
+
managedSessionCompatibilityWorkaround,
|
|
1634
|
+
managedSessionHeadedAutosaveDisabled,
|
|
1635
|
+
managedSessionHeadedAutosaveInterval,
|
|
1636
|
+
managedSessionCwd,
|
|
1637
|
+
managedSessionName,
|
|
1638
|
+
managedSessionNamespace,
|
|
1639
|
+
managedSessionRestoreState,
|
|
1640
|
+
networkRoutesBySession,
|
|
1641
|
+
ownedManagedSessions,
|
|
1642
|
+
sessionPageState,
|
|
1643
|
+
traceOwners,
|
|
1644
|
+
};
|
|
1645
|
+
const selectedPlan = buildExecutionPlan(toolArgs, {
|
|
1646
|
+
freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
|
|
1647
|
+
managedSessionActive: browserRunState.managedSessionActive,
|
|
1648
|
+
managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
|
|
1649
|
+
managedSessionName: browserRunState.managedSessionName,
|
|
1650
|
+
managedSessionNamespace: browserRunState.managedSessionNamespace,
|
|
1651
|
+
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1652
|
+
stdin: resolvedInput.toolStdin,
|
|
1653
|
+
browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
|
|
1654
|
+
});
|
|
1655
|
+
const initialArtifactManifest = browserRunState.artifactManifest;
|
|
1656
|
+
const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
|
|
1657
|
+
const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
|
|
1658
|
+
|| (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
|
|
1659
|
+
const allocatesFreshManagedSession = explicitSessionName === undefined
|
|
1660
|
+
&& (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
|
|
1661
|
+
const reusableSessionKey = allocatesFreshManagedSession
|
|
1662
|
+
? undefined
|
|
1663
|
+
: getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
|
|
1664
|
+
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1665
|
+
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1666
|
+
let result = await runAgentBrowserTool({
|
|
1667
|
+
ctx,
|
|
1668
|
+
cwd: ctx.cwd,
|
|
1669
|
+
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
1670
|
+
electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
|
|
1671
|
+
implicitSessionCloseTimeoutMs,
|
|
1672
|
+
implicitSessionIdleTimeoutMs,
|
|
1673
|
+
input: resolvedInput,
|
|
1674
|
+
onUpdate,
|
|
1675
|
+
params,
|
|
1676
|
+
establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
|
|
1677
|
+
preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
|
|
1678
|
+
promptPolicy,
|
|
1679
|
+
sessionPageStateUpdate,
|
|
1680
|
+
signal,
|
|
1681
|
+
state: browserRunState,
|
|
1682
|
+
});
|
|
1683
|
+
const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
|
|
1684
|
+
const resultDetails = isRecord(result.details) ? result.details : undefined;
|
|
1685
|
+
const resultSessionName = typeof resultDetails?.sessionName === "string"
|
|
1686
|
+
? resultDetails.sessionName
|
|
1687
|
+
: extractExplicitSessionName(toolArgs);
|
|
1688
|
+
const resultNamespace = typeof resultDetails?.namespace === "string"
|
|
1689
|
+
? resultDetails.namespace
|
|
1690
|
+
: selectedPlan.namespace;
|
|
1691
|
+
if (branchRestoreStillCurrent) {
|
|
1692
|
+
const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
|
|
1693
|
+
const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
|
|
1694
|
+
const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
|
|
1695
|
+
const closeAllApplied = resultDetails?.closeAllApplied === true;
|
|
1696
|
+
const attachedSessionRemainsActive = result.isError !== true
|
|
1697
|
+
|| ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
|
|
1698
|
+
const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
|
|
1699
|
+
|| resultBatchCloseLifecycle?.endsClosed === true;
|
|
1700
|
+
if (closeAllApplied) {
|
|
1701
|
+
deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
|
|
1702
|
+
if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
|
|
1703
|
+
attachedSessionKeys.add(resultSessionKey);
|
|
1704
|
+
result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
else if (resultSessionKey && closesAttachedSession)
|
|
1708
|
+
attachedSessionKeys.delete(resultSessionKey);
|
|
1709
|
+
else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
|
|
1712
1710
|
attachedSessionKeys.add(resultSessionKey);
|
|
1713
1711
|
result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
|
|
1714
1712
|
}
|
|
1715
1713
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
|
|
1726
|
-
const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
|
|
1727
|
-
if (resultDetails?.closeAllApplied === true) {
|
|
1728
|
-
for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
|
|
1729
|
-
if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
|
|
1730
|
-
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1714
|
+
if (branchRestoreStillCurrent) {
|
|
1715
|
+
networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
|
|
1716
|
+
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
|
|
1717
|
+
const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
|
|
1718
|
+
if (resultDetails?.closeAllApplied === true) {
|
|
1719
|
+
for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
|
|
1720
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
|
|
1721
|
+
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1722
|
+
}
|
|
1731
1723
|
}
|
|
1732
1724
|
}
|
|
1725
|
+
for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
|
|
1726
|
+
if (handledBatchCloseKeys.has(closedSessionKey))
|
|
1727
|
+
continue;
|
|
1728
|
+
const reservation = activeRecordingReservations.get(closedSessionKey);
|
|
1729
|
+
if (reservation)
|
|
1730
|
+
retireRecordingSession(reservation.sessionName, reservation.namespace);
|
|
1731
|
+
}
|
|
1732
|
+
if (resultSessionName) {
|
|
1733
|
+
const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
|
|
1734
|
+
if (reservation)
|
|
1735
|
+
result = appendActiveRecordingCleanupAction(result, reservation);
|
|
1736
|
+
}
|
|
1737
|
+
if (artifactManifest) {
|
|
1738
|
+
result = {
|
|
1739
|
+
...result,
|
|
1740
|
+
details: {
|
|
1741
|
+
...(isRecord(result.details) ? result.details : {}),
|
|
1742
|
+
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1743
|
+
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1744
|
+
},
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1733
1747
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
},
|
|
1757
|
-
};
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
const branchStateStillCurrent = generationAtStart === branchStateGeneration;
|
|
1761
|
-
if (serializeBrowserCommand || branchStateStillCurrent) {
|
|
1762
|
-
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1763
|
-
managedSessionActive = browserRunState.managedSessionActive;
|
|
1764
|
-
managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
|
|
1765
|
-
managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
|
|
1766
|
-
managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
|
|
1767
|
-
managedSessionCwd = browserRunState.managedSessionCwd;
|
|
1768
|
-
managedSessionName = browserRunState.managedSessionName;
|
|
1769
|
-
managedSessionNamespace = browserRunState.managedSessionNamespace;
|
|
1770
|
-
for (const closedSessionName of browserRunState.closedManagedSessionNames) {
|
|
1771
|
-
untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
|
|
1748
|
+
const branchStateStillCurrent = generationAtStart === branchStateGeneration;
|
|
1749
|
+
if (serializeBrowserCommand || branchStateStillCurrent) {
|
|
1750
|
+
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1751
|
+
managedSessionActive = browserRunState.managedSessionActive;
|
|
1752
|
+
managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
|
|
1753
|
+
managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
|
|
1754
|
+
managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
|
|
1755
|
+
managedSessionCwd = browserRunState.managedSessionCwd;
|
|
1756
|
+
managedSessionName = browserRunState.managedSessionName;
|
|
1757
|
+
managedSessionNamespace = browserRunState.managedSessionNamespace;
|
|
1758
|
+
for (const closedSessionName of browserRunState.closedManagedSessionNames) {
|
|
1759
|
+
untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
|
|
1760
|
+
}
|
|
1761
|
+
syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
|
|
1762
|
+
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
1763
|
+
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1764
|
+
touchedLaunchIds: !result.isError
|
|
1765
|
+
? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
|
|
1766
|
+
: undefined,
|
|
1767
|
+
});
|
|
1768
|
+
if (serializeBrowserCommand)
|
|
1769
|
+
branchStateGeneration += 1;
|
|
1772
1770
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
const plan = buildExecutionPlan(toolArgs, {
|
|
1790
|
-
freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
|
|
1791
|
-
managedSessionActive,
|
|
1792
|
-
managedSessionCompatibilityWorkaround,
|
|
1793
|
-
managedSessionName,
|
|
1794
|
-
managedSessionNamespace,
|
|
1795
|
-
sessionMode: params.sessionMode ?? "auto",
|
|
1771
|
+
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1772
|
+
};
|
|
1773
|
+
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
1774
|
+
const runWithinSessionQueue = () => {
|
|
1775
|
+
if (closesAllSessions)
|
|
1776
|
+
return managedSessionExecutionQueue.run(() => {
|
|
1777
|
+
const plan = buildExecutionPlan(toolArgs, {
|
|
1778
|
+
freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
|
|
1779
|
+
managedSessionActive,
|
|
1780
|
+
managedSessionCompatibilityWorkaround,
|
|
1781
|
+
managedSessionName,
|
|
1782
|
+
managedSessionNamespace,
|
|
1783
|
+
sessionMode: params.sessionMode ?? "auto",
|
|
1784
|
+
stdin: resolvedInput.toolStdin,
|
|
1785
|
+
});
|
|
1786
|
+
return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runBrowserCommand);
|
|
1796
1787
|
});
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
};
|
|
1805
|
-
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1806
|
-
return runWithinSessionQueue();
|
|
1807
|
-
return artifactExecutionQueue.run(async () => {
|
|
1808
|
-
const artifactValidationError = getArtifactPreflightValidationError({
|
|
1809
|
-
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1810
|
-
args: toolArgs,
|
|
1811
|
-
cwd: ctx.cwd,
|
|
1812
|
-
outputPath,
|
|
1813
|
-
stdin: resolvedInput.toolStdin,
|
|
1814
|
-
});
|
|
1815
|
-
if (!artifactValidationError)
|
|
1788
|
+
if (serializeBrowserCommand)
|
|
1789
|
+
return managedSessionExecutionQueue.run(runBrowserCommand);
|
|
1790
|
+
return callerOwnedSessionQueueKey
|
|
1791
|
+
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runBrowserCommand)
|
|
1792
|
+
: runBrowserCommand();
|
|
1793
|
+
};
|
|
1794
|
+
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1816
1795
|
return runWithinSessionQueue();
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1796
|
+
return artifactExecutionQueue.run(async () => {
|
|
1797
|
+
const artifactValidationError = getArtifactPreflightValidationError({
|
|
1798
|
+
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1799
|
+
args: toolArgs,
|
|
1800
|
+
cwd: ctx.cwd,
|
|
1801
|
+
outputPath,
|
|
1802
|
+
stdin: resolvedInput.toolStdin,
|
|
1803
|
+
});
|
|
1804
|
+
if (!artifactValidationError)
|
|
1805
|
+
return runWithinSessionQueue();
|
|
1806
|
+
flushRecordingReservations();
|
|
1807
|
+
return warnRecordingPersistence(buildValidationFailureResult({
|
|
1822
1808
|
attemptedKind: resolvedInput.kind,
|
|
1823
1809
|
kind: "invalid",
|
|
1824
1810
|
redactedArgs: resolvedInput.redactedArgs,
|
|
@@ -1826,11 +1812,11 @@ export default function agentBrowserExtension(pi) {
|
|
|
1826
1812
|
toolArgs: resolvedInput.toolArgs,
|
|
1827
1813
|
toolStdin: resolvedInput.toolStdin,
|
|
1828
1814
|
validationError: artifactValidationError,
|
|
1829
|
-
}))
|
|
1815
|
+
}));
|
|
1830
1816
|
});
|
|
1831
1817
|
});
|
|
1832
1818
|
},
|
|
1833
1819
|
};
|
|
1834
|
-
pi.registerTool(agentBrowserTool);
|
|
1820
|
+
pi.registerTool(beforeExecute ? { ...agentBrowserTool, executionMode: "sequential" } : agentBrowserTool);
|
|
1835
1821
|
registerWebSearchToolIfAvailable(agentBrowserConfig);
|
|
1836
1822
|
}
|