pi-agent-browser-native 0.2.48 → 0.2.49

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 (185) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +16 -6
  3. package/dist/extensions/agent-browser/index.js +785 -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 +686 -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 +448 -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 +960 -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 +816 -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/RELEASE.md +22 -11
  95. package/docs/SUPPORT_MATRIX.md +4 -3
  96. package/package.json +9 -5
  97. package/scripts/config.mjs +8 -2
  98. package/scripts/doctor.mjs +8 -7
  99. package/extensions/agent-browser/index.ts +0 -961
  100. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  101. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  102. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  103. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  104. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  105. package/extensions/agent-browser/lib/config-policy.js +0 -690
  106. package/extensions/agent-browser/lib/config.ts +0 -211
  107. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  108. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  109. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  110. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  111. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  112. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  113. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  114. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  115. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  116. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  117. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  118. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  119. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  120. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  121. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  122. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  123. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  124. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  125. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  126. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  127. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  128. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  129. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  130. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  131. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  132. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  133. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  136. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  138. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  139. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  140. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  141. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  142. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  143. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  144. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  145. package/extensions/agent-browser/lib/playbook.ts +0 -142
  146. package/extensions/agent-browser/lib/process.ts +0 -516
  147. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  148. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  149. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  150. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  151. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  152. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  153. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  154. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  155. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  156. package/extensions/agent-browser/lib/results/network.ts +0 -78
  157. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  158. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  159. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  160. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  161. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  162. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  163. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  164. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  165. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  166. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  167. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  168. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  169. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  170. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  171. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  172. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  173. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  174. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  175. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  176. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  177. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  178. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  179. package/extensions/agent-browser/lib/results/text.ts +0 -40
  180. package/extensions/agent-browser/lib/runtime.ts +0 -988
  181. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  182. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  183. package/extensions/agent-browser/lib/temp.ts +0 -577
  184. package/extensions/agent-browser/lib/web-search.ts +0 -728
  185. /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
+ }
package/docs/RELEASE.md CHANGED
@@ -38,7 +38,7 @@ For PR-ready local confidence before release-only lifecycle and platform cost, r
38
38
  npm run verify -- pre-pr
39
39
  ```
40
40
 
41
- `pre-pr` composes the default gate with `npm run verify -- package`: generated docs, TypeScript, the full unit/fake suite, live command-reference sampling, and package-content verification. It intentionally does not run lifecycle, packaged Pi smoke, Crabbox platform smoke, real-upstream, dogfood, or benchmark modes.
41
+ `pre-pr` composes the default gate with `npm run verify -- package`: generated docs, clean `dist/` build, TypeScript, the full unit/fake suite, live command-reference sampling, and package-content verification. It intentionally does not run lifecycle, packaged Pi smoke, Crabbox platform smoke, startup-profile, real-upstream, dogfood, or benchmark modes.
42
42
 
43
43
  `npm run verify -- release` runs:
44
44
 
@@ -47,9 +47,18 @@ npm run verify -- pre-pr
47
47
  3. `npm run verify -- package-pi`, which first validates package contents via `npm pack --json --dry-run` and then smoke-loads the packed package in Pi isolation
48
48
  4. `npm run smoke:platform:doctor` and the full Crabbox matrix from [`platform-smoke.md`](platform-smoke.md): macOS SSH, Ubuntu local-container, and native Windows Parallels targets running fast target-local `platform-build` plus `browser-dogfood-smoke`
49
49
 
50
- `npm publish` runs npm’s `prepublishOnly` script from `package.json`, which executes the same `npm run verify -- release` gate and then `npm pack --dry-run`. That concatenated gate is everything in the default `npm run verify` step (generated playbook drift, TypeScript, the unit/fake suite, generated command-reference blocks, and live upstream command-reference sampling against the targeted `agent-browser` on `PATH`), the configured-source lifecycle harness, the packaged Pi smoke in `package-pi`, and the release-blocking Crabbox platform matrix. Using `npm publish --ignore-scripts` skips that contract intentionally.
50
+ `npm publish` runs npm’s `prepublishOnly` script from `package.json`, which executes the same `npm run verify -- release` gate and then `npm pack --dry-run`. That concatenated gate is everything in the default `npm run verify` step (generated playbook drift, clean `dist/` build, TypeScript, the unit/fake suite, generated command-reference blocks, and live upstream command-reference sampling against the targeted `agent-browser` on `PATH`), the configured-source lifecycle harness, the packaged Pi smoke in `package-pi`, and the release-blocking Crabbox platform matrix. Using `npm publish --ignore-scripts` skips that contract intentionally.
51
51
 
52
- `prepublishOnly` intentionally does **not** run the standalone host-only `npm run verify -- real-upstream`, `npm run verify -- dogfood`, or `npm run verify -- benchmark` modes; those remain separate `npm run verify` modes in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs). The platform matrix includes its own fast target-local build/package gate and browser dogfood suite, and is automated through the `release` slice.
52
+ `prepublishOnly` intentionally does **not** run the standalone host-only `npm run verify -- startup-profile`, `npm run verify -- real-upstream`, `npm run verify -- dogfood`, or `npm run verify -- benchmark` modes; those remain separate `npm run verify` modes in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs). The platform matrix includes its own fast target-local build/package gate and browser dogfood suite, and is automated through the `release` slice.
53
+
54
+ Run the opt-in startup profiler whenever package layout, the compiled entrypoint, top-level imports, schema registration, or prompt/config startup logic changes:
55
+
56
+ ```bash
57
+ npm run build
58
+ npm run verify -- startup-profile --samples 3
59
+ ```
60
+
61
+ The profiler first clean-builds `dist/`, then records only direct package entrypoint import/factory timing in fresh Node processes, writes `.artifacts/startup-profile/latest.json`, and includes a safety block confirming it did not launch Pi, tmux, mise, npm, browsers, or `agent-browser`. Full Pi TUI ready-prompt profiling is intentionally excluded because repeated real Pi/tmux launches proved too invasive for routine verification on the operator machine.
53
62
 
54
63
  For a deterministic host-only real-browser wrapper smoke without model choice in the loop, run:
55
64
 
@@ -76,7 +85,7 @@ Every release also requires interactive `tmux`-driven Pi dogfood with the native
76
85
 
77
86
  When reviewing saved session JSONL after a failed smoke or a `qa` preset that reclassified an upstream-successful batch, expect `agent_browser` tool rows to carry `isError: true` whenever `details.resultCategory` is `failure`. For normal prose output, model-visible text should end with a `Pi tool isError: true` category line; for caller-requested `--json` output, the hook preserves parseable JSON and only patches `isError`. The extension applies that patch on the `tool_result` path so Pi’s transcript matches the wrapper contract ([`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details)). Preserve a normal Pi session directory for those checks; avoiding `--no-session` keeps this evidence intact ([`AGENTS.md`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/AGENTS.md) preferred validation workflow).
78
87
 
79
- The configured-source lifecycle regression harness is required before release because it launches an interactive `pi` process under `tmux` with `--approve` and validates `/reload`, full relaunch with the same exact Pi 0.79 `--session-id`, managed-session continuity, persisted artifacts, and Pi failure-patch behavior. Branch-backed `session_tree` rehydration and cleanup ownership are validated by focused extension harness tests:
88
+ The configured-source lifecycle regression harness is required before release because it launches an interactive `pi` process under `tmux` with `--approve` and validates `/reload`, full relaunch with the same exact Pi 0.79 `--session-id`, managed-session continuity, persisted artifacts, compiled-entrypoint pickup after process restart, and Pi failure-patch behavior. Branch-backed `session_tree` rehydration and cleanup ownership are validated by focused extension harness tests:
80
89
 
81
90
  ```bash
82
91
  npm run verify -- lifecycle
@@ -155,7 +164,7 @@ Evaluator expectations after the queued Sauce Demo fixes: the agent should indep
155
164
  [`scripts/agent-browser-efficiency-benchmark.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/agent-browser-efficiency-benchmark.mjs) is an accounting-only benchmark: it does not shell out to `agent-browser`, launch a browser, or read or write Pi sessions. It models representative `agent_browser` call shapes (including optional `stdin` for `batch` and top-level `job`, `qa`, or experimental `sourceLookup` / `networkSourceLookup` objects that compile to batch) and aggregates success rate, tool-call counts, UTF-8 size of model-visible strings, stale-ref failure and recovery counts, artifact success, distinct failure-category coverage, and summed elapsed-time estimates. When extending scenarios, keep them aligned with the closed `RQ-0068` “no reusable recipe layer” rationale in [`ARCHITECTURE.md`](ARCHITECTURE.md#no-reusable-recipe-layer-yet) (benchmark ids cited there are the canonical inventory for that evidence bar).
156
165
 
157
166
  - **During development:** `npm run benchmark:agent-browser` prints a Markdown report; `npm run benchmark:agent-browser -- --json` saves machine-readable metrics; `npm run benchmark:agent-browser -- --compare path/to/prior.json` fails with exit code `1` on regressions (see the script’s `--help` for exit codes). Optional `--sample-jsonl path/to/session.jsonl` adds a `jsonlSample` section with real UTF-8 byte totals and per-workflow/overall p95 sizes for model-visible `agent_browser` tool-result text without changing deterministic scenario metrics; comparison ignores `jsonlSample` blocks.
158
- - **Default gate:** `npm run verify` checks generated playbook drift, runs `tsc --noEmit`, runs the full unit/fake suite under `test/**/*.test.ts` with Node test concurrency pinned to `1` (including [`test/agent-browser.efficiency-benchmark.test.ts`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/test/agent-browser.efficiency-benchmark.test.ts) for scenario coverage and comparison behavior), verifies generated command-reference baseline blocks, and samples live upstream command-reference tokens. It does not spawn the standalone benchmark script’s JSON/Markdown run; that is what the opt-in slice below adds.
167
+ - **Default gate:** `npm run verify` checks generated playbook drift, clean-builds `dist/`, runs `tsc --noEmit`, runs the full unit/fake suite under `test/**/*.test.ts` with Node test concurrency pinned to `1` (including [`test/agent-browser.efficiency-benchmark.test.ts`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/test/agent-browser.efficiency-benchmark.test.ts) for scenario coverage and comparison behavior), verifies generated command-reference baseline blocks, and samples live upstream command-reference tokens. It does not spawn the standalone benchmark script’s JSON/Markdown run; that is what the opt-in slice below adds.
159
168
  - **Pre-PR gate:** `npm run verify -- pre-pr` runs the default gate plus `npm run verify -- package` for larger handoffs that need package-content confidence without lifecycle, platform, real-upstream, dogfood, or benchmark cost.
160
169
  - **Opt-in slice:** `npm run verify -- benchmark` runs the benchmark script once with `--json` and then that same test module alone. It is intentionally **not** part of `npm run verify -- pre-pr` or `npm run verify -- release`, so routine handoff and publish gates stay decoupled from benchmark churn while still allowing a focused check after editing scenarios or `CURRENT_BENCHMARK_VERSION`.
161
170
 
@@ -168,9 +177,11 @@ Maintainer constraints for evolving scenarios and version bumps are summarized u
168
177
  - no repo-local `.pi/extensions/agent-browser.ts` autoload shim is present
169
178
  - `LICENSE` exists in the repo and the packed tarball
170
179
  - canonical published docs are present
180
+ - `npm pack --json --dry-run` runs the `prepack` build and packs the compiled `dist/extensions/agent-browser/index.js` entrypoint
181
+ - GitHub/source installs run the package `prepare` build so Pi can load the ignored compiled `dist/extensions/agent-browser/index.js` entrypoint from a fresh clone
171
182
  - the package-level doctor command and capability baseline are present
172
- - extension source files are present, including the split result-rendering modules required by the published facade
173
- - agent-only and superseded docs are absent from the tarball
183
+ - compiled extension runtime files are present, including the split result-rendering modules required by the published facade
184
+ - source-only, agent-only, and superseded docs are absent from the tarball
174
185
 
175
186
  `npm run verify -- package-pi` runs the same package-content checks and additionally confirms that:
176
187
 
@@ -187,7 +198,7 @@ Current forbidden packed files include:
187
198
  - `AGENTS.md`
188
199
  - archived planning drafts under `docs/archive/`
189
200
  - `.pi/extensions/agent-browser.ts`
190
- - test and repo-only maintenance files
201
+ - TypeScript extension source and other test/repo-only maintenance files
191
202
 
192
203
  For a full packed file listing:
193
204
 
@@ -203,7 +214,7 @@ Before publishing, validate both local-checkout modes without mixing their assum
203
214
 
204
215
  1. Install `agent-browser` separately.
205
216
  2. Launch `pi --approve --no-extensions -e .` from this trusted repository root. Omit `--approve` only when testing Pi's Project Trust prompt.
206
- 3. Confirm the checkout extension loads from `extensions/agent-browser/index.ts`.
217
+ 3. Confirm the checkout package loads the compiled `dist/extensions/agent-browser/index.js` entrypoint (run `npm run build` first after source edits).
207
218
  4. Run a smoke prompt that exercises `agent_browser`.
208
219
  5. Restart the `pi` process after extension edits; Pi settings and `/reload` are not the validation target in this isolated mode.
209
220
 
@@ -221,7 +232,7 @@ Run the automated harness for deterministic configured-source lifecycle regressi
221
232
  npm run verify -- lifecycle
222
233
  ```
223
234
 
224
- The harness creates an isolated `PI_CODING_AGENT_DIR`, writes settings with exactly one temporary configured package source, runs `pi` in `tmux` with `--approve`, default model **`zai/glm-5.1`**, and a deterministic `--session-id`, puts a deterministic fake `agent-browser` first on `PATH`, drives `/reload`, closes Pi, and relaunches with the same exact session id instead of typing `/resume`. It also asserts the JSONL session header id, same-page managed-session continuity, persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification. Per-step tmux waits default to **180000 ms** (three minutes) in [`scripts/verify-lifecycle.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/verify-lifecycle.mjs) (`DEFAULT_TIMEOUT_MS`); override with `--timeout-ms <ms>` when slower models or cold starts need more headroom. Override the model when needed:
235
+ The harness creates an isolated `PI_CODING_AGENT_DIR`, writes settings with exactly one temporary configured package source, runs `pi` in `tmux` with `--approve`, default model **`zai/glm-5.1`**, and a deterministic `--session-id`, puts a deterministic fake `agent-browser` first on `PATH`, drives `/reload`, closes Pi, and relaunches with the same exact session id instead of typing `/resume`. It also asserts the JSONL session header id, same-page managed-session continuity, compiled JS code pickup after full process relaunch, persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification. Per-step tmux waits default to **180000 ms** (three minutes) in [`scripts/verify-lifecycle.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/verify-lifecycle.mjs) (`DEFAULT_TIMEOUT_MS`); override with `--timeout-ms <ms>` when slower models or cold starts need more headroom. Override the model when needed:
225
236
 
226
237
  ```bash
227
238
  npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal
@@ -235,7 +246,7 @@ npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal --timeout-ms 60
235
246
 
236
247
  On failure it retains transcripts/session artifacts; on success it performs best-effort cleanup. It does not replace occasional real-browser manual smoke testing.
237
248
 
238
- **Lifecycle triage:** a timeout on sentinel `v2` after `/reload` often means Pi rejected reload while the TUI still showed `Working…` (`Wait for the current response to finish before reloading`), even when the session JSONL already has a final assistant message. Re-run with `--keep-artifacts --verbose`, inspect the retained pane capture, and confirm the configured model follows tool prompts reliably. Slower models may need a higher `--timeout-ms` than the **180000 ms** default.
249
+ **Lifecycle triage:** a timeout on sentinel `v2` after exact-session relaunch means the new compiled entrypoint did not load after process restart. A reload-step timeout or missing post-reload snapshot often means Pi rejected reload while the TUI still showed `Working…` (`Wait for the current response to finish before reloading`), even when the session JSONL already has a final assistant message. Re-run with `--keep-artifacts --verbose`, inspect the retained pane capture, and confirm the configured model follows tool prompts reliably. Slower models may need a higher `--timeout-ms` than the **180000 ms** default.
239
250
 
240
251
  ### Environment and automation pitfalls
241
252