pi-agent-browser-native 0.6.17 → 0.7.1
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 +19 -0
- package/README.md +55 -167
- package/dist/extensions/agent-browser/index.js +312 -223
- package/dist/extensions/agent-browser/lib/browser-transcript.js +33 -0
- package/dist/extensions/agent-browser/lib/execution-cwd.js +4 -2
- package/dist/extensions/agent-browser/lib/input-modes/job.js +2 -213
- 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/diagnostics.js +13 -1
- 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 +35 -20
- 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/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 +138 -0
- package/dist/extensions/agent-browser/script-worker.js +4 -1
- package/docs/ARCHITECTURE.md +46 -62
- package/docs/COMMAND_REFERENCE.md +77 -136
- package/docs/ELECTRON.md +51 -49
- package/docs/RELEASE.md +35 -21
- package/docs/REQUIREMENTS.md +18 -14
- package/docs/SUPPORT_MATRIX.md +33 -15
- package/docs/TOOL_CONTRACT.md +216 -405
- package/docs/platform-smoke.md +2 -2
- package/package.json +1 -1
- 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,14 +6,16 @@ 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 { 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";
|
|
9
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";
|
|
17
20
|
import { getBrowserCwdError, resolveExecutionCwd, restoreManagedSessionCwd } from "./lib/execution-cwd.js";
|
|
18
21
|
import { resolveOperationPaths } from "./lib/orchestration/operation-paths.js";
|
|
@@ -22,15 +25,16 @@ import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt
|
|
|
22
25
|
import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
|
|
23
26
|
import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
|
|
24
27
|
import { cleanupSecureTempArtifacts } from "./lib/temp.js";
|
|
25
|
-
import {
|
|
26
|
-
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";
|
|
27
29
|
import { canonicalizeExplicitArtifactDestination, getExplicitArtifactDestination, getRecordContactSheetDestination } from "./lib/orchestration/browser-run/artifact-paths.js";
|
|
28
|
-
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";
|
|
29
31
|
import { parseBatchCommandArgument, parseUserBatchStdin } from "./lib/orchestration/batch-stdin.js";
|
|
30
32
|
import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
|
|
31
33
|
import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
|
|
32
34
|
import { applyAgentBrowserOutputPath, canWriteAgentBrowserOutput, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
|
|
33
|
-
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";
|
|
34
38
|
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingCommand, isSessionArtifactManifest, mergeSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
|
|
35
39
|
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
|
|
36
40
|
import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
|
|
@@ -39,6 +43,12 @@ import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
|
|
|
39
43
|
import { scopeReadConfirmationArgs } from "./lib/read-confirmation.js";
|
|
40
44
|
import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
|
|
41
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
|
+
}
|
|
42
52
|
function isBashToolCallEvent(event) {
|
|
43
53
|
if (!isRecord(event) || event.toolName !== "bash" || !isRecord(event.input))
|
|
44
54
|
return false;
|
|
@@ -167,10 +177,8 @@ function getResultFileArtifacts(result) {
|
|
|
167
177
|
function restoreArtifactManifestFromBranch(branch) {
|
|
168
178
|
let restoredManifest;
|
|
169
179
|
for (const entry of branch) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
173
|
-
if (!message || message.toolName !== "agent_browser")
|
|
180
|
+
const message = getBrowserResultMessage(entry);
|
|
181
|
+
if (!message)
|
|
174
182
|
continue;
|
|
175
183
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
176
184
|
if (isSessionArtifactManifest(details?.artifactManifest) && (!restoredManifest || details.artifactManifest.updatedAtMs >= restoredManifest.updatedAtMs)) {
|
|
@@ -189,10 +197,8 @@ function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionN
|
|
|
189
197
|
let restored;
|
|
190
198
|
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
191
199
|
for (const entry of branch) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
195
|
-
if (!message || message.toolName !== "agent_browser")
|
|
200
|
+
const message = getBrowserResultMessage(entry);
|
|
201
|
+
if (!message)
|
|
196
202
|
continue;
|
|
197
203
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
198
204
|
if (!details)
|
|
@@ -222,10 +228,8 @@ function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionNa
|
|
|
222
228
|
let restored = false;
|
|
223
229
|
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
224
230
|
for (const entry of branch) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
228
|
-
if (!message || message.toolName !== "agent_browser")
|
|
231
|
+
const message = getBrowserResultMessage(entry);
|
|
232
|
+
if (!message)
|
|
229
233
|
continue;
|
|
230
234
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
231
235
|
if (!details)
|
|
@@ -246,10 +250,8 @@ function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionNa
|
|
|
246
250
|
let restored;
|
|
247
251
|
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
248
252
|
for (const entry of branch) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
252
|
-
if (!message || message.toolName !== "agent_browser")
|
|
253
|
+
const message = getBrowserResultMessage(entry);
|
|
254
|
+
if (!message)
|
|
253
255
|
continue;
|
|
254
256
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
255
257
|
if (!details)
|
|
@@ -293,10 +295,8 @@ function isAttachedBrowserInvocation(args, env = getAgentBrowserProcessEnvironme
|
|
|
293
295
|
function restoreAttachedSessionKeysFromBranch(branch) {
|
|
294
296
|
const attachedSessionKeys = new Set();
|
|
295
297
|
for (const entry of branch) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
299
|
-
if (!message || message.toolName !== "agent_browser")
|
|
298
|
+
const message = getBrowserResultMessage(entry);
|
|
299
|
+
if (!message)
|
|
300
300
|
continue;
|
|
301
301
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
302
302
|
if (!details)
|
|
@@ -539,10 +539,8 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
539
539
|
};
|
|
540
540
|
let eventRank = 0;
|
|
541
541
|
for (const entry of branch) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
545
|
-
if (!message || message.toolName !== "agent_browser")
|
|
542
|
+
const message = getBrowserResultMessage(entry);
|
|
543
|
+
if (!message)
|
|
546
544
|
continue;
|
|
547
545
|
const details = isRecord(message.details) ? message.details : undefined;
|
|
548
546
|
if (!details)
|
|
@@ -661,36 +659,56 @@ function shouldSerializeBrowserCommand(options) {
|
|
|
661
659
|
// Serializes managed-session read/modify/write work so overlapping tool calls cannot promote stale state or close an in-use session.
|
|
662
660
|
class AsyncExecutionQueue {
|
|
663
661
|
tail = Promise.resolve();
|
|
664
|
-
|
|
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
|
+
}
|
|
665
669
|
const previous = this.tail;
|
|
666
670
|
let release;
|
|
667
671
|
this.tail = new Promise((resolve) => {
|
|
668
672
|
release = resolve;
|
|
669
673
|
});
|
|
670
|
-
|
|
671
|
-
|
|
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);
|
|
672
683
|
try {
|
|
673
|
-
|
|
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
|
+
}
|
|
674
692
|
}
|
|
675
693
|
finally {
|
|
676
694
|
release();
|
|
677
695
|
}
|
|
678
696
|
})();
|
|
697
|
+
return signal ? Promise.race([execution, cancelled]) : execution;
|
|
679
698
|
}
|
|
680
699
|
}
|
|
681
700
|
export class KeyedAsyncExecutionQueue {
|
|
682
701
|
barriers = new Map();
|
|
683
702
|
entries = new Map();
|
|
684
|
-
async run(key, namespace, work) {
|
|
703
|
+
async run(key, namespace, work, signal) {
|
|
685
704
|
const entry = this.entries.get(key) ?? { queue: new AsyncExecutionQueue(), users: 0 };
|
|
705
|
+
if (entry.queue.isCurrent())
|
|
706
|
+
return work();
|
|
686
707
|
const barrier = this.barriers.get(getAgentBrowserSessionIdentityKey("", namespace)) ?? Promise.resolve();
|
|
687
708
|
entry.users += 1;
|
|
688
709
|
this.entries.set(key, entry);
|
|
689
710
|
try {
|
|
690
|
-
return await entry.queue.run(
|
|
691
|
-
await barrier;
|
|
692
|
-
return await work();
|
|
693
|
-
});
|
|
711
|
+
return await entry.queue.run(work, signal, barrier);
|
|
694
712
|
}
|
|
695
713
|
finally {
|
|
696
714
|
entry.users -= 1;
|
|
@@ -1228,17 +1246,18 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1228
1246
|
cwd: ctx.cwd,
|
|
1229
1247
|
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
1230
1248
|
}));
|
|
1231
|
-
await
|
|
1249
|
+
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1232
1250
|
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1233
1251
|
flushRecordingReservations();
|
|
1234
1252
|
notifyRecordingPersistence(ctx);
|
|
1235
1253
|
}));
|
|
1236
1254
|
});
|
|
1237
1255
|
pi.on("session_tree", async (_event, ctx) => {
|
|
1256
|
+
branchRestoreGeneration += 1;
|
|
1238
1257
|
for (const controller of activeScriptControllers)
|
|
1239
1258
|
controller.abort();
|
|
1240
1259
|
await Promise.allSettled([...activeScriptExecutions]);
|
|
1241
|
-
await
|
|
1260
|
+
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1242
1261
|
restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1243
1262
|
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1244
1263
|
flushRecordingReservations();
|
|
@@ -1246,13 +1265,13 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1246
1265
|
}));
|
|
1247
1266
|
});
|
|
1248
1267
|
// Fork-only checkpoint event; official Pi validation types do not declare it.
|
|
1249
|
-
// Pi owns awaited tools/events (including their execution queues).
|
|
1250
|
-
// children and retained browser/recording resources need extension-side checks.
|
|
1268
|
+
// Pi owns awaited tools/events (including their execution queues). Detached code
|
|
1269
|
+
// children and retained browser/recording resources still need extension-side checks.
|
|
1251
1270
|
const checkpointPi = pi;
|
|
1252
1271
|
checkpointPi.on("session_checkpoint", async (event, ctx) => {
|
|
1253
1272
|
const blocked = (reason) => ({ sleepReady: false, reason });
|
|
1254
1273
|
if (activeScriptControllers.size || activeScriptExecutions.size)
|
|
1255
|
-
return blocked("Browser
|
|
1274
|
+
return blocked("Browser code execution is pending");
|
|
1256
1275
|
flushRecordingReservations();
|
|
1257
1276
|
if (recordingReservationsDirty)
|
|
1258
1277
|
return blocked("Browser recording journal persistence is dirty");
|
|
@@ -1278,9 +1297,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1278
1297
|
if (managedSessionActive)
|
|
1279
1298
|
trackOwnedManagedSession(sessions, managedSessionName, managedSessionCwd, { namespace: managedSessionNamespace });
|
|
1280
1299
|
for (const entry of entries) {
|
|
1281
|
-
|
|
1300
|
+
const message = getBrowserResultMessage(entry);
|
|
1301
|
+
if (!message)
|
|
1282
1302
|
continue;
|
|
1283
|
-
const details = isRecord(
|
|
1303
|
+
const details = isRecord(message.details) ? message.details : undefined;
|
|
1284
1304
|
// Helpers can launch before a main-process failure. Conversely, native
|
|
1285
1305
|
// launch flags on sessionless reads can open the unnamed default browser.
|
|
1286
1306
|
const sessionName = typeof details?.sessionName === "string" ? details.sessionName
|
|
@@ -1324,7 +1344,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1324
1344
|
branchRestoreGeneration += 1;
|
|
1325
1345
|
branchStateGeneration += 1;
|
|
1326
1346
|
let preservedElectronProfileDirs = [];
|
|
1327
|
-
await
|
|
1347
|
+
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1328
1348
|
const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
|
|
1329
1349
|
const quitting = event?.reason === "quit";
|
|
1330
1350
|
preservedElectronProfileDirs = quitting
|
|
@@ -1398,9 +1418,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1398
1418
|
const runtimeConfigPrompt = browserGuidance.length > 0
|
|
1399
1419
|
? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
|
|
1400
1420
|
: "";
|
|
1401
|
-
|
|
1402
|
-
systemPrompt: `${event.systemPrompt}\n\n${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`,
|
|
1403
|
-
};
|
|
1421
|
+
event.systemPromptOptions.sections.agent_browser = `${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`;
|
|
1404
1422
|
});
|
|
1405
1423
|
pi.on("tool_call", async (event, ctx) => {
|
|
1406
1424
|
const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
|
|
@@ -1417,12 +1435,6 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1417
1435
|
});
|
|
1418
1436
|
pi.on("tool_result", async (event) => buildAgentBrowserToolResultPatch(event));
|
|
1419
1437
|
const agentBrowserTool = {
|
|
1420
|
-
name: "agent_browser",
|
|
1421
|
-
label: "Agent Browser",
|
|
1422
|
-
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.",
|
|
1423
|
-
promptSnippet: "Browse websites, read live docs, click and fill pages, extract browser content, take screenshots, and automate real web workflows.",
|
|
1424
|
-
promptGuidelines: toolPromptGuidelines,
|
|
1425
|
-
parameters: AGENT_BROWSER_PARAMS,
|
|
1426
1438
|
renderCall(args, theme, context) {
|
|
1427
1439
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1428
1440
|
text.setText(formatAgentBrowserRenderCall(args, theme, context.expanded));
|
|
@@ -1435,7 +1447,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1435
1447
|
component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
|
|
1436
1448
|
return component;
|
|
1437
1449
|
},
|
|
1438
|
-
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd) {
|
|
1450
|
+
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true) {
|
|
1439
1451
|
let operationCwd;
|
|
1440
1452
|
try {
|
|
1441
1453
|
operationCwd = capturedCwd ?? resolveExecutionCwd(pi, ctx);
|
|
@@ -1454,11 +1466,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1454
1466
|
}
|
|
1455
1467
|
const explicitConfig = scanUpstreamGlobalFlagOccurrences(admittedInput.toolArgs, "--config").length > 0 || getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIG !== undefined;
|
|
1456
1468
|
// Managed selection must follow earlier queued fresh launches, while file paths keep their admission snapshot.
|
|
1457
|
-
const managedAtAdmission = admittedInput.kind !== "
|
|
1469
|
+
const managedAtAdmission = admittedInput.kind !== "electron"
|
|
1458
1470
|
&& !extractExplicitSessionName(admittedInput.toolArgs) && !explicitConfig;
|
|
1459
|
-
const artifactsAtAdmission = managedAtAdmission && commandTouchesArtifactLifecycle(admittedInput.toolArgs, admittedInput.toolStdin, outputPath);
|
|
1460
|
-
const withManagedQueue = (run) => managedAtAdmission ? run() : managedSessionExecutionQueue.run(run);
|
|
1461
|
-
const withArtifactQueue = (run) => artifactsAtAdmission ? run() : artifactExecutionQueue.run(run);
|
|
1462
1471
|
const executeAdmitted = async () => {
|
|
1463
1472
|
let resolvedInput = admittedInput;
|
|
1464
1473
|
const requestedSession = extractExplicitSessionName(resolvedInput.toolArgs);
|
|
@@ -1473,8 +1482,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1473
1482
|
const bound = resolveOperationPaths(resolvedInput.toolArgs, resolvedInput.toolStdin, operationCwd);
|
|
1474
1483
|
resolvedInput = { ...resolvedInput, toolArgs: bound.args, toolStdin: bound.stdin };
|
|
1475
1484
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
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 });
|
|
1478
1487
|
const runtimeBrowserConfig = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
|
|
1479
1488
|
const rootProfile = runtimeBrowserConfig.trustedBrowserDefaultProfile;
|
|
1480
1489
|
const pendingReadConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
@@ -1490,7 +1499,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1490
1499
|
const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
|
|
1491
1500
|
if (!outputPath || !canWriteAgentBrowserOutput(result))
|
|
1492
1501
|
return warnRecordingPersistence(result);
|
|
1493
|
-
return
|
|
1502
|
+
return artifactExecutionQueue.run(async () => {
|
|
1494
1503
|
flushRecordingReservations();
|
|
1495
1504
|
const reservationError = getArtifactPreflightValidationError({
|
|
1496
1505
|
activeRecordingReservations: activeRecordingReservations.values(),
|
|
@@ -1507,118 +1516,15 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1507
1516
|
const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
|
|
1508
1517
|
const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
|
|
1509
1518
|
const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
|
|
1510
|
-
if (
|
|
1519
|
+
if (!electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
|
|
1511
1520
|
const versionFailure = await validateUpstreamVersion(browserCwd, signal);
|
|
1512
1521
|
if (versionFailure)
|
|
1513
1522
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: versionFailure });
|
|
1514
1523
|
}
|
|
1515
|
-
if (resolvedInput.kind === "script") {
|
|
1516
|
-
if (!ctx.sessionManager.getSessionFile()) {
|
|
1517
|
-
return buildValidationFailureResult({
|
|
1518
|
-
attemptedKind: "script",
|
|
1519
|
-
kind: "invalid",
|
|
1520
|
-
redactedArgs: [],
|
|
1521
|
-
status: "invalid",
|
|
1522
|
-
toolArgs: [],
|
|
1523
|
-
validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1526
|
-
const sessionName = createAgentBrowserScriptSessionName();
|
|
1527
|
-
const innerResults = [];
|
|
1528
|
-
const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
|
|
1529
|
-
const deadline = Date.now() + scriptTimeoutMs;
|
|
1530
|
-
let leased = false;
|
|
1531
|
-
let cleanupError;
|
|
1532
|
-
let run = {
|
|
1533
|
-
callCount: 0,
|
|
1534
|
-
emitCount: 0,
|
|
1535
|
-
error: "Script sandbox execution failed.",
|
|
1536
|
-
failureCategory: "upstream-error",
|
|
1537
|
-
ok: false,
|
|
1538
|
-
rejectedCallCount: 0,
|
|
1539
|
-
steps: [],
|
|
1540
|
-
};
|
|
1541
|
-
const scriptController = new AbortController();
|
|
1542
|
-
const abortScript = () => scriptController.abort();
|
|
1543
|
-
signal?.addEventListener("abort", abortScript, { once: true });
|
|
1544
|
-
if (signal?.aborted)
|
|
1545
|
-
scriptController.abort();
|
|
1546
|
-
activeScriptControllers.add(scriptController);
|
|
1547
|
-
let finishScriptExecution;
|
|
1548
|
-
const scriptExecution = new Promise((resolve) => {
|
|
1549
|
-
finishScriptExecution = resolve;
|
|
1550
|
-
});
|
|
1551
|
-
activeScriptExecutions.add(scriptExecution);
|
|
1552
|
-
try {
|
|
1553
|
-
// Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
|
|
1554
|
-
const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(browserCwd, scriptController.signal));
|
|
1555
|
-
if (versionFailure)
|
|
1556
|
-
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: versionFailure });
|
|
1557
|
-
const pendingRun = runAgentBrowserScript({
|
|
1558
|
-
beforeFirstCall() {
|
|
1559
|
-
appendScriptSessionLease(pi, sessionName, "active");
|
|
1560
|
-
trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
|
|
1561
|
-
managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1562
|
-
// This fresh, unselectable lease owns any daemon its first read starts.
|
|
1563
|
-
managedSessionRestoreState.recordDaemonRestoreKey(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE, null);
|
|
1564
|
-
leased = true;
|
|
1565
|
-
},
|
|
1566
|
-
code: resolvedInput.compiledScript.code,
|
|
1567
|
-
dispatch: async (innerParams, innerSignal) => {
|
|
1568
|
-
const remainingMs = Math.max(1, deadline - Date.now());
|
|
1569
|
-
const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
|
|
1570
|
-
const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
|
|
1571
|
-
args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
|
|
1572
|
-
stdin: innerParams.stdin,
|
|
1573
|
-
timeoutMs: innerTimeoutMs,
|
|
1574
|
-
}, innerSignal, undefined, ctx, nativeToolCallId, operationCwd));
|
|
1575
|
-
innerResults.push(innerResult);
|
|
1576
|
-
return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
|
|
1577
|
-
},
|
|
1578
|
-
signal: scriptController.signal,
|
|
1579
|
-
timeoutMs: scriptTimeoutMs,
|
|
1580
|
-
});
|
|
1581
|
-
run = await pendingRun;
|
|
1582
|
-
}
|
|
1583
|
-
catch { }
|
|
1584
|
-
finally {
|
|
1585
|
-
activeScriptControllers.delete(scriptController);
|
|
1586
|
-
signal?.removeEventListener("abort", abortScript);
|
|
1587
|
-
if (leased) {
|
|
1588
|
-
try {
|
|
1589
|
-
cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => {
|
|
1590
|
-
flushRecordingReservations();
|
|
1591
|
-
return closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
|
|
1592
|
-
}));
|
|
1593
|
-
}
|
|
1594
|
-
catch {
|
|
1595
|
-
cleanupError = "The isolated script session cleanup operation failed.";
|
|
1596
|
-
try {
|
|
1597
|
-
appendScriptSessionLease(pi, sessionName, "failed");
|
|
1598
|
-
}
|
|
1599
|
-
catch { }
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
activeScriptExecutions.delete(scriptExecution);
|
|
1603
|
-
finishScriptExecution();
|
|
1604
|
-
}
|
|
1605
|
-
let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
|
|
1606
|
-
if (artifactManifest) {
|
|
1607
|
-
scriptResult = {
|
|
1608
|
-
...scriptResult,
|
|
1609
|
-
details: {
|
|
1610
|
-
...(isRecord(scriptResult.details) ? scriptResult.details : {}),
|
|
1611
|
-
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1612
|
-
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1613
|
-
},
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
return applyUnserializedOutputPath(scriptResult);
|
|
1617
|
-
}
|
|
1618
1524
|
const { toolArgs } = resolvedInput;
|
|
1619
1525
|
const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
|
|
1620
1526
|
const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
|
|
1621
|
-
const runElectronHostInput = async () => {
|
|
1527
|
+
const runElectronHostInput = async (executionSignal = signal) => {
|
|
1622
1528
|
const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
|
|
1623
1529
|
branchRecords: electronLaunchRecords,
|
|
1624
1530
|
compiledElectron,
|
|
@@ -1638,7 +1544,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1638
1544
|
ownedManagedSessions,
|
|
1639
1545
|
redactedCompiledElectron,
|
|
1640
1546
|
sessionPageState,
|
|
1641
|
-
signal,
|
|
1547
|
+
signal: executionSignal,
|
|
1642
1548
|
});
|
|
1643
1549
|
if (electronHostResult && compiledElectron?.action === "cleanup") {
|
|
1644
1550
|
branchStateGeneration += 1;
|
|
@@ -1686,27 +1592,43 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1686
1592
|
};
|
|
1687
1593
|
}
|
|
1688
1594
|
}
|
|
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 : {}) } };
|
|
1598
|
+
}
|
|
1689
1599
|
return electronHostResult;
|
|
1690
1600
|
};
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
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);
|
|
1624
|
+
}
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
return browserExecutionFailure(error, signal);
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
const electronHostResult = await (shouldSerializeElectronHostInput(compiledElectron)
|
|
1630
|
+
? managedSessionExecutionQueue.run(runCoordinatedElectronHostInput, signal)
|
|
1631
|
+
: runCoordinatedElectronHostInput());
|
|
1710
1632
|
if (electronHostResult) {
|
|
1711
1633
|
return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
|
|
1712
1634
|
}
|
|
@@ -1724,7 +1646,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1724
1646
|
const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
|
|
1725
1647
|
? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
|
|
1726
1648
|
: undefined;
|
|
1727
|
-
const runBrowserCommand = async (daemonInactive) => {
|
|
1649
|
+
const runBrowserCommand = async (daemonInactive, executionSignal = signal) => {
|
|
1728
1650
|
// Load execution-only preparation/output code inside the existing session queue,
|
|
1729
1651
|
// before capturing state. Registration, rendering, and restore stay synchronous/eager.
|
|
1730
1652
|
const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
|
|
@@ -1786,18 +1708,20 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1786
1708
|
implicitSessionCloseTimeoutMs,
|
|
1787
1709
|
implicitSessionIdleTimeoutMs,
|
|
1788
1710
|
input: resolvedInput,
|
|
1711
|
+
modelVisible,
|
|
1789
1712
|
onUpdate,
|
|
1790
1713
|
params,
|
|
1791
1714
|
establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
|
|
1792
1715
|
preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
|
|
1793
1716
|
promptPolicy,
|
|
1794
1717
|
sessionPageStateUpdate,
|
|
1795
|
-
signal,
|
|
1718
|
+
signal: executionSignal,
|
|
1796
1719
|
state: browserRunState,
|
|
1797
1720
|
});
|
|
1798
1721
|
const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
|
|
1799
1722
|
const resultDetails = isRecord(result.details) ? result.details : undefined;
|
|
1800
|
-
if (resultDetails?.sessionName ===
|
|
1723
|
+
if (typeof resultDetails?.sessionName === "string" && browserRunState.managedSessionActive
|
|
1724
|
+
&& getAgentBrowserSessionIdentityKey(resultDetails.sessionName, typeof resultDetails.namespace === "string" ? resultDetails.namespace : undefined) === getAgentBrowserSessionIdentityKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace)) {
|
|
1801
1725
|
resultDetails.managedSessionCwd = browserRunState.managedSessionCwd;
|
|
1802
1726
|
}
|
|
1803
1727
|
const resultSessionName = typeof resultDetails?.sessionName === "string"
|
|
@@ -1889,10 +1813,47 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1889
1813
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1890
1814
|
};
|
|
1891
1815
|
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
1892
|
-
const runWithLaunchDefaults = () =>
|
|
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);
|
|
1849
|
+
}
|
|
1850
|
+
catch (error) {
|
|
1851
|
+
return browserExecutionFailure(error, signal);
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1893
1854
|
const runWithinSessionQueue = () => {
|
|
1894
1855
|
if (closesAllSessions)
|
|
1895
|
-
return
|
|
1856
|
+
return managedSessionExecutionQueue.run(() => {
|
|
1896
1857
|
const plan = buildExecutionPlan(toolArgs, {
|
|
1897
1858
|
freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
|
|
1898
1859
|
managedSessionActive,
|
|
@@ -1905,34 +1866,12 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1905
1866
|
return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
|
|
1906
1867
|
});
|
|
1907
1868
|
if (serializeBrowserCommand)
|
|
1908
|
-
return
|
|
1869
|
+
return managedSessionExecutionQueue.run(runWithLaunchDefaults, signal);
|
|
1909
1870
|
return callerOwnedSessionQueueKey
|
|
1910
|
-
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults)
|
|
1871
|
+
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults, signal)
|
|
1911
1872
|
: runWithLaunchDefaults();
|
|
1912
1873
|
};
|
|
1913
|
-
|
|
1914
|
-
return runWithinSessionQueue();
|
|
1915
|
-
return withArtifactQueue(async () => {
|
|
1916
|
-
const artifactValidationError = getArtifactPreflightValidationError({
|
|
1917
|
-
activeRecordingReservations: activeRecordingReservations.values(),
|
|
1918
|
-
args: toolArgs,
|
|
1919
|
-
cwd: operationCwd,
|
|
1920
|
-
outputPath,
|
|
1921
|
-
stdin: resolvedInput.toolStdin,
|
|
1922
|
-
});
|
|
1923
|
-
if (!artifactValidationError)
|
|
1924
|
-
return runWithinSessionQueue();
|
|
1925
|
-
flushRecordingReservations();
|
|
1926
|
-
return warnRecordingPersistence(buildValidationFailureResult({
|
|
1927
|
-
attemptedKind: resolvedInput.kind,
|
|
1928
|
-
kind: "invalid",
|
|
1929
|
-
redactedArgs: resolvedInput.redactedArgs,
|
|
1930
|
-
status: "invalid",
|
|
1931
|
-
toolArgs: resolvedInput.toolArgs,
|
|
1932
|
-
toolStdin: resolvedInput.toolStdin,
|
|
1933
|
-
validationError: artifactValidationError,
|
|
1934
|
-
}));
|
|
1935
|
-
});
|
|
1874
|
+
return runWithinSessionQueue();
|
|
1936
1875
|
}, managedSessionActive || freshSessionOrdinal > 0 || params.sessionMode === "fresh" || pendingReadConfirmation ? undefined : {
|
|
1937
1876
|
id: rootSessionId,
|
|
1938
1877
|
profile: rootProfile?.policy === "always" && !/[\\/~]/.test(rootProfile.name) ? rootProfile.name : undefined,
|
|
@@ -1941,10 +1880,160 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1941
1880
|
};
|
|
1942
1881
|
if (!managedAtAdmission)
|
|
1943
1882
|
return executeAdmitted();
|
|
1944
|
-
|
|
1945
|
-
return artifactsAtAdmission ? artifactExecutionQueue.run(executeManaged) : executeManaged();
|
|
1883
|
+
return managedSessionExecutionQueue.run(executeAdmitted, signal);
|
|
1946
1884
|
},
|
|
1947
1885
|
};
|
|
1948
|
-
|
|
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
|
+
promptGuidelines: toolPromptGuidelines,
|
|
2036
|
+
renderCall: agentBrowserTool.renderCall, renderResult: agentBrowserTool.renderResult,
|
|
2037
|
+
});
|
|
1949
2038
|
registerWebSearchToolIfAvailable(agentBrowserConfig);
|
|
1950
2039
|
}
|