agent-browser-stealth 0.17.0-fork.2 → 0.24.0-fork.2

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 (122) hide show
  1. package/README.md +1256 -240
  2. package/bin/agent-browser-darwin-arm64 +0 -0
  3. package/bin/agent-browser-darwin-x64 +0 -0
  4. package/bin/agent-browser-linux-arm64 +0 -0
  5. package/bin/agent-browser-linux-x64 +0 -0
  6. package/bin/agent-browser-win32-x64.exe +0 -0
  7. package/bin/agent-browser.js +13 -2
  8. package/extensions/tab-group-cdp/content-script.js +425 -0
  9. package/extensions/tab-group-cdp/icons/icon.svg +7 -0
  10. package/extensions/tab-group-cdp/manifest.json +34 -0
  11. package/extensions/tab-group-cdp/page-bridge.js +133 -0
  12. package/extensions/tab-group-cdp/service-worker.js +2249 -0
  13. package/extensions/tab-group-cdp/sidepanel.css +258 -0
  14. package/extensions/tab-group-cdp/sidepanel.html +28 -0
  15. package/extensions/tab-group-cdp/sidepanel.js +1225 -0
  16. package/package.json +17 -69
  17. package/scripts/build-all-platforms.sh +6 -0
  18. package/scripts/check-version-sync.js +14 -2
  19. package/scripts/copy-native.js +8 -50
  20. package/scripts/postinstall.js +149 -165
  21. package/scripts/windows-debug/provision.sh +220 -0
  22. package/scripts/windows-debug/run.sh +92 -0
  23. package/scripts/windows-debug/start.sh +43 -0
  24. package/scripts/windows-debug/stop.sh +28 -0
  25. package/scripts/windows-debug/sync.sh +27 -0
  26. package/skills/agent-browser/SKILL.md +256 -159
  27. package/skills/agent-browser/references/authentication.md +101 -0
  28. package/skills/agent-browser/references/commands.md +34 -2
  29. package/skills/agent-browser/references/snapshot-refs.md +25 -0
  30. package/skills/agentcore/SKILL.md +115 -0
  31. package/skills/dogfood/SKILL.md +4 -2
  32. package/skills/electron/SKILL.md +26 -2
  33. package/skills/slack/SKILL.md +0 -9
  34. package/skills/slack/references/slack-tasks.md +2 -8
  35. package/skills/vercel-sandbox/SKILL.md +280 -0
  36. package/bin/agent-browser-local +0 -0
  37. package/bin/agent-browser-stealth +0 -0
  38. package/bin/agent-browser-stealth.d +0 -1
  39. package/dist/action-policy.d.ts +0 -14
  40. package/dist/action-policy.d.ts.map +0 -1
  41. package/dist/action-policy.js +0 -253
  42. package/dist/action-policy.js.map +0 -1
  43. package/dist/actions.d.ts +0 -21
  44. package/dist/actions.d.ts.map +0 -1
  45. package/dist/actions.js +0 -2139
  46. package/dist/actions.js.map +0 -1
  47. package/dist/auth-cli.d.ts +0 -2
  48. package/dist/auth-cli.d.ts.map +0 -1
  49. package/dist/auth-cli.js +0 -97
  50. package/dist/auth-cli.js.map +0 -1
  51. package/dist/auth-vault.d.ts +0 -36
  52. package/dist/auth-vault.d.ts.map +0 -1
  53. package/dist/auth-vault.js +0 -125
  54. package/dist/auth-vault.js.map +0 -1
  55. package/dist/browser.d.ts +0 -665
  56. package/dist/browser.d.ts.map +0 -1
  57. package/dist/browser.js +0 -3210
  58. package/dist/browser.js.map +0 -1
  59. package/dist/confirmation.d.ts +0 -8
  60. package/dist/confirmation.d.ts.map +0 -1
  61. package/dist/confirmation.js +0 -30
  62. package/dist/confirmation.js.map +0 -1
  63. package/dist/daemon.d.ts +0 -78
  64. package/dist/daemon.d.ts.map +0 -1
  65. package/dist/daemon.js +0 -744
  66. package/dist/daemon.js.map +0 -1
  67. package/dist/diff.d.ts +0 -18
  68. package/dist/diff.d.ts.map +0 -1
  69. package/dist/diff.js +0 -271
  70. package/dist/diff.js.map +0 -1
  71. package/dist/domain-filter.d.ts +0 -28
  72. package/dist/domain-filter.d.ts.map +0 -1
  73. package/dist/domain-filter.js +0 -149
  74. package/dist/domain-filter.js.map +0 -1
  75. package/dist/encryption.d.ts +0 -73
  76. package/dist/encryption.d.ts.map +0 -1
  77. package/dist/encryption.js +0 -171
  78. package/dist/encryption.js.map +0 -1
  79. package/dist/ios-actions.d.ts +0 -11
  80. package/dist/ios-actions.d.ts.map +0 -1
  81. package/dist/ios-actions.js +0 -228
  82. package/dist/ios-actions.js.map +0 -1
  83. package/dist/ios-manager.d.ts +0 -266
  84. package/dist/ios-manager.d.ts.map +0 -1
  85. package/dist/ios-manager.js +0 -1073
  86. package/dist/ios-manager.js.map +0 -1
  87. package/dist/protocol.d.ts +0 -26
  88. package/dist/protocol.d.ts.map +0 -1
  89. package/dist/protocol.js +0 -990
  90. package/dist/protocol.js.map +0 -1
  91. package/dist/snapshot.d.ts +0 -67
  92. package/dist/snapshot.d.ts.map +0 -1
  93. package/dist/snapshot.js +0 -514
  94. package/dist/snapshot.js.map +0 -1
  95. package/dist/state-utils.d.ts +0 -77
  96. package/dist/state-utils.d.ts.map +0 -1
  97. package/dist/state-utils.js +0 -178
  98. package/dist/state-utils.js.map +0 -1
  99. package/dist/stealth.d.ts +0 -41
  100. package/dist/stealth.d.ts.map +0 -1
  101. package/dist/stealth.js +0 -1743
  102. package/dist/stealth.js.map +0 -1
  103. package/dist/stream-server.d.ts +0 -117
  104. package/dist/stream-server.d.ts.map +0 -1
  105. package/dist/stream-server.js +0 -309
  106. package/dist/stream-server.js.map +0 -1
  107. package/dist/types.d.ts +0 -973
  108. package/dist/types.d.ts.map +0 -1
  109. package/dist/types.js +0 -2
  110. package/dist/types.js.map +0 -1
  111. package/scripts/check-creepjs-headless.js +0 -137
  112. package/scripts/check-daemon-pid-recovery.js +0 -148
  113. package/scripts/check-sannysoft-webdriver.js +0 -112
  114. package/scripts/check-stealth-regression.js +0 -199
  115. package/scripts/check-turnstile-testkey.ts +0 -125
  116. package/scripts/clawhub-sync.sh +0 -27
  117. package/scripts/sync-upstream.sh +0 -142
  118. package/scripts/verify-bundled-binaries.js +0 -71
  119. package/scripts/verify-native-version.js +0 -48
  120. package/scripts/verify-packed-host-binary.js +0 -88
  121. package/scripts/verify-registry-host-binary.js +0 -120
  122. package/skills/agent-browser-stealth/SKILL.md +0 -127
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -8,7 +8,7 @@
8
8
  * binary directly (zero overhead).
9
9
  */
10
10
 
11
- import { spawn } from 'child_process';
11
+ import { spawn, execSync } from 'child_process';
12
12
  import { existsSync, accessSync, chmodSync, constants } from 'fs';
13
13
  import { dirname, join } from 'path';
14
14
  import { fileURLToPath } from 'url';
@@ -16,6 +16,17 @@ import { platform, arch } from 'os';
16
16
 
17
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
18
 
19
+ // Detect if the system uses musl libc (e.g. Alpine Linux)
20
+ function isMusl() {
21
+ if (platform() !== 'linux') return false;
22
+ try {
23
+ const result = execSync('ldd --version 2>&1 || true', { encoding: 'utf8' });
24
+ return result.toLowerCase().includes('musl');
25
+ } catch {
26
+ return existsSync('/lib/ld-musl-x86_64.so.1') || existsSync('/lib/ld-musl-aarch64.so.1');
27
+ }
28
+ }
29
+
19
30
  // Map Node.js platform/arch to binary naming convention
20
31
  function getBinaryName() {
21
32
  const os = platform();
@@ -27,7 +38,7 @@ function getBinaryName() {
27
38
  osKey = 'darwin';
28
39
  break;
29
40
  case 'linux':
30
- osKey = 'linux';
41
+ osKey = isMusl() ? 'linux-musl' : 'linux';
31
42
  break;
32
43
  case 'win32':
33
44
  osKey = 'win32';
@@ -0,0 +1,425 @@
1
+ (() => {
2
+ const REQUEST_TYPE = 'AB_TAB_GROUP_REQUEST';
3
+ const RESPONSE_TYPE = 'AB_TAB_GROUP_RESPONSE';
4
+
5
+ const CONTENT_EVENT_TYPE = 'AB_CONTENT_EVENT';
6
+ const CONTENT_EXECUTE_ACTION = 'AB_CONTENT_EXECUTE_ACTION';
7
+ const CONTENT_GET_DOM_STATE = 'AB_CONTENT_GET_DOM_STATE';
8
+ const CONTENT_PING = 'AB_CONTENT_PING';
9
+ const PAGE_BRIDGE_EVENT = 'AB_PAGE_BRIDGE_EVENT';
10
+ const STORAGE_OPTIONS_KEY = 'abExtensionOptionsV1';
11
+
12
+ const mutationState = {
13
+ total: 0,
14
+ recent: [],
15
+ observerReady: false,
16
+ };
17
+
18
+ function pushMutationSummary(entry) {
19
+ mutationState.total += 1;
20
+ mutationState.recent.push({
21
+ ...entry,
22
+ timestamp: Date.now(),
23
+ });
24
+ if (mutationState.recent.length > 40) {
25
+ mutationState.recent.splice(0, mutationState.recent.length - 40);
26
+ }
27
+ }
28
+
29
+ function serializeValue(value, depth = 0) {
30
+ if (value === null || typeof value === 'undefined') return value;
31
+ if (typeof value === 'string') return value.slice(0, 300);
32
+ if (typeof value === 'number' || typeof value === 'boolean') return value;
33
+ if (value instanceof Error) return `${value.name}: ${value.message}`;
34
+ if (depth > 2) return '[depth-limit]';
35
+
36
+ if (Array.isArray(value)) {
37
+ return value.slice(0, 10).map((item) => serializeValue(item, depth + 1));
38
+ }
39
+
40
+ if (typeof value === 'object') {
41
+ const out = {};
42
+ for (const [key, entry] of Object.entries(value).slice(0, 15)) {
43
+ out[key] = serializeValue(entry, depth + 1);
44
+ }
45
+ return out;
46
+ }
47
+
48
+ return String(value).slice(0, 300);
49
+ }
50
+
51
+ function sendRuntimeEvent(kind, payload) {
52
+ try {
53
+ chrome.runtime.sendMessage({
54
+ type: CONTENT_EVENT_TYPE,
55
+ kind,
56
+ payload: serializeValue(payload),
57
+ url: window.location.href,
58
+ title: document.title,
59
+ timestamp: Date.now(),
60
+ });
61
+ } catch {
62
+ // Ignore runtime channel errors.
63
+ }
64
+ }
65
+
66
+ function getPageBridgeEnabled() {
67
+ return new Promise((resolve) => {
68
+ try {
69
+ chrome.storage.local.get([STORAGE_OPTIONS_KEY], (result) => {
70
+ if (chrome.runtime.lastError) {
71
+ resolve(false);
72
+ return;
73
+ }
74
+ const rawOptions = result?.[STORAGE_OPTIONS_KEY];
75
+ resolve(Boolean(rawOptions && typeof rawOptions === 'object' && rawOptions.pageBridgeEnabled === true));
76
+ });
77
+ } catch {
78
+ resolve(false);
79
+ }
80
+ });
81
+ }
82
+
83
+ async function installPageBridge() {
84
+ // Receives events emitted by the injected page-world hook script.
85
+ const bridgeListener = (event) => {
86
+ if (event.source !== window) return;
87
+ const data = event.data;
88
+ if (!data || data.type !== PAGE_BRIDGE_EVENT) return;
89
+ sendRuntimeEvent(data.kind || 'page-event', data.payload || {});
90
+ };
91
+
92
+ window.addEventListener('message', bridgeListener);
93
+
94
+ const parent = document.documentElement || document.head || document.body;
95
+ if (!parent) return;
96
+
97
+ if (!(await getPageBridgeEnabled())) {
98
+ sendRuntimeEvent('lifecycle', {
99
+ event: 'bridge-disabled-default',
100
+ });
101
+ return;
102
+ }
103
+
104
+ // Use external extension script instead of inline text to reduce CSP conflicts.
105
+ const script = document.createElement('script');
106
+ script.src = chrome.runtime.getURL('page-bridge.js');
107
+ script.async = false;
108
+ script.dataset.abBridgeEvent = PAGE_BRIDGE_EVENT;
109
+ script.onload = () => script.remove();
110
+ script.onerror = () => {
111
+ sendRuntimeEvent('lifecycle', {
112
+ event: 'bridge-load-failed',
113
+ host: window.location.hostname,
114
+ });
115
+ script.remove();
116
+ };
117
+ parent.appendChild(script);
118
+ }
119
+
120
+ function ensureMutationObserver() {
121
+ if (mutationState.observerReady) return;
122
+ if (!document.documentElement) return;
123
+
124
+ const observer = new MutationObserver((records) => {
125
+ const summary = {
126
+ records: records.length,
127
+ addedNodes: 0,
128
+ removedNodes: 0,
129
+ };
130
+
131
+ for (const record of records.slice(0, 40)) {
132
+ summary.addedNodes += record.addedNodes?.length || 0;
133
+ summary.removedNodes += record.removedNodes?.length || 0;
134
+ }
135
+
136
+ pushMutationSummary(summary);
137
+ });
138
+
139
+ observer.observe(document.documentElement, {
140
+ childList: true,
141
+ subtree: true,
142
+ attributes: true,
143
+ attributeFilter: ['class', 'style', 'hidden', 'disabled', 'aria-hidden'],
144
+ });
145
+
146
+ mutationState.observerReady = true;
147
+ }
148
+
149
+ function toSimpleNode(element) {
150
+ if (!element || typeof element !== 'object') return null;
151
+ const node = {
152
+ tag: element.tagName?.toLowerCase() || 'unknown',
153
+ id: element.id || undefined,
154
+ className: typeof element.className === 'string' ? element.className.slice(0, 120) : '',
155
+ role: element.getAttribute?.('role') || undefined,
156
+ name:
157
+ element.getAttribute?.('aria-label') ||
158
+ element.getAttribute?.('name') ||
159
+ element.getAttribute?.('placeholder') ||
160
+ '',
161
+ text: (element.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 160),
162
+ disabled: element.disabled === true,
163
+ hidden: element.hidden === true,
164
+ };
165
+
166
+ return node;
167
+ }
168
+
169
+ function collectInteractiveElements(root, limit = 80) {
170
+ const selector = [
171
+ 'a[href]',
172
+ 'button',
173
+ 'input',
174
+ 'select',
175
+ 'textarea',
176
+ 'summary',
177
+ '[role="button"]',
178
+ '[role="link"]',
179
+ '[tabindex]'
180
+ ].join(',');
181
+
182
+ const out = [];
183
+ const nodes = root.querySelectorAll(selector);
184
+ for (const element of nodes) {
185
+ if (out.length >= limit) break;
186
+ out.push(toSimpleNode(element));
187
+ }
188
+ return out.filter(Boolean);
189
+ }
190
+
191
+ function collectDomState(options = {}) {
192
+ const selector = typeof options.selector === 'string' ? options.selector.trim() : '';
193
+ const root = selector ? document.querySelector(selector) : document.body || document.documentElement;
194
+
195
+ if (!root) {
196
+ return {
197
+ ok: false,
198
+ error: selector ? `selector-not-found: ${selector}` : 'root-not-found',
199
+ };
200
+ }
201
+
202
+ const textPreview = (root.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 1000);
203
+ const interactiveOnly = options.interactiveOnly === true;
204
+ const interactiveElements = collectInteractiveElements(root, options.maxNodes || 80);
205
+
206
+ const dom = {
207
+ href: window.location.href,
208
+ title: document.title,
209
+ readyState: document.readyState,
210
+ selector: selector || null,
211
+ rootTag: root.tagName?.toLowerCase() || 'unknown',
212
+ textPreview,
213
+ interactiveCount: interactiveElements.length,
214
+ interactiveElements,
215
+ mutation: {
216
+ total: mutationState.total,
217
+ recent: mutationState.recent.slice(-10),
218
+ },
219
+ capturedAt: Date.now(),
220
+ };
221
+
222
+ if (interactiveOnly) {
223
+ dom.textPreview = '';
224
+ }
225
+
226
+ return {
227
+ ok: true,
228
+ state: dom,
229
+ };
230
+ }
231
+
232
+ function queryElement(selector) {
233
+ if (typeof selector !== 'string' || selector.trim().length === 0) {
234
+ throw new Error('selector is required');
235
+ }
236
+ const element = document.querySelector(selector);
237
+ if (!element) {
238
+ throw new Error(`Element not found: ${selector}`);
239
+ }
240
+ return element;
241
+ }
242
+
243
+ function focusElement(element) {
244
+ if (typeof element.focus === 'function') {
245
+ element.focus({ preventScroll: false });
246
+ }
247
+ }
248
+
249
+ function dispatchInputEvents(element) {
250
+ element.dispatchEvent(new Event('input', { bubbles: true }));
251
+ element.dispatchEvent(new Event('change', { bubbles: true }));
252
+ }
253
+
254
+ async function executeAction(command, args = {}) {
255
+ switch (command) {
256
+ case 'click': {
257
+ const element = queryElement(args.selector);
258
+ focusElement(element);
259
+ element.click();
260
+ return { ok: true, action: command, selector: args.selector };
261
+ }
262
+ case 'fill': {
263
+ const element = queryElement(args.selector);
264
+ if (!('value' in element)) {
265
+ throw new Error(`Element is not fillable: ${args.selector}`);
266
+ }
267
+ focusElement(element);
268
+ element.value = typeof args.value === 'string' ? args.value : String(args.value || '');
269
+ dispatchInputEvents(element);
270
+ return { ok: true, action: command, selector: args.selector, valueLength: element.value.length };
271
+ }
272
+ case 'press': {
273
+ const key = typeof args.key === 'string' && args.key.trim().length > 0 ? args.key.trim() : 'Enter';
274
+ let target;
275
+ if (typeof args.selector === 'string' && args.selector.trim().length > 0) {
276
+ target = queryElement(args.selector);
277
+ focusElement(target);
278
+ } else {
279
+ target = document.activeElement || document.body;
280
+ }
281
+
282
+ const down = new KeyboardEvent('keydown', { key, bubbles: true });
283
+ const up = new KeyboardEvent('keyup', { key, bubbles: true });
284
+ target.dispatchEvent(down);
285
+ target.dispatchEvent(up);
286
+ return { ok: true, action: command, key };
287
+ }
288
+ case 'eval': {
289
+ if (typeof args.expression !== 'string' || args.expression.trim().length === 0) {
290
+ throw new Error('expression is required');
291
+ }
292
+ const fn = new Function(`return (${args.expression});`);
293
+ const result = fn();
294
+ return { ok: true, action: command, result: serializeValue(result) };
295
+ }
296
+ case 'snapshot': {
297
+ return {
298
+ ok: true,
299
+ action: command,
300
+ ...collectDomState({
301
+ selector: args.selector,
302
+ interactiveOnly: args.interactiveOnly === true,
303
+ maxNodes: args.maxNodes,
304
+ }),
305
+ };
306
+ }
307
+ default:
308
+ throw new Error(`Unknown content action: ${command}`);
309
+ }
310
+ }
311
+
312
+ ensureMutationObserver();
313
+ installPageBridge();
314
+
315
+ window.addEventListener('message', (event) => {
316
+ if (event.source !== window) {
317
+ return;
318
+ }
319
+
320
+ const data = event.data;
321
+ if (!data || data.type !== REQUEST_TYPE) {
322
+ return;
323
+ }
324
+
325
+ const request = {
326
+ type: REQUEST_TYPE,
327
+ nonce: data.nonce,
328
+ session: data.session,
329
+ groupTitle: data.groupTitle,
330
+ pluginId: data.pluginId,
331
+ allowedDomains: Array.isArray(data.allowedDomains) ? data.allowedDomains : undefined,
332
+ };
333
+
334
+ try {
335
+ chrome.runtime.sendMessage(request, (response) => {
336
+ const lastError = chrome.runtime.lastError;
337
+ if (lastError) {
338
+ window.postMessage(
339
+ {
340
+ type: RESPONSE_TYPE,
341
+ nonce: request.nonce,
342
+ ok: false,
343
+ error: lastError.message,
344
+ },
345
+ '*'
346
+ );
347
+ return;
348
+ }
349
+
350
+ const payload = response && typeof response === 'object' ? response : { ok: false };
351
+
352
+ window.postMessage(
353
+ {
354
+ type: RESPONSE_TYPE,
355
+ nonce: request.nonce,
356
+ ok: payload.ok === true,
357
+ extensionId:
358
+ typeof payload.extensionId === 'string' && payload.extensionId.length > 0
359
+ ? payload.extensionId
360
+ : chrome.runtime.id,
361
+ groupId: typeof payload.groupId === 'number' ? payload.groupId : undefined,
362
+ windowId: typeof payload.windowId === 'number' ? payload.windowId : undefined,
363
+ color: typeof payload.color === 'string' ? payload.color : undefined,
364
+ collapsed: payload.collapsed === true,
365
+ policy:
366
+ payload.policy && typeof payload.policy === 'object'
367
+ ? {
368
+ enforced: payload.policy.enforced === true,
369
+ blocked: payload.policy.blocked === true,
370
+ reason:
371
+ typeof payload.policy.reason === 'string' ? payload.policy.reason : undefined,
372
+ }
373
+ : undefined,
374
+ riskHints: Array.isArray(payload.riskHints) ? payload.riskHints : undefined,
375
+ error: typeof payload.error === 'string' ? payload.error : undefined,
376
+ },
377
+ '*'
378
+ );
379
+ });
380
+ } catch (error) {
381
+ const errorMessage = error instanceof Error ? error.message : String(error);
382
+ window.postMessage(
383
+ {
384
+ type: RESPONSE_TYPE,
385
+ nonce: request.nonce,
386
+ ok: false,
387
+ error: errorMessage,
388
+ },
389
+ '*'
390
+ );
391
+ }
392
+ });
393
+
394
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
395
+ if (!message || typeof message !== 'object') return;
396
+
397
+ if (message.type === CONTENT_PING) {
398
+ sendResponse({
399
+ ok: true,
400
+ href: window.location.href,
401
+ title: document.title,
402
+ readyState: document.readyState,
403
+ });
404
+ return;
405
+ }
406
+
407
+ if (message.type === CONTENT_GET_DOM_STATE) {
408
+ sendResponse(collectDomState(message.options || {}));
409
+ return;
410
+ }
411
+
412
+ if (message.type === CONTENT_EXECUTE_ACTION) {
413
+ executeAction(message.command, message.args || {})
414
+ .then((result) => sendResponse(result))
415
+ .catch((error) => {
416
+ sendResponse({
417
+ ok: false,
418
+ action: message.command,
419
+ error: error instanceof Error ? error.message : String(error),
420
+ });
421
+ });
422
+ return true;
423
+ }
424
+ });
425
+ })();
@@ -0,0 +1,7 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="128" height="128" rx="32" fill="#1A73E8"/>
3
+ <rect x="30" y="34" width="68" height="10" rx="2" fill="white"/>
4
+ <rect x="30" y="54" width="48" height="10" rx="2" fill="white" fill-opacity="0.8"/>
5
+ <rect x="30" y="74" width="28" height="10" rx="2" fill="white" fill-opacity="0.6"/>
6
+ <circle cx="94" cy="90" r="10" fill="#34A853" stroke="#1A73E8" stroke-width="4"/>
7
+ </svg>
@@ -0,0 +1,34 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "agent-browser-stealth",
4
+ "version": "0.2.0",
5
+ "description": "Session-aware tab grouping and coordination for CDP-driven agent-browser workflows.",
6
+ "icons": {
7
+ "128": "icons/icon.svg"
8
+ },
9
+ "permissions": ["tabs", "tabGroups", "downloads", "storage", "sidePanel", "alarms"],
10
+ "host_permissions": ["<all_urls>"],
11
+ "background": {
12
+ "service_worker": "service-worker.js"
13
+ },
14
+ "action": {
15
+ "default_title": "agent-browser-stealth"
16
+ },
17
+ "side_panel": {
18
+ "default_path": "sidepanel.html"
19
+ },
20
+ "content_scripts": [
21
+ {
22
+ "matches": ["<all_urls>"],
23
+ "js": ["content-script.js"],
24
+ "run_at": "document_start",
25
+ "match_about_blank": true
26
+ }
27
+ ],
28
+ "web_accessible_resources": [
29
+ {
30
+ "resources": ["page-bridge.js"],
31
+ "matches": ["<all_urls>"]
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,133 @@
1
+ (() => {
2
+ if (window.__AB_STEALTH_BRIDGE_INSTALLED__) return;
3
+ window.__AB_STEALTH_BRIDGE_INSTALLED__ = true;
4
+
5
+ const currentScript = document.currentScript;
6
+ const TYPE = currentScript?.dataset?.abBridgeEvent || 'AB_PAGE_BRIDGE_EVENT';
7
+
8
+ const post = (kind, payload) => {
9
+ try {
10
+ window.postMessage({ type: TYPE, kind, payload, timestamp: Date.now() }, '*');
11
+ } catch {
12
+ // Ignore post failures.
13
+ }
14
+ };
15
+
16
+ const serializeArg = (value, depth = 0) => {
17
+ if (value === null || typeof value === 'undefined') return value;
18
+ if (typeof value === 'string') return value.slice(0, 250);
19
+ if (typeof value === 'number' || typeof value === 'boolean') return value;
20
+ if (value instanceof Error) return `${value.name}: ${value.message}`;
21
+ if (depth > 2) return '[depth-limit]';
22
+ if (Array.isArray(value)) return value.slice(0, 10).map((item) => serializeArg(item, depth + 1));
23
+ if (typeof value === 'object') {
24
+ const out = {};
25
+ const entries = Object.entries(value).slice(0, 12);
26
+ for (const [k, v] of entries) {
27
+ out[k] = serializeArg(v, depth + 1);
28
+ }
29
+ return out;
30
+ }
31
+ return String(value).slice(0, 250);
32
+ };
33
+
34
+ const patchConsoleMethod = (name) => {
35
+ const original = console[name];
36
+ if (typeof original !== 'function') return;
37
+ console[name] = function patchedConsole(...args) {
38
+ post('console', {
39
+ level: name,
40
+ args: args.map((arg) => serializeArg(arg)),
41
+ });
42
+ return original.apply(this, args);
43
+ };
44
+ };
45
+
46
+ patchConsoleMethod('error');
47
+ patchConsoleMethod('warn');
48
+
49
+ window.addEventListener('error', (event) => {
50
+ post('console', {
51
+ level: 'error',
52
+ message: event.message,
53
+ source: event.filename,
54
+ line: event.lineno,
55
+ column: event.colno,
56
+ });
57
+ });
58
+
59
+ window.addEventListener('unhandledrejection', (event) => {
60
+ post('console', {
61
+ level: 'error',
62
+ message: 'Unhandled rejection',
63
+ reason: serializeArg(event.reason),
64
+ });
65
+ });
66
+
67
+ if (typeof window.fetch === 'function') {
68
+ const originalFetch = window.fetch.bind(window);
69
+ window.fetch = async (...args) => {
70
+ const startedAt = Date.now();
71
+ const requestInfo = args[0];
72
+ const requestInit = args[1] || {};
73
+ const method = requestInit.method || 'GET';
74
+ const url = typeof requestInfo === 'string' ? requestInfo : requestInfo?.url || '';
75
+
76
+ try {
77
+ const response = await originalFetch(...args);
78
+ post('network', {
79
+ transport: 'fetch',
80
+ method,
81
+ url,
82
+ status: response.status,
83
+ ok: response.ok,
84
+ durationMs: Date.now() - startedAt,
85
+ });
86
+ return response;
87
+ } catch (error) {
88
+ post('network', {
89
+ transport: 'fetch',
90
+ method,
91
+ url,
92
+ error: serializeArg(error),
93
+ durationMs: Date.now() - startedAt,
94
+ });
95
+ throw error;
96
+ }
97
+ };
98
+ }
99
+
100
+ if (typeof window.XMLHttpRequest === 'function') {
101
+ const originalOpen = XMLHttpRequest.prototype.open;
102
+ const originalSend = XMLHttpRequest.prototype.send;
103
+
104
+ XMLHttpRequest.prototype.open = function patchedOpen(method, url, ...rest) {
105
+ this.__abRequestMeta = {
106
+ method: typeof method === 'string' ? method : 'GET',
107
+ url: typeof url === 'string' ? url : String(url || ''),
108
+ startedAt: Date.now(),
109
+ };
110
+ return originalOpen.call(this, method, url, ...rest);
111
+ };
112
+
113
+ XMLHttpRequest.prototype.send = function patchedSend(...args) {
114
+ this.addEventListener('loadend', () => {
115
+ const meta = this.__abRequestMeta || {};
116
+ post('network', {
117
+ transport: 'xhr',
118
+ method: meta.method || 'GET',
119
+ url: meta.url || '',
120
+ status: this.status,
121
+ ok: this.status >= 200 && this.status < 400,
122
+ durationMs: Date.now() - (meta.startedAt || Date.now()),
123
+ });
124
+ });
125
+ return originalSend.apply(this, args);
126
+ };
127
+ }
128
+
129
+ post('lifecycle', {
130
+ event: 'bridge-installed',
131
+ href: location.href,
132
+ });
133
+ })();