@weavetab/mcp 2.5.0-beta.2 → 2.5.0-beta.5

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 (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +216 -631
  4. package/README.md +153 -1074
  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 +10 -6
  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.d.ts +1 -0
  17. package/dist/cli/plugin.js +81 -18
  18. package/dist/cli/updater.d.ts +1 -1
  19. package/dist/cli/updater.js +27 -7
  20. package/dist/config/loader.d.ts +9 -1
  21. package/dist/config/loader.js +42 -3
  22. package/dist/desktop/discovery.d.ts +44 -0
  23. package/dist/desktop/discovery.js +364 -0
  24. package/dist/index.js +137 -21
  25. package/dist/intelligence/marks.d.ts +36 -0
  26. package/dist/intelligence/marks.js +30 -0
  27. package/dist/intelligence/patternCompiler.d.ts +118 -0
  28. package/dist/intelligence/patternCompiler.js +259 -0
  29. package/dist/intelligence/sessionMemory.d.ts +2 -0
  30. package/dist/intelligence/sessionMemory.js +18 -8
  31. package/dist/overlay/injector.js +2 -2
  32. package/dist/plugin/interface.d.ts +88 -5
  33. package/dist/plugin/loader.d.ts +12 -1
  34. package/dist/plugin/loader.js +280 -70
  35. package/dist/plugin/pipeline.d.ts +75 -0
  36. package/dist/plugin/pipeline.js +183 -0
  37. package/dist/plugin/registry.d.ts +2 -1
  38. package/dist/plugin/registry.js +24 -14
  39. package/dist/security/secrets.d.ts +7 -0
  40. package/dist/security/secrets.js +37 -0
  41. package/dist/sensors/actionability.js +1 -2
  42. package/dist/sensors/guard.js +4 -10
  43. package/dist/sensors/mutations.js +12 -0
  44. package/dist/sensors/operation_monitor.js +1 -0
  45. package/dist/server.d.ts +1 -1
  46. package/dist/server.js +453 -178
  47. package/dist/state/agent.d.ts +1 -0
  48. package/dist/state/agent.js +3 -0
  49. package/dist/state/delta.d.ts +12 -4
  50. package/dist/state/delta.js +67 -14
  51. package/dist/state/session.d.ts +6 -0
  52. package/dist/state/session.js +27 -0
  53. package/dist/tools/browser_detect.d.ts +16 -3
  54. package/dist/tools/browser_detect.js +52 -7
  55. package/dist/tools/burst.d.ts +17 -8
  56. package/dist/tools/burst.js +236 -77
  57. package/dist/tools/captcha.js +1 -1
  58. package/dist/tools/checkpoint.d.ts +38 -0
  59. package/dist/tools/checkpoint.js +271 -0
  60. package/dist/tools/click.d.ts +1 -0
  61. package/dist/tools/click.js +74 -70
  62. package/dist/tools/design_extract.js +1 -1
  63. package/dist/tools/eval.js +1 -1
  64. package/dist/tools/fill.d.ts +20 -1
  65. package/dist/tools/fill.js +199 -40
  66. package/dist/tools/find.d.ts +16 -0
  67. package/dist/tools/find.js +67 -44
  68. package/dist/tools/github/index.d.ts +19 -4
  69. package/dist/tools/github/index.js +40 -4
  70. package/dist/tools/highlight.d.ts +6 -2
  71. package/dist/tools/highlight.js +158 -72
  72. package/dist/tools/inspect.js +1 -1
  73. package/dist/tools/key.js +65 -56
  74. package/dist/tools/navigate.d.ts +1 -1
  75. package/dist/tools/navigate.js +34 -39
  76. package/dist/tools/pdf.js +21 -13
  77. package/dist/tools/pointer.d.ts +18 -3
  78. package/dist/tools/pointer.js +89 -1
  79. package/dist/tools/read.d.ts +28 -8
  80. package/dist/tools/read.js +230 -56
  81. package/dist/tools/scrape.js +67 -13
  82. package/dist/tools/screenshot.js +21 -16
  83. package/dist/tools/scroll.js +6 -11
  84. package/dist/tools/select.js +8 -2
  85. package/dist/tools/snapshot.js +3 -34
  86. package/dist/tools/tabs.js +15 -7
  87. package/dist/tools/thought.d.ts +2 -0
  88. package/dist/tools/thought.js +13 -14
  89. package/dist/tools/type.d.ts +2 -0
  90. package/dist/tools/type.js +107 -58
  91. package/dist/tools/type_secret.js +42 -76
  92. package/dist/tools/upload.js +1 -1
  93. package/dist/tools/video_compiler.d.ts +1 -1
  94. package/dist/tools/video_compiler.js +11 -12
  95. package/dist/version.d.ts +5 -0
  96. package/dist/version.js +5 -0
  97. package/docs/DOCUMENTATION.md +37 -41
  98. package/docs/PLUGINS.md +121 -0
  99. package/docs/TOOLS.md +54 -0
  100. package/docs/architecture/blind-injection.md +83 -20
  101. package/docs/architecture/overview.md +49 -49
  102. package/docs/architecture/security.md +8 -7
  103. package/docs/architecture/transport.md +2 -2
  104. package/docs/cli/cli-commands.md +7 -1
  105. package/docs/cli/cli-flags.md +15 -7
  106. package/docs/cli/cli-scripting.md +6 -3
  107. package/docs/configuration/config-locations.md +3 -4
  108. package/docs/configuration/env-variables.md +4 -4
  109. package/docs/configuration/folders.md +55 -54
  110. package/docs/desktop-automation/electron-apps.md +214 -162
  111. package/docs/get-started/introduction.md +60 -60
  112. package/docs/get-started/setup-wizard.md +7 -6
  113. package/docs/intelligence/site-profiles.md +151 -0
  114. package/docs/tools/tools.md +631 -293
  115. package/docs/troubleshooting/trouble-browser.md +1 -1
  116. package/docs/troubleshooting/trouble-loop.md +31 -31
  117. package/ext-dist/background.js +10 -62
  118. package/ext-dist/content.js +333 -920
  119. package/ext-dist/hud/hud.js +9 -9
  120. package/ext-dist/manifest.json +2 -6
  121. package/ext-dist/styles.css +6 -1
  122. package/icon.png +0 -0
  123. package/mcp.json +87 -71
  124. package/package.json +118 -119
  125. package/server.json +5 -5
  126. package/ext-dist/sidepanel/sidepanel.js +0 -25
  127. package/ext-dist/sidepanel.html +0 -547
@@ -0,0 +1,183 @@
1
+ /**
2
+ * src/plugin/pipeline.ts — Central Hook Dispatch Pipeline for Weavetab Plugins
3
+ *
4
+ * Dispatches lifecycle events:
5
+ * - onBeforeAction / beforeToolCall: Intercept tool calls, modify arguments, or block unauthorized actions
6
+ * - onAfterAction / afterToolCall: Transform, redact, or augment tool return results
7
+ * - onDOMMutation: Stream real-time DOM mutation deltas to registered plugins
8
+ * - onPageNavigate: Notify plugins upon browser navigation
9
+ */
10
+ class HookPipeline {
11
+ beforeToolCallHooks = [];
12
+ afterToolCallHooks = [];
13
+ onDOMMutationHooks = [];
14
+ onPageNavigateHooks = [];
15
+ /**
16
+ * Register hooks for a specific plugin.
17
+ */
18
+ registerPluginHooks(pluginName, hooks) {
19
+ if (!hooks)
20
+ return;
21
+ // 1. Before Action / Before Tool Call
22
+ const beforeFns = hooks.onBeforeAction || hooks.beforeToolCall;
23
+ if (beforeFns) {
24
+ const list = Array.isArray(beforeFns) ? beforeFns : [beforeFns];
25
+ for (const fn of list) {
26
+ if (typeof fn === "function") {
27
+ this.beforeToolCallHooks.push({ pluginName, fn });
28
+ }
29
+ }
30
+ }
31
+ // 2. After Action (signature: (tool, result, session, args))
32
+ if (hooks.onAfterAction) {
33
+ const list = Array.isArray(hooks.onAfterAction) ? hooks.onAfterAction : [hooks.onAfterAction];
34
+ for (const fn of list) {
35
+ if (typeof fn === "function") {
36
+ this.afterToolCallHooks.push({
37
+ pluginName,
38
+ fn: async (toolName, args, result, session) => {
39
+ return await fn(toolName, result, session, args);
40
+ },
41
+ });
42
+ }
43
+ }
44
+ }
45
+ // Legacy After Tool Call (signature: (tool, args, result, session))
46
+ if (hooks.afterToolCall) {
47
+ const list = Array.isArray(hooks.afterToolCall) ? hooks.afterToolCall : [hooks.afterToolCall];
48
+ for (const fn of list) {
49
+ if (typeof fn === "function") {
50
+ this.afterToolCallHooks.push({ pluginName, fn });
51
+ }
52
+ }
53
+ }
54
+ // 3. DOM Mutation Hooks
55
+ if (hooks.onDOMMutation) {
56
+ const list = Array.isArray(hooks.onDOMMutation) ? hooks.onDOMMutation : [hooks.onDOMMutation];
57
+ for (const fn of list) {
58
+ if (typeof fn === "function") {
59
+ this.onDOMMutationHooks.push({ pluginName, fn });
60
+ }
61
+ }
62
+ }
63
+ // 4. Page Navigate Hooks
64
+ if (hooks.onPageNavigate) {
65
+ const list = Array.isArray(hooks.onPageNavigate) ? hooks.onPageNavigate : [hooks.onPageNavigate];
66
+ for (const fn of list) {
67
+ if (typeof fn === "function") {
68
+ this.onPageNavigateHooks.push({ pluginName, fn });
69
+ }
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Remove all hooks registered by a plugin on unload.
75
+ */
76
+ unregisterPluginHooks(pluginName) {
77
+ this.beforeToolCallHooks = this.beforeToolCallHooks.filter((h) => h.pluginName !== pluginName);
78
+ this.afterToolCallHooks = this.afterToolCallHooks.filter((h) => h.pluginName !== pluginName);
79
+ this.onDOMMutationHooks = this.onDOMMutationHooks.filter((h) => h.pluginName !== pluginName);
80
+ this.onPageNavigateHooks = this.onPageNavigateHooks.filter((h) => h.pluginName !== pluginName);
81
+ }
82
+ /**
83
+ * Dispatches beforeAction / beforeToolCall hooks in sequence.
84
+ * If a hook returns an object, arguments are augmented/modified.
85
+ * If a hook returns { __blocked: true, reason } or throws, execution is aborted.
86
+ */
87
+ async dispatchBeforeToolCall(toolName, args, session) {
88
+ let currentArgs = { ...args };
89
+ for (const { pluginName, fn } of this.beforeToolCallHooks) {
90
+ try {
91
+ const modified = await fn(toolName, currentArgs, session);
92
+ if (modified && typeof modified === "object") {
93
+ if (modified.__blocked) {
94
+ const reason = modified.reason || `Action '${toolName}' blocked by plugin '${pluginName}'`;
95
+ const blockError = new Error(reason);
96
+ blockError.isBlocked = true;
97
+ blockError.pluginName = pluginName;
98
+ throw blockError;
99
+ }
100
+ if (!Array.isArray(modified)) {
101
+ currentArgs = { ...currentArgs, ...modified };
102
+ }
103
+ }
104
+ }
105
+ catch (err) {
106
+ if (err?.isBlocked) {
107
+ throw err;
108
+ }
109
+ process.stderr.write(`[Weavetab Hooks] Error in beforeToolCall (${pluginName} on ${toolName}): ${err?.message || err}\n`);
110
+ }
111
+ }
112
+ return currentArgs;
113
+ }
114
+ /**
115
+ * Alias for dispatchBeforeToolCall.
116
+ */
117
+ async dispatchBeforeAction(toolName, args, session) {
118
+ return this.dispatchBeforeToolCall(toolName, args, session);
119
+ }
120
+ /**
121
+ * Dispatches afterAction / afterToolCall hooks in sequence.
122
+ * Allows plugins to inspect, transform, or redact tool results.
123
+ */
124
+ async dispatchAfterToolCall(toolName, args, result, session) {
125
+ let currentResult = result;
126
+ for (const { pluginName, fn } of this.afterToolCallHooks) {
127
+ try {
128
+ const modified = await fn(toolName, args, currentResult, session);
129
+ if (modified !== undefined) {
130
+ currentResult = modified;
131
+ }
132
+ }
133
+ catch (err) {
134
+ process.stderr.write(`[Weavetab Hooks] Error in afterToolCall (${pluginName} on ${toolName}): ${err?.message || err}\n`);
135
+ }
136
+ }
137
+ return currentResult;
138
+ }
139
+ /**
140
+ * Alias for dispatchAfterToolCall.
141
+ */
142
+ async dispatchAfterAction(toolName, result, session, args) {
143
+ return this.dispatchAfterToolCall(toolName, args || {}, result, session);
144
+ }
145
+ /**
146
+ * Dispatches DOM mutation deltas to all listening plugins.
147
+ */
148
+ async dispatchDOMMutation(delta, session) {
149
+ if (this.onDOMMutationHooks.length === 0)
150
+ return;
151
+ for (const { pluginName, fn } of this.onDOMMutationHooks) {
152
+ try {
153
+ await fn(delta, session);
154
+ }
155
+ catch (err) {
156
+ process.stderr.write(`[Weavetab Hooks] Error in onDOMMutation (${pluginName}): ${err?.message || err}\n`);
157
+ }
158
+ }
159
+ }
160
+ /**
161
+ * Dispatches onPageNavigate hooks when navigation occurs.
162
+ */
163
+ async dispatchPageNavigate(url, session) {
164
+ for (const { pluginName, fn } of this.onPageNavigateHooks) {
165
+ try {
166
+ await fn(url, session);
167
+ }
168
+ catch (err) {
169
+ process.stderr.write(`[Weavetab Hooks] Error in onPageNavigate (${pluginName}): ${err?.message || err}\n`);
170
+ }
171
+ }
172
+ }
173
+ /**
174
+ * Clears all registered hooks.
175
+ */
176
+ clearAllHooks() {
177
+ this.beforeToolCallHooks = [];
178
+ this.afterToolCallHooks = [];
179
+ this.onDOMMutationHooks = [];
180
+ this.onPageNavigateHooks = [];
181
+ }
182
+ }
183
+ export const hookPipeline = new HookPipeline();
@@ -19,7 +19,8 @@ export interface PluginCandidate {
19
19
  description?: string;
20
20
  }
21
21
  export declare const OFFICIAL_PLUGIN_PREFIX = "@weavetab/plugin-";
22
- export declare const REGISTRY_URL = "https://raw.githubusercontent.com/weavetab/registry/main/verified.json";
22
+ export declare const REGISTRY_URL = "https://weavetab.dev/api/registry/verified.json";
23
+ export declare const REGISTRY_FALLBACK_URL = "https://raw.githubusercontent.com/weavetab/registry/main/verified.json";
23
24
  /**
24
25
  * Returns the set of verified package names.
25
26
  * - Tries local registry/verified.json first (dev/test mode).
@@ -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);
@@ -17,8 +18,9 @@ const __dirname = dirname(__filename);
17
18
  // Verification Registry
18
19
  // ---------------------------------------------------------------------------
19
20
  export const OFFICIAL_PLUGIN_PREFIX = "@weavetab/plugin-";
20
- // The production remote URL — points to weavetab/registry on GitHub.
21
- export const REGISTRY_URL = "https://raw.githubusercontent.com/weavetab/registry/main/verified.json";
21
+ // The production remote URLs — weavetab.dev (D1-backed) primary, GitHub raw fallback.
22
+ export const REGISTRY_URL = "https://weavetab.dev/api/registry/verified.json";
23
+ export const REGISTRY_FALLBACK_URL = "https://raw.githubusercontent.com/weavetab/registry/main/verified.json";
22
24
  // Local override: when this file exists at the root of the repo, we use it instead
23
25
  // of the remote URL. This allows full offline/local testing.
24
26
  const LOCAL_REGISTRY_PATH = join(__dirname, "..", "..", "..", "registry", "verified.json");
@@ -58,16 +60,24 @@ function loadLocalRegistry() {
58
60
  return null;
59
61
  }
60
62
  async function fetchRemoteRegistry() {
61
- const res = await fetch(REGISTRY_URL, {
62
- signal: AbortSignal.timeout(5000),
63
- headers: { "User-Agent": "Weavetab-Registry-Check/1.0" },
64
- });
65
- if (!res.ok)
66
- throw new Error(`registry fetch failed: ${res.status}`);
67
- const data = await res.json();
68
- if (!Array.isArray(data.verified))
69
- throw new Error("invalid registry format");
70
- return extractPackageNames(data.verified);
63
+ for (const url of [REGISTRY_URL, REGISTRY_FALLBACK_URL]) {
64
+ try {
65
+ const res = await fetch(url, {
66
+ signal: AbortSignal.timeout(5000),
67
+ headers: { "User-Agent": "Weavetab-Registry-Check/1.0" },
68
+ });
69
+ if (!res.ok)
70
+ continue;
71
+ const data = await res.json();
72
+ if (!Array.isArray(data.verified))
73
+ continue;
74
+ return extractPackageNames(data.verified);
75
+ }
76
+ catch {
77
+ continue;
78
+ }
79
+ }
80
+ throw new Error("registry fetch failed: all sources unreachable");
71
81
  }
72
82
  /**
73
83
  * Returns the set of verified package names.
@@ -18,6 +18,12 @@ export interface SecretListEntry {
18
18
  updatedAt: string;
19
19
  }
20
20
  export declare const GLOBAL_SECRETS_PATH: string;
21
+ export declare const PROJECT_SECRETS_DIRNAME = ".weavetab";
22
+ export declare const PROJECT_SECRETS_FILENAME = "secrets.json";
23
+ /** Absolute path of the project-scoped secrets file for a directory (default: cwd). */
24
+ export declare function getProjectSecretsPath(cwd?: string): string;
25
+ /** Creates an empty project-scoped secrets file (chmod 600). Returns its path. */
26
+ export declare function initProjectSecrets(cwd?: string): string;
21
27
  declare class JsonSecretStoreImpl implements SecretProvider {
22
28
  /**
23
29
  * Resolve a secret value by key.
@@ -69,6 +75,7 @@ declare class SessionSecretTrackerImpl {
69
75
  isNodeSecret(sessionId: string, backendNodeId: number): boolean;
70
76
  registerSecretValue(sessionId: string, value: string): void;
71
77
  redact(sessionId: string, text: string): string;
78
+ redactAll(text: string): string;
72
79
  clearSession(sessionId: string): void;
73
80
  }
74
81
  export declare const SessionSecretTracker: SessionSecretTrackerImpl;
@@ -86,7 +86,31 @@ function decrypt(ciphertext) {
86
86
  import { GLOBAL_CONFIG_DIR } from "../config/loader.js";
87
87
  // ─── Path resolution ──────────────────────────────────────────────────────────
88
88
  export const GLOBAL_SECRETS_PATH = path.join(GLOBAL_CONFIG_DIR, "secrets.json");
89
+ export const PROJECT_SECRETS_DIRNAME = ".weavetab";
90
+ export const PROJECT_SECRETS_FILENAME = "secrets.json";
91
+ /** Absolute path of the project-scoped secrets file for a directory (default: cwd). */
92
+ export function getProjectSecretsPath(cwd = process.cwd()) {
93
+ return path.join(cwd, PROJECT_SECRETS_DIRNAME, PROJECT_SECRETS_FILENAME);
94
+ }
95
+ /** Creates an empty project-scoped secrets file (chmod 600). Returns its path. */
96
+ export function initProjectSecrets(cwd = process.cwd()) {
97
+ const filePath = getProjectSecretsPath(cwd);
98
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
99
+ if (!fs.existsSync(filePath)) {
100
+ fs.writeFileSync(filePath, JSON.stringify({ version: 1, secrets: {} }, null, 2), { mode: 0o600 });
101
+ }
102
+ return filePath;
103
+ }
89
104
  function resolveSecretsPath() {
105
+ // Project-scoped file wins when present — lets a checkout carry its own keys.
106
+ try {
107
+ const projectPath = getProjectSecretsPath();
108
+ if (fs.existsSync(projectPath))
109
+ return projectPath;
110
+ }
111
+ catch {
112
+ // fall through to global
113
+ }
90
114
  return GLOBAL_SECRETS_PATH;
91
115
  }
92
116
  // ─── Permission guard ─────────────────────────────────────────────────────────
@@ -357,6 +381,19 @@ class SessionSecretTrackerImpl {
357
381
  }
358
382
  return redacted;
359
383
  }
384
+ redactAll(text) {
385
+ if (!text || typeof text !== 'string')
386
+ return text;
387
+ let redacted = text;
388
+ for (const set of this.sessionSecrets.values()) {
389
+ for (const secret of set) {
390
+ if (secret && secret.length >= 3) {
391
+ redacted = redacted.split(secret).join('[REDACTED]');
392
+ }
393
+ }
394
+ }
395
+ return redacted;
396
+ }
360
397
  clearSession(sessionId) {
361
398
  this.trackedNodes.delete(sessionId);
362
399
  this.sessionSecrets.delete(sessionId);
@@ -23,7 +23,7 @@
23
23
  */
24
24
  // ── Constants ─────────────────────────────────────────────────────────────────
25
25
  /** Playwright's per-action retry backoff (ms). */
26
- const BACKOFF = [0, 20, 100, 100, 500];
26
+ const BACKOFF = [0, 20, 100];
27
27
  /**
28
28
  * 4-way scroll alignment rotation — escape sticky headers.
29
29
  * undefined = browser default (nearest), then progressively more aggressive.
@@ -121,7 +121,6 @@ function buildPageExpression(selector, scrollOptions, fingerprint, stabilityMs)
121
121
  let stable = false;
122
122
  while (Date.now() < stabilityDeadline) {
123
123
  await new Promise(r => requestAnimationFrame(r));
124
- await new Promise(r => requestAnimationFrame(r)); // two ticks like Playwright
125
124
  const cur = el.getBoundingClientRect();
126
125
  if (
127
126
  Math.abs(cur.x - prevRect.x) < 0.5 &&
@@ -148,9 +148,7 @@ export async function detectPageWeight(session) {
148
148
  };
149
149
  }
150
150
  }
151
- // ─── Navigation wait — used after clicks that trigger navigation ──────────────
152
- export async function awaitNavigation(session, timeout = 6000 // Reduced from 10000ms
153
- ) {
151
+ export async function awaitNavigation(session, timeout = 6000) {
154
152
  // Poll readyState instead of consuming one-shot event
155
153
  await pollUntil(async () => {
156
154
  const { result } = await session.Runtime.evaluate({
@@ -158,11 +156,7 @@ export async function awaitNavigation(session, timeout = 6000 // Reduced from 10
158
156
  returnByValue: true,
159
157
  }).catch(() => ({ result: { value: 'loading' } }));
160
158
  return result?.value === 'complete' || result?.value === 'interactive';
161
- }, { timeout, interval: 50 }).catch(() => {
162
- throw new Error(`Navigation timeout after ${timeout}ms`);
163
- });
164
- // After DOM content fires, detect page weight for post-load stability wait
165
- const weight = await detectPageWeight(session);
166
- const stabilityTimeout = weight.level === "heavy" ? 3000 : weight.level === "medium" ? 2000 : 1000; // Reduced from 5000/3000/2000
167
- await waitForDOMStable(session, 50, stabilityTimeout); // Reduced quiet period from 100ms
159
+ }, { timeout, interval: 30 }).catch(() => { });
160
+ // Quick 40ms settle window for hydration/initial script exec
161
+ await new Promise(r => setTimeout(r, 40));
168
162
  }
@@ -1,3 +1,4 @@
1
+ import { hookPipeline } from "../plugin/pipeline.js";
1
2
  // Heuristics for "significant" changes
2
3
  const SIGNIFICANT_TAGS = new Set(['button', 'a', 'input', 'textarea', 'select', 'h1', 'h2', 'h3', '[role="button"]', '[role="link"]']);
3
4
  const MAX_TEXT_LEN = 150;
@@ -89,5 +90,16 @@ export async function watchMutations(session, durationMs) {
89
90
  // Disabling the DOM agent automatically removes all listeners.
90
91
  await DOM.disable();
91
92
  }
93
+ if (summary.significantChange || summary.newElements.length > 0 || summary.removedElements.length > 0 || summary.attributeChanges.length > 0) {
94
+ const delta = {
95
+ type: "cdp_mutation",
96
+ timestamp: Date.now(),
97
+ added: summary.newElements.map(e => ({ backendNodeId: e.backendNodeId, tag: e.tag, text: e.text, role: e.role })),
98
+ removed: summary.removedElements.map(e => ({ backendNodeId: e.backendNodeId, tag: e.tag })),
99
+ changed: summary.attributeChanges.map(a => ({ backendNodeId: a.backendNodeId, attribute: a.attribute, newValue: a.newValue })),
100
+ significantChange: summary.significantChange,
101
+ };
102
+ hookPipeline.dispatchDOMMutation(delta, session).catch(() => { });
103
+ }
92
104
  return summary;
93
105
  }
@@ -33,6 +33,7 @@ class OperationMonitor {
33
33
  const diagnosticsTimer = setInterval(() => {
34
34
  this.checkOperationHealth(operationId);
35
35
  }, 1000); // Check every 1 second (reduced from 2s for faster detection)
36
+ diagnosticsTimer.unref();
36
37
  this.activeOperations.get(operationId).diagnosticsTimer = diagnosticsTimer;
37
38
  }
38
39
  /**
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Weavetab — server.ts
3
- * MCP server: 43 browser automation tools with security, rate limiting, and visual overlay.
3
+ * MCP server: 44 browser automation tools with security, rate limiting, and visual overlay.
4
4
  * Weavetab by fy2ne
5
5
  */
6
6
  import type { Config } from "./config/loader.js";