@mercuryo-ai/agentbrowse 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 (301) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +335 -0
  3. package/dist/assistive-runtime.d.ts +110 -0
  4. package/dist/assistive-runtime.d.ts.map +1 -0
  5. package/dist/assistive-runtime.js +79 -0
  6. package/dist/assistive-runtime.test-support.d.ts +7 -0
  7. package/dist/assistive-runtime.test-support.d.ts.map +1 -0
  8. package/dist/assistive-runtime.test-support.js +106 -0
  9. package/dist/assistive-stagehand.d.ts +12 -0
  10. package/dist/assistive-stagehand.d.ts.map +1 -0
  11. package/dist/assistive-stagehand.js +10 -0
  12. package/dist/browser-session-state.d.ts +95 -0
  13. package/dist/browser-session-state.d.ts.map +1 -0
  14. package/dist/browser-session-state.js +279 -0
  15. package/dist/client-bindings.d.ts +10 -0
  16. package/dist/client-bindings.d.ts.map +1 -0
  17. package/dist/client-bindings.js +18 -0
  18. package/dist/client.d.ts +49 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +63 -0
  21. package/dist/command-api-tracing.d.ts +20 -0
  22. package/dist/command-api-tracing.d.ts.map +1 -0
  23. package/dist/command-api-tracing.js +149 -0
  24. package/dist/command-name.d.ts +3 -0
  25. package/dist/command-name.d.ts.map +1 -0
  26. package/dist/command-name.js +11 -0
  27. package/dist/commands/act.d.ts +43 -0
  28. package/dist/commands/act.d.ts.map +1 -0
  29. package/dist/commands/act.js +1107 -0
  30. package/dist/commands/action-acceptance.d.ts +93 -0
  31. package/dist/commands/action-acceptance.d.ts.map +1 -0
  32. package/dist/commands/action-acceptance.js +1938 -0
  33. package/dist/commands/action-artifacts.d.ts +33 -0
  34. package/dist/commands/action-artifacts.d.ts.map +1 -0
  35. package/dist/commands/action-artifacts.js +104 -0
  36. package/dist/commands/action-execution-guards.d.ts +5 -0
  37. package/dist/commands/action-execution-guards.d.ts.map +1 -0
  38. package/dist/commands/action-execution-guards.js +3 -0
  39. package/dist/commands/action-executor-helpers.d.ts +21 -0
  40. package/dist/commands/action-executor-helpers.d.ts.map +1 -0
  41. package/dist/commands/action-executor-helpers.js +265 -0
  42. package/dist/commands/action-executor.d.ts +14 -0
  43. package/dist/commands/action-executor.d.ts.map +1 -0
  44. package/dist/commands/action-executor.js +46 -0
  45. package/dist/commands/action-fallbacks.d.ts +6 -0
  46. package/dist/commands/action-fallbacks.d.ts.map +1 -0
  47. package/dist/commands/action-fallbacks.js +43 -0
  48. package/dist/commands/action-result-resolution.d.ts +17 -0
  49. package/dist/commands/action-result-resolution.d.ts.map +1 -0
  50. package/dist/commands/action-result-resolution.js +132 -0
  51. package/dist/commands/action-value-projection.d.ts +32 -0
  52. package/dist/commands/action-value-projection.d.ts.map +1 -0
  53. package/dist/commands/action-value-projection.js +151 -0
  54. package/dist/commands/attach.d.ts +41 -0
  55. package/dist/commands/attach.d.ts.map +1 -0
  56. package/dist/commands/attach.js +103 -0
  57. package/dist/commands/browse-actions.d.ts +4 -0
  58. package/dist/commands/browse-actions.d.ts.map +1 -0
  59. package/dist/commands/browse-actions.js +4 -0
  60. package/dist/commands/browser-status.d.ts +57 -0
  61. package/dist/commands/browser-status.d.ts.map +1 -0
  62. package/dist/commands/browser-status.js +243 -0
  63. package/dist/commands/click-action-executor.d.ts +12 -0
  64. package/dist/commands/click-action-executor.d.ts.map +1 -0
  65. package/dist/commands/click-action-executor.js +111 -0
  66. package/dist/commands/click-activation-policy.d.ts +5 -0
  67. package/dist/commands/click-activation-policy.d.ts.map +1 -0
  68. package/dist/commands/click-activation-policy.js +13 -0
  69. package/dist/commands/close.d.ts +26 -0
  70. package/dist/commands/close.d.ts.map +1 -0
  71. package/dist/commands/close.js +124 -0
  72. package/dist/commands/datepicker-action-executor.d.ts +12 -0
  73. package/dist/commands/datepicker-action-executor.d.ts.map +1 -0
  74. package/dist/commands/datepicker-action-executor.js +218 -0
  75. package/dist/commands/descriptor-validation.d.ts +27 -0
  76. package/dist/commands/descriptor-validation.d.ts.map +1 -0
  77. package/dist/commands/descriptor-validation.js +192 -0
  78. package/dist/commands/extract-scope-resolution.d.ts +20 -0
  79. package/dist/commands/extract-scope-resolution.d.ts.map +1 -0
  80. package/dist/commands/extract-scope-resolution.js +109 -0
  81. package/dist/commands/extract-scoped-dialog-text.d.ts +3 -0
  82. package/dist/commands/extract-scoped-dialog-text.d.ts.map +1 -0
  83. package/dist/commands/extract-scoped-dialog-text.js +210 -0
  84. package/dist/commands/extract-snapshot-sanitizer.d.ts +5 -0
  85. package/dist/commands/extract-snapshot-sanitizer.d.ts.map +1 -0
  86. package/dist/commands/extract-snapshot-sanitizer.js +98 -0
  87. package/dist/commands/extract-stagehand-executor.d.ts +17 -0
  88. package/dist/commands/extract-stagehand-executor.d.ts.map +1 -0
  89. package/dist/commands/extract-stagehand-executor.js +112 -0
  90. package/dist/commands/extract.d.ts +57 -0
  91. package/dist/commands/extract.d.ts.map +1 -0
  92. package/dist/commands/extract.js +668 -0
  93. package/dist/commands/interaction-kernel.d.ts +46 -0
  94. package/dist/commands/interaction-kernel.d.ts.map +1 -0
  95. package/dist/commands/interaction-kernel.js +215 -0
  96. package/dist/commands/launch.d.ts +41 -0
  97. package/dist/commands/launch.d.ts.map +1 -0
  98. package/dist/commands/launch.js +182 -0
  99. package/dist/commands/navigate.d.ts +31 -0
  100. package/dist/commands/navigate.d.ts.map +1 -0
  101. package/dist/commands/navigate.js +202 -0
  102. package/dist/commands/observe-accessibility.d.ts +22 -0
  103. package/dist/commands/observe-accessibility.d.ts.map +1 -0
  104. package/dist/commands/observe-accessibility.js +566 -0
  105. package/dist/commands/observe-display-label.d.ts +4 -0
  106. package/dist/commands/observe-display-label.d.ts.map +1 -0
  107. package/dist/commands/observe-display-label.js +26 -0
  108. package/dist/commands/observe-dom-label-contract.d.ts +2 -0
  109. package/dist/commands/observe-dom-label-contract.d.ts.map +1 -0
  110. package/dist/commands/observe-dom-label-contract.js +564 -0
  111. package/dist/commands/observe-fallback-semantics.d.ts +6 -0
  112. package/dist/commands/observe-fallback-semantics.d.ts.map +1 -0
  113. package/dist/commands/observe-fallback-semantics.js +86 -0
  114. package/dist/commands/observe-inventory.d.ts +149 -0
  115. package/dist/commands/observe-inventory.d.ts.map +1 -0
  116. package/dist/commands/observe-inventory.js +3545 -0
  117. package/dist/commands/observe-label-policy.d.ts +8 -0
  118. package/dist/commands/observe-label-policy.d.ts.map +1 -0
  119. package/dist/commands/observe-label-policy.js +21 -0
  120. package/dist/commands/observe-page-state.d.ts +11 -0
  121. package/dist/commands/observe-page-state.d.ts.map +1 -0
  122. package/dist/commands/observe-page-state.js +89 -0
  123. package/dist/commands/observe-persistence.d.ts +15 -0
  124. package/dist/commands/observe-persistence.d.ts.map +1 -0
  125. package/dist/commands/observe-persistence.js +238 -0
  126. package/dist/commands/observe-projection.d.ts +119 -0
  127. package/dist/commands/observe-projection.d.ts.map +1 -0
  128. package/dist/commands/observe-projection.js +726 -0
  129. package/dist/commands/observe-protected.d.ts +6 -0
  130. package/dist/commands/observe-protected.d.ts.map +1 -0
  131. package/dist/commands/observe-protected.js +31 -0
  132. package/dist/commands/observe-semantics.d.ts +10 -0
  133. package/dist/commands/observe-semantics.d.ts.map +1 -0
  134. package/dist/commands/observe-semantics.js +535 -0
  135. package/dist/commands/observe-signals.d.ts +48 -0
  136. package/dist/commands/observe-signals.d.ts.map +1 -0
  137. package/dist/commands/observe-signals.js +461 -0
  138. package/dist/commands/observe-stagehand.d.ts +49 -0
  139. package/dist/commands/observe-stagehand.d.ts.map +1 -0
  140. package/dist/commands/observe-stagehand.js +94 -0
  141. package/dist/commands/observe-surfaces.d.ts +11 -0
  142. package/dist/commands/observe-surfaces.d.ts.map +1 -0
  143. package/dist/commands/observe-surfaces.js +290 -0
  144. package/dist/commands/observe.d.ts +113 -0
  145. package/dist/commands/observe.d.ts.map +1 -0
  146. package/dist/commands/observe.js +556 -0
  147. package/dist/commands/screenshot.d.ts +37 -0
  148. package/dist/commands/screenshot.d.ts.map +1 -0
  149. package/dist/commands/screenshot.js +269 -0
  150. package/dist/commands/select-action-executor.d.ts +10 -0
  151. package/dist/commands/select-action-executor.d.ts.map +1 -0
  152. package/dist/commands/select-action-executor.js +156 -0
  153. package/dist/commands/semantic-observe-lexical.d.ts +31 -0
  154. package/dist/commands/semantic-observe-lexical.d.ts.map +1 -0
  155. package/dist/commands/semantic-observe-lexical.js +186 -0
  156. package/dist/commands/semantic-observe.d.ts +37 -0
  157. package/dist/commands/semantic-observe.d.ts.map +1 -0
  158. package/dist/commands/semantic-observe.js +1339 -0
  159. package/dist/commands/structured-grid-action-executor.d.ts +3 -0
  160. package/dist/commands/structured-grid-action-executor.d.ts.map +1 -0
  161. package/dist/commands/structured-grid-action-executor.js +4 -0
  162. package/dist/commands/target-resolution.d.ts +4 -0
  163. package/dist/commands/target-resolution.d.ts.map +1 -0
  164. package/dist/commands/target-resolution.js +33 -0
  165. package/dist/commands/text-input-action-executor.d.ts +5 -0
  166. package/dist/commands/text-input-action-executor.d.ts.map +1 -0
  167. package/dist/commands/text-input-action-executor.js +116 -0
  168. package/dist/commands/user-actionable.d.ts +4 -0
  169. package/dist/commands/user-actionable.d.ts.map +1 -0
  170. package/dist/commands/user-actionable.js +216 -0
  171. package/dist/control-semantics.d.ts +30 -0
  172. package/dist/control-semantics.d.ts.map +1 -0
  173. package/dist/control-semantics.js +419 -0
  174. package/dist/diagnostics.d.ts +132 -0
  175. package/dist/diagnostics.d.ts.map +1 -0
  176. package/dist/diagnostics.js +120 -0
  177. package/dist/index.d.ts +5 -0
  178. package/dist/index.d.ts.map +1 -0
  179. package/dist/index.js +350 -0
  180. package/dist/library.d.ts +17 -0
  181. package/dist/library.d.ts.map +1 -0
  182. package/dist/library.js +14 -0
  183. package/dist/output.d.ts +32 -0
  184. package/dist/output.d.ts.map +1 -0
  185. package/dist/output.js +33 -0
  186. package/dist/owned-browser.d.ts +12 -0
  187. package/dist/owned-browser.d.ts.map +1 -0
  188. package/dist/owned-browser.js +69 -0
  189. package/dist/owned-process.d.ts +19 -0
  190. package/dist/owned-process.d.ts.map +1 -0
  191. package/dist/owned-process.js +145 -0
  192. package/dist/playwright-runtime.d.ts +43 -0
  193. package/dist/playwright-runtime.d.ts.map +1 -0
  194. package/dist/playwright-runtime.js +339 -0
  195. package/dist/protected-fill-browser.d.ts +22 -0
  196. package/dist/protected-fill-browser.d.ts.map +1 -0
  197. package/dist/protected-fill-browser.js +52 -0
  198. package/dist/protected-fill.d.ts +82 -0
  199. package/dist/protected-fill.d.ts.map +1 -0
  200. package/dist/protected-fill.js +20 -0
  201. package/dist/runtime-metrics.d.ts +27 -0
  202. package/dist/runtime-metrics.d.ts.map +1 -0
  203. package/dist/runtime-metrics.js +66 -0
  204. package/dist/runtime-page-state.d.ts +11 -0
  205. package/dist/runtime-page-state.d.ts.map +1 -0
  206. package/dist/runtime-page-state.js +62 -0
  207. package/dist/runtime-protected-state.d.ts +14 -0
  208. package/dist/runtime-protected-state.d.ts.map +1 -0
  209. package/dist/runtime-protected-state.js +148 -0
  210. package/dist/runtime-resolution.d.ts +9 -0
  211. package/dist/runtime-resolution.d.ts.map +1 -0
  212. package/dist/runtime-resolution.js +19 -0
  213. package/dist/runtime-state.d.ts +251 -0
  214. package/dist/runtime-state.d.ts.map +1 -0
  215. package/dist/runtime-state.js +599 -0
  216. package/dist/secrets/catalog-applicability.d.ts +5 -0
  217. package/dist/secrets/catalog-applicability.d.ts.map +1 -0
  218. package/dist/secrets/catalog-applicability.js +59 -0
  219. package/dist/secrets/field-policy.d.ts +3 -0
  220. package/dist/secrets/field-policy.d.ts.map +1 -0
  221. package/dist/secrets/field-policy.js +3 -0
  222. package/dist/secrets/fill-ordering.d.ts +10 -0
  223. package/dist/secrets/fill-ordering.d.ts.map +1 -0
  224. package/dist/secrets/fill-ordering.js +41 -0
  225. package/dist/secrets/form-matcher.d.ts +60 -0
  226. package/dist/secrets/form-matcher.d.ts.map +1 -0
  227. package/dist/secrets/form-matcher.js +948 -0
  228. package/dist/secrets/protected-artifact-guard.d.ts +39 -0
  229. package/dist/secrets/protected-artifact-guard.d.ts.map +1 -0
  230. package/dist/secrets/protected-artifact-guard.js +44 -0
  231. package/dist/secrets/protected-bindings.d.ts +14 -0
  232. package/dist/secrets/protected-bindings.d.ts.map +1 -0
  233. package/dist/secrets/protected-bindings.js +29 -0
  234. package/dist/secrets/protected-exact-value-redaction.d.ts +14 -0
  235. package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -0
  236. package/dist/secrets/protected-exact-value-redaction.js +360 -0
  237. package/dist/secrets/protected-field-semantics.d.ts +9 -0
  238. package/dist/secrets/protected-field-semantics.d.ts.map +1 -0
  239. package/dist/secrets/protected-field-semantics.js +154 -0
  240. package/dist/secrets/protected-field-values.d.ts +15 -0
  241. package/dist/secrets/protected-field-values.d.ts.map +1 -0
  242. package/dist/secrets/protected-field-values.js +131 -0
  243. package/dist/secrets/protected-fill.d.ts +47 -0
  244. package/dist/secrets/protected-fill.d.ts.map +1 -0
  245. package/dist/secrets/protected-fill.js +446 -0
  246. package/dist/secrets/protected-value-adapters.d.ts +4 -0
  247. package/dist/secrets/protected-value-adapters.d.ts.map +1 -0
  248. package/dist/secrets/protected-value-adapters.js +118 -0
  249. package/dist/secrets/types.d.ts +70 -0
  250. package/dist/secrets/types.d.ts.map +1 -0
  251. package/dist/secrets/types.js +30 -0
  252. package/dist/session.d.ts +19 -0
  253. package/dist/session.d.ts.map +1 -0
  254. package/dist/session.js +120 -0
  255. package/dist/solver/browser-launcher.d.ts +14 -0
  256. package/dist/solver/browser-launcher.d.ts.map +1 -0
  257. package/dist/solver/browser-launcher.js +799 -0
  258. package/dist/solver/config.d.ts +18 -0
  259. package/dist/solver/config.d.ts.map +1 -0
  260. package/dist/solver/config.js +67 -0
  261. package/dist/solver/fingerprint.d.ts +9 -0
  262. package/dist/solver/fingerprint.d.ts.map +1 -0
  263. package/dist/solver/fingerprint.js +96 -0
  264. package/dist/solver/profile-manager.d.ts +8 -0
  265. package/dist/solver/profile-manager.d.ts.map +1 -0
  266. package/dist/solver/profile-manager.js +74 -0
  267. package/dist/solver/turnstile-challenge.d.ts +3 -0
  268. package/dist/solver/turnstile-challenge.d.ts.map +1 -0
  269. package/dist/solver/turnstile-challenge.js +173 -0
  270. package/dist/solver/types.d.ts +67 -0
  271. package/dist/solver/types.d.ts.map +1 -0
  272. package/dist/solver/types.js +1 -0
  273. package/dist/stagehand-runtime.d.ts +4 -0
  274. package/dist/stagehand-runtime.d.ts.map +1 -0
  275. package/dist/stagehand-runtime.js +10 -0
  276. package/dist/stagehand.d.ts +15 -0
  277. package/dist/stagehand.d.ts.map +1 -0
  278. package/dist/stagehand.js +19 -0
  279. package/dist/testing.d.ts +5 -0
  280. package/dist/testing.d.ts.map +1 -0
  281. package/dist/testing.js +4 -0
  282. package/dist/update-check.d.ts +14 -0
  283. package/dist/update-check.d.ts.map +1 -0
  284. package/dist/update-check.js +182 -0
  285. package/dist/workflow-session-state.d.ts +30 -0
  286. package/dist/workflow-session-state.d.ts.map +1 -0
  287. package/dist/workflow-session-state.js +74 -0
  288. package/docs/README.md +25 -0
  289. package/docs/api-reference.md +242 -0
  290. package/docs/assistive-runtime.md +148 -0
  291. package/docs/configuration.md +287 -0
  292. package/docs/getting-started.md +237 -0
  293. package/docs/integration-checklist.md +36 -0
  294. package/docs/protected-fill.md +112 -0
  295. package/docs/testing.md +50 -0
  296. package/docs/troubleshooting.md +71 -0
  297. package/examples/README.md +18 -0
  298. package/examples/attach.ts +27 -0
  299. package/examples/basic.ts +36 -0
  300. package/examples/extract.ts +50 -0
  301. package/package.json +83 -0
@@ -0,0 +1,668 @@
1
+ /**
2
+ * browse extract '<schema-json>' [scopeRef] — Extract structured data from the page or a stored scope.
3
+ */
4
+ import { z } from 'zod';
5
+ import { AssistiveStructuredOutputTruncatedError } from '../assistive-runtime.js';
6
+ import { saveSession } from '../session.js';
7
+ import { getSurface, getTarget, markSurfaceLifecycle, markTargetLifecycle, } from '../runtime-state.js';
8
+ import { getPageScopeEpoch, setCurrentPage } from '../runtime-page-state.js';
9
+ import { outputContractFailure, outputJSON, } from '../output.js';
10
+ import { captureDiagnosticSnapshotBestEffort, finishDiagnosticStepBestEffort, recordCommandLifecycleEventBestEffort, startDiagnosticStep, } from '../diagnostics.js';
11
+ import { connectPlaywright, disconnectPlaywright, resolveCurrentPageContext, resolvePageByRef as resolvePlaywrightPageByRef, syncSessionPage, } from '../playwright-runtime.js';
12
+ import { withApiTraceContext } from '../command-api-tracing.js';
13
+ import { normalizePageSignature } from './descriptor-validation.js';
14
+ import { readScopedDialogText } from './extract-scoped-dialog-text.js';
15
+ import { resolveScopedExtractContext } from './extract-scope-resolution.js';
16
+ import { executeStagehandExtract } from './extract-stagehand-executor.js';
17
+ /** Stable top-level error codes returned by `extract(...)`. */
18
+ export const EXTRACT_ERROR_CODES = [
19
+ 'browser_connection_failed',
20
+ 'expired_extract_scope',
21
+ 'extract_failed',
22
+ 'extract_output_truncated',
23
+ 'invalid_extract_schema',
24
+ 'invalid_extract_scope',
25
+ 'stale_extract_scope',
26
+ 'unknown_scope_ref',
27
+ ];
28
+ /** Stable outcome categories emitted by `extract(...)`. */
29
+ export const EXTRACT_OUTCOME_TYPES = [
30
+ 'binding_stale',
31
+ 'blocked',
32
+ 'extraction_completed',
33
+ 'unsupported',
34
+ ];
35
+ function surfaceExtractScopeLifetime(surface) {
36
+ if (surface.extractScopeLifetime === 'durable' || surface.extractScopeLifetime === 'snapshot') {
37
+ return surface.extractScopeLifetime;
38
+ }
39
+ return (surface.kind ?? '').toLowerCase() === 'form' && surface.locatorCandidates.length > 0
40
+ ? 'durable'
41
+ : 'snapshot';
42
+ }
43
+ function snapshotScopeUnavailableReason(session, surface) {
44
+ if (surfaceExtractScopeLifetime(surface) !== 'snapshot') {
45
+ return null;
46
+ }
47
+ if (surface.availability.state !== 'available') {
48
+ return `Snapshot scope ${surface.ref} is no longer visible or current (availability=${surface.availability.state}${surface.availability.reason ? `:${surface.availability.reason}` : ''}).`;
49
+ }
50
+ const currentEpoch = getPageScopeEpoch(session, surface.pageRef);
51
+ if ((surface.scopeEpoch ?? currentEpoch) !== currentEpoch) {
52
+ return `Snapshot scope ${surface.ref} belongs to page scope epoch ${surface.scopeEpoch ?? 0}, but the current epoch is ${currentEpoch}.`;
53
+ }
54
+ return null;
55
+ }
56
+ function buildSchemaValue(descriptor) {
57
+ if (descriptor === 'string')
58
+ return z.string();
59
+ if (descriptor === 'number')
60
+ return z.number();
61
+ if (descriptor === 'boolean')
62
+ return z.boolean();
63
+ if (Array.isArray(descriptor)) {
64
+ const itemDescriptor = descriptor[0];
65
+ return z.array(itemDescriptor === undefined ? z.unknown() : buildSchemaValue(itemDescriptor));
66
+ }
67
+ if (typeof descriptor === 'object' && descriptor !== null) {
68
+ const shape = {};
69
+ for (const [key, value] of Object.entries(descriptor)) {
70
+ shape[key] = buildSchemaValue(value);
71
+ }
72
+ return z.object(shape);
73
+ }
74
+ return z.unknown();
75
+ }
76
+ function buildSchema(descriptor) {
77
+ return buildSchemaValue(descriptor);
78
+ }
79
+ function describeSchema(descriptor, prefix) {
80
+ const lines = [];
81
+ for (const [key, value] of Object.entries(descriptor)) {
82
+ const fieldPath = prefix ? `${prefix}.${key}` : key;
83
+ if (value === 'string' || value === 'number' || value === 'boolean') {
84
+ lines.push(`${fieldPath}: ${value}`);
85
+ continue;
86
+ }
87
+ if (Array.isArray(value)) {
88
+ const itemDescriptor = value[0];
89
+ if (itemDescriptor === 'string' ||
90
+ itemDescriptor === 'number' ||
91
+ itemDescriptor === 'boolean') {
92
+ lines.push(`${fieldPath}[]: ${itemDescriptor}`);
93
+ }
94
+ else if (typeof itemDescriptor === 'object' &&
95
+ itemDescriptor !== null &&
96
+ !Array.isArray(itemDescriptor)) {
97
+ lines.push(...describeSchema(itemDescriptor, `${fieldPath}[]`));
98
+ }
99
+ else {
100
+ lines.push(`${fieldPath}[]: unknown`);
101
+ }
102
+ continue;
103
+ }
104
+ if (typeof value === 'object' && value !== null) {
105
+ lines.push(...describeSchema(value, fieldPath));
106
+ continue;
107
+ }
108
+ lines.push(`${fieldPath}: unknown`);
109
+ }
110
+ return lines;
111
+ }
112
+ function buildInstruction(fields, scopeRef) {
113
+ const scopeNote = scopeRef
114
+ ? 'Only inspect the provided scoped container, not the full page.'
115
+ : 'Inspect the current page.';
116
+ return [
117
+ 'Extract structured data that is explicitly visible in the current DOM state.',
118
+ scopeNote,
119
+ 'Return only the fields defined by the schema and do not infer missing values.',
120
+ fields.length > 0
121
+ ? `Fields: ${fields.join('; ')}.`
122
+ : 'Return data that matches the provided schema exactly.',
123
+ ].join(' ');
124
+ }
125
+ function isExtractSchemaDescriptorValue(value) {
126
+ if (value === 'string' || value === 'number' || value === 'boolean') {
127
+ return true;
128
+ }
129
+ if (Array.isArray(value)) {
130
+ return value.length <= 1 && (value.length === 0 || isExtractSchemaDescriptorValue(value[0]));
131
+ }
132
+ return isExtractSchemaDescriptor(value);
133
+ }
134
+ function isExtractSchemaDescriptor(value) {
135
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
136
+ return false;
137
+ }
138
+ return Object.values(value).every((entry) => isExtractSchemaDescriptorValue(entry));
139
+ }
140
+ function isZodSchema(value) {
141
+ return value !== null && typeof value === 'object' && 'safeParse' in value;
142
+ }
143
+ function unwrapZodSchema(schema) {
144
+ let current = schema;
145
+ while (true) {
146
+ const withUnwrap = current;
147
+ if (typeof withUnwrap.unwrap === 'function') {
148
+ current = withUnwrap.unwrap();
149
+ continue;
150
+ }
151
+ const innerType = current._def?.innerType;
152
+ if (innerType && innerType !== current) {
153
+ current = innerType;
154
+ continue;
155
+ }
156
+ const schemaType = current._def?.schema;
157
+ if (schemaType && schemaType !== current) {
158
+ current = schemaType;
159
+ continue;
160
+ }
161
+ const outputType = current._def?.out;
162
+ if (outputType && outputType !== current) {
163
+ current = outputType;
164
+ continue;
165
+ }
166
+ break;
167
+ }
168
+ return current;
169
+ }
170
+ function describeZodSchema(schema, prefix) {
171
+ const normalized = unwrapZodSchema(schema);
172
+ const fieldPath = prefix ?? 'value';
173
+ if (normalized instanceof z.ZodString) {
174
+ return [`${fieldPath}: string`];
175
+ }
176
+ if (normalized instanceof z.ZodNumber) {
177
+ return [`${fieldPath}: number`];
178
+ }
179
+ if (normalized instanceof z.ZodBoolean) {
180
+ return [`${fieldPath}: boolean`];
181
+ }
182
+ if (normalized instanceof z.ZodArray) {
183
+ return describeZodSchema(normalized.element, `${fieldPath}[]`);
184
+ }
185
+ if (normalized instanceof z.ZodObject) {
186
+ return Object.entries(normalized.shape).flatMap(([key, value]) => describeZodSchema(value, prefix ? `${prefix}.${key}` : key));
187
+ }
188
+ return [`${fieldPath}: structured`];
189
+ }
190
+ function zodSchemaRequestsScopedDialogText(schema) {
191
+ const normalized = unwrapZodSchema(schema);
192
+ if (!(normalized instanceof z.ZodObject)) {
193
+ return false;
194
+ }
195
+ const dialogField = normalized.shape.dialog_text;
196
+ if (!dialogField) {
197
+ return false;
198
+ }
199
+ return unwrapZodSchema(dialogField) instanceof z.ZodString;
200
+ }
201
+ function normalizeExtractSchemaInput(schemaInput) {
202
+ if (isExtractSchemaDescriptor(schemaInput)) {
203
+ return {
204
+ schema: buildSchema(schemaInput),
205
+ summary: {
206
+ schemaKind: 'descriptor',
207
+ fields: describeSchema(schemaInput),
208
+ },
209
+ requestsScopedDialogText: schemaInput.dialog_text === 'string',
210
+ };
211
+ }
212
+ if (isZodSchema(schemaInput)) {
213
+ return {
214
+ schema: schemaInput,
215
+ summary: {
216
+ schemaKind: 'zod',
217
+ fields: describeZodSchema(schemaInput),
218
+ },
219
+ requestsScopedDialogText: zodSchemaRequestsScopedDialogText(schemaInput),
220
+ };
221
+ }
222
+ return null;
223
+ }
224
+ function canUseTargetAsExtractScope(target) {
225
+ if (target.capability === 'informational') {
226
+ return false;
227
+ }
228
+ if (target.capability === 'scope') {
229
+ return true;
230
+ }
231
+ const kind = (target.kind ?? '').toLowerCase();
232
+ const role = (target.semantics?.role ?? '').toLowerCase();
233
+ const scopeLikeKinds = new Set([
234
+ 'card',
235
+ 'article',
236
+ 'section',
237
+ 'row',
238
+ 'grid',
239
+ 'gridcell',
240
+ 'listitem',
241
+ 'dialog',
242
+ 'tabpanel',
243
+ 'region',
244
+ 'form',
245
+ 'group',
246
+ ]);
247
+ const leafInteractiveKinds = new Set([
248
+ 'input',
249
+ 'textarea',
250
+ 'select',
251
+ 'option',
252
+ 'button',
253
+ 'link',
254
+ 'combobox',
255
+ ]);
256
+ const leafInteractiveRoles = new Set([
257
+ 'textbox',
258
+ 'combobox',
259
+ 'option',
260
+ 'menuitem',
261
+ 'button',
262
+ 'link',
263
+ ]);
264
+ const iframeFieldLike = Boolean(target.framePath?.length) &&
265
+ (target.allowedActions.includes('fill') ||
266
+ target.allowedActions.includes('type') ||
267
+ target.allowedActions.includes('select') ||
268
+ ['input', 'textarea', 'select', 'combobox'].includes(kind) ||
269
+ ['textbox', 'combobox'].includes(role));
270
+ if (target.allowedActions.length === 0) {
271
+ return true;
272
+ }
273
+ if (iframeFieldLike) {
274
+ return true;
275
+ }
276
+ if (target.allowedActions.includes('fill') ||
277
+ target.allowedActions.includes('type') ||
278
+ target.allowedActions.includes('select')) {
279
+ return false;
280
+ }
281
+ if (leafInteractiveKinds.has(kind) || leafInteractiveRoles.has(role)) {
282
+ return false;
283
+ }
284
+ return Boolean(target.surfaceRef) || scopeLikeKinds.has(kind) || scopeLikeKinds.has(role);
285
+ }
286
+ function buildTruncationReason(error) {
287
+ const details = [
288
+ error.provider ? `provider=${error.provider}` : null,
289
+ error.model ? `model=${error.model}` : null,
290
+ error.finishReason ? `finish_reason=${error.finishReason}` : null,
291
+ Number.isFinite(error.completionTokens) ? `completion_tokens=${error.completionTokens}` : null,
292
+ Number.isFinite(error.maxOutputTokens) ? `max_output_tokens=${error.maxOutputTokens}` : null,
293
+ ].filter((value) => Boolean(value));
294
+ return details.length > 0
295
+ ? `Structured output was truncated by the LLM provider (${details.join(', ')}).`
296
+ : 'Structured output was truncated by the LLM provider.';
297
+ }
298
+ async function finalizeExtractStepBestEffort(step, options) {
299
+ await finishDiagnosticStepBestEffort({
300
+ step,
301
+ ...options,
302
+ });
303
+ }
304
+ async function buildExtractSuccessResult(session, step, payload) {
305
+ captureDiagnosticSnapshotBestEffort({
306
+ session,
307
+ step,
308
+ phase: 'after',
309
+ pageRef: typeof payload.pageRef === 'string' ? payload.pageRef : session.runtime?.currentPageRef,
310
+ url: typeof payload.url === 'string' ? payload.url : undefined,
311
+ title: typeof payload.title === 'string' ? payload.title : undefined,
312
+ });
313
+ recordCommandLifecycleEventBestEffort({
314
+ step,
315
+ phase: 'completed',
316
+ attributes: {
317
+ outcomeType: 'extraction_completed',
318
+ pageRef: typeof payload.pageRef === 'string' ? payload.pageRef : undefined,
319
+ scopeRef: typeof payload.scopeRef === 'string' ? payload.scopeRef : undefined,
320
+ },
321
+ });
322
+ await finalizeExtractStepBestEffort(step, {
323
+ success: true,
324
+ outcomeType: 'extraction_completed',
325
+ message: 'Extraction completed.',
326
+ });
327
+ return payload;
328
+ }
329
+ async function buildExtractContractFailureResult(session, params) {
330
+ captureDiagnosticSnapshotBestEffort({
331
+ session,
332
+ step: params.step,
333
+ phase: 'point-in-time',
334
+ pageRef: typeof params.pageRef === 'string' ? params.pageRef : session.runtime?.currentPageRef,
335
+ });
336
+ recordCommandLifecycleEventBestEffort({
337
+ step: params.step,
338
+ phase: 'failed',
339
+ attributes: {
340
+ outcomeType: params.outcomeType,
341
+ pageRef: typeof params.pageRef === 'string' ? params.pageRef : undefined,
342
+ scopeRef: typeof params.scopeRef === 'string' ? params.scopeRef : undefined,
343
+ staleScope: params.staleScope === true,
344
+ reason: params.reason,
345
+ },
346
+ });
347
+ await finalizeExtractStepBestEffort(params.step, {
348
+ success: false,
349
+ outcomeType: params.outcomeType,
350
+ message: params.message,
351
+ reason: params.reason,
352
+ });
353
+ const { step: _step, ...result } = params;
354
+ return {
355
+ success: false,
356
+ ...result,
357
+ };
358
+ }
359
+ function invalidExtractSchemaResult(session, step) {
360
+ return buildExtractContractFailureResult(session, {
361
+ step,
362
+ error: 'invalid_extract_schema',
363
+ outcomeType: 'blocked',
364
+ message: 'Extraction could not start because the schema is invalid.',
365
+ reason: 'Provide a plain schema object or a Zod schema.',
366
+ });
367
+ }
368
+ /**
369
+ * Extracts structured data from the current page or a previously observed scope.
370
+ *
371
+ * `schemaInput` accepts either a plain schema object or a Zod schema.
372
+ */
373
+ export async function extractBrowser(session, schemaInput, scopeRef) {
374
+ const initialPageRef = session.runtime?.currentPageRef ?? 'p0';
375
+ const normalizedSchema = normalizeExtractSchemaInput(schemaInput);
376
+ const extractStep = startDiagnosticStep({
377
+ runId: session.activeRunId,
378
+ command: 'extract',
379
+ input: {
380
+ schema: normalizedSchema?.summary ?? { schemaKind: 'invalid', fields: [] },
381
+ ...(scopeRef ? { scopeRef } : {}),
382
+ },
383
+ refs: {
384
+ pageRef: initialPageRef,
385
+ },
386
+ }, { session });
387
+ captureDiagnosticSnapshotBestEffort({
388
+ session,
389
+ step: extractStep,
390
+ phase: 'before',
391
+ pageRef: initialPageRef,
392
+ });
393
+ recordCommandLifecycleEventBestEffort({
394
+ step: extractStep,
395
+ phase: 'started',
396
+ attributes: {
397
+ schemaKind: normalizedSchema?.summary.schemaKind ?? 'invalid',
398
+ schemaFieldCount: normalizedSchema?.summary.fields.length ?? 0,
399
+ pageRef: initialPageRef,
400
+ ...(scopeRef ? { scopeRef } : {}),
401
+ },
402
+ });
403
+ return withApiTraceContext({
404
+ runId: session.activeRunId,
405
+ stepId: extractStep?.stepId,
406
+ command: 'extract',
407
+ }, async () => {
408
+ if (!normalizedSchema) {
409
+ return invalidExtractSchemaResult(session, extractStep);
410
+ }
411
+ const instruction = buildInstruction(normalizedSchema.summary.fields, scopeRef);
412
+ const targetScope = scopeRef ? getTarget(session, scopeRef) : null;
413
+ const surfaceScope = !targetScope && scopeRef ? getSurface(session, scopeRef) : null;
414
+ const scopeTarget = targetScope ?? surfaceScope;
415
+ let pageRef = scopeTarget?.pageRef ?? session.runtime?.currentPageRef ?? 'p0';
416
+ if (scopeRef && !scopeTarget) {
417
+ return buildExtractContractFailureResult(session, {
418
+ step: extractStep,
419
+ error: 'unknown_scope_ref',
420
+ outcomeType: 'blocked',
421
+ message: 'Extraction could not start because the requested scopeRef is unknown.',
422
+ reason: `No live scope target matches scopeRef ${scopeRef}.`,
423
+ scopeRef,
424
+ });
425
+ }
426
+ if (scopeTarget && scopeTarget.lifecycle !== 'live') {
427
+ return buildExtractContractFailureResult(session, {
428
+ step: extractStep,
429
+ error: 'stale_extract_scope',
430
+ outcomeType: 'binding_stale',
431
+ message: 'Extraction could not start because the requested scope is no longer live.',
432
+ reason: `Scope ${scopeRef} is ${scopeTarget.lifecycle}${scopeTarget.lifecycleReason ? ` because ${scopeTarget.lifecycleReason}` : ''}.`,
433
+ scopeRef,
434
+ });
435
+ }
436
+ if (surfaceScope) {
437
+ const snapshotScopeReason = snapshotScopeUnavailableReason(session, surfaceScope);
438
+ if (snapshotScopeReason) {
439
+ return buildExtractContractFailureResult(session, {
440
+ step: extractStep,
441
+ error: 'expired_extract_scope',
442
+ outcomeType: 'binding_stale',
443
+ message: 'Extraction could not start because the requested snapshot scope is no longer current.',
444
+ reason: snapshotScopeReason,
445
+ scopeRef,
446
+ pageRef,
447
+ staleScope: true,
448
+ staleReason: 'snapshot-scope-expired',
449
+ });
450
+ }
451
+ }
452
+ if (targetScope && !canUseTargetAsExtractScope(targetScope)) {
453
+ return buildExtractContractFailureResult(session, {
454
+ step: extractStep,
455
+ error: 'invalid_extract_scope',
456
+ outcomeType: 'unsupported',
457
+ message: 'Extraction cannot use the requested target as a scope.',
458
+ reason: `Target ${scopeRef} is a leaf control, not an extractable scope container.`,
459
+ scopeRef,
460
+ });
461
+ }
462
+ let browser = null;
463
+ let failureMessage = null;
464
+ let cleanupScopedExtract = null;
465
+ let staleScope = false;
466
+ let staleReason = null;
467
+ try {
468
+ browser = await connectPlaywright(session.cdpUrl);
469
+ }
470
+ catch (err) {
471
+ return buildExtractContractFailureResult(session, {
472
+ step: extractStep,
473
+ error: 'browser_connection_failed',
474
+ outcomeType: 'blocked',
475
+ message: 'Extraction could not start because AgentBrowse failed to connect to the browser.',
476
+ reason: err instanceof Error ? err.message : String(err),
477
+ scopeRef,
478
+ pageRef,
479
+ });
480
+ }
481
+ try {
482
+ let sourcePage;
483
+ if (scopeTarget) {
484
+ sourcePage = await resolvePlaywrightPageByRef(browser, session, pageRef);
485
+ }
486
+ else {
487
+ const resolvedPage = await resolveCurrentPageContext(browser, session);
488
+ pageRef = resolvedPage.pageRef;
489
+ sourcePage = resolvedPage.page;
490
+ }
491
+ let page = sourcePage;
492
+ let scopedResolution = null;
493
+ const { url, title } = await syncSessionPage(session, pageRef, sourcePage);
494
+ if (scopeTarget?.pageSignature &&
495
+ normalizePageSignature(url) !== scopeTarget.pageSignature) {
496
+ staleScope = true;
497
+ staleReason = 'page-signature-mismatch';
498
+ throw new Error('stale_scope_target_page_signature_changed');
499
+ }
500
+ let effectiveSelector;
501
+ if (scopeTarget) {
502
+ try {
503
+ scopedResolution = await resolveScopedExtractContext({
504
+ page: sourcePage,
505
+ scopeTarget,
506
+ validateDomSignature: Boolean(targetScope),
507
+ });
508
+ cleanupScopedExtract = scopedResolution.cleanup;
509
+ page = scopedResolution.page;
510
+ effectiveSelector = scopedResolution.selector;
511
+ }
512
+ catch (error) {
513
+ if (error instanceof Error &&
514
+ error.message === 'stale_scope_target_dom_signature_changed') {
515
+ staleScope = true;
516
+ staleReason = 'dom-signature-mismatch';
517
+ }
518
+ else if (surfaceScope &&
519
+ surfaceExtractScopeLifetime(surfaceScope) === 'snapshot' &&
520
+ error instanceof Error &&
521
+ error.message === 'scope_target_unresolvable') {
522
+ return buildExtractContractFailureResult(session, {
523
+ step: extractStep,
524
+ error: 'expired_extract_scope',
525
+ outcomeType: 'binding_stale',
526
+ message: 'Extraction failed because the requested snapshot scope expired before it could be rebound.',
527
+ reason: `Snapshot scope ${scopeRef} is no longer present in the current visible page state.`,
528
+ scopeRef,
529
+ pageRef,
530
+ staleScope: true,
531
+ staleReason: 'snapshot-scope-expired',
532
+ });
533
+ }
534
+ throw error;
535
+ }
536
+ }
537
+ setCurrentPage(session, pageRef);
538
+ const execution = await executeStagehandExtract({
539
+ session,
540
+ instruction,
541
+ schema: normalizedSchema.schema,
542
+ page,
543
+ selector: effectiveSelector,
544
+ degradationReason: scopedResolution?.degraded
545
+ ? scopedResolution.degradationReason
546
+ : undefined,
547
+ });
548
+ let data = execution.data;
549
+ if (scopeTarget &&
550
+ effectiveSelector &&
551
+ normalizedSchema.requestsScopedDialogText &&
552
+ data &&
553
+ typeof data === 'object' &&
554
+ !Array.isArray(data)) {
555
+ const dialogText = await readScopedDialogText(page, effectiveSelector);
556
+ if (typeof dialogText === 'string' && dialogText.trim().length > 0) {
557
+ data = {
558
+ ...data,
559
+ dialog_text: dialogText,
560
+ };
561
+ }
562
+ }
563
+ return buildExtractSuccessResult(session, extractStep, {
564
+ success: true,
565
+ ...execution,
566
+ data,
567
+ pageRef,
568
+ scopeRef,
569
+ metrics: session.runtime?.metrics,
570
+ url,
571
+ title,
572
+ });
573
+ }
574
+ catch (err) {
575
+ if (staleScope && scopeRef) {
576
+ if (targetScope) {
577
+ markTargetLifecycle(session, scopeRef, 'stale', staleReason ?? 'unknown');
578
+ }
579
+ else if (surfaceScope) {
580
+ markSurfaceLifecycle(session, scopeRef, 'stale', staleReason ?? 'unknown');
581
+ }
582
+ }
583
+ if (!staleScope && err instanceof AssistiveStructuredOutputTruncatedError) {
584
+ return buildExtractContractFailureResult(session, {
585
+ step: extractStep,
586
+ error: 'extract_output_truncated',
587
+ outcomeType: 'blocked',
588
+ message: 'Extraction failed because the provider truncated structured output.',
589
+ reason: buildTruncationReason(err),
590
+ scopeRef,
591
+ pageRef,
592
+ staleScope: false,
593
+ provider: err.provider,
594
+ model: err.model,
595
+ finishReason: err.finishReason,
596
+ maxOutputTokens: err.maxOutputTokens,
597
+ completionTokens: err.completionTokens,
598
+ });
599
+ }
600
+ failureMessage = `Extract failed: ${err instanceof Error ? err.message : String(err)}`;
601
+ }
602
+ finally {
603
+ if (cleanupScopedExtract) {
604
+ await cleanupScopedExtract().catch(() => undefined);
605
+ }
606
+ if (browser) {
607
+ await disconnectPlaywright(browser);
608
+ }
609
+ }
610
+ if (failureMessage) {
611
+ return buildExtractContractFailureResult(session, {
612
+ step: extractStep,
613
+ error: staleScope ? 'stale_extract_scope' : 'extract_failed',
614
+ outcomeType: staleScope ? 'binding_stale' : 'blocked',
615
+ message: staleScope
616
+ ? 'Extraction failed because the requested scope became stale.'
617
+ : 'Extraction failed.',
618
+ reason: staleScope && scopeRef
619
+ ? `${failureMessage} (${scopeRef} marked stale: ${staleReason ?? 'stale'})`
620
+ : failureMessage.replace(/^Extract failed:\s*/, ''),
621
+ scopeRef,
622
+ pageRef,
623
+ staleScope,
624
+ staleReason: staleReason ?? undefined,
625
+ });
626
+ }
627
+ return buildExtractContractFailureResult(session, {
628
+ step: extractStep,
629
+ error: 'extract_failed',
630
+ outcomeType: 'blocked',
631
+ message: 'Extraction failed.',
632
+ reason: 'Extraction did not produce a success or failure result.',
633
+ scopeRef,
634
+ pageRef,
635
+ });
636
+ });
637
+ }
638
+ /** CLI wrapper for `extractBrowser(...)` that accepts JSON text or schema objects. */
639
+ export async function extract(session, schemaInput, scopeRef) {
640
+ let normalizedInput;
641
+ if (typeof schemaInput === 'string') {
642
+ let parsedSchema;
643
+ try {
644
+ parsedSchema = JSON.parse(schemaInput);
645
+ }
646
+ catch {
647
+ return outputContractFailure({
648
+ error: 'invalid_extract_schema',
649
+ outcomeType: 'blocked',
650
+ message: 'Extraction could not start because the schema is invalid.',
651
+ reason: 'Provide valid JSON that parses to a plain schema object.',
652
+ });
653
+ }
654
+ normalizedInput = parsedSchema;
655
+ }
656
+ else {
657
+ normalizedInput = schemaInput;
658
+ }
659
+ const result = await extractBrowser(session, normalizedInput, scopeRef);
660
+ if (result.success || result.staleScope === true) {
661
+ saveSession(session);
662
+ }
663
+ if (result.success) {
664
+ return outputJSON(result);
665
+ }
666
+ const { success: _success, ...failure } = result;
667
+ return outputContractFailure(failure);
668
+ }