pi-agent-browser-native 0.5.0 → 0.6.6
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 +144 -0
- package/README.md +75 -42
- package/dist/extensions/agent-browser/index.js +13 -83
- package/dist/extensions/agent-browser/lib/argv-grammar.js +8 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +1 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +4 -7
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +19 -11
- package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
- package/dist/extensions/agent-browser/lib/config.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/job.js +0 -9
- package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +18 -4
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -138
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +1 -81
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +4 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +31 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +60 -8
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +1 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +31 -40
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +76 -70
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +6 -8
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +5 -10
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
- package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
- package/dist/extensions/agent-browser/lib/playbook.js +13 -12
- package/dist/extensions/agent-browser/lib/process-identity.js +11 -10
- package/dist/extensions/agent-browser/lib/process.js +18 -82
- package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -78
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +27 -14
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +20 -2
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +11 -16
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +12 -9
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +2 -2
- package/dist/extensions/agent-browser/lib/results/presentation.js +31 -4
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +1 -1
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +3 -3
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
- package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
- package/dist/extensions/agent-browser/lib/runtime.js +73 -72
- package/dist/extensions/agent-browser/lib/session-page-state.js +12 -3
- package/dist/extensions/agent-browser/lib/temp.js +1 -2
- package/dist/extensions/agent-browser/lib/upstream-version.js +5 -5
- package/dist/extensions/agent-browser/lib/web-search.js +108 -24
- package/dist/scripts/agent-browser-target.mjs +19 -1
- package/docs/ARCHITECTURE.md +24 -20
- package/docs/COMMAND_REFERENCE.md +181 -49
- package/docs/ELECTRON.md +2 -2
- package/docs/RELEASE.md +10 -8
- package/docs/REQUIREMENTS.md +8 -7
- package/docs/SUPPORT_MATRIX.md +31 -26
- package/docs/TOOL_CONTRACT.md +89 -56
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +65 -5
- package/scripts/agent-browser-target.mjs +19 -1
- package/scripts/config.mjs +1 -0
- package/scripts/doctor.mjs +15 -9
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -601
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
2
|
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "../../argv-grammar.js";
|
|
3
3
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "../../batch-lifecycle.js";
|
|
4
|
-
import { isCloseAllCommand, isCloseCommand,
|
|
4
|
+
import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand, isUnverifiedPageTransitionCommand } from "../../command-taxonomy.js";
|
|
5
5
|
import { OPEN_RESULT_TAB_CORRECTION_FLAGS } from "../../launch-scoped-flags.js";
|
|
6
6
|
import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../../electron/cleanup.js";
|
|
7
|
-
import {
|
|
8
|
-
import { getManagedSessionResultingPageState, getObservedBrowserPageValidationError, managedSessionCommandRequiresLivePageVerification } from "../../managed-session-state-policy.js";
|
|
7
|
+
import { getResultingPageTargetState, commandRequiresLivePageVerification } from "../../page-target-validation.js";
|
|
9
8
|
import { analyzeNetworkSourceLookupResults, analyzeSourceLookupResults, redactNetworkSourceLookupAnalysis } from "../../input-modes/lookups.js";
|
|
10
9
|
import { analyzeQaPresetResults, analyzeQaPresetTimeout, buildQaCompactFailureText, buildQaCompactPassText, extractQaPageContext } from "../../input-modes/job.js";
|
|
11
10
|
import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../../results/network-routes.js";
|
|
@@ -14,7 +13,7 @@ import { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../resul
|
|
|
14
13
|
import { omitUpstreamLifecycle } from "../../results/presentation/common.js";
|
|
15
14
|
import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
|
|
16
15
|
import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
|
|
17
|
-
import { buildPageTransitionRefSnapshotInvalidation, commandExplicitlyTargetsAboutBlank, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
16
|
+
import { buildPageTransitionRefSnapshotInvalidation, commandExplicitlyTargetsAboutBlank, getCommandRefSnapshotInvalidation, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
18
17
|
import { isRecord } from "../../parsing.js";
|
|
19
18
|
import { pruneOwnedManagedSessionRestoreSnapshots } from "../../managed-session-restore.js";
|
|
20
19
|
import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
|
|
@@ -64,6 +63,35 @@ function isStreamEnableAlreadyEnabledNoop(options) {
|
|
|
64
63
|
const message = (getEnvelopeErrorString(options.envelope) ?? "").trim().replace(/[.!]+$/, "").toLowerCase();
|
|
65
64
|
return message === "streaming is already enabled for this session" || message === "streaming is already enabled" || message === "stream already enabled";
|
|
66
65
|
}
|
|
66
|
+
function isPendingWebMcpMutation(command, subcommand, data) {
|
|
67
|
+
return command === "webmcp"
|
|
68
|
+
&& ["invoke", "result"].includes(subcommand ?? "")
|
|
69
|
+
&& isRecord(data)
|
|
70
|
+
&& data.status === "pending";
|
|
71
|
+
}
|
|
72
|
+
function isWebMcpSettlementCommand(command, subcommand) {
|
|
73
|
+
return command === "webmcp" && ["result", "cancel"].includes(subcommand ?? "");
|
|
74
|
+
}
|
|
75
|
+
function batchHasPendingWebMcpMutation(data) {
|
|
76
|
+
if (!Array.isArray(data))
|
|
77
|
+
return false;
|
|
78
|
+
return data.some((row) => {
|
|
79
|
+
if (!isRecord(row) || row.success === false || !Array.isArray(row.command) || !row.command.every((token) => typeof token === "string"))
|
|
80
|
+
return false;
|
|
81
|
+
const [command, subcommand] = extractUpstreamCommandTokens(row.command);
|
|
82
|
+
return isPendingWebMcpMutation(command, subcommand, row.result);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function batchHasFailedWebMcpSettlement(data) {
|
|
86
|
+
if (!Array.isArray(data))
|
|
87
|
+
return false;
|
|
88
|
+
return data.some((row) => {
|
|
89
|
+
if (!isRecord(row) || row.success !== false || !Array.isArray(row.command) || !row.command.every((token) => typeof token === "string"))
|
|
90
|
+
return false;
|
|
91
|
+
const [command, subcommand] = extractUpstreamCommandTokens(row.command);
|
|
92
|
+
return isWebMcpSettlementCommand(command, subcommand);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
67
95
|
function batchStartedManagedBrowser(data) {
|
|
68
96
|
if (!Array.isArray(data))
|
|
69
97
|
return false;
|
|
@@ -120,7 +148,6 @@ export async function processBrowserOutput(input) {
|
|
|
120
148
|
const { ctx, cwd, electronPostCommandStatusSettleMs, implicitSessionCloseTimeoutMs, sessionPageStateUpdate, signal, state } = input;
|
|
121
149
|
const { prepared, processResult } = input;
|
|
122
150
|
const { electronChildProcesses, electronLaunchRecords, sessionPageState, traceOwners } = state;
|
|
123
|
-
let allowedDomainsBySession = state.allowedDomainsBySession;
|
|
124
151
|
let artifactManifest = state.artifactManifest;
|
|
125
152
|
let freshSessionOrdinal = state.freshSessionOrdinal;
|
|
126
153
|
let managedSessionActive = state.managedSessionActive;
|
|
@@ -183,7 +210,6 @@ export async function processBrowserOutput(input) {
|
|
|
183
210
|
const sessionStateKey = getSessionContextKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
|
|
184
211
|
const closeAllApplied = nestedBatchClosesAll || (directCloseAllRequested && succeeded);
|
|
185
212
|
if (closeAllApplied) {
|
|
186
|
-
allowedDomainsBySession = withoutNamespaceEntries(allowedDomainsBySession, prepared.executionPlan.namespace);
|
|
187
213
|
networkRoutesBySession = withoutNamespaceEntries(networkRoutesBySession, prepared.executionPlan.namespace);
|
|
188
214
|
deleteNamespaceEntries(state.attachedSessionKeys, prepared.executionPlan.namespace);
|
|
189
215
|
deleteNamespaceEntries(traceOwners, prepared.executionPlan.namespace);
|
|
@@ -197,8 +223,6 @@ export async function processBrowserOutput(input) {
|
|
|
197
223
|
}
|
|
198
224
|
}
|
|
199
225
|
else if (nestedBatchClose && sessionStateKey) {
|
|
200
|
-
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
201
|
-
allowedDomainsBySession.delete(sessionStateKey);
|
|
202
226
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
203
227
|
networkRoutesBySession.delete(sessionStateKey);
|
|
204
228
|
sessionPageState.clearSession(sessionStateKey);
|
|
@@ -227,33 +251,25 @@ export async function processBrowserOutput(input) {
|
|
|
227
251
|
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: clickDispatchDiagnostic.summary, success: false };
|
|
228
252
|
}
|
|
229
253
|
}
|
|
230
|
-
const
|
|
254
|
+
const tabTransition = prepared.executionPlan.commandInfo.command === "tab" && prepared.executionPlan.commandInfo.subcommand !== undefined && !["list", "new"].includes(prepared.executionPlan.commandInfo.subcommand);
|
|
255
|
+
const resultingPageState = getResultingPageTargetState({
|
|
231
256
|
args: prepared.executionPlan.effectiveArgs,
|
|
232
257
|
currentPageUrl: prepared.priorSessionTabTarget?.url,
|
|
233
258
|
pageUrlUnknown: prepared.priorSessionTabTargetUnknown === true,
|
|
234
259
|
stdin: prepared.runtimeToolStdin,
|
|
235
260
|
trustedFirstBatchTabSelection: prepared.pinnedBatchUnwrapMode !== undefined,
|
|
236
261
|
});
|
|
237
|
-
const presentationDataRecord = isRecord(presentationEnvelope?.data) ? presentationEnvelope.data : undefined;
|
|
238
|
-
const dataClicked = typeof presentationDataRecord?.clicked === "string" ? presentationDataRecord.clicked : undefined;
|
|
239
|
-
const cssClickWithoutHref = prepared.executionPlan.commandInfo.command === "click" && dataClicked !== undefined && !dataClicked.startsWith("@") && !dataClicked.startsWith("ref=") && typeof presentationDataRecord?.href !== "string";
|
|
240
|
-
const parsedAllowedDomainsPolicy = parseAllowedDomainsPolicyFromArgs(prepared.runtimeToolArgs);
|
|
241
|
-
const sessionAllowedDomainsPolicy = sessionStateKey
|
|
242
|
-
? parsedAllowedDomainsPolicy ?? allowedDomainsBySession.get(sessionStateKey)
|
|
243
|
-
: parsedAllowedDomainsPolicy;
|
|
244
|
-
const shouldCaptureAllowedDomainNavigationSummary = sessionAllowedDomainsPolicy !== undefined && !cssClickWithoutHref && (prepared.executionPlan.commandInfo.command === "batch" || isNavigationObservableCommandName(prepared.executionPlan.commandInfo.command));
|
|
245
262
|
if (succeeded &&
|
|
246
263
|
!navigationSummary &&
|
|
247
|
-
(shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data) ||
|
|
264
|
+
(shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data, prepared.executionPlan.commandInfo.subcommand) ||
|
|
248
265
|
shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data) ||
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName, signal });
|
|
266
|
+
commandRequiresLivePageVerification(prepared.executionPlan.effectiveArgs, prepared.runtimeToolStdin) ||
|
|
267
|
+
tabTransition)) {
|
|
268
|
+
navigationSummary = await collectNavigationSummary({ cwd, namespace: prepared.executionPlan.namespace, priorTarget: prepared.priorSessionTabTarget, reusePriorTitle: !tabTransition, sessionName: prepared.executionPlan.sessionName, signal });
|
|
253
269
|
}
|
|
254
270
|
// A failed eval/back/forward/reload/state-load/tab would otherwise drop the page to unverified and force the
|
|
255
|
-
// agent through a manual get url round trip. Probe the live URL ourselves instead: an observed
|
|
256
|
-
// stays verified,
|
|
271
|
+
// agent through a manual get url round trip. Probe the live URL ourselves instead: an observed page
|
|
272
|
+
// stays verified, and a failed
|
|
257
273
|
// probe preserves the existing unknown-target behavior.
|
|
258
274
|
if (succeeded === false &&
|
|
259
275
|
!navigationSummary &&
|
|
@@ -281,15 +297,16 @@ export async function processBrowserOutput(input) {
|
|
|
281
297
|
}
|
|
282
298
|
const verifiesCurrentUrl = prepared.executionPlan.commandInfo.command === "get" && prepared.executionPlan.commandInfo.subcommand === "url";
|
|
283
299
|
const trustsReportedPageTarget = !resultingPageState.pageUrlUnknown || verifiesCurrentUrl;
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
300
|
+
const pendingWebMcpMutation = isPendingWebMcpMutation(prepared.executionPlan.commandInfo.command, prepared.executionPlan.commandInfo.subcommand, presentationEnvelope?.data) || (prepared.executionPlan.commandInfo.command === "batch" && batchHasPendingWebMcpMutation(presentationEnvelope?.data));
|
|
301
|
+
const failedWebMcpSettlement = prepared.priorSessionTabTargetUnknown === true && ((!succeeded && isWebMcpSettlementCommand(prepared.executionPlan.commandInfo.command, prepared.executionPlan.commandInfo.subcommand))
|
|
302
|
+
|| (prepared.executionPlan.commandInfo.command === "batch" && batchHasFailedWebMcpSettlement(presentationEnvelope?.data)));
|
|
303
|
+
const unsettledWebMcpMutation = pendingWebMcpMutation || failedWebMcpSettlement;
|
|
304
|
+
const observedSessionTabTarget = unsettledWebMcpMutation
|
|
305
|
+
? undefined
|
|
306
|
+
: normalizeSessionTabTarget(navigationSummary)
|
|
307
|
+
?? (trustsReportedPageTarget ? extractSessionTabTargetFromBatchResults(presentationEnvelope?.data) : undefined)
|
|
308
|
+
?? (succeeded && trustsReportedPageTarget ? extractSessionTabTargetFromCommandData(prepared.commandTokens, presentationEnvelope?.data) : undefined);
|
|
309
|
+
const safeObservedSessionTabTarget = observedSessionTabTarget;
|
|
293
310
|
let currentSessionTabTarget = safeObservedSessionTabTarget;
|
|
294
311
|
if (!currentSessionTabTarget && nestedBatchClose === undefined) {
|
|
295
312
|
currentSessionTabTarget = resultingPageState.pageTargetMayHaveChanged
|
|
@@ -301,7 +318,7 @@ export async function processBrowserOutput(input) {
|
|
|
301
318
|
let electronRefFreshnessDiagnostic;
|
|
302
319
|
let electronSessionMismatch;
|
|
303
320
|
let electronStatusAfterCommand;
|
|
304
|
-
const shouldTreatAboutBlankAsMismatch = succeeded && nestedBatchClose === undefined && prepared.priorSessionTabTarget !== undefined && !isAboutBlankSessionTabTarget(prepared.priorSessionTabTarget) && isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget) && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens);
|
|
321
|
+
const shouldTreatAboutBlankAsMismatch = succeeded && !tabTransition && nestedBatchClose === undefined && prepared.priorSessionTabTarget !== undefined && !isAboutBlankSessionTabTarget(prepared.priorSessionTabTarget) && isAboutBlankSessionTabTarget(observedSessionTabTarget ?? currentSessionTabTarget) && !commandExplicitlyTargetsAboutBlank(prepared.commandTokens);
|
|
305
322
|
let sessionTabCorrection = prepared.sessionTabCorrection;
|
|
306
323
|
if (shouldTreatAboutBlankAsMismatch && prepared.priorSessionTabTarget) {
|
|
307
324
|
const aboutBlankObservedTarget = observedSessionTabTarget ?? currentSessionTabTarget;
|
|
@@ -328,18 +345,6 @@ export async function processBrowserOutput(input) {
|
|
|
328
345
|
sessionTabCorrection = appliedPostCommandCorrection;
|
|
329
346
|
}
|
|
330
347
|
}
|
|
331
|
-
if (succeeded && sessionStateKey && parsedAllowedDomainsPolicy) {
|
|
332
|
-
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
333
|
-
allowedDomainsBySession.set(sessionStateKey, parsedAllowedDomainsPolicy);
|
|
334
|
-
}
|
|
335
|
-
const allowedDomainsViolation = succeeded ? getAllowedDomainsViolation({
|
|
336
|
-
policy: sessionAllowedDomainsPolicy,
|
|
337
|
-
url: currentSessionTabTarget?.url ?? observedSessionTabTarget?.url ?? navigationSummary?.url,
|
|
338
|
-
}) : undefined;
|
|
339
|
-
if (allowedDomainsViolation) {
|
|
340
|
-
succeeded = false;
|
|
341
|
-
presentationEnvelope = { ...(presentationEnvelope ?? {}), error: allowedDomainsViolation.summary, success: false };
|
|
342
|
-
}
|
|
343
348
|
const electronRecordForCommand = findElectronLaunchRecordForSession(prepared.executionPlan.sessionName, electronLaunchRecords, prepared.executionPlan.namespace);
|
|
344
349
|
if (succeeded && electronRecordForCommand && shouldInspectElectronPostCommandHealth(prepared.executionPlan.commandInfo.command)) {
|
|
345
350
|
electronStatusAfterCommand ??= await inspectElectronLaunchStatus(electronRecordForCommand);
|
|
@@ -391,8 +396,6 @@ export async function processBrowserOutput(input) {
|
|
|
391
396
|
const sessionClosed = (isCloseCommand(prepared.executionPlan.commandInfo.command) && succeeded) || nestedBatchClosed;
|
|
392
397
|
if (sessionClosed) {
|
|
393
398
|
state.attachedSessionKeys.delete(sessionStateKey);
|
|
394
|
-
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
395
|
-
allowedDomainsBySession.delete(sessionStateKey);
|
|
396
399
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
397
400
|
networkRoutesBySession.delete(sessionStateKey);
|
|
398
401
|
sessionPageState.clearSession(sessionStateKey);
|
|
@@ -400,17 +403,21 @@ export async function processBrowserOutput(input) {
|
|
|
400
403
|
}
|
|
401
404
|
else {
|
|
402
405
|
// A batch that times out or returns unparseable output yields no result rows, but the daemon
|
|
403
|
-
// may already have executed a recording page
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
406
|
+
// may already have executed a recording swap or page-provided WebMCP tool; fall back to the
|
|
407
|
+
// planned steps then. This can over-invalidate by one snapshot, but never under-invalidates.
|
|
408
|
+
const directTransitionInvalidation = getCommandRefSnapshotInvalidation(prepared.commandTokens);
|
|
409
|
+
const plannedBatchTransitionInvalidation = !Array.isArray(presentationEnvelope?.data)
|
|
410
|
+
? batchCommandSteps.map(getCommandRefSnapshotInvalidation).find((invalidation) => invalidation !== undefined)
|
|
411
|
+
: undefined;
|
|
412
|
+
const pageTransitionInvalidation = unsettledWebMcpMutation
|
|
413
|
+
? buildPageTransitionRefSnapshotInvalidation("A detached WebMCP invocation is still pending or failed to settle and can mutate, rerender, or navigate the page, so prior snapshot refs remain invalid after URL verification. Run webmcp result or cancel, then take a fresh snapshot before using page-scoped refs.")
|
|
414
|
+
: processResult.agentBrowserStarted && (directTransitionInvalidation || plannedBatchTransitionInvalidation)
|
|
415
|
+
? directTransitionInvalidation ?? plannedBatchTransitionInvalidation
|
|
416
|
+
: failedTransitionReverification
|
|
417
|
+
? buildPageTransitionRefSnapshotInvalidation("A failed eval/back/forward/reload/connect/state-load/tab command may still have changed the page, so the prior snapshot refs were invalidated. Run snapshot -i before using page-scoped refs.")
|
|
418
|
+
: batchRefSnapshotState?.invalidation?.reason === "page-transition"
|
|
419
|
+
? batchRefSnapshotState.invalidation
|
|
420
|
+
: undefined;
|
|
414
421
|
if (currentSessionTabTarget) {
|
|
415
422
|
const tabUpdate = sessionPageState.applyTabTarget({ sessionName: sessionStateKey, target: currentSessionTabTarget, update: sessionPageStateUpdate });
|
|
416
423
|
if (!tabUpdate.applied && succeeded)
|
|
@@ -419,7 +426,13 @@ export async function processBrowserOutput(input) {
|
|
|
419
426
|
else if (processResult.agentBrowserStarted && (resultingPageState.pageUrlUnknown || resultingPageState.pageTargetMayHaveChanged)) {
|
|
420
427
|
sessionPageState.markTabTargetUnknown({ sessionName: sessionStateKey, update: sessionPageStateUpdate });
|
|
421
428
|
}
|
|
422
|
-
const refSnapshot =
|
|
429
|
+
const refSnapshot = unsettledWebMcpMutation
|
|
430
|
+
? undefined
|
|
431
|
+
: prepared.executionPlan.commandInfo.command === "batch"
|
|
432
|
+
? batchRefSnapshotState?.snapshot
|
|
433
|
+
: succeeded
|
|
434
|
+
? prepared.executionPlan.commandInfo.command === "snapshot" ? extractRefSnapshotFromData(presentationEnvelope?.data) : prepared.resolvedSemanticActionRefSnapshot ?? overlayBlockerDiagnostic?.snapshot
|
|
435
|
+
: undefined;
|
|
423
436
|
if (refSnapshot) {
|
|
424
437
|
const refUpdate = sessionPageState.applyRefSnapshot({ fallbackTarget: currentSessionTabTarget, sessionName: sessionStateKey, snapshot: refSnapshot, update: sessionPageStateUpdate });
|
|
425
438
|
currentRefSnapshot = refUpdate.refSnapshot;
|
|
@@ -458,7 +471,6 @@ export async function processBrowserOutput(input) {
|
|
|
458
471
|
: closeCommandSucceeded && prepared.executionPlan.sessionName === priorManagedSessionName && closeTargetsPriorManagedNamespace
|
|
459
472
|
? prepared.executionPlan.sessionName
|
|
460
473
|
: prepared.executionPlan.managedSessionName;
|
|
461
|
-
const policyBlockedFreshManagedSession = allowedDomainsViolation !== undefined && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName === prepared.executionPlan.sessionName;
|
|
462
474
|
const postLaunchBatchFailure = !succeeded && processSucceeded && parseSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && batchStartedManagedBrowser(presentationEnvelope?.data);
|
|
463
475
|
const postLaunchTimeoutWithPage = !succeeded && processResult.timedOut && prepared.sessionMode === "fresh" && prepared.executionPlan.commandInfo.command === "batch" && timeoutPartialProgress?.liveUrlRecovered === true;
|
|
464
476
|
const failedFreshSessionMayHaveStarted = !succeeded
|
|
@@ -479,7 +491,7 @@ export async function processBrowserOutput(input) {
|
|
|
479
491
|
}
|
|
480
492
|
// Only a confirmed inactive daemon proves that a started fresh command did not establish browser ownership.
|
|
481
493
|
const postLaunchFreshFailure = failedFreshDaemon !== undefined && failedFreshDaemon.status !== "inactive";
|
|
482
|
-
const managedTransitionSucceeded = succeeded || nestedBatchClosed || nestedBatchRemainsActive ||
|
|
494
|
+
const managedTransitionSucceeded = succeeded || nestedBatchClosed || nestedBatchRemainsActive || postLaunchBatchFailure || postLaunchTimeoutWithPage || postLaunchFreshFailure;
|
|
483
495
|
const managedSessionState = resolveManagedSessionState({ command: commandClosesSession ? "close" : prepared.executionPlan.commandInfo.command, managedSessionName: managedCloseSessionName, managedSessionNamespace: prepared.executionPlan.namespace, priorActive: priorManagedSessionActive, priorNamespace: priorManagedSessionNamespace, priorSessionName: priorManagedSessionName, succeeded: managedTransitionSucceeded });
|
|
484
496
|
if (!managedTransitionSucceeded && prepared.sessionMode === "fresh" && prepared.executionPlan.managedSessionName) {
|
|
485
497
|
state.managedSessionRestoreState.clear(prepared.executionPlan.managedSessionName, prepared.executionPlan.namespace);
|
|
@@ -528,9 +540,7 @@ export async function processBrowserOutput(input) {
|
|
|
528
540
|
sessionPageState.clearRestorePinning(sessionStateKey);
|
|
529
541
|
}
|
|
530
542
|
if (replacedManagedSessionName) {
|
|
531
|
-
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
532
543
|
const replacedSessionStateKey = getSessionContextKey(replacedManagedSessionName, priorManagedSessionNamespace);
|
|
533
|
-
allowedDomainsBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
534
544
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
535
545
|
networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
536
546
|
sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
@@ -585,8 +595,6 @@ export async function processBrowserOutput(input) {
|
|
|
585
595
|
}
|
|
586
596
|
if (!closeError) {
|
|
587
597
|
state.closedManagedSessionNames.add(electronSessionStateKey);
|
|
588
|
-
allowedDomainsBySession = new Map(allowedDomainsBySession);
|
|
589
|
-
allowedDomainsBySession.delete(electronSessionStateKey);
|
|
590
598
|
networkRoutesBySession = new Map(networkRoutesBySession);
|
|
591
599
|
networkRoutesBySession.delete(electronSessionStateKey);
|
|
592
600
|
sessionPageState.clearSession(electronSessionStateKey);
|
|
@@ -626,8 +634,6 @@ export async function processBrowserOutput(input) {
|
|
|
626
634
|
presentationEnvelope = { ...presentationEnvelope, error: redactClipboardPermissionErrorValue(prepared.executionPlan.commandInfo, presentationEnvelope.error, clipboardWritePayloadCandidates) };
|
|
627
635
|
}
|
|
628
636
|
const presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : await buildToolPresentation({ args: prepared.redactedProcessArgs, artifactManifest, artifactMaxUpdatedAtMs: Date.now(), artifactMinUpdatedAtMs: input.artifactRunStartedAtMs, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd, envelope: presentationEnvelope, errorText, namespace: prepared.executionPlan.namespace, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
|
|
629
|
-
if (observedPageValidationError)
|
|
630
|
-
presentation.failureCategory = "validation-error";
|
|
631
637
|
if (electronHandoff?.error && electronHandoff.failureCategory)
|
|
632
638
|
presentation.failureCategory = electronHandoff.failureCategory;
|
|
633
639
|
networkRoutesBySession = applyBatchNetworkRouteState({ data: presentationEnvelope?.data, routesBySession: networkRoutesBySession, sessionName: sessionStateKey, succeeded });
|
|
@@ -749,11 +755,11 @@ export async function processBrowserOutput(input) {
|
|
|
749
755
|
const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !(commandClosesSession && succeeded)
|
|
750
756
|
? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
|
|
751
757
|
: undefined;
|
|
752
|
-
const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, inspectionText, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
758
|
+
const result = buildFinalAgentBrowserToolResult({ aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
753
759
|
const resultWithCloseAll = closeAllApplied
|
|
754
760
|
? { ...result, details: { ...(isRecord(result.details) ? result.details : {}), closeAllApplied: true } }
|
|
755
761
|
: result;
|
|
756
|
-
const statePatch = {
|
|
762
|
+
const statePatch = { artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionHeadedAutosaveDisabled, managedSessionHeadedAutosaveInterval, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession };
|
|
757
763
|
return { result: resultWithCloseAll, statePatch };
|
|
758
764
|
}
|
|
759
765
|
finally {
|
|
@@ -13,8 +13,6 @@ export const NAVIGATION_SUMMARY_EVAL = `({ title: document.title, url: location.
|
|
|
13
13
|
export function applyBrowserRunStatePatch(state, patch) {
|
|
14
14
|
if (!patch)
|
|
15
15
|
return;
|
|
16
|
-
if (patch.allowedDomainsBySession)
|
|
17
|
-
state.allowedDomainsBySession = patch.allowedDomainsBySession;
|
|
18
16
|
if ("artifactManifest" in patch)
|
|
19
17
|
state.artifactManifest = patch.artifactManifest;
|
|
20
18
|
if (patch.freshSessionOrdinal !== undefined)
|
|
@@ -249,12 +247,13 @@ export function extractNavigationSummaryFromData(data) {
|
|
|
249
247
|
const result = isRecord(data) && isRecord(data.result) ? data.result : data;
|
|
250
248
|
const title = extractStringResultField(result, "title");
|
|
251
249
|
const url = extractStringResultField(result, "url");
|
|
252
|
-
|
|
250
|
+
const urlChanged = isRecord(result) && typeof result.urlChanged === "boolean" ? result.urlChanged : undefined;
|
|
251
|
+
return title || url ? { title, url, urlChanged } : undefined;
|
|
253
252
|
}
|
|
254
|
-
export function shouldCaptureNavigationSummary(command, data) {
|
|
253
|
+
export function shouldCaptureNavigationSummary(command, data, subcommand) {
|
|
255
254
|
if (command === "eval")
|
|
256
255
|
return true;
|
|
257
|
-
return (isNavigationObservableCommandName(command) &&
|
|
256
|
+
return (isNavigationObservableCommandName(command, subcommand) &&
|
|
258
257
|
(!isRecord(data) || (typeof data.title !== "string" && typeof data.url !== "string")));
|
|
259
258
|
}
|
|
260
259
|
export function mergeNavigationSummaryIntoData(data, navigationSummary) {
|
|
@@ -442,7 +441,7 @@ export function buildPinnedBatchPlan(options) {
|
|
|
442
441
|
if (options.commandTokens.length === 0) {
|
|
443
442
|
return undefined;
|
|
444
443
|
}
|
|
445
|
-
const includeNavigationSummary = isNavigationObservableCommandName(options.command);
|
|
444
|
+
const includeNavigationSummary = isNavigationObservableCommandName(options.command, options.commandTokens[1]);
|
|
446
445
|
const tabSelectionStep = ["tab", options.selectedTab];
|
|
447
446
|
const commandStep = options.commandTokens;
|
|
448
447
|
const navigationSummarySteps = includeNavigationSummary ? [["eval", NAVIGATION_SUMMARY_EVAL]] : [];
|
|
@@ -532,11 +531,10 @@ export function unwrapPinnedSessionBatchEnvelope(options) {
|
|
|
532
531
|
};
|
|
533
532
|
}
|
|
534
533
|
export async function runSessionCommandData(options) {
|
|
535
|
-
const {
|
|
534
|
+
const { args, cwd, namespace, pinNamespace, sessionName, signal, stdin, throwOnFailure, timeoutMs } = options;
|
|
536
535
|
if (!sessionName)
|
|
537
536
|
return undefined;
|
|
538
537
|
const processResult = await runAgentBrowserProcess({
|
|
539
|
-
allowManagedSessionTarget,
|
|
540
538
|
args: ["--json", ...(namespace !== undefined || pinNamespace ? ["--namespace", namespace ?? ""] : []), "--session", sessionName, ...args],
|
|
541
539
|
cwd,
|
|
542
540
|
signal,
|
|
@@ -2,7 +2,7 @@ import { cleanupElectronLaunchResources, inspectElectronLaunchStatus } from "../
|
|
|
2
2
|
import { discoverElectronApps } from "../../electron/discovery.js";
|
|
3
3
|
import { boundElectronProbeString } from "../../electron/cdp.js";
|
|
4
4
|
import { buildOwnedManagedSessionRestoreContext, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getPageTargetValidationError } from "../../page-target-validation.js";
|
|
6
6
|
import { isRecord } from "../../parsing.js";
|
|
7
7
|
import { withAttachedBrowserSessionContext } from "../../process.js";
|
|
8
8
|
import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
|
|
@@ -431,7 +431,6 @@ async function withOwnedElectronManagedSessionPolicy(options, run) {
|
|
|
431
431
|
async function collectOwnedElectronManagedSessionTarget(options) {
|
|
432
432
|
try {
|
|
433
433
|
const target = await withOwnedElectronManagedSessionPolicy({ ...options, args: ["get", "url"] }, async () => await collectElectronManagedSessionTarget({
|
|
434
|
-
allowManagedSessionTarget: true,
|
|
435
434
|
cwd: options.cwd,
|
|
436
435
|
namespace: options.namespace,
|
|
437
436
|
sessionName: options.sessionName,
|
|
@@ -446,7 +445,7 @@ async function collectOwnedElectronManagedSessionTarget(options) {
|
|
|
446
445
|
}
|
|
447
446
|
async function runElectronProbeCommandData(options) {
|
|
448
447
|
try {
|
|
449
|
-
return { data: await runSessionCommandData({ ...options,
|
|
448
|
+
return { data: await runSessionCommandData({ ...options, pinNamespace: true, throwOnFailure: true }) };
|
|
450
449
|
}
|
|
451
450
|
catch (error) {
|
|
452
451
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
@@ -460,9 +459,6 @@ async function collectElectronProbe(options) {
|
|
|
460
459
|
const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
|
|
461
460
|
if (!url)
|
|
462
461
|
throw new Error("get url returned no active page URL.");
|
|
463
|
-
const fileAccessError = getManagedSessionStateAccessValidationError({ args: ["snapshot", "-i"], currentPageUrl: url, cwd: options.cwd });
|
|
464
|
-
if (fileAccessError)
|
|
465
|
-
throw new ElectronManagedSessionPolicyError(fileAccessError);
|
|
466
462
|
const titleResult = await runElectronProbeCommandData({ ...commandContext, args: ["get", "title"] });
|
|
467
463
|
const focusedResult = await runElectronProbeCommandData({ ...commandContext, args: ["eval", "--stdin"], stdin: ELECTRON_FOCUSED_ELEMENT_EVAL });
|
|
468
464
|
const tabsResult = await runElectronProbeCommandData({ ...commandContext, args: ["tab", "list"] });
|
|
@@ -739,14 +735,13 @@ async function handleElectronHostInputInContext(options) {
|
|
|
739
735
|
const probeNamespace = compiledElectron.launchId ? launchRecord?.namespace : managedSessionNamespace;
|
|
740
736
|
const pageStateKey = getSessionPageStateKey(probeSessionName, probeNamespace) ?? probeSessionName;
|
|
741
737
|
const currentPageState = sessionPageState.get(pageStateKey);
|
|
742
|
-
const
|
|
738
|
+
const pageTargetError = getPageTargetValidationError({
|
|
743
739
|
args: ["snapshot", "-i"],
|
|
744
740
|
currentPageUrl: currentPageState.tabTarget?.url,
|
|
745
|
-
cwd,
|
|
746
741
|
pageUrlUnknown: currentPageState.tabTargetUnknown === true,
|
|
747
742
|
});
|
|
748
|
-
if (
|
|
749
|
-
throw new ElectronManagedSessionPolicyError(
|
|
743
|
+
if (pageTargetError)
|
|
744
|
+
throw new ElectronManagedSessionPolicyError(pageTargetError);
|
|
750
745
|
const managedSessionOwner = ownedManagedSessions.get(pageStateKey);
|
|
751
746
|
const headedManagedAutosaveDisabled = managedSessionOwner?.headedManagedAutosaveDisabled === true;
|
|
752
747
|
const headedManagedAutosaveInterval = managedSessionOwner?.headedManagedAutosaveInterval;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mkdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
-
import { getAgentBrowserStoragePathValidationError } from "../managed-session-state-policy.js";
|
|
1
|
+
import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, isAbsolute, resolve } from "node:path";
|
|
4
3
|
import { isRecord } from "../parsing.js";
|
|
4
|
+
import { isSessionArtifactManifest } from "../results/artifact-manifest.js";
|
|
5
5
|
export function normalizeRequestedOutputPath(path) {
|
|
6
6
|
return path.startsWith("@") ? path.slice(1) : path;
|
|
7
7
|
}
|
|
@@ -11,11 +11,41 @@ function getTextContent(result) {
|
|
|
11
11
|
.map((item) => item.text)
|
|
12
12
|
.join("\n\n") ?? "";
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function getOutputSource(result) {
|
|
15
|
+
return isRecord(result.details) && result.details.data !== undefined ? "details.data" : "content.text";
|
|
16
|
+
}
|
|
17
|
+
async function readCompactedSpill(path, manifest) {
|
|
18
|
+
if (!path || !manifest?.entries.some((entry) => entry.kind === "spill"
|
|
19
|
+
&& (entry.path === path || entry.absolutePath === path)
|
|
20
|
+
&& (entry.storageScope === "persistent-session" || entry.storageScope === "process-temp")
|
|
21
|
+
&& (entry.retentionState === "live" || entry.retentionState === "ephemeral")))
|
|
22
|
+
throw new Error("Full compacted output is unavailable from the wrapper-managed spill; outputPath was not written.");
|
|
23
|
+
const text = await readFile(path, "utf8");
|
|
24
|
+
return extname(path) === ".json" ? JSON.parse(text) : text;
|
|
25
|
+
}
|
|
26
|
+
async function rehydrateCompactedData(data, details, manifest) {
|
|
27
|
+
if (isRecord(data) && data.compacted === true) {
|
|
28
|
+
return readCompactedSpill(typeof details.fullOutputPath === "string" ? details.fullOutputPath : undefined, manifest);
|
|
29
|
+
}
|
|
30
|
+
if (!Array.isArray(data))
|
|
31
|
+
return data;
|
|
32
|
+
const batchSteps = Array.isArray(details.batchSteps) ? details.batchSteps : [];
|
|
33
|
+
return Promise.all(data.map(async (row, index) => {
|
|
34
|
+
if (!isRecord(row) || !isRecord(row.result) || row.result.compacted !== true)
|
|
35
|
+
return row;
|
|
36
|
+
const step = isRecord(batchSteps[index]) ? batchSteps[index] : undefined;
|
|
37
|
+
const path = typeof step?.fullOutputPath === "string"
|
|
38
|
+
? step.fullOutputPath
|
|
39
|
+
: typeof row.result.fullOutputPath === "string" ? row.result.fullOutputPath : undefined;
|
|
40
|
+
return { ...row, result: await readCompactedSpill(path, manifest) };
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
async function getOutputPayload(result) {
|
|
15
44
|
const details = isRecord(result.details) ? result.details : undefined;
|
|
16
|
-
if (details
|
|
17
|
-
return { source: "
|
|
18
|
-
|
|
45
|
+
if (details?.data === undefined)
|
|
46
|
+
return { source: "content.text", value: getTextContent(result) };
|
|
47
|
+
const manifest = isSessionArtifactManifest(details.artifactManifest) ? details.artifactManifest : undefined;
|
|
48
|
+
return { source: "details.data", value: await rehydrateCompactedData(details.data, details, manifest) };
|
|
19
49
|
}
|
|
20
50
|
function serializeOutputPayload(value) {
|
|
21
51
|
return typeof value === "string" ? value : `${JSON.stringify(value, null, 2)}\n`;
|
|
@@ -28,9 +58,6 @@ function appendOutputFileNotice(result, message) {
|
|
|
28
58
|
}
|
|
29
59
|
return [{ type: "text", text: message }, ...content];
|
|
30
60
|
}
|
|
31
|
-
export function getAgentBrowserOutputPathValidationError(outputPath, cwd) {
|
|
32
|
-
return outputPath ? getAgentBrowserStoragePathValidationError(normalizeRequestedOutputPath(outputPath), cwd) : undefined;
|
|
33
|
-
}
|
|
34
61
|
function getArtifactPaths(result, cwd) {
|
|
35
62
|
const details = isRecord(result.details) ? result.details : undefined;
|
|
36
63
|
if (!details || !Array.isArray(details.artifacts))
|
|
@@ -61,24 +88,16 @@ async function pathsReferToSameFile(left, right) {
|
|
|
61
88
|
export async function applyAgentBrowserOutputPath(options) {
|
|
62
89
|
if (!options.outputPath)
|
|
63
90
|
return options.result;
|
|
64
|
-
const validationError = getAgentBrowserOutputPathValidationError(options.outputPath, options.cwd);
|
|
65
|
-
if (validationError) {
|
|
66
|
-
return {
|
|
67
|
-
content: [{ type: "text", text: validationError }],
|
|
68
|
-
details: { failureCategory: "validation-error", resultCategory: "failure", validationError },
|
|
69
|
-
isError: true,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
91
|
if (options.result.isError || (isRecord(options.result.details) && options.result.details.resultCategory === "failure"))
|
|
73
92
|
return options.result;
|
|
74
93
|
const requestedPath = normalizeRequestedOutputPath(options.outputPath);
|
|
75
94
|
const absolutePath = isAbsolute(requestedPath) ? requestedPath : resolve(options.cwd, requestedPath);
|
|
76
|
-
const
|
|
95
|
+
const source = getOutputSource(options.result);
|
|
77
96
|
for (const artifactPath of getArtifactPaths(options.result, options.cwd)) {
|
|
78
97
|
if (!await pathsReferToSameFile(absolutePath, artifactPath))
|
|
79
98
|
continue;
|
|
80
99
|
const message = "outputPath resolves to the same file as a browser artifact destination; choose a separate outputPath or omit it. The browser artifact was preserved.";
|
|
81
|
-
const outputFile = { absolutePath, error: message, path: requestedPath, source
|
|
100
|
+
const outputFile = { absolutePath, error: message, path: requestedPath, source, status: "failed" };
|
|
82
101
|
const details = isRecord(options.result.details) ? { ...options.result.details } : {};
|
|
83
102
|
delete details.successCategory;
|
|
84
103
|
return {
|
|
@@ -89,6 +108,7 @@ export async function applyAgentBrowserOutputPath(options) {
|
|
|
89
108
|
};
|
|
90
109
|
}
|
|
91
110
|
try {
|
|
111
|
+
const payload = await getOutputPayload(options.result);
|
|
92
112
|
const serialized = serializeOutputPayload(payload.value);
|
|
93
113
|
await mkdir(dirname(absolutePath), { recursive: true });
|
|
94
114
|
await writeFile(absolutePath, serialized, "utf8");
|
|
@@ -103,7 +123,7 @@ export async function applyAgentBrowserOutputPath(options) {
|
|
|
103
123
|
}
|
|
104
124
|
catch (error) {
|
|
105
125
|
const message = error instanceof Error ? error.message : String(error);
|
|
106
|
-
const outputFile = { absolutePath, error: message, path: requestedPath, source
|
|
126
|
+
const outputFile = { absolutePath, error: message, path: requestedPath, source, status: "failed" };
|
|
107
127
|
const details = isRecord(options.result.details)
|
|
108
128
|
? (() => {
|
|
109
129
|
const rest = { ...options.result.details };
|