pi-agent-browser-native 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +24 -15
- package/dist/extensions/agent-browser/index.js +496 -343
- package/dist/extensions/agent-browser/lib/browser-journal.js +439 -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/execution-cwd.js +0 -16
- 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/native-output.js +46 -0
- 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/pi-tool-rendering.js +30 -16
- package/dist/extensions/agent-browser/lib/playbook.js +9 -2
- package/dist/extensions/agent-browser/lib/prompt-policy.js +16 -26
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +4 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +17 -9
- 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 +50 -33
- package/dist/extensions/agent-browser/lib/web-search.js +85 -72
- package/docs/ARCHITECTURE.md +25 -25
- package/docs/COMMAND_REFERENCE.md +13 -9
- package/docs/RELEASE.md +25 -19
- package/docs/REQUIREMENTS.md +6 -6
- package/docs/SESSION_STORAGE.md +60 -0
- package/docs/SUPPORT_MATRIX.md +16 -11
- package/docs/TOOL_CONTRACT.md +26 -19
- package/package.json +9 -5
- package/scripts/build.mjs +4 -3
- package/scripts/convert-browser-session.mjs +22 -0
- package/scripts/doctor.mjs +2 -2
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, 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
|
-
import { ManagedSessionRestoreState
|
|
16
|
+
import { ManagedSessionRestoreState } 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";
|
|
20
|
-
import { getBrowserCwdError, resolveExecutionCwd
|
|
22
|
+
import { getBrowserCwdError, resolveExecutionCwd } from "./lib/execution-cwd.js";
|
|
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,
|
|
26
|
+
import { buildPromptPolicy, getLatestUserMessage, getMessageText } 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";
|
|
@@ -42,7 +44,7 @@ import { appendRecordingReservationTransition, applyRecordingArtifactsToReservat
|
|
|
42
44
|
import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
|
|
43
45
|
import { scopeReadConfirmationArgs } from "./lib/read-confirmation.js";
|
|
44
46
|
import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
|
|
45
|
-
import { AgentBrowserResultComponent,
|
|
47
|
+
import { AgentBrowserResultComponent, formatAgentBrowserRenderCall, } from "./lib/pi-tool-rendering.js";
|
|
46
48
|
function browserExecutionFailure(error, signal, fallbackCategory = "upstream-error") {
|
|
47
49
|
const timeout = error instanceof Error && error.name === "TimeoutError";
|
|
48
50
|
const summary = redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
@@ -174,103 +176,60 @@ 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"
|
|
193
230
|
? { id: workaround.id, reason: workaround.reason }
|
|
194
231
|
: undefined;
|
|
195
232
|
}
|
|
196
|
-
function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionName, namespace) {
|
|
197
|
-
let restored;
|
|
198
|
-
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
199
|
-
for (const entry of branch) {
|
|
200
|
-
const message = getBrowserResultMessage(entry);
|
|
201
|
-
if (!message)
|
|
202
|
-
continue;
|
|
203
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
204
|
-
if (!details)
|
|
205
|
-
continue;
|
|
206
|
-
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
207
|
-
continue;
|
|
208
|
-
const recognizedWorkaround = getRecognizedCompatibilityWorkaround(details.compatibilityWorkaround);
|
|
209
|
-
const succeeded = getSuccessfulToolResult(details, message);
|
|
210
|
-
const outcome = getManagedSessionOutcome(details);
|
|
211
|
-
const activeAfterFailure = recognizedWorkaround
|
|
212
|
-
&& outcome?.activeAfter === true
|
|
213
|
-
&& typeof outcome.currentSessionName === "string"
|
|
214
|
-
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey
|
|
215
|
-
&& (outcome.status === "created" || outcome.status === "replaced" || outcome.status === "unchanged");
|
|
216
|
-
if (!succeeded && !activeAfterFailure)
|
|
217
|
-
continue;
|
|
218
|
-
if (recognizedWorkaround) {
|
|
219
|
-
restored = recognizedWorkaround;
|
|
220
|
-
}
|
|
221
|
-
else if (!canUseHeadlessCompatibilityUserAgent(getToolResultArgs(details))) {
|
|
222
|
-
restored = undefined;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return restored;
|
|
226
|
-
}
|
|
227
|
-
function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionName, namespace) {
|
|
228
|
-
let restored = false;
|
|
229
|
-
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
230
|
-
for (const entry of branch) {
|
|
231
|
-
const message = getBrowserResultMessage(entry);
|
|
232
|
-
if (!message)
|
|
233
|
-
continue;
|
|
234
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
235
|
-
if (!details)
|
|
236
|
-
continue;
|
|
237
|
-
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
238
|
-
continue;
|
|
239
|
-
const outcome = getManagedSessionOutcome(details);
|
|
240
|
-
const activeAfterFailure = outcome?.activeAfter === true
|
|
241
|
-
&& typeof outcome.currentSessionName === "string"
|
|
242
|
-
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
|
|
243
|
-
if ((getSuccessfulToolResult(details, message) || activeAfterFailure) && typeof details.managedSessionHeadedAutosaveDisabled === "boolean") {
|
|
244
|
-
restored = details.managedSessionHeadedAutosaveDisabled;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return restored;
|
|
248
|
-
}
|
|
249
|
-
function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionName, namespace) {
|
|
250
|
-
let restored;
|
|
251
|
-
const targetKey = getSessionContextKey(sessionName, namespace);
|
|
252
|
-
for (const entry of branch) {
|
|
253
|
-
const message = getBrowserResultMessage(entry);
|
|
254
|
-
if (!message)
|
|
255
|
-
continue;
|
|
256
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
257
|
-
if (!details)
|
|
258
|
-
continue;
|
|
259
|
-
if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
|
|
260
|
-
continue;
|
|
261
|
-
const outcome = getManagedSessionOutcome(details);
|
|
262
|
-
const activeAfterFailure = outcome?.activeAfter === true
|
|
263
|
-
&& typeof outcome.currentSessionName === "string"
|
|
264
|
-
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
|
|
265
|
-
if (!getSuccessfulToolResult(details, message) && !activeAfterFailure)
|
|
266
|
-
continue;
|
|
267
|
-
if (typeof details.managedSessionHeadedAutosaveInterval === "string")
|
|
268
|
-
restored = details.managedSessionHeadedAutosaveInterval;
|
|
269
|
-
else if (details.managedSessionHeadedAutosaveDisabled === true)
|
|
270
|
-
restored = "0";
|
|
271
|
-
}
|
|
272
|
-
return restored;
|
|
273
|
-
}
|
|
274
233
|
function getToolResultArgs(details) {
|
|
275
234
|
if (Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string"))
|
|
276
235
|
return details.args;
|
|
@@ -346,7 +305,7 @@ function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
|
|
|
346
305
|
: existing?.compatibilityWorkaround;
|
|
347
306
|
const headedManagedAutosaveDisabled = options.headedManagedAutosaveDisabled ?? existing?.headedManagedAutosaveDisabled;
|
|
348
307
|
const headedManagedAutosaveInterval = options.headedManagedAutosaveInterval ?? existing?.headedManagedAutosaveInterval;
|
|
349
|
-
sessions.set(key, { branchOwned, compatibilityWorkaround, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName });
|
|
308
|
+
sessions.set(key, { branchOwned, compatibilityWorkaround, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName, socketDir: options.socketDir ?? existing?.socketDir });
|
|
350
309
|
}
|
|
351
310
|
function untrackOwnedManagedSession(sessions, sessionName, namespace) {
|
|
352
311
|
if (!sessionName)
|
|
@@ -382,6 +341,7 @@ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
|
|
|
382
341
|
headedManagedAutosaveDisabled: details?.managedSessionHeadedAutosaveDisabled === true,
|
|
383
342
|
headedManagedAutosaveInterval: typeof details?.managedSessionHeadedAutosaveInterval === "string" ? details.managedSessionHeadedAutosaveInterval : undefined,
|
|
384
343
|
namespace,
|
|
344
|
+
socketDir: typeof details?.managedSessionSocketDir === "string" ? details.managedSessionSocketDir : undefined,
|
|
385
345
|
});
|
|
386
346
|
}
|
|
387
347
|
if (succeeded && status === "closed") {
|
|
@@ -394,6 +354,8 @@ function getTouchedElectronLaunchIds(sessionName, records, namespace) {
|
|
|
394
354
|
}
|
|
395
355
|
function mergeActiveElectronLaunchRecords(target, source, options = {}) {
|
|
396
356
|
for (const record of getActiveElectronRecords(source)) {
|
|
357
|
+
if (options.ownerSessionId !== undefined && record.ownerSessionId !== options.ownerSessionId)
|
|
358
|
+
continue;
|
|
397
359
|
const alreadyRuntimeOwned = target.has(record.launchId) && options.branchOwnedLaunchIds?.has(record.launchId) === false;
|
|
398
360
|
target.set(record.launchId, record);
|
|
399
361
|
if (options.branchOwnedLaunchIds) {
|
|
@@ -450,8 +412,10 @@ function shouldSerializeElectronHostInput(compiledElectron) {
|
|
|
450
412
|
return compiledElectron?.action === "status" || compiledElectron?.action === "probe" || compiledElectron?.action === "cleanup";
|
|
451
413
|
}
|
|
452
414
|
function getElectronHostLaunchRecordsForInput(options) {
|
|
415
|
+
if (options.compiledElectron?.action === "cleanup") {
|
|
416
|
+
return new Map([...mergeElectronLaunchRecordMaps(options.branchRecords, options.ownedRecords)].filter(([, record]) => record.ownerSessionId === options.ownerSessionId));
|
|
417
|
+
}
|
|
453
418
|
if (options.compiledElectron?.action === "status" ||
|
|
454
|
-
options.compiledElectron?.action === "cleanup" ||
|
|
455
419
|
(options.compiledElectron?.action === "probe" && options.compiledElectron.launchId)) {
|
|
456
420
|
return mergeElectronLaunchRecordMaps(options.branchRecords, options.ownedRecords);
|
|
457
421
|
}
|
|
@@ -536,6 +500,7 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
536
500
|
managedSessionActiveIdentities: new Map(),
|
|
537
501
|
managedSessionActiveRanks: new Map(),
|
|
538
502
|
managedSessionCloseRanks: new Map(),
|
|
503
|
+
managedSessionLaunchState: new Map(),
|
|
539
504
|
};
|
|
540
505
|
let eventRank = 0;
|
|
541
506
|
for (const entry of branch) {
|
|
@@ -557,6 +522,32 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
557
522
|
const batchCloseLifecycle = getSuccessfulBatchCloseLifecycle(details.batchSteps);
|
|
558
523
|
const closeAllApplied = detailsReportCloseAllApplied(details, succeeded);
|
|
559
524
|
const outcome = getManagedSessionOutcome(details);
|
|
525
|
+
if (sessionName) {
|
|
526
|
+
const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
527
|
+
const launch = events.managedSessionLaunchState.get(key) ?? { compatibilityWorkaround: undefined, headedManagedAutosaveDisabled: false };
|
|
528
|
+
if (typeof details.managedSessionCwd === "string" && isAbsolute(details.managedSessionCwd))
|
|
529
|
+
launch.cwd = details.managedSessionCwd;
|
|
530
|
+
if (typeof details.managedSessionSocketDir === "string")
|
|
531
|
+
launch.socketDir = details.managedSessionSocketDir;
|
|
532
|
+
const retainedAfterFailure = outcome?.activeAfter === true && typeof outcome.currentSessionName === "string"
|
|
533
|
+
&& getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === key;
|
|
534
|
+
const workaround = getRecognizedCompatibilityWorkaround(details.compatibilityWorkaround);
|
|
535
|
+
if (succeeded || workaround && retainedAfterFailure && ["created", "replaced", "unchanged"].includes(String(outcome?.status))) {
|
|
536
|
+
if (workaround)
|
|
537
|
+
launch.compatibilityWorkaround = workaround;
|
|
538
|
+
else if (!canUseHeadlessCompatibilityUserAgent(getToolResultArgs(details)))
|
|
539
|
+
launch.compatibilityWorkaround = undefined;
|
|
540
|
+
}
|
|
541
|
+
if (succeeded || retainedAfterFailure) {
|
|
542
|
+
if (typeof details.managedSessionHeadedAutosaveDisabled === "boolean")
|
|
543
|
+
launch.headedManagedAutosaveDisabled = details.managedSessionHeadedAutosaveDisabled;
|
|
544
|
+
if (typeof details.managedSessionHeadedAutosaveInterval === "string")
|
|
545
|
+
launch.headedManagedAutosaveInterval = details.managedSessionHeadedAutosaveInterval;
|
|
546
|
+
else if (details.managedSessionHeadedAutosaveDisabled === true)
|
|
547
|
+
launch.headedManagedAutosaveInterval = "0";
|
|
548
|
+
}
|
|
549
|
+
events.managedSessionLaunchState.set(key, launch);
|
|
550
|
+
}
|
|
560
551
|
const outcomeSucceeded = outcome?.succeeded === true;
|
|
561
552
|
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
562
553
|
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
@@ -631,7 +622,7 @@ async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessi
|
|
|
631
622
|
for (const [key, owner] of [...sessions]) {
|
|
632
623
|
if (key === keepKey)
|
|
633
624
|
continue;
|
|
634
|
-
const error = await closeManagedSession({ cwd: owner.cwd, headedManagedAutosaveInterval: owner.headedManagedAutosaveInterval, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, timeoutMs });
|
|
625
|
+
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
626
|
if (!error) {
|
|
636
627
|
sessions.delete(key);
|
|
637
628
|
onClosed?.(owner);
|
|
@@ -804,7 +795,7 @@ function hasArgvFlag(argv, longFlag, shortFlag) {
|
|
|
804
795
|
function shouldIncludeProjectConfig(ctx, argv = process.argv) {
|
|
805
796
|
if (hasArgvFlag(argv, "--no-approve", "-na"))
|
|
806
797
|
return false;
|
|
807
|
-
return ctx
|
|
798
|
+
return ctx.isProjectTrusted();
|
|
808
799
|
}
|
|
809
800
|
export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
810
801
|
const ephemeralSessionSeed = createEphemeralSessionSeed();
|
|
@@ -812,15 +803,23 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
812
803
|
cwd: process.cwd(),
|
|
813
804
|
includeProjectConfig: false,
|
|
814
805
|
});
|
|
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
806
|
const implicitSessionIdleTimeoutMs = String(getImplicitSessionIdleTimeoutMs());
|
|
823
807
|
const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
|
|
808
|
+
let promptState;
|
|
809
|
+
const restorePromptState = (ctx) => {
|
|
810
|
+
return promptState = { sessionId: ctx.sessionManager.getSessionId(), message: getLatestUserMessage(ctx.sessionManager) };
|
|
811
|
+
};
|
|
812
|
+
const getPromptPolicy = (ctx) => {
|
|
813
|
+
const state = promptState?.sessionId === ctx.sessionManager.getSessionId() ? promptState : restorePromptState(ctx);
|
|
814
|
+
// Native message_end precedes later transforms/persistence; retain the message
|
|
815
|
+
// reference so replacement user text cannot leave policy on the earlier text.
|
|
816
|
+
const text = getMessageText(state.message?.content);
|
|
817
|
+
if (!state.policy || state.text !== text) {
|
|
818
|
+
state.text = text;
|
|
819
|
+
state.policy = buildPromptPolicy(text);
|
|
820
|
+
}
|
|
821
|
+
return state.policy;
|
|
822
|
+
};
|
|
824
823
|
let webSearchToolRegistered = false;
|
|
825
824
|
let managedSessionActive = false;
|
|
826
825
|
let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
|
|
@@ -832,6 +831,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
832
831
|
let managedSessionNamespace;
|
|
833
832
|
let freshSessionOrdinal = 0;
|
|
834
833
|
let sessionPageState = new SessionPageState();
|
|
834
|
+
let browserReplayError;
|
|
835
835
|
let traceOwners = new Map();
|
|
836
836
|
let artifactManifest;
|
|
837
837
|
let activeRecordingReservations = new Map();
|
|
@@ -841,6 +841,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
841
841
|
let networkRoutesBySession = new Map();
|
|
842
842
|
let electronLaunchRecords = new Map();
|
|
843
843
|
let ownedElectronLaunchRecords = new Map();
|
|
844
|
+
let browserOwnerSessionId = "";
|
|
844
845
|
let branchOwnedElectronLaunchIds = new Set();
|
|
845
846
|
let electronChildProcesses = new Map();
|
|
846
847
|
const managedSessionRestoreState = new ManagedSessionRestoreState();
|
|
@@ -1054,13 +1055,13 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1054
1055
|
}));
|
|
1055
1056
|
if (closeError) {
|
|
1056
1057
|
try {
|
|
1057
|
-
appendScriptSessionLease(pi, sessionName, "failed");
|
|
1058
|
+
appendScriptSessionLease(pi, sessionName, "failed", browserOwnerSessionId);
|
|
1058
1059
|
}
|
|
1059
1060
|
catch { }
|
|
1060
1061
|
return redactSensitiveText(closeError);
|
|
1061
1062
|
}
|
|
1062
1063
|
try {
|
|
1063
|
-
appendScriptSessionLease(pi, sessionName, "closed");
|
|
1064
|
+
appendScriptSessionLease(pi, sessionName, "closed", browserOwnerSessionId);
|
|
1064
1065
|
}
|
|
1065
1066
|
catch {
|
|
1066
1067
|
managedSessionRestoreState.disable(sessionName);
|
|
@@ -1072,11 +1073,11 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1072
1073
|
clearSessionScopedBrowserState(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
|
|
1073
1074
|
return undefined;
|
|
1074
1075
|
};
|
|
1075
|
-
const recoverScriptSessionLeasesWithinQueue = async (ctx) => {
|
|
1076
|
+
const recoverScriptSessionLeasesWithinQueue = async (ctx, branch) => {
|
|
1076
1077
|
const pendingSessionNames = new Set([...ownedManagedSessions.values()]
|
|
1077
1078
|
.map((session) => session.sessionName)
|
|
1078
1079
|
.filter(isAgentBrowserScriptSessionName));
|
|
1079
|
-
for (const lease of getScriptSessionLeasesFromBranch(ctx.sessionManager.
|
|
1080
|
+
for (const lease of getScriptSessionLeasesFromBranch(branch, ctx.sessionManager.getSessionId()).values()) {
|
|
1080
1081
|
if (lease.cleanup !== "closed")
|
|
1081
1082
|
pendingSessionNames.add(lease.sessionName);
|
|
1082
1083
|
}
|
|
@@ -1086,15 +1087,26 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1086
1087
|
await closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
|
|
1087
1088
|
}
|
|
1088
1089
|
};
|
|
1089
|
-
const restoreBranchBackedState = (ctx, options) => {
|
|
1090
|
+
const restoreBranchBackedState = async (ctx, options) => {
|
|
1090
1091
|
branchRestoreGeneration += 1;
|
|
1091
1092
|
branchStateGeneration += 1;
|
|
1093
|
+
restorePromptState(ctx);
|
|
1092
1094
|
const previousManagedSessionActive = managedSessionActive;
|
|
1093
1095
|
const previousManagedSessionName = managedSessionName;
|
|
1094
1096
|
const previousFreshSessionOrdinal = freshSessionOrdinal;
|
|
1095
1097
|
const previousAttachedSessionKeys = attachedSessionKeys;
|
|
1096
1098
|
managedSessionBaseName = createImplicitSessionName(ctx.sessionManager.getSessionId(), ctx.cwd, ephemeralSessionSeed);
|
|
1097
|
-
|
|
1099
|
+
browserOwnerSessionId = ctx.sessionManager.getSessionId();
|
|
1100
|
+
let branch;
|
|
1101
|
+
try {
|
|
1102
|
+
branch = await readBrowserEntries(ctx.sessionManager);
|
|
1103
|
+
browserReplayError = undefined;
|
|
1104
|
+
}
|
|
1105
|
+
catch (error) {
|
|
1106
|
+
browserReplayError = error instanceof Error ? error.message : String(error);
|
|
1107
|
+
sessionPageState.reset();
|
|
1108
|
+
throw error;
|
|
1109
|
+
}
|
|
1098
1110
|
const branchResourceEvents = collectBranchManagedResourceEvents(branch);
|
|
1099
1111
|
const restoredState = restoreManagedSessionStateFromBranch(branch, managedSessionBaseName);
|
|
1100
1112
|
managedSessionRestoreState.replace(restoredState.managedSessionRestoreDisabledIdentities, {
|
|
@@ -1120,15 +1132,11 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1120
1132
|
: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, nextFreshSessionOrdinal)
|
|
1121
1133
|
: restoredState.sessionName;
|
|
1122
1134
|
managedSessionNamespace = shouldReservePostCloseSession ? undefined : restoredState.namespace;
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
managedSessionHeadedAutosaveInterval = managedSessionActive
|
|
1129
|
-
? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
|
|
1130
|
-
: undefined;
|
|
1131
|
-
managedSessionCwd = restoreManagedSessionCwd(branch, managedSessionName, managedSessionNamespace, ctx.cwd);
|
|
1135
|
+
const currentLaunch = branchResourceEvents.managedSessionLaunchState.get(getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName);
|
|
1136
|
+
managedSessionCompatibilityWorkaround = managedSessionActive ? currentLaunch?.compatibilityWorkaround : undefined;
|
|
1137
|
+
managedSessionHeadedAutosaveDisabled = managedSessionActive && currentLaunch?.headedManagedAutosaveDisabled === true;
|
|
1138
|
+
managedSessionHeadedAutosaveInterval = managedSessionActive ? currentLaunch?.headedManagedAutosaveInterval : undefined;
|
|
1139
|
+
managedSessionCwd = currentLaunch?.cwd ?? ctx.cwd;
|
|
1132
1140
|
freshSessionOrdinal = nextFreshSessionOrdinal;
|
|
1133
1141
|
sessionPageState = SessionPageState.fromBranch(branch);
|
|
1134
1142
|
traceOwners = new Map();
|
|
@@ -1179,12 +1187,14 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1179
1187
|
continue;
|
|
1180
1188
|
if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
|
|
1181
1189
|
continue;
|
|
1182
|
-
|
|
1190
|
+
const launch = branchResourceEvents.managedSessionLaunchState.get(sessionKey);
|
|
1191
|
+
trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, launch?.cwd ?? ctx.cwd, {
|
|
1183
1192
|
branchOwned: true,
|
|
1184
|
-
compatibilityWorkaround:
|
|
1185
|
-
headedManagedAutosaveDisabled:
|
|
1186
|
-
headedManagedAutosaveInterval:
|
|
1193
|
+
compatibilityWorkaround: launch?.compatibilityWorkaround,
|
|
1194
|
+
headedManagedAutosaveDisabled: launch?.headedManagedAutosaveDisabled ?? false,
|
|
1195
|
+
headedManagedAutosaveInterval: launch?.headedManagedAutosaveInterval,
|
|
1187
1196
|
namespace: identity.namespace,
|
|
1197
|
+
socketDir: launch?.socketDir,
|
|
1188
1198
|
});
|
|
1189
1199
|
}
|
|
1190
1200
|
if (restoredState.active) {
|
|
@@ -1194,6 +1204,32 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1194
1204
|
headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
|
|
1195
1205
|
headedManagedAutosaveInterval: managedSessionHeadedAutosaveInterval,
|
|
1196
1206
|
namespace: restoredState.namespace,
|
|
1207
|
+
socketDir: currentLaunch?.socketDir,
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
const pendingOwnedBegins = new Map();
|
|
1211
|
+
for (const entry of branch) {
|
|
1212
|
+
const event = getBrowserRecord(entry)?.event;
|
|
1213
|
+
if (!event)
|
|
1214
|
+
continue;
|
|
1215
|
+
if (event.phase === "begin" && event.state.ownerSessionId === browserOwnerSessionId && event.state.wrapperManaged === true)
|
|
1216
|
+
pendingOwnedBegins.set(event.operationId, event);
|
|
1217
|
+
else if (event.phase === "finish")
|
|
1218
|
+
pendingOwnedBegins.delete(event.operationId);
|
|
1219
|
+
for (const page of event.pages ?? [])
|
|
1220
|
+
if (page.clear) {
|
|
1221
|
+
for (const [id, begin] of pendingOwnedBegins)
|
|
1222
|
+
if (begin.pages?.some(candidate => candidate.key === page.key))
|
|
1223
|
+
pendingOwnedBegins.delete(id);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
for (const begin of pendingOwnedBegins.values()) {
|
|
1227
|
+
if (typeof begin.state.sessionName !== "string")
|
|
1228
|
+
continue;
|
|
1229
|
+
// The persisted wrapper-selected intent proves cleanup scope, not launch success.
|
|
1230
|
+
trackOwnedManagedSession(ownedManagedSessions, begin.state.sessionName, typeof begin.state.managedSessionCwd === "string" ? begin.state.managedSessionCwd : ctx.cwd, {
|
|
1231
|
+
branchOwned: true, namespace: typeof begin.state.namespace === "string" ? begin.state.namespace : undefined,
|
|
1232
|
+
socketDir: typeof begin.state.managedSessionSocketDir === "string" ? begin.state.managedSessionSocketDir : undefined,
|
|
1197
1233
|
});
|
|
1198
1234
|
}
|
|
1199
1235
|
for (const record of getActiveElectronRecords(electronLaunchRecords)) {
|
|
@@ -1204,16 +1240,29 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1204
1240
|
const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
|
|
1205
1241
|
if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
|
|
1206
1242
|
continue;
|
|
1207
|
-
|
|
1243
|
+
const launch = branchResourceEvents.managedSessionLaunchState.get(sessionKey);
|
|
1244
|
+
trackOwnedManagedSession(ownedManagedSessions, record.sessionName, launch?.cwd ?? ctx.cwd, {
|
|
1208
1245
|
branchOwned: true,
|
|
1209
|
-
headedManagedAutosaveDisabled:
|
|
1210
|
-
headedManagedAutosaveInterval:
|
|
1246
|
+
headedManagedAutosaveDisabled: launch?.headedManagedAutosaveDisabled ?? false,
|
|
1247
|
+
headedManagedAutosaveInterval: launch?.headedManagedAutosaveInterval,
|
|
1248
|
+
socketDir: launch?.socketDir,
|
|
1211
1249
|
});
|
|
1212
1250
|
}
|
|
1213
|
-
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
1251
|
+
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, new Map([...electronLaunchRecords].filter(([, record]) => record.ownerSessionId === browserOwnerSessionId)), {
|
|
1214
1252
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1215
1253
|
markBranchOwned: true,
|
|
1216
1254
|
});
|
|
1255
|
+
for (const entry of branch) {
|
|
1256
|
+
const event = getBrowserRecord(entry)?.event;
|
|
1257
|
+
const state = event?.state;
|
|
1258
|
+
if (event?.phase === "begin" || state?.ownerSessionId !== browserOwnerSessionId || typeof state.sessionName !== "string")
|
|
1259
|
+
continue;
|
|
1260
|
+
const namespace = typeof state.namespace === "string" ? state.namespace : undefined;
|
|
1261
|
+
const key = getAgentBrowserSessionIdentityKey(state.sessionName, namespace);
|
|
1262
|
+
if (ownedManagedSessions.has(key) && !managedSessionRestoreState.hasDaemonRestoreKey(state.sessionName, namespace)) {
|
|
1263
|
+
managedSessionRestoreState.restoreDaemonReceipt(state.sessionName, namespace, state.managedSessionDaemon);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1217
1266
|
if (!options.resetRuntimeOwnership) {
|
|
1218
1267
|
for (const sessionKey of previousAttachedSessionKeys) {
|
|
1219
1268
|
if (ownedManagedSessions.has(sessionKey))
|
|
@@ -1225,12 +1274,12 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1225
1274
|
attachedSessionKeys.add(sessionKey);
|
|
1226
1275
|
}
|
|
1227
1276
|
}
|
|
1277
|
+
return branch;
|
|
1228
1278
|
};
|
|
1229
1279
|
const registerWebSearchToolIfAvailable = (configState) => {
|
|
1230
1280
|
if (webSearchToolRegistered || !canRegisterWebSearchTool(configState))
|
|
1231
1281
|
return;
|
|
1232
1282
|
pi.registerTool({
|
|
1233
|
-
...{ discovery: { group: AGENT_BROWSER_DISCOVERY_GROUP, role: "entry" } },
|
|
1234
1283
|
...createAgentBrowserWebSearchTool(configState, {
|
|
1235
1284
|
loadConfigState(ctx) {
|
|
1236
1285
|
return loadAgentBrowserConfigSync({
|
|
@@ -1242,15 +1291,19 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1242
1291
|
});
|
|
1243
1292
|
webSearchToolRegistered = true;
|
|
1244
1293
|
};
|
|
1294
|
+
pi.on("message_end", (event, ctx) => {
|
|
1295
|
+
if (event.message.role === "user")
|
|
1296
|
+
promptState = { sessionId: ctx.sessionManager.getSessionId(), message: event.message };
|
|
1297
|
+
});
|
|
1245
1298
|
pi.on("session_start", async (_event, ctx) => {
|
|
1246
|
-
restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1247
|
-
electronChildProcesses = new Map();
|
|
1248
|
-
registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
|
|
1249
|
-
cwd: ctx.cwd,
|
|
1250
|
-
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
1251
|
-
}));
|
|
1252
1299
|
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1253
|
-
await
|
|
1300
|
+
const branch = await restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
|
|
1301
|
+
electronChildProcesses = new Map();
|
|
1302
|
+
registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
|
|
1303
|
+
cwd: ctx.cwd,
|
|
1304
|
+
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
1305
|
+
}));
|
|
1306
|
+
await recoverScriptSessionLeasesWithinQueue(ctx, branch);
|
|
1254
1307
|
flushRecordingReservations();
|
|
1255
1308
|
notifyRecordingPersistence(ctx);
|
|
1256
1309
|
}));
|
|
@@ -1261,85 +1314,12 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1261
1314
|
controller.abort();
|
|
1262
1315
|
await Promise.allSettled([...activeScriptExecutions]);
|
|
1263
1316
|
await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
|
|
1264
|
-
restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1265
|
-
await recoverScriptSessionLeasesWithinQueue(ctx);
|
|
1317
|
+
const branch = await restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
|
|
1318
|
+
await recoverScriptSessionLeasesWithinQueue(ctx, branch);
|
|
1266
1319
|
flushRecordingReservations();
|
|
1267
1320
|
notifyRecordingPersistence(ctx);
|
|
1268
1321
|
}));
|
|
1269
1322
|
});
|
|
1270
|
-
// Fork-only checkpoint event; official Pi validation types do not declare it.
|
|
1271
|
-
// Pi owns awaited tools/events (including their execution queues). Detached code
|
|
1272
|
-
// children and retained browser/recording resources still need extension-side checks.
|
|
1273
|
-
const checkpointPi = pi;
|
|
1274
|
-
checkpointPi.on("session_checkpoint", async (event, ctx) => {
|
|
1275
|
-
const blocked = (reason) => ({ sleepReady: false, reason });
|
|
1276
|
-
if (activeScriptControllers.size || activeScriptExecutions.size)
|
|
1277
|
-
return blocked("Browser code execution is pending");
|
|
1278
|
-
flushRecordingReservations();
|
|
1279
|
-
if (recordingReservationsDirty)
|
|
1280
|
-
return blocked("Browser recording journal persistence is dirty");
|
|
1281
|
-
if (activeRecordingReservations.size)
|
|
1282
|
-
return blocked("Browser recording is pending; finish it explicitly before sleep");
|
|
1283
|
-
const entries = ctx.sessionManager.getEntries();
|
|
1284
|
-
if ([...getScriptSessionLeasesFromBranch(entries).values()].some(lease => lease.cleanup !== "closed")
|
|
1285
|
-
|| [...ownedManagedSessions.values()].some(owner => isAgentBrowserScriptSessionName(owner.sessionName))) {
|
|
1286
|
-
return blocked("Browser script cleanup lease is unresolved");
|
|
1287
|
-
}
|
|
1288
|
-
if (getActiveElectronRecords(ownedElectronLaunchRecords).length || getActiveElectronRecords(electronLaunchRecords).length
|
|
1289
|
-
|| [...electronChildProcesses.values()].some(child => child.exitCode === null && child.signalCode === null)) {
|
|
1290
|
-
return blocked("Electron launch is still active");
|
|
1291
|
-
}
|
|
1292
|
-
if (attachedSessionKeys.size || restoreAttachedSessionKeysFromBranch(entries).size)
|
|
1293
|
-
return blocked("Attached browser state is caller-owned and not checkpointed");
|
|
1294
|
-
if (traceOwners.size || [...networkRoutesBySession.values()].some(routes => routes.length))
|
|
1295
|
-
return blocked("Browser trace or network routes are still active");
|
|
1296
|
-
// Include off-branch and caller-owned/root identities, without acquiring
|
|
1297
|
-
// cleanup ownership. Transcript page/ref details do not serialize a browser.
|
|
1298
|
-
const historicalResources = collectBranchManagedResourceEvents(entries);
|
|
1299
|
-
const sessions = new Map(ownedManagedSessions);
|
|
1300
|
-
if (managedSessionActive)
|
|
1301
|
-
trackOwnedManagedSession(sessions, managedSessionName, managedSessionCwd, { namespace: managedSessionNamespace });
|
|
1302
|
-
for (const entry of entries) {
|
|
1303
|
-
const message = getBrowserResultMessage(entry);
|
|
1304
|
-
if (!message)
|
|
1305
|
-
continue;
|
|
1306
|
-
const details = isRecord(message.details) ? message.details : undefined;
|
|
1307
|
-
// Helpers can launch before a main-process failure. Conversely, native
|
|
1308
|
-
// launch flags on sessionless reads can open the unnamed default browser.
|
|
1309
|
-
const sessionName = typeof details?.sessionName === "string" ? details.sessionName
|
|
1310
|
-
: details?.agentBrowserStarted === true ? "default" : undefined;
|
|
1311
|
-
if (!sessionName)
|
|
1312
|
-
continue;
|
|
1313
|
-
const namespace = typeof details?.namespace === "string" ? details.namespace : undefined;
|
|
1314
|
-
const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
1315
|
-
if (!sessions.has(key))
|
|
1316
|
-
trackOwnedManagedSession(sessions, sessionName, ctx.cwd, { namespace });
|
|
1317
|
-
}
|
|
1318
|
-
for (const [key, owner] of sessions) {
|
|
1319
|
-
event.signal.throwIfAborted();
|
|
1320
|
-
// An abnormal restart can leave a wrapper-created browser only off-branch.
|
|
1321
|
-
// Reuse native ownership events for inspection, never cleanup ownership.
|
|
1322
|
-
// A later close can refer to the same identity in a different socket root.
|
|
1323
|
-
const historicalIdentity = historicalResources.managedSessionActiveIdentities.get(key);
|
|
1324
|
-
const historicallyOwned = historicalIdentity && isRestorableManagedSessionName(historicalIdentity.sessionName, managedSessionBaseName);
|
|
1325
|
-
const context = resolveOwnedManagedSessionContext({
|
|
1326
|
-
...owner,
|
|
1327
|
-
currentManagedSessionName: managedSessionActive ? managedSessionName : undefined,
|
|
1328
|
-
currentManagedSessionNamespace: managedSessionNamespace,
|
|
1329
|
-
recordedOwnedSession: ownedManagedSessions.get(key) ?? (historicallyOwned ? { ...historicalIdentity, cwd: ctx.cwd } : undefined),
|
|
1330
|
-
restoreState: managedSessionRestoreState,
|
|
1331
|
-
});
|
|
1332
|
-
// Owned routing must not mask an explicit caller's same-name ambient daemon.
|
|
1333
|
-
for (const inspectionContext of context ? [{ ...context, reuseOnly: true }, undefined] : [undefined]) {
|
|
1334
|
-
const daemon = await withOwnedManagedSessionContext(inspectionContext, () => inspectManagedSessionDaemon({ ...owner, signal: event.signal, timeoutMs: 2_000 }));
|
|
1335
|
-
if (daemon.status !== "inactive")
|
|
1336
|
-
return blocked("Browser daemon is live or unverified; finish browser work explicitly before sleep");
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
// No detached writer remains to pause/resume. Native ingress stays held;
|
|
1340
|
-
// checkpoint never closes a browser or changes ordinary shutdown ownership.
|
|
1341
|
-
return { sleepReady: true };
|
|
1342
|
-
});
|
|
1343
1323
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
1344
1324
|
for (const controller of activeScriptControllers)
|
|
1345
1325
|
controller.abort();
|
|
@@ -1351,7 +1331,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1351
1331
|
const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
|
|
1352
1332
|
const quitting = event?.reason === "quit";
|
|
1353
1333
|
preservedElectronProfileDirs = quitting
|
|
1354
|
-
?
|
|
1334
|
+
? getActiveElectronRecords(electronLaunchRecords).filter(record => !ownedElectronLaunchRecords.has(record.launchId)).map(record => record.userDataDir)
|
|
1355
1335
|
: getActiveElectronRecords(electronLaunchRecords).map((record) => record.userDataDir);
|
|
1356
1336
|
const electronRecordsToCleanup = quitting
|
|
1357
1337
|
? ownedElectronLaunchRecords
|
|
@@ -1402,11 +1382,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1402
1382
|
ownedManagedSessions.clear();
|
|
1403
1383
|
await cleanupSecureTempArtifacts({ preservePaths: preservedElectronProfileDirs });
|
|
1404
1384
|
});
|
|
1405
|
-
|
|
1406
|
-
const sectionTools = event.systemPromptOptions?.sectionTools;
|
|
1407
|
-
if (!Object.hasOwn(sectionTools ?? {}, "agent_browser") && !shouldAppendBrowserSystemPrompt(event.prompt)) {
|
|
1408
|
-
return undefined;
|
|
1409
|
-
}
|
|
1385
|
+
const fullInstructions = (ctx) => {
|
|
1410
1386
|
const runtimeConfig = loadAgentBrowserConfigSync({
|
|
1411
1387
|
cwd: ctx.cwd,
|
|
1412
1388
|
includeProjectConfig: shouldIncludeProjectConfig(ctx),
|
|
@@ -1422,14 +1398,42 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1422
1398
|
const runtimeConfigPrompt = browserGuidance.length > 0
|
|
1423
1399
|
? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
|
|
1424
1400
|
: "";
|
|
1425
|
-
|
|
1401
|
+
const guidelines = [
|
|
1402
|
+
...buildToolPromptGuidelines({
|
|
1403
|
+
browserDefaultProfile: runtimeConfig.trustedBrowserDefaultProfile,
|
|
1404
|
+
browserExecutablePath: runtimeConfig.trustedBrowserExecutablePath,
|
|
1405
|
+
includeWebSearch: webSearchToolRegistered,
|
|
1406
|
+
docs: getInstalledDocsPaths(),
|
|
1407
|
+
}),
|
|
1408
|
+
...QUICK_START_GUIDELINES,
|
|
1409
|
+
...SHARED_BROWSER_PLAYBOOK_GUIDELINES,
|
|
1410
|
+
...WRAPPER_TAB_RECOVERY_BEHAVIOR,
|
|
1411
|
+
...Object.values(ADVANCED_TOOL_PROMPT_GUIDELINES).flat(),
|
|
1412
|
+
...(webSearchToolRegistered ? WEB_SEARCH_TOOL_PROMPT_GUIDELINES : []),
|
|
1413
|
+
];
|
|
1414
|
+
return `${PROJECT_RULE_PROMPT}\n\n${[...new Set(guidelines)].map(line => `- ${line}`).join("\n")}${runtimeConfigPrompt}`;
|
|
1415
|
+
};
|
|
1416
|
+
let isInstructionsManaged = () => false;
|
|
1417
|
+
pi.events.on("pi:instruction-groups", (data) => {
|
|
1418
|
+
const collector = data;
|
|
1419
|
+
collector.register({
|
|
1420
|
+
...AGENT_BROWSER_INSTRUCTION_GROUP,
|
|
1421
|
+
tools: ["agent_browser", "agent_browser_code", "agent_browser_tools", "agent_browser_web_search",
|
|
1422
|
+
...Object.values(AGENT_BROWSER_TOOL_INVENTORY).map(({ name }) => name)],
|
|
1423
|
+
instructions: fullInstructions,
|
|
1424
|
+
});
|
|
1425
|
+
isInstructionsManaged = collector.isManaged;
|
|
1426
|
+
});
|
|
1427
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
1428
|
+
if (!isInstructionsManaged())
|
|
1429
|
+
event.systemPromptOptions.sections.agent_browser = fullInstructions(ctx);
|
|
1426
1430
|
});
|
|
1427
1431
|
pi.on("tool_call", async (event, ctx) => {
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1432
|
+
if (!isBashToolCallEvent(event) || !looksLikeDirectAgentBrowserBash(event.input.command)
|
|
1433
|
+
|| isHarmlessAgentBrowserInspectionCommand(event.input.command))
|
|
1434
|
+
return;
|
|
1435
|
+
const promptPolicy = getPromptPolicy(ctx);
|
|
1436
|
+
if (!promptPolicy.allowLegacyAgentBrowserBash &&
|
|
1433
1437
|
!(await isDirectAgentBrowserBashAllowed(ctx.cwd))) {
|
|
1434
1438
|
return {
|
|
1435
1439
|
block: true,
|
|
@@ -1437,7 +1441,18 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1437
1441
|
};
|
|
1438
1442
|
}
|
|
1439
1443
|
});
|
|
1440
|
-
|
|
1444
|
+
let emptyBrowserBranch;
|
|
1445
|
+
const captureCurrentBrowserBranch = (ctx) => {
|
|
1446
|
+
const generation = branchRestoreGeneration;
|
|
1447
|
+
const empty = ctx.sessionManager.getLeafId() === null;
|
|
1448
|
+
// Concurrent empty-root callers share only their own first synchronous append.
|
|
1449
|
+
if (empty && emptyBrowserBranch?.anchorId === null
|
|
1450
|
+
&& emptyBrowserBranch.sessionId === ctx.sessionManager.getSessionId() && emptyBrowserBranch.isCurrent())
|
|
1451
|
+
return emptyBrowserBranch;
|
|
1452
|
+
const branch = captureBrowserBranch(ctx.sessionManager, () => generation === branchRestoreGeneration);
|
|
1453
|
+
emptyBrowserBranch = empty ? branch : undefined;
|
|
1454
|
+
return branch;
|
|
1455
|
+
};
|
|
1441
1456
|
const agentBrowserTool = {
|
|
1442
1457
|
renderCall(args, theme, context) {
|
|
1443
1458
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -1448,10 +1463,11 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1448
1463
|
const component = context.lastComponent instanceof AgentBrowserResultComponent
|
|
1449
1464
|
? context.lastComponent
|
|
1450
1465
|
: new AgentBrowserResultComponent();
|
|
1451
|
-
component.
|
|
1466
|
+
component.setResult(result, options, theme, context.isError);
|
|
1452
1467
|
return component;
|
|
1453
1468
|
},
|
|
1454
|
-
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true) {
|
|
1469
|
+
async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true, commandIndex, selectedBranch) {
|
|
1470
|
+
const branch = selectedBranch ?? captureCurrentBrowserBranch(ctx);
|
|
1455
1471
|
let operationCwd;
|
|
1456
1472
|
try {
|
|
1457
1473
|
operationCwd = capturedCwd ?? resolveExecutionCwd(pi, ctx);
|
|
@@ -1459,7 +1475,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1459
1475
|
catch (error) {
|
|
1460
1476
|
return buildValidationFailureResult({ kind: "invalid", status: "invalid", redactedArgs: [], toolArgs: [], validationError: error instanceof Error ? error.message : String(error) });
|
|
1461
1477
|
}
|
|
1462
|
-
const promptPolicy =
|
|
1478
|
+
const promptPolicy = getPromptPolicy(ctx);
|
|
1463
1479
|
const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
|
|
1464
1480
|
const admittedInput = resolveAgentBrowserInput({
|
|
1465
1481
|
getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: operationCwd, outputPath, stdin }),
|
|
@@ -1468,6 +1484,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1468
1484
|
if (admittedInput.status === "invalid") {
|
|
1469
1485
|
return buildValidationFailureResult(admittedInput);
|
|
1470
1486
|
}
|
|
1487
|
+
if (browserReplayError && !isPlainTextInspectionArgs(admittedInput.toolArgs))
|
|
1488
|
+
return browserExecutionFailure(new Error(browserReplayError), signal, "validation-error");
|
|
1471
1489
|
const explicitConfig = scanUpstreamGlobalFlagOccurrences(admittedInput.toolArgs, "--config").length > 0 || getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIG !== undefined;
|
|
1472
1490
|
// Managed selection must follow earlier queued fresh launches, while file paths keep their admission snapshot.
|
|
1473
1491
|
const managedAtAdmission = admittedInput.kind !== "electron"
|
|
@@ -1533,7 +1551,31 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1533
1551
|
branchRecords: electronLaunchRecords,
|
|
1534
1552
|
compiledElectron,
|
|
1535
1553
|
ownedRecords: ownedElectronLaunchRecords,
|
|
1554
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
1536
1555
|
});
|
|
1556
|
+
const hostAffecting = compiledElectron && ["cleanup", "probe"].includes(compiledElectron.action);
|
|
1557
|
+
const workingPageState = sessionPageState.fork();
|
|
1558
|
+
const priorPages = workingPageState.views();
|
|
1559
|
+
const priorManifest = artifactManifest;
|
|
1560
|
+
const operationId = randomUUID();
|
|
1561
|
+
const launchId = compiledElectron && "launchId" in compiledElectron ? compiledElectron.launchId : undefined;
|
|
1562
|
+
const selected = launchId ? [electronHostLaunchRecords.get(launchId)].filter((record) => record !== undefined) : getActiveElectronRecords(electronHostLaunchRecords);
|
|
1563
|
+
const keys = new Set(selected.flatMap(record => record.sessionName ? [getAgentBrowserSessionIdentityKey(record.sessionName, record.namespace)] : []));
|
|
1564
|
+
if (compiledElectron?.action === "probe" && !launchId && managedSessionActive)
|
|
1565
|
+
keys.add(getAgentBrowserSessionIdentityKey(managedSessionName, managedSessionNamespace));
|
|
1566
|
+
const begin = { event: { version: 1, phase: "begin", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: true,
|
|
1567
|
+
state: {}, pages: [...keys].map(key => ({ key, refs: { kind: "unknown" }, unknown: true })) } };
|
|
1568
|
+
if (hostAffecting) {
|
|
1569
|
+
try {
|
|
1570
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, begin), begin, branch)
|
|
1571
|
+
|| !branch.isCurrent())
|
|
1572
|
+
throw new Error("The selected Pi branch changed before host-operation admission.");
|
|
1573
|
+
}
|
|
1574
|
+
catch (error) {
|
|
1575
|
+
return browserExecutionFailure(new Error("Could not persist Electron/browser-state begin; the host operation was not run.", { cause: error }), executionSignal);
|
|
1576
|
+
}
|
|
1577
|
+
sessionPageState.applyBrowserRecord(begin);
|
|
1578
|
+
}
|
|
1537
1579
|
let electronHostResult = await handleElectronHostInput({
|
|
1538
1580
|
attachedSessionKeys,
|
|
1539
1581
|
compiledElectron,
|
|
@@ -1547,7 +1589,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1547
1589
|
managedSessionRestoreState,
|
|
1548
1590
|
ownedManagedSessions,
|
|
1549
1591
|
redactedCompiledElectron,
|
|
1550
|
-
sessionPageState,
|
|
1592
|
+
sessionPageState: workingPageState,
|
|
1551
1593
|
signal: executionSignal,
|
|
1552
1594
|
});
|
|
1553
1595
|
if (electronHostResult && compiledElectron?.action === "cleanup") {
|
|
@@ -1575,6 +1617,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1575
1617
|
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1576
1618
|
const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
|
|
1577
1619
|
clearSessionScopedBrowserState(closedSessionKey);
|
|
1620
|
+
workingPageState.clearSession(closedSessionKey);
|
|
1578
1621
|
if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
|
|
1579
1622
|
managedSessionActive = false;
|
|
1580
1623
|
managedSessionCompatibilityWorkaround = undefined;
|
|
@@ -1585,15 +1628,37 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1585
1628
|
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
1586
1629
|
}
|
|
1587
1630
|
}
|
|
1588
|
-
if (artifactManifest)
|
|
1589
|
-
electronHostResult =
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1631
|
+
if (artifactManifest && isRecord(electronHostResult.details))
|
|
1632
|
+
electronHostResult.details.artifactRetentionSummary = formatSessionArtifactRetentionSummary(artifactManifest);
|
|
1633
|
+
}
|
|
1634
|
+
if (electronHostResult && hostAffecting && branch.isCurrent()) {
|
|
1635
|
+
for (const key of keys)
|
|
1636
|
+
if (workingPageState.get(key).refSnapshot && workingPageState.get(key).refSnapshot?.snapshotId !== priorPages.get(key)?.refSnapshot?.snapshotId) {
|
|
1637
|
+
const record = selected.find(record => record.sessionName && getAgentBrowserSessionIdentityKey(record.sessionName, record.namespace) === key);
|
|
1638
|
+
const owner = ownedManagedSessions.get(key);
|
|
1639
|
+
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) });
|
|
1640
|
+
workingPageState.bindSnapshotGeneration(key, daemon.status === "active" ? daemon.generation : undefined);
|
|
1641
|
+
}
|
|
1642
|
+
const changes = browserPageChanges(workingPageState, priorPages, keys);
|
|
1643
|
+
const finish = { event: { version: 1, phase: hostAffecting ? "finish" : "state", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: electronHostResult.isError === true,
|
|
1644
|
+
state: browserStateEffects(isRecord(electronHostResult.details) ? electronHostResult.details : {}), pages: changes.pages, artifacts: artifactChanges(priorManifest, artifactManifest) },
|
|
1645
|
+
...(changes.snapshot ? { snapshot: changes.snapshot } : {}) };
|
|
1646
|
+
try {
|
|
1647
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, finish), finish, branch)
|
|
1648
|
+
|| !branch.isCurrent())
|
|
1649
|
+
throw new Error("The selected Pi branch changed before host-state confirmation.");
|
|
1650
|
+
}
|
|
1651
|
+
catch (error) {
|
|
1652
|
+
if (branch.isCurrent())
|
|
1653
|
+
sessionPageState.applyBrowserRecord(begin);
|
|
1654
|
+
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),
|
|
1655
|
+
details: { ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}), browserStatePersistence: "finish-unconfirmed", resultCategory: "failure", failureCategory: "upstream-error", nextActions: undefined } };
|
|
1656
|
+
}
|
|
1657
|
+
sessionPageState.applyBrowserRecord(finish);
|
|
1658
|
+
artifactManifest = applyArtifactChanges(artifactManifest, finish.event.artifacts);
|
|
1659
|
+
if (isRecord(electronHostResult.details)) {
|
|
1660
|
+
electronHostResult.details.browserEventVersion = 1;
|
|
1661
|
+
electronHostResult.details.artifactManifest = invocationArtifactManifest(artifactManifest, priorManifest, electronHostResult.details);
|
|
1597
1662
|
}
|
|
1598
1663
|
}
|
|
1599
1664
|
if (electronHostResult && executionSignal?.aborted) {
|
|
@@ -1614,7 +1679,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1614
1679
|
}) : runElectronHostInput(executionSignal);
|
|
1615
1680
|
if (!shouldSerializeElectronHostInput(compiledElectron))
|
|
1616
1681
|
return execute();
|
|
1617
|
-
const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords });
|
|
1682
|
+
const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords, ownerSessionId: ctx.sessionManager.getSessionId() });
|
|
1618
1683
|
const launchId = compiledElectron && "launchId" in compiledElectron ? compiledElectron.launchId : undefined;
|
|
1619
1684
|
const selected = launchId ? [records.get(launchId)].filter((record) => record !== undefined) : getActiveElectronRecords(records);
|
|
1620
1685
|
const sessions = selected.flatMap(record => record.sessionName ? [{ sessionName: record.sessionName, namespace: record.namespace }] : []);
|
|
@@ -1655,9 +1720,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1655
1720
|
// before capturing state. Registration, rendering, and restore stay synchronous/eager.
|
|
1656
1721
|
const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
|
|
1657
1722
|
flushRecordingReservations();
|
|
1658
|
-
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1659
1723
|
const generationAtStart = branchStateGeneration;
|
|
1660
|
-
const
|
|
1724
|
+
const workingPageState = sessionPageState.fork();
|
|
1725
|
+
const priorPages = workingPageState.views();
|
|
1726
|
+
const sessionPageStateUpdate = workingPageState.beginUpdate();
|
|
1661
1727
|
const browserRunState = {
|
|
1662
1728
|
activeRecordingReservations,
|
|
1663
1729
|
artifactManifest,
|
|
@@ -1678,7 +1744,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1678
1744
|
managedSessionRestoreState,
|
|
1679
1745
|
networkRoutesBySession,
|
|
1680
1746
|
ownedManagedSessions,
|
|
1681
|
-
sessionPageState,
|
|
1747
|
+
sessionPageState: workingPageState,
|
|
1682
1748
|
traceOwners,
|
|
1683
1749
|
};
|
|
1684
1750
|
const selectedPlan = buildExecutionPlan(toolArgs, {
|
|
@@ -1691,6 +1757,30 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1691
1757
|
stdin: resolvedInput.toolStdin,
|
|
1692
1758
|
browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
|
|
1693
1759
|
});
|
|
1760
|
+
const browserAffecting = !selectedPlan.plainTextInspection && !(selectedPlan.commandInfo.command === "session" && selectedPlan.commandInfo.subcommand === "info")
|
|
1761
|
+
&& (needsManagedSession(parseArgvDescriptor(toolArgs), resolvedInput.toolStdin) || (selectedPlan.startupScopedFlags?.length ?? 0) > 0);
|
|
1762
|
+
const operationId = randomUUID();
|
|
1763
|
+
const selectedKey = getAgentBrowserSessionIdentityKey(selectedPlan.sessionName ?? "default", selectedPlan.namespace);
|
|
1764
|
+
const begin = { event: {
|
|
1765
|
+
version: 1, phase: "begin", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: true,
|
|
1766
|
+
state: { args: redactInvocationArgs(toolArgs), sessionName: selectedPlan.sessionName, namespace: selectedPlan.namespace,
|
|
1767
|
+
ownerSessionId: ctx.sessionManager.getSessionId(), usedImplicitSession: selectedPlan.usedImplicitSession,
|
|
1768
|
+
wrapperManaged: selectedPlan.managedSessionName !== undefined || ownedManagedSessions.has(selectedKey), managedSessionCwd: browserCwd,
|
|
1769
|
+
managedSessionSocketDir: resolveAgentBrowserSocketDir({ ownedManagedSession: selectedPlan.managedSessionName !== undefined || ownedManagedSessions.has(selectedKey) }) },
|
|
1770
|
+
pages: [{ key: selectedKey, refs: { kind: "unknown" }, unknown: true }],
|
|
1771
|
+
} };
|
|
1772
|
+
if (browserAffecting) {
|
|
1773
|
+
try {
|
|
1774
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, begin), begin, branch)
|
|
1775
|
+
|| !branch.isCurrent())
|
|
1776
|
+
throw new Error("The selected Pi branch changed before browser-command admission.");
|
|
1777
|
+
}
|
|
1778
|
+
catch (error) {
|
|
1779
|
+
const failure = browserExecutionFailure(new Error("Could not persist browser-state begin; the browser command was not run.", { cause: error }), executionSignal);
|
|
1780
|
+
return { ...failure, details: { ...(isRecord(failure.details) ? failure.details : {}), browserStatePersistence: "begin-unconfirmed" } };
|
|
1781
|
+
}
|
|
1782
|
+
sessionPageState.applyBrowserRecord(begin);
|
|
1783
|
+
}
|
|
1694
1784
|
const initialArtifactManifest = browserRunState.artifactManifest;
|
|
1695
1785
|
const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
|
|
1696
1786
|
const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
|
|
@@ -1702,28 +1792,37 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1702
1792
|
: getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
|
|
1703
1793
|
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1704
1794
|
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
|
-
|
|
1795
|
+
let result;
|
|
1796
|
+
try {
|
|
1797
|
+
result = await runAgentBrowserTool({
|
|
1798
|
+
daemonInactive,
|
|
1799
|
+
ctx,
|
|
1800
|
+
cwd: browserCwd,
|
|
1801
|
+
operationCwd,
|
|
1802
|
+
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
1803
|
+
electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
|
|
1804
|
+
implicitSessionCloseTimeoutMs,
|
|
1805
|
+
implicitSessionIdleTimeoutMs,
|
|
1806
|
+
input: resolvedInput,
|
|
1807
|
+
modelVisible,
|
|
1808
|
+
onUpdate,
|
|
1809
|
+
params,
|
|
1810
|
+
establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
|
|
1811
|
+
preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
|
|
1812
|
+
promptPolicy,
|
|
1813
|
+
sessionPageStateUpdate,
|
|
1814
|
+
signal: executionSignal,
|
|
1815
|
+
state: browserRunState,
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
catch (error) {
|
|
1819
|
+
const failure = browserExecutionFailure(error, executionSignal);
|
|
1820
|
+
result = { ...failure, details: { ...browserRunState.observedBrowserEffects, ...(isRecord(failure.details) ? failure.details : {}) } };
|
|
1821
|
+
}
|
|
1822
|
+
const branchRestoreStillCurrent = branch.isCurrent();
|
|
1726
1823
|
const resultDetails = isRecord(result.details) ? result.details : undefined;
|
|
1824
|
+
if (resultDetails && begin.event.state.wrapperManaged === true)
|
|
1825
|
+
resultDetails.managedSessionSocketDir = begin.event.state.managedSessionSocketDir;
|
|
1727
1826
|
if (typeof resultDetails?.sessionName === "string" && browserRunState.managedSessionActive
|
|
1728
1827
|
&& getAgentBrowserSessionIdentityKey(resultDetails.sessionName, typeof resultDetails.namespace === "string" ? resultDetails.namespace : undefined) === getAgentBrowserSessionIdentityKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace)) {
|
|
1729
1828
|
resultDetails.managedSessionCwd = browserRunState.managedSessionCwd;
|
|
@@ -1780,18 +1879,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1780
1879
|
if (reservation)
|
|
1781
1880
|
result = appendActiveRecordingCleanupAction(result, reservation);
|
|
1782
1881
|
}
|
|
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
|
-
}
|
|
1882
|
+
if (artifactManifest && isRecord(result.details))
|
|
1883
|
+
result.details.artifactRetentionSummary = formatSessionArtifactRetentionSummary(artifactManifest);
|
|
1793
1884
|
}
|
|
1794
|
-
const branchStateStillCurrent = generationAtStart === branchStateGeneration;
|
|
1885
|
+
const branchStateStillCurrent = generationAtStart === branchStateGeneration && branch.isCurrent();
|
|
1795
1886
|
if (serializeBrowserCommand || branchStateStillCurrent) {
|
|
1796
1887
|
freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
|
|
1797
1888
|
managedSessionActive = browserRunState.managedSessionActive;
|
|
@@ -1806,6 +1897,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1806
1897
|
}
|
|
1807
1898
|
syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
|
|
1808
1899
|
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
1900
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
1809
1901
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
1810
1902
|
touchedLaunchIds: !result.isError
|
|
1811
1903
|
? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
|
|
@@ -1814,6 +1906,39 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1814
1906
|
if (serializeBrowserCommand)
|
|
1815
1907
|
branchStateGeneration += 1;
|
|
1816
1908
|
}
|
|
1909
|
+
if (!selectedPlan.plainTextInspection && branchRestoreStillCurrent) {
|
|
1910
|
+
const changes = browserPageChanges(workingPageState, priorPages, [...browserRunState.closedManagedSessionNames, ...(browserAffecting ? [selectedKey] : [])]);
|
|
1911
|
+
const artifacts = artifactChanges(initialArtifactManifest, artifactManifest);
|
|
1912
|
+
if (browserAffecting || changes.pages.length > 0 || artifacts && hasPublishedBrowserJournal(ctx.sessionManager) || resultDetails?.readConfirmation !== undefined || browserRunState.observedBrowserEffects?.readConfirmation !== undefined) {
|
|
1913
|
+
const finish = { event: {
|
|
1914
|
+
version: 1, phase: browserAffecting ? "finish" : "state", operationId, toolCallId, commandIndex: commandIndex ?? 0, isError: result.isError === true,
|
|
1915
|
+
state: { ...browserStateEffects(isRecord(result.details) ? result.details : {}), ...browserRunState.observedBrowserEffects,
|
|
1916
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
1917
|
+
...(begin.event.state.wrapperManaged === true ? { managedSessionDaemon: managedSessionRestoreState.getDaemonReceipt(resultSessionName, resultNamespace) } : {}) }, pages: changes.pages,
|
|
1918
|
+
artifacts,
|
|
1919
|
+
}, ...(changes.snapshot ? { snapshot: changes.snapshot } : {}) };
|
|
1920
|
+
try {
|
|
1921
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, finish), finish, branch)
|
|
1922
|
+
|| !branch.isCurrent())
|
|
1923
|
+
throw new Error("The selected Pi branch changed before browser-state confirmation.");
|
|
1924
|
+
}
|
|
1925
|
+
catch (error) {
|
|
1926
|
+
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: [{
|
|
1927
|
+
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"]]) },
|
|
1928
|
+
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.",
|
|
1929
|
+
}] } };
|
|
1930
|
+
}
|
|
1931
|
+
sessionPageState.applyBrowserRecord(finish);
|
|
1932
|
+
artifactManifest = applyArtifactChanges(artifactManifest, finish.event.artifacts);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
if (isRecord(result.details)) {
|
|
1936
|
+
result.details.browserEventVersion = 1;
|
|
1937
|
+
if (!(compiledElectron?.action === "launch" && compiledElectron.handoff === "snapshot")
|
|
1938
|
+
&& !getArtifactCommandSteps(toolArgs, resolvedInput.toolStdin).steps.some(step => step[0] === "snapshot"))
|
|
1939
|
+
delete result.details.refSnapshot;
|
|
1940
|
+
result.details.artifactManifest = invocationArtifactManifest(branch.isCurrent() ? artifactManifest : browserRunState.artifactManifest, initialArtifactManifest, result.details);
|
|
1941
|
+
}
|
|
1817
1942
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1818
1943
|
};
|
|
1819
1944
|
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
@@ -1825,35 +1950,39 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1825
1950
|
sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
|
|
1826
1951
|
stdin: resolvedInput.toolStdin,
|
|
1827
1952
|
});
|
|
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)
|
|
1953
|
+
const owner = ownedManagedSessions.get(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace));
|
|
1954
|
+
return withAgentBrowserProcessEnvironment(owner?.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: owner.socketDir } : {}, async () => {
|
|
1955
|
+
const run = (executionSignal = signal) => {
|
|
1956
|
+
const execute = () => withLaunchDefaults
|
|
1957
|
+
? withLaunchDefaults(inactive => runBrowserCommand(inactive, executionSignal), executionSignal)
|
|
1958
|
+
: runBrowserCommand(undefined, executionSignal);
|
|
1959
|
+
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1837
1960
|
return execute();
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1961
|
+
return artifactExecutionQueue.run(async () => {
|
|
1962
|
+
const error = getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: toolArgs, cwd: operationCwd, outputPath, stdin: resolvedInput.toolStdin });
|
|
1963
|
+
if (!error)
|
|
1964
|
+
return execute();
|
|
1965
|
+
flushRecordingReservations();
|
|
1966
|
+
return warnRecordingPersistence(buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: error }));
|
|
1967
|
+
});
|
|
1968
|
+
};
|
|
1969
|
+
if (plan.validationError || (!plan.sessionName && plan.commandInfo.command !== "read" && !closesAllSessions))
|
|
1970
|
+
return run();
|
|
1971
|
+
try {
|
|
1972
|
+
const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace));
|
|
1973
|
+
const identities = [await resolveBrowserExecutionIdentity({ namespace: plan.namespace, ownedManagedSession: owned, sessionName: closesAllSessions ? undefined : plan.sessionName ?? "default" })];
|
|
1974
|
+
if (managedSessionActive && (params.sessionMode === "fresh" || compiledElectron?.action === "launch") && !explicitSessionName) {
|
|
1975
|
+
const previousOwner = ownedManagedSessions.get(getAgentBrowserSessionIdentityKey(managedSessionName, managedSessionNamespace));
|
|
1976
|
+
identities.push(await resolveBrowserExecutionIdentity({ namespace: managedSessionNamespace, ownedManagedSession: true, sessionName: managedSessionName, env: previousOwner?.socketDir ? { AGENT_BROWSER_SOCKET_DIR: previousOwner.socketDir } : undefined }));
|
|
1977
|
+
}
|
|
1978
|
+
const activePageUrl = sessionPageState.get(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace)).tabTarget?.url;
|
|
1979
|
+
const deadline = Date.now() + (params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(toolArgs), resolvedInput.toolStdin, activePageUrl) ?? getAgentBrowserProcessTimeoutMs());
|
|
1980
|
+
return await withBrowserExecutionLocks({ identities, signal, deadline, waitOnly: true }, run);
|
|
1849
1981
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
}
|
|
1854
|
-
catch (error) {
|
|
1855
|
-
return browserExecutionFailure(error, signal);
|
|
1856
|
-
}
|
|
1982
|
+
catch (error) {
|
|
1983
|
+
return browserExecutionFailure(error, signal);
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1857
1986
|
};
|
|
1858
1987
|
const runWithinSessionQueue = () => {
|
|
1859
1988
|
if (closesAllSessions)
|
|
@@ -1887,9 +2016,15 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1887
2016
|
return managedSessionExecutionQueue.run(executeAdmitted, signal);
|
|
1888
2017
|
},
|
|
1889
2018
|
};
|
|
1890
|
-
const executeCode = async (toolCallId, params, signal, _onUpdate, ctx) => {
|
|
1891
|
-
if (
|
|
1892
|
-
return browserExecutionFailure(new Error(
|
|
2019
|
+
const executeCode = async (toolCallId, params, signal, _onUpdate, ctx, branch) => {
|
|
2020
|
+
if (browserReplayError)
|
|
2021
|
+
return browserExecutionFailure(new Error(browserReplayError), signal, "validation-error");
|
|
2022
|
+
try {
|
|
2023
|
+
requirePublishedBrowserJournal(ctx.sessionManager);
|
|
2024
|
+
}
|
|
2025
|
+
catch (error) {
|
|
2026
|
+
return browserExecutionFailure(error, signal, "validation-error");
|
|
2027
|
+
}
|
|
1893
2028
|
let operationCwd;
|
|
1894
2029
|
try {
|
|
1895
2030
|
operationCwd = resolveExecutionCwd(pi, ctx);
|
|
@@ -1903,12 +2038,8 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1903
2038
|
if (signal?.aborted)
|
|
1904
2039
|
abort();
|
|
1905
2040
|
activeScriptControllers.add(controller);
|
|
1906
|
-
let finish;
|
|
1907
|
-
const execution = new Promise(resolve => { finish = resolve; });
|
|
1908
|
-
activeScriptExecutions.add(execution);
|
|
1909
2041
|
const deadline = Date.now() + (params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS);
|
|
1910
2042
|
const deadlineTimer = setTimeout(() => controller.abort(new DOMException("Browser code deadline exceeded.", "TimeoutError")), Math.max(0, deadline - Date.now()));
|
|
1911
|
-
const generation = branchRestoreGeneration;
|
|
1912
2043
|
const output = createBrowserCodeOutput();
|
|
1913
2044
|
const nativeEnvironment = getAgentBrowserProcessEnvironment();
|
|
1914
2045
|
try {
|
|
@@ -1937,9 +2068,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1937
2068
|
const namespace = plan.namespace || undefined;
|
|
1938
2069
|
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
1939
2070
|
const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(key);
|
|
1940
|
-
const run = async () => {
|
|
2071
|
+
const run = async () => withAgentBrowserProcessEnvironment(ownedManagedSessions.get(key)?.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: ownedManagedSessions.get(key).socketDir } : {}, async () => {
|
|
1941
2072
|
const identity = await resolveBrowserExecutionIdentity({ sessionName, namespace, ownedManagedSession: owned });
|
|
1942
2073
|
return withBrowserExecutionLock({ identity, deadline, signal: controller.signal }, async (executionSignal) => {
|
|
2074
|
+
let commandIndex = 0;
|
|
1943
2075
|
let journalFailed = false;
|
|
1944
2076
|
const run = await runAgentBrowserScript({
|
|
1945
2077
|
code: params.code, signal: executionSignal, timeoutMs: Math.max(1, deadline - Date.now()),
|
|
@@ -1950,33 +2082,24 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1950
2082
|
const { args } = bindBrowserCodeCall(inner, { sessionName, namespace });
|
|
1951
2083
|
if (plan.usedImplicitSession && !managedSessionActive && managedSessionName === sessionName && extractExplicitSessionName(inner.args) === undefined)
|
|
1952
2084
|
args.splice(args.indexOf("--session"), 2);
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
}
|
|
1957
|
-
catch {
|
|
2085
|
+
// The ordinary executor journals direct, code and hidden-helper effects alike.
|
|
2086
|
+
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));
|
|
2087
|
+
if (isRecord(result.details) && result.details.browserStatePersistence !== undefined) {
|
|
1958
2088
|
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
|
-
}
|
|
2089
|
+
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
2090
|
}
|
|
1974
2091
|
return output.observe(result);
|
|
1975
2092
|
},
|
|
1976
2093
|
});
|
|
1977
2094
|
const result = await output.finish(run, sessionName, namespace);
|
|
1978
|
-
if (
|
|
1979
|
-
result.
|
|
2095
|
+
if (journalFailed && isRecord(result.details)) {
|
|
2096
|
+
result.isError = true;
|
|
2097
|
+
result.details.resultCategory = "failure";
|
|
2098
|
+
result.details.failureCategory = "upstream-error";
|
|
2099
|
+
result.details.error = "Browser-state persistence failed. Dependent calls were stopped; inspect before continuing because already-dispatched effects may have happened.";
|
|
2100
|
+
}
|
|
2101
|
+
if (isRecord(result.details))
|
|
2102
|
+
result.details.browserEventVersion = 1;
|
|
1980
2103
|
if (!params.outputPath)
|
|
1981
2104
|
return result;
|
|
1982
2105
|
return artifactExecutionQueue.run(async () => {
|
|
@@ -1988,7 +2111,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1988
2111
|
return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath: params.outputPath, preserveTextContent: true, result });
|
|
1989
2112
|
});
|
|
1990
2113
|
});
|
|
1991
|
-
};
|
|
2114
|
+
});
|
|
1992
2115
|
return shouldSerializeBrowserCommand({ namespace, explicitSessionName: sessionName, managedSessionName, ownedElectronLaunchRecords, ownedManagedSessions })
|
|
1993
2116
|
? managedSessionExecutionQueue.run(run, controller.signal)
|
|
1994
2117
|
: callerOwnedSessionExecutionQueues.run(key, namespace, run, controller.signal);
|
|
@@ -2004,11 +2127,9 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2004
2127
|
clearTimeout(deadlineTimer);
|
|
2005
2128
|
activeScriptControllers.delete(controller);
|
|
2006
2129
|
signal?.removeEventListener("abort", abort);
|
|
2007
|
-
activeScriptExecutions.delete(execution);
|
|
2008
|
-
finish();
|
|
2009
2130
|
}
|
|
2010
2131
|
};
|
|
2011
|
-
const finalizeObservation = async (result, params, ctx) => {
|
|
2132
|
+
const finalizeObservation = async (result, params, ctx, branch) => {
|
|
2012
2133
|
if ("args" in params && params.args && isPlainTextInspectionArgs(params.args))
|
|
2013
2134
|
return result;
|
|
2014
2135
|
const { renderAgentBrowserObservation } = await import("./lib/results/presentation/large-output.js");
|
|
@@ -2016,27 +2137,59 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
2016
2137
|
if (result.isError && details.error === undefined)
|
|
2017
2138
|
details.error = details.validationError ?? details.summary ?? result.content.filter(item => item.type === "text").map(item => item.text).join("\n");
|
|
2018
2139
|
const priorManifest = isSessionArtifactManifest(details.artifactManifest) ? details.artifactManifest : undefined;
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2140
|
+
const renderOptions = { content: result.content, details, json: "code" in params || params.args?.includes("--json") === true, succeeded: result.isError !== true,
|
|
2141
|
+
persistentArtifactStore: branch.isCurrent() ? getPersistentSessionArtifactStore(ctx) : undefined,
|
|
2142
|
+
withArtifactWrite: (write) => artifactExecutionQueue.run(() => {
|
|
2143
|
+
if (!branch.isCurrent())
|
|
2144
|
+
renderOptions.persistentArtifactStore = undefined;
|
|
2145
|
+
return write();
|
|
2146
|
+
}) };
|
|
2147
|
+
const rendered = await renderAgentBrowserObservation(renderOptions);
|
|
2148
|
+
if (!priorManifest && !rendered.artifactManifest)
|
|
2149
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1 } };
|
|
2150
|
+
return artifactExecutionQueue.run(async () => {
|
|
2151
|
+
if (!branch.isCurrent())
|
|
2152
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2153
|
+
const before = artifactManifest;
|
|
2154
|
+
const merged = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
|
|
2155
|
+
const changes = artifactChanges(before, merged);
|
|
2156
|
+
if (changes && hasPublishedBrowserJournal(ctx.sessionManager)) {
|
|
2157
|
+
const record = { event: { version: 1, phase: "state", operationId: randomUUID(), toolCallId: "observation", commandIndex: 0, isError: result.isError === true, state: {}, artifacts: changes } };
|
|
2158
|
+
if (!await appendBrowserRecord(ctx.sessionManager, () => appendBrowserTransition(pi, record), record, branch)
|
|
2159
|
+
|| !branch.isCurrent())
|
|
2160
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1, artifactManifest: rendered.artifactManifest } };
|
|
2161
|
+
}
|
|
2162
|
+
artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
|
|
2163
|
+
return { ...result, content: rendered.content, structuredContent: rendered.structuredContent, details: { ...details, browserEventVersion: 1, artifactManifest: invocationArtifactManifest(artifactManifest, before, details, priorManifest) } };
|
|
2164
|
+
});
|
|
2022
2165
|
};
|
|
2023
2166
|
registerAgentBrowserToolSurface(pi, {
|
|
2024
2167
|
async execute(id, params, signal, onUpdate, ctx) {
|
|
2168
|
+
const branch = captureCurrentBrowserBranch(ctx);
|
|
2025
2169
|
let result;
|
|
2026
2170
|
try {
|
|
2027
|
-
result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx);
|
|
2171
|
+
result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx, id, undefined, true, undefined, branch);
|
|
2028
2172
|
}
|
|
2029
2173
|
catch (error) {
|
|
2030
2174
|
result = browserExecutionFailure(error, signal);
|
|
2031
2175
|
}
|
|
2032
|
-
return finalizeObservation(result, params, ctx);
|
|
2176
|
+
return finalizeObservation(result, params, ctx, branch);
|
|
2033
2177
|
},
|
|
2034
2178
|
async executeCode(id, params, signal, onUpdate, ctx) {
|
|
2035
|
-
const
|
|
2036
|
-
|
|
2179
|
+
const branch = captureCurrentBrowserBranch(ctx);
|
|
2180
|
+
let finish;
|
|
2181
|
+
const execution = new Promise(resolve => { finish = resolve; });
|
|
2182
|
+
activeScriptExecutions.add(execution);
|
|
2183
|
+
try {
|
|
2184
|
+
const result = await executeCode(id, params, signal, onUpdate, ctx, branch);
|
|
2185
|
+
return await finalizeObservation(result, params, ctx, branch);
|
|
2186
|
+
}
|
|
2187
|
+
finally {
|
|
2188
|
+
activeScriptExecutions.delete(execution);
|
|
2189
|
+
finish();
|
|
2190
|
+
}
|
|
2037
2191
|
},
|
|
2038
2192
|
executionMode: beforeExecute ? "sequential" : undefined,
|
|
2039
|
-
promptGuidelines: toolPromptGuidelines,
|
|
2040
2193
|
renderCall: agentBrowserTool.renderCall, renderResult: agentBrowserTool.renderResult,
|
|
2041
2194
|
});
|
|
2042
2195
|
registerWebSearchToolIfAvailable(agentBrowserConfig);
|