pi-agent-browser-native 0.8.2 → 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 +23 -0
- package/README.md +16 -7
- package/dist/extensions/agent-browser/index.js +430 -157
- 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 +12 -23
- package/dist/extensions/agent-browser/lib/web-search.js +0 -8
- package/docs/ARCHITECTURE.md +13 -11
- package/docs/COMMAND_REFERENCE.md +9 -7
- 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 +17 -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 {
|
|
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))
|
|
@@ -1230,7 +1328,6 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1230
1328
|
if (webSearchToolRegistered || !canRegisterWebSearchTool(configState))
|
|
1231
1329
|
return;
|
|
1232
1330
|
pi.registerTool({
|
|
1233
|
-
...{ discovery: { group: AGENT_BROWSER_DISCOVERY_GROUP, role: "entry" } },
|
|
1234
1331
|
...createAgentBrowserWebSearchTool(configState, {
|
|
1235
1332
|
loadConfigState(ctx) {
|
|
1236
1333
|
return loadAgentBrowserConfigSync({
|
|
@@ -1243,7 +1340,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1243
1340
|
webSearchToolRegistered = true;
|
|
1244
1341
|
};
|
|
1245
1342
|
pi.on("session_start", async (_event, ctx) => {
|
|
1246
|
-
restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1343
|
+
await restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1247
1344
|
electronChildProcesses = new Map();
|
|
1248
1345
|
registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
|
|
1249
1346
|
cwd: ctx.cwd,
|
|
@@ -1261,7 +1358,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1261
1358
|
controller.abort();
|
|
1262
1359
|
await Promise.allSettled([...activeScriptExecutions]);
|
|
1263
1360
|
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1264
|
-
restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1361
|
+
await restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1265
1362
|
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1266
1363
|
flushRecordingReservations();
|
|
1267
1364
|
notifyRecordingPersistence(ctx);
|
|
@@ -1273,6 +1370,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1273
1370
|
const checkpointPi = pi;
|
|
1274
1371
|
checkpointPi.on("session_checkpoint", async (event, ctx) => {
|
|
1275
1372
|
const blocked = (reason) => ({ sleepReady: false, reason });
|
|
1373
|
+
if (browserReplayError)
|
|
1374
|
+
return blocked(browserReplayError);
|
|
1276
1375
|
if (activeScriptControllers.size || activeScriptExecutions.size)
|
|
1277
1376
|
return blocked("Browser code execution is pending");
|
|
1278
1377
|
flushRecordingReservations();
|
|
@@ -1280,7 +1379,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1280
1379
|
return blocked("Browser recording journal persistence is dirty");
|
|
1281
1380
|
if (activeRecordingReservations.size)
|
|
1282
1381
|
return blocked("Browser recording is pending; finish it explicitly before sleep");
|
|
1283
|
-
const entries = ctx.sessionManager
|
|
1382
|
+
const entries = await readBrowserEntries(ctx.sessionManager, true);
|
|
1284
1383
|
if ([...getScriptSessionLeasesFromBranch(entries).values()].some(lease => lease.cleanup !== "closed")
|
|
1285
1384
|
|| [...ownedManagedSessions.values()].some(owner => isAgentBrowserScriptSessionName(owner.sessionName))) {
|
|
1286
1385
|
return blocked("Browser script cleanup lease is unresolved");
|
|
@@ -1331,7 +1430,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1331
1430
|
});
|
|
1332
1431
|
// Owned routing must not mask an explicit caller's same-name ambient daemon.
|
|
1333
1432
|
for (const inspectionContext of context ? [{ ...context, reuseOnly: true }, undefined] : [undefined]) {
|
|
1334
|
-
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 })));
|
|
1335
1434
|
if (daemon.status !== "inactive")
|
|
1336
1435
|
return blocked("Browser daemon is live or unverified; finish browser work explicitly before sleep");
|
|
1337
1436
|
}
|
|
@@ -1351,7 +1450,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1351
1450
|
const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
|
|
1352
1451
|
const quitting = event?.reason === "quit";
|
|
1353
1452
|
preservedElectronProfileDirs = quitting
|
|
1354
|
-
?
|
|
1453
|
+
? getActiveElectronRecords(electronLaunchRecords).filter(record => !ownedElectronLaunchRecords.has(record.launchId)).map(record => record.userDataDir)
|
|
1355
1454
|
: getActiveElectronRecords(electronLaunchRecords).map((record) => record.userDataDir);
|
|
1356
1455
|
const electronRecordsToCleanup = quitting
|
|
1357
1456
|
? ownedElectronLaunchRecords
|
|
@@ -1402,11 +1501,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1402
1501
|
ownedManagedSessions.clear();
|
|
1403
1502
|
await cleanupSecureTempArtifacts({ preservePaths: preservedElectronProfileDirs });
|
|
1404
1503
|
});
|
|
1405
|
-
|
|
1406
|
-
const sectionTools = event.systemPromptOptions?.sectionTools;
|
|
1407
|
-
if (!Object.hasOwn(sectionTools ?? {}, "agent_browser") && !shouldAppendBrowserSystemPrompt(event.prompt)) {
|
|
1408
|
-
return undefined;
|
|
1409
|
-
}
|
|
1504
|
+
const fullInstructions = (ctx) => {
|
|
1410
1505
|
const runtimeConfig = loadAgentBrowserConfigSync({
|
|
1411
1506
|
cwd: ctx.cwd,
|
|
1412
1507
|
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
@@ -1422,7 +1517,35 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1422
1517
|
const runtimeConfigPrompt = browserGuidance.length > 0
|
|
1423
1518
|
? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
|
|
1424
1519
|
: "";
|
|
1425
|
-
|
|
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);
|
|
1426
1549
|
});
|
|
1427
1550
|
pi.on("tool_call", async (event, ctx) => {
|
|
1428
1551
|
const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
|
|
@@ -1438,6 +1561,18 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1438
1561
|
}
|
|
1439
1562
|
});
|
|
1440
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
|
+
};
|
|
1441
1576
|
const agentBrowserTool = {
|
|
1442
1577
|
renderCall(args, theme, context) {
|
|
1443
1578
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -1451,7 +1586,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1451
1586
|
component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
|
|
1452
1587
|
return component;
|
|
1453
1588
|
},
|
|
1454
|
-
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);
|
|
1455
1591
|
let operationCwd;
|
|
1456
1592
|
try {
|
|
1457
1593
|
operationCwd = capturedCwd ?? resolveExecutionCwd(pi, ctx);
|
|
@@ -1468,6 +1604,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1468
1604
|
if (admittedInput.status === "invalid") {
|
|
1469
1605
|
return buildValidationFailureResult(admittedInput);
|
|
1470
1606
|
}
|
|
1607
|
+
if (browserReplayError && !isPlainTextInspectionArgs(admittedInput.toolArgs))
|
|
1608
|
+
return browserExecutionFailure(new Error(browserReplayError), signal, "validation-error");
|
|
1471
1609
|
const explicitConfig = scanUpstreamGlobalFlagOccurrences(admittedInput.toolArgs, "--config").length > 0 || getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIG !== undefined;
|
|
1472
1610
|
// Managed selection must follow earlier queued fresh launches, while file paths keep their admission snapshot.
|
|
1473
1611
|
const managedAtAdmission = admittedInput.kind !== "electron"
|
|
@@ -1533,7 +1671,31 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1533
1671
|
branchRecords: electronLaunchRecords,
|
|
1534
1672
|
compiledElectron,
|
|
1535
1673
|
ownedRecords: ownedElectronLaunchRecords,
|
|
1674
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
1536
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
|
+
}
|
|
1537
1699
|
let electronHostResult = await handleElectronHostInput({
|
|
1538
1700
|
attachedSessionKeys,
|
|
1539
1701
|
compiledElectron,
|
|
@@ -1547,7 +1709,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1547
1709
|
managedSessionRestoreState,
|
|
1548
1710
|
ownedManagedSessions,
|
|
1549
1711
|
redactedCompiledElectron,
|
|
1550
|
-
sessionPageState,
|
|
1712
|
+
sessionPageState: workingPageState,
|
|
1551
1713
|
signal: executionSignal,
|
|
1552
1714
|
});
|
|
1553
1715
|
if (electronHostResult && compiledElectron?.action === "cleanup") {
|
|
@@ -1575,6 +1737,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1575
1737
|
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1576
1738
|
const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
|
|
1577
1739
|
clearSessionScopedBrowserState(closedSessionKey);
|
|
1740
|
+
workingPageState.clearSession(closedSessionKey);
|
|
1578
1741
|
if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
|
|
1579
1742
|
managedSessionActive = false;
|
|
1580
1743
|
managedSessionCompatibilityWorkaround = undefined;
|
|
@@ -1585,15 +1748,37 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1585
1748
|
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
1586
1749
|
}
|
|
1587
1750
|
}
|
|
1588
|
-
if (artifactManifest)
|
|
1589
|
-
electronHostResult =
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
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);
|
|
1597
1782
|
}
|
|
1598
1783
|
}
|
|
1599
1784
|
if (electronHostResult && executionSignal?.aborted) {
|
|
@@ -1614,7 +1799,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1614
1799
|
}) : runElectronHostInput(executionSignal);
|
|
1615
1800
|
if (!shouldSerializeElectronHostInput(compiledElectron))
|
|
1616
1801
|
return execute();
|
|
1617
|
-
const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords });
|
|
1802
|
+
const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords, ownerSessionId: ctx.sessionManager.getSessionId() });
|
|
1618
1803
|
const launchId = compiledElectron && "launchId" in compiledElectron ? compiledElectron.launchId : undefined;
|
|
1619
1804
|
const selected = launchId ? [records.get(launchId)].filter((record) => record !== undefined) : getActiveElectronRecords(records);
|
|
1620
1805
|
const sessions = selected.flatMap(record => record.sessionName ? [{ sessionName: record.sessionName, namespace: record.namespace }] : []);
|
|
@@ -1655,9 +1840,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1655
1840
|
// before capturing state. Registration, rendering, and restore stay synchronous/eager.
|
|
1656
1841
|
const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
|
|
1657
1842
|
flushRecordingReservations();
|
|
1658
|
-
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1659
1843
|
const generationAtStart = branchStateGeneration;
|
|
1660
|
-
const
|
|
1844
|
+
const workingPageState = sessionPageState.fork();
|
|
1845
|
+
const priorPages = workingPageState.views();
|
|
1846
|
+
const sessionPageStateUpdate = workingPageState.beginUpdate();
|
|
1661
1847
|
const browserRunState = {
|
|
1662
1848
|
activeRecordingReservations,
|
|
1663
1849
|
artifactManifest,
|
|
@@ -1678,7 +1864,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1678
1864
|
managedSessionRestoreState,
|
|
1679
1865
|
networkRoutesBySession,
|
|
1680
1866
|
ownedManagedSessions,
|
|
1681
|
-
sessionPageState,
|
|
1867
|
+
sessionPageState: workingPageState,
|
|
1682
1868
|
traceOwners,
|
|
1683
1869
|
};
|
|
1684
1870
|
const selectedPlan = buildExecutionPlan(toolArgs, {
|
|
@@ -1691,6 +1877,30 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1691
1877
|
stdin: resolvedInput.toolStdin,
|
|
1692
1878
|
browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
|
|
1693
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
|
+
}
|
|
1694
1904
|
const initialArtifactManifest = browserRunState.artifactManifest;
|
|
1695
1905
|
const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
|
|
1696
1906
|
const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
|
|
@@ -1702,28 +1912,37 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1702
1912
|
: getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
|
|
1703
1913
|
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1704
1914
|
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1705
|
-
let result
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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();
|
|
1726
1943
|
const resultDetails = isRecord(result.details) ? result.details : undefined;
|
|
1944
|
+
if (resultDetails && begin.event.state.wrapperManaged === true)
|
|
1945
|
+
resultDetails.managedSessionSocketDir = begin.event.state.managedSessionSocketDir;
|
|
1727
1946
|
if (typeof resultDetails?.sessionName === "string" && browserRunState.managedSessionActive
|
|
1728
1947
|
&& getAgentBrowserSessionIdentityKey(resultDetails.sessionName, typeof resultDetails.namespace === "string" ? resultDetails.namespace : undefined) === getAgentBrowserSessionIdentityKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace)) {
|
|
1729
1948
|
resultDetails.managedSessionCwd = browserRunState.managedSessionCwd;
|
|
@@ -1780,18 +1999,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1780
1999
|
if (reservation)
|
|
1781
2000
|
result = appendActiveRecordingCleanupAction(result, reservation);
|
|
1782
2001
|
}
|
|
1783
|
-
if (artifactManifest)
|
|
1784
|
-
result =
|
|
1785
|
-
...result,
|
|
1786
|
-
details: {
|
|
1787
|
-
...(isRecord(result.details) ? result.details : {}),
|
|
1788
|
-
artifactManifest: redactSensitiveValue(artifactManifest),
|
|
1789
|
-
artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
|
|
1790
|
-
},
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
2002
|
+
if (artifactManifest && isRecord(result.details))
|
|
2003
|
+
result.details.artifactRetentionSummary = formatSessionArtifactRetentionSummary(artifactManifest);
|
|
1793
2004
|
}
|
|
1794
|
-
const branchStateStillCurrent = generationAtStart === branchStateGeneration;
|
|
2005
|
+
const branchStateStillCurrent = generationAtStart === branchStateGeneration && branch.isCurrent();
|
|
1795
2006
|
if (serializeBrowserCommand || branchStateStillCurrent) {
|
|
1796
2007
|
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1797
2008
|
managedSessionActive = browserRunState.managedSessionActive;
|
|
@@ -1806,6 +2017,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1806
2017
|
}
|
|
1807
2018
|
syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
|
|
1808
2019
|
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
2020
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
1809
2021
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1810
2022
|
touchedLaunchIds: !result.isError
|
|
1811
2023
|
? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
|
|
@@ -1814,6 +2026,39 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1814
2026
|
if (serializeBrowserCommand)
|
|
1815
2027
|
branchStateGeneration += 1;
|
|
1816
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
|
+
}
|
|
1817
2062
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1818
2063
|
};
|
|
1819
2064
|
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
@@ -1825,35 +2070,39 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1825
2070
|
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1826
2071
|
stdin: resolvedInput.toolStdin,
|
|
1827
2072
|
});
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
const error = getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: toolArgs, cwd: operationCwd, outputPath, stdin: resolvedInput.toolStdin });
|
|
1836
|
-
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))
|
|
1837
2080
|
return execute();
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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);
|
|
1849
2101
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
}
|
|
1854
|
-
catch (error) {
|
|
1855
|
-
return browserExecutionFailure(error, signal);
|
|
1856
|
-
}
|
|
2102
|
+
catch (error) {
|
|
2103
|
+
return browserExecutionFailure(error, signal);
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
1857
2106
|
};
|
|
1858
2107
|
const runWithinSessionQueue = () => {
|
|
1859
2108
|
if (closesAllSessions)
|
|
@@ -1887,9 +2136,15 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1887
2136
|
return managedSessionExecutionQueue.run(executeAdmitted, signal);
|
|
1888
2137
|
},
|
|
1889
2138
|
};
|
|
1890
|
-
const executeCode = async (toolCallId, params, signal, _onUpdate, ctx) => {
|
|
1891
|
-
if (
|
|
1892
|
-
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
|
+
}
|
|
1893
2148
|
let operationCwd;
|
|
1894
2149
|
try {
|
|
1895
2150
|
operationCwd = resolveExecutionCwd(pi, ctx);
|
|
@@ -1903,12 +2158,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1903
2158
|
if (signal?.aborted)
|
|
1904
2159
|
abort();
|
|
1905
2160
|
activeScriptControllers.add(controller);
|
|
1906
|
-
let finish;
|
|
1907
|
-
const execution = new Promise(resolve => { finish = resolve; });
|
|
1908
|
-
activeScriptExecutions.add(execution);
|
|
1909
2161
|
const deadline = Date.now() + (params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS);
|
|
1910
2162
|
const deadlineTimer = setTimeout(() => controller.abort(new DOMException("Browser code deadline exceeded.", "TimeoutError")), Math.max(0, deadline - Date.now()));
|
|
1911
|
-
const generation = branchRestoreGeneration;
|
|
1912
2163
|
const output = createBrowserCodeOutput();
|
|
1913
2164
|
const nativeEnvironment = getAgentBrowserProcessEnvironment();
|
|
1914
2165
|
try {
|
|
@@ -1937,9 +2188,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1937
2188
|
const namespace = plan.namespace || undefined;
|
|
1938
2189
|
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
1939
2190
|
const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(key);
|
|
1940
|
-
const run = async () => {
|
|
2191
|
+
const run = async () => withAgentBrowserProcessEnvironment(ownedManagedSessions.get(key)?.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: ownedManagedSessions.get(key).socketDir } : {}, async () => {
|
|
1941
2192
|
const identity = await resolveBrowserExecutionIdentity({ sessionName, namespace, ownedManagedSession: owned });
|
|
1942
2193
|
return withBrowserExecutionLock({ identity, deadline, signal: controller.signal }, async (executionSignal) => {
|
|
2194
|
+
let commandIndex = 0;
|
|
1943
2195
|
let journalFailed = false;
|
|
1944
2196
|
const run = await runAgentBrowserScript({
|
|
1945
2197
|
code: params.code, signal: executionSignal, timeoutMs: Math.max(1, deadline - Date.now()),
|
|
@@ -1950,33 +2202,24 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1950
2202
|
const { args } = bindBrowserCodeCall(inner, { sessionName, namespace });
|
|
1951
2203
|
if (plan.usedImplicitSession && !managedSessionActive && managedSessionName === sessionName && extractExplicitSessionName(inner.args) === undefined)
|
|
1952
2204
|
args.splice(args.indexOf("--session"), 2);
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
}
|
|
1957
|
-
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) {
|
|
1958
2208
|
journalFailed = true;
|
|
1959
|
-
throw new Error("
|
|
1960
|
-
}
|
|
1961
|
-
// Resolve launch defaults from the real command, never the routing-only get-url probe.
|
|
1962
|
-
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));
|
|
1963
|
-
const page = sessionPageState.get(key);
|
|
1964
|
-
const details = { ...(isRecord(result.details) ? result.details : {}), sessionName, namespace, sessionTabTarget: page.tabTarget, sessionTabTargetUnknown: page.tabTargetUnknown, sessionTabReopenPending: page.tabReopenPending, refSnapshot: page.refSnapshot, refSnapshotInvalidation: page.refSnapshotInvalidation };
|
|
1965
|
-
if (generation === branchRestoreGeneration) {
|
|
1966
|
-
try {
|
|
1967
|
-
appendBrowserTransition(pi, toolCallId, details, result.isError === true);
|
|
1968
|
-
}
|
|
1969
|
-
catch {
|
|
1970
|
-
journalFailed = true;
|
|
1971
|
-
throw new Error("The browser command finished, but its state could not be persisted. Inspect before retrying; changes may already have happened.");
|
|
1972
|
-
}
|
|
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.");
|
|
1973
2210
|
}
|
|
1974
2211
|
return output.observe(result);
|
|
1975
2212
|
},
|
|
1976
2213
|
});
|
|
1977
2214
|
const result = await output.finish(run, sessionName, namespace);
|
|
1978
|
-
if (
|
|
1979
|
-
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;
|
|
1980
2223
|
if (!params.outputPath)
|
|
1981
2224
|
return result;
|
|
1982
2225
|
return artifactExecutionQueue.run(async () => {
|
|
@@ -1988,7 +2231,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1988
2231
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath: params.outputPath, preserveTextContent: true, result });
|
|
1989
2232
|
});
|
|
1990
2233
|
});
|
|
1991
|
-
};
|
|
2234
|
+
});
|
|
1992
2235
|
return shouldSerializeBrowserCommand({ namespace, explicitSessionName: sessionName, managedSessionName, ownedElectronLaunchRecords, ownedManagedSessions })
|
|
1993
2236
|
? managedSessionExecutionQueue.run(run, controller.signal)
|
|
1994
2237
|
: callerOwnedSessionExecutionQueues.run(key, namespace, run, controller.signal);
|
|
@@ -2004,11 +2247,9 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2004
2247
|
clearTimeout(deadlineTimer);
|
|
2005
2248
|
activeScriptControllers.delete(controller);
|
|
2006
2249
|
signal?.removeEventListener("abort", abort);
|
|
2007
|
-
activeScriptExecutions.delete(execution);
|
|
2008
|
-
finish();
|
|
2009
2250
|
}
|
|
2010
2251
|
};
|
|
2011
|
-
const finalizeObservation = async (result, params, ctx) => {
|
|
2252
|
+
const finalizeObservation = async (result, params, ctx, branch) => {
|
|
2012
2253
|
if ("args" in params && params.args && isPlainTextInspectionArgs(params.args))
|
|
2013
2254
|
return result;
|
|
2014
2255
|
const { renderAgentBrowserObservation } = await import("./lib/results/presentation/large-output.js");
|
|
@@ -2016,27 +2257,59 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2016
2257
|
if (result.isError && details.error === undefined)
|
|
2017
2258
|
details.error = details.validationError ?? details.summary ?? result.content.filter(item => item.type === "text").map(item => item.text).join("\n");
|
|
2018
2259
|
const priorManifest = isSessionArtifactManifest(details.artifactManifest) ? details.artifactManifest : undefined;
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
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
|
+
});
|
|
2022
2285
|
};
|
|
2023
2286
|
registerAgentBrowserToolSurface(pi, {
|
|
2024
2287
|
async execute(id, params, signal, onUpdate, ctx) {
|
|
2288
|
+
const branch = captureCurrentBrowserBranch(ctx);
|
|
2025
2289
|
let result;
|
|
2026
2290
|
try {
|
|
2027
|
-
result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx);
|
|
2291
|
+
result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx, id, undefined, true, undefined, branch);
|
|
2028
2292
|
}
|
|
2029
2293
|
catch (error) {
|
|
2030
2294
|
result = browserExecutionFailure(error, signal);
|
|
2031
2295
|
}
|
|
2032
|
-
return finalizeObservation(result, params, ctx);
|
|
2296
|
+
return finalizeObservation(result, params, ctx, branch);
|
|
2033
2297
|
},
|
|
2034
2298
|
async executeCode(id, params, signal, onUpdate, ctx) {
|
|
2035
|
-
const
|
|
2036
|
-
|
|
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
|
+
}
|
|
2037
2311
|
},
|
|
2038
2312
|
executionMode: beforeExecute ? "sequential" : undefined,
|
|
2039
|
-
promptGuidelines: toolPromptGuidelines,
|
|
2040
2313
|
renderCall: agentBrowserTool.renderCall, renderResult: agentBrowserTool.renderResult,
|
|
2041
2314
|
});
|
|
2042
2315
|
registerWebSearchToolIfAvailable(agentBrowserConfig);
|