@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.
Files changed (113) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +1 -1
  4. package/README.md +158 -961
  5. package/SECURITY.md +33 -22
  6. package/TROUBLESHOOTING.md +2 -2
  7. package/dist/cdp/bridge.js +2 -1
  8. package/dist/cdp/connector.d.ts +2 -0
  9. package/dist/cdp/connector.js +8 -4
  10. package/dist/cdp/helpers.d.ts +4 -0
  11. package/dist/cdp/helpers.js +181 -0
  12. package/dist/cdp/omni_broker.d.ts +1 -1
  13. package/dist/cdp/omni_broker.js +1 -1
  14. package/dist/cdp/walker.d.ts +25 -1
  15. package/dist/cdp/walker.js +304 -7
  16. package/dist/cli/plugin.js +24 -17
  17. package/dist/cli/updater.d.ts +1 -1
  18. package/dist/cli/updater.js +27 -7
  19. package/dist/config/loader.d.ts +8 -0
  20. package/dist/config/loader.js +41 -2
  21. package/dist/desktop/discovery.d.ts +44 -0
  22. package/dist/desktop/discovery.js +364 -0
  23. package/dist/index.js +95 -15
  24. package/dist/intelligence/marks.d.ts +36 -0
  25. package/dist/intelligence/marks.js +30 -0
  26. package/dist/intelligence/patternCompiler.d.ts +118 -0
  27. package/dist/intelligence/patternCompiler.js +259 -0
  28. package/dist/intelligence/sessionMemory.d.ts +2 -0
  29. package/dist/intelligence/sessionMemory.js +18 -8
  30. package/dist/overlay/injector.js +2 -2
  31. package/dist/plugin/interface.d.ts +76 -2
  32. package/dist/plugin/loader.d.ts +15 -1
  33. package/dist/plugin/loader.js +217 -70
  34. package/dist/plugin/pipeline.d.ts +75 -0
  35. package/dist/plugin/pipeline.js +183 -0
  36. package/dist/plugin/registry.js +3 -2
  37. package/dist/security/secrets.d.ts +1 -0
  38. package/dist/security/secrets.js +13 -0
  39. package/dist/sensors/actionability.js +1 -2
  40. package/dist/sensors/guard.js +4 -10
  41. package/dist/sensors/mutations.js +12 -0
  42. package/dist/sensors/operation_monitor.js +1 -0
  43. package/dist/server.d.ts +1 -1
  44. package/dist/server.js +428 -156
  45. package/dist/state/agent.d.ts +1 -0
  46. package/dist/state/agent.js +3 -0
  47. package/dist/state/delta.d.ts +12 -4
  48. package/dist/state/delta.js +67 -14
  49. package/dist/state/session.d.ts +6 -0
  50. package/dist/state/session.js +27 -0
  51. package/dist/tools/browser_detect.d.ts +16 -3
  52. package/dist/tools/browser_detect.js +50 -5
  53. package/dist/tools/burst.d.ts +17 -8
  54. package/dist/tools/burst.js +236 -77
  55. package/dist/tools/checkpoint.d.ts +38 -0
  56. package/dist/tools/checkpoint.js +271 -0
  57. package/dist/tools/click.d.ts +1 -0
  58. package/dist/tools/click.js +74 -70
  59. package/dist/tools/design_extract.js +1 -1
  60. package/dist/tools/eval.js +1 -1
  61. package/dist/tools/fill.d.ts +20 -1
  62. package/dist/tools/fill.js +199 -40
  63. package/dist/tools/find.d.ts +16 -0
  64. package/dist/tools/find.js +67 -44
  65. package/dist/tools/github/index.d.ts +19 -4
  66. package/dist/tools/github/index.js +40 -4
  67. package/dist/tools/highlight.d.ts +6 -2
  68. package/dist/tools/highlight.js +158 -72
  69. package/dist/tools/inspect.js +1 -1
  70. package/dist/tools/key.js +65 -56
  71. package/dist/tools/navigate.js +29 -34
  72. package/dist/tools/pdf.js +21 -13
  73. package/dist/tools/pointer.d.ts +18 -3
  74. package/dist/tools/pointer.js +89 -1
  75. package/dist/tools/read.d.ts +28 -8
  76. package/dist/tools/read.js +230 -56
  77. package/dist/tools/scrape.js +67 -13
  78. package/dist/tools/screenshot.js +21 -16
  79. package/dist/tools/scroll.js +6 -11
  80. package/dist/tools/select.js +8 -2
  81. package/dist/tools/snapshot.js +3 -34
  82. package/dist/tools/tabs.js +14 -6
  83. package/dist/tools/thought.d.ts +2 -0
  84. package/dist/tools/thought.js +13 -14
  85. package/dist/tools/type.d.ts +2 -0
  86. package/dist/tools/type.js +107 -58
  87. package/dist/tools/type_secret.js +42 -76
  88. package/dist/tools/upload.js +1 -1
  89. package/dist/tools/video_compiler.d.ts +1 -1
  90. package/dist/tools/video_compiler.js +11 -12
  91. package/dist/version.d.ts +5 -0
  92. package/dist/version.js +5 -0
  93. package/docs/DOCUMENTATION.md +21 -32
  94. package/docs/PLUGINS.md +121 -0
  95. package/docs/TOOLS.md +56 -0
  96. package/docs/architecture/blind-injection.md +83 -20
  97. package/docs/architecture/overview.md +49 -49
  98. package/docs/configuration/folders.md +55 -54
  99. package/docs/desktop-automation/electron-apps.md +217 -162
  100. package/docs/get-started/introduction.md +60 -60
  101. package/docs/intelligence/site-profiles.md +151 -0
  102. package/docs/tools/tools.md +631 -293
  103. package/docs/troubleshooting/trouble-loop.md +31 -31
  104. package/ext-dist/background.js +10 -62
  105. package/ext-dist/content.js +333 -920
  106. package/ext-dist/hud/hud.js +9 -9
  107. package/ext-dist/manifest.json +2 -6
  108. package/ext-dist/styles.css +6 -1
  109. package/mcp.json +101 -67
  110. package/package.json +5 -6
  111. package/server.json +3 -3
  112. package/ext-dist/sidepanel/sidepanel.js +0 -25
  113. package/ext-dist/sidepanel.html +0 -547
@@ -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(process.cwd(), "node_modules"),
12
- join(process.cwd(), "MCP", "node_modules"),
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 — no name check needed.
95
- * This gives developers complete freedom to name their packages anything.
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
- pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
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 pluginMain = pkg.main || "index.js";
110
- const pluginModule = require(join(pkgDir, pluginMain));
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
- Boolean(manifest.permissions?.tools) ||
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
- : manifest.permissions?.tools?.remove || [];
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, manifest.permissions);
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 rich PluginContext
140
- const pluginContext = {
141
- name: plugin.name,
142
- version: plugin.version,
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
- // console.error(`Failed loading plugin at ${pkgDir}:`, e);
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, @microsoft, @mycompany)
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-github, my-cool-plugin, etc.
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. @weavetab/name)
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();
@@ -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 || "2.4.0";
9
+ return _require("../../package.json").version || WEAVETAB_VERSION;
9
10
  }
10
11
  catch {
11
- return "2.4.0";
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;