pi-agent-browser-native 0.2.29 → 0.2.31
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 +28 -0
- package/README.md +26 -16
- package/docs/ARCHITECTURE.md +6 -6
- package/docs/COMMAND_REFERENCE.md +25 -12
- package/docs/RELEASE.md +46 -5
- package/docs/REQUIREMENTS.md +4 -3
- package/docs/SUPPORT_MATRIX.md +30 -14
- package/docs/TOOL_CONTRACT.md +46 -33
- package/extensions/agent-browser/index.ts +356 -60
- package/extensions/agent-browser/lib/playbook.ts +19 -18
- package/extensions/agent-browser/lib/results/presentation.ts +154 -2
- package/extensions/agent-browser/lib/results/shared.ts +7 -1
- package/package.json +1 -1
|
@@ -18,14 +18,14 @@ 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
|
|
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), 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 other command/stdin combinations are rejected before launch; 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.",
|
|
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
23
|
"Common first calls: { args: [\"open\", \"https://example.com\"] } then { args: [\"snapshot\", \"-i\"] }; after navigation, use { args: [\"click\", \"@e2\"] } then { args: [\"snapshot\", \"-i\"] }.",
|
|
24
|
-
"Locator-first clicks and
|
|
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\" } }, { 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\" }.",
|
|
26
|
-
"High-value command reference: 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.",
|
|
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 try-*-candidate next actions (and an Agent-browser candidate fallbacks prose block) for specific placeholder/text/label shapes, and stale-ref failures can return retry-semantic-action-after-stale-ref for compiled find actions when retry safety is provable.",
|
|
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\" } }, { 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
|
+
"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
27
|
"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
|
-
"When details.nextActions is present, prefer those exact native agent_browser follow-up payloads over prose guidance; they may include args, stdin, sessionMode, safety notes, or artifactPath for saved files.",
|
|
28
|
+
"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.",
|
|
29
29
|
] as const;
|
|
30
30
|
|
|
31
31
|
export const BRAVE_SEARCH_PROMPT_GUIDELINE =
|
|
@@ -33,10 +33,11 @@ export const BRAVE_SEARCH_PROMPT_GUIDELINE =
|
|
|
33
33
|
|
|
34
34
|
export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
35
35
|
"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
|
+
"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.",
|
|
36
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.",
|
|
37
38
|
"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.",
|
|
38
39
|
"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.",
|
|
39
|
-
"For authenticated or user-specific content
|
|
40
|
+
"For authenticated or user-specific content explicitly requested by the user, such as feeds, inboxes, account pages, or private dashboards, prefer --profile Default on the first browser call and let the implicit session carry continuity. Do not use a real profile for public pages just because they are dashboards. Treat visible page content from real profiles as model-visible transcript data; use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser.",
|
|
40
41
|
"Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.",
|
|
41
42
|
"When using --profile, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device, put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.",
|
|
42
43
|
"If you already used the implicit session and now need launch-scoped flags like --profile, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device, retry with sessionMode set to fresh or pass an explicit --session for the new launch. After a successful unnamed fresh launch, later auto calls follow that new session.",
|
|
@@ -44,18 +45,18 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
|
|
|
44
45
|
"For first-navigation setup, use open without a URL plus network route --resource-type <csv>, cookies set --curl <file>, or --init-script/--enable before navigate/opening the target page.",
|
|
45
46
|
"For stateful browser context work, prefer purpose-specific page actions before dumping browser data: use auth save --password-stdin with the tool stdin field for credentials, state save/load for portable test state, cookies get/set/clear and storage local|session only when the task needs those values, and expect cookie/storage/auth/state summaries to redact credential-like fields.",
|
|
46
47
|
"For batch chains that touch cookies, storage, auth, or other secret-bearing commands, use details.batchSteps for per-step artifacts, categories, spill paths, and full structured errors; top-level details.data on batch is only a compact redacted step matrix (success, argv-redacted command, redacted result or scrubbed error text) built from the same presentation rules as standalone calls.",
|
|
47
|
-
"For non-core families, pass current upstream commands through the native tool directly: network route/requests/har, diff snapshot/screenshot/url, trace/profiler/record, console/errors/highlight/inspect/clipboard, stream enable/disable/status, dashboard start/stop, and chat. Artifact-producing commands report details.artifacts and verification state; long-running starts such as stream, dashboard, trace/profiler, and record should be paired with the matching stop/disable command when the task is done.",
|
|
48
|
+
"For non-core families, pass current upstream commands through the native tool directly: network route/requests/har, diff snapshot/screenshot/url, trace/profiler/record, console/errors/highlight/inspect/clipboard, stream enable/disable/status, dashboard start/stop, and chat. For compact network requests output, prefer details.nextActions for request detail, actionable failed-request networkSourceLookup, filtering, or HAR capture follow-ups instead of guessing request-id syntax. Artifact-producing commands report details.artifacts and verification state; long-running starts such as stream, dashboard, trace/profiler, and record should be paired with the matching stop/disable command when the task is done.",
|
|
48
49
|
"For provider or specialized app workflows, load version-matched upstream guidance with skills get agentcore|electron|slack|dogfood|vercel-sandbox through the native tool. Provider launches such as -p ios, --provider browserbase/kernel/browseruse/browserless/agentcore, and iOS --device are upstream-owned setup paths; use sessionMode fresh when switching providers and expect external credentials or local Appium/Xcode setup to be required.",
|
|
49
50
|
"For dialogs and frames, use dialog status/accept/dismiss and frame <selector|main> through native args; when --confirm-actions produces a pending confirmation, use details.nextActions or exact confirm <id> / deny <id> calls instead of inventing ids.",
|
|
50
51
|
"If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. Only use wait with an explicit argument like milliseconds, --load <state>, --url <matcher>, --fn <js>, or --text <matcher>.",
|
|
51
52
|
"For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
|
|
52
53
|
"For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or eval --stdin on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.",
|
|
53
54
|
"For downloads, prefer download <selector> <path> when an element click should save a file. Do not rely on click alone when you need the downloaded file on disk.",
|
|
54
|
-
"On dashboards with nested scroll containers, verify scroll with a screenshot or fresh snapshot -i; if the viewport did not move, prefer scrollintoview <@ref> or target the actual scrollable region. For comboboxes, a click/semanticAction may only focus the field
|
|
55
|
+
"On dashboards with nested scroll containers, verify scroll with a screenshot or fresh snapshot -i; if the viewport did not move, prefer scrollintoview <@ref> or target the actual scrollable region. For native selects, use select <selector> <value...> (or semanticAction/job select) instead of clicking option refs; for custom comboboxes, a click/semanticAction may only focus the field, so re-snapshot and fall back to type, press Enter/arrow keys, or visible option refs.",
|
|
55
56
|
"When using eval --stdin, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.",
|
|
56
57
|
"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.",
|
|
57
58
|
"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.",
|
|
58
|
-
"When commands save or spill files (screenshots, downloads, PDFs, traces, recordings, HAR, large snapshot spills), 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.",
|
|
59
|
+
"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.",
|
|
59
60
|
"Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary.",
|
|
60
61
|
] as const;
|
|
61
62
|
|
|
@@ -75,8 +76,8 @@ export const INSPECTION_TOOL_CALL_EXAMPLES = [
|
|
|
75
76
|
|
|
76
77
|
export const WRAPPER_TAB_RECOVERY_BEHAVIOR = [
|
|
77
78
|
"After launch-scoped open/goto/navigate calls that can restore existing tabs (for example --profile, --session-name, or --state), agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch.",
|
|
78
|
-
"After
|
|
79
|
-
"
|
|
79
|
+
"After the wrapper observes tab-drift risk for a session (for example profile restore correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab is known.",
|
|
80
|
+
"For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.",
|
|
80
81
|
"If a known session target unexpectedly reports about:blank, agent_browser preserves the prior intended target, best-effort re-selects it when it still exists, and reports exact recovery guidance when it cannot be re-selected.",
|
|
81
82
|
] as const;
|
|
82
83
|
|
|
@@ -90,14 +91,14 @@ export function buildSharedBrowserPlaybookGuidelines(options: { includeBraveSear
|
|
|
90
91
|
|
|
91
92
|
const RUNTIME_PROMPT_GUIDELINES = [
|
|
92
93
|
"Use exactly one input mode: args, semanticAction, job, qa, sourceLookup, or networkSourceLookup. Use stdin only for batch, eval --stdin, auth save --password-stdin, or wrapper-generated batch modes.",
|
|
93
|
-
"Common flow: open, snapshot -i, interact with current @refs or semanticAction, then re-snapshot after navigation, scrolling, rerenders, or DOM changes.",
|
|
94
|
-
"Prefer stable locators for visible text/names: semanticAction or upstream find with role/text/label/placeholder/alt/title/testid. Use current @refs only from the latest same-page snapshot.",
|
|
95
|
-
"Use sessionMode=fresh for launch-scoped state such as --
|
|
96
|
-
"For
|
|
97
|
-
"When details.nextActions is present, prefer those exact follow-up payloads over prose or guessed selectors.",
|
|
94
|
+
"Common flow: open, snapshot -i, interact with current @refs or semanticAction, then re-snapshot after navigation, scrolling, rerenders, or DOM changes. For ordinary forms, batch same-snapshot fill @refs before the submit/click step; split if a fill may autosubmit, navigate, or rerender later fields. Respect explicit stop boundaries: if the user says to stop before order/post/purchase/submit, do not click that final action.",
|
|
95
|
+
"Prefer stable locators for visible text/names: semanticAction or upstream find with role/text/label/placeholder/alt/title/testid. For native selects, prefer select <selector> <value...> or semanticAction/job select over clicking option refs. Use current @refs only from the latest same-page snapshot.",
|
|
96
|
+
"For tasks that explicitly require the user's signed-in/account-specific content, start with --profile Default plus sessionMode=fresh unless the user asks otherwise; visible page content is model-visible. Use sessionMode=fresh for other launch-scoped state such as --session-name, --cdp, --state, --auto-connect, --init-script, --enable, providers, or iOS devices; otherwise let the implicit session carry continuity.",
|
|
97
|
+
"For requested screenshots, recordings, downloads, PDFs, or HARs, save the exact user path and read details.artifactVerification before claiming success; report unavailable/missing artifacts instead of silently substituting paths. record stop needs ffmpeg on PATH. close does not delete saved files; cleanup is host-owned.",
|
|
98
|
+
"When details.nextActions is present, prefer those exact follow-up payloads over prose or guessed selectors; network request diagnostics may include request-detail, actionable failed-request networkSourceLookup, filter, or HAR-capture follow-ups.",
|
|
98
99
|
"For dense snapshots, check Omitted high-value controls and details.data.highValueControlRefIds before opening large spill files.",
|
|
99
|
-
"For dashboards, verify scroll with screenshot/snapshot; if nothing moved, use scrollintoview <@ref> or target the real scroll region.
|
|
100
|
-
"For extraction, prefer get title/url/text/html/value/attr/count or eval --stdin
|
|
100
|
+
"For dashboards, verify scroll with screenshot/snapshot; if nothing moved, use scrollintoview <@ref> or target the real scroll region. For native selects use select/semanticAction/job select instead of option refs; custom combobox clicks may only focus, so re-snapshot and fall back to type, Enter/arrows, or visible option refs.",
|
|
101
|
+
"For extraction, prefer get title/url/text/html/value/attr/count or eval --stdin with a plain expression in the tool stdin field; do not rely on console.log. When reading several known refs/selectors, use batch with JSON-array stdin (for example [[\"get\",\"text\",\"@e1\"]]) or eval --stdin instead of many serial get calls. If selector visibility warnings appear, prefer visible @refs or nextActions.",
|
|
101
102
|
"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.",
|
|
102
103
|
] as const;
|
|
103
104
|
|
|
@@ -96,6 +96,10 @@ const DIAGNOSTIC_REQUEST_PREVIEW_LIMIT = 40;
|
|
|
96
96
|
const DIAGNOSTIC_LOG_PREVIEW_LIMIT = 80;
|
|
97
97
|
const NETWORK_BODY_PREVIEW_MAX_CHARS = 280;
|
|
98
98
|
const NETWORK_ERROR_PREVIEW_MAX_CHARS = 220;
|
|
99
|
+
const NETWORK_NEXT_ACTION_LIMIT = 4;
|
|
100
|
+
const NETWORK_FILTER_MAX_CHARS = 160;
|
|
101
|
+
const NETWORK_FILTER_SENSITIVE_SEGMENT_TERMS = ["apikey", "api-key", "api_key", "authentication", "authorization", "bearer", "credential", "credentials", "jwt", "passwd", "password", "reset", "secret", "session", "token"] as const;
|
|
102
|
+
const NETWORK_FILTER_OPAQUE_SEGMENT_PATTERN = /^(?:[A-Fa-f0-9]{16,}|(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9_-]{16,})$/;
|
|
99
103
|
const NETWORK_PREVIEW_FIELD_CANDIDATES = {
|
|
100
104
|
request: ["postData"] as const,
|
|
101
105
|
response: ["responseBody"] as const,
|
|
@@ -641,6 +645,145 @@ function formatNetworkRequestText(data: Record<string, unknown>): string | undef
|
|
|
641
645
|
return formatNetworkRequestLine(data, 0).join("\n");
|
|
642
646
|
}
|
|
643
647
|
|
|
648
|
+
interface NetworkRequestActionCandidate {
|
|
649
|
+
filter?: string;
|
|
650
|
+
item: Record<string, unknown>;
|
|
651
|
+
kind: "actionable" | "api" | "benign" | "request";
|
|
652
|
+
requestId: string;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function getSafeNetworkActionValue(value: string | undefined): string | undefined {
|
|
656
|
+
if (!value) return undefined;
|
|
657
|
+
const trimmed = value.trim();
|
|
658
|
+
if (trimmed.length === 0 || redactSensitiveText(trimmed) !== trimmed) return undefined;
|
|
659
|
+
return trimmed;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function getNetworkRequestId(item: Record<string, unknown>): string | undefined {
|
|
663
|
+
return getSafeNetworkActionValue(getStringField(item, "requestId") ?? getStringField(item, "id"));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function isSensitiveNetworkPathSegment(segment: string): boolean {
|
|
667
|
+
const normalized = segment.toLowerCase();
|
|
668
|
+
return normalized === "auth" || NETWORK_FILTER_SENSITIVE_SEGMENT_TERMS.some((term) => normalized.includes(term));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function pathFilterMayExposeSensitiveSegment(filter: string): boolean {
|
|
672
|
+
const decoded = (() => {
|
|
673
|
+
try {
|
|
674
|
+
return decodeURIComponent(filter);
|
|
675
|
+
} catch {
|
|
676
|
+
return filter;
|
|
677
|
+
}
|
|
678
|
+
})();
|
|
679
|
+
return decoded.split("/").some((segment) => isSensitiveNetworkPathSegment(segment) || NETWORK_FILTER_OPAQUE_SEGMENT_PATTERN.test(segment));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function getNetworkRequestPathFilter(item: Record<string, unknown>): string | undefined {
|
|
683
|
+
const url = getStringField(item, "url");
|
|
684
|
+
if (!url) return undefined;
|
|
685
|
+
let filter: string | undefined;
|
|
686
|
+
try {
|
|
687
|
+
filter = new URL(url).pathname;
|
|
688
|
+
} catch {
|
|
689
|
+
filter = url.split(/[?#]/, 1)[0];
|
|
690
|
+
}
|
|
691
|
+
filter = filter?.trim();
|
|
692
|
+
if (!filter || filter === "/" || filter.length > NETWORK_FILTER_MAX_CHARS || pathFilterMayExposeSensitiveSegment(filter)) return undefined;
|
|
693
|
+
return getSafeNetworkActionValue(filter);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function isApiLikeNetworkRequest(item: Record<string, unknown>): boolean {
|
|
697
|
+
const method = (getStringField(item, "method") ?? "GET").toUpperCase();
|
|
698
|
+
const resourceType = (getStringField(item, "resourceType") ?? "").toLowerCase();
|
|
699
|
+
const mimeType = (getStringField(item, "mimeType") ?? "").toLowerCase();
|
|
700
|
+
const filter = getNetworkRequestPathFilter(item) ?? "";
|
|
701
|
+
return resourceType === "fetch" || resourceType === "xhr" || mimeType.includes("json") || /\/(?:api|graphql|rpc)(?:\/|$)/i.test(filter) || !["GET", "HEAD"].includes(method);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function getNetworkRequestActionCandidate(item: Record<string, unknown>): NetworkRequestActionCandidate | undefined {
|
|
705
|
+
const requestId = getNetworkRequestId(item);
|
|
706
|
+
if (!requestId) return undefined;
|
|
707
|
+
const classification = classifyNetworkRequestFailure(item);
|
|
708
|
+
const kind: NetworkRequestActionCandidate["kind"] = classification?.impact === "actionable"
|
|
709
|
+
? "actionable"
|
|
710
|
+
: classification?.impact === "benign"
|
|
711
|
+
? "benign"
|
|
712
|
+
: isApiLikeNetworkRequest(item)
|
|
713
|
+
? "api"
|
|
714
|
+
: "request";
|
|
715
|
+
return { filter: getNetworkRequestPathFilter(item), item, kind, requestId };
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function chooseNetworkRequestActionCandidate(candidates: NetworkRequestActionCandidate[]): NetworkRequestActionCandidate | undefined {
|
|
719
|
+
return candidates.find((candidate) => candidate.kind === "actionable")
|
|
720
|
+
?? candidates.find((candidate) => candidate.kind === "api")
|
|
721
|
+
?? candidates.find((candidate) => candidate.kind === "benign")
|
|
722
|
+
?? candidates[0];
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function formatNetworkRequestActionDescriptor(candidate: NetworkRequestActionCandidate): string {
|
|
726
|
+
const method = getStringField(candidate.item, "method") ?? "GET";
|
|
727
|
+
const status = typeof candidate.item.status === "number" ? String(candidate.item.status) : "pending";
|
|
728
|
+
const target = candidate.filter ? ` ${candidate.filter}` : "";
|
|
729
|
+
return `${status} ${method}${target} [${candidate.requestId}]`;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function getNetworkRequestDetailActionId(candidate: NetworkRequestActionCandidate): string {
|
|
733
|
+
if (candidate.kind === "actionable") return "inspect-actionable-network-request";
|
|
734
|
+
if (candidate.kind === "benign") return "inspect-benign-network-request";
|
|
735
|
+
return "inspect-network-request";
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function buildNetworkRequestsNextActions(data: unknown, sessionName: string | undefined): AgentBrowserNextAction[] | undefined {
|
|
739
|
+
if (!isRecord(data)) return undefined;
|
|
740
|
+
const requests = getArrayField(data, "requests");
|
|
741
|
+
if (!requests) return undefined;
|
|
742
|
+
const candidates = requests.flatMap((item) => {
|
|
743
|
+
if (!isRecord(item)) return [];
|
|
744
|
+
const candidate = getNetworkRequestActionCandidate(item);
|
|
745
|
+
return candidate ? [candidate] : [];
|
|
746
|
+
});
|
|
747
|
+
const selected = chooseNetworkRequestActionCandidate(candidates);
|
|
748
|
+
if (!selected) return undefined;
|
|
749
|
+
const descriptor = formatNetworkRequestActionDescriptor(selected);
|
|
750
|
+
const actions: AgentBrowserNextAction[] = [
|
|
751
|
+
{
|
|
752
|
+
id: getNetworkRequestDetailActionId(selected),
|
|
753
|
+
params: { args: withSessionPrefix(sessionName, ["network", "request", selected.requestId]) },
|
|
754
|
+
reason: `Inspect full request details for ${descriptor}.`,
|
|
755
|
+
safety: "Read-only network diagnostic; request inspection must not replace the active page/ref context.",
|
|
756
|
+
tool: "agent_browser",
|
|
757
|
+
},
|
|
758
|
+
];
|
|
759
|
+
if (selected.kind === "actionable") {
|
|
760
|
+
actions.push({
|
|
761
|
+
id: "trace-actionable-network-source",
|
|
762
|
+
params: { networkSourceLookup: { requestId: selected.requestId, ...(sessionName ? { session: sessionName } : {}) } },
|
|
763
|
+
reason: `Look for local source candidates related to ${descriptor}.`,
|
|
764
|
+
safety: "Read-only experimental helper; it reports bounded candidates and may miss bundled or dynamic call sites.",
|
|
765
|
+
tool: "agent_browser",
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
if (selected.filter) {
|
|
769
|
+
actions.push({
|
|
770
|
+
id: "filter-network-requests-by-path",
|
|
771
|
+
params: { args: withSessionPrefix(sessionName, ["network", "requests", "--filter", selected.filter]) },
|
|
772
|
+
reason: `List captured requests matching ${selected.filter}.`,
|
|
773
|
+
safety: "Read-only request-list filter; absence from a compact preview is not proof the request did not happen.",
|
|
774
|
+
tool: "agent_browser",
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
actions.push({
|
|
778
|
+
id: "start-network-har-capture",
|
|
779
|
+
params: { args: withSessionPrefix(sessionName, ["network", "har", "start"]) },
|
|
780
|
+
reason: "Start HAR capture before reproducing the network behavior again.",
|
|
781
|
+
safety: "HARs can contain URLs and headers; stop to an explicit path, inspect metadata, and avoid sharing sensitive captures.",
|
|
782
|
+
tool: "agent_browser",
|
|
783
|
+
});
|
|
784
|
+
return actions.slice(0, NETWORK_NEXT_ACTION_LIMIT);
|
|
785
|
+
}
|
|
786
|
+
|
|
644
787
|
function formatConsoleText(data: Record<string, unknown>): string | undefined {
|
|
645
788
|
const messages = getArrayField(data, "messages");
|
|
646
789
|
if (!messages) return undefined;
|
|
@@ -1694,7 +1837,7 @@ async function buildBatchStepPresentation(options: {
|
|
|
1694
1837
|
secondaryPaths: presentation.imagePaths,
|
|
1695
1838
|
});
|
|
1696
1839
|
const text = getPresentationText(presentation) || presentation.summary;
|
|
1697
|
-
const nextActions = buildAgentBrowserNextActions({
|
|
1840
|
+
const nextActions = presentation.nextActions ?? buildAgentBrowserNextActions({
|
|
1698
1841
|
artifacts: presentation.artifacts,
|
|
1699
1842
|
args: command,
|
|
1700
1843
|
command: command?.[0],
|
|
@@ -2129,6 +2272,11 @@ function buildSpillArtifactEntries(options: {
|
|
|
2129
2272
|
];
|
|
2130
2273
|
}
|
|
2131
2274
|
|
|
2275
|
+
function mergeNextActions(...groups: Array<AgentBrowserNextAction[] | undefined>): AgentBrowserNextAction[] | undefined {
|
|
2276
|
+
const merged = groups.flatMap((group) => group ?? []);
|
|
2277
|
+
return merged.length > 0 ? merged : undefined;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2132
2280
|
async function compactLargePresentationOutput(options: {
|
|
2133
2281
|
artifactManifest?: SessionArtifactManifest;
|
|
2134
2282
|
commandInfo: CommandInfo;
|
|
@@ -2321,7 +2469,7 @@ export async function buildToolPresentation(options: {
|
|
|
2321
2469
|
savedFile: presentationWithManifest.savedFile,
|
|
2322
2470
|
});
|
|
2323
2471
|
}
|
|
2324
|
-
|
|
2472
|
+
const genericNextActions = presentationWithManifest.nextActions ? undefined : buildAgentBrowserNextActions({
|
|
2325
2473
|
artifacts: presentationWithManifest.artifacts,
|
|
2326
2474
|
args,
|
|
2327
2475
|
command: commandInfo.command,
|
|
@@ -2331,6 +2479,10 @@ export async function buildToolPresentation(options: {
|
|
|
2331
2479
|
savedFilePath: presentationWithManifest.savedFilePath,
|
|
2332
2480
|
successCategory: presentationWithManifest.successCategory,
|
|
2333
2481
|
});
|
|
2482
|
+
const networkNextActions = commandInfo.command === "network" && commandInfo.subcommand === "requests" && presentationWithManifest.resultCategory === "success"
|
|
2483
|
+
? buildNetworkRequestsNextActions(data, sessionName)
|
|
2484
|
+
: undefined;
|
|
2485
|
+
presentationWithManifest.nextActions = mergeNextActions(presentationWithManifest.nextActions, genericNextActions, networkNextActions);
|
|
2334
2486
|
presentationWithManifest.pageChangeSummary = presentationWithManifest.pageChangeSummary ?? buildPageChangeSummary({
|
|
2335
2487
|
artifacts: presentationWithManifest.artifacts,
|
|
2336
2488
|
commandInfo,
|
|
@@ -59,7 +59,13 @@ export interface AgentBrowserNextAction {
|
|
|
59
59
|
artifactPath?: string;
|
|
60
60
|
id: string;
|
|
61
61
|
params?: {
|
|
62
|
-
args
|
|
62
|
+
args?: string[];
|
|
63
|
+
networkSourceLookup?: {
|
|
64
|
+
filter?: string;
|
|
65
|
+
requestId?: string;
|
|
66
|
+
session?: string;
|
|
67
|
+
url?: string;
|
|
68
|
+
};
|
|
63
69
|
sessionMode?: "auto" | "fresh";
|
|
64
70
|
stdin?: string;
|
|
65
71
|
};
|
package/package.json
CHANGED