@weavetab/mcp 2.5.0-beta.2 → 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 +1 -1
- package/ARCHITECTURE.md +6 -6
- package/LICENSE +1 -1
- package/README.md +158 -961
- package/SECURITY.md +33 -22
- package/TROUBLESHOOTING.md +2 -2
- package/dist/cdp/bridge.js +2 -1
- package/dist/cdp/connector.d.ts +2 -0
- package/dist/cdp/connector.js +8 -4
- package/dist/cdp/helpers.d.ts +4 -0
- package/dist/cdp/helpers.js +181 -0
- package/dist/cdp/omni_broker.d.ts +1 -1
- package/dist/cdp/omni_broker.js +1 -1
- package/dist/cdp/walker.d.ts +25 -1
- package/dist/cdp/walker.js +304 -7
- package/dist/cli/plugin.js +24 -17
- package/dist/cli/updater.d.ts +1 -1
- package/dist/cli/updater.js +27 -7
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.js +41 -2
- package/dist/desktop/discovery.d.ts +44 -0
- package/dist/desktop/discovery.js +364 -0
- package/dist/index.js +95 -15
- 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/injector.js +2 -2
- package/dist/plugin/interface.d.ts +76 -2
- package/dist/plugin/loader.d.ts +15 -1
- package/dist/plugin/loader.js +217 -70
- package/dist/plugin/pipeline.d.ts +75 -0
- package/dist/plugin/pipeline.js +183 -0
- package/dist/plugin/registry.js +3 -2
- 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 +428 -156
- 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 +6 -0
- package/dist/state/session.js +27 -0
- package/dist/tools/browser_detect.d.ts +16 -3
- package/dist/tools/browser_detect.js +50 -5
- package/dist/tools/burst.d.ts +17 -8
- package/dist/tools/burst.js +236 -77
- 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 +74 -70
- 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.js +29 -34
- 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.js +14 -6
- 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 +107 -58
- 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 +21 -32
- package/docs/PLUGINS.md +121 -0
- package/docs/TOOLS.md +56 -0
- package/docs/architecture/blind-injection.md +83 -20
- package/docs/architecture/overview.md +49 -49
- package/docs/configuration/folders.md +55 -54
- package/docs/desktop-automation/electron-apps.md +217 -162
- package/docs/get-started/introduction.md +60 -60
- package/docs/intelligence/site-profiles.md +151 -0
- package/docs/tools/tools.md +631 -293
- package/docs/troubleshooting/trouble-loop.md +31 -31
- package/ext-dist/background.js +10 -62
- package/ext-dist/content.js +333 -920
- 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 +101 -67
- package/package.json +5 -6
- package/server.json +3 -3
- package/ext-dist/sidepanel/sidepanel.js +0 -25
- package/ext-dist/sidepanel.html +0 -547
package/dist/plugin/loader.js
CHANGED
|
@@ -1,18 +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";
|
|
4
5
|
import { PluginStorageManager } from "./storage.js";
|
|
5
6
|
import { PluginExtensionBridge } from "./extension_bridge.js";
|
|
7
|
+
import { getSiteProfile, learnPattern, recordPatternExecution, getPatternReliability, recordQuirk } from "../intelligence/patternCompiler.js";
|
|
6
8
|
const require = createRequire(import.meta.url);
|
|
9
|
+
import { WEAVETAB_VERSION } from "../version.js";
|
|
10
|
+
const RUNTIME_VERSION = WEAVETAB_VERSION;
|
|
7
11
|
function getNodeModulesDirs(customDir) {
|
|
8
12
|
if (customDir)
|
|
9
13
|
return [customDir];
|
|
14
|
+
const cwd = process.cwd();
|
|
10
15
|
const dirs = [
|
|
11
|
-
join(
|
|
12
|
-
join(
|
|
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"),
|
|
13
21
|
];
|
|
14
22
|
return Array.from(new Set(dirs.filter((d) => existsSync(d))));
|
|
15
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
|
+
}
|
|
16
92
|
/**
|
|
17
93
|
* Reads the manifest (weavetab.json) and package.json for an installed package
|
|
18
94
|
* without executing its runtime code (safe for listing/inspection).
|
|
@@ -59,7 +135,7 @@ export function listInstalledPlugins(pluginDir) {
|
|
|
59
135
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
60
136
|
continue;
|
|
61
137
|
if (entry.name.startsWith("@")) {
|
|
62
|
-
// Scoped packages (@weavetab/plugin-xyz, @scope/xyz)
|
|
138
|
+
// Scoped packages (@weavetab/plugin-xyz, @scope/xyz, @fy2ne/wt-youtube)
|
|
63
139
|
const scopeDir = join(modulesDir, entry.name);
|
|
64
140
|
try {
|
|
65
141
|
const scopeEntries = readdirSync(scopeDir, { withFileTypes: true });
|
|
@@ -76,7 +152,7 @@ export function listInstalledPlugins(pluginDir) {
|
|
|
76
152
|
catch { /* scope dir unreadable */ }
|
|
77
153
|
}
|
|
78
154
|
else {
|
|
79
|
-
// Top-level packages (any name)
|
|
155
|
+
// Top-level packages (any name: wt-github, custom-plugin, etc.)
|
|
80
156
|
const info = inspectInstalledPackage(join(modulesDir, entry.name));
|
|
81
157
|
if (info && !seenPackages.has(info.packageName)) {
|
|
82
158
|
seenPackages.add(info.packageName);
|
|
@@ -89,10 +165,89 @@ export function listInstalledPlugins(pluginDir) {
|
|
|
89
165
|
}
|
|
90
166
|
return results;
|
|
91
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
|
+
}
|
|
92
247
|
/**
|
|
93
248
|
* Attempt to load a single package as a Weavetab plugin for MCP server execution.
|
|
94
|
-
* The presence of weavetab.json is the ONLY requirement —
|
|
95
|
-
*
|
|
249
|
+
* The presence of weavetab.json is the ONLY requirement — any package name works.
|
|
250
|
+
* Constructs the rich PluginContext WITHOUT executing onLoad eagerly.
|
|
96
251
|
*/
|
|
97
252
|
function tryLoadPlugin(pkgDir) {
|
|
98
253
|
const manifestPath = join(pkgDir, "weavetab.json");
|
|
@@ -101,19 +256,47 @@ function tryLoadPlugin(pkgDir) {
|
|
|
101
256
|
try {
|
|
102
257
|
const rawManifest = readFileSync(manifestPath, "utf-8");
|
|
103
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
|
+
}
|
|
104
266
|
let pkg = {};
|
|
105
267
|
const pkgPath = join(pkgDir, "package.json");
|
|
106
268
|
if (existsSync(pkgPath)) {
|
|
107
|
-
|
|
269
|
+
try {
|
|
270
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
271
|
+
}
|
|
272
|
+
catch { /* malformed package.json */ }
|
|
273
|
+
}
|
|
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
|
+
}
|
|
108
283
|
}
|
|
109
|
-
const
|
|
110
|
-
|
|
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);
|
|
111
290
|
const plugin = pluginModule.default || pluginModule.plugin || pluginModule;
|
|
291
|
+
const normalizedPerms = normalizePermissions(manifest.permissions);
|
|
112
292
|
if (plugin &&
|
|
113
293
|
(Array.isArray(plugin.tools) ||
|
|
114
294
|
Array.isArray(plugin.overrides) ||
|
|
115
295
|
Array.isArray(plugin.removeTools) ||
|
|
116
|
-
|
|
296
|
+
typeof plugin.onBeforeAction === "function" ||
|
|
297
|
+
typeof plugin.onAfterAction === "function" ||
|
|
298
|
+
typeof plugin.onDOMMutation === "function" ||
|
|
299
|
+
Boolean(normalizedPerms?.tools) ||
|
|
117
300
|
typeof plugin.onLoad === "function")) {
|
|
118
301
|
plugin.name = plugin.name || manifest.name || pkg.name || basename(pkgDir);
|
|
119
302
|
plugin.version = plugin.version || manifest.version || pkg.version || "1.0.0";
|
|
@@ -121,80 +304,44 @@ function tryLoadPlugin(pkgDir) {
|
|
|
121
304
|
plugin.manifest = manifest;
|
|
122
305
|
plugin.tools = Array.isArray(plugin.tools) ? plugin.tools : [];
|
|
123
306
|
plugin.overrides = Array.isArray(plugin.overrides) ? plugin.overrides : [];
|
|
307
|
+
plugin.manifest.permissions = normalizedPerms;
|
|
124
308
|
plugin.removeTools = Array.isArray(plugin.removeTools)
|
|
125
309
|
? plugin.removeTools
|
|
126
|
-
:
|
|
310
|
+
: normalizedPerms.tools?.remove || [];
|
|
127
311
|
// Initialize isolated storage sandbox in ~/.weavetab/plugins/<plugin-name>/
|
|
128
312
|
const storageManager = new PluginStorageManager(plugin.name);
|
|
129
|
-
const extensionBridge = new PluginExtensionBridge(plugin.name,
|
|
313
|
+
const extensionBridge = new PluginExtensionBridge(plugin.name, normalizedPerms);
|
|
130
314
|
const pluginConfig = storageManager.loadConfig(plugin.defaultConfig || manifest.defaultConfig || {});
|
|
131
315
|
const hooks = {
|
|
132
316
|
beforeToolCall: [],
|
|
133
317
|
afterToolCall: [],
|
|
318
|
+
onDOMMutation: [],
|
|
134
319
|
onPageNavigate: [],
|
|
135
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
|
+
}
|
|
136
333
|
plugin.storage = storageManager;
|
|
137
334
|
plugin.extension = extensionBridge;
|
|
138
335
|
plugin.hooks = hooks;
|
|
139
|
-
// Construct
|
|
140
|
-
const pluginContext =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
pluginDir: storageManager.getDirectory(),
|
|
144
|
-
config: pluginConfig,
|
|
145
|
-
storage: storageManager,
|
|
146
|
-
extension: extensionBridge,
|
|
147
|
-
mcp: {
|
|
148
|
-
registerTool: (tool) => {
|
|
149
|
-
plugin.tools = plugin.tools || [];
|
|
150
|
-
const existingIndex = plugin.tools.findIndex((t) => t.name === tool.name);
|
|
151
|
-
if (existingIndex >= 0) {
|
|
152
|
-
plugin.tools[existingIndex] = tool;
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
plugin.tools.push(tool);
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
overrideTool: (toolName, overrideHandler) => {
|
|
159
|
-
plugin.overrides = plugin.overrides || [];
|
|
160
|
-
plugin.overrides.push({ name: toolName, handler: overrideHandler });
|
|
161
|
-
},
|
|
162
|
-
removeTool: (toolName) => {
|
|
163
|
-
plugin.removeTools = plugin.removeTools || [];
|
|
164
|
-
plugin.removeTools.push(toolName);
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
hooks: {
|
|
168
|
-
beforeToolCall: (fn) => hooks.beforeToolCall.push(fn),
|
|
169
|
-
afterToolCall: (fn) => hooks.afterToolCall.push(fn),
|
|
170
|
-
onPageNavigate: (fn) => hooks.onPageNavigate.push(fn),
|
|
171
|
-
},
|
|
172
|
-
logger: {
|
|
173
|
-
info: (...args) => console.error(`[Plugin: ${plugin.name}] [INFO]`, ...args),
|
|
174
|
-
warn: (...args) => console.error(`[Plugin: ${plugin.name}] [WARN]`, ...args),
|
|
175
|
-
error: (...args) => console.error(`[Plugin: ${plugin.name}] [ERROR]`, ...args),
|
|
176
|
-
debug: (...args) => console.error(`[Plugin: ${plugin.name}] [DEBUG]`, ...args),
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
// Execute dynamic onLoad if present
|
|
180
|
-
if (typeof plugin.onLoad === "function") {
|
|
181
|
-
try {
|
|
182
|
-
const loadPromise = plugin.onLoad(pluginContext);
|
|
183
|
-
if (loadPromise && typeof loadPromise.then === "function") {
|
|
184
|
-
loadPromise.catch((err) => {
|
|
185
|
-
console.error(`[Weavetab] Error in plugin "${plugin.name}" onLoad:`, err);
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
catch (e) {
|
|
190
|
-
console.error(`[Weavetab] Synchronous error in plugin "${plugin.name}" onLoad:`, e);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
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.
|
|
193
340
|
return plugin;
|
|
194
341
|
}
|
|
195
342
|
}
|
|
196
343
|
catch (e) {
|
|
197
|
-
|
|
344
|
+
console.error(`[Weavetab] Failed loading plugin at ${pkgDir}:`, e?.message || e);
|
|
198
345
|
}
|
|
199
346
|
return null;
|
|
200
347
|
}
|
|
@@ -209,7 +356,7 @@ export function discoverPlugins(pluginDir) {
|
|
|
209
356
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
210
357
|
continue;
|
|
211
358
|
if (entry.name.startsWith("@")) {
|
|
212
|
-
// Scoped package directory (e.g. @weavetab, @
|
|
359
|
+
// Scoped package directory (e.g. @weavetab, @fy2ne, @mycompany)
|
|
213
360
|
const scopeDir = join(modulesDir, entry.name);
|
|
214
361
|
try {
|
|
215
362
|
const scopeEntries = readdirSync(scopeDir, { withFileTypes: true });
|
|
@@ -226,7 +373,7 @@ export function discoverPlugins(pluginDir) {
|
|
|
226
373
|
catch { /* scope dir unreadable */ }
|
|
227
374
|
}
|
|
228
375
|
else {
|
|
229
|
-
// Top-level package — any name works: wt-
|
|
376
|
+
// Top-level package — any name works: wt-youtube, my-plugin, etc.
|
|
230
377
|
const plugin = tryLoadPlugin(join(modulesDir, entry.name));
|
|
231
378
|
if (plugin && !seenPluginNames.has(plugin.name)) {
|
|
232
379
|
seenPluginNames.add(plugin.name);
|
|
@@ -255,7 +402,7 @@ export function loadPluginByName(name, pluginDir) {
|
|
|
255
402
|
if (p)
|
|
256
403
|
return p;
|
|
257
404
|
}
|
|
258
|
-
// Check if it's a scoped package (e.g. @
|
|
405
|
+
// Check if it's a scoped package (e.g. @scope/name)
|
|
259
406
|
if (name.startsWith("@")) {
|
|
260
407
|
const scopeParts = name.split("/");
|
|
261
408
|
if (scopeParts.length === 2) {
|
|
@@ -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 {};
|
|
@@ -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
|
@@ -2,13 +2,14 @@ import { readFileSync, existsSync } from "node:fs";
|
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
+
import { WEAVETAB_VERSION } from "../version.js";
|
|
5
6
|
const _require = createRequire(import.meta.url);
|
|
6
7
|
function getVersion() {
|
|
7
8
|
try {
|
|
8
|
-
return _require("../../package.json").version ||
|
|
9
|
+
return _require("../../package.json").version || WEAVETAB_VERSION;
|
|
9
10
|
}
|
|
10
11
|
catch {
|
|
11
|
-
return
|
|
12
|
+
return WEAVETAB_VERSION;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -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;
|