pi-agent-browser-native 0.2.48 → 0.2.50

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.
Files changed (189) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +21 -11
  3. package/dist/extensions/agent-browser/index.js +808 -0
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
  9. package/dist/extensions/agent-browser/lib/config-policy.js +669 -0
  10. package/dist/extensions/agent-browser/lib/config.js +122 -0
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
  15. package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
  16. package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
  17. package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
  18. package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
  19. package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
  21. package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
  22. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
  23. package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
  24. package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
  25. package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
  26. package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
  27. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
  28. package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
  29. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
  49. package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
  50. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
  51. package/dist/extensions/agent-browser/lib/playbook.js +121 -0
  52. package/dist/extensions/agent-browser/lib/process.js +363 -0
  53. package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
  54. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
  55. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
  56. package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
  57. package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
  58. package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
  59. package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
  60. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
  61. package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
  62. package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
  63. package/dist/extensions/agent-browser/lib/results/network.js +73 -0
  64. package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
  65. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
  66. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
  67. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
  68. package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
  69. package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
  70. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +956 -0
  71. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
  72. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
  73. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
  74. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
  75. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
  76. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
  77. package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
  78. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
  79. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
  80. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
  81. package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
  82. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
  83. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
  84. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
  85. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
  86. package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
  87. package/dist/extensions/agent-browser/lib/results/text.js +40 -0
  88. package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
  89. package/dist/extensions/agent-browser/lib/runtime.js +855 -0
  90. package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
  91. package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
  92. package/dist/extensions/agent-browser/lib/temp.js +498 -0
  93. package/dist/extensions/agent-browser/lib/web-search.js +562 -0
  94. package/docs/ARCHITECTURE.md +5 -5
  95. package/docs/COMMAND_REFERENCE.md +4 -4
  96. package/docs/RELEASE.md +22 -11
  97. package/docs/REQUIREMENTS.md +1 -1
  98. package/docs/SUPPORT_MATRIX.md +5 -4
  99. package/docs/TOOL_CONTRACT.md +1 -1
  100. package/package.json +9 -5
  101. package/scripts/config.mjs +14 -20
  102. package/scripts/doctor.mjs +8 -7
  103. package/extensions/agent-browser/index.ts +0 -961
  104. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  105. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  106. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  107. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  108. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  109. package/extensions/agent-browser/lib/config-policy.js +0 -690
  110. package/extensions/agent-browser/lib/config.ts +0 -211
  111. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  112. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  113. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  114. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  115. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  116. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  117. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  118. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  119. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  120. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  121. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  122. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  123. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  124. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  125. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  126. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  127. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  128. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  129. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  130. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  131. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  132. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  133. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  136. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  138. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  139. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  140. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  141. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  142. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  143. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  144. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  145. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  146. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  147. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  148. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  149. package/extensions/agent-browser/lib/playbook.ts +0 -142
  150. package/extensions/agent-browser/lib/process.ts +0 -516
  151. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  152. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  153. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  154. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  155. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  156. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  157. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  158. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  159. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  160. package/extensions/agent-browser/lib/results/network.ts +0 -78
  161. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  162. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  163. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  164. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  165. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  166. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  167. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  168. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  169. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  170. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  171. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  172. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  173. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  174. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  175. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  176. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  177. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  178. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  179. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  180. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  181. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  182. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  183. package/extensions/agent-browser/lib/results/text.ts +0 -40
  184. package/extensions/agent-browser/lib/runtime.ts +0 -988
  185. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  186. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  187. package/extensions/agent-browser/lib/temp.ts +0 -577
  188. package/extensions/agent-browser/lib/web-search.ts +0 -728
  189. /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
@@ -0,0 +1,562 @@
1
+ /**
2
+ * Purpose: Provide the optional provider-backed `agent_browser_web_search` companion tool.
3
+ * Responsibilities: Define strict search input schema, resolve configured Brave/Exa credentials lazily, call the selected search API with cancellation/timeout, normalize compact results, and keep secrets out of content/details.
4
+ * Scope: Live web search only; browser automation remains in the `agent_browser` tool.
5
+ */
6
+ import { JsonSchema } from "./json-schema.js";
7
+ import { StringEnum as localStringEnum } from "./string-enum-schema.js";
8
+ import { DEFAULT_WEB_SEARCH_PROVIDER, WEB_SEARCH_PROVIDERS, resolvePreferredWebSearchCredential, } from "./config.js";
9
+ export const AGENT_BROWSER_WEB_SEARCH_TOOL_NAME = "agent_browser_web_search";
10
+ export const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
11
+ export const EXA_SEARCH_ENDPOINT = "https://api.exa.ai/search";
12
+ export const DEFAULT_SEARCH_RESULT_COUNT = 5;
13
+ export const MAX_SEARCH_RESULT_COUNT = 10;
14
+ export const SEARCH_REQUEST_TIMEOUT_MS = 15_000;
15
+ export const EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS = 45_000;
16
+ export const WEB_SEARCH_MIN_REQUEST_INTERVAL_MS = 1_100;
17
+ export const EXA_SEARCH_TYPES = ["auto", "fast", "instant", "deep-lite", "deep", "deep-reasoning"];
18
+ export const WEB_SEARCH_PROVIDER_PARAM_VALUES = ["auto", ...WEB_SEARCH_PROVIDERS];
19
+ export function createAgentBrowserWebSearchParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
20
+ return Type.Object({
21
+ query: Type.String({
22
+ minLength: 1,
23
+ description: "Search query to run with the configured Exa or Brave web search provider.",
24
+ }),
25
+ provider: Type.Optional(StringEnum(WEB_SEARCH_PROVIDER_PARAM_VALUES, {
26
+ description: `Optional provider override. auto uses configured keys and preferredProvider; when both Exa and Brave are available, the default preferred provider is ${DEFAULT_WEB_SEARCH_PROVIDER}.`,
27
+ })),
28
+ searchType: Type.Optional(StringEnum(EXA_SEARCH_TYPES, {
29
+ description: "Optional Exa search type. Defaults to auto; ignored by Brave. Use deep/deep-reasoning only for harder research because they are slower.",
30
+ })),
31
+ count: Type.Optional(Type.Integer({
32
+ minimum: 1,
33
+ maximum: MAX_SEARCH_RESULT_COUNT,
34
+ description: `Number of web results to return. Defaults to ${DEFAULT_SEARCH_RESULT_COUNT}; max ${MAX_SEARCH_RESULT_COUNT}.`,
35
+ })),
36
+ offset: Type.Optional(Type.Integer({
37
+ minimum: 0,
38
+ maximum: 9,
39
+ description: "Zero-based result offset for pagination. Defaults to 0.",
40
+ })),
41
+ country: Type.Optional(Type.String({
42
+ pattern: "^[A-Za-z]{2}$",
43
+ description: "Optional 2-letter country code, such as US or GB.",
44
+ })),
45
+ searchLang: Type.Optional(Type.String({
46
+ minLength: 2,
47
+ maxLength: 8,
48
+ description: "Optional Brave search language code, such as en or en-US.",
49
+ })),
50
+ safesearch: Type.Optional(StringEnum(["off", "moderate", "strict"], {
51
+ description: "Optional search safety setting. Brave forwards this as safesearch; Exa maps moderate/strict to moderation=true.",
52
+ })),
53
+ freshness: Type.Optional(StringEnum(["pd", "pw", "pm", "py"], {
54
+ description: "Optional freshness window: pd=past day, pw=past week, pm=past month, py=past year.",
55
+ })),
56
+ }, { additionalProperties: false });
57
+ }
58
+ export const AgentBrowserWebSearchParams = createAgentBrowserWebSearchParamsSchema();
59
+ const HTML_ENTITY_REPLACEMENTS = {
60
+ amp: "&",
61
+ apos: "'",
62
+ gt: ">",
63
+ lt: "<",
64
+ nbsp: " ",
65
+ quot: '"',
66
+ };
67
+ const HTML_TAG_NAMES_TO_STRIP = new Set([
68
+ "a",
69
+ "abbr",
70
+ "address",
71
+ "article",
72
+ "aside",
73
+ "audio",
74
+ "b",
75
+ "base",
76
+ "blockquote",
77
+ "body",
78
+ "br",
79
+ "button",
80
+ "canvas",
81
+ "code",
82
+ "div",
83
+ "em",
84
+ "embed",
85
+ "footer",
86
+ "form",
87
+ "h1",
88
+ "h2",
89
+ "h3",
90
+ "h4",
91
+ "h5",
92
+ "h6",
93
+ "head",
94
+ "header",
95
+ "html",
96
+ "i",
97
+ "iframe",
98
+ "img",
99
+ "input",
100
+ "li",
101
+ "link",
102
+ "main",
103
+ "mark",
104
+ "math",
105
+ "meta",
106
+ "nav",
107
+ "object",
108
+ "ol",
109
+ "option",
110
+ "p",
111
+ "pre",
112
+ "script",
113
+ "section",
114
+ "select",
115
+ "source",
116
+ "span",
117
+ "strong",
118
+ "style",
119
+ "svg",
120
+ "table",
121
+ "tbody",
122
+ "td",
123
+ "textarea",
124
+ "tfoot",
125
+ "th",
126
+ "thead",
127
+ "tr",
128
+ "u",
129
+ "ul",
130
+ "video",
131
+ ]);
132
+ function decodeHtmlEntity(entity) {
133
+ const named = HTML_ENTITY_REPLACEMENTS[entity.toLowerCase()];
134
+ if (named !== undefined)
135
+ return named;
136
+ const decimalMatch = /^#(\d+)$/.exec(entity);
137
+ const hexMatch = /^#x([0-9a-f]+)$/i.exec(entity);
138
+ const codePoint = decimalMatch ? Number.parseInt(decimalMatch[1] ?? "", 10) : hexMatch ? Number.parseInt(hexMatch[1] ?? "", 16) : undefined;
139
+ if (codePoint === undefined || !Number.isFinite(codePoint))
140
+ return `&${entity};`;
141
+ try {
142
+ return String.fromCodePoint(codePoint);
143
+ }
144
+ catch {
145
+ return `&${entity};`;
146
+ }
147
+ }
148
+ export function decodeHtmlEntities(value) {
149
+ return value.replace(/&([a-z][a-z0-9]+|#\d+|#x[0-9a-f]+);/gi, (_match, entity) => decodeHtmlEntity(entity));
150
+ }
151
+ function stripDecodedHtmlTags(value) {
152
+ return value.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, " ").replace(/<\/?([a-z][a-z0-9-]*)(\s[^>]*)?>/gi, (match, tagName, attributes) => {
153
+ if (attributes || match.startsWith("</") || HTML_TAG_NAMES_TO_STRIP.has(tagName.toLowerCase()))
154
+ return " ";
155
+ return match;
156
+ });
157
+ }
158
+ export function cleanSearchText(value, maxLength = 500) {
159
+ if (typeof value !== "string")
160
+ return undefined;
161
+ const cleaned = stripDecodedHtmlTags(decodeHtmlEntities(value.replace(/<[^>]*>/g, " ")))
162
+ .replace(/\s+/g, " ")
163
+ .trim();
164
+ if (!cleaned)
165
+ return undefined;
166
+ if (cleaned.length <= maxLength)
167
+ return cleaned;
168
+ return `${cleaned.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
169
+ }
170
+ export function normalizeSearchUrl(value) {
171
+ if (typeof value !== "string")
172
+ return undefined;
173
+ try {
174
+ const url = new URL(value);
175
+ if (url.protocol !== "http:" && url.protocol !== "https:")
176
+ return undefined;
177
+ return url.toString();
178
+ }
179
+ catch {
180
+ return undefined;
181
+ }
182
+ }
183
+ function getHostname(url) {
184
+ try {
185
+ return new URL(url).hostname;
186
+ }
187
+ catch {
188
+ return undefined;
189
+ }
190
+ }
191
+ function normalizeHighlightList(value) {
192
+ if (!Array.isArray(value))
193
+ return undefined;
194
+ const highlights = value
195
+ .map((entry) => cleanSearchText(entry, 320))
196
+ .filter((entry) => Boolean(entry))
197
+ .slice(0, 3);
198
+ return highlights.length > 0 ? highlights : undefined;
199
+ }
200
+ export function normalizeBraveSearchResult(result) {
201
+ const title = cleanSearchText(result.title, 180);
202
+ const url = normalizeSearchUrl(result.url);
203
+ if (!title || !url)
204
+ return undefined;
205
+ return {
206
+ title,
207
+ url,
208
+ description: cleanSearchText(result.description, 320),
209
+ source: cleanSearchText(result.profile?.name, 120) ?? cleanSearchText(result.meta_url?.hostname, 120),
210
+ age: cleanSearchText(result.age, 80),
211
+ language: cleanSearchText(result.language, 40),
212
+ };
213
+ }
214
+ export function normalizeExaSearchResult(result) {
215
+ const title = cleanSearchText(result.title, 180);
216
+ const url = normalizeSearchUrl(result.url);
217
+ if (!title || !url)
218
+ return undefined;
219
+ const highlights = normalizeHighlightList(result.highlights);
220
+ return {
221
+ title,
222
+ url,
223
+ description: cleanSearchText(result.summary, 320) ?? highlights?.[0] ?? cleanSearchText(result.text, 320),
224
+ highlights,
225
+ source: cleanSearchText(result.author, 120) ?? cleanSearchText(getHostname(url), 120),
226
+ age: cleanSearchText(result.publishedDate, 80),
227
+ };
228
+ }
229
+ function getProviderLabel(provider) {
230
+ return provider === "exa" ? "Exa" : "Brave";
231
+ }
232
+ export function formatSearchResults(provider, query, results) {
233
+ const providerLabel = getProviderLabel(provider);
234
+ if (results.length === 0) {
235
+ return `No ${providerLabel} web results found for: ${query}`;
236
+ }
237
+ const lines = [`${providerLabel} web search results for: ${query}`, ""];
238
+ results.forEach((result, index) => {
239
+ lines.push(`${index + 1}. ${result.title}`);
240
+ lines.push(` URL: ${result.url}`);
241
+ if (result.source)
242
+ lines.push(` Source: ${result.source}`);
243
+ if (result.age)
244
+ lines.push(` Age: ${result.age}`);
245
+ if (result.description)
246
+ lines.push(` Summary: ${result.description}`);
247
+ if (result.highlights && result.highlights.length > 1) {
248
+ lines.push(" Highlights:");
249
+ for (const highlight of result.highlights)
250
+ lines.push(` - ${highlight}`);
251
+ }
252
+ lines.push("");
253
+ });
254
+ return lines.join("\n").trimEnd();
255
+ }
256
+ export function buildBraveSearchUrl(params) {
257
+ const url = new URL(BRAVE_SEARCH_ENDPOINT);
258
+ url.searchParams.set("q", params.query);
259
+ url.searchParams.set("count", String(params.count));
260
+ url.searchParams.set("offset", String(params.offset));
261
+ if (params.country)
262
+ url.searchParams.set("country", params.country.toUpperCase());
263
+ if (params.searchLang)
264
+ url.searchParams.set("search_lang", params.searchLang);
265
+ if (params.safesearch)
266
+ url.searchParams.set("safesearch", params.safesearch);
267
+ if (params.freshness)
268
+ url.searchParams.set("freshness", params.freshness);
269
+ return url;
270
+ }
271
+ const FRESHNESS_DAYS = {
272
+ pd: 1,
273
+ pw: 7,
274
+ pm: 31,
275
+ py: 365,
276
+ };
277
+ function getStartPublishedDate(freshness, now) {
278
+ if (!freshness)
279
+ return undefined;
280
+ const days = FRESHNESS_DAYS[freshness];
281
+ return new Date(now().getTime() - days * 24 * 60 * 60 * 1000).toISOString();
282
+ }
283
+ export function buildExaSearchRequestBody(params, now = () => new Date()) {
284
+ const body = {
285
+ query: params.query,
286
+ type: params.searchType ?? "auto",
287
+ numResults: Math.min(params.count + params.offset, 100),
288
+ contents: { highlights: true },
289
+ };
290
+ if (params.country)
291
+ body.userLocation = params.country.toUpperCase();
292
+ if (params.safesearch && params.safesearch !== "off")
293
+ body.moderation = true;
294
+ const startPublishedDate = getStartPublishedDate(params.freshness, now);
295
+ if (startPublishedDate)
296
+ body.startPublishedDate = startPublishedDate;
297
+ return body;
298
+ }
299
+ function redactSearchSecret(text, apiKey) {
300
+ return apiKey ? text.split(apiKey).join("[REDACTED]") : text;
301
+ }
302
+ function sleepWithAbort(ms, signal) {
303
+ if (ms <= 0)
304
+ return Promise.resolve();
305
+ if (signal?.aborted)
306
+ return Promise.reject(signal.reason ?? new Error("Web search cancelled"));
307
+ return new Promise((resolve, reject) => {
308
+ const cleanup = () => signal?.removeEventListener("abort", abort);
309
+ const timeout = setTimeout(() => {
310
+ cleanup();
311
+ resolve();
312
+ }, ms);
313
+ const abort = () => {
314
+ clearTimeout(timeout);
315
+ cleanup();
316
+ reject(signal?.reason ?? new Error("Web search cancelled"));
317
+ };
318
+ signal?.addEventListener("abort", abort, { once: true });
319
+ });
320
+ }
321
+ export class WebSearchRequestGate {
322
+ now;
323
+ sleep;
324
+ lastRequestStartedAt = 0;
325
+ tail = Promise.resolve();
326
+ constructor(now = Date.now, sleep = sleepWithAbort) {
327
+ this.now = now;
328
+ this.sleep = sleep;
329
+ }
330
+ run(signal, task) {
331
+ const runTask = async () => {
332
+ const elapsedMs = this.lastRequestStartedAt === 0 ? WEB_SEARCH_MIN_REQUEST_INTERVAL_MS : this.now() - this.lastRequestStartedAt;
333
+ const waitMs = Math.max(0, WEB_SEARCH_MIN_REQUEST_INTERVAL_MS - elapsedMs);
334
+ if (waitMs > 0)
335
+ await this.sleep(waitMs, signal);
336
+ if (signal?.aborted)
337
+ throw signal.reason ?? new Error("Web search cancelled");
338
+ this.lastRequestStartedAt = this.now();
339
+ return task();
340
+ };
341
+ const result = this.tail.then(runTask, runTask);
342
+ this.tail = result.catch(() => undefined);
343
+ return result;
344
+ }
345
+ }
346
+ function formatSearchHttpError(provider, status, statusText, body, apiKey) {
347
+ const providerLabel = getProviderLabel(provider);
348
+ const errorPreview = cleanSearchText(redactSearchSecret(body, apiKey), 300);
349
+ if (status === 429) {
350
+ const preview = errorPreview ? ` Upstream details: ${redactSearchSecret(errorPreview, apiKey)}` : "";
351
+ return `${providerLabel} search rate limit exceeded (HTTP 429). Do not issue parallel or repeated agent_browser_web_search calls; use one high-signal query, inspect those results, then wait before retrying or ask the user to adjust their ${providerLabel} API plan/limits.${preview}`;
352
+ }
353
+ return `${providerLabel} search failed with HTTP ${status}: ${errorPreview ? redactSearchSecret(errorPreview, apiKey) : statusText}`;
354
+ }
355
+ async function fetchSearchJson(options) {
356
+ if (options.signal?.aborted) {
357
+ throw options.signal.reason ?? new Error(options.cancelMessage);
358
+ }
359
+ const controller = new AbortController();
360
+ const timeout = setTimeout(() => controller.abort(new Error(options.timeoutMessage)), options.timeoutMs);
361
+ const abort = () => controller.abort(options.signal?.reason ?? new Error(options.cancelMessage));
362
+ options.signal?.addEventListener("abort", abort, { once: true });
363
+ try {
364
+ const response = await fetch(options.request, {
365
+ ...(options.init ?? {}),
366
+ signal: controller.signal,
367
+ });
368
+ const text = await response.text();
369
+ if (!response.ok) {
370
+ throw new Error(formatSearchHttpError(options.provider, response.status, response.statusText, text, options.apiKey));
371
+ }
372
+ try {
373
+ return JSON.parse(text);
374
+ }
375
+ catch (error) {
376
+ throw new Error(`${options.invalidJsonMessage}: ${error instanceof Error ? error.message : String(error)}`);
377
+ }
378
+ }
379
+ finally {
380
+ clearTimeout(timeout);
381
+ options.signal?.removeEventListener("abort", abort);
382
+ }
383
+ }
384
+ export async function fetchBraveSearchJson(url, apiKey, signal) {
385
+ return fetchSearchJson({
386
+ apiKey,
387
+ cancelMessage: "Brave search cancelled",
388
+ init: {
389
+ headers: {
390
+ Accept: "application/json",
391
+ "X-Subscription-Token": apiKey,
392
+ },
393
+ },
394
+ invalidJsonMessage: "Brave search returned invalid JSON",
395
+ provider: "brave",
396
+ request: url,
397
+ signal,
398
+ timeoutMessage: "Brave search timed out",
399
+ timeoutMs: SEARCH_REQUEST_TIMEOUT_MS,
400
+ });
401
+ }
402
+ function getExaRequestTimeoutMs(searchType) {
403
+ return searchType?.startsWith("deep") ? EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS : SEARCH_REQUEST_TIMEOUT_MS;
404
+ }
405
+ export async function fetchExaSearchJson(body, apiKey, signal, timeoutMs = SEARCH_REQUEST_TIMEOUT_MS) {
406
+ return fetchSearchJson({
407
+ apiKey,
408
+ cancelMessage: "Exa search cancelled",
409
+ init: {
410
+ body: JSON.stringify(body),
411
+ headers: {
412
+ Accept: "application/json",
413
+ "Content-Type": "application/json",
414
+ "x-api-key": apiKey,
415
+ },
416
+ method: "POST",
417
+ },
418
+ invalidJsonMessage: "Exa search returned invalid JSON",
419
+ provider: "exa",
420
+ request: EXA_SEARCH_ENDPOINT,
421
+ signal,
422
+ timeoutMessage: "Exa search timed out",
423
+ timeoutMs,
424
+ });
425
+ }
426
+ const BRAVE_WEB_SEARCH_ADAPTER = {
427
+ provider: "brave",
428
+ buildRequest(params) {
429
+ return buildBraveSearchUrl({
430
+ query: params.query,
431
+ count: params.count,
432
+ offset: params.offset,
433
+ country: params.country,
434
+ searchLang: params.searchLang,
435
+ safesearch: params.safesearch,
436
+ freshness: params.freshness,
437
+ });
438
+ },
439
+ fetchJson(request, apiKey, signal) {
440
+ return fetchBraveSearchJson(request, apiKey, signal);
441
+ },
442
+ normalizeResponse(response, params) {
443
+ return {
444
+ results: (response.web?.results ?? [])
445
+ .map(normalizeBraveSearchResult)
446
+ .filter((result) => Boolean(result)),
447
+ returnedQuery: cleanSearchText(response.query?.altered, 300) ?? cleanSearchText(response.query?.original, 300) ?? params.query,
448
+ };
449
+ },
450
+ };
451
+ const EXA_WEB_SEARCH_ADAPTER = {
452
+ provider: "exa",
453
+ buildRequest(params) {
454
+ const searchType = params.searchType ?? "auto";
455
+ return {
456
+ body: buildExaSearchRequestBody({
457
+ query: params.query,
458
+ count: params.count,
459
+ offset: params.offset,
460
+ country: params.country,
461
+ safesearch: params.safesearch,
462
+ freshness: params.freshness,
463
+ searchType,
464
+ }),
465
+ timeoutMs: getExaRequestTimeoutMs(searchType),
466
+ };
467
+ },
468
+ fetchJson(request, apiKey, signal) {
469
+ return fetchExaSearchJson(request.body, apiKey, signal, request.timeoutMs);
470
+ },
471
+ normalizeResponse(response, params) {
472
+ const searchType = params.searchType ?? "auto";
473
+ return {
474
+ extraDetails: {
475
+ requestId: cleanSearchText(response.requestId, 120),
476
+ searchType: cleanSearchText(response.searchType, 80) ?? searchType,
477
+ },
478
+ results: (response.results ?? [])
479
+ .map(normalizeExaSearchResult)
480
+ .filter((result) => Boolean(result))
481
+ .slice(params.offset, params.offset + params.count),
482
+ returnedQuery: params.query,
483
+ };
484
+ },
485
+ };
486
+ export const WEB_SEARCH_PROVIDER_ADAPTERS = {
487
+ exa: EXA_WEB_SEARCH_ADAPTER,
488
+ brave: BRAVE_WEB_SEARCH_ADAPTER,
489
+ };
490
+ export function getWebSearchProviderAdapter(provider) {
491
+ return WEB_SEARCH_PROVIDER_ADAPTERS[provider];
492
+ }
493
+ function buildMissingCredentialError(provider) {
494
+ if (provider === "brave")
495
+ return "agent_browser_web_search provider brave was requested but no BRAVE_API_KEY/config credential resolved.";
496
+ if (provider === "exa")
497
+ return "agent_browser_web_search provider exa was requested but no EXA_API_KEY/config credential resolved.";
498
+ return "No Exa or Brave web search credential resolved. Configure webSearch.exaApiKey or webSearch.braveApiKey, or load EXA_API_KEY/BRAVE_API_KEY in the runtime environment.";
499
+ }
500
+ export function createAgentBrowserWebSearchTool(configState, options = {}) {
501
+ const requestGate = new WebSearchRequestGate();
502
+ return {
503
+ name: AGENT_BROWSER_WEB_SEARCH_TOOL_NAME,
504
+ label: "Agent Browser Web Search",
505
+ description: `Search the web with Exa or Brave when configured. Returns up to ${MAX_SEARCH_RESULT_COUNT} concise web results.`,
506
+ promptSnippet: "Search the live web with Exa or Brave for current or external information.",
507
+ promptGuidelines: [
508
+ "Use agent_browser_web_search when live web search would help answer the task, find current external information, or discover candidate URLs for agent_browser.",
509
+ "agent_browser_web_search chooses Exa or Brave from configured keys; when both are available, Exa is preferred by default unless webSearch.preferredProvider says otherwise. Use provider only when the user/config calls for a specific provider.",
510
+ "Prefer agent_browser_web_search over opening a search engine results page with agent_browser when a quick result list is enough; use agent_browser for interaction, DOM, screenshots, or auth.",
511
+ "Do not issue parallel or repeated agent_browser_web_search calls; use one high-signal query, inspect the results, then only run a focused follow-up if needed. If the provider returns HTTP 429, stop searching and tell the user the API plan/rate limit needs time or a plan change.",
512
+ "After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
513
+ ],
514
+ parameters: AgentBrowserWebSearchParams,
515
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
516
+ const runtimeConfigState = ctx ? options.loadConfigState?.(ctx) ?? configState : configState;
517
+ if (runtimeConfigState.errors.length > 0) {
518
+ throw new Error(`agent_browser_web_search config is invalid: ${runtimeConfigState.errors.join("; ")}`);
519
+ }
520
+ if (!runtimeConfigState.webSearchEnabled) {
521
+ throw new Error("agent_browser_web_search is disabled by pi-agent-browser-native config.");
522
+ }
523
+ const requestedProvider = params.provider ?? "auto";
524
+ const resolved = await resolvePreferredWebSearchCredential(runtimeConfigState, { provider: requestedProvider, signal });
525
+ if (!resolved)
526
+ throw new Error(buildMissingCredentialError(requestedProvider));
527
+ const query = params.query.trim();
528
+ if (!query)
529
+ throw new Error("query must not be blank");
530
+ const count = Math.min(Math.max(params.count ?? DEFAULT_SEARCH_RESULT_COUNT, 1), MAX_SEARCH_RESULT_COUNT);
531
+ const offset = Math.max(params.offset ?? 0, 0);
532
+ const adapter = getWebSearchProviderAdapter(resolved.provider);
533
+ const executionParams = {
534
+ country: params.country,
535
+ count,
536
+ freshness: params.freshness,
537
+ offset,
538
+ query,
539
+ safesearch: params.safesearch,
540
+ searchLang: params.searchLang,
541
+ searchType: params.searchType ?? "auto",
542
+ };
543
+ const request = adapter.buildRequest(executionParams);
544
+ const data = await requestGate.run(signal, () => adapter.fetchJson(request, resolved.credential.value, signal));
545
+ const normalized = adapter.normalizeResponse(data, executionParams);
546
+ const details = {
547
+ provider: adapter.provider,
548
+ query,
549
+ returnedQuery: normalized.returnedQuery,
550
+ count,
551
+ offset,
552
+ ...normalized.extraDetails,
553
+ fetchedAt: new Date().toISOString(),
554
+ results: normalized.results,
555
+ };
556
+ return {
557
+ content: [{ type: "text", text: formatSearchResults(adapter.provider, normalized.returnedQuery, normalized.results) }],
558
+ details,
559
+ };
560
+ },
561
+ };
562
+ }
@@ -23,7 +23,7 @@ V1 exposes one native browser tool:
23
23
 
24
24
  It may also expose one optional companion tool:
25
25
 
26
- - `agent_browser_web_search`, registered only when an Exa or Brave Search credential source is configured or resolvable and `webSearch.enabled` is not false
26
+ - `agent_browser_web_search`, available when an Exa or Brave Search credential source is configured or resolvable from startup config or trusted session config and runtime `webSearch.enabled` is not false
27
27
 
28
28
  Why:
29
29
  - keeps browser automation centered on `agent_browser`
@@ -68,11 +68,11 @@ Pi docs use `settings.json` for package/resource loading and filtering, not arbi
68
68
  - project-local: `.pi/config/pi-agent-browser-native/config.json`
69
69
  - explicit override: `PI_AGENT_BROWSER_CONFIG=/path/to/config.json`
70
70
 
71
- Config layers merge in that order: global, project, override. The shared policy module (`extensions/agent-browser/lib/config-policy.js`) owns provider descriptors, environment variable names, config keys, project-local credential safety, developer-trusted project layer inclusion, layer validation/merge, redacted status projection, and credential summaries for both runtime config loading and the package config helper. Under Pi 0.79+, globally installed or CLI-loaded extensions are developer-trusted code, so this extension reads `.pi/config/pi-agent-browser-native/config.json` by default and skips that project layer when Pi reports the project is untrusted or when launched with `--no-approve`. Global config and explicit `PI_AGENT_BROWSER_CONFIG` overrides remain available either way. The config reader accepts v1 fields for `webSearch.enabled`, `webSearch.preferredProvider`, `webSearch.exaApiKey`, `webSearch.braveApiKey`, and conservative browser defaults such as `browser.defaultProfile` and `browser.executablePath`. Web-search key fields follow Pi model/provider-style value resolution for trusted global/override config: literal values, `$ENV_VAR` / `${ENV_VAR}` interpolation, escapes (`$$`, `$!`), and leading `!command` resolved at request time. Project-local plaintext, interpolation-literal, malformed, and command-backed web-search keys are rejected because project config can be copied, committed, or supplied by a repository; project config may use only the matching provider env ref (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa, `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave), so a repository cannot redirect search credentials to arbitrary host env vars. `EXA_API_KEY` and `BRAVE_API_KEY` remain environment fallbacks when no config credential source exists for that provider. Browser default values keep their source scope; profile/executable prompt guidance is emitted only from trusted global or explicit override config, not from project-local config that could steer host profile or executable choices.
71
+ Config layers merge in that order: global, project, override. The shared policy module (`extensions/agent-browser/lib/config-policy.js`) owns provider descriptors, environment variable names, config keys, credential source parsing, developer-trusted project layer inclusion, layer validation/merge, redacted status projection, and credential summaries for both runtime config loading and the package config helper. Under Pi 0.79+, globally installed or CLI-loaded extensions are developer-trusted code, so this extension reads `.pi/config/pi-agent-browser-native/config.json` by default and skips that project layer when Pi reports the project is untrusted or when launched with `--no-approve`. Global config and explicit `PI_AGENT_BROWSER_CONFIG` overrides remain available either way. The config reader accepts v1 fields for `webSearch.enabled`, `webSearch.preferredProvider`, `webSearch.exaApiKey`, `webSearch.braveApiKey`, and conservative browser defaults such as `browser.defaultProfile` and `browser.executablePath`. Web-search key fields follow Pi model/provider-style value resolution from any loaded layer: literal values, `$ENV_VAR` / `${ENV_VAR}` interpolation, escapes (`$$`, `$!`), and leading `!command` resolved at request time. `EXA_API_KEY` and `BRAVE_API_KEY` remain environment fallbacks when no config credential source exists for that provider. Browser default values keep their source scope; prompt guidance is emitted from the highest-priority loaded layer, including project config when Pi trust/loading allows it.
72
72
 
73
- `agent_browser_web_search` registration is conditional. `webSearch.enabled: false` disables registration even when environment keys are present, but it is evaluated after the available config layers merge. A global disable is the normal user default and can still be overridden by project config or `PI_AGENT_BROWSER_CONFIG`; a project disable applies to one repo; an explicit `PI_AGENT_BROWSER_CONFIG` file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Literal and env-backed sources must resolve at startup; command-backed sources are considered configured without running the command until tool execution, so secret managers do not slow startup or prompt unexpectedly. The tool resolves the selected key lazily, chooses Exa or Brave from available credentials (preferring Exa by default unless `webSearch.preferredProvider` says otherwise), then follows one provider-agnostic execution path through provider adapters for request building, HTTP JSON fetch, response normalization, and provider-specific detail fields. It calls Exa `/search` with highlights or Brave Search and returns compact result details without exposing keys.
73
+ `agent_browser_web_search` availability is conditional. Startup registration uses global, override, and environment fallback config without reading project-local config before Pi trust context exists; trusted project config can register the companion tool on `session_start`, and every execution reloads the final session config so `webSearch.enabled: false` still prevents a request even if a startup credential made the tool visible. A global disable is the normal user default and can still be overridden by project config or `PI_AGENT_BROWSER_CONFIG`; a project disable applies to one repo; an explicit `PI_AGENT_BROWSER_CONFIG` file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Literal and env-backed sources must resolve before they make the tool available; command-backed sources are considered configured without running the command until tool execution, so secret managers do not slow startup or prompt unexpectedly. The tool resolves the selected key lazily, chooses Exa or Brave from available credentials (preferring Exa by default unless `webSearch.preferredProvider` says otherwise), then follows one provider-agnostic execution path through provider adapters for request building, HTTP JSON fetch, response normalization, and provider-specific detail fields. It calls Exa `/search` with highlights or Brave Search and returns compact result details without exposing keys.
74
74
 
75
- Browser default config is intentionally conservative. It can add prompt guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables, but current releases do not auto-inject `--profile` or `--executable-path` into every launch. Project-local browser config is status-only for launch guidance unless the profile policy is `explicit-only`; executable guidance must come from global or explicit override config. Automatic launch-default mutation would affect privacy, browser state, and host executable choice, so it needs a separate explicit design and test pass.
75
+ Browser default config is intentionally advisory. It can add prompt guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables, but current releases do not auto-inject `--profile` or `--executable-path` into every launch. Loaded project config can provide the same guidance as global and override config; Pi/project trust decides whether that project layer is loaded. Automatic launch-default mutation would affect privacy, browser state, and host executable choice, so it needs a separate explicit design and test pass.
76
76
 
77
77
  ### Prompt guidance budget
78
78
 
@@ -199,7 +199,7 @@ This keeps the product centered on native tool usage instead of auxiliary skill
199
199
  ### `pi-agent-browser-native` owns
200
200
 
201
201
  - tool registration and schema (including the optional `semanticAction` compilation path to upstream `find` or `select`)
202
- - subprocess execution and JSON parsing through a filtered child environment (`buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`): copies an allowlisted inherited-name set plus every parent `AGENT_BROWSER_*` variable and provider-related prefixes (`AGENTCORE_*`, `AI_GATEWAY_*`, `BROWSERBASE_*`, `BROWSERLESS_*`, `BROWSER_USE_*`, `KERNEL_*`, `XDG_*`) instead of cloning the full parent process environment
202
+ - subprocess execution and JSON parsing through `buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`: copies the parent process environment so user-approved provider credentials and other runtime variables reach upstream, then applies wrapper overrides such as the managed socket directory and clamped default operation timeout
203
203
  - clear missing-binary errors
204
204
  - compact result summaries, including presentation-time redaction: stateful browser-context commands (`auth`, `cookies`, `storage`, `dialog`, `frame`, `state`) use field-aware value redaction and compact formatters, while other structured upstream JSON (for example `network`, `diff`, `trace` / `profiler` / `record`, `console` / `errors` / `highlight` / `inspect` / `clipboard`, `stream`, `dashboard`, and `chat`) is passed through `redactPresentationData` in `extensions/agent-browser/lib/results/presentation.ts` so model-facing `details.data` and batch roll-ups stay compact and do not echo bearer tokens, proxy passwords, or similar fields verbatim; `redactInvocationArgs` in `extensions/agent-browser/lib/runtime.ts` masks trailing values for sensitive global flags such as `--body`, `--headers`, `--password`, and `--proxy`, preserves positional rules for `cookies set` and `storage local|session set`, and nested `batch` steps use the same argv and error-body scrubbing before echoing commands or errors
205
205
  - bounded machine-readable outcome metadata on tool `details` (`resultCategory`, `successCategory`, `failureCategory`, optional `nextActions`, optional `pageChangeSummary` with per-step summaries on `batch`, optional `artifactVerification` with the same shape on each successful `batchSteps[]` row) so agents can branch without parsing prose; enums, classifier precedence, and generic follow-up payloads are implemented under `extensions/agent-browser/lib/results/` in focused modules (`contracts.ts` for shared types, `categories.ts` for `classifyAgentBrowserSuccessCategory` / `classifyAgentBrowserFailureCategory` / `buildAgentBrowserResultCategoryDetails`, `action-recommendations.ts` for `buildAgentBrowserNextActions`, `next-actions.ts` for the `AgentBrowserNextAction` shape and merge helpers, `recovery-actions.ts` for recovery id registries and `buildRecoveryNextActions`, `network.ts` for `classifyNetworkRequestFailure` / `summarizeNetworkFailures`, and related helpers; `shared.ts` in that directory is a **re-export-only** barrel so historical `./results/shared.js` imports keep working without growing a monolith). Per-session tab target, `refSnapshot` alignment, invalidation, and tab pinning observations flow through `extensions/agent-browser/lib/session-page-state.ts` from `extensions/agent-browser/index.ts`. Compact page-change summaries and artifact verification rollups are built in `extensions/agent-browser/lib/results/presentation.ts` (`buildPageChangeSummary`, `buildArtifactVerificationSummary`), and the human contract lives in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). Real Pi custom tools otherwise only mark a row failed when `execute` throws, so the extension also registers `pi.on("tool_result", …)` and patches `agent_browser` results whose `details.resultCategory` is `failure` to set `isError: true`. Prose results also receive a short category notice, while caller-requested `--json` results with parseable JSON content keep that text unchanged so JSONL transcripts, UI affordances, and the machine-readable contract stay aligned for wrapper-side reclassifications such as `qa-failure` (`buildAgentBrowserToolResultPatch` in `extensions/agent-browser/lib/pi-tool-rendering.ts`; transcript semantics in the same contract doc)
@@ -748,7 +748,7 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
748
748
  npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
749
749
  ```
750
750
 
751
- The optional `agent_browser_web_search` tool is registered only when Exa or Brave credentials are available and the final available config has not set `webSearch.enabled` to `false`. It is a separate custom tool, not an `agent_browser` input mode, and does not launch a browser. Use it when current/live external web information would help; use `agent_browser` for browser interaction, screenshots, authenticated/profile pages, and DOM inspection. Disable scope is explicit: `web-search disable --global` sets the normal user default, `web-search disable --project` disables it for one repo, and a `PI_AGENT_BROWSER_CONFIG` override containing `{ "version": 1, "webSearch": { "enabled": false } }` wins over both for a hard per-run disable. Project-local plaintext, custom env aliases, interpolation-literal, malformed, and command-backed web-search keys are refused; project config may only use the matching provider env refs (`$EXA_API_KEY` / `${EXA_API_KEY}` for Exa and `$BRAVE_API_KEY` / `${BRAVE_API_KEY}` for Brave). `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`. For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`; use `fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning` only when the task needs that latency/depth tradeoff.
751
+ The optional `agent_browser_web_search` tool is available when Exa or Brave credentials are visible from startup config or trusted session config and the runtime config has not set `webSearch.enabled` to `false`. It is a separate custom tool, not an `agent_browser` input mode, and does not launch a browser. Use it when current/live external web information would help; use `agent_browser` for browser interaction, screenshots, authenticated/profile pages, and DOM inspection. Disable scope is explicit: `web-search disable --global` sets the normal user default, `web-search disable --project` disables it for one repo, and a `PI_AGENT_BROWSER_CONFIG` override containing `{ "version": 1, "webSearch": { "enabled": false } }` wins over both for a hard per-run disable. Loaded config may use plaintext, custom env aliases, interpolation literals, malformed-or-late-bound `$` values, and command-backed web-search keys; the resolved secret reaches the provider request while model-facing tool output and status text stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`. For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`; use `fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning` only when the task needs that latency/depth tradeoff.
752
752
 
753
753
  Example config:
754
754
 
@@ -771,7 +771,7 @@ Example config:
771
771
  }
772
772
  ```
773
773
 
774
- Browser default config is conservative: it adds agent guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally or through `PI_AGENT_BROWSER_CONFIG`; project-local browser config is loaded by default but is never trusted to steer host executable/profile prompt guidance. Ask the agent to run `profiles` and `doctor` when profile resolution fails, then use the reported Chrome profile directory name, a full profile/user-data directory path if upstream accepts one, or the configured `browser.executablePath` with top-level `sessionMode: "fresh"`.
774
+ Browser default config is conservative: it adds agent guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally, in trusted project config, or through `PI_AGENT_BROWSER_CONFIG`. Ask the agent to run `profiles` and `doctor` when profile resolution fails, then use the reported Chrome profile directory name, a full profile/user-data directory path if upstream accepts one, or the configured `browser.executablePath` with top-level `sessionMode: "fresh"`.
775
775
 
776
776
  ## Important global flags, config, and environment
777
777
 
@@ -819,7 +819,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
819
819
  - `--confirm-interactive`: interactive confirmations; auto-denies when stdin is not a TTY. Environment: `AGENT_BROWSER_CONFIRM_INTERACTIVE`.
820
820
  - `-p, --provider <name>`: provider such as `ios`, `browserbase`, `kernel`, `browseruse`, `browserless`, or `agentcore`. Environment: `AGENT_BROWSER_PROVIDER`.
821
821
  - `--device <name>`: iOS device name. Environment: `AGENT_BROWSER_IOS_DEVICE`.
822
- - Provider-specific iOS examples from upstream include `agent-browser -p ios device list`, `agent-browser -p ios swipe up`, and `agent-browser -p ios tap @e1`; in pi, pass those tokens through `args` rather than bash. iOS requires external Xcode/Appium setup, and cloud providers (`browserbase`, `kernel`, `browseruse`, `browserless`, `agentcore`) require their upstream accounts, credentials, and provider-specific environment variables. Common forwarded provider variables include `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `BROWSERLESS_API_KEY`, `BROWSERLESS_API_URL`, `BROWSERLESS_BROWSER_TYPE`, `BROWSERLESS_STEALTH`, `BROWSERLESS_TTL`, `BROWSER_USE_API_KEY`, `KERNEL_API_KEY`, `KERNEL_HEADLESS`, `KERNEL_STEALTH`, `KERNEL_TIMEOUT_SECONDS`, `KERNEL_PROFILE_NAME`, `AGENTCORE_API_KEY`, `AGENTCORE_REGION`, `AGENTCORE_BROWSER_ID`, `AGENTCORE_PROFILE_ID`, `AGENTCORE_SESSION_TIMEOUT`, plus AWS names used by AgentCore such as `AWS_PROFILE`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`, and `AWS_DEFAULT_REGION`. The wrapper forwards provider flags/env and stays thin; it does not emulate provider setup or cloud browser behavior.
822
+ - Provider-specific iOS examples from upstream include `agent-browser -p ios device list`, `agent-browser -p ios swipe up`, and `agent-browser -p ios tap @e1`; in pi, pass those tokens through `args` rather than bash. iOS requires external Xcode/Appium setup, and cloud providers (`browserbase`, `kernel`, `browseruse`, `browserless`, `agentcore`) require their upstream accounts, credentials, and provider-specific environment variables. Common provider variables include `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `BROWSERLESS_API_KEY`, `BROWSERLESS_API_URL`, `BROWSERLESS_BROWSER_TYPE`, `BROWSERLESS_STEALTH`, `BROWSERLESS_TTL`, `BROWSER_USE_API_KEY`, `KERNEL_API_KEY`, `KERNEL_HEADLESS`, `KERNEL_STEALTH`, `KERNEL_TIMEOUT_SECONDS`, `KERNEL_PROFILE_NAME`, `AGENTCORE_API_KEY`, `AGENTCORE_REGION`, `AGENTCORE_BROWSER_ID`, `AGENTCORE_PROFILE_ID`, `AGENTCORE_SESSION_TIMEOUT`, plus AWS names used by AgentCore such as `AWS_PROFILE`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`, and `AWS_DEFAULT_REGION`. The wrapper forwards parent environment variables plus provider flags and stays thin; it does not emulate provider setup or cloud browser behavior.
823
823
  - `--model <name>`: AI model for `chat`. Environment: `AI_GATEWAY_MODEL`.
824
824
  - `-v, --verbose`: show tool commands and raw output.
825
825
  - `-q, --quiet`: show only AI text responses.
@@ -837,7 +837,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
837
837
 
838
838
  Use `--config <path>` to load a specific config file. Boolean flags accept optional `true` or `false` values, such as `--headed false`, to override config. Browser extensions from user and project configs are merged rather than replaced.
839
839
 
840
- Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGENT_BROWSER_STREAM_PORT`, `AGENT_BROWSER_IDLE_TIMEOUT_MS`, `AGENT_BROWSER_ENCRYPTION_KEY`, `AGENT_BROWSER_STATE_EXPIRE_DAYS`, `AGENT_BROWSER_IOS_DEVICE`, `AGENT_BROWSER_IOS_UDID`, `AI_GATEWAY_URL`, `AI_GATEWAY_API_KEY`, the provider credential names listed above, and AWS credential names when using AgentCore. The upstream child also receives every parent variable whose name starts with `AGENT_BROWSER_`, `AGENTCORE_`, `AI_GATEWAY_`, `BROWSERBASE_`, `BROWSERLESS_`, `BROWSER_USE_`, `KERNEL_`, or `XDG_`, plus the explicit inherited-name allowlist in `buildAgentBrowserProcessEnv` (`extensions/agent-browser/lib/process.ts`).
840
+ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGENT_BROWSER_STREAM_PORT`, `AGENT_BROWSER_IDLE_TIMEOUT_MS`, `AGENT_BROWSER_ENCRYPTION_KEY`, `AGENT_BROWSER_STATE_EXPIRE_DAYS`, `AGENT_BROWSER_IOS_DEVICE`, `AGENT_BROWSER_IOS_UDID`, `AI_GATEWAY_URL`, `AI_GATEWAY_API_KEY`, provider credential names, and AWS credential names when using AgentCore. The upstream child receives the parent environment plus wrapper overrides such as the managed socket directory and clamped default operation timeout (`buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`). Model-facing output still redacts recognized secret values.
841
841
 
842
842
  ## Wrapper-specific behavior worth knowing
843
843