@poolzin/pool-bot 2026.3.22 → 2026.3.24
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.
- package/CHANGELOG.md +111 -0
- package/dist/.buildstamp +1 -1
- package/dist/acp/bindings-store.js +209 -0
- package/dist/acp/control-plane/runtime-cache.js +54 -0
- package/dist/acp/control-plane/runtime-options.js +215 -0
- package/dist/acp/control-plane/session-actor-queue.js +36 -0
- package/dist/acp/policy.js +52 -0
- package/dist/acp/runtime/errors.js +47 -0
- package/dist/acp/runtime/registry.js +86 -0
- package/dist/acp/runtime/types.js +1 -0
- package/dist/acp/translator.js +97 -0
- package/dist/agents/btw.js +280 -0
- package/dist/agents/failover-error.js +145 -47
- package/dist/agents/fast-mode.js +24 -0
- package/dist/agents/live-model-errors.js +23 -0
- package/dist/agents/model-auth-env-vars.js +44 -0
- package/dist/agents/model-auth-markers.js +69 -0
- package/dist/agents/models-config.providers.discovery.js +180 -0
- package/dist/agents/models-config.providers.static.js +480 -0
- package/dist/auto-reply/reply/typing-policy.js +15 -0
- package/dist/browser/browser-profile-manager.js +319 -0
- package/dist/browser/cdp-proxy-bypass.js +129 -0
- package/dist/browser/cdp-timeouts.js +41 -0
- package/dist/browser/chrome-extension-validator.js +406 -0
- package/dist/browser/chrome-mcp-snapshot.js +222 -0
- package/dist/browser/chrome-mcp.js +421 -0
- package/dist/browser/chrome-mcp.snapshot.js +133 -0
- package/dist/browser/errors.js +67 -0
- package/dist/browser/form-fields.js +22 -0
- package/dist/browser/output-atomic.js +44 -0
- package/dist/browser/profile-capabilities.js +47 -0
- package/dist/browser/safe-filename.js +25 -0
- package/dist/browser/snapshot-roles.js +60 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/account-snapshot-fields.js +176 -0
- package/dist/channels/draft-stream-controls.js +89 -0
- package/dist/channels/inbound-debounce-policy.js +28 -0
- package/dist/channels/typing-lifecycle.js +39 -0
- package/dist/cli/program/command-registry.js +52 -0
- package/dist/commands/agent-binding.js +123 -0
- package/dist/commands/agents.commands.bind.js +280 -0
- package/dist/commands/backup-shared.js +186 -0
- package/dist/commands/backup-verify.js +236 -0
- package/dist/commands/backup.js +166 -0
- package/dist/commands/channel-account-context.js +15 -0
- package/dist/commands/channel-account.js +190 -0
- package/dist/commands/gateway-install-token.js +117 -0
- package/dist/commands/oauth-tls-preflight.js +121 -0
- package/dist/commands/ollama-setup.js +402 -0
- package/dist/commands/security-owner-only.js +86 -0
- package/dist/commands/self-hosted-provider-setup.js +207 -0
- package/dist/commands/session-store-targets.js +12 -0
- package/dist/commands/sessions-cleanup.js +97 -0
- package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/cron/cron-filters.js +150 -0
- package/dist/cron/heartbeat-policy.js +26 -0
- package/dist/gateway/device-pairing-security.js +197 -0
- package/dist/gateway/event-deduplication.js +167 -0
- package/dist/gateway/hooks-mapping.js +46 -7
- package/dist/gateway/run-tracker.js +253 -0
- package/dist/gateway/server-methods/nodes.js +14 -0
- package/dist/gateway/websocket-preauth-security.js +188 -0
- package/dist/hooks/module-loader.js +28 -0
- package/dist/infra/agent-command-binding.js +144 -0
- package/dist/infra/backup.js +328 -0
- package/dist/infra/channel-account-context.js +173 -0
- package/dist/infra/errors.js +53 -13
- package/dist/infra/exec-approvals-security.js +217 -0
- package/dist/infra/security/command-analyzer.js +257 -0
- package/dist/infra/session-cleanup.js +143 -0
- package/dist/plugins/loader.js +16 -8
- package/dist/security/external-content.js +51 -1
- package/dist/sessions/session-costs.js +228 -0
- package/dist/shared/param-key.js +16 -0
- package/dist/shared/poll-params.js +58 -0
- package/dist/shared/polls.js +55 -0
- package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
- package/docs/FEATURES.md +523 -0
- package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
- package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
- package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
- package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
- package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
- package/docs/MIKRODASH-ANALYSIS.md +412 -0
- package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
- package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
- package/docs/PHASE-7-SUMMARY.md +144 -0
- package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
- package/docs/PROJECT-FINAL-STATUS.md +237 -0
- package/docs/README.md +116 -0
- package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
- package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
- package/docs/channels/googlechat.md +235 -206
- package/docs/channels/irc.md +332 -0
- package/docs/channels/nostr.md +255 -168
- package/docs/components/command-palette.md +166 -0
- package/docs/components/login-gate.md +219 -0
- package/docs/getting-started/installation.md +191 -0
- package/docs/getting-started/introduction.md +120 -0
- package/docs/improvements/USAGE-GUIDE.md +359 -0
- package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
- package/docs/reference/deadcode-detection.md +72 -0
- package/extensions/acpx/node_modules/.bin/acpx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +4 -4
- package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
- package/extensions/googlechat/node_modules/.bin/tsc +21 -0
- package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
- package/extensions/googlechat/node_modules/.bin/vitest +21 -0
- package/extensions/googlechat/package.json +11 -28
- package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
- package/extensions/googlechat/src/googlechat-channel.ts +120 -0
- package/extensions/googlechat/src/index.ts +14 -0
- package/extensions/irc/node_modules/.bin/tsc +21 -0
- package/extensions/irc/node_modules/.bin/tsserver +21 -0
- package/extensions/irc/node_modules/.bin/vitest +21 -0
- package/extensions/irc/package.json +16 -8
- package/extensions/irc/src/index.ts +14 -0
- package/extensions/irc/src/irc-channel.test.ts +43 -0
- package/extensions/irc/src/irc-channel.ts +191 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
- package/extensions/keyed-async-queue/package.json +20 -0
- package/extensions/keyed-async-queue/src/index.ts +14 -0
- package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
- package/extensions/keyed-async-queue/src/queue.ts +200 -0
- package/extensions/memory-core/node_modules/.bin/tsc +21 -0
- package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
- package/extensions/memory-core/node_modules/.bin/vitest +21 -0
- package/extensions/memory-core/package.json +11 -8
- package/extensions/memory-core/src/index.ts +14 -0
- package/extensions/memory-core/src/memory-manager.test.ts +124 -0
- package/extensions/memory-core/src/memory-manager.ts +186 -0
- package/extensions/nostr/node_modules/.bin/tsc +2 -2
- package/extensions/nostr/node_modules/.bin/tsserver +2 -2
- package/extensions/nostr/node_modules/.bin/vitest +21 -0
- package/extensions/nostr/package.json +15 -24
- package/extensions/nostr/src/index.ts +14 -0
- package/extensions/nostr/src/nostr-channel.test.ts +55 -0
- package/extensions/nostr/src/nostr-channel.ts +228 -0
- package/extensions/page-agent/node_modules/.bin/vitest +2 -2
- package/extensions/test-utils/node_modules/.bin/jiti +21 -0
- package/extensions/test-utils/node_modules/.bin/playwright +21 -0
- package/extensions/test-utils/node_modules/.bin/tsx +21 -0
- package/extensions/test-utils/node_modules/.bin/vite +21 -0
- package/extensions/test-utils/node_modules/.bin/vitest +21 -0
- package/extensions/test-utils/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +2 -2
- package/package.json +2 -1
- package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
- package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
- 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
|
+
}
|