@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
package/dist/plugin/loader.js
CHANGED
|
@@ -1,7 +1,94 @@
|
|
|
1
1
|
import { readdirSync, existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { join, basename } from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { PluginStorageManager } from "./storage.js";
|
|
6
|
+
import { PluginExtensionBridge } from "./extension_bridge.js";
|
|
7
|
+
import { getSiteProfile, learnPattern, recordPatternExecution, getPatternReliability, recordQuirk } from "../intelligence/patternCompiler.js";
|
|
4
8
|
const require = createRequire(import.meta.url);
|
|
9
|
+
import { WEAVETAB_VERSION } from "../version.js";
|
|
10
|
+
const RUNTIME_VERSION = WEAVETAB_VERSION;
|
|
11
|
+
function getNodeModulesDirs(customDir) {
|
|
12
|
+
if (customDir)
|
|
13
|
+
return [customDir];
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const dirs = [
|
|
16
|
+
join(cwd, "node_modules"),
|
|
17
|
+
join(cwd, "MCP", "node_modules"),
|
|
18
|
+
join(cwd, "..", "node_modules"),
|
|
19
|
+
join(os.homedir(), ".weavetab", "plugins", "node_modules"),
|
|
20
|
+
join(os.homedir(), ".weavetab-system", "plugins", "node_modules"),
|
|
21
|
+
];
|
|
22
|
+
return Array.from(new Set(dirs.filter((d) => existsSync(d))));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Normalizes permissions format across legacy arrays and object aliases (filesystem vs fs).
|
|
26
|
+
*/
|
|
27
|
+
export function normalizePermissions(perms) {
|
|
28
|
+
if (!perms)
|
|
29
|
+
return {};
|
|
30
|
+
// Handle flat string array: ["browser.evaluate", "browser.navigate", "fs.read", ...]
|
|
31
|
+
if (Array.isArray(perms)) {
|
|
32
|
+
const normalized = { cdp: {}, fs: {}, extension: {} };
|
|
33
|
+
for (const item of perms) {
|
|
34
|
+
if (typeof item !== "string")
|
|
35
|
+
continue;
|
|
36
|
+
const [cat, sub] = item.split(".");
|
|
37
|
+
if (cat === "browser" || cat === "cdp") {
|
|
38
|
+
normalized.cdp[sub || "evaluate"] = true;
|
|
39
|
+
}
|
|
40
|
+
else if (cat === "fs" || cat === "filesystem") {
|
|
41
|
+
normalized.fs[sub || "read"] = ["*"];
|
|
42
|
+
}
|
|
43
|
+
else if (cat === "extension" || cat === "hud") {
|
|
44
|
+
normalized.extension[sub || "hud"] = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
const result = { ...perms };
|
|
50
|
+
// Reconcile filesystem <-> fs alias
|
|
51
|
+
if (perms.filesystem && !perms.fs) {
|
|
52
|
+
result.fs = perms.filesystem;
|
|
53
|
+
}
|
|
54
|
+
else if (perms.fs && !perms.filesystem) {
|
|
55
|
+
result.filesystem = perms.fs;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validates whether the plugin's engine requirement matches the current runtime version.
|
|
61
|
+
*/
|
|
62
|
+
function checkEngineCompatibility(engineReq) {
|
|
63
|
+
if (!engineReq || engineReq === "*" || engineReq === "latest") {
|
|
64
|
+
return { compatible: true };
|
|
65
|
+
}
|
|
66
|
+
// Simple semver range parsing (handles ^x.y.z, >=x.y.z, ~x.y.z, x.y.z)
|
|
67
|
+
const cleanReq = engineReq.replace(/[\^~>=]/g, "").trim();
|
|
68
|
+
const reqParts = cleanReq.split(".").map((p) => parseInt(p, 10));
|
|
69
|
+
const curParts = RUNTIME_VERSION.split("-")[0].split(".").map((p) => parseInt(p, 10));
|
|
70
|
+
if (engineReq.startsWith("^")) {
|
|
71
|
+
// Caret: same major
|
|
72
|
+
if (curParts[0] === reqParts[0] && (curParts[1] > reqParts[1] || (curParts[1] === reqParts[1] && curParts[2] >= reqParts[2]))) {
|
|
73
|
+
return { compatible: true };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (engineReq.startsWith(">=")) {
|
|
77
|
+
if (curParts[0] > reqParts[0] || (curParts[0] === reqParts[0] && curParts[1] >= reqParts[1])) {
|
|
78
|
+
return { compatible: true };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Exact or direct match
|
|
83
|
+
if (curParts[0] === reqParts[0] && curParts[1] === reqParts[1]) {
|
|
84
|
+
return { compatible: true };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
compatible: false,
|
|
89
|
+
message: `Plugin requires Weavetab version ${engineReq}, but current runtime is ${RUNTIME_VERSION}.`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
5
92
|
/**
|
|
6
93
|
* Reads the manifest (weavetab.json) and package.json for an installed package
|
|
7
94
|
* without executing its runtime code (safe for listing/inspection).
|
|
@@ -39,44 +126,128 @@ export function inspectInstalledPackage(pkgDir) {
|
|
|
39
126
|
*/
|
|
40
127
|
export function listInstalledPlugins(pluginDir) {
|
|
41
128
|
const results = [];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
129
|
+
const seenPackages = new Set();
|
|
130
|
+
const modulesDirs = getNodeModulesDirs(pluginDir);
|
|
131
|
+
for (const modulesDir of modulesDirs) {
|
|
132
|
+
try {
|
|
133
|
+
const entries = readdirSync(modulesDir, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
136
|
+
continue;
|
|
137
|
+
if (entry.name.startsWith("@")) {
|
|
138
|
+
// Scoped packages (@weavetab/plugin-xyz, @scope/xyz, @fy2ne/wt-youtube)
|
|
139
|
+
const scopeDir = join(modulesDir, entry.name);
|
|
140
|
+
try {
|
|
141
|
+
const scopeEntries = readdirSync(scopeDir, { withFileTypes: true });
|
|
142
|
+
for (const scopeEntry of scopeEntries) {
|
|
143
|
+
if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink())
|
|
144
|
+
continue;
|
|
145
|
+
const info = inspectInstalledPackage(join(scopeDir, scopeEntry.name));
|
|
146
|
+
if (info && !seenPackages.has(info.packageName)) {
|
|
147
|
+
seenPackages.add(info.packageName);
|
|
148
|
+
results.push(info);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch { /* scope dir unreadable */ }
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Top-level packages (any name: wt-github, custom-plugin, etc.)
|
|
156
|
+
const info = inspectInstalledPackage(join(modulesDir, entry.name));
|
|
157
|
+
if (info && !seenPackages.has(info.packageName)) {
|
|
158
|
+
seenPackages.add(info.packageName);
|
|
159
|
+
results.push(info);
|
|
61
160
|
}
|
|
62
161
|
}
|
|
63
|
-
catch { /* scope dir unreadable */ }
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
// Top-level packages (any name)
|
|
67
|
-
const info = inspectInstalledPackage(join(modulesDir, entry.name));
|
|
68
|
-
if (info)
|
|
69
|
-
results.push(info);
|
|
70
162
|
}
|
|
71
163
|
}
|
|
164
|
+
catch { /* ignore filesystem errors */ }
|
|
72
165
|
}
|
|
73
|
-
catch { /* ignore filesystem errors */ }
|
|
74
166
|
return results;
|
|
75
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Builds the canonical rich PluginContext for a loaded plugin.
|
|
170
|
+
*/
|
|
171
|
+
export function buildPluginContext(plugin, storageManager, extensionBridge, pluginConfig, hooks) {
|
|
172
|
+
return {
|
|
173
|
+
name: plugin.name,
|
|
174
|
+
version: plugin.version,
|
|
175
|
+
pluginDir: storageManager.getDirectory(),
|
|
176
|
+
config: pluginConfig,
|
|
177
|
+
storage: storageManager,
|
|
178
|
+
extension: extensionBridge,
|
|
179
|
+
mcp: {
|
|
180
|
+
registerTool: (tool) => {
|
|
181
|
+
plugin.tools = plugin.tools || [];
|
|
182
|
+
const existingIndex = plugin.tools.findIndex((t) => t.name === tool.name);
|
|
183
|
+
if (existingIndex >= 0) {
|
|
184
|
+
plugin.tools[existingIndex] = tool;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
plugin.tools.push(tool);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
overrideTool: (toolName, overrideHandler) => {
|
|
191
|
+
plugin.overrides = plugin.overrides || [];
|
|
192
|
+
plugin.overrides.push({ name: toolName, handler: overrideHandler });
|
|
193
|
+
},
|
|
194
|
+
removeTool: (toolName) => {
|
|
195
|
+
plugin.removeTools = plugin.removeTools || [];
|
|
196
|
+
plugin.removeTools.push(toolName);
|
|
197
|
+
},
|
|
198
|
+
unregisterTool: (toolName) => {
|
|
199
|
+
plugin.removeTools = plugin.removeTools || [];
|
|
200
|
+
plugin.removeTools.push(toolName);
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
hooks: {
|
|
204
|
+
onBeforeAction: (fn) => {
|
|
205
|
+
if (typeof fn === "function")
|
|
206
|
+
hooks.beforeToolCall.push(fn);
|
|
207
|
+
},
|
|
208
|
+
onAfterAction: (fn) => {
|
|
209
|
+
if (typeof fn === "function") {
|
|
210
|
+
hooks.afterToolCall.push(async (toolName, args, result, session) => {
|
|
211
|
+
return await fn(toolName, result, session, args);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
onDOMMutation: (fn) => {
|
|
216
|
+
if (typeof fn === "function")
|
|
217
|
+
hooks.onDOMMutation?.push(fn);
|
|
218
|
+
},
|
|
219
|
+
beforeToolCall: (fn) => {
|
|
220
|
+
if (typeof fn === "function")
|
|
221
|
+
hooks.beforeToolCall.push(fn);
|
|
222
|
+
},
|
|
223
|
+
afterToolCall: (fn) => {
|
|
224
|
+
if (typeof fn === "function")
|
|
225
|
+
hooks.afterToolCall.push(fn);
|
|
226
|
+
},
|
|
227
|
+
onPageNavigate: (fn) => {
|
|
228
|
+
if (typeof fn === "function")
|
|
229
|
+
hooks.onPageNavigate.push(fn);
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
memory: {
|
|
233
|
+
getSiteProfile: (origin) => getSiteProfile(origin),
|
|
234
|
+
learnPattern: (origin, name, pattern) => learnPattern(origin, name, pattern),
|
|
235
|
+
recordExecution: (origin, name, success, durationMs) => recordPatternExecution(origin, name, success, durationMs),
|
|
236
|
+
getReliability: (origin, patternName) => getPatternReliability(origin, patternName),
|
|
237
|
+
recordQuirk: (origin, quirkKey, quirkData) => recordQuirk(origin, quirkKey, quirkData),
|
|
238
|
+
},
|
|
239
|
+
logger: {
|
|
240
|
+
info: (...args) => console.error(`[Plugin: ${plugin.name}] [INFO]`, ...args),
|
|
241
|
+
warn: (...args) => console.error(`[Plugin: ${plugin.name}] [WARN]`, ...args),
|
|
242
|
+
error: (...args) => console.error(`[Plugin: ${plugin.name}] [ERROR]`, ...args),
|
|
243
|
+
debug: (...args) => console.error(`[Plugin: ${plugin.name}] [DEBUG]`, ...args),
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
76
247
|
/**
|
|
77
248
|
* Attempt to load a single package as a Weavetab plugin for MCP server execution.
|
|
78
|
-
* The presence of weavetab.json is the ONLY requirement —
|
|
79
|
-
*
|
|
249
|
+
* The presence of weavetab.json is the ONLY requirement — any package name works.
|
|
250
|
+
* Constructs the rich PluginContext WITHOUT executing onLoad eagerly.
|
|
80
251
|
*/
|
|
81
252
|
function tryLoadPlugin(pkgDir) {
|
|
82
253
|
const manifestPath = join(pkgDir, "weavetab.json");
|
|
@@ -85,71 +256,168 @@ function tryLoadPlugin(pkgDir) {
|
|
|
85
256
|
try {
|
|
86
257
|
const rawManifest = readFileSync(manifestPath, "utf-8");
|
|
87
258
|
const manifest = JSON.parse(rawManifest);
|
|
259
|
+
// Check engine compatibility
|
|
260
|
+
if (manifest.engines?.weavetab) {
|
|
261
|
+
const compat = checkEngineCompatibility(manifest.engines.weavetab);
|
|
262
|
+
if (!compat.compatible) {
|
|
263
|
+
console.error(`[Weavetab] Warning: ${compat.message}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
88
266
|
let pkg = {};
|
|
89
267
|
const pkgPath = join(pkgDir, "package.json");
|
|
90
268
|
if (existsSync(pkgPath)) {
|
|
91
|
-
|
|
269
|
+
try {
|
|
270
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
271
|
+
}
|
|
272
|
+
catch { /* malformed package.json */ }
|
|
92
273
|
}
|
|
93
|
-
|
|
94
|
-
|
|
274
|
+
// Resolve entry point: manifest.entry > pkg.main > "dist/index.js" > "index.js"
|
|
275
|
+
let entryFile = manifest.entry || pkg.main;
|
|
276
|
+
if (!entryFile) {
|
|
277
|
+
if (existsSync(join(pkgDir, "dist", "index.js"))) {
|
|
278
|
+
entryFile = "dist/index.js";
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
entryFile = "index.js";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const resolvedEntry = join(pkgDir, entryFile);
|
|
285
|
+
if (!existsSync(resolvedEntry)) {
|
|
286
|
+
console.error(`[Weavetab] Plugin entry point not found: ${resolvedEntry}`);
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const pluginModule = require(resolvedEntry);
|
|
95
290
|
const plugin = pluginModule.default || pluginModule.plugin || pluginModule;
|
|
291
|
+
const normalizedPerms = normalizePermissions(manifest.permissions);
|
|
96
292
|
if (plugin &&
|
|
97
293
|
(Array.isArray(plugin.tools) ||
|
|
98
294
|
Array.isArray(plugin.overrides) ||
|
|
99
295
|
Array.isArray(plugin.removeTools) ||
|
|
100
|
-
|
|
296
|
+
typeof plugin.onBeforeAction === "function" ||
|
|
297
|
+
typeof plugin.onAfterAction === "function" ||
|
|
298
|
+
typeof plugin.onDOMMutation === "function" ||
|
|
299
|
+
Boolean(normalizedPerms?.tools) ||
|
|
300
|
+
typeof plugin.onLoad === "function")) {
|
|
101
301
|
plugin.name = plugin.name || manifest.name || pkg.name || basename(pkgDir);
|
|
102
302
|
plugin.version = plugin.version || manifest.version || pkg.version || "1.0.0";
|
|
103
303
|
plugin.description = plugin.description || manifest.description || pkg.description || "";
|
|
104
304
|
plugin.manifest = manifest;
|
|
105
305
|
plugin.tools = Array.isArray(plugin.tools) ? plugin.tools : [];
|
|
106
306
|
plugin.overrides = Array.isArray(plugin.overrides) ? plugin.overrides : [];
|
|
307
|
+
plugin.manifest.permissions = normalizedPerms;
|
|
107
308
|
plugin.removeTools = Array.isArray(plugin.removeTools)
|
|
108
309
|
? plugin.removeTools
|
|
109
|
-
:
|
|
310
|
+
: normalizedPerms.tools?.remove || [];
|
|
311
|
+
// Initialize isolated storage sandbox in ~/.weavetab/plugins/<plugin-name>/
|
|
312
|
+
const storageManager = new PluginStorageManager(plugin.name);
|
|
313
|
+
const extensionBridge = new PluginExtensionBridge(plugin.name, normalizedPerms);
|
|
314
|
+
const pluginConfig = storageManager.loadConfig(plugin.defaultConfig || manifest.defaultConfig || {});
|
|
315
|
+
const hooks = {
|
|
316
|
+
beforeToolCall: [],
|
|
317
|
+
afterToolCall: [],
|
|
318
|
+
onDOMMutation: [],
|
|
319
|
+
onPageNavigate: [],
|
|
320
|
+
};
|
|
321
|
+
// Top-level plugin definition hooks
|
|
322
|
+
if (typeof plugin.onBeforeAction === "function") {
|
|
323
|
+
hooks.beforeToolCall.push(plugin.onBeforeAction);
|
|
324
|
+
}
|
|
325
|
+
if (typeof plugin.onAfterAction === "function") {
|
|
326
|
+
hooks.afterToolCall.push(async (toolName, args, result, session) => {
|
|
327
|
+
return await plugin.onAfterAction(toolName, result, session, args);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (typeof plugin.onDOMMutation === "function") {
|
|
331
|
+
hooks.onDOMMutation.push(plugin.onDOMMutation);
|
|
332
|
+
}
|
|
333
|
+
plugin.storage = storageManager;
|
|
334
|
+
plugin.extension = extensionBridge;
|
|
335
|
+
plugin.hooks = hooks;
|
|
336
|
+
// Construct authoritative PluginContext
|
|
337
|
+
const pluginContext = buildPluginContext(plugin, storageManager, extensionBridge, pluginConfig, hooks);
|
|
338
|
+
plugin._context = pluginContext;
|
|
339
|
+
// Note: We do NOT invoke plugin.onLoad here. server.ts is the sole lifecycle owner.
|
|
110
340
|
return plugin;
|
|
111
341
|
}
|
|
112
342
|
}
|
|
113
|
-
catch {
|
|
114
|
-
|
|
343
|
+
catch (e) {
|
|
344
|
+
console.error(`[Weavetab] Failed loading plugin at ${pkgDir}:`, e?.message || e);
|
|
115
345
|
}
|
|
116
346
|
return null;
|
|
117
347
|
}
|
|
118
348
|
export function discoverPlugins(pluginDir) {
|
|
119
349
|
const plugins = [];
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
350
|
+
const seenPluginNames = new Set();
|
|
351
|
+
const modulesDirs = getNodeModulesDirs(pluginDir);
|
|
352
|
+
for (const modulesDir of modulesDirs) {
|
|
353
|
+
try {
|
|
354
|
+
const entries = readdirSync(modulesDir, { withFileTypes: true });
|
|
355
|
+
for (const entry of entries) {
|
|
356
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
357
|
+
continue;
|
|
358
|
+
if (entry.name.startsWith("@")) {
|
|
359
|
+
// Scoped package directory (e.g. @weavetab, @fy2ne, @mycompany)
|
|
360
|
+
const scopeDir = join(modulesDir, entry.name);
|
|
361
|
+
try {
|
|
362
|
+
const scopeEntries = readdirSync(scopeDir, { withFileTypes: true });
|
|
363
|
+
for (const scopeEntry of scopeEntries) {
|
|
364
|
+
if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink())
|
|
365
|
+
continue;
|
|
366
|
+
const plugin = tryLoadPlugin(join(scopeDir, scopeEntry.name));
|
|
367
|
+
if (plugin && !seenPluginNames.has(plugin.name)) {
|
|
368
|
+
seenPluginNames.add(plugin.name);
|
|
369
|
+
plugins.push(plugin);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch { /* scope dir unreadable */ }
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Top-level package — any name works: wt-youtube, my-plugin, etc.
|
|
377
|
+
const plugin = tryLoadPlugin(join(modulesDir, entry.name));
|
|
378
|
+
if (plugin && !seenPluginNames.has(plugin.name)) {
|
|
379
|
+
seenPluginNames.add(plugin.name);
|
|
380
|
+
plugins.push(plugin);
|
|
139
381
|
}
|
|
140
382
|
}
|
|
141
|
-
catch { /* scope dir unreadable */ }
|
|
142
383
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// node_modules may not exist or be inaccessible
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return plugins;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Loads a specific plugin by name from node_modules.
|
|
393
|
+
*/
|
|
394
|
+
export function loadPluginByName(name, pluginDir) {
|
|
395
|
+
try {
|
|
396
|
+
const modulesDirs = getNodeModulesDirs(pluginDir);
|
|
397
|
+
for (const modulesDir of modulesDirs) {
|
|
398
|
+
// Check if it's a top-level package
|
|
399
|
+
let pkgDir = join(modulesDir, name);
|
|
400
|
+
if (existsSync(pkgDir)) {
|
|
401
|
+
const p = tryLoadPlugin(pkgDir);
|
|
402
|
+
if (p)
|
|
403
|
+
return p;
|
|
404
|
+
}
|
|
405
|
+
// Check if it's a scoped package (e.g. @scope/name)
|
|
406
|
+
if (name.startsWith("@")) {
|
|
407
|
+
const scopeParts = name.split("/");
|
|
408
|
+
if (scopeParts.length === 2) {
|
|
409
|
+
pkgDir = join(modulesDir, scopeParts[0], scopeParts[1]);
|
|
410
|
+
if (existsSync(pkgDir)) {
|
|
411
|
+
const p = tryLoadPlugin(pkgDir);
|
|
412
|
+
if (p)
|
|
413
|
+
return p;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
148
416
|
}
|
|
149
417
|
}
|
|
150
418
|
}
|
|
151
419
|
catch {
|
|
152
|
-
//
|
|
420
|
+
// Ignore
|
|
153
421
|
}
|
|
154
|
-
return
|
|
422
|
+
return null;
|
|
155
423
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import type { DOMMutationDelta } from "./interface.js";
|
|
11
|
+
export type BeforeToolCallHook = (toolName: string, args: Record<string, any>, session?: any) => Promise<Record<string, any> | {
|
|
12
|
+
__blocked: boolean;
|
|
13
|
+
reason?: string;
|
|
14
|
+
} | void> | Record<string, any> | {
|
|
15
|
+
__blocked: boolean;
|
|
16
|
+
reason?: string;
|
|
17
|
+
} | void;
|
|
18
|
+
export type AfterToolCallHook = (toolName: string, args: Record<string, any>, result: any, session?: any) => Promise<any | void> | any | void;
|
|
19
|
+
export type PageNavigateHook = (url: string, session?: any) => Promise<void> | void;
|
|
20
|
+
export type DOMMutationHook = (delta: DOMMutationDelta, session?: any) => Promise<void> | void;
|
|
21
|
+
export interface PluginHooksRegistry {
|
|
22
|
+
beforeToolCall?: BeforeToolCallHook[] | BeforeToolCallHook;
|
|
23
|
+
afterToolCall?: AfterToolCallHook[] | AfterToolCallHook;
|
|
24
|
+
onBeforeAction?: BeforeToolCallHook[] | BeforeToolCallHook;
|
|
25
|
+
onAfterAction?: Array<(tool: string, result: any, session?: any, args?: Record<string, any>) => any> | ((tool: string, result: any, session?: any, args?: Record<string, any>) => any);
|
|
26
|
+
onDOMMutation?: DOMMutationHook[] | DOMMutationHook;
|
|
27
|
+
onPageNavigate?: PageNavigateHook[] | PageNavigateHook;
|
|
28
|
+
}
|
|
29
|
+
declare class HookPipeline {
|
|
30
|
+
private beforeToolCallHooks;
|
|
31
|
+
private afterToolCallHooks;
|
|
32
|
+
private onDOMMutationHooks;
|
|
33
|
+
private onPageNavigateHooks;
|
|
34
|
+
/**
|
|
35
|
+
* Register hooks for a specific plugin.
|
|
36
|
+
*/
|
|
37
|
+
registerPluginHooks(pluginName: string, hooks: Partial<PluginHooksRegistry> & Record<string, any>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Remove all hooks registered by a plugin on unload.
|
|
40
|
+
*/
|
|
41
|
+
unregisterPluginHooks(pluginName: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Dispatches beforeAction / beforeToolCall hooks in sequence.
|
|
44
|
+
* If a hook returns an object, arguments are augmented/modified.
|
|
45
|
+
* If a hook returns { __blocked: true, reason } or throws, execution is aborted.
|
|
46
|
+
*/
|
|
47
|
+
dispatchBeforeToolCall(toolName: string, args: Record<string, any>, session?: any): Promise<Record<string, any>>;
|
|
48
|
+
/**
|
|
49
|
+
* Alias for dispatchBeforeToolCall.
|
|
50
|
+
*/
|
|
51
|
+
dispatchBeforeAction(toolName: string, args: Record<string, any>, session?: any): Promise<Record<string, any>>;
|
|
52
|
+
/**
|
|
53
|
+
* Dispatches afterAction / afterToolCall hooks in sequence.
|
|
54
|
+
* Allows plugins to inspect, transform, or redact tool results.
|
|
55
|
+
*/
|
|
56
|
+
dispatchAfterToolCall(toolName: string, args: Record<string, any>, result: any, session?: any): Promise<any>;
|
|
57
|
+
/**
|
|
58
|
+
* Alias for dispatchAfterToolCall.
|
|
59
|
+
*/
|
|
60
|
+
dispatchAfterAction(toolName: string, result: any, session?: any, args?: Record<string, any>): Promise<any>;
|
|
61
|
+
/**
|
|
62
|
+
* Dispatches DOM mutation deltas to all listening plugins.
|
|
63
|
+
*/
|
|
64
|
+
dispatchDOMMutation(delta: DOMMutationDelta, session?: any): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Dispatches onPageNavigate hooks when navigation occurs.
|
|
67
|
+
*/
|
|
68
|
+
dispatchPageNavigate(url: string, session?: any): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Clears all registered hooks.
|
|
71
|
+
*/
|
|
72
|
+
clearAllHooks(): void;
|
|
73
|
+
}
|
|
74
|
+
export declare const hookPipeline: HookPipeline;
|
|
75
|
+
export {};
|