pi-agent-browser-native 0.3.0 → 0.6.5
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 +265 -0
- package/README.md +130 -54
- package/dist/extensions/agent-browser/index.js +781 -169
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +50 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +5 -8
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +53 -12
- 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 +61 -13
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -24
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +26 -4
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +6 -139
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -116
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +54 -25
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +54 -48
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +71 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -7
- 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 +152 -64
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +244 -102
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +63 -37
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +20 -21
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
- package/dist/extensions/agent-browser/lib/playbook.js +29 -25
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +5 -12
- package/dist/extensions/agent-browser/lib/process.js +130 -104
- package/dist/extensions/agent-browser/lib/recording-reservations.js +116 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -6
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
- package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +86 -18
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +38 -2
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +18 -17
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +38 -20
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +60 -15
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
- package/dist/extensions/agent-browser/lib/results/presentation.js +36 -6
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +3 -1
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +54 -11
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
- 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 +186 -108
- package/dist/extensions/agent-browser/lib/session-page-state.js +71 -10
- package/dist/extensions/agent-browser/lib/temp.js +1 -2
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +108 -24
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +21 -0
- package/docs/ARCHITECTURE.md +57 -34
- package/docs/COMMAND_REFERENCE.md +255 -68
- package/docs/ELECTRON.md +2 -2
- package/docs/RELEASE.md +12 -10
- package/docs/REQUIREMENTS.md +11 -8
- package/docs/SUPPORT_MATRIX.md +36 -24
- package/docs/TOOL_CONTRACT.md +169 -95
- package/package.json +3 -1
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +87 -9
- package/scripts/agent-browser-target.mjs +21 -0
- package/scripts/build.mjs +41 -0
- package/scripts/config.mjs +1 -0
- package/scripts/doctor.mjs +16 -9
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -583
- 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,12 +1,15 @@
|
|
|
1
|
+
import { isCloseCommand } from "../../command-taxonomy.js";
|
|
1
2
|
import { isRecord } from "../../parsing.js";
|
|
2
|
-
import {
|
|
3
|
+
import { getAgentBrowserSessionIdentityKey } from "../../argv-grammar.js";
|
|
4
|
+
import { extractUpstreamCommandTokens, parseCommandInfo, redactInvocationArgs, redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
3
5
|
import { buildAgentBrowserNextActions } from "../action-recommendations.js";
|
|
4
|
-
import {
|
|
6
|
+
import { buildPendingWebMcpNextActions } from "../recovery-next-actions.js";
|
|
7
|
+
import { formatSessionArtifactRetentionSummary, isPendingRecordingArtifact } from "../artifact-manifest.js";
|
|
5
8
|
import { classifyAgentBrowserFailureCategory } from "../categories.js";
|
|
6
9
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
7
10
|
import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../network-routes.js";
|
|
8
|
-
import { applyNamespaceToNextActions, withOptionalSessionArgs } from "../next-actions.js";
|
|
9
|
-
import { stringifyModelFacing } from "./common.js";
|
|
11
|
+
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, withOptionalSessionArgs } from "../next-actions.js";
|
|
12
|
+
import { extractAgentBrowserLifecycle, stringifyModelFacing } from "./common.js";
|
|
10
13
|
import { buildArtifactVerificationSummary, classifyPresentationSuccessCategory, manifestHasNewNoticeWorthyEntries } from "./artifacts.js";
|
|
11
14
|
import { formatBatchStepCommand, getPresentationImages, getPresentationPaths, getPresentationText, isStringArray } from "./content.js";
|
|
12
15
|
import { buildPageChangeSummary } from "./navigation.js";
|
|
@@ -62,7 +65,7 @@ function hasModelFacingArgRedaction(args) {
|
|
|
62
65
|
function getStatefulCommandSensitiveValues(command) {
|
|
63
66
|
if (!command)
|
|
64
67
|
return [];
|
|
65
|
-
const tokens =
|
|
68
|
+
const tokens = extractUpstreamCommandTokens(command);
|
|
66
69
|
const values = [];
|
|
67
70
|
if (tokens[0] === "cookies" && tokens[1] === "set" && tokens[3])
|
|
68
71
|
values.push(tokens[3]);
|
|
@@ -92,6 +95,11 @@ function redactExactValues(value, sensitiveValues) {
|
|
|
92
95
|
return value;
|
|
93
96
|
return redactSensitiveValue(Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, redactExactValues(entryValue, sensitiveValues)])));
|
|
94
97
|
}
|
|
98
|
+
export function redactBatchStepErrorData(command, value) {
|
|
99
|
+
return command?.[0] === "clipboard"
|
|
100
|
+
? redactSensitiveValue(redactClipboardPermissionErrorValue({ command: "clipboard", subcommand: command[1] }, value, getClipboardWritePayloadCandidates(command)))
|
|
101
|
+
: redactExactValues(value, getStatefulCommandSensitiveValues(command));
|
|
102
|
+
}
|
|
95
103
|
function getTypedTextLength(command) {
|
|
96
104
|
return command?.[0] === "keyboard" && command[1] === "type" && typeof command[2] === "string"
|
|
97
105
|
? Array.from(command[2]).length
|
|
@@ -181,14 +189,13 @@ function formatBatchStepsText(steps) {
|
|
|
181
189
|
return lines.join("\n\n");
|
|
182
190
|
}
|
|
183
191
|
async function buildBatchStepPresentation(options) {
|
|
184
|
-
const { artifactManifest, artifactRequest, buildNestedToolPresentation, cwd, index, item, namespace, networkRoutes, persistentArtifactStore, sessionName } = options;
|
|
192
|
+
const { artifactManifest, artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs, artifactRequest, buildNestedToolPresentation, cwd, index, item, namespace, networkRoutes, persistentArtifactStore, sessionName } = options;
|
|
185
193
|
const command = isStringArray(item.command) ? item.command : undefined;
|
|
186
194
|
const redactedCommand = command ? redactInvocationArgs(command) : undefined;
|
|
187
195
|
const commandText = formatBatchStepCommand(hasModelFacingArgRedaction(redactedCommand) ? redactedCommand : command, index);
|
|
196
|
+
const lifecycle = extractAgentBrowserLifecycle(item.result);
|
|
188
197
|
if (item.success === false) {
|
|
189
|
-
const redactedErrorData = command
|
|
190
|
-
? redactSensitiveValue(redactClipboardPermissionErrorValue({ command: "clipboard", subcommand: command[1] }, item.error, getClipboardWritePayloadCandidates(command)))
|
|
191
|
-
: redactExactValues(item.error, getStatefulCommandSensitiveValues(command));
|
|
198
|
+
const redactedErrorData = redactBatchStepErrorData(command, item.error);
|
|
192
199
|
const errorText = formatBatchStepError(redactedErrorData);
|
|
193
200
|
const failureCategory = classifyAgentBrowserFailureCategory({
|
|
194
201
|
args: command,
|
|
@@ -196,13 +203,15 @@ async function buildBatchStepPresentation(options) {
|
|
|
196
203
|
errorText,
|
|
197
204
|
});
|
|
198
205
|
const confirmationRequired = detectConfirmationRequired(item.error);
|
|
199
|
-
const nextActions = applyNamespaceToNextActions(mergePresentationNextActions(buildAgentBrowserNextActions({
|
|
206
|
+
const nextActions = applyNamespaceToNextActions(mergePresentationNextActions(isWaitTextAssertionCommand(command) ? [buildWaitTextAssertionFailureNextAction(sessionName)] : undefined, buildAgentBrowserNextActions({
|
|
200
207
|
args: command,
|
|
201
208
|
command: command?.[0],
|
|
202
209
|
confirmationId: confirmationRequired?.id,
|
|
203
210
|
failureCategory,
|
|
204
211
|
resultCategory: "failure",
|
|
205
|
-
|
|
212
|
+
sessionName,
|
|
213
|
+
subcommand: command?.[1],
|
|
214
|
+
})), namespace);
|
|
206
215
|
const presentation = {
|
|
207
216
|
content: [{ type: "text", text: errorText }],
|
|
208
217
|
failureCategory,
|
|
@@ -219,6 +228,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
219
228
|
data: redactedErrorData,
|
|
220
229
|
failureCategory,
|
|
221
230
|
index,
|
|
231
|
+
lifecycle,
|
|
222
232
|
nextActions,
|
|
223
233
|
resultCategory: "failure",
|
|
224
234
|
success: false,
|
|
@@ -235,12 +245,15 @@ async function buildBatchStepPresentation(options) {
|
|
|
235
245
|
: undefined;
|
|
236
246
|
const presentation = await buildNestedToolPresentation({
|
|
237
247
|
artifactManifest,
|
|
248
|
+
artifactMaxUpdatedAtMs,
|
|
249
|
+
artifactMinUpdatedAtMs,
|
|
238
250
|
artifactRequest,
|
|
239
251
|
commandInfo: commandInfoWithTokens,
|
|
240
252
|
cwd,
|
|
241
253
|
args: command,
|
|
242
254
|
envelope: { data: item.result, success: true },
|
|
243
255
|
networkRouteDiagnostics,
|
|
256
|
+
namespace,
|
|
244
257
|
persistentArtifactStore,
|
|
245
258
|
sessionName,
|
|
246
259
|
});
|
|
@@ -254,13 +267,20 @@ async function buildBatchStepPresentation(options) {
|
|
|
254
267
|
});
|
|
255
268
|
const text = getPresentationText(presentation) || presentation.summary;
|
|
256
269
|
const stepSucceeded = presentation.resultCategory !== "failure";
|
|
257
|
-
const
|
|
270
|
+
const pendingWebMcpMutation = stepSucceeded
|
|
271
|
+
&& commandInfo.command === "webmcp"
|
|
272
|
+
&& ["invoke", "result"].includes(commandInfo.subcommand ?? "")
|
|
273
|
+
&& isRecord(presentation.data)
|
|
274
|
+
&& presentation.data.status === "pending";
|
|
275
|
+
const nextActions = applyNamespaceToNextActions(pendingWebMcpMutation ? buildPendingWebMcpNextActions(sessionName) : presentation.nextActions ?? buildAgentBrowserNextActions({
|
|
258
276
|
artifacts: presentation.artifacts,
|
|
259
277
|
args: command,
|
|
260
278
|
command: command?.[0],
|
|
261
279
|
failureCategory: presentation.failureCategory,
|
|
262
280
|
resultCategory: stepSucceeded ? "success" : "failure",
|
|
263
281
|
savedFilePath: presentation.savedFilePath,
|
|
282
|
+
sessionName,
|
|
283
|
+
subcommand: command?.[1],
|
|
264
284
|
successCategory: presentation.successCategory,
|
|
265
285
|
}), namespace);
|
|
266
286
|
const pageChangeSummary = buildPageChangeSummary({
|
|
@@ -284,6 +304,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
284
304
|
imagePath: imagePaths[0],
|
|
285
305
|
imagePaths: imagePaths.length > 0 ? imagePaths : undefined,
|
|
286
306
|
index,
|
|
307
|
+
lifecycle,
|
|
287
308
|
networkRouteDiagnostics: presentation.networkRouteDiagnostics,
|
|
288
309
|
nextActions,
|
|
289
310
|
pageChangeSummary,
|
|
@@ -298,6 +319,42 @@ async function buildBatchStepPresentation(options) {
|
|
|
298
319
|
presentation,
|
|
299
320
|
};
|
|
300
321
|
}
|
|
322
|
+
function abandonedRecordingArtifact(artifact) {
|
|
323
|
+
const { recordingState: _recordingState, willExistOnStop: _willExistOnStop, ...terminal } = artifact;
|
|
324
|
+
return { ...terminal, exists: false, status: "missing", subcommand: "close-abandoned" };
|
|
325
|
+
}
|
|
326
|
+
function coalesceTerminalBatchRecordingArtifacts(steps, sessionName, namespace) {
|
|
327
|
+
const artifacts = [];
|
|
328
|
+
const pendingIndexesBySession = new Map();
|
|
329
|
+
const removedPendingIndexes = new Set();
|
|
330
|
+
for (const step of steps) {
|
|
331
|
+
for (const artifact of step.presentation.artifacts ?? []) {
|
|
332
|
+
const index = artifacts.push(artifact) - 1;
|
|
333
|
+
if (artifact.command !== "record" || artifact.kind !== "video")
|
|
334
|
+
continue;
|
|
335
|
+
const session = artifact.session ? getAgentBrowserSessionIdentityKey(artifact.session, artifact.namespace) : "";
|
|
336
|
+
if (isPendingRecordingArtifact(artifact)) {
|
|
337
|
+
const pendingIndexes = pendingIndexesBySession.get(session) ?? [];
|
|
338
|
+
pendingIndexes.push(index);
|
|
339
|
+
pendingIndexesBySession.set(session, pendingIndexes);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const pendingIndex = pendingIndexesBySession.get(session)?.pop();
|
|
343
|
+
if (pendingIndex !== undefined)
|
|
344
|
+
removedPendingIndexes.add(pendingIndex);
|
|
345
|
+
}
|
|
346
|
+
const command = step.details.command;
|
|
347
|
+
if (step.details.success !== true || !command || !isCloseCommand(extractUpstreamCommandTokens(command)[0]))
|
|
348
|
+
continue;
|
|
349
|
+
const session = sessionName ? getAgentBrowserSessionIdentityKey(sessionName, namespace) : "";
|
|
350
|
+
for (const pendingIndex of pendingIndexesBySession.get(session) ?? []) {
|
|
351
|
+
const pending = artifacts[pendingIndex];
|
|
352
|
+
if (pending && !removedPendingIndexes.has(pendingIndex))
|
|
353
|
+
artifacts[pendingIndex] = abandonedRecordingArtifact(pending);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return artifacts.filter((_, index) => !removedPendingIndexes.has(index));
|
|
357
|
+
}
|
|
301
358
|
export async function buildBatchPresentation(options) {
|
|
302
359
|
const { artifactRequests, buildNestedToolPresentation, cwd, data, namespace, networkRoutes, persistentArtifactStore, sessionName, summary } = options;
|
|
303
360
|
const steps = [];
|
|
@@ -307,6 +364,8 @@ export async function buildBatchPresentation(options) {
|
|
|
307
364
|
for (const [index, item] of data.entries()) {
|
|
308
365
|
const step = await buildBatchStepPresentation({
|
|
309
366
|
artifactManifest: currentArtifactManifest,
|
|
367
|
+
artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs,
|
|
368
|
+
artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs,
|
|
310
369
|
artifactRequest: artifactRequests?.[index],
|
|
311
370
|
buildNestedToolPresentation,
|
|
312
371
|
cwd,
|
|
@@ -319,7 +378,7 @@ export async function buildBatchPresentation(options) {
|
|
|
319
378
|
});
|
|
320
379
|
steps.push(step);
|
|
321
380
|
currentArtifactManifest = step.presentation.artifactManifest ?? currentArtifactManifest;
|
|
322
|
-
currentNetworkRoutes = applyNetworkRouteRecords(currentNetworkRoutes, isStringArray(item.command) ?
|
|
381
|
+
currentNetworkRoutes = applyNetworkRouteRecords(currentNetworkRoutes, isStringArray(item.command) ? extractUpstreamCommandTokens(item.command) : undefined, item.success !== false && step.details.success);
|
|
323
382
|
protectedPersistentPaths.push(...getPresentationPaths({
|
|
324
383
|
primaryPath: step.presentation.fullOutputPath,
|
|
325
384
|
secondaryPaths: step.presentation.fullOutputPaths,
|
|
@@ -327,7 +386,7 @@ export async function buildBatchPresentation(options) {
|
|
|
327
386
|
}
|
|
328
387
|
const batchFailure = getBatchFailureDetails(steps);
|
|
329
388
|
const images = steps.flatMap((step) => getPresentationImages(step.presentation));
|
|
330
|
-
const artifacts = steps
|
|
389
|
+
const artifacts = coalesceTerminalBatchRecordingArtifacts(steps, sessionName, namespace);
|
|
331
390
|
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
332
391
|
const fullOutputPaths = steps.flatMap((step) => getPresentationPaths({
|
|
333
392
|
primaryPath: step.presentation.fullOutputPath,
|
|
@@ -340,6 +399,10 @@ export async function buildBatchPresentation(options) {
|
|
|
340
399
|
const redactedBatchData = steps.map(({ details }) => (details.success
|
|
341
400
|
? { command: details.command, result: details.data, success: true }
|
|
342
401
|
: { command: details.command, error: details.text, success: false }));
|
|
402
|
+
const unverifiedMutationCount = steps.filter((step) => step.details.pageChangeSummary?.changeType === "mutation" && step.details.pageChangeSummary.observed === false).length;
|
|
403
|
+
const mutationEvidenceText = unverifiedMutationCount > 0
|
|
404
|
+
? `Mutation evidence: ${unverifiedMutationCount} action result${unverifiedMutationCount === 1 ? " proves" : "s prove"} dispatch only, not application state change. Use explicit later assertions or external receipts as postconditions; fixed waits are not postconditions.`
|
|
405
|
+
: undefined;
|
|
343
406
|
const stepText = formatBatchStepsText(steps);
|
|
344
407
|
const batchSummary = batchFailure === undefined
|
|
345
408
|
? summary
|
|
@@ -351,15 +414,17 @@ export async function buildBatchPresentation(options) {
|
|
|
351
414
|
`First failing step: ${batchFailure.failedStep.index + 1} — ${batchFailure.failedStep.commandText}`,
|
|
352
415
|
batchFailure.failureCount > 1 ? `${batchFailure.failureCount} steps failed. See the per-step results below.` : "See the per-step results below.",
|
|
353
416
|
].join("\n");
|
|
354
|
-
const text = failureHeader
|
|
417
|
+
const text = [failureHeader, mutationEvidenceText, stepText].filter((line) => line !== undefined).join("\n\n");
|
|
355
418
|
const artifactRetentionSummary = currentArtifactManifest ? formatSessionArtifactRetentionSummary(currentArtifactManifest) : undefined;
|
|
356
419
|
const contentText = artifactRetentionSummary && manifestHasNewNoticeWorthyEntries(options.artifactManifest, currentArtifactManifest)
|
|
357
420
|
? `${text}\n\n${artifactRetentionSummary}`
|
|
358
421
|
: text;
|
|
422
|
+
const artifactLifecycleActions = applyNamespaceToNextActions(buildAgentBrowserNextActions({ artifacts, command: "batch", resultCategory: batchFailure ? "failure" : "success", sessionName }), namespace);
|
|
359
423
|
const nextActions = batchFailure
|
|
360
|
-
? batchFailure.failedStep.nextActions
|
|
361
|
-
:
|
|
424
|
+
? appendUniqueAgentBrowserNextActions([...(batchFailure.failedStep.nextActions ?? [])], artifactLifecycleActions)
|
|
425
|
+
: artifactLifecycleActions;
|
|
362
426
|
const changedSteps = steps.map((step) => step.details).filter((details) => details.pageChangeSummary !== undefined);
|
|
427
|
+
const observedChangeCount = changedSteps.filter((details) => details.pageChangeSummary?.observed === true).length;
|
|
363
428
|
const pageChangeSummary = artifacts.length > 0
|
|
364
429
|
? buildPageChangeSummary({
|
|
365
430
|
artifacts,
|
|
@@ -373,7 +438,10 @@ export async function buildBatchPresentation(options) {
|
|
|
373
438
|
changeType: "mutation",
|
|
374
439
|
command: "batch",
|
|
375
440
|
nextActionIds: nextActions?.map((action) => action.id),
|
|
376
|
-
|
|
441
|
+
observed: observedChangeCount > 0,
|
|
442
|
+
summary: observedChangeCount > 0
|
|
443
|
+
? `batch → ${observedChangeCount} observed change${observedChangeCount === 1 ? "" : "s"}${unverifiedMutationCount > 0 ? `; ${unverifiedMutationCount} dispatched action${unverifiedMutationCount === 1 ? "" : "s"} unverified` : ""}`
|
|
444
|
+
: `batch → ${unverifiedMutationCount} action${unverifiedMutationCount === 1 ? "" : "s"} dispatched → application change unverified`,
|
|
377
445
|
}
|
|
378
446
|
: undefined;
|
|
379
447
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isRecord } from "../../parsing.js";
|
|
2
2
|
import { redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
3
3
|
import { stringifyUnknown, truncateText } from "../text.js";
|
|
4
|
+
const UNTITLED_PAGE_SUMMARY = "(untitled page)";
|
|
4
5
|
export function stringifyModelFacing(value) {
|
|
5
6
|
return stringifyUnknown(redactSensitiveValue(value));
|
|
6
7
|
}
|
|
@@ -23,7 +24,7 @@ export function redactModelFacingText(text) {
|
|
|
23
24
|
return redactSensitiveText(text);
|
|
24
25
|
}
|
|
25
26
|
export function redactModelFacingTextIfSensitive(text) {
|
|
26
|
-
return
|
|
27
|
+
return /(?:@|\b(?:access[_-]?key|api[_-]?key|auth|authorization|basic|bearer|connection[_-]?string|cookie|database[_-]?url|db[_-]?url|mongo(?:db)?[_-]?uri|pass(?:word)?|private[_-]?key|redis[_-]?url|secret|session[_-]?id|token)\b)/i.test(text)
|
|
27
28
|
? redactModelFacingText(text)
|
|
28
29
|
: text;
|
|
29
30
|
}
|
|
@@ -34,6 +35,41 @@ export function getStringField(data, key) {
|
|
|
34
35
|
const value = data[key];
|
|
35
36
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
36
37
|
}
|
|
38
|
+
// `lifecycle` is upstream launch/reuse bookkeeping, never page content, so it must not be the
|
|
39
|
+
// answer an agent reads when a command has no dedicated presenter.
|
|
40
|
+
export function extractAgentBrowserLifecycle(result) {
|
|
41
|
+
if (Array.isArray(result)) {
|
|
42
|
+
let latest;
|
|
43
|
+
for (const row of result) {
|
|
44
|
+
const nested = isRecord(row) ? row.result ?? row.data ?? row : row;
|
|
45
|
+
latest = extractAgentBrowserLifecycle(nested) ?? latest;
|
|
46
|
+
}
|
|
47
|
+
return latest;
|
|
48
|
+
}
|
|
49
|
+
if (!isRecord(result))
|
|
50
|
+
return undefined;
|
|
51
|
+
if (isRecord(result.lifecycle) && isRecord(result.lifecycle.effectiveLaunch)) {
|
|
52
|
+
const browserLaunched = result.lifecycle.effectiveLaunch.browserLaunched;
|
|
53
|
+
if (typeof browserLaunched === "boolean")
|
|
54
|
+
return { effectiveLaunch: { browserLaunched } };
|
|
55
|
+
}
|
|
56
|
+
return extractAgentBrowserLifecycle(result.result ?? result.data);
|
|
57
|
+
}
|
|
58
|
+
export function omitUpstreamLifecycle(data) {
|
|
59
|
+
const { lifecycle: _lifecycle, ...rest } = data;
|
|
60
|
+
return rest;
|
|
61
|
+
}
|
|
62
|
+
export function getPageSummary(data) {
|
|
63
|
+
const title = typeof data.title === "string" ? data.title : undefined;
|
|
64
|
+
const url = typeof data.url === "string" ? data.url : undefined;
|
|
65
|
+
if (title === undefined && url === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (title && url)
|
|
68
|
+
return `${title}\n${url}`;
|
|
69
|
+
if (url)
|
|
70
|
+
return url;
|
|
71
|
+
return title || UNTITLED_PAGE_SUMMARY;
|
|
72
|
+
}
|
|
37
73
|
export function formatCount(count, singular, plural = `${singular}s`) {
|
|
38
74
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
39
75
|
}
|
|
@@ -4,7 +4,6 @@ import { classifyNetworkRequestFailure, isApiLikeNetworkRequest, isNetworkArtifa
|
|
|
4
4
|
import { withOptionalSessionArgs } from "../next-actions.js";
|
|
5
5
|
import { stringifyUnknown, truncateText } from "../text.js";
|
|
6
6
|
import { firstLine, formatCount, getArrayField, getStringField, parseJsonPreviewString, redactModelFacingText, redactModelFacingTextIfSensitive, stringifyModelFacing, } from "./common.js";
|
|
7
|
-
import { filterCallerOwnedSessionListItems, filterCallerOwnedStateListItems, filterManagedSessionListRows, filterManagedStateListRows, } from "./managed-list-filter.js";
|
|
8
7
|
const DIAGNOSTIC_REQUEST_PREVIEW_LIMIT = 40;
|
|
9
8
|
const DIAGNOSTIC_LOG_PREVIEW_LIMIT = 80;
|
|
10
9
|
const NETWORK_BODY_PREVIEW_MAX_CHARS = 280;
|
|
@@ -61,7 +60,9 @@ export function getTabSummary(data) {
|
|
|
61
60
|
? String(tab.index)
|
|
62
61
|
: String(index);
|
|
63
62
|
const labelText = label && label !== tabSelector ? ` label=${redactModelFacingText(label)}` : "";
|
|
64
|
-
|
|
63
|
+
const targetId = typeof tab.targetId === "string" && tab.targetId.trim().length > 0 ? tab.targetId.trim() : undefined;
|
|
64
|
+
const targetText = targetId && targetId !== tabSelector ? ` target=${redactModelFacingText(targetId)}` : "";
|
|
65
|
+
return `${marker} [${tabSelector}]${labelText}${targetText} ${title} — ${url}`;
|
|
65
66
|
});
|
|
66
67
|
return lines.join("\n");
|
|
67
68
|
}
|
|
@@ -110,7 +111,7 @@ export function formatDiagnosticSummary(commandInfo, data) {
|
|
|
110
111
|
if (commandInfo.command === "session") {
|
|
111
112
|
const sessions = getArrayField(data, "sessions");
|
|
112
113
|
if (sessions)
|
|
113
|
-
return `Sessions: ${
|
|
114
|
+
return `Sessions: ${sessions.length}`;
|
|
114
115
|
const session = getStringField(data, "session");
|
|
115
116
|
if (session)
|
|
116
117
|
return `Session: ${session}`;
|
|
@@ -168,10 +169,8 @@ export function formatDiagnosticSummary(commandInfo, data) {
|
|
|
168
169
|
}
|
|
169
170
|
if (commandInfo.command === "state") {
|
|
170
171
|
const states = getArrayField(data, "states") ?? getArrayField(data, "files");
|
|
171
|
-
if (states)
|
|
172
|
-
|
|
173
|
-
return `States: ${visibleStates.length}`;
|
|
174
|
-
}
|
|
172
|
+
if (states)
|
|
173
|
+
return `States: ${states.length}`;
|
|
175
174
|
if (commandInfo.subcommand === "load")
|
|
176
175
|
return undefined;
|
|
177
176
|
const stateName = getStringField(data, "name") ?? getStringField(data, "file") ?? getStringField(data, "filename") ?? getStringField(data, "path") ?? commandInfo.subcommand;
|
|
@@ -259,10 +258,9 @@ export function formatDiagnosticSummary(commandInfo, data) {
|
|
|
259
258
|
function formatSessionText(data) {
|
|
260
259
|
const sessions = getArrayField(data, "sessions");
|
|
261
260
|
if (sessions) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return visibleSessions
|
|
261
|
+
if (sessions.length === 0)
|
|
262
|
+
return "No active sessions.";
|
|
263
|
+
return sessions
|
|
266
264
|
.map((item, index) => {
|
|
267
265
|
if (!isRecord(item))
|
|
268
266
|
return `${index + 1}. ${stringifyModelFacing(item)}`;
|
|
@@ -348,6 +346,8 @@ function formatNetworkRequestLine(item, index) {
|
|
|
348
346
|
}
|
|
349
347
|
function formatNetworkRequestsText(data, commandInfo) {
|
|
350
348
|
const requests = getArrayField(data, "requests");
|
|
349
|
+
if (isClearDiagnosticCommand(commandInfo) && data.cleared === true && !requests)
|
|
350
|
+
return "Network request buffer cleared.";
|
|
351
351
|
if (!requests)
|
|
352
352
|
return undefined;
|
|
353
353
|
if (isClearDiagnosticCommand(commandInfo)) {
|
|
@@ -593,6 +593,8 @@ export function buildStreamNextActions(commandInfo, data, sessionName) {
|
|
|
593
593
|
}
|
|
594
594
|
function formatConsoleText(data, commandInfo) {
|
|
595
595
|
const messages = getArrayField(data, "messages");
|
|
596
|
+
if (isClearDiagnosticCommand(commandInfo) && data.cleared === true && !messages)
|
|
597
|
+
return "Console buffer cleared.";
|
|
596
598
|
if (!messages)
|
|
597
599
|
return undefined;
|
|
598
600
|
if (isClearDiagnosticCommand(commandInfo)) {
|
|
@@ -926,10 +928,9 @@ function formatStateText(data, subcommand) {
|
|
|
926
928
|
}
|
|
927
929
|
const states = getArrayField(data, "states") ?? getArrayField(data, "files");
|
|
928
930
|
if (states) {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
return visibleStates
|
|
931
|
+
if (states.length === 0)
|
|
932
|
+
return "No saved states.";
|
|
933
|
+
return states
|
|
933
934
|
.map((item, index) => {
|
|
934
935
|
if (!isRecord(item))
|
|
935
936
|
return `${index + 1}. ${redactModelFacingTextIfSensitive(stringifyModelFacing(item))}`;
|
|
@@ -973,9 +974,9 @@ export function redactPresentationData(commandInfo, data) {
|
|
|
973
974
|
if (commandInfo.command === "storage")
|
|
974
975
|
return redactStorageData(data);
|
|
975
976
|
if (commandInfo.command === "session" && commandInfo.subcommand === "list")
|
|
976
|
-
return redactStructuredPresentationValue(
|
|
977
|
+
return redactStructuredPresentationValue(data);
|
|
977
978
|
if (commandInfo.command === "state" && commandInfo.subcommand === "list")
|
|
978
|
-
return redactStructuredPresentationValue(
|
|
979
|
+
return redactStructuredPresentationValue(data);
|
|
979
980
|
if (commandInfo.command === "state" && commandInfo.subcommand === "show")
|
|
980
981
|
return redactStatefulValues(data, new Set(["value"]));
|
|
981
982
|
return redactStructuredPresentationValue(data);
|
|
@@ -189,11 +189,12 @@ export function buildErrorPresentation(options) {
|
|
|
189
189
|
const nextActions = [
|
|
190
190
|
...(buildUnknownCommandSuggestionActions(unknownCommandSuggestions, sessionName) ?? []),
|
|
191
191
|
...(browserProfileConfigRecovery?.actions ?? []),
|
|
192
|
-
...(buildAgentBrowserNextActions({
|
|
192
|
+
...(browserProfileConfigRecovery ? [] : buildAgentBrowserNextActions({
|
|
193
193
|
args,
|
|
194
194
|
command: commandInfo.command,
|
|
195
195
|
failureCategory: categoryDetails.failureCategory,
|
|
196
196
|
resultCategory: "failure",
|
|
197
|
+
sessionName,
|
|
197
198
|
}) ?? []),
|
|
198
199
|
];
|
|
199
200
|
return {
|
|
@@ -1,16 +1,31 @@
|
|
|
1
|
-
import { isNavigationObservableCommandName, isPageChangeSummaryCommand } from "../../command-taxonomy.js";
|
|
1
|
+
import { isNavigationObservableCommandName, isOpenNavigationCommand, isPageChangeSummaryCommand } from "../../command-taxonomy.js";
|
|
2
2
|
import { isRecord } from "../../parsing.js";
|
|
3
3
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
4
|
-
import { redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
4
|
+
import { omitUpstreamLifecycle, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
5
5
|
const NAVIGATION_SUMMARY_FIELD = "navigationSummary";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const GET_RESULT_FIELDS = {
|
|
7
|
+
attr: "value",
|
|
8
|
+
count: "count",
|
|
9
|
+
html: "html",
|
|
10
|
+
text: "text",
|
|
11
|
+
title: "title",
|
|
12
|
+
url: "url",
|
|
13
|
+
value: "value",
|
|
14
|
+
};
|
|
15
|
+
function getScalarExtractionResult(commandInfo, data) {
|
|
16
|
+
const fallbackField = commandInfo.command === "get" && commandInfo.subcommand ? GET_RESULT_FIELDS[commandInfo.subcommand] ?? "" : "";
|
|
17
|
+
const resultField = Object.hasOwn(data, "result") ? "result" : fallbackField.length > 0 && Object.hasOwn(data, fallbackField) ? fallbackField : undefined;
|
|
18
|
+
if (resultField === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
const result = data[resultField];
|
|
21
|
+
if (typeof result === "string")
|
|
22
|
+
return result.trim().length > 0 ? result : "(empty string)";
|
|
23
|
+
if (typeof result === "number" || typeof result === "boolean")
|
|
12
24
|
return String(result);
|
|
13
|
-
|
|
25
|
+
if (result === null || result === undefined)
|
|
26
|
+
return "null";
|
|
27
|
+
if (typeof result === "object")
|
|
28
|
+
return JSON.stringify(result);
|
|
14
29
|
return undefined;
|
|
15
30
|
}
|
|
16
31
|
function getExtractionOrigin(data) {
|
|
@@ -32,7 +47,7 @@ function formatGetSummaryLabel(subcommand) {
|
|
|
32
47
|
return `${subcommand.slice(0, 1).toUpperCase()}${subcommand.slice(1)}`;
|
|
33
48
|
}
|
|
34
49
|
export function formatExtractionSummary(commandInfo, data) {
|
|
35
|
-
const scalarResult = getScalarExtractionResult(data);
|
|
50
|
+
const scalarResult = getScalarExtractionResult(commandInfo, data);
|
|
36
51
|
if (!scalarResult) {
|
|
37
52
|
return undefined;
|
|
38
53
|
}
|
|
@@ -50,7 +65,7 @@ export function formatExtractionText(commandInfo, data) {
|
|
|
50
65
|
if (commandInfo.command !== "get" && commandInfo.command !== "eval") {
|
|
51
66
|
return undefined;
|
|
52
67
|
}
|
|
53
|
-
const scalarResult = getScalarExtractionResult(data);
|
|
68
|
+
const scalarResult = getScalarExtractionResult(commandInfo, data);
|
|
54
69
|
if (!scalarResult) {
|
|
55
70
|
return undefined;
|
|
56
71
|
}
|
|
@@ -59,8 +74,8 @@ export function formatExtractionText(commandInfo, data) {
|
|
|
59
74
|
const safeOrigin = origin ? redactModelFacingText(origin) : undefined;
|
|
60
75
|
return safeOrigin && safeOrigin !== safeScalarResult ? `${safeScalarResult}\n\nOrigin: ${safeOrigin}` : safeScalarResult;
|
|
61
76
|
}
|
|
62
|
-
export function isNavigationObservableCommand(command) {
|
|
63
|
-
return isNavigationObservableCommandName(command);
|
|
77
|
+
export function isNavigationObservableCommand(command, subcommand) {
|
|
78
|
+
return isNavigationObservableCommandName(command, subcommand);
|
|
64
79
|
}
|
|
65
80
|
function isNavigationSummary(value) {
|
|
66
81
|
return isRecord(value) && (typeof value.title === "string" || typeof value.url === "string");
|
|
@@ -80,7 +95,7 @@ function getTopLevelNavigationSummary(data) {
|
|
|
80
95
|
function getNormalizedNavigationSummary(summary) {
|
|
81
96
|
const title = typeof summary?.title === "string" && summary.title.trim().length > 0 ? summary.title.trim() : undefined;
|
|
82
97
|
const url = typeof summary?.url === "string" && summary.url.trim().length > 0 ? summary.url.trim() : undefined;
|
|
83
|
-
return title || url ? { title, url } : undefined;
|
|
98
|
+
return title || url ? { title, url, ...(typeof summary?.urlChanged === "boolean" ? { urlChanged: summary.urlChanged } : {}) } : undefined;
|
|
84
99
|
}
|
|
85
100
|
export function formatNavigationSummary(summary) {
|
|
86
101
|
const normalized = getNormalizedNavigationSummary(summary);
|
|
@@ -94,20 +109,22 @@ export function buildPageChangeSummary(options) {
|
|
|
94
109
|
const { artifacts, commandInfo, data, nextActions, savedFilePath } = options;
|
|
95
110
|
const artifactCount = artifacts?.length ?? 0;
|
|
96
111
|
const navigation = isRecord(data)
|
|
97
|
-
? getNormalizedNavigationSummary(getNavigationSummary(data) ?? (isPageChangeSummaryCommand(commandInfo.command) ? getTopLevelNavigationSummary(data) : undefined))
|
|
112
|
+
? getNormalizedNavigationSummary(getNavigationSummary(data) ?? (isPageChangeSummaryCommand(commandInfo.command, commandInfo.subcommand) ? getTopLevelNavigationSummary(data) : undefined))
|
|
98
113
|
: undefined;
|
|
99
114
|
const confirmationRequired = detectConfirmationRequired(data) !== undefined;
|
|
100
|
-
if (!navigation && !confirmationRequired && artifactCount === 0 && !savedFilePath && !isPageChangeSummaryCommand(commandInfo.command)) {
|
|
115
|
+
if (!navigation && !confirmationRequired && artifactCount === 0 && !savedFilePath && !isPageChangeSummaryCommand(commandInfo.command, commandInfo.subcommand)) {
|
|
101
116
|
return undefined;
|
|
102
117
|
}
|
|
118
|
+
const navigationObserved = navigation && (navigation.urlChanged === true || isOpenNavigationCommand(commandInfo.command) || ["back", "forward", "pushstate", "reload"].includes(commandInfo.command ?? ""));
|
|
103
119
|
const changeType = savedFilePath || artifactCount > 0
|
|
104
120
|
? "artifact"
|
|
105
|
-
:
|
|
121
|
+
: navigationObserved
|
|
106
122
|
? "navigation"
|
|
107
123
|
: confirmationRequired
|
|
108
124
|
? "confirmation"
|
|
109
125
|
: "mutation";
|
|
110
|
-
const
|
|
126
|
+
const observed = changeType !== "mutation";
|
|
127
|
+
const parts = [commandInfo.command ?? "agent-browser", observed ? changeType : "action dispatched"];
|
|
111
128
|
if (navigation?.title)
|
|
112
129
|
parts.push(navigation.title);
|
|
113
130
|
if (navigation?.url)
|
|
@@ -121,8 +138,9 @@ export function buildPageChangeSummary(options) {
|
|
|
121
138
|
changeType,
|
|
122
139
|
...(commandInfo.command ? { command: commandInfo.command } : {}),
|
|
123
140
|
...(nextActions ? { nextActionIds: nextActions.map((action) => action.id) } : {}),
|
|
141
|
+
observed,
|
|
124
142
|
...(savedFilePath ? { savedFilePath } : {}),
|
|
125
|
-
summary: parts.join(" → ")
|
|
143
|
+
summary: `${parts.join(" → ")}${observed ? "" : " → application change unverified"}`,
|
|
126
144
|
...(navigation?.title ? { title: navigation.title } : {}),
|
|
127
145
|
...(navigation?.url ? { url: navigation.url } : {}),
|
|
128
146
|
};
|
|
@@ -132,7 +150,7 @@ function stripNavigationSummary(data) {
|
|
|
132
150
|
return rest;
|
|
133
151
|
}
|
|
134
152
|
export function formatNavigationActionResult(data) {
|
|
135
|
-
const actionData = stripNavigationSummary(data);
|
|
153
|
+
const actionData = omitUpstreamLifecycle(stripNavigationSummary(data));
|
|
136
154
|
const lines = [];
|
|
137
155
|
if (typeof actionData.clicked === "string" || typeof actionData.clicked === "boolean") {
|
|
138
156
|
lines.push(`Clicked: ${String(actionData.clicked)}`);
|