@weavetab/mcp 2.5.0-beta.1 → 2.5.0-beta.4
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/AGENTS.md +2 -1
- package/ARCHITECTURE.md +6 -6
- package/LICENSE +1 -1
- package/README.md +164 -802
- package/SECURITY.md +33 -22
- package/TROUBLESHOOTING.md +3 -3
- package/dist/cdp/bridge.d.ts +10 -0
- package/dist/cdp/bridge.js +285 -78
- package/dist/cdp/connector.d.ts +7 -3
- package/dist/cdp/connector.js +196 -75
- package/dist/cdp/helpers.d.ts +4 -0
- package/dist/cdp/helpers.js +181 -0
- package/dist/cdp/lock.d.ts +9 -6
- package/dist/cdp/lock.js +72 -59
- package/dist/cdp/omni_broker.d.ts +2 -1
- package/dist/cdp/omni_broker.js +13 -7
- package/dist/cdp/profiles.d.ts +10 -0
- package/dist/cdp/profiles.js +65 -0
- package/dist/cdp/walker.d.ts +25 -1
- package/dist/cdp/walker.js +354 -7
- package/dist/cli/plugin.d.ts +0 -2
- package/dist/cli/plugin.js +51 -696
- package/dist/cli/updater.d.ts +1 -1
- package/dist/cli/updater.js +27 -7
- package/dist/config/loader.d.ts +33 -6
- package/dist/config/loader.js +174 -22
- package/dist/desktop/discovery.d.ts +44 -0
- package/dist/desktop/discovery.js +364 -0
- package/dist/index.js +140 -64
- package/dist/intelligence/marks.d.ts +36 -0
- package/dist/intelligence/marks.js +30 -0
- package/dist/intelligence/patternCompiler.d.ts +118 -0
- package/dist/intelligence/patternCompiler.js +259 -0
- package/dist/intelligence/sessionMemory.d.ts +2 -0
- package/dist/intelligence/sessionMemory.js +18 -8
- package/dist/overlay/ghost.js +2 -2
- package/dist/overlay/injector.js +3 -5
- package/dist/plugin/extension_bridge.d.ts +40 -0
- package/dist/plugin/extension_bridge.js +158 -0
- package/dist/plugin/interface.d.ts +151 -15
- package/dist/plugin/loader.d.ts +19 -1
- package/dist/plugin/loader.js +331 -63
- package/dist/plugin/pipeline.d.ts +75 -0
- package/dist/plugin/pipeline.js +183 -0
- package/dist/plugin/registry.js +12 -1
- package/dist/plugin/storage.d.ts +51 -0
- package/dist/plugin/storage.js +164 -0
- package/dist/security/secrets.d.ts +1 -0
- package/dist/security/secrets.js +13 -0
- package/dist/sensors/actionability.js +1 -2
- package/dist/sensors/guard.js +4 -10
- package/dist/sensors/mutations.js +12 -0
- package/dist/sensors/operation_monitor.js +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +600 -236
- package/dist/state/agent.d.ts +1 -0
- package/dist/state/agent.js +3 -0
- package/dist/state/delta.d.ts +12 -4
- package/dist/state/delta.js +67 -14
- package/dist/state/session.d.ts +8 -2
- package/dist/state/session.js +60 -13
- package/dist/tools/browser_detect.d.ts +16 -2
- package/dist/tools/browser_detect.js +54 -6
- package/dist/tools/burst.d.ts +17 -7
- package/dist/tools/burst.js +240 -83
- package/dist/tools/checkpoint.d.ts +38 -0
- package/dist/tools/checkpoint.js +271 -0
- package/dist/tools/click.d.ts +1 -0
- package/dist/tools/click.js +97 -98
- package/dist/tools/clipboard.js +31 -24
- package/dist/tools/design_extract.js +1 -1
- package/dist/tools/eval.js +1 -1
- package/dist/tools/fill.d.ts +20 -1
- package/dist/tools/fill.js +199 -40
- package/dist/tools/find.d.ts +16 -0
- package/dist/tools/find.js +67 -44
- package/dist/tools/github/index.d.ts +19 -4
- package/dist/tools/github/index.js +40 -4
- package/dist/tools/highlight.d.ts +6 -2
- package/dist/tools/highlight.js +158 -72
- package/dist/tools/inspect.js +1 -1
- package/dist/tools/key.js +65 -56
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +57 -44
- package/dist/tools/pdf.js +21 -13
- package/dist/tools/pointer.d.ts +18 -3
- package/dist/tools/pointer.js +89 -1
- package/dist/tools/read.d.ts +28 -8
- package/dist/tools/read.js +230 -56
- package/dist/tools/scrape.js +67 -13
- package/dist/tools/screenshot.js +21 -16
- package/dist/tools/scroll.js +6 -11
- package/dist/tools/select.js +8 -2
- package/dist/tools/snapshot.js +3 -34
- package/dist/tools/tabs.d.ts +10 -10
- package/dist/tools/tabs.js +55 -39
- package/dist/tools/thought.d.ts +2 -0
- package/dist/tools/thought.js +13 -14
- package/dist/tools/type.d.ts +2 -0
- package/dist/tools/type.js +109 -60
- package/dist/tools/type_secret.js +42 -76
- package/dist/tools/upload.js +1 -1
- package/dist/tools/video_compiler.d.ts +1 -1
- package/dist/tools/video_compiler.js +11 -12
- package/dist/version.d.ts +5 -0
- package/dist/version.js +5 -0
- package/docs/DOCUMENTATION.md +82 -36
- package/docs/PLUGINS.md +82 -499
- package/docs/TOOLS.md +51 -99
- package/docs/architecture/blind-injection.md +83 -0
- package/docs/architecture/dual-injection.md +32 -0
- package/docs/architecture/overview.md +49 -0
- package/docs/architecture/security.md +35 -0
- package/docs/architecture/transport.md +37 -0
- package/docs/captcha-stealth/captcha-solver.md +75 -0
- package/docs/captcha-stealth/stealth-physics.md +60 -0
- package/docs/cli/cli-commands.md +44 -0
- package/docs/cli/cli-flags.md +20 -0
- package/docs/cli/cli-introduction.md +20 -0
- package/docs/cli/cli-scripting.md +32 -0
- package/docs/cli/g-ide/ide-antigravity.md +24 -0
- package/docs/cli/g-ide/ide-claude-code.md +24 -0
- package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
- package/docs/cli/g-ide/ide-cursor.md +31 -0
- package/docs/cli/g-ide/ide-generic.md +34 -0
- package/docs/cli/g-ide/ide-opencode.md +24 -0
- package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
- package/docs/cli/g-ide/ide-windsurf.md +24 -0
- package/docs/configuration/config-locations.md +24 -0
- package/docs/configuration/configuration.md +73 -0
- package/docs/configuration/env-variables.md +19 -0
- package/docs/configuration/folders.md +55 -0
- package/docs/desktop-automation/electron-apps.md +217 -0
- package/docs/get-started/installation.md +47 -0
- package/docs/get-started/introduction.md +60 -0
- package/docs/get-started/quickstart.md +54 -0
- package/docs/get-started/setup-wizard.md +26 -0
- package/docs/intelligence/site-profiles.md +151 -0
- package/docs/skills-ecosystem/skills-overview.md +35 -0
- package/docs/skills-ecosystem/skills-recipes.md +36 -0
- package/docs/tools/tools.md +631 -0
- package/docs/troubleshooting/trouble-browser.md +27 -0
- package/docs/troubleshooting/trouble-config.md +20 -0
- package/docs/troubleshooting/trouble-loop.md +31 -0
- package/ext-dist/background.js +10 -62
- package/ext-dist/content.js +333 -921
- package/ext-dist/hud/hud.js +9 -9
- package/ext-dist/manifest.json +2 -6
- package/ext-dist/styles.css +6 -1
- package/mcp.json +108 -72
- package/package.json +14 -9
- package/server.json +3 -3
- package/docs/DEV_MODE.md +0 -68
- package/docs/dev_plugin.md +0 -310
- package/ext-dist/sidepanel/sidepanel.js +0 -25
- package/ext-dist/sidepanel.html +0 -547
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/plugin/pipeline.ts — Central Hook Dispatch Pipeline for Weavetab Plugins
|
|
3
|
+
*
|
|
4
|
+
* Dispatches lifecycle events:
|
|
5
|
+
* - onBeforeAction / beforeToolCall: Intercept tool calls, modify arguments, or block unauthorized actions
|
|
6
|
+
* - onAfterAction / afterToolCall: Transform, redact, or augment tool return results
|
|
7
|
+
* - onDOMMutation: Stream real-time DOM mutation deltas to registered plugins
|
|
8
|
+
* - onPageNavigate: Notify plugins upon browser navigation
|
|
9
|
+
*/
|
|
10
|
+
class HookPipeline {
|
|
11
|
+
beforeToolCallHooks = [];
|
|
12
|
+
afterToolCallHooks = [];
|
|
13
|
+
onDOMMutationHooks = [];
|
|
14
|
+
onPageNavigateHooks = [];
|
|
15
|
+
/**
|
|
16
|
+
* Register hooks for a specific plugin.
|
|
17
|
+
*/
|
|
18
|
+
registerPluginHooks(pluginName, hooks) {
|
|
19
|
+
if (!hooks)
|
|
20
|
+
return;
|
|
21
|
+
// 1. Before Action / Before Tool Call
|
|
22
|
+
const beforeFns = hooks.onBeforeAction || hooks.beforeToolCall;
|
|
23
|
+
if (beforeFns) {
|
|
24
|
+
const list = Array.isArray(beforeFns) ? beforeFns : [beforeFns];
|
|
25
|
+
for (const fn of list) {
|
|
26
|
+
if (typeof fn === "function") {
|
|
27
|
+
this.beforeToolCallHooks.push({ pluginName, fn });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// 2. After Action (signature: (tool, result, session, args))
|
|
32
|
+
if (hooks.onAfterAction) {
|
|
33
|
+
const list = Array.isArray(hooks.onAfterAction) ? hooks.onAfterAction : [hooks.onAfterAction];
|
|
34
|
+
for (const fn of list) {
|
|
35
|
+
if (typeof fn === "function") {
|
|
36
|
+
this.afterToolCallHooks.push({
|
|
37
|
+
pluginName,
|
|
38
|
+
fn: async (toolName, args, result, session) => {
|
|
39
|
+
return await fn(toolName, result, session, args);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Legacy After Tool Call (signature: (tool, args, result, session))
|
|
46
|
+
if (hooks.afterToolCall) {
|
|
47
|
+
const list = Array.isArray(hooks.afterToolCall) ? hooks.afterToolCall : [hooks.afterToolCall];
|
|
48
|
+
for (const fn of list) {
|
|
49
|
+
if (typeof fn === "function") {
|
|
50
|
+
this.afterToolCallHooks.push({ pluginName, fn });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 3. DOM Mutation Hooks
|
|
55
|
+
if (hooks.onDOMMutation) {
|
|
56
|
+
const list = Array.isArray(hooks.onDOMMutation) ? hooks.onDOMMutation : [hooks.onDOMMutation];
|
|
57
|
+
for (const fn of list) {
|
|
58
|
+
if (typeof fn === "function") {
|
|
59
|
+
this.onDOMMutationHooks.push({ pluginName, fn });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// 4. Page Navigate Hooks
|
|
64
|
+
if (hooks.onPageNavigate) {
|
|
65
|
+
const list = Array.isArray(hooks.onPageNavigate) ? hooks.onPageNavigate : [hooks.onPageNavigate];
|
|
66
|
+
for (const fn of list) {
|
|
67
|
+
if (typeof fn === "function") {
|
|
68
|
+
this.onPageNavigateHooks.push({ pluginName, fn });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Remove all hooks registered by a plugin on unload.
|
|
75
|
+
*/
|
|
76
|
+
unregisterPluginHooks(pluginName) {
|
|
77
|
+
this.beforeToolCallHooks = this.beforeToolCallHooks.filter((h) => h.pluginName !== pluginName);
|
|
78
|
+
this.afterToolCallHooks = this.afterToolCallHooks.filter((h) => h.pluginName !== pluginName);
|
|
79
|
+
this.onDOMMutationHooks = this.onDOMMutationHooks.filter((h) => h.pluginName !== pluginName);
|
|
80
|
+
this.onPageNavigateHooks = this.onPageNavigateHooks.filter((h) => h.pluginName !== pluginName);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Dispatches beforeAction / beforeToolCall hooks in sequence.
|
|
84
|
+
* If a hook returns an object, arguments are augmented/modified.
|
|
85
|
+
* If a hook returns { __blocked: true, reason } or throws, execution is aborted.
|
|
86
|
+
*/
|
|
87
|
+
async dispatchBeforeToolCall(toolName, args, session) {
|
|
88
|
+
let currentArgs = { ...args };
|
|
89
|
+
for (const { pluginName, fn } of this.beforeToolCallHooks) {
|
|
90
|
+
try {
|
|
91
|
+
const modified = await fn(toolName, currentArgs, session);
|
|
92
|
+
if (modified && typeof modified === "object") {
|
|
93
|
+
if (modified.__blocked) {
|
|
94
|
+
const reason = modified.reason || `Action '${toolName}' blocked by plugin '${pluginName}'`;
|
|
95
|
+
const blockError = new Error(reason);
|
|
96
|
+
blockError.isBlocked = true;
|
|
97
|
+
blockError.pluginName = pluginName;
|
|
98
|
+
throw blockError;
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(modified)) {
|
|
101
|
+
currentArgs = { ...currentArgs, ...modified };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err?.isBlocked) {
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
process.stderr.write(`[Weavetab Hooks] Error in beforeToolCall (${pluginName} on ${toolName}): ${err?.message || err}\n`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return currentArgs;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Alias for dispatchBeforeToolCall.
|
|
116
|
+
*/
|
|
117
|
+
async dispatchBeforeAction(toolName, args, session) {
|
|
118
|
+
return this.dispatchBeforeToolCall(toolName, args, session);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Dispatches afterAction / afterToolCall hooks in sequence.
|
|
122
|
+
* Allows plugins to inspect, transform, or redact tool results.
|
|
123
|
+
*/
|
|
124
|
+
async dispatchAfterToolCall(toolName, args, result, session) {
|
|
125
|
+
let currentResult = result;
|
|
126
|
+
for (const { pluginName, fn } of this.afterToolCallHooks) {
|
|
127
|
+
try {
|
|
128
|
+
const modified = await fn(toolName, args, currentResult, session);
|
|
129
|
+
if (modified !== undefined) {
|
|
130
|
+
currentResult = modified;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
process.stderr.write(`[Weavetab Hooks] Error in afterToolCall (${pluginName} on ${toolName}): ${err?.message || err}\n`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return currentResult;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Alias for dispatchAfterToolCall.
|
|
141
|
+
*/
|
|
142
|
+
async dispatchAfterAction(toolName, result, session, args) {
|
|
143
|
+
return this.dispatchAfterToolCall(toolName, args || {}, result, session);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Dispatches DOM mutation deltas to all listening plugins.
|
|
147
|
+
*/
|
|
148
|
+
async dispatchDOMMutation(delta, session) {
|
|
149
|
+
if (this.onDOMMutationHooks.length === 0)
|
|
150
|
+
return;
|
|
151
|
+
for (const { pluginName, fn } of this.onDOMMutationHooks) {
|
|
152
|
+
try {
|
|
153
|
+
await fn(delta, session);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
process.stderr.write(`[Weavetab Hooks] Error in onDOMMutation (${pluginName}): ${err?.message || err}\n`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Dispatches onPageNavigate hooks when navigation occurs.
|
|
162
|
+
*/
|
|
163
|
+
async dispatchPageNavigate(url, session) {
|
|
164
|
+
for (const { pluginName, fn } of this.onPageNavigateHooks) {
|
|
165
|
+
try {
|
|
166
|
+
await fn(url, session);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
process.stderr.write(`[Weavetab Hooks] Error in onPageNavigate (${pluginName}): ${err?.message || err}\n`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Clears all registered hooks.
|
|
175
|
+
*/
|
|
176
|
+
clearAllHooks() {
|
|
177
|
+
this.beforeToolCallHooks = [];
|
|
178
|
+
this.afterToolCallHooks = [];
|
|
179
|
+
this.onDOMMutationHooks = [];
|
|
180
|
+
this.onPageNavigateHooks = [];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export const hookPipeline = new HookPipeline();
|
package/dist/plugin/registry.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { WEAVETAB_VERSION } from "../version.js";
|
|
6
|
+
const _require = createRequire(import.meta.url);
|
|
7
|
+
function getVersion() {
|
|
8
|
+
try {
|
|
9
|
+
return _require("../../package.json").version || WEAVETAB_VERSION;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return WEAVETAB_VERSION;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
4
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
16
|
const __dirname = dirname(__filename);
|
|
6
17
|
// ---------------------------------------------------------------------------
|
|
@@ -142,7 +153,7 @@ export async function checkNpmPackageExists(pkgName) {
|
|
|
142
153
|
const res = await fetch(`https://registry.npmjs.org/${encodedName}`, {
|
|
143
154
|
method: "HEAD",
|
|
144
155
|
signal: AbortSignal.timeout(3000),
|
|
145
|
-
headers: { "User-Agent":
|
|
156
|
+
headers: { "User-Agent": `Weavetab/${getVersion()}` },
|
|
146
157
|
});
|
|
147
158
|
return res.status === 200;
|
|
148
159
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for isolated plugin file and key-value storage.
|
|
3
|
+
*/
|
|
4
|
+
export interface IPluginStorage {
|
|
5
|
+
get<T = any>(key: string, defaultValue?: T): Promise<T | null>;
|
|
6
|
+
set<T = any>(key: string, value: T): Promise<void>;
|
|
7
|
+
delete(key: string): Promise<boolean>;
|
|
8
|
+
getJSON<T = any>(filename: string): Promise<T | null>;
|
|
9
|
+
setJSON<T = any>(filename: string, data: T): Promise<void>;
|
|
10
|
+
getPath(subpath?: string): string;
|
|
11
|
+
list(subpath?: string): Promise<string[]>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates an isolated storage sandbox for a plugin under:
|
|
15
|
+
* ~/.weavetab/plugins/<plugin-name>/
|
|
16
|
+
*/
|
|
17
|
+
export declare class PluginStorageManager implements IPluginStorage {
|
|
18
|
+
readonly pluginName: string;
|
|
19
|
+
private readonly pluginDir;
|
|
20
|
+
private readonly configPath;
|
|
21
|
+
private readonly kvStorePath;
|
|
22
|
+
private inMemoryKV;
|
|
23
|
+
constructor(pluginName: string);
|
|
24
|
+
/**
|
|
25
|
+
* Returns the absolute path to the plugin's isolated directory.
|
|
26
|
+
*/
|
|
27
|
+
getDirectory(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the absolute path to the plugin's config.json.
|
|
30
|
+
*/
|
|
31
|
+
getConfigPath(): string;
|
|
32
|
+
private ensureDir;
|
|
33
|
+
/**
|
|
34
|
+
* Loads the plugin's isolated config.json. If it does not exist and a defaultConfig is provided,
|
|
35
|
+
* writes the default config to ~/.weavetab/plugins/<plugin-name>/config.json.
|
|
36
|
+
*/
|
|
37
|
+
loadConfig(defaultConfig?: Record<string, any>): Record<string, any>;
|
|
38
|
+
/**
|
|
39
|
+
* Saves updated configuration to ~/.weavetab/plugins/<plugin-name>/config.json.
|
|
40
|
+
*/
|
|
41
|
+
saveConfig(configData: Record<string, any>): void;
|
|
42
|
+
private loadKV;
|
|
43
|
+
private persistKV;
|
|
44
|
+
get<T = any>(key: string, defaultValue?: T): Promise<T | null>;
|
|
45
|
+
set<T = any>(key: string, value: T): Promise<void>;
|
|
46
|
+
delete(key: string): Promise<boolean>;
|
|
47
|
+
getPath(subpath?: string): string;
|
|
48
|
+
getJSON<T = any>(filename: string): Promise<T | null>;
|
|
49
|
+
setJSON<T = any>(filename: string, data: T): Promise<void>;
|
|
50
|
+
list(subpath?: string): Promise<string[]>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { GLOBAL_CONFIG_DIR } from "../config/loader.js";
|
|
4
|
+
import { logDebug } from "../audit/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* Creates an isolated storage sandbox for a plugin under:
|
|
7
|
+
* ~/.weavetab/plugins/<plugin-name>/
|
|
8
|
+
*/
|
|
9
|
+
export class PluginStorageManager {
|
|
10
|
+
pluginName;
|
|
11
|
+
pluginDir;
|
|
12
|
+
configPath;
|
|
13
|
+
kvStorePath;
|
|
14
|
+
inMemoryKV = null;
|
|
15
|
+
constructor(pluginName) {
|
|
16
|
+
this.pluginName = pluginName;
|
|
17
|
+
// Sanitize plugin name for safe filesystem directory creation
|
|
18
|
+
const safeName = pluginName.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
19
|
+
this.pluginDir = path.join(GLOBAL_CONFIG_DIR, "plugins", safeName);
|
|
20
|
+
this.configPath = path.join(this.pluginDir, "config.json");
|
|
21
|
+
this.kvStorePath = path.join(this.pluginDir, "storage.json");
|
|
22
|
+
this.ensureDir(this.pluginDir);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the absolute path to the plugin's isolated directory.
|
|
26
|
+
*/
|
|
27
|
+
getDirectory() {
|
|
28
|
+
return this.pluginDir;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns the absolute path to the plugin's config.json.
|
|
32
|
+
*/
|
|
33
|
+
getConfigPath() {
|
|
34
|
+
return this.configPath;
|
|
35
|
+
}
|
|
36
|
+
ensureDir(dir) {
|
|
37
|
+
if (!fs.existsSync(dir)) {
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
logDebug(`[Weavetab PluginStorage] Failed to create dir ${dir}:`, e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Loads the plugin's isolated config.json. If it does not exist and a defaultConfig is provided,
|
|
48
|
+
* writes the default config to ~/.weavetab/plugins/<plugin-name>/config.json.
|
|
49
|
+
*/
|
|
50
|
+
loadConfig(defaultConfig = {}) {
|
|
51
|
+
try {
|
|
52
|
+
if (fs.existsSync(this.configPath)) {
|
|
53
|
+
const raw = fs.readFileSync(this.configPath, "utf-8").trim();
|
|
54
|
+
if (raw) {
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// If config.json doesn't exist yet, save default config
|
|
59
|
+
this.ensureDir(this.pluginDir);
|
|
60
|
+
fs.writeFileSync(this.configPath, JSON.stringify(defaultConfig, null, 2), "utf-8");
|
|
61
|
+
logDebug(`[Weavetab PluginStorage] Created default config for "${this.pluginName}" at: ${this.configPath}`);
|
|
62
|
+
return { ...defaultConfig };
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
logDebug(`[Weavetab PluginStorage] Error loading config for "${this.pluginName}":`, e);
|
|
66
|
+
return { ...defaultConfig };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Saves updated configuration to ~/.weavetab/plugins/<plugin-name>/config.json.
|
|
71
|
+
*/
|
|
72
|
+
saveConfig(configData) {
|
|
73
|
+
try {
|
|
74
|
+
this.ensureDir(this.pluginDir);
|
|
75
|
+
fs.writeFileSync(this.configPath, JSON.stringify(configData, null, 2), "utf-8");
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
logDebug(`[Weavetab PluginStorage] Error saving config for "${this.pluginName}":`, e);
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
loadKV() {
|
|
83
|
+
if (this.inMemoryKV !== null)
|
|
84
|
+
return this.inMemoryKV;
|
|
85
|
+
try {
|
|
86
|
+
if (fs.existsSync(this.kvStorePath)) {
|
|
87
|
+
const raw = fs.readFileSync(this.kvStorePath, "utf-8").trim();
|
|
88
|
+
this.inMemoryKV = raw ? JSON.parse(raw) : {};
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.inMemoryKV = {};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
this.inMemoryKV = {};
|
|
96
|
+
}
|
|
97
|
+
return this.inMemoryKV || {};
|
|
98
|
+
}
|
|
99
|
+
persistKV() {
|
|
100
|
+
try {
|
|
101
|
+
this.ensureDir(this.pluginDir);
|
|
102
|
+
fs.writeFileSync(this.kvStorePath, JSON.stringify(this.inMemoryKV || {}, null, 2), "utf-8");
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
logDebug(`[Weavetab PluginStorage] Failed to persist KV for "${this.pluginName}":`, e);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async get(key, defaultValue) {
|
|
109
|
+
const kv = this.loadKV();
|
|
110
|
+
return key in kv ? kv[key] : defaultValue !== undefined ? defaultValue : null;
|
|
111
|
+
}
|
|
112
|
+
async set(key, value) {
|
|
113
|
+
const kv = this.loadKV();
|
|
114
|
+
kv[key] = value;
|
|
115
|
+
this.persistKV();
|
|
116
|
+
}
|
|
117
|
+
async delete(key) {
|
|
118
|
+
const kv = this.loadKV();
|
|
119
|
+
if (key in kv) {
|
|
120
|
+
delete kv[key];
|
|
121
|
+
this.persistKV();
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
getPath(subpath = "") {
|
|
127
|
+
const resolved = path.resolve(this.pluginDir, subpath);
|
|
128
|
+
// Security check: ensure subpath does not escape the plugin directory
|
|
129
|
+
if (!resolved.startsWith(this.pluginDir)) {
|
|
130
|
+
throw new Error(`Security Violation: Path "${subpath}" attempts to escape plugin directory.`);
|
|
131
|
+
}
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
134
|
+
async getJSON(filename) {
|
|
135
|
+
const filePath = this.getPath(filename);
|
|
136
|
+
if (!fs.existsSync(filePath))
|
|
137
|
+
return null;
|
|
138
|
+
try {
|
|
139
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
140
|
+
return JSON.parse(raw);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
logDebug(`[Weavetab PluginStorage] Error reading JSON from "${filePath}":`, e);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async setJSON(filename, data) {
|
|
148
|
+
const filePath = this.getPath(filename);
|
|
149
|
+
const parent = path.dirname(filePath);
|
|
150
|
+
this.ensureDir(parent);
|
|
151
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
152
|
+
}
|
|
153
|
+
async list(subpath = "") {
|
|
154
|
+
const targetDir = this.getPath(subpath);
|
|
155
|
+
if (!fs.existsSync(targetDir))
|
|
156
|
+
return [];
|
|
157
|
+
try {
|
|
158
|
+
return fs.readdirSync(targetDir);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -69,6 +69,7 @@ declare class SessionSecretTrackerImpl {
|
|
|
69
69
|
isNodeSecret(sessionId: string, backendNodeId: number): boolean;
|
|
70
70
|
registerSecretValue(sessionId: string, value: string): void;
|
|
71
71
|
redact(sessionId: string, text: string): string;
|
|
72
|
+
redactAll(text: string): string;
|
|
72
73
|
clearSession(sessionId: string): void;
|
|
73
74
|
}
|
|
74
75
|
export declare const SessionSecretTracker: SessionSecretTrackerImpl;
|
package/dist/security/secrets.js
CHANGED
|
@@ -357,6 +357,19 @@ class SessionSecretTrackerImpl {
|
|
|
357
357
|
}
|
|
358
358
|
return redacted;
|
|
359
359
|
}
|
|
360
|
+
redactAll(text) {
|
|
361
|
+
if (!text || typeof text !== 'string')
|
|
362
|
+
return text;
|
|
363
|
+
let redacted = text;
|
|
364
|
+
for (const set of this.sessionSecrets.values()) {
|
|
365
|
+
for (const secret of set) {
|
|
366
|
+
if (secret && secret.length >= 3) {
|
|
367
|
+
redacted = redacted.split(secret).join('[REDACTED]');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return redacted;
|
|
372
|
+
}
|
|
360
373
|
clearSession(sessionId) {
|
|
361
374
|
this.trackedNodes.delete(sessionId);
|
|
362
375
|
this.sessionSecrets.delete(sessionId);
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
25
25
|
/** Playwright's per-action retry backoff (ms). */
|
|
26
|
-
const BACKOFF = [0, 20, 100
|
|
26
|
+
const BACKOFF = [0, 20, 100];
|
|
27
27
|
/**
|
|
28
28
|
* 4-way scroll alignment rotation — escape sticky headers.
|
|
29
29
|
* undefined = browser default (nearest), then progressively more aggressive.
|
|
@@ -121,7 +121,6 @@ function buildPageExpression(selector, scrollOptions, fingerprint, stabilityMs)
|
|
|
121
121
|
let stable = false;
|
|
122
122
|
while (Date.now() < stabilityDeadline) {
|
|
123
123
|
await new Promise(r => requestAnimationFrame(r));
|
|
124
|
-
await new Promise(r => requestAnimationFrame(r)); // two ticks like Playwright
|
|
125
124
|
const cur = el.getBoundingClientRect();
|
|
126
125
|
if (
|
|
127
126
|
Math.abs(cur.x - prevRect.x) < 0.5 &&
|
package/dist/sensors/guard.js
CHANGED
|
@@ -148,9 +148,7 @@ export async function detectPageWeight(session) {
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
export async function awaitNavigation(session, timeout = 6000 // Reduced from 10000ms
|
|
153
|
-
) {
|
|
151
|
+
export async function awaitNavigation(session, timeout = 6000) {
|
|
154
152
|
// Poll readyState instead of consuming one-shot event
|
|
155
153
|
await pollUntil(async () => {
|
|
156
154
|
const { result } = await session.Runtime.evaluate({
|
|
@@ -158,11 +156,7 @@ export async function awaitNavigation(session, timeout = 6000 // Reduced from 10
|
|
|
158
156
|
returnByValue: true,
|
|
159
157
|
}).catch(() => ({ result: { value: 'loading' } }));
|
|
160
158
|
return result?.value === 'complete' || result?.value === 'interactive';
|
|
161
|
-
}, { timeout, interval:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// After DOM content fires, detect page weight for post-load stability wait
|
|
165
|
-
const weight = await detectPageWeight(session);
|
|
166
|
-
const stabilityTimeout = weight.level === "heavy" ? 3000 : weight.level === "medium" ? 2000 : 1000; // Reduced from 5000/3000/2000
|
|
167
|
-
await waitForDOMStable(session, 50, stabilityTimeout); // Reduced quiet period from 100ms
|
|
159
|
+
}, { timeout, interval: 30 }).catch(() => { });
|
|
160
|
+
// Quick 40ms settle window for hydration/initial script exec
|
|
161
|
+
await new Promise(r => setTimeout(r, 40));
|
|
168
162
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hookPipeline } from "../plugin/pipeline.js";
|
|
1
2
|
// Heuristics for "significant" changes
|
|
2
3
|
const SIGNIFICANT_TAGS = new Set(['button', 'a', 'input', 'textarea', 'select', 'h1', 'h2', 'h3', '[role="button"]', '[role="link"]']);
|
|
3
4
|
const MAX_TEXT_LEN = 150;
|
|
@@ -89,5 +90,16 @@ export async function watchMutations(session, durationMs) {
|
|
|
89
90
|
// Disabling the DOM agent automatically removes all listeners.
|
|
90
91
|
await DOM.disable();
|
|
91
92
|
}
|
|
93
|
+
if (summary.significantChange || summary.newElements.length > 0 || summary.removedElements.length > 0 || summary.attributeChanges.length > 0) {
|
|
94
|
+
const delta = {
|
|
95
|
+
type: "cdp_mutation",
|
|
96
|
+
timestamp: Date.now(),
|
|
97
|
+
added: summary.newElements.map(e => ({ backendNodeId: e.backendNodeId, tag: e.tag, text: e.text, role: e.role })),
|
|
98
|
+
removed: summary.removedElements.map(e => ({ backendNodeId: e.backendNodeId, tag: e.tag })),
|
|
99
|
+
changed: summary.attributeChanges.map(a => ({ backendNodeId: a.backendNodeId, attribute: a.attribute, newValue: a.newValue })),
|
|
100
|
+
significantChange: summary.significantChange,
|
|
101
|
+
};
|
|
102
|
+
hookPipeline.dispatchDOMMutation(delta, session).catch(() => { });
|
|
103
|
+
}
|
|
92
104
|
return summary;
|
|
93
105
|
}
|
|
@@ -33,6 +33,7 @@ class OperationMonitor {
|
|
|
33
33
|
const diagnosticsTimer = setInterval(() => {
|
|
34
34
|
this.checkOperationHealth(operationId);
|
|
35
35
|
}, 1000); // Check every 1 second (reduced from 2s for faster detection)
|
|
36
|
+
diagnosticsTimer.unref();
|
|
36
37
|
this.activeOperations.get(operationId).diagnosticsTimer = diagnosticsTimer;
|
|
37
38
|
}
|
|
38
39
|
/**
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Weavetab — server.ts
|
|
3
|
-
* MCP server:
|
|
3
|
+
* MCP server: 44 browser automation tools with security, rate limiting, and visual overlay.
|
|
4
4
|
* Weavetab by fy2ne
|
|
5
5
|
*/
|
|
6
6
|
import type { Config } from "./config/loader.js";
|