pi-agent-browser-native 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +16 -7
- package/dist/extensions/agent-browser/index.js +440 -163
- package/dist/extensions/agent-browser/lib/browser-journal.js +418 -0
- package/dist/extensions/agent-browser/lib/browser-session-conversion.js +381 -0
- package/dist/extensions/agent-browser/lib/browser-transcript.js +119 -19
- package/dist/extensions/agent-browser/lib/input-modes/job.js +49 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +32 -35
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +25 -5
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +20 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +97 -78
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +23 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +52 -17
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +13 -8
- package/dist/extensions/agent-browser/lib/playbook.js +9 -2
- package/dist/extensions/agent-browser/lib/prompt-policy.js +0 -14
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +2 -1
- package/dist/extensions/agent-browser/lib/session-page-state.js +95 -157
- package/dist/extensions/agent-browser/lib/temp.js +14 -6
- package/dist/extensions/agent-browser/lib/tool-surface.js +15 -11
- package/dist/extensions/agent-browser/lib/web-search.js +0 -8
- package/docs/ARCHITECTURE.md +15 -9
- package/docs/COMMAND_REFERENCE.md +10 -6
- package/docs/RELEASE.md +3 -3
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SESSION_STORAGE.md +60 -0
- package/docs/SUPPORT_MATRIX.md +8 -5
- package/docs/TOOL_CONTRACT.md +19 -14
- package/package.json +9 -5
- package/scripts/build.mjs +4 -3
- package/scripts/convert-browser-session.mjs +22 -0
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
7
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./lib/batch-lifecycle.js";
|
|
7
|
-
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
8
|
+
import { PROJECT_RULE_PROMPT, ADVANCED_TOOL_PROMPT_GUIDELINES, QUICK_START_GUIDELINES, SHARED_BROWSER_PLAYBOOK_GUIDELINES, WEB_SEARCH_TOOL_PROMPT_GUIDELINES, WRAPPER_TAB_RECOVERY_BEHAVIOR, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
8
9
|
import { SessionPageState } from "./lib/session-page-state.js";
|
|
9
|
-
import { appendBrowserTransition, getBrowserResultMessage } from "./lib/browser-transcript.js";
|
|
10
|
+
import { appendBrowserTransition, applyArtifactChanges, artifactChanges, browserStateEffects, getBrowserRecord, getBrowserResultMessage, pageChange, snapshotDefinition } from "./lib/browser-transcript.js";
|
|
11
|
+
import { appendBrowserRecord, captureBrowserBranch, hasPublishedBrowserJournal, readBrowserEntries, requirePublishedBrowserJournal } from "./lib/browser-journal.js";
|
|
10
12
|
import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactInvocationArgs, redactSensitiveText, redactSensitiveValue, isPlainTextInspectionArgs, } from "./lib/runtime.js";
|
|
11
13
|
import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences } from "./lib/argv-grammar.js";
|
|
12
14
|
import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
|
|
13
15
|
import { needsManagedSession } from "./lib/command-policy.js";
|
|
14
16
|
import { ManagedSessionRestoreState, resolveOwnedManagedSessionContext, withOwnedManagedSessionContext } from "./lib/managed-session-restore.js";
|
|
15
17
|
import { isRecord } from "./lib/parsing.js";
|
|
16
|
-
import { getAgentBrowserProcessTimeoutMs, runAgentBrowserProcess } from "./lib/process.js";
|
|
18
|
+
import { getAgentBrowserProcessTimeoutMs, resolveAgentBrowserSocketDir, runAgentBrowserProcess } from "./lib/process.js";
|
|
17
19
|
import { getCommandAwareProcessTimeoutMs } from "./lib/orchestration/browser-run/prepare/wait-timeouts.js";
|
|
18
20
|
import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
|
|
19
21
|
import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
|
|
@@ -21,7 +23,7 @@ import { getBrowserCwdError, resolveExecutionCwd, restoreManagedSessionCwd } fro
|
|
|
21
23
|
import { resolveOperationPaths } from "./lib/orchestration/operation-paths.js";
|
|
22
24
|
import { closeManagedSession, inspectManagedSessionDaemon } from "./lib/orchestration/browser-run/managed-session-daemon-policy.js";
|
|
23
25
|
import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
|
|
24
|
-
import { buildPromptPolicy, getLatestUserPrompt
|
|
26
|
+
import { buildPromptPolicy, getLatestUserPrompt } from "./lib/prompt-policy.js";
|
|
25
27
|
import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
|
|
26
28
|
import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
|
|
27
29
|
import { cleanupSecureTempArtifacts } from "./lib/temp.js";
|
|
@@ -34,7 +36,7 @@ import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/or
|
|
|
34
36
|
import { applyAgentBrowserOutputPath, canWriteAgentBrowserOutput, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
|
|
35
37
|
import { appendScriptSessionLease, createBrowserCodeOutput, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
|
|
36
38
|
import { resolveBrowserExecutionIdentity, withBrowserExecutionLock, withBrowserExecutionLocks } from "./lib/managed-session-policy-lock.js";
|
|
37
|
-
import { registerAgentBrowserToolSurface } from "./lib/tool-surface.js";
|
|
39
|
+
import { AGENT_BROWSER_INSTRUCTION_GROUP, AGENT_BROWSER_TOOL_INVENTORY, registerAgentBrowserToolSurface } from "./lib/tool-surface.js";
|
|
38
40
|
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingCommand, isSessionArtifactManifest, mergeSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
|
|
39
41
|
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
|
|
40
42
|
import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
|
|
@@ -174,19 +176,54 @@ function getResultFileArtifacts(result) {
|
|
|
174
176
|
const details = isRecord(result.details) ? result.details : undefined;
|
|
175
177
|
return Array.isArray(details?.artifacts) ? details.artifacts.filter(isResultFileArtifact) : [];
|
|
176
178
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
function invocationArtifactManifest(current, previous, details, retainedReceipts) {
|
|
180
|
+
if (!current)
|
|
181
|
+
return undefined;
|
|
182
|
+
const paths = new Set();
|
|
183
|
+
for (const row of [details, ...(Array.isArray(details.batchSteps) ? details.batchSteps.filter(isRecord) : [])]) {
|
|
184
|
+
if (typeof row.fullOutputPath === "string")
|
|
185
|
+
paths.add(row.fullOutputPath);
|
|
186
|
+
for (const path of Array.isArray(row.fullOutputPaths) ? row.fullOutputPaths : [])
|
|
187
|
+
if (typeof path === "string")
|
|
188
|
+
paths.add(path);
|
|
189
|
+
for (const artifact of Array.isArray(row.artifacts) ? row.artifacts.filter(isRecord) : []) {
|
|
190
|
+
if (typeof artifact.absolutePath === "string")
|
|
191
|
+
paths.add(artifact.absolutePath);
|
|
192
|
+
if (typeof artifact.path === "string")
|
|
193
|
+
paths.add(artifact.path);
|
|
186
194
|
}
|
|
187
195
|
}
|
|
196
|
+
const changed = new Set((artifactChanges(previous, current)?.upserts ?? []).map(getSessionArtifactManifestEntryKey));
|
|
197
|
+
for (const row of retainedReceipts?.entries ?? [])
|
|
198
|
+
changed.add(getSessionArtifactManifestEntryKey(row));
|
|
199
|
+
const entries = current.entries.filter(row => changed.has(getSessionArtifactManifestEntryKey(row)) || paths.has(row.path) || (row.absolutePath !== undefined && paths.has(row.absolutePath)));
|
|
200
|
+
return entries.length ? redactSensitiveValue({
|
|
201
|
+
...current, entries, liveCount: entries.filter(row => row.retentionState === "live").length,
|
|
202
|
+
evictedCount: entries.filter(row => row.retentionState === "evicted").length,
|
|
203
|
+
}) : undefined;
|
|
204
|
+
}
|
|
205
|
+
function restoreArtifactManifestFromBranch(branch) {
|
|
206
|
+
let restoredManifest;
|
|
207
|
+
for (const entry of branch)
|
|
208
|
+
restoredManifest = applyArtifactChanges(restoredManifest, getBrowserRecord(entry)?.event.artifacts);
|
|
188
209
|
return restoredManifest;
|
|
189
210
|
}
|
|
211
|
+
function browserPageChanges(working, prior, touched) {
|
|
212
|
+
const pages = working.views();
|
|
213
|
+
const keys = new Set(touched);
|
|
214
|
+
for (const [key, page] of pages) {
|
|
215
|
+
const before = prior.get(key);
|
|
216
|
+
if (JSON.stringify(pageChange(key, page, before?.refSnapshot?.snapshotId)) !== JSON.stringify(pageChange(key, before ?? {}, before?.refSnapshot?.snapshotId)))
|
|
217
|
+
keys.add(key);
|
|
218
|
+
}
|
|
219
|
+
for (const key of prior.keys())
|
|
220
|
+
if (!pages.has(key))
|
|
221
|
+
keys.add(key);
|
|
222
|
+
const changes = [...keys].map(key => pages.has(key) ? pageChange(key, pages.get(key), prior.get(key)?.refSnapshot?.snapshotId) : { key, refs: { kind: "invalidate" }, clear: true });
|
|
223
|
+
const capture = changes.find(page => page.refs.kind === "replace");
|
|
224
|
+
const snapshot = capture ? pages.get(capture.key)?.refSnapshot : undefined;
|
|
225
|
+
return { pages: changes, ...(snapshot ? { snapshot: snapshotDefinition(snapshot) } : {}) };
|
|
226
|
+
}
|
|
190
227
|
function getRecognizedCompatibilityWorkaround(value) {
|
|
191
228
|
const workaround = isRecord(value) ? value : undefined;
|
|
192
229
|
return (workaround?.id === "chatgpt-headless-user-agent" || workaround?.id === "cloudflare-headless-user-agent") && typeof workaround.reason === "string"
|
|
@@ -346,7 +383,19 @@ function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
|
|
|
346
383
|
: existing?.compatibilityWorkaround;
|
|
347
384
|
const headedManagedAutosaveDisabled = options.headedManagedAutosaveDisabled ?? existing?.headedManagedAutosaveDisabled;
|
|
348
385
|
const headedManagedAutosaveInterval = options.headedManagedAutosaveInterval ?? existing?.headedManagedAutosaveInterval;
|
|
349
|
-
sessions.set(key, { branchOwned, compatibilityWorkaround, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName });
|
|
386
|
+
sessions.set(key, { branchOwned, compatibilityWorkaround, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName, socketDir: options.socketDir ?? existing?.socketDir });
|
|
387
|
+
}
|
|
388
|
+
function restoreManagedSessionSocketDir(branch, sessionName, namespace) {
|
|
389
|
+
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
390
|
+
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
391
|
+
const details = getBrowserResultMessage(branch[index])?.details;
|
|
392
|
+
if (!isRecord(details) || typeof details.sessionName !== "string")
|
|
393
|
+
continue;
|
|
394
|
+
if (getAgentBrowserSessionIdentityKey(details.sessionName, typeof details.namespace === "string" ? details.namespace : undefined) === key
|
|
395
|
+
&& typeof details.managedSessionSocketDir === "string")
|
|
396
|
+
return details.managedSessionSocketDir;
|
|
397
|
+
}
|
|
398
|
+
return undefined;
|
|
350
399
|
}
|
|
351
400
|
function untrackOwnedManagedSession(sessions, sessionName, namespace) {
|
|
352
401
|
if (!sessionName)
|
|
@@ -382,6 +431,7 @@ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
|
|
|
382
431
|
headedManagedAutosaveDisabled: details?.managedSessionHeadedAutosaveDisabled === true,
|
|
383
432
|
headedManagedAutosaveInterval: typeof details?.managedSessionHeadedAutosaveInterval === "string" ? details.managedSessionHeadedAutosaveInterval : undefined,
|
|
384
433
|
namespace,
|
|
434
|
+
socketDir: typeof details?.managedSessionSocketDir === "string" ? details.managedSessionSocketDir : undefined,
|
|
385
435
|
});
|
|
386
436
|
}
|
|
387
437
|
if (succeeded && status === "closed") {
|
|
@@ -394,6 +444,8 @@ function getTouchedElectronLaunchIds(sessionName, records, namespace) {
|
|
|
394
444
|
}
|
|
395
445
|
function mergeActiveElectronLaunchRecords(target, source, options = {}) {
|
|
396
446
|
for (const record of getActiveElectronRecords(source)) {
|
|
447
|
+
if (options.ownerSessionId !== undefined && record.ownerSessionId !== options.ownerSessionId)
|
|
448
|
+
continue;
|
|
397
449
|
const alreadyRuntimeOwned = target.has(record.launchId) && options.branchOwnedLaunchIds?.has(record.launchId) === false;
|
|
398
450
|
target.set(record.launchId, record);
|
|
399
451
|
if (options.branchOwnedLaunchIds) {
|
|
@@ -450,8 +502,10 @@ function shouldSerializeElectronHostInput(compiledElectron) {
|
|
|
450
502
|
return compiledElectron?.action === "status" || compiledElectron?.action === "probe" || compiledElectron?.action === "cleanup";
|
|
451
503
|
}
|
|
452
504
|
function getElectronHostLaunchRecordsForInput(options) {
|
|
505
|
+
if (options.compiledElectron?.action === "cleanup") {
|
|
506
|
+
return new Map([...mergeElectronLaunchRecordMaps(options.branchRecords, options.ownedRecords)].filter(([, record]) => record.ownerSessionId === options.ownerSessionId));
|
|
507
|
+
}
|
|
453
508
|
if (options.compiledElectron?.action === "status" ||
|
|
454
|
-
options.compiledElectron?.action === "cleanup" ||
|
|
455
509
|
(options.compiledElectron?.action === "probe" && options.compiledElectron.launchId)) {
|
|
456
510
|
return mergeElectronLaunchRecordMaps(options.branchRecords, options.ownedRecords);
|
|
457
511
|
}
|
|
@@ -631,7 +685,7 @@ async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessi
|
|
|
631
685
|
for (const [key, owner] of [...sessions]) {
|
|
632
686
|
if (key === keepKey)
|
|
633
687
|
continue;
|
|
634
|
-
const error = await closeManagedSession({ cwd: owner.cwd, headedManagedAutosaveInterval: owner.headedManagedAutosaveInterval, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, timeoutMs });
|
|
688
|
+
const error = await closeManagedSession({ cwd: owner.cwd, headedManagedAutosaveInterval: owner.headedManagedAutosaveInterval, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, socketDir: owner.socketDir, timeoutMs });
|
|
635
689
|
if (!error) {
|
|
636
690
|
sessions.delete(key);
|
|
637
691
|
onClosed?.(owner);
|
|
@@ -812,13 +866,6 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
812
866
|
cwd: process.cwd(),
|
|
813
867
|
includeProjectConfig: false,
|
|
814
868
|
});
|
|
815
|
-
const webSearchToolAvailable = canRegisterWebSearchTool(agentBrowserConfig);
|
|
816
|
-
const toolPromptGuidelines = buildToolPromptGuidelines({
|
|
817
|
-
browserDefaultProfile: agentBrowserConfig.trustedBrowserDefaultProfile,
|
|
818
|
-
browserExecutablePath: agentBrowserConfig.trustedBrowserExecutablePath,
|
|
819
|
-
includeWebSearch: webSearchToolAvailable,
|
|
820
|
-
docs: getInstalledDocsPaths(),
|
|
821
|
-
});
|
|
822
869
|
const implicitSessionIdleTimeoutMs = String(getImplicitSessionIdleTimeoutMs());
|
|
823
870
|
const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
|
|
824
871
|
let webSearchToolRegistered = false;
|
|
@@ -832,6 +879,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
832
879
|
let managedSessionNamespace;
|
|
833
880
|
let freshSessionOrdinal = 0;
|
|
834
881
|
let sessionPageState = new SessionPageState();
|
|
882
|
+
let browserReplayError;
|
|
835
883
|
let traceOwners = new Map();
|
|
836
884
|
let artifactManifest;
|
|
837
885
|
let activeRecordingReservations = new Map();
|
|
@@ -841,6 +889,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
841
889
|
let networkRoutesBySession = new Map();
|
|
842
890
|
let electronLaunchRecords = new Map();
|
|
843
891
|
let ownedElectronLaunchRecords = new Map();
|
|
892
|
+
let browserOwnerSessionId = "";
|
|
844
893
|
let branchOwnedElectronLaunchIds = new Set();
|
|
845
894
|
let electronChildProcesses = new Map();
|
|
846
895
|
const managedSessionRestoreState = new ManagedSessionRestoreState();
|
|
@@ -1054,13 +1103,13 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1054
1103
|
}));
|
|
1055
1104
|
if (closeError) {
|
|
1056
1105
|
try {
|
|
1057
|
-
appendScriptSessionLease(pi, sessionName, "failed");
|
|
1106
|
+
appendScriptSessionLease(pi, sessionName, "failed", browserOwnerSessionId);
|
|
1058
1107
|
}
|
|
1059
1108
|
catch { }
|
|
1060
1109
|
return redactSensitiveText(closeError);
|
|
1061
1110
|
}
|
|
1062
1111
|
try {
|
|
1063
|
-
appendScriptSessionLease(pi, sessionName, "closed");
|
|
1112
|
+
appendScriptSessionLease(pi, sessionName, "closed", browserOwnerSessionId);
|
|
1064
1113
|
}
|
|
1065
1114
|
catch {
|
|
1066
1115
|
managedSessionRestoreState.disable(sessionName);
|
|
@@ -1076,7 +1125,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1076
1125
|
const pendingSessionNames = new Set([...ownedManagedSessions.values()]
|
|
1077
1126
|
.map((session) => session.sessionName)
|
|
1078
1127
|
.filter(isAgentBrowserScriptSessionName));
|
|
1079
|
-
for (const lease of getScriptSessionLeasesFromBranch(ctx.sessionManager.
|
|
1128
|
+
for (const lease of getScriptSessionLeasesFromBranch(await readBrowserEntries(ctx.sessionManager), ctx.sessionManager.getSessionId()).values()) {
|
|
1080
1129
|
if (lease.cleanup !== "closed")
|
|
1081
1130
|
pendingSessionNames.add(lease.sessionName);
|
|
1082
1131
|
}
|
|
@@ -1086,7 +1135,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1086
1135
|
await closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
|
|
1087
1136
|
}
|
|
1088
1137
|
};
|
|
1089
|
-
const restoreBranchBackedState = (ctx, options) => {
|
|
1138
|
+
const restoreBranchBackedState = async (ctx, options) => {
|
|
1090
1139
|
branchRestoreGeneration += 1;
|
|
1091
1140
|
branchStateGeneration += 1;
|
|
1092
1141
|
const previousManagedSessionActive = managedSessionActive;
|
|
@@ -1094,7 +1143,17 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1094
1143
|
const previousFreshSessionOrdinal = freshSessionOrdinal;
|
|
1095
1144
|
const previousAttachedSessionKeys = attachedSessionKeys;
|
|
1096
1145
|
managedSessionBaseName = createImplicitSessionName(ctx.sessionManager.getSessionId(), ctx.cwd, ephemeralSessionSeed);
|
|
1097
|
-
|
|
1146
|
+
browserOwnerSessionId = ctx.sessionManager.getSessionId();
|
|
1147
|
+
let branch;
|
|
1148
|
+
try {
|
|
1149
|
+
branch = await readBrowserEntries(ctx.sessionManager);
|
|
1150
|
+
browserReplayError = undefined;
|
|
1151
|
+
}
|
|
1152
|
+
catch (error) {
|
|
1153
|
+
browserReplayError = error instanceof Error ? error.message : String(error);
|
|
1154
|
+
sessionPageState.reset();
|
|
1155
|
+
throw error;
|
|
1156
|
+
}
|
|
1098
1157
|
const branchResourceEvents = collectBranchManagedResourceEvents(branch);
|
|
1099
1158
|
const restoredState = restoreManagedSessionStateFromBranch(branch, managedSessionBaseName);
|
|
1100
1159
|
managedSessionRestoreState.replace(restoredState.managedSessionRestoreDisabledIdentities, {
|
|
@@ -1185,6 +1244,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1185
1244
|
headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, identity.sessionName, identity.namespace),
|
|
1186
1245
|
headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, identity.sessionName, identity.namespace),
|
|
1187
1246
|
namespace: identity.namespace,
|
|
1247
|
+
socketDir: restoreManagedSessionSocketDir(branch, identity.sessionName, identity.namespace),
|
|
1188
1248
|
});
|
|
1189
1249
|
}
|
|
1190
1250
|
if (restoredState.active) {
|
|
@@ -1194,6 +1254,32 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1194
1254
|
headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
|
|
1195
1255
|
headedManagedAutosaveInterval: managedSessionHeadedAutosaveInterval,
|
|
1196
1256
|
namespace: restoredState.namespace,
|
|
1257
|
+
socketDir: restoreManagedSessionSocketDir(branch, restoredState.sessionName, restoredState.namespace),
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
const pendingOwnedBegins = new Map();
|
|
1261
|
+
for (const entry of branch) {
|
|
1262
|
+
const event = getBrowserRecord(entry)?.event;
|
|
1263
|
+
if (!event)
|
|
1264
|
+
continue;
|
|
1265
|
+
if (event.phase === "begin" && event.state.ownerSessionId === browserOwnerSessionId && event.state.wrapperManaged === true)
|
|
1266
|
+
pendingOwnedBegins.set(event.operationId, event);
|
|
1267
|
+
else if (event.phase === "finish")
|
|
1268
|
+
pendingOwnedBegins.delete(event.operationId);
|
|
1269
|
+
for (const page of event.pages ?? [])
|
|
1270
|
+
if (page.clear) {
|
|
1271
|
+
for (const [id, begin] of pendingOwnedBegins)
|
|
1272
|
+
if (begin.pages?.some(candidate => candidate.key === page.key))
|
|
1273
|
+
pendingOwnedBegins.delete(id);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
for (const begin of pendingOwnedBegins.values()) {
|
|
1277
|
+
if (typeof begin.state.sessionName !== "string")
|
|
1278
|
+
continue;
|
|
1279
|
+
// The persisted wrapper-selected intent proves cleanup scope, not launch success.
|
|
1280
|
+
trackOwnedManagedSession(ownedManagedSessions, begin.state.sessionName, typeof begin.state.managedSessionCwd === "string" ? begin.state.managedSessionCwd : ctx.cwd, {
|
|
1281
|
+
branchOwned: true, namespace: typeof begin.state.namespace === "string" ? begin.state.namespace : undefined,
|
|
1282
|
+
socketDir: typeof begin.state.managedSessionSocketDir === "string" ? begin.state.managedSessionSocketDir : undefined,
|
|
1197
1283
|
});
|
|
1198
1284
|
}
|
|
1199
1285
|
for (const record of getActiveElectronRecords(electronLaunchRecords)) {
|
|
@@ -1208,12 +1294,24 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1208
1294
|
branchOwned: true,
|
|
1209
1295
|
headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, record.sessionName),
|
|
1210
1296
|
headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, record.sessionName),
|
|
1297
|
+
socketDir: restoreManagedSessionSocketDir(branch, record.sessionName),
|
|
1211
1298
|
});
|
|
1212
1299
|
}
|
|
1213
|
-
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
1300
|
+
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, new Map([...electronLaunchRecords].filter(([, record]) => record.ownerSessionId === browserOwnerSessionId)), {
|
|
1214
1301
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1215
1302
|
markBranchOwned: true,
|
|
1216
1303
|
});
|
|
1304
|
+
for (const entry of branch) {
|
|
1305
|
+
const event = getBrowserRecord(entry)?.event;
|
|
1306
|
+
const state = event?.state;
|
|
1307
|
+
if (event?.phase === "begin" || state?.ownerSessionId !== browserOwnerSessionId || typeof state.sessionName !== "string")
|
|
1308
|
+
continue;
|
|
1309
|
+
const namespace = typeof state.namespace === "string" ? state.namespace : undefined;
|
|
1310
|
+
const key = getAgentBrowserSessionIdentityKey(state.sessionName, namespace);
|
|
1311
|
+
if (ownedManagedSessions.has(key) && !managedSessionRestoreState.hasDaemonRestoreKey(state.sessionName, namespace)) {
|
|
1312
|
+
managedSessionRestoreState.restoreDaemonReceipt(state.sessionName, namespace, state.managedSessionDaemon);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1217
1315
|
if (!options.resetRuntimeOwnership) {
|
|
1218
1316
|
for (const sessionKey of previousAttachedSessionKeys) {
|
|
1219
1317
|
if (ownedManagedSessions.has(sessionKey))
|
|
@@ -1229,18 +1327,20 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1229
1327
|
const registerWebSearchToolIfAvailable = (configState) => {
|
|
1230
1328
|
if (webSearchToolRegistered || !canRegisterWebSearchTool(configState))
|
|
1231
1329
|
return;
|
|
1232
|
-
pi.registerTool(
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1330
|
+
pi.registerTool({
|
|
1331
|
+
...createAgentBrowserWebSearchTool(configState, {
|
|
1332
|
+
loadConfigState(ctx) {
|
|
1333
|
+
return loadAgentBrowserConfigSync({
|
|
1334
|
+
cwd: ctx.cwd,
|
|
1335
|
+
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
1336
|
+
});
|
|
1337
|
+
},
|
|
1338
|
+
}),
|
|
1339
|
+
});
|
|
1240
1340
|
webSearchToolRegistered = true;
|
|
1241
1341
|
};
|
|
1242
1342
|
pi.on("session_start", async (_event, ctx) => {
|
|
1243
|
-
restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1343
|
+
await restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1244
1344
|
electronChildProcesses = new Map();
|
|
1245
1345
|
registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
|
|
1246
1346
|
cwd: ctx.cwd,
|
|
@@ -1258,7 +1358,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1258
1358
|
controller.abort();
|
|
1259
1359
|
await Promise.allSettled([...activeScriptExecutions]);
|
|
1260
1360
|
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1261
|
-
restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1361
|
+
await restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1262
1362
|
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1263
1363
|
flushRecordingReservations();
|
|
1264
1364
|
notifyRecordingPersistence(ctx);
|
|
@@ -1270,6 +1370,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1270
1370
|
const checkpointPi = pi;
|
|
1271
1371
|
checkpointPi.on("session_checkpoint", async (event, ctx) => {
|
|
1272
1372
|
const blocked = (reason) => ({ sleepReady: false, reason });
|
|
1373
|
+
if (browserReplayError)
|
|
1374
|
+
return blocked(browserReplayError);
|
|
1273
1375
|
if (activeScriptControllers.size || activeScriptExecutions.size)
|
|
1274
1376
|
return blocked("Browser code execution is pending");
|
|
1275
1377
|
flushRecordingReservations();
|
|
@@ -1277,7 +1379,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1277
1379
|
return blocked("Browser recording journal persistence is dirty");
|
|
1278
1380
|
if (activeRecordingReservations.size)
|
|
1279
1381
|
return blocked("Browser recording is pending; finish it explicitly before sleep");
|
|
1280
|
-
const entries = ctx.sessionManager
|
|
1382
|
+
const entries = await readBrowserEntries(ctx.sessionManager, true);
|
|
1281
1383
|
if ([...getScriptSessionLeasesFromBranch(entries).values()].some(lease => lease.cleanup !== "closed")
|
|
1282
1384
|
|| [...ownedManagedSessions.values()].some(owner => isAgentBrowserScriptSessionName(owner.sessionName))) {
|
|
1283
1385
|
return blocked("Browser script cleanup lease is unresolved");
|
|
@@ -1328,7 +1430,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1328
1430
|
});
|
|
1329
1431
|
// Owned routing must not mask an explicit caller's same-name ambient daemon.
|
|
1330
1432
|
for (const inspectionContext of context ? [{ ...context, reuseOnly: true }, undefined] : [undefined]) {
|
|
1331
|
-
const daemon = await withOwnedManagedSessionContext(inspectionContext, () => inspectManagedSessionDaemon({ ...owner, signal: event.signal, timeoutMs: 2_000 }));
|
|
1433
|
+
const daemon = await withAgentBrowserProcessEnvironment(inspectionContext && owner.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: owner.socketDir } : {}, () => withOwnedManagedSessionContext(inspectionContext, () => inspectManagedSessionDaemon({ ...owner, signal: event.signal, timeoutMs: 2_000 })));
|
|
1332
1434
|
if (daemon.status !== "inactive")
|
|
1333
1435
|
return blocked("Browser daemon is live or unverified; finish browser work explicitly before sleep");
|
|
1334
1436
|
}
|
|
@@ -1348,7 +1450,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1348
1450
|
const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
|
|
1349
1451
|
const quitting = event?.reason === "quit";
|
|
1350
1452
|
preservedElectronProfileDirs = quitting
|
|
1351
|
-
?
|
|
1453
|
+
? getActiveElectronRecords(electronLaunchRecords).filter(record => !ownedElectronLaunchRecords.has(record.launchId)).map(record => record.userDataDir)
|
|
1352
1454
|
: getActiveElectronRecords(electronLaunchRecords).map((record) => record.userDataDir);
|
|
1353
1455
|
const electronRecordsToCleanup = quitting
|
|
1354
1456
|
? ownedElectronLaunchRecords
|
|
@@ -1399,10 +1501,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1399
1501
|
ownedManagedSessions.clear();
|
|
1400
1502
|
await cleanupSecureTempArtifacts({ preservePaths: preservedElectronProfileDirs });
|
|
1401
1503
|
});
|
|
1402
|
-
|
|
1403
|
-
if (!shouldAppendBrowserSystemPrompt(event.prompt)) {
|
|
1404
|
-
return undefined;
|
|
1405
|
-
}
|
|
1504
|
+
const fullInstructions = (ctx) => {
|
|
1406
1505
|
const runtimeConfig = loadAgentBrowserConfigSync({
|
|
1407
1506
|
cwd: ctx.cwd,
|
|
1408
1507
|
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
@@ -1418,7 +1517,35 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1418
1517
|
const runtimeConfigPrompt = browserGuidance.length > 0
|
|
1419
1518
|
? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
|
|
1420
1519
|
: "";
|
|
1421
|
-
|
|
1520
|
+
const guidelines = [
|
|
1521
|
+
...buildToolPromptGuidelines({
|
|
1522
|
+
browserDefaultProfile: runtimeConfig.trustedBrowserDefaultProfile,
|
|
1523
|
+
browserExecutablePath: runtimeConfig.trustedBrowserExecutablePath,
|
|
1524
|
+
includeWebSearch: webSearchToolRegistered,
|
|
1525
|
+
docs: getInstalledDocsPaths(),
|
|
1526
|
+
}),
|
|
1527
|
+
...QUICK_START_GUIDELINES,
|
|
1528
|
+
...SHARED_BROWSER_PLAYBOOK_GUIDELINES,
|
|
1529
|
+
...WRAPPER_TAB_RECOVERY_BEHAVIOR,
|
|
1530
|
+
...Object.values(ADVANCED_TOOL_PROMPT_GUIDELINES).flat(),
|
|
1531
|
+
...(webSearchToolRegistered ? WEB_SEARCH_TOOL_PROMPT_GUIDELINES : []),
|
|
1532
|
+
];
|
|
1533
|
+
return `${PROJECT_RULE_PROMPT}\n\n${[...new Set(guidelines)].map(line => `- ${line}`).join("\n")}${runtimeConfigPrompt}`;
|
|
1534
|
+
};
|
|
1535
|
+
let isInstructionsManaged = () => false;
|
|
1536
|
+
pi.events.on("pi:instruction-groups", (data) => {
|
|
1537
|
+
const collector = data;
|
|
1538
|
+
collector.register({
|
|
1539
|
+
...AGENT_BROWSER_INSTRUCTION_GROUP,
|
|
1540
|
+
tools: ["agent_browser", "agent_browser_code", "agent_browser_tools", "agent_browser_web_search",
|
|
1541
|
+
...Object.values(AGENT_BROWSER_TOOL_INVENTORY).map(({ name }) => name)],
|
|
1542
|
+
instructions: fullInstructions,
|
|
1543
|
+
});
|
|
1544
|
+
isInstructionsManaged = collector.isManaged;
|
|
1545
|
+
});
|
|
1546
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
1547
|
+
if (!isInstructionsManaged())
|
|
1548
|
+
event.systemPromptOptions.sections.agent_browser = fullInstructions(ctx);
|
|
1422
1549
|
});
|
|
1423
1550
|
pi.on("tool_call", async (event, ctx) => {
|
|
1424
1551
|
const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
|
|
@@ -1434,6 +1561,18 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1434
1561
|
}
|
|
1435
1562
|
});
|
|
1436
1563
|
pi.on("tool_result", async (event) => buildAgentBrowserToolResultPatch(event));
|
|
1564
|
+
let emptyBrowserBranch;
|
|
1565
|
+
const captureCurrentBrowserBranch = (ctx) => {
|
|
1566
|
+
const generation = branchRestoreGeneration;
|
|
1567
|
+
const empty = ctx.sessionManager.getLeafId() === null;
|
|
1568
|
+
// Concurrent empty-root callers share only their own first synchronous append.
|
|
1569
|
+
if (empty && emptyBrowserBranch?.anchorId === null
|
|
1570
|
+
&& emptyBrowserBranch.sessionId === ctx.sessionManager.getSessionId() && emptyBrowserBranch.isCurrent())
|
|
1571
|
+
return emptyBrowserBranch;
|
|
1572
|
+
const branch = captureBrowserBranch(ctx.sessionManager, () => generation === branchRestoreGeneration);
|
|
1573
|
+
emptyBrowserBranch = empty ? branch : undefined;
|
|
1574
|
+
return branch;
|
|
1575
|
+
};
|
|
1437
1576
|
const agentBrowserTool = {
|
|
1438
1577
|
renderCall(args, theme, context) {
|
|
1439
1578
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -1447,7 +1586,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1447
1586
|
component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
|
|
1448
1587
|
return component;
|
|
1449
1588
|
},
|
|
1450
|
-
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true) {
|
|
1589
|
+
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true, commandIndex, selectedBranch) {
|
|
1590
|
+
const branch = selectedBranch ?? captureCurrentBrowserBranch(ctx);
|
|
1451
1591
|
let operationCwd;
|
|
1452
1592
|
try {
|
|
1453
1593
|
operationCwd = capturedCwd ?? resolveExecutionCwd(pi, ctx);
|
|
@@ -1464,6 +1604,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1464
1604
|
if (admittedInput.status === "invalid") {
|
|
1465
1605
|
return buildValidationFailureResult(admittedInput);
|
|
1466
1606
|
}
|
|
1607
|
+
if (browserReplayError && !isPlainTextInspectionArgs(admittedInput.toolArgs))
|
|
1608
|
+
return browserExecutionFailure(new Error(browserReplayError), signal, "validation-error");
|
|
1467
1609
|
const explicitConfig = scanUpstreamGlobalFlagOccurrences(admittedInput.toolArgs, "--config").length > 0 || getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIG !== undefined;
|
|
1468
1610
|
// Managed selection must follow earlier queued fresh launches, while file paths keep their admission snapshot.
|
|
1469
1611
|
const managedAtAdmission = admittedInput.kind !== "electron"
|
|
@@ -1529,7 +1671,31 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1529
1671
|
branchRecords: electronLaunchRecords,
|
|
1530
1672
|
compiledElectron,
|
|
1531
1673
|
ownedRecords: ownedElectronLaunchRecords,
|
|
1674
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
1532
1675
|
});
|
|
1676
|
+
const hostAffecting = compiledElectron && ["cleanup", "probe"].includes(compiledElectron.action);
|
|
1677
|
+
const workingPageState = sessionPageState.fork();
|
|
1678
|
+
const priorPages = workingPageState.views();
|
|
1679
|
+
const priorManifest = artifactManifest;
|
|
1680
|
+
const operationId = randomUUID();
|
|
1681
|
+
const launchId = compiledElectron && "launchId" in compiledElectron ? compiledElectron.launchId : undefined;
|
|
1682
|
+
const selected = launchId ? [electronHostLaunchRecords.get(launchId)].filter((record) => record !== undefined) : getActiveElectronRecords(electronHostLaunchRecords);
|
|
1683
|
+
const keys = new Set(selected.flatMap(record => record.sessionName ? [getAgentBrowserSessionIdentityKey(record.sessionName, record.namespace)] : []));
|
|
1684
|
+
if (compiledElectron?.action === "probe" && !launchId && managedSessionActive)
|
|
1685
|
+
keys.add(getAgentBrowserSessionIdentityKey(managedSessionName, managedSessionNamespace));
|
|
1686
|
+
const begin = { event: { version: 1, phase: "begin", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: true,
|
|
1687
|
+
state: {}, pages: [...keys].map(key => ({ key, refs: { kind: "unknown" }, unknown: true })) } };
|
|
1688
|
+
if (hostAffecting) {
|
|
1689
|
+
try {
|
|
1690
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, begin), begin, branch)
|
|
1691
|
+
|| !branch.isCurrent())
|
|
1692
|
+
throw new Error("The selected Pi branch changed before host-operation admission.");
|
|
1693
|
+
}
|
|
1694
|
+
catch (error) {
|
|
1695
|
+
return browserExecutionFailure(new Error("Could not persist Electron/browser-state begin; the host operation was not run.", { cause: error }), executionSignal);
|
|
1696
|
+
}
|
|
1697
|
+
sessionPageState.applyBrowserRecord(begin);
|
|
1698
|
+
}
|
|
1533
1699
|
let electronHostResult = await handleElectronHostInput({
|
|
1534
1700
|
attachedSessionKeys,
|
|
1535
1701
|
compiledElectron,
|
|
@@ -1543,7 +1709,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1543
1709
|
managedSessionRestoreState,
|
|
1544
1710
|
ownedManagedSessions,
|
|
1545
1711
|
redactedCompiledElectron,
|
|
1546
|
-
sessionPageState,
|
|
1712
|
+
sessionPageState: workingPageState,
|
|
1547
1713
|
signal: executionSignal,
|
|
1548
1714
|
});
|
|
1549
1715
|
if (electronHostResult && compiledElectron?.action === "cleanup") {
|
|
@@ -1571,6 +1737,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1571
1737
|
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1572
1738
|
const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
|
|
1573
1739
|
clearSessionScopedBrowserState(closedSessionKey);
|
|
1740
|
+
workingPageState.clearSession(closedSessionKey);
|
|
1574
1741
|
if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
|
|
1575
1742
|
managedSessionActive = false;
|
|
1576
1743
|
managedSessionCompatibilityWorkaround = undefined;
|
|
@@ -1581,15 +1748,37 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1581
1748
|
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
1582
1749
|
}
|
|
1583
1750
|
}
|
|
1584
|
-
if (artifactManifest)
|
|
1585
|
-
electronHostResult =
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1751
|
+
if (artifactManifest && isRecord(electronHostResult.details))
|
|
1752
|
+
electronHostResult.details.artifactRetentionSummary = formatSessionArtifactRetentionSummary(artifactManifest);
|
|
1753
|
+
}
|
|
1754
|
+
if (electronHostResult && hostAffecting && branch.isCurrent()) {
|
|
1755
|
+
for (const key of keys)
|
|
1756
|
+
if (workingPageState.get(key).refSnapshot && workingPageState.get(key).refSnapshot?.snapshotId !== priorPages.get(key)?.refSnapshot?.snapshotId) {
|
|
1757
|
+
const record = selected.find(record => record.sessionName && getAgentBrowserSessionIdentityKey(record.sessionName, record.namespace) === key);
|
|
1758
|
+
const owner = ownedManagedSessions.get(key);
|
|
1759
|
+
const daemon = await inspectManagedSessionDaemon({ cwd: ctx.cwd, sessionName: record?.sessionName ?? managedSessionName, namespace: record?.namespace ?? managedSessionNamespace, signal: executionSignal, includeGeneration: true, headedManagedAutosaveInterval: owner?.headedManagedAutosaveInterval ?? (owner?.headedManagedAutosaveDisabled ? "0" : undefined) });
|
|
1760
|
+
workingPageState.bindSnapshotGeneration(key, daemon.status === "active" ? daemon.generation : undefined);
|
|
1761
|
+
}
|
|
1762
|
+
const changes = browserPageChanges(workingPageState, priorPages, keys);
|
|
1763
|
+
const finish = { event: { version: 1, phase: hostAffecting ? "finish" : "state", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: electronHostResult.isError === true,
|
|
1764
|
+
state: browserStateEffects(isRecord(electronHostResult.details) ? electronHostResult.details : {}), pages: changes.pages, artifacts: artifactChanges(priorManifest, artifactManifest) },
|
|
1765
|
+
...(changes.snapshot ? { snapshot: changes.snapshot } : {}) };
|
|
1766
|
+
try {
|
|
1767
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, finish), finish, branch)
|
|
1768
|
+
|| !branch.isCurrent())
|
|
1769
|
+
throw new Error("The selected Pi branch changed before host-state confirmation.");
|
|
1770
|
+
}
|
|
1771
|
+
catch (error) {
|
|
1772
|
+
if (branch.isCurrent())
|
|
1773
|
+
sessionPageState.applyBrowserRecord(begin);
|
|
1774
|
+
return { ...browserExecutionFailure(new Error("The host operation finished, but its state could not be persisted. Inspect before retrying; changes may already have happened.", { cause: error }), executionSignal),
|
|
1775
|
+
details: { ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}), browserStatePersistence: "finish-unconfirmed", resultCategory: "failure", failureCategory: "upstream-error", nextActions: undefined } };
|
|
1776
|
+
}
|
|
1777
|
+
sessionPageState.applyBrowserRecord(finish);
|
|
1778
|
+
artifactManifest = applyArtifactChanges(artifactManifest, finish.event.artifacts);
|
|
1779
|
+
if (isRecord(electronHostResult.details)) {
|
|
1780
|
+
electronHostResult.details.browserEventVersion = 1;
|
|
1781
|
+
electronHostResult.details.artifactManifest = invocationArtifactManifest(artifactManifest, priorManifest, electronHostResult.details);
|
|
1593
1782
|
}
|
|
1594
1783
|
}
|
|
1595
1784
|
if (electronHostResult && executionSignal?.aborted) {
|
|
@@ -1610,7 +1799,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1610
1799
|
}) : runElectronHostInput(executionSignal);
|
|
1611
1800
|
if (!shouldSerializeElectronHostInput(compiledElectron))
|
|
1612
1801
|
return execute();
|
|
1613
|
-
const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords });
|
|
1802
|
+
const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords, ownerSessionId: ctx.sessionManager.getSessionId() });
|
|
1614
1803
|
const launchId = compiledElectron && "launchId" in compiledElectron ? compiledElectron.launchId : undefined;
|
|
1615
1804
|
const selected = launchId ? [records.get(launchId)].filter((record) => record !== undefined) : getActiveElectronRecords(records);
|
|
1616
1805
|
const sessions = selected.flatMap(record => record.sessionName ? [{ sessionName: record.sessionName, namespace: record.namespace }] : []);
|
|
@@ -1651,9 +1840,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1651
1840
|
// before capturing state. Registration, rendering, and restore stay synchronous/eager.
|
|
1652
1841
|
const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
|
|
1653
1842
|
flushRecordingReservations();
|
|
1654
|
-
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1655
1843
|
const generationAtStart = branchStateGeneration;
|
|
1656
|
-
const
|
|
1844
|
+
const workingPageState = sessionPageState.fork();
|
|
1845
|
+
const priorPages = workingPageState.views();
|
|
1846
|
+
const sessionPageStateUpdate = workingPageState.beginUpdate();
|
|
1657
1847
|
const browserRunState = {
|
|
1658
1848
|
activeRecordingReservations,
|
|
1659
1849
|
artifactManifest,
|
|
@@ -1674,7 +1864,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1674
1864
|
managedSessionRestoreState,
|
|
1675
1865
|
networkRoutesBySession,
|
|
1676
1866
|
ownedManagedSessions,
|
|
1677
|
-
sessionPageState,
|
|
1867
|
+
sessionPageState: workingPageState,
|
|
1678
1868
|
traceOwners,
|
|
1679
1869
|
};
|
|
1680
1870
|
const selectedPlan = buildExecutionPlan(toolArgs, {
|
|
@@ -1687,6 +1877,30 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1687
1877
|
stdin: resolvedInput.toolStdin,
|
|
1688
1878
|
browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
|
|
1689
1879
|
});
|
|
1880
|
+
const browserAffecting = !selectedPlan.plainTextInspection && !(selectedPlan.commandInfo.command === "session" && selectedPlan.commandInfo.subcommand === "info")
|
|
1881
|
+
&& (needsManagedSession(parseArgvDescriptor(toolArgs), resolvedInput.toolStdin) || (selectedPlan.startupScopedFlags?.length ?? 0) > 0);
|
|
1882
|
+
const operationId = randomUUID();
|
|
1883
|
+
const selectedKey = getAgentBrowserSessionIdentityKey(selectedPlan.sessionName ?? "default", selectedPlan.namespace);
|
|
1884
|
+
const begin = { event: {
|
|
1885
|
+
version: 1, phase: "begin", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: true,
|
|
1886
|
+
state: { args: redactInvocationArgs(toolArgs), sessionName: selectedPlan.sessionName, namespace: selectedPlan.namespace,
|
|
1887
|
+
ownerSessionId: ctx.sessionManager.getSessionId(), usedImplicitSession: selectedPlan.usedImplicitSession,
|
|
1888
|
+
wrapperManaged: selectedPlan.managedSessionName !== undefined || ownedManagedSessions.has(selectedKey), managedSessionCwd: browserCwd,
|
|
1889
|
+
managedSessionSocketDir: resolveAgentBrowserSocketDir({ ownedManagedSession: selectedPlan.managedSessionName !== undefined || ownedManagedSessions.has(selectedKey) }) },
|
|
1890
|
+
pages: [{ key: selectedKey, refs: { kind: "unknown" }, unknown: true }],
|
|
1891
|
+
} };
|
|
1892
|
+
if (browserAffecting) {
|
|
1893
|
+
try {
|
|
1894
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, begin), begin, branch)
|
|
1895
|
+
|| !branch.isCurrent())
|
|
1896
|
+
throw new Error("The selected Pi branch changed before browser-command admission.");
|
|
1897
|
+
}
|
|
1898
|
+
catch (error) {
|
|
1899
|
+
const failure = browserExecutionFailure(new Error("Could not persist browser-state begin; the browser command was not run.", { cause: error }), executionSignal);
|
|
1900
|
+
return { ...failure, details: { ...(isRecord(failure.details) ? failure.details : {}), browserStatePersistence: "begin-unconfirmed" } };
|
|
1901
|
+
}
|
|
1902
|
+
sessionPageState.applyBrowserRecord(begin);
|
|
1903
|
+
}
|
|
1690
1904
|
const initialArtifactManifest = browserRunState.artifactManifest;
|
|
1691
1905
|
const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
|
|
1692
1906
|
const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
|
|
@@ -1698,28 +1912,37 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1698
1912
|
: getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
|
|
1699
1913
|
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1700
1914
|
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1701
|
-
let result
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1915
|
+
let result;
|
|
1916
|
+
try {
|
|
1917
|
+
result = await runAgentBrowserTool({
|
|
1918
|
+
daemonInactive,
|
|
1919
|
+
ctx,
|
|
1920
|
+
cwd: browserCwd,
|
|
1921
|
+
operationCwd,
|
|
1922
|
+
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
1923
|
+
electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
|
|
1924
|
+
implicitSessionCloseTimeoutMs,
|
|
1925
|
+
implicitSessionIdleTimeoutMs,
|
|
1926
|
+
input: resolvedInput,
|
|
1927
|
+
modelVisible,
|
|
1928
|
+
onUpdate,
|
|
1929
|
+
params,
|
|
1930
|
+
establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
|
|
1931
|
+
preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
|
|
1932
|
+
promptPolicy,
|
|
1933
|
+
sessionPageStateUpdate,
|
|
1934
|
+
signal: executionSignal,
|
|
1935
|
+
state: browserRunState,
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
catch (error) {
|
|
1939
|
+
const failure = browserExecutionFailure(error, executionSignal);
|
|
1940
|
+
result = { ...failure, details: { ...browserRunState.observedBrowserEffects, ...(isRecord(failure.details) ? failure.details : {}) } };
|
|
1941
|
+
}
|
|
1942
|
+
const branchRestoreStillCurrent = branch.isCurrent();
|
|
1722
1943
|
const resultDetails = isRecord(result.details) ? result.details : undefined;
|
|
1944
|
+
if (resultDetails && begin.event.state.wrapperManaged === true)
|
|
1945
|
+
resultDetails.managedSessionSocketDir = begin.event.state.managedSessionSocketDir;
|
|
1723
1946
|
if (typeof resultDetails?.sessionName === "string" && browserRunState.managedSessionActive
|
|
1724
1947
|
&& getAgentBrowserSessionIdentityKey(resultDetails.sessionName, typeof resultDetails.namespace === "string" ? resultDetails.namespace : undefined) === getAgentBrowserSessionIdentityKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace)) {
|
|
1725
1948
|
resultDetails.managedSessionCwd = browserRunState.managedSessionCwd;
|
|
@@ -1776,18 +1999,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1776
1999
|
if (reservation)
|
|
1777
2000
|
result = appendActiveRecordingCleanupAction(result, reservation);
|
|
1778
2001
|
}
|
|
1779
|
-
if (artifactManifest)
|
|
1780
|
-
result =
|
|
1781
|
-
...result,
|
|
1782
|
-
details: {
|
|
1783
|
-
...(isRecord(result.details) ? result.details : {}),
|
|
1784
|
-
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1785
|
-
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1786
|
-
},
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
2002
|
+
if (artifactManifest && isRecord(result.details))
|
|
2003
|
+
result.details.artifactRetentionSummary = formatSessionArtifactRetentionSummary(artifactManifest);
|
|
1789
2004
|
}
|
|
1790
|
-
const branchStateStillCurrent = generationAtStart === branchStateGeneration;
|
|
2005
|
+
const branchStateStillCurrent = generationAtStart === branchStateGeneration && branch.isCurrent();
|
|
1791
2006
|
if (serializeBrowserCommand || branchStateStillCurrent) {
|
|
1792
2007
|
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1793
2008
|
managedSessionActive = browserRunState.managedSessionActive;
|
|
@@ -1802,6 +2017,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1802
2017
|
}
|
|
1803
2018
|
syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
|
|
1804
2019
|
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
2020
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
1805
2021
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1806
2022
|
touchedLaunchIds: !result.isError
|
|
1807
2023
|
? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
|
|
@@ -1810,6 +2026,39 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1810
2026
|
if (serializeBrowserCommand)
|
|
1811
2027
|
branchStateGeneration += 1;
|
|
1812
2028
|
}
|
|
2029
|
+
if (!selectedPlan.plainTextInspection && branchRestoreStillCurrent) {
|
|
2030
|
+
const changes = browserPageChanges(workingPageState, priorPages, [...browserRunState.closedManagedSessionNames, ...(browserAffecting ? [selectedKey] : [])]);
|
|
2031
|
+
const artifacts = artifactChanges(initialArtifactManifest, artifactManifest);
|
|
2032
|
+
if (browserAffecting || changes.pages.length > 0 || artifacts && hasPublishedBrowserJournal(ctx.sessionManager) || resultDetails?.readConfirmation !== undefined || browserRunState.observedBrowserEffects?.readConfirmation !== undefined) {
|
|
2033
|
+
const finish = { event: {
|
|
2034
|
+
version: 1, phase: browserAffecting ? "finish" : "state", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: result.isError === true,
|
|
2035
|
+
state: { ...browserStateEffects(isRecord(result.details) ? result.details : {}), ...browserRunState.observedBrowserEffects,
|
|
2036
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
2037
|
+
...(begin.event.state.wrapperManaged === true ? { managedSessionDaemon: managedSessionRestoreState.getDaemonReceipt(resultSessionName, resultNamespace) } : {}) }, pages: changes.pages,
|
|
2038
|
+
artifacts,
|
|
2039
|
+
}, ...(changes.snapshot ? { snapshot: changes.snapshot } : {}) };
|
|
2040
|
+
try {
|
|
2041
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, finish), finish, branch)
|
|
2042
|
+
|| !branch.isCurrent())
|
|
2043
|
+
throw new Error("The selected Pi branch changed before browser-state confirmation.");
|
|
2044
|
+
}
|
|
2045
|
+
catch (error) {
|
|
2046
|
+
return { ...result, ...browserExecutionFailure(new Error("The browser command finished, but its state could not be persisted. Changes may already have happened; inspect the current page before retrying.", { cause: error }), executionSignal), details: { ...(isRecord(result.details) ? result.details : {}), resultCategory: "failure", failureCategory: "upstream-error", browserStatePersistence: "finish-unconfirmed", nextActions: [{
|
|
2047
|
+
id: "inspect-after-browser-state-persistence-failure", tool: "agent_browser", params: { args: ["--session", selectedPlan.sessionName ?? "default", "--namespace", selectedPlan.namespace ?? "", "batch", "--bail"], stdin: JSON.stringify([["get", "url"], ["snapshot", "-i"]]) },
|
|
2048
|
+
reason: "The effect may already have happened. Verify the current URL and inspect before deciding whether to retry.", safety: "Read-only inspection; no mutation is replayed.",
|
|
2049
|
+
}] } };
|
|
2050
|
+
}
|
|
2051
|
+
sessionPageState.applyBrowserRecord(finish);
|
|
2052
|
+
artifactManifest = applyArtifactChanges(artifactManifest, finish.event.artifacts);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
if (isRecord(result.details)) {
|
|
2056
|
+
result.details.browserEventVersion = 1;
|
|
2057
|
+
if (!(compiledElectron?.action === "launch" && compiledElectron.handoff === "snapshot")
|
|
2058
|
+
&& !getArtifactCommandSteps(toolArgs, resolvedInput.toolStdin).steps.some(step => step[0] === "snapshot"))
|
|
2059
|
+
delete result.details.refSnapshot;
|
|
2060
|
+
result.details.artifactManifest = invocationArtifactManifest(branch.isCurrent() ? artifactManifest : browserRunState.artifactManifest, initialArtifactManifest, result.details);
|
|
2061
|
+
}
|
|
1813
2062
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1814
2063
|
};
|
|
1815
2064
|
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
@@ -1821,35 +2070,39 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1821
2070
|
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1822
2071
|
stdin: resolvedInput.toolStdin,
|
|
1823
2072
|
});
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
const error = getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: toolArgs, cwd: operationCwd, outputPath, stdin: resolvedInput.toolStdin });
|
|
1832
|
-
if (!error)
|
|
2073
|
+
const owner = ownedManagedSessions.get(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace));
|
|
2074
|
+
return withAgentBrowserProcessEnvironment(owner?.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: owner.socketDir } : {}, async () => {
|
|
2075
|
+
const run = (executionSignal = signal) => {
|
|
2076
|
+
const execute = () => withLaunchDefaults
|
|
2077
|
+
? withLaunchDefaults(inactive => runBrowserCommand(inactive, executionSignal), executionSignal)
|
|
2078
|
+
: runBrowserCommand(undefined, executionSignal);
|
|
2079
|
+
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1833
2080
|
return execute();
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2081
|
+
return artifactExecutionQueue.run(async () => {
|
|
2082
|
+
const error = getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: toolArgs, cwd: operationCwd, outputPath, stdin: resolvedInput.toolStdin });
|
|
2083
|
+
if (!error)
|
|
2084
|
+
return execute();
|
|
2085
|
+
flushRecordingReservations();
|
|
2086
|
+
return warnRecordingPersistence(buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: error }));
|
|
2087
|
+
});
|
|
2088
|
+
};
|
|
2089
|
+
if (plan.validationError || (!plan.sessionName && plan.commandInfo.command !== "read" && !closesAllSessions))
|
|
2090
|
+
return run();
|
|
2091
|
+
try {
|
|
2092
|
+
const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace));
|
|
2093
|
+
const identities = [await resolveBrowserExecutionIdentity({ namespace: plan.namespace, ownedManagedSession: owned, sessionName: closesAllSessions ? undefined : plan.sessionName ?? "default" })];
|
|
2094
|
+
if (managedSessionActive && (params.sessionMode === "fresh" || compiledElectron?.action === "launch") && !explicitSessionName) {
|
|
2095
|
+
const previousOwner = ownedManagedSessions.get(getAgentBrowserSessionIdentityKey(managedSessionName, managedSessionNamespace));
|
|
2096
|
+
identities.push(await resolveBrowserExecutionIdentity({ namespace: managedSessionNamespace, ownedManagedSession: true, sessionName: managedSessionName, env: previousOwner?.socketDir ? { AGENT_BROWSER_SOCKET_DIR: previousOwner.socketDir } : undefined }));
|
|
2097
|
+
}
|
|
2098
|
+
const activePageUrl = sessionPageState.get(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace)).tabTarget?.url;
|
|
2099
|
+
const deadline = Date.now() + (params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(toolArgs), resolvedInput.toolStdin, activePageUrl) ?? getAgentBrowserProcessTimeoutMs());
|
|
2100
|
+
return await withBrowserExecutionLocks({ identities, signal, deadline, waitOnly: true }, run);
|
|
1845
2101
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
}
|
|
1850
|
-
catch (error) {
|
|
1851
|
-
return browserExecutionFailure(error, signal);
|
|
1852
|
-
}
|
|
2102
|
+
catch (error) {
|
|
2103
|
+
return browserExecutionFailure(error, signal);
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
1853
2106
|
};
|
|
1854
2107
|
const runWithinSessionQueue = () => {
|
|
1855
2108
|
if (closesAllSessions)
|
|
@@ -1883,9 +2136,15 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1883
2136
|
return managedSessionExecutionQueue.run(executeAdmitted, signal);
|
|
1884
2137
|
},
|
|
1885
2138
|
};
|
|
1886
|
-
const executeCode = async (toolCallId, params, signal, _onUpdate, ctx) => {
|
|
1887
|
-
if (
|
|
1888
|
-
return browserExecutionFailure(new Error(
|
|
2139
|
+
const executeCode = async (toolCallId, params, signal, _onUpdate, ctx, branch) => {
|
|
2140
|
+
if (browserReplayError)
|
|
2141
|
+
return browserExecutionFailure(new Error(browserReplayError), signal, "validation-error");
|
|
2142
|
+
try {
|
|
2143
|
+
requirePublishedBrowserJournal(ctx.sessionManager);
|
|
2144
|
+
}
|
|
2145
|
+
catch (error) {
|
|
2146
|
+
return browserExecutionFailure(error, signal, "validation-error");
|
|
2147
|
+
}
|
|
1889
2148
|
let operationCwd;
|
|
1890
2149
|
try {
|
|
1891
2150
|
operationCwd = resolveExecutionCwd(pi, ctx);
|
|
@@ -1899,12 +2158,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1899
2158
|
if (signal?.aborted)
|
|
1900
2159
|
abort();
|
|
1901
2160
|
activeScriptControllers.add(controller);
|
|
1902
|
-
let finish;
|
|
1903
|
-
const execution = new Promise(resolve => { finish = resolve; });
|
|
1904
|
-
activeScriptExecutions.add(execution);
|
|
1905
2161
|
const deadline = Date.now() + (params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS);
|
|
1906
2162
|
const deadlineTimer = setTimeout(() => controller.abort(new DOMException("Browser code deadline exceeded.", "TimeoutError")), Math.max(0, deadline - Date.now()));
|
|
1907
|
-
const generation = branchRestoreGeneration;
|
|
1908
2163
|
const output = createBrowserCodeOutput();
|
|
1909
2164
|
const nativeEnvironment = getAgentBrowserProcessEnvironment();
|
|
1910
2165
|
try {
|
|
@@ -1933,9 +2188,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1933
2188
|
const namespace = plan.namespace || undefined;
|
|
1934
2189
|
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
1935
2190
|
const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(key);
|
|
1936
|
-
const run = async () => {
|
|
2191
|
+
const run = async () => withAgentBrowserProcessEnvironment(ownedManagedSessions.get(key)?.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: ownedManagedSessions.get(key).socketDir } : {}, async () => {
|
|
1937
2192
|
const identity = await resolveBrowserExecutionIdentity({ sessionName, namespace, ownedManagedSession: owned });
|
|
1938
2193
|
return withBrowserExecutionLock({ identity, deadline, signal: controller.signal }, async (executionSignal) => {
|
|
2194
|
+
let commandIndex = 0;
|
|
1939
2195
|
let journalFailed = false;
|
|
1940
2196
|
const run = await runAgentBrowserScript({
|
|
1941
2197
|
code: params.code, signal: executionSignal, timeoutMs: Math.max(1, deadline - Date.now()),
|
|
@@ -1946,33 +2202,24 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1946
2202
|
const { args } = bindBrowserCodeCall(inner, { sessionName, namespace });
|
|
1947
2203
|
if (plan.usedImplicitSession && !managedSessionActive && managedSessionName === sessionName && extractExplicitSessionName(inner.args) === undefined)
|
|
1948
2204
|
args.splice(args.indexOf("--session"), 2);
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
}
|
|
1953
|
-
catch {
|
|
2205
|
+
// The ordinary executor journals direct, code and hidden-helper effects alike.
|
|
2206
|
+
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, commandIndex++, branch));
|
|
2207
|
+
if (isRecord(result.details) && result.details.browserStatePersistence !== undefined) {
|
|
1954
2208
|
journalFailed = true;
|
|
1955
|
-
throw new Error("
|
|
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
|
-
}
|
|
2209
|
+
throw new Error("The browser effect may already have happened and its finish could not be persisted. Inspect before continuing; dependent commands were stopped.");
|
|
1969
2210
|
}
|
|
1970
2211
|
return output.observe(result);
|
|
1971
2212
|
},
|
|
1972
2213
|
});
|
|
1973
2214
|
const result = await output.finish(run, sessionName, namespace);
|
|
1974
|
-
if (
|
|
1975
|
-
result.
|
|
2215
|
+
if (journalFailed && isRecord(result.details)) {
|
|
2216
|
+
result.isError = true;
|
|
2217
|
+
result.details.resultCategory = "failure";
|
|
2218
|
+
result.details.failureCategory = "upstream-error";
|
|
2219
|
+
result.details.error = "Browser-state persistence failed. Dependent calls were stopped; inspect before continuing because already-dispatched effects may have happened.";
|
|
2220
|
+
}
|
|
2221
|
+
if (isRecord(result.details))
|
|
2222
|
+
result.details.browserEventVersion = 1;
|
|
1976
2223
|
if (!params.outputPath)
|
|
1977
2224
|
return result;
|
|
1978
2225
|
return artifactExecutionQueue.run(async () => {
|
|
@@ -1984,7 +2231,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1984
2231
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath: params.outputPath, preserveTextContent: true, result });
|
|
1985
2232
|
});
|
|
1986
2233
|
});
|
|
1987
|
-
};
|
|
2234
|
+
});
|
|
1988
2235
|
return shouldSerializeBrowserCommand({ namespace, explicitSessionName: sessionName, managedSessionName, ownedElectronLaunchRecords, ownedManagedSessions })
|
|
1989
2236
|
? managedSessionExecutionQueue.run(run, controller.signal)
|
|
1990
2237
|
: callerOwnedSessionExecutionQueues.run(key, namespace, run, controller.signal);
|
|
@@ -2000,11 +2247,9 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2000
2247
|
clearTimeout(deadlineTimer);
|
|
2001
2248
|
activeScriptControllers.delete(controller);
|
|
2002
2249
|
signal?.removeEventListener("abort", abort);
|
|
2003
|
-
activeScriptExecutions.delete(execution);
|
|
2004
|
-
finish();
|
|
2005
2250
|
}
|
|
2006
2251
|
};
|
|
2007
|
-
const finalizeObservation = async (result, params, ctx) => {
|
|
2252
|
+
const finalizeObservation = async (result, params, ctx, branch) => {
|
|
2008
2253
|
if ("args" in params && params.args && isPlainTextInspectionArgs(params.args))
|
|
2009
2254
|
return result;
|
|
2010
2255
|
const { renderAgentBrowserObservation } = await import("./lib/results/presentation/large-output.js");
|
|
@@ -2012,27 +2257,59 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2012
2257
|
if (result.isError && details.error === undefined)
|
|
2013
2258
|
details.error = details.validationError ?? details.summary ?? result.content.filter(item => item.type === "text").map(item => item.text).join("\n");
|
|
2014
2259
|
const priorManifest = isSessionArtifactManifest(details.artifactManifest) ? details.artifactManifest : undefined;
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2260
|
+
const renderOptions = { content: result.content, details, json: "code" in params || params.args?.includes("--json") === true, succeeded: result.isError !== true,
|
|
2261
|
+
persistentArtifactStore: branch.isCurrent() ? getPersistentSessionArtifactStore(ctx) : undefined,
|
|
2262
|
+
withArtifactWrite: (write) => artifactExecutionQueue.run(() => {
|
|
2263
|
+
if (!branch.isCurrent())
|
|
2264
|
+
renderOptions.persistentArtifactStore = undefined;
|
|
2265
|
+
return write();
|
|
2266
|
+
}) };
|
|
2267
|
+
const rendered = await renderAgentBrowserObservation(renderOptions);
|
|
2268
|
+
if (!priorManifest && !rendered.artifactManifest)
|
|
2269
|
+
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1 } };
|
|
2270
|
+
return artifactExecutionQueue.run(async () => {
|
|
2271
|
+
if (!branch.isCurrent())
|
|
2272
|
+
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2273
|
+
const before = artifactManifest;
|
|
2274
|
+
const merged = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
|
|
2275
|
+
const changes = artifactChanges(before, merged);
|
|
2276
|
+
if (changes && hasPublishedBrowserJournal(ctx.sessionManager)) {
|
|
2277
|
+
const record = { event: { version: 1, phase: "state", operationId: randomUUID(), toolCallId: "observation", commandIndex: 0, isError: result.isError === true, state: {}, artifacts: changes } };
|
|
2278
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, record), record, branch)
|
|
2279
|
+
|| !branch.isCurrent())
|
|
2280
|
+
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2281
|
+
}
|
|
2282
|
+
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
|
|
2283
|
+
return { ...result, content: rendered.content, details: { ...details, browserEventVersion: 1, artifactManifest: invocationArtifactManifest(artifactManifest, before, details, priorManifest) } };
|
|
2284
|
+
});
|
|
2018
2285
|
};
|
|
2019
2286
|
registerAgentBrowserToolSurface(pi, {
|
|
2020
2287
|
async execute(id, params, signal, onUpdate, ctx) {
|
|
2288
|
+
const branch = captureCurrentBrowserBranch(ctx);
|
|
2021
2289
|
let result;
|
|
2022
2290
|
try {
|
|
2023
|
-
result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx);
|
|
2291
|
+
result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx, id, undefined, true, undefined, branch);
|
|
2024
2292
|
}
|
|
2025
2293
|
catch (error) {
|
|
2026
2294
|
result = browserExecutionFailure(error, signal);
|
|
2027
2295
|
}
|
|
2028
|
-
return finalizeObservation(result, params, ctx);
|
|
2296
|
+
return finalizeObservation(result, params, ctx, branch);
|
|
2029
2297
|
},
|
|
2030
2298
|
async executeCode(id, params, signal, onUpdate, ctx) {
|
|
2031
|
-
const
|
|
2032
|
-
|
|
2299
|
+
const branch = captureCurrentBrowserBranch(ctx);
|
|
2300
|
+
let finish;
|
|
2301
|
+
const execution = new Promise(resolve => { finish = resolve; });
|
|
2302
|
+
activeScriptExecutions.add(execution);
|
|
2303
|
+
try {
|
|
2304
|
+
const result = await executeCode(id, params, signal, onUpdate, ctx, branch);
|
|
2305
|
+
return await finalizeObservation(result, params, ctx, branch);
|
|
2306
|
+
}
|
|
2307
|
+
finally {
|
|
2308
|
+
activeScriptExecutions.delete(execution);
|
|
2309
|
+
finish();
|
|
2310
|
+
}
|
|
2033
2311
|
},
|
|
2034
2312
|
executionMode: beforeExecute ? "sequential" : undefined,
|
|
2035
|
-
promptGuidelines: toolPromptGuidelines,
|
|
2036
2313
|
renderCall: agentBrowserTool.renderCall, renderResult: agentBrowserTool.renderResult,
|
|
2037
2314
|
});
|
|
2038
2315
|
registerWebSearchToolIfAvailable(agentBrowserConfig);
|