@nuanu-ai/agentbrowse 0.2.21 → 0.2.23

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 (106) hide show
  1. package/README.md +52 -9
  2. package/dist/commands/act.d.ts.map +1 -1
  3. package/dist/commands/act.js +100 -73
  4. package/dist/commands/action-acceptance.d.ts +4 -1
  5. package/dist/commands/action-acceptance.d.ts.map +1 -1
  6. package/dist/commands/action-acceptance.js +127 -37
  7. package/dist/commands/action-executor-helpers.d.ts.map +1 -1
  8. package/dist/commands/action-executor-helpers.js +12 -4
  9. package/dist/commands/action-fallbacks.d.ts.map +1 -1
  10. package/dist/commands/action-result-resolution.d.ts.map +1 -1
  11. package/dist/commands/action-result-resolution.js +1 -1
  12. package/dist/commands/action-value-projection.d.ts.map +1 -1
  13. package/dist/commands/close.d.ts +12 -1
  14. package/dist/commands/close.d.ts.map +1 -1
  15. package/dist/commands/close.js +19 -21
  16. package/dist/commands/datepicker-action-executor.d.ts.map +1 -1
  17. package/dist/commands/datepicker-action-executor.js +1 -1
  18. package/dist/commands/descriptor-validation.d.ts.map +1 -1
  19. package/dist/commands/descriptor-validation.js +13 -172
  20. package/dist/commands/extract-scoped-dialog-text.d.ts.map +1 -1
  21. package/dist/commands/extract-scoped-dialog-text.js +1 -4
  22. package/dist/commands/extract-snapshot-sanitizer.d.ts.map +1 -1
  23. package/dist/commands/extract-stagehand-executor.d.ts.map +1 -1
  24. package/dist/commands/extract.d.ts.map +1 -1
  25. package/dist/commands/extract.js +23 -4
  26. package/dist/commands/launch.d.ts +22 -1
  27. package/dist/commands/launch.d.ts.map +1 -1
  28. package/dist/commands/launch.js +122 -59
  29. package/dist/commands/observe-accessibility.d.ts +22 -0
  30. package/dist/commands/observe-accessibility.d.ts.map +1 -0
  31. package/dist/commands/observe-accessibility.js +497 -0
  32. package/dist/commands/observe-display-label.d.ts +4 -0
  33. package/dist/commands/observe-display-label.d.ts.map +1 -0
  34. package/dist/commands/observe-display-label.js +26 -0
  35. package/dist/commands/observe-dom-label-contract.d.ts +2 -0
  36. package/dist/commands/observe-dom-label-contract.d.ts.map +1 -0
  37. package/dist/commands/observe-dom-label-contract.js +521 -0
  38. package/dist/commands/observe-fallback-semantics.d.ts +6 -0
  39. package/dist/commands/observe-fallback-semantics.d.ts.map +1 -0
  40. package/dist/commands/observe-fallback-semantics.js +86 -0
  41. package/dist/commands/observe-inventory.d.ts +23 -18
  42. package/dist/commands/observe-inventory.d.ts.map +1 -1
  43. package/dist/commands/observe-inventory.js +172 -719
  44. package/dist/commands/observe-label-policy.d.ts +8 -0
  45. package/dist/commands/observe-label-policy.d.ts.map +1 -0
  46. package/dist/commands/observe-label-policy.js +21 -0
  47. package/dist/commands/observe-page-state.d.ts +1 -1
  48. package/dist/commands/observe-page-state.d.ts.map +1 -1
  49. package/dist/commands/observe-persistence.d.ts.map +1 -1
  50. package/dist/commands/observe-persistence.js +10 -3
  51. package/dist/commands/observe-projection.d.ts +2 -1
  52. package/dist/commands/observe-projection.d.ts.map +1 -1
  53. package/dist/commands/observe-projection.js +5 -2
  54. package/dist/commands/observe-semantics.d.ts.map +1 -1
  55. package/dist/commands/observe-semantics.js +18 -1
  56. package/dist/commands/observe-signals.d.ts +48 -0
  57. package/dist/commands/observe-signals.d.ts.map +1 -0
  58. package/dist/commands/observe-signals.js +461 -0
  59. package/dist/commands/observe-stagehand.d.ts.map +1 -1
  60. package/dist/commands/observe-stagehand.js +5 -18
  61. package/dist/commands/observe-surfaces.d.ts.map +1 -1
  62. package/dist/commands/observe-surfaces.js +5 -4
  63. package/dist/commands/observe.d.ts +0 -6
  64. package/dist/commands/observe.d.ts.map +1 -1
  65. package/dist/commands/observe.js +30 -6
  66. package/dist/commands/observe.test-harness.d.ts +0 -6
  67. package/dist/commands/observe.test-harness.d.ts.map +1 -1
  68. package/dist/commands/screenshot.d.ts.map +1 -1
  69. package/dist/commands/select-action-executor.d.ts.map +1 -1
  70. package/dist/commands/select-action-executor.js +1 -1
  71. package/dist/commands/semantic-observe.d.ts.map +1 -1
  72. package/dist/commands/semantic-observe.js +1 -4
  73. package/dist/commands/status.d.ts.map +1 -1
  74. package/dist/commands/text-input-action-executor.d.ts.map +1 -1
  75. package/dist/commands/text-input-action-executor.js +1 -1
  76. package/dist/control-semantics.d.ts.map +1 -1
  77. package/dist/control-semantics.js +2 -6
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +30 -9
  80. package/dist/owned-process.d.ts +4 -0
  81. package/dist/owned-process.d.ts.map +1 -0
  82. package/dist/owned-process.js +57 -0
  83. package/dist/playwright-runtime.d.ts.map +1 -1
  84. package/dist/playwright-runtime.js +3 -4
  85. package/dist/runtime-state.d.ts +3 -0
  86. package/dist/runtime-state.d.ts.map +1 -1
  87. package/dist/runtime-state.js +3 -0
  88. package/dist/secrets/catalog-applicability.d.ts.map +1 -1
  89. package/dist/secrets/form-matcher.d.ts.map +1 -1
  90. package/dist/secrets/form-matcher.js +2 -1
  91. package/dist/secrets/mock-agentpay-backend.d.ts +1 -1
  92. package/dist/secrets/mock-agentpay-backend.d.ts.map +1 -1
  93. package/dist/secrets/mock-agentpay-backend.js +2 -4
  94. package/dist/secrets/mock-agentpay-cabinet.d.ts.map +1 -1
  95. package/dist/secrets/mock-agentpay-cabinet.js +6 -1
  96. package/dist/secrets/protected-field-values.d.ts.map +1 -1
  97. package/dist/secrets/protected-field-values.js +1 -1
  98. package/dist/secrets/protected-fill.d.ts.map +1 -1
  99. package/dist/secrets/protected-fill.js +16 -4
  100. package/dist/session.d.ts +13 -0
  101. package/dist/session.d.ts.map +1 -1
  102. package/dist/session.js +52 -7
  103. package/dist/solver/captcha-detector.d.ts.map +1 -1
  104. package/dist/solver/types.d.ts +2 -0
  105. package/dist/solver/types.d.ts.map +1 -1
  106. package/package.json +3 -3
@@ -1,3 +1,4 @@
1
+ import { OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT } from './observe-dom-label-contract.js';
1
2
  export function normalizePageSignature(url) {
2
3
  try {
3
4
  const parsed = new URL(url);
@@ -8,184 +9,30 @@ export function normalizePageSignature(url) {
8
9
  }
9
10
  }
10
11
  export const LOCATOR_DOM_SIGNATURE_SCRIPT = String.raw `
12
+ ${OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT}
11
13
  if (!(element instanceof HTMLElement)) {
12
14
  return null;
13
15
  }
14
16
 
15
- const inputTypeOf = () => {
16
- if (element.tagName.toLowerCase() !== 'input') return '';
17
- return (element.getAttribute('type') || 'text').toLowerCase();
18
- };
19
-
20
- const isButtonLikeInput = () => {
21
- return element.tagName.toLowerCase() === 'input' &&
22
- ['button', 'submit', 'reset'].includes(inputTypeOf());
23
- };
24
-
25
- const inferRole = () => {
26
- const explicitRole = element.getAttribute('role')?.trim();
27
- if (explicitRole) return explicitRole;
28
-
29
- const tag = element.tagName.toLowerCase();
30
- if (tag === 'button') return 'button';
31
- if (tag === 'a' && element.getAttribute('href')) return 'link';
32
- if (tag === 'select') return 'combobox';
33
- if (tag === 'textarea') return 'textbox';
34
- if (tag === 'input') {
35
- if (isButtonLikeInput()) return 'button';
36
- return 'textbox';
37
- }
38
- return '';
39
- };
40
-
41
- const textOf = (target) => {
42
- if (target === element && isButtonLikeInput()) {
43
- const value = (element.getAttribute('value') || element.value || '').trim();
44
- if (value) {
45
- return value.replace(/\s+/g, ' ');
46
- }
47
- }
48
- const value = target?.innerText?.trim() || target?.textContent?.trim() || '';
49
- return value.replace(/\s+/g, ' ');
50
- };
51
-
52
- const isMeaningfulLabel = (value) => {
53
- const normalized = (value || '').replace(/\s+/g, ' ').trim();
54
- return Boolean(normalized) && normalized !== '[object Object]';
55
- };
56
-
57
- const composedParentElement = (node) => {
58
- if (!node) {
59
- return null;
60
- }
61
- if (node.parentElement) {
62
- return node.parentElement;
63
- }
64
- const root = node.getRootNode?.();
65
- return root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null;
66
- };
67
-
68
- const ariaLabelledbyTextOf = () => {
69
- const labelledBy = element.getAttribute('aria-labelledby')?.trim();
70
- if (!labelledBy) return undefined;
71
-
72
- const text = labelledBy
73
- .split(/\s+/)
74
- .map((id) => textOf(document.getElementById(id)))
75
- .filter(Boolean)
76
- .join(' ')
77
- .trim();
78
-
79
- return isMeaningfulLabel(text) ? text : undefined;
80
- };
81
-
82
- const nearestFieldLabelOf = () => {
83
- const anchors = [
84
- composedParentElement(element),
85
- composedParentElement(composedParentElement(element)),
86
- composedParentElement(composedParentElement(composedParentElement(element))),
87
- ].filter(Boolean);
88
-
89
- for (const anchor of anchors) {
90
- if (!(anchor instanceof HTMLElement)) continue;
91
-
92
- const explicitLabels = Array.from(
93
- anchor.querySelectorAll('label, legend, [class*="label"], [data-testid*="label"]')
94
- ).filter((candidate) => {
95
- return (
96
- candidate instanceof HTMLElement &&
97
- candidate !== element &&
98
- !candidate.contains(element) &&
99
- !element.contains(candidate)
100
- );
101
- });
102
-
103
- for (const candidate of explicitLabels) {
104
- const candidateText = textOf(candidate);
105
- if (isMeaningfulLabel(candidateText)) {
106
- return candidateText;
107
- }
108
- }
109
-
110
- for (const child of Array.from(anchor.children).slice(0, 8)) {
111
- if (!(child instanceof HTMLElement)) continue;
112
- if (child === element || child.contains(element) || element.contains(child)) continue;
113
-
114
- const candidateText = textOf(child);
115
- if (isMeaningfulLabel(candidateText)) {
116
- return candidateText;
117
- }
118
- }
119
- }
120
-
121
- return undefined;
122
- };
123
-
124
- const syntheticLabelOf = () => {
125
- const tag = element.tagName.toLowerCase();
126
- const explicitRole = element.getAttribute('role')?.trim();
127
- if (tag === 'input') {
128
- const inputType = inputTypeOf();
129
- if (isButtonLikeInput()) return 'Button';
130
- if (inputType === 'tel') return 'Phone input';
131
- if (inputType === 'email') return 'Email input';
132
- if (inputType === 'password') return 'Password input';
133
- if (inputType === 'search') return 'Search input';
134
- if (inputType === 'date') return 'Date input';
135
- return 'Text input';
136
- }
137
- if (tag === 'textarea') return 'Text area';
138
- if (tag === 'select' || explicitRole === 'combobox') return 'Combobox';
139
- if (explicitRole === 'textbox') return 'Text input';
140
- return undefined;
141
- };
142
-
143
- const labelOf = () => {
144
- const ariaLabel = element.getAttribute('aria-label')?.trim();
145
- if (isMeaningfulLabel(ariaLabel)) return ariaLabel;
146
-
147
- const customLabel = element.getAttribute('label')?.trim();
148
- if (isMeaningfulLabel(customLabel)) return customLabel;
149
-
150
- const ariaLabelledbyText = ariaLabelledbyTextOf();
151
- if (isMeaningfulLabel(ariaLabelledbyText)) return ariaLabelledbyText;
152
-
153
- if ('labels' in element && Array.isArray(Array.from(element.labels ?? []))) {
154
- const labels = element.labels;
155
- const firstLabel = labels && labels.length > 0 ? textOf(labels[0]) : '';
156
- if (isMeaningfulLabel(firstLabel)) return firstLabel;
157
- }
158
-
159
- const text = textOf(element);
160
- if (isMeaningfulLabel(text)) return text;
161
-
162
- const nearestFieldLabel = isButtonLikeInput() ? undefined : nearestFieldLabelOf();
163
- if (isMeaningfulLabel(nearestFieldLabel)) return nearestFieldLabel;
164
-
165
- return syntheticLabelOf() || '';
166
- };
167
-
168
- return element.tagName.toLowerCase() + '|' + inferRole() + '|' + labelOf();
17
+ return (
18
+ element.tagName.toLowerCase() +
19
+ '|' +
20
+ observedInferRole(element) +
21
+ '|' +
22
+ observedLabelOf(element)
23
+ );
169
24
  `;
170
25
  function readLocatorDomSignatureInBrowser(element) {
171
26
  return Function('element', LOCATOR_DOM_SIGNATURE_SCRIPT)(element);
172
27
  }
173
28
  const LOCATOR_BINDING_SNAPSHOT_SCRIPT = String.raw `
29
+ ${OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT}
174
30
  if (!(element instanceof HTMLElement)) {
175
31
  return null;
176
32
  }
177
33
 
178
34
  const read = Function('element', ${JSON.stringify(LOCATOR_DOM_SIGNATURE_SCRIPT)});
179
- const explicitRole = element.getAttribute('role')?.trim() || '';
180
- const role =
181
- explicitRole ||
182
- (element.tagName.toLowerCase() === 'select'
183
- ? 'combobox'
184
- : element.tagName.toLowerCase() === 'textarea'
185
- ? 'textbox'
186
- : element.tagName.toLowerCase() === 'input'
187
- ? 'textbox'
188
- : '');
35
+ const role = observedInferRole(element);
189
36
  const domSignature = read(element);
190
37
  const label = typeof domSignature === 'string' ? domSignature.split('|').slice(2).join('|') : undefined;
191
38
  return {
@@ -287,14 +134,8 @@ export async function readLocatorOuterHtml(locator) {
287
134
  'track',
288
135
  'wbr',
289
136
  ]);
290
- const escapeText = (value) => value
291
- .replace(/&/g, '&')
292
- .replace(/</g, '&lt;')
293
- .replace(/>/g, '&gt;');
294
- const escapeAttribute = (value) => value
295
- .replace(/&/g, '&amp;')
296
- .replace(/"/g, '&quot;')
297
- .replace(/</g, '&lt;');
137
+ const escapeText = (value) => value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
138
+ const escapeAttribute = (value) => value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
298
139
  const serializeChildren = (parent) => Array.from(parent.childNodes)
299
140
  .map((node) => serializeNode(node))
300
141
  .join('');
@@ -1 +1 @@
1
- {"version":3,"file":"extract-scoped-dialog-text.d.ts","sourceRoot":"","sources":["../../src/commands/extract-scoped-dialog-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAuM5C,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyB/F"}
1
+ {"version":3,"file":"extract-scoped-dialog-text.d.ts","sourceRoot":"","sources":["../../src/commands/extract-scoped-dialog-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAuM5C,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsB/F"}
@@ -196,10 +196,7 @@ export async function readScopedDialogText(page, selector) {
196
196
  return null;
197
197
  }
198
198
  try {
199
- const result = await page
200
- .locator(selector)
201
- .first()
202
- .evaluate(readScopedDialogTextInBrowser, {
199
+ const result = await page.locator(selector).first().evaluate(readScopedDialogTextInBrowser, {
203
200
  marker: BLOCK_MARKER,
204
201
  anchorSelector: ANCHOR_SELECTOR,
205
202
  minBlockTextLength: 20,
@@ -1 +1 @@
1
- {"version":3,"file":"extract-snapshot-sanitizer.d.ts","sourceRoot":"","sources":["../../src/commands/extract-snapshot-sanitizer.ts"],"names":[],"mappings":"AAwEA,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQhE"}
1
+ {"version":3,"file":"extract-snapshot-sanitizer.d.ts","sourceRoot":"","sources":["../../src/commands/extract-snapshot-sanitizer.ts"],"names":[],"mappings":"AAuEA,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQhE"}
@@ -1 +1 @@
1
- {"version":3,"file":"extract-stagehand-executor.d.ts","sourceRoot":"","sources":["../../src/commands/extract-stagehand-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAI5C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAA8B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAI9F,MAAM,MAAM,yBAAyB,GAAG,iBAAiB,GAAG;IAC1D,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,mBAAmB,CAAC;CACjC,CAAC;AAaF,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAsDrC"}
1
+ {"version":3,"file":"extract-stagehand-executor.d.ts","sourceRoot":"","sources":["../../src/commands/extract-stagehand-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAI5C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAA8B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAI9F,MAAM,MAAM,yBAAyB,GAAG,iBAAiB,GAAG;IAC1D,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,mBAAmB,CAAC;CACjC,CAAC;AAiBF,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAsDrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/commands/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA8KnD,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAkMf"}
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/commands/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAmMnD,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAkMf"}
@@ -44,10 +44,14 @@ function describeSchema(descriptor, prefix) {
44
44
  }
45
45
  if (Array.isArray(value)) {
46
46
  const itemDescriptor = value[0];
47
- if (itemDescriptor === 'string' || itemDescriptor === 'number' || itemDescriptor === 'boolean') {
47
+ if (itemDescriptor === 'string' ||
48
+ itemDescriptor === 'number' ||
49
+ itemDescriptor === 'boolean') {
48
50
  lines.push(`${fieldPath}[]: ${itemDescriptor}`);
49
51
  }
50
- else if (typeof itemDescriptor === 'object' && itemDescriptor !== null && !Array.isArray(itemDescriptor)) {
52
+ else if (typeof itemDescriptor === 'object' &&
53
+ itemDescriptor !== null &&
54
+ !Array.isArray(itemDescriptor)) {
51
55
  lines.push(...describeSchema(itemDescriptor, `${fieldPath}[]`));
52
56
  }
53
57
  else {
@@ -98,8 +102,23 @@ function canUseTargetAsExtractScope(target) {
98
102
  'form',
99
103
  'group',
100
104
  ]);
101
- const leafInteractiveKinds = new Set(['input', 'textarea', 'select', 'option', 'button', 'link', 'combobox']);
102
- const leafInteractiveRoles = new Set(['textbox', 'combobox', 'option', 'menuitem', 'button', 'link']);
105
+ const leafInteractiveKinds = new Set([
106
+ 'input',
107
+ 'textarea',
108
+ 'select',
109
+ 'option',
110
+ 'button',
111
+ 'link',
112
+ 'combobox',
113
+ ]);
114
+ const leafInteractiveRoles = new Set([
115
+ 'textbox',
116
+ 'combobox',
117
+ 'option',
118
+ 'menuitem',
119
+ 'button',
120
+ 'link',
121
+ ]);
103
122
  const iframeFieldLike = Boolean(target.framePath?.length) &&
104
123
  (target.allowedActions.includes('fill') ||
105
124
  target.allowedActions.includes('type') ||
@@ -1,10 +1,31 @@
1
1
  /**
2
2
  * browse launch [url] — Start browser session, optionally navigate.
3
3
  */
4
+ import type { SecretCatalogSummary } from '../secrets/catalog-sync.js';
4
5
  export type LaunchCliOptions = {
5
6
  compact?: boolean;
6
7
  profile?: string;
7
8
  headless?: boolean;
9
+ proxy?: string;
10
+ noProxy?: boolean;
8
11
  };
9
- export declare function launch(url?: string, opts?: LaunchCliOptions): Promise<void>;
12
+ export type LaunchSuccessResult = {
13
+ success: true;
14
+ runtime: 'managed';
15
+ captchaSolveCapable: true;
16
+ profile: string;
17
+ cdpUrl: string;
18
+ url: string;
19
+ title: string;
20
+ secretCatalog?: SecretCatalogSummary;
21
+ };
22
+ export type LaunchFailureResult = {
23
+ success: false;
24
+ error: 'browser_launch_failed';
25
+ outcomeType: 'blocked';
26
+ message: 'Browser launch failed.';
27
+ reason: string;
28
+ };
29
+ export type LaunchResult = LaunchSuccessResult | LaunchFailureResult;
30
+ export declare function launch(url?: string, opts?: LaunchCliOptions): Promise<LaunchResult>;
10
31
  //# sourceMappingURL=launch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsBH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,wBAAsB,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF"}
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAWvE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,SAAS,CAAC;IACnB,mBAAmB,EAAE,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,uBAAuB,CAAC;IAC/B,WAAW,EAAE,SAAS,CAAC;IACvB,OAAO,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAErE,wBAAsB,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAazF"}
@@ -1,92 +1,112 @@
1
1
  /**
2
2
  * browse launch [url] — Start browser session, optionally navigate.
3
3
  */
4
- import { execSync } from 'node:child_process';
5
4
  import { readConfig } from '../solver/config.js';
6
5
  import { ensureProfile } from '../solver/profile-manager.js';
7
6
  import { launchSolver } from '../solver/browser-launcher.js';
8
- import { saveSession } from '../session.js';
7
+ import { buildOwnedSession, isOwnedSession, loadSession, saveSession } from '../session.js';
9
8
  import { findChromePid } from '../stagehand.js';
10
- import { outputContractFailure, outputJSON } from '../output.js';
11
9
  import { applyAgentpayGatewayEnv, tryResolveAgentpayGatewayConfig } from '../agentpay-gateway.js';
12
10
  import { connectPlaywright, disconnectPlaywright, syncLaunchPage } from '../playwright-runtime.js';
13
11
  import { summarizeSecretCatalog, syncSecretCatalogForUrl } from '../secrets/catalog-sync.js';
14
- const CDP_PORT = 9222;
12
+ import { terminateOwnedPid } from '../owned-process.js';
13
+ import { info } from '../output.js';
15
14
  const DEFAULT_PROFILE = 'default';
16
15
  const COMPACT_WINDOW = {
17
16
  width: 1280,
18
17
  height: 900,
19
18
  };
20
19
  export async function launch(url, opts) {
21
- await killChromeOnPort(CDP_PORT);
20
+ const cleanupFailure = await cleanupOwnedSession();
21
+ if (cleanupFailure) {
22
+ return cleanupFailure;
23
+ }
22
24
  const compact = opts?.compact ?? true;
23
25
  const profileName = opts?.profile ?? DEFAULT_PROFILE;
24
26
  const headless = opts?.headless ?? false;
25
- await launchManaged(url, profileName, headless, compact);
27
+ const proxyOverride = opts?.proxy;
28
+ const noProxy = opts?.noProxy ?? false;
29
+ return launchManaged(url, profileName, headless, compact, proxyOverride, noProxy);
26
30
  }
27
- async function killChromeOnPort(port) {
28
- const pids = getPidsOnPort(port);
29
- if (pids.length === 0)
30
- return;
31
- for (const pid of pids) {
32
- try {
33
- process.kill(pid, 'SIGTERM');
34
- }
35
- catch {
36
- // Skip stale PID.
37
- }
31
+ async function cleanupOwnedSession() {
32
+ const existingSession = loadSession();
33
+ if (!isOwnedSession(existingSession)) {
34
+ return null;
38
35
  }
39
- for (let i = 0; i < 20; i++) {
40
- if (getPidsOnPort(port).length === 0)
41
- return;
42
- await sleep(100);
36
+ const termination = await terminateOwnedPid(existingSession.pid);
37
+ if (termination === 'still_alive') {
38
+ return buildLaunchFailure(new Error(`Existing owned browser pid ${existingSession.pid} did not exit after SIGTERM/SIGKILL.`));
43
39
  }
44
- for (const pid of getPidsOnPort(port)) {
45
- try {
46
- process.kill(pid, 'SIGKILL');
47
- }
48
- catch {
49
- // Skip stale PID.
40
+ return null;
41
+ }
42
+ function buildLaunchFailure(err) {
43
+ return {
44
+ success: false,
45
+ error: 'browser_launch_failed',
46
+ outcomeType: 'blocked',
47
+ message: 'Browser launch failed.',
48
+ reason: formatUnknownError(err),
49
+ };
50
+ }
51
+ function getPortFromCdpUrl(cdpUrl) {
52
+ try {
53
+ const url = new URL(cdpUrl);
54
+ if (!url.port) {
55
+ return undefined;
50
56
  }
57
+ const port = Number(url.port);
58
+ return Number.isFinite(port) && port > 0 ? port : undefined;
59
+ }
60
+ catch {
61
+ return undefined;
51
62
  }
52
63
  }
53
- async function launchManaged(url, profileName, headless, compact) {
64
+ async function launchManaged(url, profileName, headless, compact, proxyOverride, noProxy = false) {
54
65
  let session;
55
66
  let browser = null;
56
67
  let secretCatalogSummary;
57
68
  try {
58
- const profile = ensureProfile(profileName);
69
+ const baseProfile = ensureProfile(profileName);
59
70
  const config = readConfig();
71
+ const runtimeProxy = resolveLaunchProxy({
72
+ profileProxy: baseProfile.fingerprint.proxy,
73
+ configProxy: config.defaults?.proxy,
74
+ cliProxy: proxyOverride,
75
+ noProxy,
76
+ });
77
+ const profile = withLaunchProxy(baseProfile, runtimeProxy);
60
78
  const gateway = tryResolveAgentpayGatewayConfig();
61
79
  if (gateway) {
62
80
  applyAgentpayGatewayEnv(gateway);
63
81
  }
82
+ if (runtimeProxy) {
83
+ info(`[launch] starting browser with proxy ${runtimeProxy.server}`);
84
+ }
64
85
  session = await launchSolver(profile, {
65
86
  headless: headless || config.defaults?.headless,
66
87
  url,
67
- cdpPort: CDP_PORT,
88
+ cdpPort: undefined,
68
89
  windowSize: compact ? COMPACT_WINDOW : undefined,
69
90
  });
70
91
  }
71
92
  catch (err) {
72
- outputContractFailure({
73
- error: 'browser_launch_failed',
74
- outcomeType: 'blocked',
75
- message: 'Browser launch failed.',
76
- reason: formatUnknownError(err),
77
- });
93
+ return buildLaunchFailure(err);
94
+ }
95
+ const cdpPort = getPortFromCdpUrl(session.cdpUrl);
96
+ const chromePid = findChromePid(cdpPort);
97
+ if (!chromePid) {
98
+ await session.close().catch(() => undefined);
99
+ return buildLaunchFailure(new Error('Launched browser PID could not be resolved.'));
78
100
  }
79
- const chromePid = findChromePid(CDP_PORT) ?? process.pid;
80
- const persistedSession = {
101
+ const persistedSession = buildOwnedSession({
81
102
  cdpUrl: session.cdpUrl,
82
103
  pid: chromePid,
83
- port: CDP_PORT,
84
104
  profile: profileName,
85
105
  launchedAt: new Date().toISOString(),
86
106
  capabilities: {
87
107
  captchaSolve: true,
88
108
  },
89
- };
109
+ });
90
110
  saveSession(persistedSession);
91
111
  let currentUrl = session.page.url();
92
112
  let title = await session.page.title().catch(() => '');
@@ -111,10 +131,12 @@ async function launchManaged(url, profileName, headless, compact) {
111
131
  }
112
132
  if (url) {
113
133
  const snapshot = await syncSecretCatalogForUrl(persistedSession, currentUrl || url);
114
- secretCatalogSummary = summarizeSecretCatalog(snapshot);
134
+ if (snapshot) {
135
+ secretCatalogSummary = summarizeSecretCatalog(snapshot);
136
+ }
115
137
  }
116
138
  saveSession(persistedSession);
117
- outputJSON({
139
+ return {
118
140
  success: true,
119
141
  runtime: 'managed',
120
142
  captchaSolveCapable: true,
@@ -123,21 +145,7 @@ async function launchManaged(url, profileName, headless, compact) {
123
145
  url: currentUrl,
124
146
  title,
125
147
  secretCatalog: secretCatalogSummary,
126
- });
127
- }
128
- function getPidsOnPort(port) {
129
- try {
130
- const out = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
131
- if (!out)
132
- return [];
133
- return out
134
- .split('\n')
135
- .map((value) => Number(value))
136
- .filter((value) => Number.isFinite(value) && value > 0);
137
- }
138
- catch {
139
- return [];
140
- }
148
+ };
141
149
  }
142
150
  function formatUnknownError(err) {
143
151
  if (err instanceof Error)
@@ -154,6 +162,61 @@ function formatUnknownError(err) {
154
162
  }
155
163
  return String(err);
156
164
  }
157
- function sleep(ms) {
158
- return new Promise((resolve) => setTimeout(resolve, ms));
165
+ function resolveLaunchProxy(options) {
166
+ if (options.noProxy) {
167
+ return undefined;
168
+ }
169
+ if (options.cliProxy) {
170
+ return normalizeProxySetting(options.cliProxy);
171
+ }
172
+ if (options.configProxy) {
173
+ return normalizeProxySetting(options.configProxy);
174
+ }
175
+ if (options.profileProxy) {
176
+ return normalizeProxySetting(options.profileProxy);
177
+ }
178
+ return undefined;
179
+ }
180
+ function withLaunchProxy(profile, proxy) {
181
+ return {
182
+ ...profile,
183
+ fingerprint: {
184
+ ...profile.fingerprint,
185
+ proxy,
186
+ },
187
+ };
188
+ }
189
+ function normalizeProxySetting(value) {
190
+ if (typeof value === 'string') {
191
+ return parseProxyString(value);
192
+ }
193
+ const normalized = parseProxyString(value.server);
194
+ const username = value.username?.trim() || normalized.username;
195
+ const password = value.password ?? normalized.password;
196
+ return {
197
+ server: normalized.server,
198
+ ...(username ? { username } : {}),
199
+ ...(password ? { password } : {}),
200
+ };
201
+ }
202
+ function parseProxyString(value) {
203
+ const trimmed = value.trim();
204
+ if (!trimmed) {
205
+ throw new Error('Proxy value must not be empty.');
206
+ }
207
+ try {
208
+ const parsed = new URL(trimmed);
209
+ const server = `${parsed.protocol}//${parsed.host}`;
210
+ if (!parsed.hostname) {
211
+ throw new Error('missing hostname');
212
+ }
213
+ return {
214
+ server,
215
+ ...(parsed.username ? { username: decodeURIComponent(parsed.username) } : {}),
216
+ ...(parsed.password ? { password: decodeURIComponent(parsed.password) } : {}),
217
+ };
218
+ }
219
+ catch {
220
+ return { server: trimmed };
221
+ }
159
222
  }
@@ -0,0 +1,22 @@
1
+ import type { Page } from 'playwright-core';
2
+ import type { DomObservedTarget } from './observe-inventory.js';
3
+ type ObserveAccessibilitySemantics = {
4
+ role?: string;
5
+ name?: string;
6
+ states?: Record<string, string | boolean | number>;
7
+ };
8
+ export type ObserveAccessibilityStats = {
9
+ axAttempts: number;
10
+ axHits: number;
11
+ fallbackUses: number;
12
+ };
13
+ type ObserveAccessibilityOptions = {
14
+ onStats?: (stats: ObserveAccessibilityStats) => void;
15
+ };
16
+ export declare function parseObserveAriaSnapshot(snapshot: string | undefined | null): ObserveAccessibilitySemantics | null;
17
+ export declare function enrichDomTargetsWithAccessibility(page: Page, targets: ReadonlyArray<DomObservedTarget>, options?: ObserveAccessibilityOptions): Promise<DomObservedTarget[]>;
18
+ export declare const __testObserveAccessibility: {
19
+ parseObserveAriaSnapshot: typeof parseObserveAriaSnapshot;
20
+ };
21
+ export {};
22
+ //# sourceMappingURL=observe-accessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observe-accessibility.d.ts","sourceRoot":"","sources":["../../src/commands/observe-accessibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAS5C,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAEhC,KAAK,6BAA6B,GAAG;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;CACpD,CAAC;AAOF,MAAM,MAAM,yBAAyB,GAAG;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,KAAK,2BAA2B,GAAG;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACtD,CAAC;AAyDF,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAClC,6BAA6B,GAAG,IAAI,CAoDtC;AA+nBD,wBAAsB,iCAAiC,CACrD,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,aAAa,CAAC,iBAAiB,CAAC,EACzC,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAY9B;AAED,eAAO,MAAM,0BAA0B;;CAEtC,CAAC"}