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,24 +1,54 @@
|
|
|
1
1
|
import { isRecord } from "../../parsing.js";
|
|
2
2
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
3
3
|
import { formatRawSnapshotText, formatSnapshotSummary } from "../snapshot.js";
|
|
4
|
-
import { redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
4
|
+
import { getPageSummary, omitUpstreamLifecycle, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
5
5
|
import { formatDiagnosticSummary, formatDiagnosticText, formatProfilesText, getStreamSummary, getTabSummary } from "./diagnostics.js";
|
|
6
6
|
import { getScreenshotSummary } from "./artifacts.js";
|
|
7
7
|
import { formatSkillsText } from "./skills.js";
|
|
8
8
|
import { formatExtractionSummary, formatExtractionText, formatNavigationActionResult, formatNavigationSummary, getNavigationSummary, isNavigationObservableCommand, } from "./navigation.js";
|
|
9
9
|
import { formatSemanticActionPresentationSummary, formatSemanticActionPresentationText, resolvePresentationCommandInfo, } from "./semantic-action.js";
|
|
10
|
-
function getPageSummary(data) {
|
|
11
|
-
const title = typeof data.title === "string" ? data.title : undefined;
|
|
12
|
-
const url = typeof data.url === "string" ? data.url : undefined;
|
|
13
|
-
if (!title && !url)
|
|
14
|
-
return undefined;
|
|
15
|
-
if (title && url)
|
|
16
|
-
return `${title}\n${url}`;
|
|
17
|
-
return title ?? url;
|
|
18
|
-
}
|
|
19
10
|
function formatConfirmationRequiredSummary(confirmation) {
|
|
20
11
|
return `Confirmation required: ${confirmation.id}`;
|
|
21
12
|
}
|
|
13
|
+
const SIMPLE_ACTION_RESULTS = {
|
|
14
|
+
check: { field: "checked", label: "Checked" },
|
|
15
|
+
click: { field: "clicked", label: "Clicked" },
|
|
16
|
+
fill: { field: "filled", label: "Filled" },
|
|
17
|
+
focus: { field: "focused", label: "Focused" },
|
|
18
|
+
hover: { field: "hovered", label: "Hovered" },
|
|
19
|
+
press: { field: "pressed", label: "Pressed" },
|
|
20
|
+
select: { field: "selected", label: "Selected" },
|
|
21
|
+
type: { field: "typed", label: "Typed" },
|
|
22
|
+
uncheck: { field: "unchecked", label: "Unchecked" },
|
|
23
|
+
};
|
|
24
|
+
function formatSimpleActionResult(command, data) {
|
|
25
|
+
if (!isRecord(data))
|
|
26
|
+
return undefined;
|
|
27
|
+
if (command === "click" && getNavigationSummary(data))
|
|
28
|
+
return undefined;
|
|
29
|
+
const definition = SIMPLE_ACTION_RESULTS[command];
|
|
30
|
+
if (!definition)
|
|
31
|
+
return undefined;
|
|
32
|
+
const value = data[definition.field];
|
|
33
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
|
|
34
|
+
return undefined;
|
|
35
|
+
return `${definition.label}: ${redactModelFacingText(String(value))}`;
|
|
36
|
+
}
|
|
37
|
+
function formatWaitResult(data) {
|
|
38
|
+
if (!isRecord(data))
|
|
39
|
+
return undefined;
|
|
40
|
+
if (data.waited === "timeout")
|
|
41
|
+
return "Fixed wait elapsed; no page condition was verified.";
|
|
42
|
+
for (const field of ["selector", "text", "url", "state", "waited"]) {
|
|
43
|
+
const value = data[field];
|
|
44
|
+
if (typeof value === "string" && value.length > 0)
|
|
45
|
+
return `Wait completed: ${redactModelFacingText(value)}`;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
function formatCloseResult(data) {
|
|
50
|
+
return isRecord(data) && data.closed === true ? "Browser session closed." : undefined;
|
|
51
|
+
}
|
|
22
52
|
const VITALS_METRICS = ["lcp", "fcp", "ttfb", "inp", "cls"];
|
|
23
53
|
function coerceVitalsMetricValue(value) {
|
|
24
54
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -80,6 +110,14 @@ function formatConfirmationRequiredText(confirmation) {
|
|
|
80
110
|
return lines.join("\n");
|
|
81
111
|
}
|
|
82
112
|
const COMMAND_PRESENTERS = {
|
|
113
|
+
...Object.fromEntries(Object.keys(SIMPLE_ACTION_RESULTS).map((command) => [command, {
|
|
114
|
+
summary: (_commandInfo, data) => formatSimpleActionResult(command, data),
|
|
115
|
+
text: (_commandInfo, data) => formatSimpleActionResult(command, data),
|
|
116
|
+
}])),
|
|
117
|
+
close: { summary: (_commandInfo, data) => formatCloseResult(data), text: (_commandInfo, data) => formatCloseResult(data) },
|
|
118
|
+
exit: { summary: (_commandInfo, data) => formatCloseResult(data), text: (_commandInfo, data) => formatCloseResult(data) },
|
|
119
|
+
quit: { summary: (_commandInfo, data) => formatCloseResult(data), text: (_commandInfo, data) => formatCloseResult(data) },
|
|
120
|
+
wait: { summary: (_commandInfo, data) => formatWaitResult(data), text: (_commandInfo, data) => formatWaitResult(data) },
|
|
83
121
|
profiles: {
|
|
84
122
|
summary: (_commandInfo, data) => Array.isArray(data) ? `Chrome profiles: ${data.length}` : undefined,
|
|
85
123
|
text: (_commandInfo, data) => Array.isArray(data) ? formatProfilesText(data, "Chrome profiles") : undefined,
|
|
@@ -118,8 +156,12 @@ const COMMAND_PRESENTERS = {
|
|
|
118
156
|
text: (commandInfo, data) => isRecord(data) && commandInfo.subcommand === "status" ? getStreamSummary(data) : undefined,
|
|
119
157
|
},
|
|
120
158
|
tab: {
|
|
121
|
-
summary: (_commandInfo, data) => isRecord(data) && Array.isArray(data.tabs)
|
|
122
|
-
|
|
159
|
+
summary: (_commandInfo, data) => isRecord(data) && Array.isArray(data.tabs)
|
|
160
|
+
? `Tabs: ${data.tabs.length}`
|
|
161
|
+
: isRecord(data) && data.closed === true ? "Tab closed" : undefined,
|
|
162
|
+
text: (_commandInfo, data) => isRecord(data) && data.closed === true
|
|
163
|
+
? `Tab closed${typeof data.tabId === "string" ? `: ${redactModelFacingText(data.tabId)}` : "."}`
|
|
164
|
+
: isRecord(data) ? getTabSummary(data) : undefined,
|
|
123
165
|
},
|
|
124
166
|
vitals: {
|
|
125
167
|
summary: (_commandInfo, data) => isRecord(data) ? formatVitalsSummary(data) : undefined,
|
|
@@ -155,7 +197,7 @@ export function formatPresentationSummary(commandInfo, data, compiledSemanticAct
|
|
|
155
197
|
return semanticSummary;
|
|
156
198
|
}
|
|
157
199
|
const navigationSummary = getNavigationSummary(data);
|
|
158
|
-
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command)) {
|
|
200
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command, presentationCommandInfo.subcommand)) {
|
|
159
201
|
const navigationText = formatNavigationSummary(navigationSummary);
|
|
160
202
|
if (navigationText) {
|
|
161
203
|
return `${presentationCommandInfo.command ?? "navigation"} → ${navigationText.split("\n", 1)[0] ?? navigationText}`;
|
|
@@ -200,7 +242,7 @@ export function formatPresentationContentText(commandInfo, data, compiledSemanti
|
|
|
200
242
|
}
|
|
201
243
|
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfo, compiledSemanticAction);
|
|
202
244
|
const navigationSummary = getNavigationSummary(data);
|
|
203
|
-
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command)) {
|
|
245
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command, presentationCommandInfo.subcommand)) {
|
|
204
246
|
const navigationText = formatNavigationSummary(navigationSummary);
|
|
205
247
|
if (navigationText) {
|
|
206
248
|
const actionText = formatNavigationActionResult(data);
|
|
@@ -216,5 +258,8 @@ export function formatPresentationContentText(commandInfo, data, compiledSemanti
|
|
|
216
258
|
const pageSummary = getPageSummary(data);
|
|
217
259
|
if (pageSummary)
|
|
218
260
|
return redactModelFacingText(pageSummary);
|
|
219
|
-
|
|
261
|
+
const { navigationSummary: _navigationSummary, ...pageData } = omitUpstreamLifecycle(data);
|
|
262
|
+
if (Object.keys(pageData).length > 0)
|
|
263
|
+
return stringifyModelFacing(pageData);
|
|
264
|
+
return `${presentationCommandInfo.command ?? commandInfo.command ?? "agent-browser"} completed`;
|
|
220
265
|
}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { getCompiledSemanticActionCommandIndex, isCompiledSemanticActionFindCommand, } from "../../input-modes/semantic-action.js";
|
|
2
2
|
import { isRecord } from "../../parsing.js";
|
|
3
3
|
import { formatNavigationSummary, getNavigationSummary, isNavigationObservableCommand, } from "./navigation.js";
|
|
4
|
-
import { redactModelFacingText } from "./common.js";
|
|
4
|
+
import { getPageSummary, redactModelFacingText } from "./common.js";
|
|
5
5
|
const SEMANTIC_NAVIGATION_PROBE_ACTIONS = new Set(["check", "click"]);
|
|
6
6
|
const SEMANTIC_PRESENTATION_ACTIONS = new Set(["check", "click", "fill", "select"]);
|
|
7
|
-
function getPageSummary(data) {
|
|
8
|
-
const title = typeof data.title === "string" ? data.title : undefined;
|
|
9
|
-
const url = typeof data.url === "string" ? data.url : undefined;
|
|
10
|
-
if (!title && !url)
|
|
11
|
-
return undefined;
|
|
12
|
-
if (title && url)
|
|
13
|
-
return `${title}\n${url}`;
|
|
14
|
-
return title ?? url;
|
|
15
|
-
}
|
|
16
7
|
function formatSemanticActionTarget(compiled) {
|
|
17
8
|
if (compiled.action === "select") {
|
|
18
9
|
const selector = compiled.selector ?? "selector";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { isRecord } from "../parsing.js";
|
|
2
|
-
import {
|
|
2
|
+
import { extractUpstreamCommandTokens, parseCommandInfo, redactInvocationArgs } from "../runtime.js";
|
|
3
3
|
import { buildAgentBrowserNextActions } from "./action-recommendations.js";
|
|
4
4
|
import { buildAgentBrowserResultCategoryDetails } from "./categories.js";
|
|
5
5
|
import { detectConfirmationRequired } from "./confirmation.js";
|
|
6
6
|
import { buildSnapshotPresentation } from "./snapshot.js";
|
|
7
7
|
import { parseJsonPreviewString, redactModelFacingText, stringifyModelFacing } from "./presentation/common.js";
|
|
8
8
|
import { applyArtifactManifest, attachInlineImage, buildArtifactVerificationSummary, buildManifestEntriesForFileArtifacts, classifyPresentationSuccessCategory, extractFileArtifacts, extractImagePath, formatArtifactMetadataLines, formatArtifactSummary, formatMissingArtifactFailureText, getSavedFileDetails, hasMissingFileArtifact, isManifestFileArtifact, } from "./presentation/artifacts.js";
|
|
9
|
-
import { buildBatchPresentation, isAgentBrowserBatchResultArray } from "./presentation/batch.js";
|
|
10
|
-
import { getPresentationPaths } from "./presentation/content.js";
|
|
9
|
+
import { buildBatchPresentation, isAgentBrowserBatchResultArray, redactBatchStepErrorData } from "./presentation/batch.js";
|
|
10
|
+
import { getPresentationPaths, isStringArray } from "./presentation/content.js";
|
|
11
11
|
import { buildNetworkRequestsNextActions, buildStreamNextActions, enrichStreamStatusData, formatNetworkRouteDiagnosticsText, redactPresentationData, } from "./presentation/diagnostics.js";
|
|
12
12
|
import { buildErrorPresentation } from "./presentation/errors.js";
|
|
13
13
|
import { compactLargePresentationOutput } from "./presentation/large-output.js";
|
|
@@ -31,16 +31,35 @@ function mergeNextActions(...groups) {
|
|
|
31
31
|
function shouldAddAnnotatedScreenshotGuidance(commandInfo, args) {
|
|
32
32
|
return commandInfo.command === "screenshot" && (args?.includes("--annotate") ?? false);
|
|
33
33
|
}
|
|
34
|
+
function getKeyboardInsertTextWarning(commandInfo) {
|
|
35
|
+
if (commandInfo.command !== "keyboard" || commandInfo.subcommand !== "inserttext")
|
|
36
|
+
return undefined;
|
|
37
|
+
return "Input dispatch warning: keyboard inserttext skips key events. A DOM value change does not prove a framework-controlled editor accepted it; verify application state before saving, or use keyboard type when real key events are required.";
|
|
38
|
+
}
|
|
39
|
+
function redactBatchSpillData(data) {
|
|
40
|
+
return data.map((row) => {
|
|
41
|
+
const command = isStringArray(row.command) ? row.command : undefined;
|
|
42
|
+
const commandInfo = parseCommandInfo(command ?? []);
|
|
43
|
+
return {
|
|
44
|
+
...row,
|
|
45
|
+
command: command ? redactInvocationArgs(command) : row.command,
|
|
46
|
+
error: row.error === undefined ? undefined : redactBatchStepErrorData(command, row.error),
|
|
47
|
+
result: row.result === undefined ? undefined : redactPresentationData(commandInfo, row.result),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
34
51
|
export async function buildToolPresentation(options) {
|
|
35
52
|
const { args, artifactManifest, artifactRequest, commandInfo, compiledSemanticAction, cwd, envelope, errorText, namespace, networkRouteDiagnostics, networkRoutes, persistentArtifactStore, sessionName, } = options;
|
|
36
|
-
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens:
|
|
53
|
+
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens: extractUpstreamCommandTokens(args) };
|
|
37
54
|
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfoWithTokens, compiledSemanticAction);
|
|
38
55
|
if (errorText) {
|
|
39
56
|
return buildErrorPresentation({ args, commandInfo, errorText, sessionName });
|
|
40
57
|
}
|
|
41
58
|
const data = enrichStreamStatusData(commandInfoWithTokens, envelope?.data);
|
|
42
|
-
const presentationData =
|
|
43
|
-
|
|
59
|
+
const presentationData = commandInfo.command === "batch" && isAgentBrowserBatchResultArray(data)
|
|
60
|
+
? redactBatchSpillData(data)
|
|
61
|
+
: redactPresentationData(commandInfoWithTokens, data);
|
|
62
|
+
const artifacts = await extractFileArtifacts({ artifactManifest, artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs, artifactRequest, commandInfo: presentationCommandInfo, cwd, data, namespace, sessionName });
|
|
44
63
|
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
45
64
|
const artifactSummary = formatArtifactSummary(artifacts);
|
|
46
65
|
const summary = artifactSummary ?? formatPresentationSummary(commandInfoWithTokens, data, compiledSemanticAction);
|
|
@@ -49,6 +68,8 @@ export async function buildToolPresentation(options) {
|
|
|
49
68
|
if (commandInfo.command === "batch" && isAgentBrowserBatchResultArray(data)) {
|
|
50
69
|
presentation = await buildBatchPresentation({
|
|
51
70
|
artifactManifest,
|
|
71
|
+
artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs,
|
|
72
|
+
artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs,
|
|
52
73
|
artifactRequests: options.batchArtifactRequests,
|
|
53
74
|
buildNestedToolPresentation: buildToolPresentation,
|
|
54
75
|
cwd,
|
|
@@ -93,6 +114,10 @@ export async function buildToolPresentation(options) {
|
|
|
93
114
|
const guidance = "Annotated screenshot note: dense pages can produce overlapping labels. If the labels are noisy, capture a scoped element screenshot, take a non-annotated screenshot, or use snapshot -i high-value refs as the machine-readable map.";
|
|
94
115
|
presentation.content[0] = { ...presentation.content[0], text: `${presentation.content[0].text}\n\n${guidance}` };
|
|
95
116
|
}
|
|
117
|
+
const keyboardInsertTextWarning = getKeyboardInsertTextWarning(commandInfoWithTokens);
|
|
118
|
+
if (keyboardInsertTextWarning && presentation.content[0]?.type === "text") {
|
|
119
|
+
presentation.content[0] = { ...presentation.content[0], text: `${presentation.content[0].text}\n\n${keyboardInsertTextWarning}` };
|
|
120
|
+
}
|
|
96
121
|
const imagePath = artifactRequest?.absolutePath ?? extractImagePath(commandInfo, cwd, data);
|
|
97
122
|
const presentationWithImage = imagePath ? await attachInlineImage(presentation, imagePath) : presentation;
|
|
98
123
|
const compactedPresentation = await compactLargePresentationOutput({
|
|
@@ -156,6 +181,8 @@ export async function buildToolPresentation(options) {
|
|
|
156
181
|
failureCategory: presentationWithManifest.failureCategory,
|
|
157
182
|
resultCategory: presentationWithManifest.resultCategory ?? "success",
|
|
158
183
|
savedFilePath: presentationWithManifest.savedFilePath,
|
|
184
|
+
sessionName,
|
|
185
|
+
subcommand: presentationCommandInfo.subcommand,
|
|
159
186
|
successCategory: presentationWithManifest.successCategory,
|
|
160
187
|
});
|
|
161
188
|
const networkNextActions = commandInfoWithTokens.command === "network" && commandInfoWithTokens.subcommand === "requests" && presentationWithManifest.resultCategory === "success"
|
|
@@ -171,5 +198,8 @@ export async function buildToolPresentation(options) {
|
|
|
171
198
|
savedFilePath: presentationWithManifest.savedFilePath,
|
|
172
199
|
summary: presentationWithManifest.summary,
|
|
173
200
|
});
|
|
201
|
+
if (presentationWithManifest.pageChangeSummary?.observed === false && presentationCommandInfo.command !== "batch" && presentationWithManifest.content[0]?.type === "text") {
|
|
202
|
+
presentationWithManifest.content[0] = { ...presentationWithManifest.content[0], text: `${presentationWithManifest.content[0].text}\n\nAction dispatched; application change unverified. Verify the expected URL, text, state, or external receipt before relying on it.` };
|
|
203
|
+
}
|
|
174
204
|
return sanitizeModelFacingPresentation(presentationWithManifest);
|
|
175
205
|
}
|
|
@@ -8,6 +8,8 @@ export const AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS = {
|
|
|
8
8
|
selectIntendedTabAfterDrift: "select-intended-tab-after-drift",
|
|
9
9
|
snapshotAfterTabRecovery: "snapshot-after-tab-recovery",
|
|
10
10
|
tabDriftListTabs: "list-tabs-for-tab-drift-recovery",
|
|
11
|
+
tabGoneListTabs: "list-tabs-after-tab-gone",
|
|
12
|
+
tabGoneNewTab: "open-tab-after-tab-gone",
|
|
11
13
|
};
|
|
12
14
|
export const AGENT_BROWSER_RICH_INPUT_RECOVERY_NEXT_ACTION_IDS = {
|
|
13
15
|
click: "click-current-editable-ref",
|
|
@@ -58,7 +60,7 @@ export function buildRecoveryNextActions(recovery) {
|
|
|
58
60
|
args: sessionArgs(["get", "url"]),
|
|
59
61
|
id: AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS.connectedSessionGetUrl,
|
|
60
62
|
reason: "Verify the attached page URL before any page-content inspection.",
|
|
61
|
-
safety: "Read-only URL lookup. The wrapper keeps page inspection blocked if the active target
|
|
63
|
+
safety: "Read-only URL lookup. The wrapper keeps page inspection blocked if the active target URL cannot be verified.",
|
|
62
64
|
}),
|
|
63
65
|
buildNextToolAction({
|
|
64
66
|
args: sessionArgs(["tab", "list"]),
|
|
@@ -17,6 +17,15 @@ export function buildNoActivePageNextActions(sessionName) {
|
|
|
17
17
|
resultCategory: "failure",
|
|
18
18
|
}) ?? [];
|
|
19
19
|
}
|
|
20
|
+
export function buildPendingWebMcpNextActions(sessionName) {
|
|
21
|
+
return [{
|
|
22
|
+
id: "verify-page-target-after-pending-webmcp",
|
|
23
|
+
params: { args: withOptionalSessionArgs(sessionName, ["get", "url"]) },
|
|
24
|
+
reason: "Verify the current page target before taking a fresh snapshot; use webmcp result or cancel to settle the detached invocation.",
|
|
25
|
+
safety: "Read-only URL inspection. It does not settle the pending page tool, which may still mutate or navigate later.",
|
|
26
|
+
tool: "agent_browser",
|
|
27
|
+
}];
|
|
28
|
+
}
|
|
20
29
|
export function buildSessionTabRecoveryNextActions(options) {
|
|
21
30
|
const resultCategory = options.resultCategory ?? "success";
|
|
22
31
|
return buildAgentBrowserNextActions({
|
|
@@ -9,14 +9,24 @@ const SELECTOR_RECOVERY_ACTION_NAMES = new Set(["check", "click", "fill", "selec
|
|
|
9
9
|
const VISIBLE_REF_FALLBACK_CANDIDATE_LIMIT = 3;
|
|
10
10
|
const EDITABLE_CONTROL_ROLES = new Set(["combobox", "searchbox", "textbox"]);
|
|
11
11
|
const RICH_INPUT_RECOVERY_EDITABLE_ROLES = new Set(["searchbox", "textbox"]);
|
|
12
|
-
const RICH_INPUT_RECOVERY_HINT = "After the editable ref is focused, use keyboard
|
|
12
|
+
const RICH_INPUT_RECOVERY_HINT = "After the editable ref is focused, use keyboard type when a framework-controlled editor requires real key events. keyboard inserttext is paste-like and needs separate application-state verification. Do not press Enter or otherwise submit unless the user flow explicitly calls for it.";
|
|
13
13
|
function isSelectorRecoveryActionName(action) {
|
|
14
14
|
return SELECTOR_RECOVERY_ACTION_NAMES.has(action);
|
|
15
15
|
}
|
|
16
16
|
function getFindNameFlagValue(args, startIndex) {
|
|
17
17
|
const nameFlagIndex = args.indexOf("--name", startIndex);
|
|
18
18
|
const name = nameFlagIndex >= 0 ? args[nameFlagIndex + 1] : undefined;
|
|
19
|
-
return name &&
|
|
19
|
+
return typeof name === "string" && name.length > 0 ? name : undefined;
|
|
20
|
+
}
|
|
21
|
+
function collectFindTrailingValues(args, startIndex) {
|
|
22
|
+
const values = [];
|
|
23
|
+
for (let index = startIndex; index < args.length; index += 1) {
|
|
24
|
+
const token = args[index];
|
|
25
|
+
if (!token || token.startsWith("-"))
|
|
26
|
+
break;
|
|
27
|
+
values.push(token);
|
|
28
|
+
}
|
|
29
|
+
return values;
|
|
20
30
|
}
|
|
21
31
|
function getFindVisibleRefFallbackTarget(args, options = {}) {
|
|
22
32
|
const findIndex = args[0] === "--session" ? 2 : 0;
|
|
@@ -25,8 +35,21 @@ function getFindVisibleRefFallbackTarget(args, options = {}) {
|
|
|
25
35
|
const locator = args[findIndex + 1];
|
|
26
36
|
const value = args[findIndex + 2];
|
|
27
37
|
const action = args[findIndex + 3];
|
|
28
|
-
if (!locator || !value || !isSelectorRecoveryActionName(action)
|
|
38
|
+
if (!locator || !value || !isSelectorRecoveryActionName(action))
|
|
29
39
|
return undefined;
|
|
40
|
+
if (action === "select") {
|
|
41
|
+
const optionValues = collectFindTrailingValues(args, findIndex + 4);
|
|
42
|
+
if (locator === "role") {
|
|
43
|
+
if (!/^(?:combobox|listbox)$/i.test(value))
|
|
44
|
+
return undefined;
|
|
45
|
+
const targetName = getFindNameFlagValue(args, findIndex + 4);
|
|
46
|
+
return targetName ? { action, optionValues, roles: [value.toLowerCase()], targetName } : undefined;
|
|
47
|
+
}
|
|
48
|
+
if (locator === "label") {
|
|
49
|
+
return { action, optionValues, roles: ["combobox", "listbox"], targetName: value };
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
30
53
|
const text = action === "fill" ? args[findIndex + 4] : undefined;
|
|
31
54
|
if (action === "fill" && (!text || (!options.allowLeadingDashFillText && text.startsWith("-"))))
|
|
32
55
|
return undefined;
|
|
@@ -69,7 +92,13 @@ function getVisibleRefFallbackCandidates(target, snapshotData) {
|
|
|
69
92
|
return [];
|
|
70
93
|
if (target.action === "fill" && editableEvidence === false && EDITABLE_CONTROL_ROLES.has(role.toLowerCase()))
|
|
71
94
|
return [];
|
|
72
|
-
const directRefArgs = target.action === "fill"
|
|
95
|
+
const directRefArgs = target.action === "fill"
|
|
96
|
+
? undefined
|
|
97
|
+
: target.action === "select" && target.optionValues && target.optionValues.length > 0
|
|
98
|
+
? ["select", `@${ref}`, ...target.optionValues]
|
|
99
|
+
: target.action === "select"
|
|
100
|
+
? undefined
|
|
101
|
+
: [target.action, `@${ref}`];
|
|
73
102
|
return [{
|
|
74
103
|
action: target.action,
|
|
75
104
|
...(directRefArgs ? { args: directRefArgs } : {}),
|
|
@@ -101,19 +130,33 @@ export function buildVisibleRefFallbackDiagnosticFromSnapshot(options) {
|
|
|
101
130
|
}
|
|
102
131
|
export function resolveVisibleRefActionFromSnapshot(options) {
|
|
103
132
|
const target = getFindVisibleRefFallbackTarget(options.compiledAction.args, { allowLeadingDashFillText: true });
|
|
104
|
-
if (!target
|
|
133
|
+
if (!target)
|
|
105
134
|
return undefined;
|
|
106
135
|
const snapshot = extractRefSnapshotFromData(options.snapshotData);
|
|
107
136
|
if (!snapshot)
|
|
108
137
|
return undefined;
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
const selectOptionValues = options.compiledAction.values && options.compiledAction.values.length > 0
|
|
139
|
+
? options.compiledAction.values
|
|
140
|
+
: target.optionValues;
|
|
141
|
+
const effectiveTarget = target.action === "select" && selectOptionValues
|
|
142
|
+
? { ...target, optionValues: selectOptionValues }
|
|
143
|
+
: target;
|
|
144
|
+
const candidates = getVisibleRefFallbackCandidates(effectiveTarget, options.snapshotData);
|
|
145
|
+
if (effectiveTarget.action === "fill") {
|
|
146
|
+
if (!options.allowFill || candidates.length !== 1 || effectiveTarget.text === undefined)
|
|
112
147
|
return undefined;
|
|
113
148
|
const [candidate] = candidates;
|
|
114
149
|
if (!candidate || candidate.editableEvidence === false || !EDITABLE_CONTROL_ROLES.has(candidate.role.toLowerCase()))
|
|
115
150
|
return undefined;
|
|
116
|
-
return { args: ["fill", candidate.ref,
|
|
151
|
+
return { args: ["fill", candidate.ref, effectiveTarget.text], snapshot };
|
|
152
|
+
}
|
|
153
|
+
if (effectiveTarget.action === "select") {
|
|
154
|
+
if (candidates.length !== 1 || !selectOptionValues || selectOptionValues.length === 0)
|
|
155
|
+
return undefined;
|
|
156
|
+
const [candidate] = candidates;
|
|
157
|
+
if (!candidate)
|
|
158
|
+
return undefined;
|
|
159
|
+
return { args: ["select", candidate.ref, ...selectOptionValues], snapshot };
|
|
117
160
|
}
|
|
118
161
|
const candidate = candidates.find((item) => item.args !== undefined);
|
|
119
162
|
if (!candidate?.args)
|
|
@@ -182,7 +225,7 @@ export function buildRichInputRecoveryNextActions(options) {
|
|
|
182
225
|
const clickId = getAgentBrowserRichInputRecoveryNextActionId("click", index, candidateCount);
|
|
183
226
|
const safety = ambiguous
|
|
184
227
|
? `Several editable refs share the same exact name. Inspect the current snapshot and use only the ${candidate.ref} ${candidate.role} if it is clearly the intended input. No fill text or submit key is included.`
|
|
185
|
-
: "Does not include fill text or submit the form. After focus/click succeeds, use keyboard
|
|
228
|
+
: "Does not include fill text or submit the form. After focus/click succeeds, use keyboard type for framework-controlled editors; use keyboard inserttext only with separate application-state verification.";
|
|
186
229
|
return [
|
|
187
230
|
{
|
|
188
231
|
id: focusId,
|
|
@@ -208,7 +251,7 @@ export function formatRichInputRecoveryText(diagnostic) {
|
|
|
208
251
|
"Rich input recovery:",
|
|
209
252
|
...diagnostic.candidates.map((candidate, index) => {
|
|
210
253
|
const [focusId, clickId] = diagnostic.nextActionIds.slice(index * 2, index * 2 + 2);
|
|
211
|
-
return `- ${candidate.ref} ${candidate.role} ${JSON.stringify(candidate.name)}: use ${focusId} or ${clickId}; then use keyboard
|
|
254
|
+
return `- ${candidate.ref} ${candidate.role} ${JSON.stringify(candidate.name)}: use ${focusId} or ${clickId}; then use keyboard type for framework-controlled editors, or paste-like keyboard inserttext only with separate application-state verification.`;
|
|
212
255
|
}),
|
|
213
256
|
`- ${diagnostic.inputMethodHint}`,
|
|
214
257
|
].join("\n");
|
|
@@ -2,6 +2,8 @@ import { compareRefIds } from "./text.js";
|
|
|
2
2
|
const SNAPSHOT_HIGH_VALUE_EDITABLE_REF_FILL_TARGET_LINES = 4;
|
|
3
3
|
const SNAPSHOT_HIGH_VALUE_SURFACE_REF_FILL_TARGET_LINES = 3;
|
|
4
4
|
const SNAPSHOT_HIGH_VALUE_PRIMARY_ACTION_REF_FILL_TARGET_LINES = 3;
|
|
5
|
+
const SNAPSHOT_HIGH_VALUE_NAMED_LINK_REF_FILL_TARGET_LINES = 6;
|
|
6
|
+
const SNAPSHOT_HIGH_VALUE_LINK_NAME_MAX_LENGTH = 80;
|
|
5
7
|
const SNAPSHOT_HIGH_VALUE_CONTROL_ROLES = new Set([
|
|
6
8
|
"button",
|
|
7
9
|
"checkbox",
|
|
@@ -33,9 +35,6 @@ const SNAPSHOT_PRIMARY_ACTION_BUTTON_NAME_PATTERNS = [
|
|
|
33
35
|
/^(?:add|apply|ask|attach|choose|confirm|connect|continue|create|deploy|done|download|go|insert|launch|log in|new|next|ok|open|publish|refresh|retry|run|save|search|select|send|sign in|sign up|start|submit|upload)$/i,
|
|
34
36
|
/^(?:add|apply|ask|confirm|connect|continue|create|launch|new|open|refresh|retry|run|save|search|send|start|submit)\b/i,
|
|
35
37
|
];
|
|
36
|
-
const SNAPSHOT_HIGH_VALUE_LINK_NAME_PATTERNS = [
|
|
37
|
-
/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i,
|
|
38
|
-
];
|
|
39
38
|
function getHighValueControlRole(entry) {
|
|
40
39
|
return entry.isEditable === true && (entry.role === "unknown" || entry.role === "generic") ? "textbox" : entry.role;
|
|
41
40
|
}
|
|
@@ -82,22 +81,29 @@ const SNAPSHOT_HIGH_VALUE_CONTROL_CATEGORY_RULES = [
|
|
|
82
81
|
matches: (entry) => isPrimaryActionButtonRef(entry),
|
|
83
82
|
priority: 2,
|
|
84
83
|
},
|
|
84
|
+
{
|
|
85
|
+
bucketKey: () => "named-link",
|
|
86
|
+
fillTarget: SNAPSHOT_HIGH_VALUE_NAMED_LINK_REF_FILL_TARGET_LINES,
|
|
87
|
+
id: "named-link",
|
|
88
|
+
matches: (entry) => getHighValueControlRole(entry) === "link" && isNamedActionLinkRef(entry),
|
|
89
|
+
priority: 3,
|
|
90
|
+
},
|
|
85
91
|
{
|
|
86
92
|
bucketKey: (_entry, role) => role,
|
|
87
93
|
id: "role",
|
|
88
94
|
matches: () => true,
|
|
89
|
-
priority:
|
|
95
|
+
priority: 4,
|
|
90
96
|
},
|
|
91
97
|
];
|
|
92
|
-
function
|
|
93
|
-
return entry.name.length > 0 &&
|
|
98
|
+
function isNamedActionLinkRef(entry) {
|
|
99
|
+
return entry.name.length > 0 && entry.name.length <= SNAPSHOT_HIGH_VALUE_LINK_NAME_MAX_LENGTH;
|
|
94
100
|
}
|
|
95
101
|
export function isHighValueControlEntry(entry) {
|
|
96
102
|
const role = getHighValueControlRole(entry);
|
|
97
103
|
if (!SNAPSHOT_HIGH_VALUE_CONTROL_ROLES.has(role))
|
|
98
104
|
return false;
|
|
99
105
|
if (role === "link")
|
|
100
|
-
return
|
|
106
|
+
return isNamedActionLinkRef(entry);
|
|
101
107
|
if (entry.isEditable === false && (role === "searchbox" || role === "textbox" || role === "combobox"))
|
|
102
108
|
return false;
|
|
103
109
|
return entry.name.length > 0 || isEditableControlRef(entry);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { redactSensitiveValue } from "../runtime.js";
|
|
1
2
|
import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../temp.js";
|
|
2
3
|
import { buildEvictedSessionArtifactEntries, formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest, } from "./artifact-manifest.js";
|
|
3
4
|
const SNAPSHOT_SPILL_FILE_PREFIX = "pi-agent-browser-snapshot";
|
|
4
5
|
export async function writeSnapshotSpillFile(data, persistentArtifactStore) {
|
|
5
6
|
const options = {
|
|
6
|
-
content: JSON.stringify(data, null, 2),
|
|
7
|
+
content: JSON.stringify(redactSensitiveValue(data), null, 2),
|
|
7
8
|
prefix: SNAPSHOT_SPILL_FILE_PREFIX,
|
|
8
9
|
suffix: ".json",
|
|
9
10
|
};
|
|
@@ -162,12 +162,12 @@ export async function buildSnapshotPresentation(data, persistentArtifactStore =
|
|
|
162
162
|
...(roleCountsText ? [`Top roles: ${roleCountsText}`] : []),
|
|
163
163
|
"",
|
|
164
164
|
"Compact snapshot view.",
|
|
165
|
-
"Viewport note: compact snapshots are DOM/signal-prioritized, not guaranteed to start with the currently scrolled viewport; use the full
|
|
165
|
+
"Viewport note: compact snapshots are DOM/signal-prioritized, not guaranteed to start with the currently scrolled viewport; use the full redacted snapshot, a screenshot, or listed high-value refs when viewport context matters.",
|
|
166
166
|
];
|
|
167
167
|
if (fallbackPreview) {
|
|
168
168
|
lines.push("", "Compact outline:", ...(fallbackPreview.lines.length > 0 ? fallbackPreview.lines : ["(no interactive elements)"]));
|
|
169
169
|
if (fallbackPreview.omittedCount > 0) {
|
|
170
|
-
lines.push(`- ... (${fallbackPreview.omittedCount} additional snapshot lines omitted; ${fullOutputPath ? `full output path: ${fullOutputPath}` : "the full
|
|
170
|
+
lines.push(`- ... (${fallbackPreview.omittedCount} additional snapshot lines omitted; ${fullOutputPath ? `full output path: ${fullOutputPath}` : "the full redacted snapshot was omitted"})`);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
else {
|
|
@@ -204,8 +204,8 @@ export async function buildSnapshotPresentation(data, persistentArtifactStore =
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
lines.push("", fullOutputPath
|
|
207
|
-
? `Full
|
|
208
|
-
: `Full
|
|
207
|
+
? `Full redacted snapshot path: ${fullOutputPath}`
|
|
208
|
+
: `Full redacted snapshot unavailable: ${spillErrorText ?? "temp spill file could not be created."}`);
|
|
209
209
|
const manifestFields = applySnapshotArtifactManifest({
|
|
210
210
|
baseManifest: artifactManifest,
|
|
211
211
|
command: "snapshot",
|