pi-agent-browser-native 0.2.33 → 0.2.34
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 +19 -0
- package/README.md +34 -4
- package/docs/ARCHITECTURE.md +6 -0
- package/docs/COMMAND_REFERENCE.md +28 -5
- package/docs/RELEASE.md +11 -3
- package/docs/SUPPORT_MATRIX.md +8 -6
- package/docs/TOOL_CONTRACT.md +61 -7
- package/extensions/agent-browser/index.ts +2 -1
- package/extensions/agent-browser/lib/input-modes/job.ts +62 -0
- package/extensions/agent-browser/lib/input-modes/params.ts +4 -4
- package/extensions/agent-browser/lib/input-modes.ts +3 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +67 -1
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +26 -1
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +34 -7
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +6 -0
- package/extensions/agent-browser/lib/playbook.ts +17 -16
- package/extensions/agent-browser/lib/results/categories.ts +1 -1
- package/extensions/agent-browser/lib/results/presentation/registry.ts +34 -6
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +133 -0
- package/extensions/agent-browser/lib/results/presentation.ts +11 -6
- package/package.json +1 -1
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
export { AGENT_BROWSER_PARAMS } from "./input-modes/params.js";
|
|
8
8
|
export {
|
|
9
9
|
analyzeQaPresetResults,
|
|
10
|
+
buildQaCompactPassText,
|
|
10
11
|
compileAgentBrowserJob,
|
|
11
12
|
compileAgentBrowserQaPreset,
|
|
13
|
+
extractQaPageContext,
|
|
14
|
+
isHttpOrHttpsUrl,
|
|
12
15
|
} from "./input-modes/job.js";
|
|
13
16
|
export {
|
|
14
17
|
analyzeNetworkSourceLookupResults,
|
|
@@ -4,9 +4,10 @@ import { delimiter, isAbsolute, join, resolve } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import type { ElectronLaunchRecord } from "../../electron/launch.js";
|
|
6
6
|
import type { AgentBrowserSourceLookupAnalysis, CompiledAgentBrowserJob, CompiledAgentBrowserSemanticAction } from "../../input-modes.js";
|
|
7
|
+
import { isHttpOrHttpsUrl } from "../../input-modes/job.js";
|
|
7
8
|
import type { AgentBrowserNextAction } from "../../results.js";
|
|
8
9
|
import { formatSessionArtifactRetentionSummary } from "../../results/artifact-manifest.js";
|
|
9
|
-
import { withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
10
|
+
import { buildNextToolAction, withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
10
11
|
import { buildVisibleRefFallbackDiagnosticFromSnapshot, getVisibleRefFallbackTarget, type VisibleRefFallbackDiagnostic } from "../../results/selector-recovery.js";
|
|
11
12
|
import { extractRefSnapshotFromData, normalizeComparableUrl, type SessionTabTarget } from "../../session-page-state.js";
|
|
12
13
|
import { redactInvocationArgs, redactSensitiveText, type CommandInfo } from "../../runtime.js";
|
|
@@ -29,6 +30,7 @@ import type {
|
|
|
29
30
|
NavigationSummary,
|
|
30
31
|
OverlayBlockerCandidate,
|
|
31
32
|
OverlayBlockerDiagnostic,
|
|
33
|
+
QaAttachedPreconditionFailure,
|
|
32
34
|
QaAttachedTarget,
|
|
33
35
|
RecordingDependencyWarning,
|
|
34
36
|
ScrollNoopDiagnostic,
|
|
@@ -491,6 +493,18 @@ async function collectManagedSessionCommandData(options: { args: string[]; cwd:
|
|
|
491
493
|
try { return { data: await runSessionCommandData(options) }; } catch (error) { return { error: error instanceof Error ? error.message : String(error) }; }
|
|
492
494
|
}
|
|
493
495
|
|
|
496
|
+
async function collectElectronManagedSessionUrl(options: { cwd: string; sessionName: string; signal?: AbortSignal; timeoutMs?: number }): Promise<{ error?: string; url?: string }> {
|
|
497
|
+
const urlResult = await collectManagedSessionCommandData({
|
|
498
|
+
args: ["get", "url"],
|
|
499
|
+
cwd: options.cwd,
|
|
500
|
+
sessionName: options.sessionName,
|
|
501
|
+
signal: options.signal,
|
|
502
|
+
timeoutMs: options.timeoutMs,
|
|
503
|
+
});
|
|
504
|
+
const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
|
|
505
|
+
return urlResult.error ? { error: urlResult.error } : { url };
|
|
506
|
+
}
|
|
507
|
+
|
|
494
508
|
async function collectElectronManagedSessionTarget(options: { cwd: string; sessionName?: string; signal?: AbortSignal; timeoutMs?: number }): Promise<QaAttachedTarget | undefined> {
|
|
495
509
|
if (!options.sessionName) return undefined;
|
|
496
510
|
const [titleResult, urlResult] = await Promise.all([
|
|
@@ -514,6 +528,58 @@ export function formatQaAttachedTargetText(target: QaAttachedTarget | undefined)
|
|
|
514
528
|
return ["QA attached target:", target.sessionName, target.title, target.url].filter((part): part is string => typeof part === "string" && part.length > 0).join(" — ");
|
|
515
529
|
}
|
|
516
530
|
|
|
531
|
+
export function buildQaAttachedRecoveryNextActions(sessionName: string | undefined): AgentBrowserNextAction[] {
|
|
532
|
+
const sessionArgs = (args: string[]) => withOptionalSessionArgs(sessionName, args);
|
|
533
|
+
return [
|
|
534
|
+
buildNextToolAction({
|
|
535
|
+
args: sessionArgs(["tab", "list"]),
|
|
536
|
+
id: "list-tabs-before-qa-attached",
|
|
537
|
+
reason: "Inspect the connected session tabs before retrying qa.attached.",
|
|
538
|
+
safety: "Read-only tab listing for the attached session.",
|
|
539
|
+
}),
|
|
540
|
+
buildNextToolAction({
|
|
541
|
+
args: sessionArgs(["snapshot", "-i"]),
|
|
542
|
+
id: "snapshot-before-qa-attached",
|
|
543
|
+
reason: "Capture interactive refs on the active http(s) page before retrying qa.attached.",
|
|
544
|
+
safety: "Read-only snapshot; confirms a renderable page is selected.",
|
|
545
|
+
}),
|
|
546
|
+
];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export async function validateQaAttachedPrecondition(options: {
|
|
550
|
+
cwd: string;
|
|
551
|
+
sessionName?: string;
|
|
552
|
+
signal?: AbortSignal;
|
|
553
|
+
}): Promise<QaAttachedPreconditionFailure | undefined> {
|
|
554
|
+
if (!options.sessionName) {
|
|
555
|
+
return {
|
|
556
|
+
error: "qa.attached requires an active attached session with a resolvable session name.",
|
|
557
|
+
nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const urlProbe = await collectElectronManagedSessionUrl({ cwd: options.cwd, sessionName: options.sessionName, signal: options.signal });
|
|
561
|
+
if (urlProbe.error) {
|
|
562
|
+
return {
|
|
563
|
+
error: `qa.attached could not read the attached session URL: ${urlProbe.error}. Run tab list or snapshot -i before retrying qa.attached.`,
|
|
564
|
+
nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const url = urlProbe.url?.trim();
|
|
568
|
+
if (!url) {
|
|
569
|
+
return {
|
|
570
|
+
error: "qa.attached requires an attached session with a readable http(s) page URL. Run tab list, select a stable tab, then snapshot -i before retrying.",
|
|
571
|
+
nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
if (!isHttpOrHttpsUrl(url)) {
|
|
575
|
+
return {
|
|
576
|
+
error: `qa.attached requires an http(s) page URL; the current attached URL is "${url}". Use tab list and snapshot -i to recover a web surface before retrying.`,
|
|
577
|
+
nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
|
|
517
583
|
function getTopLevelFillInvocation(commandTokens: string[]): { expected: string; selector: string } | undefined {
|
|
518
584
|
if (commandTokens[0] !== "fill" || commandTokens.length < 3) return undefined;
|
|
519
585
|
const selector = commandTokens[1];
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
shouldPinSessionTabForCommand,
|
|
28
28
|
} from "./session-state.js";
|
|
29
29
|
import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
|
|
30
|
-
import { collectScrollPositionSnapshot } from "./diagnostics.js";
|
|
30
|
+
import { collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
|
|
31
31
|
import type {
|
|
32
32
|
BrowserRunInputFields,
|
|
33
33
|
BrowserRunOptions,
|
|
@@ -555,6 +555,31 @@ export async function prepareBrowserRun(options: BrowserRunOptions): Promise<Pre
|
|
|
555
555
|
} };
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
+
if (compiledQaPreset?.checks.attached) {
|
|
559
|
+
const qaAttachedPrecondition = await validateQaAttachedPrecondition({
|
|
560
|
+
cwd,
|
|
561
|
+
sessionName: executionPlan.sessionName,
|
|
562
|
+
signal,
|
|
563
|
+
});
|
|
564
|
+
if (qaAttachedPrecondition) {
|
|
565
|
+
return { kind: "early-result", statePatch, result: {
|
|
566
|
+
content: [{ type: "text", text: qaAttachedPrecondition.error }],
|
|
567
|
+
details: {
|
|
568
|
+
args: redactedArgs,
|
|
569
|
+
compiledQaPreset: redactedCompiledQaPreset,
|
|
570
|
+
compatibilityWorkaround,
|
|
571
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
572
|
+
nextActions: qaAttachedPrecondition.nextActions,
|
|
573
|
+
sessionMode,
|
|
574
|
+
...buildAgentBrowserResultCategoryDetails({ args: redactedEffectiveArgs, command: executionPlan.commandInfo.command, errorText: qaAttachedPrecondition.error, succeeded: false, validationError: qaAttachedPrecondition.error }),
|
|
575
|
+
validationError: qaAttachedPrecondition.error,
|
|
576
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
577
|
+
},
|
|
578
|
+
isError: true,
|
|
579
|
+
} };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
558
583
|
let pinnedBatchUnwrapMode: PreparedBrowserRun["pinnedBatchUnwrapMode"];
|
|
559
584
|
let includePinnedNavigationSummary = false;
|
|
560
585
|
let sessionTabCorrection: PreparedBrowserRun["sessionTabCorrection"];
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
analyzeNetworkSourceLookupResults,
|
|
7
7
|
analyzeQaPresetResults,
|
|
8
8
|
analyzeSourceLookupResults,
|
|
9
|
+
buildQaCompactPassText,
|
|
10
|
+
extractQaPageContext,
|
|
9
11
|
redactNetworkSourceLookupAnalysis,
|
|
10
12
|
} from "../../input-modes.js";
|
|
11
13
|
import {
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
mergeSessionArtifactManifest,
|
|
21
23
|
} from "../../results/artifact-manifest.js";
|
|
22
24
|
import type { SessionArtifactManifest } from "../../results/contracts.js";
|
|
25
|
+
import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
|
|
23
26
|
import {
|
|
24
27
|
commandExplicitlyTargetsAboutBlank,
|
|
25
28
|
deriveSessionTabTarget,
|
|
@@ -205,7 +208,12 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
205
208
|
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
206
209
|
updateTraceOwnerState({ command: prepared.executionPlan.commandInfo.command, sessionName: prepared.executionPlan.sessionName, subcommand: prepared.executionPlan.commandInfo.subcommand, succeeded, traceOwners });
|
|
207
210
|
|
|
208
|
-
if (
|
|
211
|
+
if (
|
|
212
|
+
succeeded &&
|
|
213
|
+
!navigationSummary &&
|
|
214
|
+
(shouldCaptureNavigationSummary(prepared.executionPlan.commandInfo.command, presentationEnvelope?.data) ||
|
|
215
|
+
shouldCaptureSemanticActionNavigationSummary(prepared.compiledSemanticAction, presentationEnvelope?.data))
|
|
216
|
+
) {
|
|
209
217
|
navigationSummary = await collectNavigationSummary({ cwd, sessionName: prepared.executionPlan.sessionName, signal });
|
|
210
218
|
}
|
|
211
219
|
if (navigationSummary && presentationEnvelope) presentationEnvelope = { ...presentationEnvelope, data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary) };
|
|
@@ -341,7 +349,7 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
341
349
|
}
|
|
342
350
|
|
|
343
351
|
const errorText = getAgentBrowserErrorText({ aborted: processResult.aborted, command: prepared.executionPlan.commandInfo.command, effectiveArgs: prepared.redactedProcessArgs, envelope: presentationEnvelope, exitCode: processResult.exitCode, parseError, plainTextInspection, staleRefArgs: getStaleRefArgs(prepared.commandTokens, prepared.runtimeToolStdin), spawnError: processResult.spawnError, stderr: processResult.stderr, timedOut: processResult.timedOut, timeoutMs: processResult.timeoutMs, wrapperRecoveryHint: buildWrapperRecoveryHint({ pinnedBatchUnwrapMode: prepared.pinnedBatchUnwrapMode, sessionTabCorrection }) });
|
|
344
|
-
const presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text" as const, 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, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, cwd, envelope: presentationEnvelope, errorText, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
|
|
352
|
+
const presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text" as const, 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, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd, envelope: presentationEnvelope, errorText, persistentArtifactStore, sessionName: prepared.executionPlan.sessionName });
|
|
345
353
|
if (parseFailureOutput.artifactManifest) { presentation.artifactManifest = parseFailureOutput.artifactManifest; presentation.artifactRetentionSummary = parseFailureOutput.artifactRetentionSummary; }
|
|
346
354
|
if (parseFailureOutput.fullOutputPath || parseFailureOutput.fullOutputUnavailable) {
|
|
347
355
|
const existingText = presentation.content[0]?.type === "text" ? presentation.content[0].text : "";
|
|
@@ -351,7 +359,9 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
351
359
|
}
|
|
352
360
|
if (presentation.artifactManifest) artifactManifest = presentation.artifactManifest;
|
|
353
361
|
const qaPreset = prepared.compiledQaPreset ? analyzeQaPresetResults(presentationEnvelope?.data) : undefined;
|
|
354
|
-
|
|
362
|
+
let qaAttachedTarget = prepared.compiledQaPreset?.checks.attached
|
|
363
|
+
? await collectQaAttachedTarget({ currentTarget: currentSessionTabTarget ?? prepared.priorSessionTabTarget, cwd, sessionName: prepared.executionPlan.sessionName, signal })
|
|
364
|
+
: undefined;
|
|
355
365
|
const sourceLookupElectronContext = prepared.compiledSourceLookup ? getSourceLookupElectronContext({ currentTarget: currentSessionTabTarget, electronLaunchRecords, priorTarget: prepared.priorSessionTabTarget, sessionName: prepared.executionPlan.sessionName }) : undefined;
|
|
356
366
|
const sourceLookup = prepared.compiledSourceLookup ? await analyzeSourceLookupResults(presentationEnvelope?.data, prepared.compiledSourceLookup, cwd, { electronContext: sourceLookupElectronContext, workspaceRoot: cwd }) : undefined;
|
|
357
367
|
const networkSourceLookup = prepared.compiledNetworkSourceLookup ? redactNetworkSourceLookupAnalysis(await analyzeNetworkSourceLookupResults(presentationEnvelope?.data, prepared.compiledNetworkSourceLookup, cwd)) : undefined;
|
|
@@ -359,15 +369,32 @@ export async function processBrowserOutput(input: ProcessBrowserOutputInput): Pr
|
|
|
359
369
|
else if (networkSourceLookup) presentation.content.unshift({ type: "text", text: networkSourceLookup.summary });
|
|
360
370
|
if (sourceLookup && presentation.content[0]?.type === "text") presentation.content[0] = { ...presentation.content[0], text: `${sourceLookup.summary}\n\n${presentation.content[0].text}` };
|
|
361
371
|
else if (sourceLookup) presentation.content.unshift({ type: "text", text: sourceLookup.summary });
|
|
362
|
-
if (qaPreset &&
|
|
363
|
-
|
|
372
|
+
if (qaPreset && !qaPreset.passed) {
|
|
373
|
+
succeeded = false;
|
|
374
|
+
presentation.failureCategory = "qa-failure";
|
|
364
375
|
presentation.summary = qaPreset.summary;
|
|
365
376
|
if (presentation.content[0]?.type === "text") presentation.content[0] = { ...presentation.content[0], text: `${qaPreset.summary}\n\n${presentation.content[0].text}` };
|
|
366
377
|
else presentation.content.unshift({ type: "text", text: qaPreset.summary });
|
|
378
|
+
} else if (qaPreset?.passed && prepared.compiledQaPreset) {
|
|
379
|
+
const compactText = buildQaCompactPassText({
|
|
380
|
+
artifactVerification: presentation.artifactVerification,
|
|
381
|
+
batchStepCount: presentation.batchSteps?.length ?? prepared.compiledQaPreset.steps.length,
|
|
382
|
+
checks: prepared.compiledQaPreset.checks,
|
|
383
|
+
page: extractQaPageContext({
|
|
384
|
+
attachedTarget: qaAttachedTarget,
|
|
385
|
+
batchData: presentationEnvelope?.data,
|
|
386
|
+
compiled: prepared.compiledQaPreset,
|
|
387
|
+
}),
|
|
388
|
+
qaPreset,
|
|
389
|
+
});
|
|
390
|
+
presentation.summary = qaPreset.summary;
|
|
391
|
+
const nonTextContent = presentation.content.filter((item) => item.type !== "text");
|
|
392
|
+
presentation.content = [{ type: "text", text: compactText }, ...nonTextContent];
|
|
367
393
|
}
|
|
368
394
|
const qaAttachedTargetText = formatQaAttachedTargetText(qaAttachedTarget);
|
|
369
|
-
|
|
370
|
-
|
|
395
|
+
const skipAttachedTargetBanner = qaPreset?.passed && prepared.compiledQaPreset?.checks.attached;
|
|
396
|
+
if (!skipAttachedTargetBanner && qaAttachedTargetText && presentation.content[0]?.type === "text") presentation.content[0] = { ...presentation.content[0], text: `${qaAttachedTargetText}\n\n${presentation.content[0].text}` };
|
|
397
|
+
else if (!skipAttachedTargetBanner && qaAttachedTargetText) presentation.content.unshift({ type: "text", text: qaAttachedTargetText });
|
|
371
398
|
if (managedSessionOutcome && managedSessionOutcome.succeeded !== succeeded) managedSessionOutcome = { ...managedSessionOutcome, succeeded };
|
|
372
399
|
const evalStdinHint = getEvalStdinHint({ command: prepared.executionPlan.commandInfo.command, data: presentationEnvelope?.data, stdin: prepared.runtimeToolStdin });
|
|
373
400
|
const resultArtifactManifest = presentation.artifactManifest ?? artifactManifest;
|
|
@@ -140,11 +140,17 @@ export interface ElectronBroadGetTextScopeDiagnostic {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export interface QaAttachedTarget {
|
|
143
|
+
error?: string;
|
|
143
144
|
sessionName: string;
|
|
144
145
|
title?: string;
|
|
145
146
|
url?: string;
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
export interface QaAttachedPreconditionFailure {
|
|
150
|
+
error: string;
|
|
151
|
+
nextActions: AgentBrowserNextAction[];
|
|
152
|
+
}
|
|
153
|
+
|
|
148
154
|
export interface TimeoutArtifactEvidence {
|
|
149
155
|
absolutePath: string;
|
|
150
156
|
exists: boolean;
|
|
@@ -18,11 +18,12 @@ export function buildInstalledDocsGuideline(paths: { readmePath: string; command
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const QUICK_START_GUIDELINES = [
|
|
21
|
-
"Quick start mental model: use exactly one of args (exact agent-browser CLI args after the binary), semanticAction (a thin shorthand compiled to find argv for locator actions or select argv for native dropdowns), job (a constrained short-workflow schema compiled to batch), qa (a lightweight QA preset built on job/batch, including qa.attached for current sessions), electron (desktop Electron list/launch/status/cleanup/probe), or the experimental sourceLookup / networkSourceLookup helpers (each compiled to batch); stdin is only for batch, eval --stdin, auth save --password-stdin, and wrapper-generated batch stdin from job, qa, sourceLookup, or networkSourceLookup, and is rejected with electron; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new --profile, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device state.",
|
|
21
|
+
"Quick start mental model: use exactly one of args (exact agent-browser CLI args after the binary), semanticAction (a thin shorthand compiled to find argv for locator actions or select argv for native dropdowns), job (a constrained short-workflow schema compiled to batch), qa (a lightweight QA preset built on job/batch, including qa.attached for current sessions), electron (desktop Electron list/launch/status/cleanup/probe), or the experimental sourceLookup / networkSourceLookup helpers (candidates only; each compiled to batch); stdin is only for batch, eval --stdin, auth save --password-stdin, and wrapper-generated batch stdin from job, qa, sourceLookup, or networkSourceLookup, and is rejected with electron; sessionMode=fresh switches the extension-managed pi-scoped session to a fresh upstream launch when you need new --profile, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device state. Do not pass --json in args; the wrapper injects it.",
|
|
22
22
|
"There is no first-class reusable named browser recipe runtime above top-level job, the qa preset, and raw batch stdin; keep recurring flows in documentation examples or those inputs (closed RQ-0068; see docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet).",
|
|
23
|
-
"Common first calls: { args: [\"open\", \"
|
|
23
|
+
"Common first calls (first-call recipe): { args: [\"open\", \"<url>\"] } → { args: [\"snapshot\", \"-i\"] } → { args: [\"click\", \"@eN\"] } or { args: [\"fill\", \"@eN\", \"<text>\"] } using @refs and visible labels from that snapshot, then { args: [\"snapshot\", \"-i\"] } after navigation or DOM changes. On https://example.com/ the main link label is Learn more (use exact snapshot text, not guessed link copy).",
|
|
24
24
|
"Locator-first clicks/fills and native select changes without hand-building argv: { semanticAction: { action: \"click\", locator: \"text\", value: \"Close\" } }, { semanticAction: { action: \"fill\", locator: \"label\", value: \"Email\", text: \"user@example.com\" } }, or { semanticAction: { action: \"select\", selector: \"#flavor\", value: \"chocolate\" } }; add semanticAction.session when targeting a named upstream browser session; details.compiledSemanticAction shows the semantic target, while details.effectiveArgs may show a resolved current @ref for active-session role/name click/check/uncheck actions to avoid hidden duplicate matches; selector-not-found failures may append bounded click try-*-candidate next actions or, for fill misses with current editable refs, details.richInputRecovery with focus/click actions that do not copy fill text; stale-ref failures can return retry-semantic-action-after-stale-ref for compiled find actions when retry safety is provable.",
|
|
25
|
-
|
|
25
|
+
`Common advanced calls: { args: ["batch"], stdin: "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }, { job: { steps: [{ action: "open", url: "https://example.com" }, { action: "assertText", text: "Example Domain" }, { action: "screenshot", path: ".dogfood/example.png" }] } }, { qa: { url: "https://example.com", expectedText: "Example Domain", screenshotPath: ".dogfood/qa-example.png" } } (example.com smoke only; elsewhere match exact visible text from snapshot -i), { electron: { action: "list", query: "code" } }, { electron: { action: "launch", appName: "Visual Studio Code", handoff: "snapshot" } }, { electron: { action: "probe" } }, { qa: { attached: true, expectedText: "Explorer" } }, { args: ["eval", "--stdin"], stdin: "document.title" }, { args: ["auth", "save", "name", "--password-stdin"], stdin: "<password from user-approved secret source>" }, { args: ["--profile", "Default", "open", "https://example.com/account"], sessionMode: "fresh" }, and { args: ["open", "--enable", "react-devtools", "https://example.com"], sessionMode: "fresh" }. For app pages with a native dropdown, job steps can include { action: "select", selector: "#flavor", value: "chocolate" } before the dependent assertion.`,
|
|
26
|
+
"Constrained job navigation is explicit only: click (and select/submit flows that may navigate) does not prove the next page loaded; add assertUrl and/or assertText after navigation-prone steps before screenshot or later interactions. Example: { job: { steps: [{ action: \"open\", url: \"https://shop.example/checkout\" }, { action: \"fill\", selector: \"#email\", text: \"user@example.com\" }, { action: \"click\", selector: \"#continue\" }, { action: \"assertUrl\", url: \"**/shipping\" }, { action: \"assertText\", text: \"Shipping address\" }, { action: \"screenshot\", path: \".dogfood/shipping.png\" }] } }. Top-level click may add navigationSummary hints, but job never auto-inserts post-click asserts.",
|
|
26
27
|
"High-value command reference: select <selector> <value...> changes native dropdown values; download <selector> <path> saves a file triggered by a click; get title/url/text/html/value/attr/count reads page state; screenshot [path] captures an image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab; react tree/inspect/renders/suspense introspect React after --enable react-devtools; vitals [url] measures Core Web Vitals; pushstate <url> performs SPA navigation.",
|
|
27
28
|
"For artifact-producing commands, read the visible artifact block and details.artifactVerification before using files: check requested path, absolute path, existence, size bytes, artifact kind, optional mediaType, status, optional limitation, and verified/missing/pending/unverified counts. details.artifacts contains per-file metadata. Browser close does not delete explicit saved files; if close reports details.artifactCleanup, use host file tools to remove paths listed in explicitArtifactPaths (when non-empty) after inspection. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step.",
|
|
28
29
|
"When details.nextActions is present, prefer those exact native agent_browser follow-up payloads over prose guidance; they may include args, stdin, sessionMode, networkSourceLookup, safety notes, or artifactPath for saved files.",
|
|
@@ -34,7 +35,7 @@ export const BRAVE_SEARCH_PROMPT_GUIDELINE =
|
|
|
34
35
|
export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
35
36
|
"Standard workflow: open the page, snapshot -i, interact using current @refs from that snapshot, and re-snapshot after navigation, scrolling, rerendering, or other major DOM changes because refs are page-scoped; the wrapper fails mutation-prone stale/recycled refs before upstream can silently target a different current-page element.",
|
|
36
37
|
"For ordinary forms from one snapshot, batch multiple fill @refs before the submit/click step to avoid serial tool calls; if a fill may autosubmit, navigate, or rerender later fields, split the flow and refresh refs first.",
|
|
37
|
-
"When snapshot -i compacts because the tree is oversized, scan visible output for Omitted high-value controls and optional details.data.highValueControlRefIds before opening the spill file: those list bounded searchboxes, textboxes, comboboxes, buttons, tabs, checkboxes, radios, options, and menuitems that did not fit the key/other ref previews.",
|
|
38
|
+
"Snapshot choice: prefer snapshot -i for routine clicks/fills (interactive @refs, main-content-first). Use snapshot --compact when you need a denser same-page tree without full spill; use full snapshot (no -i) only when you need the complete accessibility tree. Re-snapshot after navigation or major DOM changes. When snapshot -i compacts because the tree is oversized, scan visible output for Omitted high-value controls and optional details.data.highValueControlRefIds before opening the spill file: those list bounded searchboxes, textboxes, comboboxes, buttons, tabs, checkboxes, radios, options, and menuitems that did not fit the key/other ref previews.",
|
|
38
39
|
"When a visible text or accessible-name target should survive ref churn, prefer find locators such as role, text, label, placeholder, alt, title, or testid with the intended action instead of guessing a CSS selector.",
|
|
39
40
|
"For desktop or host-controlled rich inputs, if semanticAction fill misses, refresh refs and prefer a current editable @ref from details.richInputRecovery or the latest snapshot; focus or click that ref, then use keyboard inserttext or keyboard type with the intended text. Do not auto-submit with Enter or a submit button unless the user flow explicitly calls for it.",
|
|
40
41
|
"Do not assume Playwright selector dialects such as text=Close or button:has-text('Close') are supported wrapper syntax unless current upstream agent-browser behavior has been verified.",
|
|
@@ -59,13 +60,16 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
|
59
60
|
"When using eval --stdin for extraction, return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); if a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. If get text on a CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
|
|
60
61
|
"When details.pageChangeSummary is present, use changeType and summary as a compact signal for navigation, DOM mutation, confirmations, or artifacts; when nextActionIds is set, match those ids to entries in details.nextActions (or per-step nextActions inside batch) for concrete follow-up payloads instead of inferring from prose alone. If a no-navigation click surfaces details.overlayBlockers, inspect the fresh snapshot evidence before using a close/dismiss candidate nextAction; ordinary page chrome without dialog/alertdialog evidence should not trigger this diagnostic.",
|
|
61
62
|
"When commands save or spill files (screenshots, downloads, PDFs, traces, recordings, HAR, large snapshot spills), use the user's exact requested paths when given and treat paths as provisional until details.artifactVerification shows every row verified: branch on missingCount, pendingCount, unverifiedCount, per-entry state, and optional limitation before downstream file use or PASS/FAIL reporting.",
|
|
63
|
+
"For evidence-only screenshots, QA captures, or other audit artifacts, save to an explicit path and branch on details.artifactVerification plus details.artifacts before reporting PASS/FAIL; do not require vision review of inline image attachments unless the user asked for visual inspection.",
|
|
64
|
+
"Respect explicit user stop boundaries: if the user says to stop before order/post/purchase/submit, do not click that final action.",
|
|
65
|
+
"Successful record stop needs ffmpeg on PATH; the wrapper may warn after record start when ffmpeg is missing.",
|
|
62
66
|
"Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary.",
|
|
63
67
|
] as const;
|
|
64
68
|
|
|
65
69
|
export const TOOL_PROMPT_GUIDELINES_SUFFIX = [
|
|
66
70
|
"Prefer agent_browser over bash for opening sites, reading docs on the web, clicking, filling, screenshots, eval, and batch workflows.",
|
|
67
71
|
"Do not fall back to osascript, AppleScript, or generic browser-driving bash commands when agent_browser can do the job.",
|
|
68
|
-
"Pass exact agent-browser CLI arguments in args when you are not using semanticAction, job, or qa, excluding the binary name.",
|
|
72
|
+
"Pass exact agent-browser CLI arguments in args when you are not using semanticAction, job, or qa, excluding the binary name and --json (the wrapper injects --json automatically).",
|
|
69
73
|
"Use stdin only for eval --stdin, batch, auth save --password-stdin, or wrapper-generated job/qa batches instead of shell heredocs or password args; other command/stdin combinations are rejected before launch.",
|
|
70
74
|
"Let the extension-managed session handle the common path unless you explicitly need a fresh launch for upstream flags like --profile, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device.",
|
|
71
75
|
"Use sessionMode=fresh when switching from an existing implicit session to a new profile/debug/init-script/provider launch without inventing a fixed explicit session name; later auto calls will follow that new session.",
|
|
@@ -91,17 +95,14 @@ export function buildSharedBrowserPlaybookGuidelines(options: { includeBraveSear
|
|
|
91
95
|
];
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"For signed-in/account-specific content
|
|
99
|
-
"For
|
|
100
|
-
"When details.nextActions is present, prefer
|
|
101
|
-
"For
|
|
102
|
-
"For dashboards, verify scroll with screenshot/snapshot; if nothing moved, use scrollintoview <@ref> or target the real scroll region. Native selects need select/semanticAction/job select; custom comboboxes may need re-snapshot, type, Enter/arrows, or visible option refs.",
|
|
103
|
-
"For extraction, prefer get title/url/text/html/value/attr/count or eval --stdin with a plain expression; do not rely on console.log. When reading several known refs/selectors, use batch JSON-array stdin (for example [[\"get\",\"text\",\"@e1\"]]) or eval --stdin instead of many serial gets. If selector visibility warnings appear, prefer visible @refs or nextActions.",
|
|
104
|
-
"For non-core debugging, pass upstream commands through args: network, diff, trace/profiler/record, console/errors, stream, dashboard, chat, react, vitals, pushstate, dialog, frame, tab.",
|
|
98
|
+
/** Tier A: always-on tool promptGuidelines (keep small; Tier B lives in SHARED_BROWSER_PLAYBOOK_GUIDELINES and docs). */
|
|
99
|
+
export const RUNTIME_PROMPT_GUIDELINES = [
|
|
100
|
+
"Use exactly one input mode: args (open→snapshot -i→@refs), semanticAction, job, qa, sourceLookup/networkSourceLookup (candidate hints), or electron. stdin only for batch/eval/auth or wrapper batch; electron rejects stdin. Do not pass --json in args; wrapper injects it.",
|
|
101
|
+
"Common flow: open, snapshot -i, use current @refs or semanticAction, then re-snapshot after navigation/scroll/rerender/DOM change. Batch same-snapshot fills unless they may submit/navigate/rerender. Respect explicit stop boundaries: if the user says stop before order/post/purchase/submit, do not click the final action.",
|
|
102
|
+
"Use sessionMode=fresh for launch-scoped flags on an active implicit session. For signed-in/account-specific content, start with --profile Default plus sessionMode=fresh unless asked otherwise; visible content is model-visible.",
|
|
103
|
+
"For artifacts, save the exact user path and check details.artifactVerification/details.artifacts before claiming success. record stop needs ffmpeg on PATH; close does not delete saved files; \"waited\":\"timeout\" is not proof.",
|
|
104
|
+
"When details.nextActions is present, prefer exact payloads over prose/guessed selectors. For dense snapshots, check Omitted high-value controls/details.data.highValueControlRefIds. For dashboards, verify scroll with screenshot/snapshot; if nothing moved, target the real scroll region.",
|
|
105
|
+
"For extraction, prefer get title/url/text/html/value/attr/count or eval --stdin with plain expression, not console.log. Batch three or more known refs/selectors (e.g. [[\"get\",\"text\",\"@e1\"],[\"get\",\"text\",\"@e2\"]]); selector visibility warnings → visible @refs/nextActions.",
|
|
105
106
|
] as const;
|
|
106
107
|
|
|
107
108
|
export function buildToolPromptGuidelines(options: { includeBraveSearch: boolean; docs?: { readmePath: string; commandReferencePath: string; toolContractPath: string } }): string[] {
|
|
@@ -52,6 +52,7 @@ export function classifyAgentBrowserFailureCategory(options: {
|
|
|
52
52
|
if (/aborted/i.test(text)) return "aborted";
|
|
53
53
|
if (/policy[- ]blocked|blocked by caller policy|caller deny policy|caller allow policy/i.test(text)) return "policy-blocked";
|
|
54
54
|
if (/cleanup failed|cleanup.*partial|partial cleanup|remaining resources/i.test(text)) return "cleanup-failed";
|
|
55
|
+
if (options.validationError) return "validation-error";
|
|
55
56
|
if (options.tabDrift || /could not re-select the intended tab|about:blank|selected tab looks wrong|tab drift|tab.*wrong/i.test(text)) return "tab-drift";
|
|
56
57
|
if (/\bUnknown ref\b|\bstale ref\b|@ref may be stale|\bref\b.*\b(?:not found|missing|expired)\b/i.test(text)) return "stale-ref";
|
|
57
58
|
if (usedRef && /could not locate element|element not found|no element/i.test(text)) return "stale-ref";
|
|
@@ -72,7 +73,6 @@ export function classifyAgentBrowserFailureCategory(options: {
|
|
|
72
73
|
if ((command === "download" || text.includes("wait --download") || /\bdownload\b/i.test(text)) && /missing|not verified|not found|failed|timeout|timed out/i.test(text)) {
|
|
73
74
|
return "download-not-verified";
|
|
74
75
|
}
|
|
75
|
-
if (options.validationError) return "validation-error";
|
|
76
76
|
return "upstream-error";
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CompiledAgentBrowserSemanticAction } from "../../input-modes/types.js";
|
|
1
2
|
import { isRecord } from "../../parsing.js";
|
|
2
3
|
import type { CommandInfo } from "../../runtime.js";
|
|
3
4
|
import { detectConfirmationRequired, type ConfirmationRequiredPresentation } from "../confirmation.js";
|
|
@@ -14,6 +15,11 @@ import {
|
|
|
14
15
|
getNavigationSummary,
|
|
15
16
|
isNavigationObservableCommand,
|
|
16
17
|
} from "./navigation.js";
|
|
18
|
+
import {
|
|
19
|
+
formatSemanticActionPresentationSummary,
|
|
20
|
+
formatSemanticActionPresentationText,
|
|
21
|
+
resolvePresentationCommandInfo,
|
|
22
|
+
} from "./semantic-action.js";
|
|
17
23
|
|
|
18
24
|
function getPageSummary(data: Record<string, unknown>): string | undefined {
|
|
19
25
|
const title = typeof data.title === "string" ? data.title : undefined;
|
|
@@ -91,20 +97,32 @@ function formatBatchSummary(data: unknown): string | undefined {
|
|
|
91
97
|
: `Batch failed: ${successCount}/${data.length} succeeded`;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
|
-
export function formatPresentationSummary(
|
|
100
|
+
export function formatPresentationSummary(
|
|
101
|
+
commandInfo: CommandInfo,
|
|
102
|
+
data: unknown,
|
|
103
|
+
compiledSemanticAction?: CompiledAgentBrowserSemanticAction,
|
|
104
|
+
): string {
|
|
95
105
|
const confirmationRequired = detectConfirmationRequired(data);
|
|
96
106
|
if (confirmationRequired) return formatConfirmationRequiredSummary(confirmationRequired);
|
|
97
107
|
|
|
108
|
+
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfo, compiledSemanticAction);
|
|
109
|
+
|
|
98
110
|
if (commandInfo.command === "batch") {
|
|
99
111
|
const batchSummary = formatBatchSummary(data);
|
|
100
112
|
if (batchSummary) return batchSummary;
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
if (isRecord(data)) {
|
|
116
|
+
if (compiledSemanticAction) {
|
|
117
|
+
const semanticSummary = formatSemanticActionPresentationSummary(compiledSemanticAction, data);
|
|
118
|
+
if (semanticSummary) return semanticSummary;
|
|
119
|
+
}
|
|
104
120
|
const navigationSummary = getNavigationSummary(data);
|
|
105
|
-
if (navigationSummary && isNavigationObservableCommand(
|
|
121
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command)) {
|
|
106
122
|
const navigationText = formatNavigationSummary(navigationSummary);
|
|
107
|
-
if (navigationText)
|
|
123
|
+
if (navigationText) {
|
|
124
|
+
return `${presentationCommandInfo.command ?? "navigation"} → ${navigationText.split("\n", 1)[0] ?? navigationText}`;
|
|
125
|
+
}
|
|
108
126
|
}
|
|
109
127
|
}
|
|
110
128
|
|
|
@@ -121,10 +139,14 @@ export function formatPresentationSummary(commandInfo: CommandInfo, data: unknow
|
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
if (typeof data === "string" && data.length > 0) return data.split("\n", 1)[0] ?? data;
|
|
124
|
-
return `${commandInfo.command ?? "agent-browser"} completed`;
|
|
142
|
+
return `${presentationCommandInfo.command ?? commandInfo.command ?? "agent-browser"} completed`;
|
|
125
143
|
}
|
|
126
144
|
|
|
127
|
-
export function formatPresentationContentText(
|
|
145
|
+
export function formatPresentationContentText(
|
|
146
|
+
commandInfo: CommandInfo,
|
|
147
|
+
data: unknown,
|
|
148
|
+
compiledSemanticAction?: CompiledAgentBrowserSemanticAction,
|
|
149
|
+
): string {
|
|
128
150
|
const confirmationRequired = detectConfirmationRequired(data);
|
|
129
151
|
if (confirmationRequired) return formatConfirmationRequiredText(confirmationRequired);
|
|
130
152
|
|
|
@@ -135,8 +157,14 @@ export function formatPresentationContentText(commandInfo: CommandInfo, data: un
|
|
|
135
157
|
if (typeof data === "number" || typeof data === "boolean") return String(data);
|
|
136
158
|
if (!isRecord(data)) return stringifyModelFacing(data);
|
|
137
159
|
|
|
160
|
+
if (compiledSemanticAction) {
|
|
161
|
+
const semanticText = formatSemanticActionPresentationText(compiledSemanticAction, data);
|
|
162
|
+
if (semanticText) return semanticText;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfo, compiledSemanticAction);
|
|
138
166
|
const navigationSummary = getNavigationSummary(data);
|
|
139
|
-
if (navigationSummary && isNavigationObservableCommand(
|
|
167
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command)) {
|
|
140
168
|
const navigationText = formatNavigationSummary(navigationSummary);
|
|
141
169
|
if (navigationText) {
|
|
142
170
|
const actionText = formatNavigationActionResult(data);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Map successful semanticAction results to the same presentation signals as direct ref commands.
|
|
3
|
+
* Responsibilities: Resolve presentation command names, compact action prose, and navigation-summary probe gates.
|
|
4
|
+
* Scope: semanticAction success presentation only.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getCompiledSemanticActionCommandIndex,
|
|
9
|
+
isCompiledSemanticActionFindCommand,
|
|
10
|
+
} from "../../input-modes/semantic-action.js";
|
|
11
|
+
import type { CompiledAgentBrowserSemanticAction } from "../../input-modes/types.js";
|
|
12
|
+
import { isRecord } from "../../parsing.js";
|
|
13
|
+
import type { CommandInfo } from "../../runtime.js";
|
|
14
|
+
import {
|
|
15
|
+
formatNavigationSummary,
|
|
16
|
+
getNavigationSummary,
|
|
17
|
+
isNavigationObservableCommand,
|
|
18
|
+
} from "./navigation.js";
|
|
19
|
+
import { redactModelFacingText } from "./common.js";
|
|
20
|
+
|
|
21
|
+
const SEMANTIC_NAVIGATION_PROBE_ACTIONS = new Set(["check", "click", "uncheck"]);
|
|
22
|
+
|
|
23
|
+
const SEMANTIC_PRESENTATION_ACTIONS = new Set(["check", "click", "fill", "select", "uncheck"]);
|
|
24
|
+
|
|
25
|
+
function getPageSummary(data: Record<string, unknown>): string | undefined {
|
|
26
|
+
const title = typeof data.title === "string" ? data.title : undefined;
|
|
27
|
+
const url = typeof data.url === "string" ? data.url : undefined;
|
|
28
|
+
if (!title && !url) return undefined;
|
|
29
|
+
if (title && url) return `${title}\n${url}`;
|
|
30
|
+
return title ?? url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatSemanticActionTarget(compiled: CompiledAgentBrowserSemanticAction): string {
|
|
34
|
+
if (compiled.action === "select") {
|
|
35
|
+
const selector = compiled.selector ?? "selector";
|
|
36
|
+
const values = compiled.values?.length ? compiled.values.join(", ") : "";
|
|
37
|
+
return values ? `${selector} → ${values}` : selector;
|
|
38
|
+
}
|
|
39
|
+
const commandIndex = getCompiledSemanticActionCommandIndex(compiled);
|
|
40
|
+
const locator = compiled.locator ?? compiled.args[commandIndex + 1] ?? "locator";
|
|
41
|
+
const locatorValue = compiled.args[commandIndex + 2];
|
|
42
|
+
const nameIndex = compiled.args.indexOf("--name");
|
|
43
|
+
const name = nameIndex >= 0 ? compiled.args[nameIndex + 1] : undefined;
|
|
44
|
+
const quotedValue = JSON.stringify(locatorValue ?? "");
|
|
45
|
+
const target = `${locator} ${quotedValue}`;
|
|
46
|
+
return name ? `${target} (name ${JSON.stringify(name)})` : target;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatSemanticActionCompactLine(compiled: CompiledAgentBrowserSemanticAction): string {
|
|
50
|
+
const target = formatSemanticActionTarget(compiled);
|
|
51
|
+
switch (compiled.action) {
|
|
52
|
+
case "click":
|
|
53
|
+
return `Clicked: ${target}`;
|
|
54
|
+
case "fill":
|
|
55
|
+
return `Filled: ${target}`;
|
|
56
|
+
case "check":
|
|
57
|
+
return `Checked: ${target}`;
|
|
58
|
+
case "uncheck":
|
|
59
|
+
return `Unchecked: ${target}`;
|
|
60
|
+
case "select":
|
|
61
|
+
return `Selected: ${target}`;
|
|
62
|
+
default:
|
|
63
|
+
return `${compiled.action}: ${target}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveSemanticPresentationCommand(
|
|
68
|
+
compiled: CompiledAgentBrowserSemanticAction | undefined,
|
|
69
|
+
): string | undefined {
|
|
70
|
+
if (!compiled || !SEMANTIC_PRESENTATION_ACTIONS.has(compiled.action)) return undefined;
|
|
71
|
+
if (compiled.action === "select") return "select";
|
|
72
|
+
if (isCompiledSemanticActionFindCommand(compiled)) return compiled.action;
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function resolvePresentationCommandInfo(
|
|
77
|
+
commandInfo: CommandInfo,
|
|
78
|
+
compiledSemanticAction?: CompiledAgentBrowserSemanticAction,
|
|
79
|
+
): CommandInfo {
|
|
80
|
+
const presentationCommand = resolveSemanticPresentationCommand(compiledSemanticAction);
|
|
81
|
+
if (!presentationCommand) return commandInfo;
|
|
82
|
+
return { ...commandInfo, command: presentationCommand };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function shouldCaptureSemanticActionNavigationSummary(
|
|
86
|
+
compiled: CompiledAgentBrowserSemanticAction | undefined,
|
|
87
|
+
data: unknown,
|
|
88
|
+
): boolean {
|
|
89
|
+
if (!compiled || !SEMANTIC_NAVIGATION_PROBE_ACTIONS.has(compiled.action)) return false;
|
|
90
|
+
if (!isCompiledSemanticActionFindCommand(compiled)) return false;
|
|
91
|
+
return !isRecord(data) || (typeof data.title !== "string" && typeof data.url !== "string");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function formatSemanticActionPresentationText(
|
|
95
|
+
compiled: CompiledAgentBrowserSemanticAction,
|
|
96
|
+
data: Record<string, unknown>,
|
|
97
|
+
): string | undefined {
|
|
98
|
+
const presentationCommand = resolveSemanticPresentationCommand(compiled);
|
|
99
|
+
if (!presentationCommand) return undefined;
|
|
100
|
+
|
|
101
|
+
const actionLine = formatSemanticActionCompactLine(compiled);
|
|
102
|
+
const navigationSummary = getNavigationSummary(data);
|
|
103
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommand)) {
|
|
104
|
+
const navigationText = formatNavigationSummary(navigationSummary);
|
|
105
|
+
if (navigationText) return `${actionLine}\n\nCurrent page:\n${navigationText}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const pageSummary = getPageSummary(data);
|
|
109
|
+
if (pageSummary) return `${actionLine}\n\nCurrent page:\n${redactModelFacingText(pageSummary)}`;
|
|
110
|
+
|
|
111
|
+
return actionLine;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function formatSemanticActionPresentationSummary(
|
|
115
|
+
compiled: CompiledAgentBrowserSemanticAction,
|
|
116
|
+
data: Record<string, unknown>,
|
|
117
|
+
): string | undefined {
|
|
118
|
+
const presentationCommand = resolveSemanticPresentationCommand(compiled);
|
|
119
|
+
if (!presentationCommand) return undefined;
|
|
120
|
+
|
|
121
|
+
const navigationSummary = getNavigationSummary(data);
|
|
122
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommand)) {
|
|
123
|
+
const navigationText = formatNavigationSummary(navigationSummary);
|
|
124
|
+
if (navigationText) {
|
|
125
|
+
return `${presentationCommand} → ${navigationText.split("\n", 1)[0] ?? navigationText}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const pageSummary = getPageSummary(data);
|
|
130
|
+
if (pageSummary) return `${presentationCommand} → ${pageSummary.split("\n", 1)[0] ?? pageSummary}`;
|
|
131
|
+
|
|
132
|
+
return `${presentationCommand} → ${formatSemanticActionTarget(compiled)}`;
|
|
133
|
+
}
|