@poolzin/pool-bot 2026.3.22 → 2026.3.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 (124) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/acp/bindings-store.js +209 -0
  3. package/dist/acp/control-plane/runtime-cache.js +54 -0
  4. package/dist/acp/control-plane/runtime-options.js +215 -0
  5. package/dist/acp/control-plane/session-actor-queue.js +36 -0
  6. package/dist/acp/runtime/errors.js +47 -0
  7. package/dist/acp/runtime/registry.js +86 -0
  8. package/dist/acp/runtime/types.js +1 -0
  9. package/dist/acp/translator.js +97 -0
  10. package/dist/agents/failover-error.js +145 -47
  11. package/dist/browser/browser-profile-manager.js +319 -0
  12. package/dist/browser/cdp-proxy-bypass.js +129 -0
  13. package/dist/browser/cdp-timeouts.js +41 -0
  14. package/dist/browser/chrome-extension-validator.js +406 -0
  15. package/dist/browser/chrome-mcp-snapshot.js +222 -0
  16. package/dist/browser/chrome-mcp.js +421 -0
  17. package/dist/browser/chrome-mcp.snapshot.js +133 -0
  18. package/dist/browser/errors.js +67 -0
  19. package/dist/browser/form-fields.js +22 -0
  20. package/dist/browser/output-atomic.js +44 -0
  21. package/dist/browser/profile-capabilities.js +47 -0
  22. package/dist/browser/safe-filename.js +25 -0
  23. package/dist/browser/snapshot-roles.js +60 -0
  24. package/dist/build-info.json +3 -3
  25. package/dist/commands/security-owner-only.js +86 -0
  26. package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
  27. package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
  28. package/dist/control-ui/index.html +1 -1
  29. package/dist/cron/cron-filters.js +150 -0
  30. package/dist/gateway/device-pairing-security.js +197 -0
  31. package/dist/gateway/event-deduplication.js +167 -0
  32. package/dist/gateway/run-tracker.js +253 -0
  33. package/dist/gateway/server-methods/nodes.js +14 -0
  34. package/dist/gateway/websocket-preauth-security.js +188 -0
  35. package/dist/infra/errors.js +53 -13
  36. package/dist/infra/exec-approvals-security.js +217 -0
  37. package/dist/infra/security/command-analyzer.js +257 -0
  38. package/dist/plugins/loader.js +16 -8
  39. package/dist/security/external-content.js +51 -1
  40. package/dist/sessions/session-costs.js +228 -0
  41. package/dist/shared/param-key.js +16 -0
  42. package/dist/shared/poll-params.js +58 -0
  43. package/dist/shared/polls.js +55 -0
  44. package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
  45. package/docs/FEATURES.md +523 -0
  46. package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
  47. package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
  48. package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
  49. package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
  50. package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
  51. package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
  52. package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
  53. package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
  54. package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
  55. package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
  56. package/docs/MIKRODASH-ANALYSIS.md +412 -0
  57. package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
  58. package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
  59. package/docs/PHASE-7-SUMMARY.md +144 -0
  60. package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
  61. package/docs/PROJECT-FINAL-STATUS.md +237 -0
  62. package/docs/README.md +116 -0
  63. package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
  64. package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
  65. package/docs/channels/googlechat.md +235 -206
  66. package/docs/channels/irc.md +332 -0
  67. package/docs/channels/nostr.md +255 -168
  68. package/docs/components/command-palette.md +166 -0
  69. package/docs/components/login-gate.md +219 -0
  70. package/docs/getting-started/installation.md +191 -0
  71. package/docs/getting-started/introduction.md +120 -0
  72. package/docs/improvements/USAGE-GUIDE.md +359 -0
  73. package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
  74. package/docs/reference/deadcode-detection.md +72 -0
  75. package/extensions/acpx/node_modules/.bin/acpx +21 -0
  76. package/extensions/agency-agents/node_modules/.bin/vite +4 -4
  77. package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
  78. package/extensions/googlechat/node_modules/.bin/tsc +21 -0
  79. package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
  80. package/extensions/googlechat/node_modules/.bin/vitest +21 -0
  81. package/extensions/googlechat/package.json +11 -28
  82. package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
  83. package/extensions/googlechat/src/googlechat-channel.ts +120 -0
  84. package/extensions/googlechat/src/index.ts +14 -0
  85. package/extensions/irc/node_modules/.bin/tsc +21 -0
  86. package/extensions/irc/node_modules/.bin/tsserver +21 -0
  87. package/extensions/irc/node_modules/.bin/vitest +21 -0
  88. package/extensions/irc/package.json +16 -8
  89. package/extensions/irc/src/index.ts +14 -0
  90. package/extensions/irc/src/irc-channel.test.ts +43 -0
  91. package/extensions/irc/src/irc-channel.ts +191 -0
  92. package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
  93. package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
  94. package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
  95. package/extensions/keyed-async-queue/package.json +20 -0
  96. package/extensions/keyed-async-queue/src/index.ts +14 -0
  97. package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
  98. package/extensions/keyed-async-queue/src/queue.ts +200 -0
  99. package/extensions/memory-core/node_modules/.bin/tsc +21 -0
  100. package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
  101. package/extensions/memory-core/node_modules/.bin/vitest +21 -0
  102. package/extensions/memory-core/package.json +11 -8
  103. package/extensions/memory-core/src/index.ts +14 -0
  104. package/extensions/memory-core/src/memory-manager.test.ts +124 -0
  105. package/extensions/memory-core/src/memory-manager.ts +186 -0
  106. package/extensions/nostr/node_modules/.bin/tsc +2 -2
  107. package/extensions/nostr/node_modules/.bin/tsserver +2 -2
  108. package/extensions/nostr/node_modules/.bin/vitest +21 -0
  109. package/extensions/nostr/package.json +15 -24
  110. package/extensions/nostr/src/index.ts +14 -0
  111. package/extensions/nostr/src/nostr-channel.test.ts +55 -0
  112. package/extensions/nostr/src/nostr-channel.ts +228 -0
  113. package/extensions/page-agent/node_modules/.bin/vitest +2 -2
  114. package/extensions/test-utils/node_modules/.bin/jiti +21 -0
  115. package/extensions/test-utils/node_modules/.bin/playwright +21 -0
  116. package/extensions/test-utils/node_modules/.bin/tsx +21 -0
  117. package/extensions/test-utils/node_modules/.bin/vite +21 -0
  118. package/extensions/test-utils/node_modules/.bin/vitest +21 -0
  119. package/extensions/test-utils/node_modules/.bin/yaml +21 -0
  120. package/extensions/xyops/node_modules/.bin/vitest +2 -2
  121. package/package.json +2 -1
  122. package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
  123. package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
  124. package/extensions/memory-core/node_modules/.bin/poolbot +0 -21
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Chrome Extension Validator
3
+ * Validates Chrome extension manifest, options, and background service worker
4
+ */
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ /**
8
+ * Validate Chrome extension manifest.json
9
+ */
10
+ export function validateManifest(manifest) {
11
+ const errors = [];
12
+ const warnings = [];
13
+ // Check manifest_version
14
+ if (!manifest.manifest_version) {
15
+ errors.push({
16
+ severity: "error",
17
+ field: "manifest_version",
18
+ message: "manifest_version is required",
19
+ suggestion: "Use manifest_version: 3 for Chrome extensions",
20
+ });
21
+ }
22
+ else if (manifest.manifest_version !== 3) {
23
+ warnings.push({
24
+ severity: "warning",
25
+ field: "manifest_version",
26
+ message: `Manifest version ${manifest.manifest_version} detected`,
27
+ suggestion: "Consider upgrading to manifest_version: 3",
28
+ });
29
+ }
30
+ // Check required fields
31
+ if (!manifest.name) {
32
+ errors.push({
33
+ severity: "error",
34
+ field: "name",
35
+ message: "Extension name is required",
36
+ });
37
+ }
38
+ else if (manifest.name.length > 45) {
39
+ warnings.push({
40
+ severity: "warning",
41
+ field: "name",
42
+ message: "Extension name is too long (max 45 characters)",
43
+ });
44
+ }
45
+ if (!manifest.version) {
46
+ errors.push({
47
+ severity: "error",
48
+ field: "version",
49
+ message: "Extension version is required",
50
+ suggestion: "Use semantic versioning (e.g., 1.0.0)",
51
+ });
52
+ }
53
+ else if (!/^\d+\.\d+\.\d+$/.test(manifest.version)) {
54
+ warnings.push({
55
+ severity: "warning",
56
+ field: "version",
57
+ message: "Version should follow semantic versioning",
58
+ suggestion: "Use format: major.minor.patch (e.g., 1.0.0)",
59
+ });
60
+ }
61
+ // Check background service worker (Manifest V3)
62
+ if (manifest.manifest_version === 3) {
63
+ if (!manifest.background?.service_worker) {
64
+ errors.push({
65
+ severity: "error",
66
+ field: "background.service_worker",
67
+ message: "Background service worker is required for Manifest V3",
68
+ suggestion: 'Add background: { service_worker: "background.js" }',
69
+ });
70
+ }
71
+ }
72
+ // Check action (Manifest V3)
73
+ if (manifest.manifest_version === 3 && !manifest.action) {
74
+ warnings.push({
75
+ severity: "warning",
76
+ field: "action",
77
+ message: "No action defined (popup or browser action)",
78
+ suggestion: 'Add action: { default_popup: "popup.html" }',
79
+ });
80
+ }
81
+ // Check icons
82
+ if (!manifest.icons) {
83
+ warnings.push({
84
+ severity: "warning",
85
+ field: "icons",
86
+ message: "No icons defined",
87
+ suggestion: "Add icons in sizes: 16, 32, 48, 128",
88
+ });
89
+ }
90
+ else {
91
+ const requiredSizes = ["16", "32", "48", "128"];
92
+ const availableSizes = Object.keys(manifest.icons);
93
+ const missingSizes = requiredSizes.filter((size) => !availableSizes.includes(size));
94
+ if (missingSizes.length > 0) {
95
+ warnings.push({
96
+ severity: "warning",
97
+ field: "icons",
98
+ message: `Missing icon sizes: ${missingSizes.join(", ")}`,
99
+ });
100
+ }
101
+ }
102
+ // Check permissions
103
+ if (manifest.permissions && manifest.permissions.length > 0) {
104
+ const dangerousPermissions = ["tabs", "history", "bookmarks", "downloads", "management"];
105
+ const usedDangerous = manifest.permissions.filter((p) => dangerousPermissions.includes(p));
106
+ if (usedDangerous.length > 0) {
107
+ warnings.push({
108
+ severity: "warning",
109
+ field: "permissions",
110
+ message: `Using sensitive permissions: ${usedDangerous.join(", ")}`,
111
+ suggestion: "Ensure these permissions are necessary and justify them in the store listing",
112
+ });
113
+ }
114
+ }
115
+ // Check host_permissions (Manifest V3)
116
+ if (manifest.manifest_version === 3 && manifest.host_permissions) {
117
+ if (manifest.host_permissions.includes("<all_urls>")) {
118
+ warnings.push({
119
+ severity: "warning",
120
+ field: "host_permissions",
121
+ message: "Using <all_urls> permission",
122
+ suggestion: "Consider restricting to specific domains",
123
+ });
124
+ }
125
+ }
126
+ return {
127
+ valid: errors.length === 0,
128
+ errors,
129
+ warnings,
130
+ };
131
+ }
132
+ /**
133
+ * Validate extension file structure
134
+ */
135
+ export function validateExtensionFiles(extensionDir) {
136
+ const errors = [];
137
+ const warnings = [];
138
+ // Check if directory exists
139
+ if (!fs.existsSync(extensionDir)) {
140
+ errors.push({
141
+ severity: "error",
142
+ field: "extensionDir",
143
+ message: `Extension directory does not exist: ${extensionDir}`,
144
+ });
145
+ return { valid: false, errors, warnings };
146
+ }
147
+ // Check manifest.json
148
+ const manifestPath = path.join(extensionDir, "manifest.json");
149
+ if (!fs.existsSync(manifestPath)) {
150
+ errors.push({
151
+ severity: "error",
152
+ field: "manifest.json",
153
+ message: "manifest.json not found",
154
+ });
155
+ }
156
+ else {
157
+ try {
158
+ const manifestContent = fs.readFileSync(manifestPath, "utf-8");
159
+ const manifest = JSON.parse(manifestContent);
160
+ const manifestResult = validateManifest(manifest);
161
+ errors.push(...manifestResult.errors);
162
+ warnings.push(...manifestResult.warnings);
163
+ // Check referenced files
164
+ if (manifest.background?.service_worker) {
165
+ const bgPath = path.join(extensionDir, manifest.background.service_worker);
166
+ if (!fs.existsSync(bgPath)) {
167
+ errors.push({
168
+ severity: "error",
169
+ field: "background.service_worker",
170
+ message: `Background service worker not found: ${manifest.background.service_worker}`,
171
+ });
172
+ }
173
+ }
174
+ if (manifest.action?.default_popup) {
175
+ const popupPath = path.join(extensionDir, manifest.action.default_popup);
176
+ if (!fs.existsSync(popupPath)) {
177
+ errors.push({
178
+ severity: "error",
179
+ field: "action.default_popup",
180
+ message: `Popup HTML not found: ${manifest.action.default_popup}`,
181
+ });
182
+ }
183
+ }
184
+ if (manifest.options_page) {
185
+ const optionsPath = path.join(extensionDir, manifest.options_page);
186
+ if (!fs.existsSync(optionsPath)) {
187
+ errors.push({
188
+ severity: "error",
189
+ field: "options_page",
190
+ message: `Options page not found: ${manifest.options_page}`,
191
+ });
192
+ }
193
+ }
194
+ if (manifest.options_ui?.page) {
195
+ const optionsPath = path.join(extensionDir, manifest.options_ui.page);
196
+ if (!fs.existsSync(optionsPath)) {
197
+ errors.push({
198
+ severity: "error",
199
+ field: "options_ui.page",
200
+ message: `Options UI page not found: ${manifest.options_ui.page}`,
201
+ });
202
+ }
203
+ }
204
+ // Check icons
205
+ if (manifest.icons) {
206
+ for (const [size, iconPath] of Object.entries(manifest.icons)) {
207
+ const fullPath = path.join(extensionDir, iconPath);
208
+ if (!fs.existsSync(fullPath)) {
209
+ warnings.push({
210
+ severity: "warning",
211
+ field: `icons[${size}]`,
212
+ message: `Icon not found: ${iconPath}`,
213
+ });
214
+ }
215
+ }
216
+ }
217
+ }
218
+ catch (e) {
219
+ errors.push({
220
+ severity: "error",
221
+ field: "manifest.json",
222
+ message: `Failed to parse manifest.json: ${e.message}`,
223
+ });
224
+ }
225
+ }
226
+ return {
227
+ valid: errors.length === 0,
228
+ errors,
229
+ warnings,
230
+ };
231
+ }
232
+ /**
233
+ * Validate background service worker syntax
234
+ */
235
+ export function validateBackgroundWorker(extensionDir, workerFile) {
236
+ const errors = [];
237
+ const warnings = [];
238
+ const workerPath = path.join(extensionDir, workerFile);
239
+ if (!fs.existsSync(workerPath)) {
240
+ errors.push({
241
+ severity: "error",
242
+ field: "background.service_worker",
243
+ message: `Service worker file not found: ${workerFile}`,
244
+ });
245
+ return { valid: false, errors, warnings };
246
+ }
247
+ try {
248
+ const content = fs.readFileSync(workerPath, "utf-8");
249
+ // Check for common Manifest V3 patterns
250
+ if (!content.includes("chrome.runtime.onInstalled")) {
251
+ warnings.push({
252
+ severity: "warning",
253
+ field: "background.service_worker",
254
+ message: "No onInstalled listener found",
255
+ suggestion: "Add chrome.runtime.onInstalled listener for initialization",
256
+ });
257
+ }
258
+ if (!content.includes("chrome.runtime.onMessage")) {
259
+ warnings.push({
260
+ severity: "warning",
261
+ field: "background.service_worker",
262
+ message: "No onMessage listener found",
263
+ suggestion: "Add chrome.runtime.onMessage listener for communication",
264
+ });
265
+ }
266
+ // Check for Manifest V2 deprecated APIs
267
+ const deprecatedAPIs = ["chrome.webRequest.onBeforeRequest"];
268
+ for (const api of deprecatedAPIs) {
269
+ if (content.includes(api)) {
270
+ warnings.push({
271
+ severity: "warning",
272
+ field: "background.service_worker",
273
+ message: `Using Manifest V2 API: ${api}`,
274
+ suggestion: "Migrate to declarativeNetRequest API",
275
+ });
276
+ }
277
+ }
278
+ // Check for syntax errors (basic check)
279
+ try {
280
+ new Function(content);
281
+ }
282
+ catch (e) {
283
+ // This is expected for service workers with top-level await
284
+ // Just warn about potential syntax issues
285
+ if (e.message.includes("await")) {
286
+ // Top-level await is OK in service workers
287
+ }
288
+ else {
289
+ warnings.push({
290
+ severity: "warning",
291
+ field: "background.service_worker",
292
+ message: `Potential syntax error: ${e.message}`,
293
+ suggestion: "Verify JavaScript syntax",
294
+ });
295
+ }
296
+ }
297
+ }
298
+ catch (e) {
299
+ errors.push({
300
+ severity: "error",
301
+ field: "background.service_worker",
302
+ message: `Failed to read service worker: ${e.message}`,
303
+ });
304
+ }
305
+ return {
306
+ valid: errors.length === 0,
307
+ errors,
308
+ warnings,
309
+ };
310
+ }
311
+ /**
312
+ * Validate options page
313
+ */
314
+ export function validateOptionsPage(extensionDir, optionsFile) {
315
+ const errors = [];
316
+ const warnings = [];
317
+ const optionsPath = path.join(extensionDir, optionsFile);
318
+ if (!fs.existsSync(optionsPath)) {
319
+ errors.push({
320
+ severity: "error",
321
+ field: "options_page",
322
+ message: `Options page not found: ${optionsFile}`,
323
+ });
324
+ return { valid: false, errors, warnings };
325
+ }
326
+ try {
327
+ const content = fs.readFileSync(optionsPath, "utf-8");
328
+ // Check for basic HTML structure
329
+ if (!content.includes("<!DOCTYPE html>") && !content.includes("<html")) {
330
+ warnings.push({
331
+ severity: "warning",
332
+ field: "options_page",
333
+ message: "Options page may not be valid HTML",
334
+ suggestion: "Ensure proper HTML structure",
335
+ });
336
+ }
337
+ // Check for script tag
338
+ if (!content.includes("<script")) {
339
+ warnings.push({
340
+ severity: "warning",
341
+ field: "options_page",
342
+ message: "No script tag found in options page",
343
+ suggestion: "Add JavaScript for options functionality",
344
+ });
345
+ }
346
+ // Check for form elements
347
+ if (!content.includes("<form") && !content.includes("<input") && !content.includes("<button")) {
348
+ warnings.push({
349
+ severity: "warning",
350
+ field: "options_page",
351
+ message: "No form elements found in options page",
352
+ suggestion: "Add form elements for user configuration",
353
+ });
354
+ }
355
+ }
356
+ catch (e) {
357
+ errors.push({
358
+ severity: "error",
359
+ field: "options_page",
360
+ message: `Failed to read options page: ${e.message}`,
361
+ });
362
+ }
363
+ return {
364
+ valid: errors.length === 0,
365
+ errors,
366
+ warnings,
367
+ };
368
+ }
369
+ /**
370
+ * Run all validations
371
+ */
372
+ export function validateExtension(extensionDir) {
373
+ const allErrors = [];
374
+ const allWarnings = [];
375
+ // Validate file structure
376
+ const fileResult = validateExtensionFiles(extensionDir);
377
+ allErrors.push(...fileResult.errors);
378
+ allWarnings.push(...fileResult.warnings);
379
+ // If manifest is valid, validate referenced files
380
+ if (fileResult.valid) {
381
+ const manifestPath = path.join(extensionDir, "manifest.json");
382
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
383
+ // Validate background worker
384
+ if (manifest.background?.service_worker) {
385
+ const bgResult = validateBackgroundWorker(extensionDir, manifest.background.service_worker);
386
+ allErrors.push(...bgResult.errors);
387
+ allWarnings.push(...bgResult.warnings);
388
+ }
389
+ // Validate options page
390
+ if (manifest.options_page) {
391
+ const optionsResult = validateOptionsPage(extensionDir, manifest.options_page);
392
+ allErrors.push(...optionsResult.errors);
393
+ allWarnings.push(...optionsResult.warnings);
394
+ }
395
+ if (manifest.options_ui?.page) {
396
+ const optionsResult = validateOptionsPage(extensionDir, manifest.options_ui.page);
397
+ allErrors.push(...optionsResult.errors);
398
+ allWarnings.push(...optionsResult.warnings);
399
+ }
400
+ }
401
+ return {
402
+ valid: allErrors.length === 0,
403
+ errors: allErrors,
404
+ warnings: allWarnings,
405
+ };
406
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Chrome MCP Snapshot
3
+ * Captures comprehensive page state including DOM, network, and console state
4
+ */
5
+ /**
6
+ * Capture a comprehensive snapshot of the current page state
7
+ */
8
+ export async function captureSnapshot(cdp) {
9
+ const timestamp = Date.now();
10
+ // Enable required CDP domains
11
+ await Promise.all([
12
+ cdp.send("Page.enable"),
13
+ cdp.send("DOM.enable"),
14
+ cdp.send("Network.enable"),
15
+ cdp.send("Console.enable"),
16
+ cdp.send("Runtime.enable"),
17
+ ]);
18
+ // Capture page info
19
+ const { result: pageInfo } = await cdp.send("Runtime.evaluate", {
20
+ expression: `({
21
+ url: window.location.href,
22
+ title: document.title,
23
+ loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart,
24
+ domContentLoaded: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart
25
+ })`,
26
+ returnByValue: true,
27
+ });
28
+ // Capture DOM
29
+ const { root } = await cdp.send("DOM.getDocument", { depth: -1 });
30
+ const domSnapshot = await captureDOM(cdp, root.nodeId);
31
+ // Capture network requests
32
+ const networkRequests = await captureNetwork(cdp);
33
+ // Capture console messages
34
+ const consoleMessages = await captureConsole(cdp);
35
+ // Capture viewport
36
+ const { visualViewport } = await cdp.send("Page.getLayoutMetrics");
37
+ return {
38
+ timestamp,
39
+ url: pageInfo?.value?.url || "unknown",
40
+ title: pageInfo?.value?.title || "unknown",
41
+ dom: domSnapshot,
42
+ network: networkRequests,
43
+ console: consoleMessages,
44
+ viewport: {
45
+ width: visualViewport.clientWidth,
46
+ height: visualViewport.clientHeight,
47
+ deviceScaleFactor: visualViewport.scale,
48
+ },
49
+ metadata: {
50
+ loadTime: pageInfo?.value?.loadTime,
51
+ domContentLoaded: pageInfo?.value?.domContentLoaded,
52
+ },
53
+ };
54
+ }
55
+ /**
56
+ * Capture DOM structure and interactive elements
57
+ */
58
+ async function captureDOM(cdp, rootNodeId) {
59
+ // Get outer HTML
60
+ const { outerHTML } = await cdp.send("DOM.getOuterHTML", { nodeId: rootNodeId });
61
+ // Query interactive elements
62
+ const selectors = [
63
+ "a",
64
+ "button",
65
+ "input",
66
+ "select",
67
+ "textarea",
68
+ '[role="button"]',
69
+ '[role="link"]',
70
+ '[role="menuitem"]',
71
+ "[tabindex]",
72
+ ];
73
+ const interactiveElements = [];
74
+ for (const selector of selectors) {
75
+ try {
76
+ const { nodes } = await cdp.send("DOM.querySelectorAll", {
77
+ nodeId: rootNodeId,
78
+ selector,
79
+ });
80
+ for (const nodeId of nodes || []) {
81
+ const element = await captureElement(cdp, nodeId);
82
+ if (element) {
83
+ interactiveElements.push(element);
84
+ }
85
+ }
86
+ }
87
+ catch {
88
+ // Ignore selector errors
89
+ }
90
+ }
91
+ return {
92
+ html: outerHTML,
93
+ nodeCount: interactiveElements.length,
94
+ interactiveElements,
95
+ };
96
+ }
97
+ /**
98
+ * Capture element details
99
+ */
100
+ async function captureElement(cdp, nodeId) {
101
+ try {
102
+ const [attributes, boxModel] = await Promise.all([
103
+ cdp.send("DOM.getAttributes", { nodeId }),
104
+ cdp.send("DOM.getBoxModel", { nodeId }).catch(() => null),
105
+ ]);
106
+ const attrArray = attributes?.attributes || [];
107
+ const attrMap = {};
108
+ for (let i = 0; i < attrArray.length; i += 2) {
109
+ attrMap[attrArray[i]] = attrArray[i + 1];
110
+ }
111
+ const { nodeName } = await cdp.send("DOM.getNodeName", { nodeId });
112
+ return {
113
+ tag: nodeName.toLowerCase(),
114
+ type: attrMap.type,
115
+ id: attrMap.id,
116
+ class: attrMap.class,
117
+ text: attrMap.textContent?.slice(0, 100),
118
+ href: attrMap.href,
119
+ role: attrMap.role,
120
+ bounds: boxModel
121
+ ? {
122
+ x: boxModel.content[0],
123
+ y: boxModel.content[1],
124
+ width: boxModel.width,
125
+ height: boxModel.height,
126
+ }
127
+ : undefined,
128
+ };
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ }
134
+ /**
135
+ * Capture network requests
136
+ */
137
+ async function captureNetwork(cdp) {
138
+ const requests = [];
139
+ const requestFinished = (params) => {
140
+ const { response, type, timings } = params;
141
+ if (response) {
142
+ requests.push({
143
+ url: response.url,
144
+ method: params.request?.method || "GET",
145
+ status: response.status,
146
+ type: type || "Other",
147
+ size: response.encodedDataLength || 0,
148
+ time: timings?.receiveHeadersEnd || 0,
149
+ });
150
+ }
151
+ };
152
+ cdp.on("Network.requestFinished", requestFinished);
153
+ // Wait a bit for requests to complete
154
+ await new Promise((resolve) => setTimeout(resolve, 500));
155
+ cdp.off("Network.requestFinished", requestFinished);
156
+ const totalBytes = requests.reduce((sum, r) => sum + r.size, 0);
157
+ return {
158
+ requests,
159
+ totalBytes,
160
+ };
161
+ }
162
+ /**
163
+ * Capture console messages
164
+ */
165
+ async function captureConsole(cdp) {
166
+ const messages = [];
167
+ const messageAdded = (params) => {
168
+ const { message } = params;
169
+ messages.push({
170
+ level: message.level || "log",
171
+ text: message.text || "",
172
+ timestamp: message.timestamp || Date.now(),
173
+ source: message.source,
174
+ line: message.line,
175
+ });
176
+ };
177
+ cdp.on("Console.messageAdded", messageAdded);
178
+ // Get existing messages
179
+ try {
180
+ const { messages: existingMessages } = await cdp.send("Console.getRecentMessages");
181
+ for (const msg of existingMessages || []) {
182
+ messages.push({
183
+ level: msg.level || "log",
184
+ text: msg.text || "",
185
+ timestamp: msg.timestamp || Date.now(),
186
+ source: msg.source,
187
+ line: msg.line,
188
+ });
189
+ }
190
+ }
191
+ catch {
192
+ // Ignore if not available
193
+ }
194
+ // Wait a bit for new messages
195
+ await new Promise((resolve) => setTimeout(resolve, 500));
196
+ cdp.off("Console.messageAdded", messageAdded);
197
+ const errors = messages.filter((m) => m.level === "error").length;
198
+ const warnings = messages.filter((m) => m.level === "warning").length;
199
+ return {
200
+ messages,
201
+ errors,
202
+ warnings,
203
+ };
204
+ }
205
+ /**
206
+ * Serialize snapshot to JSON (safe for storage)
207
+ */
208
+ export function serializeSnapshot(snapshot) {
209
+ return JSON.stringify(snapshot, (key, value) => {
210
+ // Truncate large strings
211
+ if (typeof value === "string" && value.length > 10000) {
212
+ return value.slice(0, 10000) + "... [truncated]";
213
+ }
214
+ return value;
215
+ }, 2);
216
+ }
217
+ /**
218
+ * Deserialize snapshot from JSON
219
+ */
220
+ export function deserializeSnapshot(json) {
221
+ return JSON.parse(json);
222
+ }