@weavetab/mcp 2.5.0-beta.0

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 (281) hide show
  1. package/AGENTS.md +48 -0
  2. package/ARCHITECTURE.md +590 -0
  3. package/LICENSE +631 -0
  4. package/README.md +909 -0
  5. package/SECURITY.md +196 -0
  6. package/TROUBLESHOOTING.md +365 -0
  7. package/dist/anticaptcha/detector.d.ts +49 -0
  8. package/dist/anticaptcha/detector.js +465 -0
  9. package/dist/anticaptcha/integration.d.ts +36 -0
  10. package/dist/anticaptcha/integration.js +123 -0
  11. package/dist/anticaptcha/solver.d.ts +27 -0
  12. package/dist/anticaptcha/solver.js +552 -0
  13. package/dist/audit/logger.d.ts +13 -0
  14. package/dist/audit/logger.js +177 -0
  15. package/dist/audit/telemetry.d.ts +36 -0
  16. package/dist/audit/telemetry.js +162 -0
  17. package/dist/cdp/bridge.d.ts +16 -0
  18. package/dist/cdp/bridge.js +826 -0
  19. package/dist/cdp/confirm.d.ts +26 -0
  20. package/dist/cdp/confirm.js +79 -0
  21. package/dist/cdp/connector.d.ts +37 -0
  22. package/dist/cdp/connector.js +661 -0
  23. package/dist/cdp/extension.d.ts +36 -0
  24. package/dist/cdp/extension.js +118 -0
  25. package/dist/cdp/finder.d.ts +1 -0
  26. package/dist/cdp/finder.js +2 -0
  27. package/dist/cdp/helpers.d.ts +11 -0
  28. package/dist/cdp/helpers.js +195 -0
  29. package/dist/cdp/label_cache.d.ts +12 -0
  30. package/dist/cdp/label_cache.js +51 -0
  31. package/dist/cdp/lock.d.ts +33 -0
  32. package/dist/cdp/lock.js +206 -0
  33. package/dist/cdp/omni_broker.d.ts +17 -0
  34. package/dist/cdp/omni_broker.js +119 -0
  35. package/dist/cdp/profiles.d.ts +1 -0
  36. package/dist/cdp/profiles.js +87 -0
  37. package/dist/cdp/pruner.d.ts +41 -0
  38. package/dist/cdp/pruner.js +263 -0
  39. package/dist/cdp/ref_cache.d.ts +101 -0
  40. package/dist/cdp/ref_cache.js +234 -0
  41. package/dist/cdp/retry.d.ts +5 -0
  42. package/dist/cdp/retry.js +39 -0
  43. package/dist/cdp/retry_enhanced.d.ts +44 -0
  44. package/dist/cdp/retry_enhanced.js +102 -0
  45. package/dist/cdp/security.d.ts +50 -0
  46. package/dist/cdp/security.js +237 -0
  47. package/dist/cdp/verify.d.ts +39 -0
  48. package/dist/cdp/verify.js +143 -0
  49. package/dist/cdp/walker.d.ts +91 -0
  50. package/dist/cdp/walker.js +896 -0
  51. package/dist/cdp/walker.test.d.ts +2 -0
  52. package/dist/cdp/walker.test.js +172 -0
  53. package/dist/cdp/websocket_transport.d.ts +15 -0
  54. package/dist/cdp/websocket_transport.js +118 -0
  55. package/dist/cli/plugin.d.ts +9 -0
  56. package/dist/cli/plugin.js +1134 -0
  57. package/dist/cli/updater.d.ts +1 -0
  58. package/dist/cli/updater.js +72 -0
  59. package/dist/config/loader.d.ts +120 -0
  60. package/dist/config/loader.js +704 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +1248 -0
  63. package/dist/intelligence/hints.d.ts +11 -0
  64. package/dist/intelligence/hints.js +39 -0
  65. package/dist/intelligence/intent.d.ts +33 -0
  66. package/dist/intelligence/intent.js +171 -0
  67. package/dist/intelligence/sessionMemory.d.ts +66 -0
  68. package/dist/intelligence/sessionMemory.js +284 -0
  69. package/dist/intelligence/sessionMemory.test.d.ts +2 -0
  70. package/dist/intelligence/sessionMemory.test.js +62 -0
  71. package/dist/intelligence/trail.d.ts +64 -0
  72. package/dist/intelligence/trail.js +233 -0
  73. package/dist/intelligence/trail.test.d.ts +2 -0
  74. package/dist/intelligence/trail.test.js +110 -0
  75. package/dist/intelligence/vision.d.ts +24 -0
  76. package/dist/intelligence/vision.js +151 -0
  77. package/dist/lifecycle/mission_manager.d.ts +16 -0
  78. package/dist/lifecycle/mission_manager.js +44 -0
  79. package/dist/overlay/bubble.d.ts +16 -0
  80. package/dist/overlay/bubble.js +37 -0
  81. package/dist/overlay/ghost.d.ts +35 -0
  82. package/dist/overlay/ghost.js +508 -0
  83. package/dist/overlay/injector.d.ts +22 -0
  84. package/dist/overlay/injector.js +198 -0
  85. package/dist/plugin/interface.d.ts +134 -0
  86. package/dist/plugin/interface.js +1 -0
  87. package/dist/plugin/loader.d.ts +18 -0
  88. package/dist/plugin/loader.js +155 -0
  89. package/dist/plugin/registry.d.ts +50 -0
  90. package/dist/plugin/registry.js +234 -0
  91. package/dist/security/blacklist.d.ts +24 -0
  92. package/dist/security/blacklist.js +80 -0
  93. package/dist/security/filter.d.ts +2 -0
  94. package/dist/security/filter.js +20 -0
  95. package/dist/security/guard.d.ts +8 -0
  96. package/dist/security/guard.js +35 -0
  97. package/dist/security/ratelimit.d.ts +11 -0
  98. package/dist/security/ratelimit.js +57 -0
  99. package/dist/security/secrets.d.ts +75 -0
  100. package/dist/security/secrets.js +365 -0
  101. package/dist/sensors/actionability.d.ts +81 -0
  102. package/dist/sensors/actionability.js +303 -0
  103. package/dist/sensors/guard.d.ts +25 -0
  104. package/dist/sensors/guard.js +168 -0
  105. package/dist/sensors/mutations.d.ts +23 -0
  106. package/dist/sensors/mutations.js +93 -0
  107. package/dist/sensors/network.d.ts +67 -0
  108. package/dist/sensors/network.js +193 -0
  109. package/dist/sensors/operation_monitor.d.ts +80 -0
  110. package/dist/sensors/operation_monitor.js +203 -0
  111. package/dist/sensors/pacing.d.ts +44 -0
  112. package/dist/sensors/pacing.js +111 -0
  113. package/dist/sensors/thoughts.d.ts +2 -0
  114. package/dist/sensors/thoughts.js +59 -0
  115. package/dist/sensors/wait.d.ts +12 -0
  116. package/dist/sensors/wait.js +44 -0
  117. package/dist/server.d.ts +7 -0
  118. package/dist/server.js +1687 -0
  119. package/dist/state/agent.d.ts +36 -0
  120. package/dist/state/agent.js +101 -0
  121. package/dist/state/cache.d.ts +16 -0
  122. package/dist/state/cache.js +92 -0
  123. package/dist/state/cache.test.d.ts +2 -0
  124. package/dist/state/cache.test.js +76 -0
  125. package/dist/state/delta.d.ts +71 -0
  126. package/dist/state/delta.js +234 -0
  127. package/dist/state/delta.test.d.ts +2 -0
  128. package/dist/state/delta.test.js +279 -0
  129. package/dist/state/groups.d.ts +15 -0
  130. package/dist/state/groups.js +80 -0
  131. package/dist/state/session.d.ts +30 -0
  132. package/dist/state/session.js +155 -0
  133. package/dist/tools/accessibility.d.ts +14 -0
  134. package/dist/tools/accessibility.js +32 -0
  135. package/dist/tools/ask.d.ts +14 -0
  136. package/dist/tools/ask.js +49 -0
  137. package/dist/tools/automation.d.ts +36 -0
  138. package/dist/tools/automation.js +218 -0
  139. package/dist/tools/browser_detect.d.ts +13 -0
  140. package/dist/tools/browser_detect.js +73 -0
  141. package/dist/tools/burst.d.ts +35 -0
  142. package/dist/tools/burst.js +344 -0
  143. package/dist/tools/canvas.d.ts +25 -0
  144. package/dist/tools/canvas.js +132 -0
  145. package/dist/tools/captcha.d.ts +38 -0
  146. package/dist/tools/captcha.js +209 -0
  147. package/dist/tools/click.d.ts +36 -0
  148. package/dist/tools/click.js +561 -0
  149. package/dist/tools/click_and_wait.d.ts +33 -0
  150. package/dist/tools/click_and_wait.js +149 -0
  151. package/dist/tools/clipboard.d.ts +29 -0
  152. package/dist/tools/clipboard.js +279 -0
  153. package/dist/tools/console.d.ts +21 -0
  154. package/dist/tools/console.js +107 -0
  155. package/dist/tools/cookies.d.ts +67 -0
  156. package/dist/tools/cookies.js +121 -0
  157. package/dist/tools/design_extract.d.ts +109 -0
  158. package/dist/tools/design_extract.js +641 -0
  159. package/dist/tools/dialog.d.ts +24 -0
  160. package/dist/tools/dialog.js +109 -0
  161. package/dist/tools/drag.d.ts +15 -0
  162. package/dist/tools/drag.js +29 -0
  163. package/dist/tools/emulation.d.ts +33 -0
  164. package/dist/tools/emulation.js +69 -0
  165. package/dist/tools/eval.d.ts +20 -0
  166. package/dist/tools/eval.js +92 -0
  167. package/dist/tools/fill.d.ts +41 -0
  168. package/dist/tools/fill.js +667 -0
  169. package/dist/tools/find.d.ts +30 -0
  170. package/dist/tools/find.js +60 -0
  171. package/dist/tools/geo.d.ts +19 -0
  172. package/dist/tools/geo.js +29 -0
  173. package/dist/tools/github/analyze.d.ts +4 -0
  174. package/dist/tools/github/analyze.js +113 -0
  175. package/dist/tools/github/cache.d.ts +21 -0
  176. package/dist/tools/github/cache.js +28 -0
  177. package/dist/tools/github/index.d.ts +4 -0
  178. package/dist/tools/github/index.js +4 -0
  179. package/dist/tools/github/issues.d.ts +9 -0
  180. package/dist/tools/github/issues.js +88 -0
  181. package/dist/tools/github/pr.d.ts +5 -0
  182. package/dist/tools/github/pr.js +88 -0
  183. package/dist/tools/github/rateLimiter.d.ts +4 -0
  184. package/dist/tools/github/rateLimiter.js +46 -0
  185. package/dist/tools/github/read.d.ts +34 -0
  186. package/dist/tools/github/read.js +74 -0
  187. package/dist/tools/github/repoContext.d.ts +20 -0
  188. package/dist/tools/github/repoContext.js +129 -0
  189. package/dist/tools/highlight.d.ts +20 -0
  190. package/dist/tools/highlight.js +140 -0
  191. package/dist/tools/hover.d.ts +19 -0
  192. package/dist/tools/hover.js +57 -0
  193. package/dist/tools/inspect.d.ts +17 -0
  194. package/dist/tools/inspect.js +125 -0
  195. package/dist/tools/integration.test.d.ts +2 -0
  196. package/dist/tools/integration.test.js +96 -0
  197. package/dist/tools/key.d.ts +25 -0
  198. package/dist/tools/key.js +173 -0
  199. package/dist/tools/macro_compiler.d.ts +7 -0
  200. package/dist/tools/macro_compiler.js +82 -0
  201. package/dist/tools/mouse.d.ts +21 -0
  202. package/dist/tools/mouse.js +62 -0
  203. package/dist/tools/navigate.d.ts +16 -0
  204. package/dist/tools/navigate.js +188 -0
  205. package/dist/tools/network_intercept.d.ts +17 -0
  206. package/dist/tools/network_intercept.js +151 -0
  207. package/dist/tools/office/index.d.ts +9 -0
  208. package/dist/tools/office/index.js +7 -0
  209. package/dist/tools/office/list.d.ts +15 -0
  210. package/dist/tools/office/list.js +41 -0
  211. package/dist/tools/office/parse.d.ts +20 -0
  212. package/dist/tools/office/parse.js +172 -0
  213. package/dist/tools/pdf.d.ts +22 -0
  214. package/dist/tools/pdf.js +42 -0
  215. package/dist/tools/performance.d.ts +12 -0
  216. package/dist/tools/performance.js +47 -0
  217. package/dist/tools/performance_test.d.ts +70 -0
  218. package/dist/tools/performance_test.js +128 -0
  219. package/dist/tools/plan.d.ts +39 -0
  220. package/dist/tools/plan.js +161 -0
  221. package/dist/tools/pointer.d.ts +35 -0
  222. package/dist/tools/pointer.js +522 -0
  223. package/dist/tools/read.d.ts +30 -0
  224. package/dist/tools/read.js +488 -0
  225. package/dist/tools/recording.d.ts +45 -0
  226. package/dist/tools/recording.js +124 -0
  227. package/dist/tools/response.d.ts +129 -0
  228. package/dist/tools/response.js +5 -0
  229. package/dist/tools/scrape.d.ts +24 -0
  230. package/dist/tools/scrape.js +140 -0
  231. package/dist/tools/screenshot.d.ts +40 -0
  232. package/dist/tools/screenshot.js +79 -0
  233. package/dist/tools/scroll.d.ts +23 -0
  234. package/dist/tools/scroll.js +196 -0
  235. package/dist/tools/select.d.ts +13 -0
  236. package/dist/tools/select.js +217 -0
  237. package/dist/tools/snapshot.d.ts +27 -0
  238. package/dist/tools/snapshot.js +99 -0
  239. package/dist/tools/storage.d.ts +18 -0
  240. package/dist/tools/storage.js +29 -0
  241. package/dist/tools/tabs.d.ts +77 -0
  242. package/dist/tools/tabs.js +342 -0
  243. package/dist/tools/thought.d.ts +16 -0
  244. package/dist/tools/thought.js +79 -0
  245. package/dist/tools/type.d.ts +65 -0
  246. package/dist/tools/type.js +1061 -0
  247. package/dist/tools/type_secret.d.ts +23 -0
  248. package/dist/tools/type_secret.js +229 -0
  249. package/dist/tools/upload.d.ts +19 -0
  250. package/dist/tools/upload.js +103 -0
  251. package/dist/tools/video_compiler.d.ts +24 -0
  252. package/dist/tools/video_compiler.js +199 -0
  253. package/dist/tools/viewport.d.ts +44 -0
  254. package/dist/tools/viewport.js +230 -0
  255. package/dist/tools/wait.d.ts +19 -0
  256. package/dist/tools/wait.js +202 -0
  257. package/dist/tools/wait_for.d.ts +23 -0
  258. package/dist/tools/wait_for.js +30 -0
  259. package/dist/tools/windows.d.ts +22 -0
  260. package/dist/tools/windows.js +57 -0
  261. package/dist/ui/index.d.ts +1 -0
  262. package/dist/ui/index.js +12 -0
  263. package/docs/DEV_MODE.md +68 -0
  264. package/docs/DOCUMENTATION.md +532 -0
  265. package/docs/PLUGINS.md +538 -0
  266. package/docs/TOOLS.md +104 -0
  267. package/docs/dev_plugin.md +310 -0
  268. package/ext-dist/background.js +467 -0
  269. package/ext-dist/content.js +3089 -0
  270. package/ext-dist/dashboard.html +583 -0
  271. package/ext-dist/hud/hud.js +73 -0
  272. package/ext-dist/hud/index.html +20 -0
  273. package/ext-dist/manifest.json +77 -0
  274. package/ext-dist/sidepanel/sidepanel.js +25 -0
  275. package/ext-dist/sidepanel.html +547 -0
  276. package/ext-dist/styles.css +218 -0
  277. package/icon.png +0 -0
  278. package/mcp.json +796 -0
  279. package/package.json +113 -0
  280. package/server.json +23 -0
  281. package/smithery.yaml +11 -0
@@ -0,0 +1,279 @@
1
+ /**
2
+ * delta.test.ts — Phase 1.1
3
+ * Unit tests for snapshot diffing and delta computation.
4
+ */
5
+ import { test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { storeSnapshot, computeDelta, seedRichSnapshot, computeRichDelta, clearAllSnapshots, } from "./delta.js";
8
+ test("delta: first visit returns null (no baseline)", () => {
9
+ const targetId = "tab-1";
10
+ clearAllSnapshots();
11
+ const refs = [
12
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
13
+ ];
14
+ const result = computeDelta(targetId, 'http://test.com', refs);
15
+ assert.equal(result, null, "First visit should return null");
16
+ });
17
+ test("delta: detects added elements", () => {
18
+ const targetId = "tab-2";
19
+ clearAllSnapshots();
20
+ const initial = [
21
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
22
+ ];
23
+ storeSnapshot(targetId, 'http://test.com', initial);
24
+ const updated = [
25
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
26
+ { id: "w:2", role: "link", label: "Home", fingerprint: "link:home:nav:1" },
27
+ ];
28
+ const result = computeDelta(targetId, 'http://test.com', updated);
29
+ assert.ok(result !== null);
30
+ assert.equal(result?.added.length, 1, "One element added");
31
+ assert.equal(result?.added[0]?.id, "w:2");
32
+ assert.equal(result?.removed.length, 0);
33
+ assert.equal(result?.changed.length, 0);
34
+ });
35
+ test("delta: detects removed elements", () => {
36
+ const targetId = "tab-3";
37
+ clearAllSnapshots();
38
+ const initial = [
39
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
40
+ { id: "w:2", role: "link", label: "Home", fingerprint: "link:home:nav:1" },
41
+ ];
42
+ storeSnapshot(targetId, 'http://test.com', initial);
43
+ const updated = [
44
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
45
+ ];
46
+ const result = computeDelta(targetId, 'http://test.com', updated);
47
+ assert.ok(result !== null);
48
+ assert.equal(result?.added.length, 0);
49
+ assert.equal(result?.removed.length, 1, "One element removed");
50
+ assert.equal(result?.removed[0], "w:2");
51
+ assert.equal(result?.changed.length, 0);
52
+ });
53
+ test("delta: detects changed elements (label)", () => {
54
+ const targetId = "tab-4";
55
+ clearAllSnapshots();
56
+ const initial = [
57
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
58
+ ];
59
+ storeSnapshot(targetId, 'http://test.com', initial);
60
+ const updated = [
61
+ { id: "w:1", role: "button", label: "Cancel", fingerprint: "button:cancel:div:0" },
62
+ ];
63
+ const result = computeDelta(targetId, 'http://test.com', updated);
64
+ assert.ok(result !== null);
65
+ assert.equal(result?.added.length, 0);
66
+ assert.equal(result?.removed.length, 0);
67
+ assert.equal(result?.changed.length, 1, "One element changed");
68
+ assert.equal(result?.changed[0]?.id, "w:1");
69
+ assert.equal(result?.changed[0]?.label, "Cancel");
70
+ });
71
+ test("delta: detects changed elements (disabled)", () => {
72
+ const targetId = "tab-5";
73
+ clearAllSnapshots();
74
+ const initial = [
75
+ { id: "w:1", role: "button", label: "Submit", disabled: false, fingerprint: "button:submit:div:0" },
76
+ ];
77
+ storeSnapshot(targetId, 'http://test.com', initial);
78
+ const updated = [
79
+ { id: "w:1", role: "button", label: "Submit", disabled: true, fingerprint: "button:submit:div:0" },
80
+ ];
81
+ const result = computeDelta(targetId, 'http://test.com', updated);
82
+ assert.ok(result !== null);
83
+ assert.equal(result?.changed.length, 1, "Disabled state change detected");
84
+ assert.equal(result?.changed[0]?.disabled, true);
85
+ });
86
+ test("delta: detects changed elements (value)", () => {
87
+ const targetId = "tab-6";
88
+ clearAllSnapshots();
89
+ const initial = [
90
+ { id: "w:1", role: "textbox", label: "Email", value: "", fingerprint: "textbox:email:form:0" },
91
+ ];
92
+ storeSnapshot(targetId, 'http://test.com', initial);
93
+ const updated = [
94
+ { id: "w:1", role: "textbox", label: "Email", value: "test@example.com", fingerprint: "textbox:email:form:0" },
95
+ ];
96
+ const result = computeDelta(targetId, 'http://test.com', updated);
97
+ assert.ok(result !== null);
98
+ assert.equal(result?.changed.length, 1, "Value change detected");
99
+ assert.equal(result?.changed[0]?.value, "test@example.com");
100
+ });
101
+ test("delta: counts stable elements", () => {
102
+ const targetId = "tab-7";
103
+ clearAllSnapshots();
104
+ const initial = [
105
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
106
+ { id: "w:2", role: "link", label: "Home", fingerprint: "link:home:nav:1" },
107
+ { id: "w:3", role: "textbox", label: "Email", fingerprint: "textbox:email:form:0" },
108
+ ];
109
+ storeSnapshot(targetId, 'http://test.com', initial);
110
+ const updated = [
111
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
112
+ { id: "w:2", role: "link", label: "Home", fingerprint: "link:home:nav:1" },
113
+ { id: "w:3", role: "textbox", label: "Email", value: "test@example.com", fingerprint: "textbox:email:form:0" },
114
+ ];
115
+ const result = computeDelta(targetId, 'http://test.com', updated);
116
+ assert.ok(result !== null);
117
+ assert.equal(result?.stable_count, 2, "Two stable elements");
118
+ assert.equal(result?.changed.length, 1);
119
+ });
120
+ test("delta: updates stored snapshot", () => {
121
+ const targetId = "tab-8";
122
+ clearAllSnapshots();
123
+ const initial = [
124
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
125
+ ];
126
+ storeSnapshot(targetId, 'http://test.com', initial);
127
+ const updated = [
128
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
129
+ { id: "w:2", role: "link", label: "Home", fingerprint: "link:home:nav:1" },
130
+ ];
131
+ computeDelta(targetId, 'http://test.com', updated);
132
+ // Second call should see no changes since snapshot was updated
133
+ const secondCall = computeDelta(targetId, 'http://test.com', updated);
134
+ assert.ok(secondCall !== null);
135
+ assert.equal(secondCall?.added.length, 0);
136
+ assert.equal(secondCall?.removed.length, 0);
137
+ assert.equal(secondCall?.changed.length, 0);
138
+ assert.equal(secondCall?.stable_count, 2);
139
+ });
140
+ test("delta: rich delta - first visit returns null", async () => {
141
+ const targetId = "tab-9";
142
+ clearAllSnapshots();
143
+ const elements = [
144
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0", group: "utility", depth: 0 },
145
+ ];
146
+ const result = await computeRichDelta(targetId, 'http://test.com', elements);
147
+ assert.equal(result, null, "First visit rich delta returns null");
148
+ });
149
+ test("delta: rich delta - basic changed properties", async () => {
150
+ const targetId = "tab-10";
151
+ clearAllSnapshots();
152
+ const initial = [
153
+ {
154
+ id: "w:1",
155
+ role: "button",
156
+ label: "Submit",
157
+ value: "",
158
+ disabled: false,
159
+ fingerprint: "button:submit:div:0",
160
+ group: "utility",
161
+ depth: 0
162
+ },
163
+ ];
164
+ // Seed the snapshot first
165
+ seedRichSnapshot(targetId, 'http://test.com', initial);
166
+ const updated = [
167
+ {
168
+ id: "w:1",
169
+ role: "button",
170
+ label: "Submit",
171
+ value: "",
172
+ disabled: true,
173
+ fingerprint: "button:submit:div:0",
174
+ group: "utility",
175
+ depth: 0
176
+ },
177
+ ];
178
+ const result = await computeRichDelta(targetId, 'http://test.com', updated);
179
+ assert.ok(result !== null);
180
+ assert.ok(!("delta_timeout" in result));
181
+ const diff = result;
182
+ assert.equal(diff.changed.length, 1);
183
+ assert.equal(diff.changed[0]?.ref, "w:1");
184
+ assert.ok("disabled" in diff.changed[0]?.changes);
185
+ assert.equal(diff.changed[0]?.changes?.disabled?.from, "false");
186
+ assert.equal(diff.changed[0]?.changes?.disabled?.to, "true");
187
+ });
188
+ test("delta: rich delta - marks interactive elements as actionable", async () => {
189
+ const targetId = "tab-11";
190
+ clearAllSnapshots();
191
+ const initial = [
192
+ {
193
+ id: "w:1",
194
+ role: "button",
195
+ label: "Submit",
196
+ fingerprint: "button:submit:div:0",
197
+ group: "utility",
198
+ depth: 0
199
+ },
200
+ ];
201
+ // Seed the snapshot first
202
+ seedRichSnapshot(targetId, 'http://test.com', initial);
203
+ const updated = [
204
+ {
205
+ id: "w:1",
206
+ role: "button",
207
+ label: "Submit",
208
+ value: "",
209
+ disabled: false, // Changed from undefined/missing to false
210
+ fingerprint: "button:submit:div:0",
211
+ group: "utility",
212
+ depth: 0
213
+ },
214
+ ];
215
+ const result = await computeRichDelta(targetId, 'http://test.com', updated);
216
+ assert.ok(result !== null);
217
+ assert.ok(!("delta_timeout" in result));
218
+ const diff = result;
219
+ assert.equal(diff.changed.length, 1);
220
+ assert.equal(diff.changed[0]?.actionable, true);
221
+ assert.equal(diff.changed[0]?.recommended_action, "click");
222
+ });
223
+ test("delta: rich delta - iframe tracking", async () => {
224
+ const targetId = "tab-12";
225
+ clearAllSnapshots();
226
+ const initial = [
227
+ {
228
+ id: "w:1",
229
+ role: "button",
230
+ label: "Submit",
231
+ frameUrl: "https://example.com",
232
+ fingerprint: "button:submit:div:0",
233
+ group: "utility",
234
+ depth: 0
235
+ },
236
+ ];
237
+ // Seed the snapshot first
238
+ seedRichSnapshot(targetId, 'http://test.com', initial);
239
+ const updated = [
240
+ {
241
+ id: "w:1",
242
+ role: "button",
243
+ label: "Submit",
244
+ frameUrl: "https://example.com",
245
+ fingerprint: "button:submit:div:0",
246
+ group: "utility",
247
+ depth: 0
248
+ },
249
+ {
250
+ id: "w:2",
251
+ role: "link",
252
+ label: "Home",
253
+ frameUrl: "https://iframe.com",
254
+ fingerprint: "link:home:nav:1",
255
+ group: "navigation",
256
+ depth: 1
257
+ },
258
+ ];
259
+ const result = await computeRichDelta(targetId, 'http://test.com', updated);
260
+ assert.ok(result !== null);
261
+ assert.ok(!("delta_timeout" in result));
262
+ const diff = result;
263
+ assert.ok(diff.iframes !== undefined);
264
+ assert.equal(diff.iframes?.length, 1);
265
+ assert.equal(diff.iframes?.[0]?.status, "added");
266
+ assert.equal(diff.iframes?.[0]?.frameUrl, "https://iframe.com");
267
+ });
268
+ test("delta: clearSnapshot removes all data", () => {
269
+ const targetId = "tab-13";
270
+ const refs = [
271
+ { id: "w:1", role: "button", label: "Submit", fingerprint: "button:submit:div:0" },
272
+ ];
273
+ storeSnapshot(targetId, 'http://test.com', refs);
274
+ seedRichSnapshot(targetId, 'http://test.com', []);
275
+ clearAllSnapshots();
276
+ const result = computeDelta(targetId, 'http://test.com', refs);
277
+ assert.equal(result, null, "Cache cleared");
278
+ });
279
+ //# sourceMappingURL=delta.test.js.map
@@ -0,0 +1,15 @@
1
+ import CDP from "chrome-remote-interface";
2
+ interface GroupEntry {
3
+ groupName: string;
4
+ tabIds: string[];
5
+ }
6
+ interface GroupStore {
7
+ [windowId: number]: GroupEntry[];
8
+ }
9
+ export declare function saveGroup(windowId: number, groupName: string, tabIds: string[]): void;
10
+ export declare function removeTabFromGroups(tabId: string): void;
11
+ export declare function clearStore(): void;
12
+ export declare function getStore(): GroupStore;
13
+ export declare function restoreGroups(session: CDP.Client): Promise<void>;
14
+ export {};
15
+ //# sourceMappingURL=groups.d.ts.map
@@ -0,0 +1,80 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ const GROUPS_DIR = path.join(os.homedir(), ".Weavetab-system", "groups");
5
+ const GROUPS_FILE = path.join(GROUPS_DIR, "groups.json");
6
+ function ensureDir() {
7
+ if (!fs.existsSync(GROUPS_DIR)) {
8
+ fs.mkdirSync(GROUPS_DIR, { recursive: true });
9
+ }
10
+ }
11
+ function readStore() {
12
+ try {
13
+ if (fs.existsSync(GROUPS_FILE)) {
14
+ return JSON.parse(fs.readFileSync(GROUPS_FILE, "utf-8"));
15
+ }
16
+ }
17
+ catch {
18
+ // corrupted — reset
19
+ }
20
+ return {};
21
+ }
22
+ function writeStore(store) {
23
+ ensureDir();
24
+ fs.writeFileSync(GROUPS_FILE, JSON.stringify(store), "utf-8");
25
+ }
26
+ export function saveGroup(windowId, groupName, tabIds) {
27
+ const store = readStore();
28
+ if (!store[windowId])
29
+ store[windowId] = [];
30
+ const existing = store[windowId].find(g => g.groupName === groupName);
31
+ if (existing) {
32
+ existing.tabIds = tabIds;
33
+ }
34
+ else {
35
+ store[windowId].push({ groupName, tabIds });
36
+ }
37
+ writeStore(store);
38
+ }
39
+ export function removeTabFromGroups(tabId) {
40
+ const store = readStore();
41
+ let changed = false;
42
+ for (const windowId of Object.keys(store)) {
43
+ const groups = store[Number(windowId)];
44
+ for (const group of groups) {
45
+ const idx = group.tabIds.indexOf(tabId);
46
+ if (idx !== -1) {
47
+ group.tabIds.splice(idx, 1);
48
+ changed = true;
49
+ }
50
+ }
51
+ store[Number(windowId)] = groups.filter(g => g.tabIds.length > 0);
52
+ }
53
+ if (changed)
54
+ writeStore(store);
55
+ }
56
+ export function clearStore() {
57
+ try {
58
+ if (fs.existsSync(GROUPS_FILE))
59
+ fs.unlinkSync(GROUPS_FILE);
60
+ }
61
+ catch { /* ignore */ }
62
+ }
63
+ export function getStore() {
64
+ return readStore();
65
+ }
66
+ export async function restoreGroups(session) {
67
+ const store = readStore();
68
+ const entries = Object.entries(store).flatMap(([windowId, groups]) => groups.map(g => ({ windowId: Number(windowId), groupName: g.groupName, tabIds: g.tabIds })));
69
+ if (entries.length === 0)
70
+ return;
71
+ try {
72
+ const { sendToExtension } = await import("../cdp/helpers.js");
73
+ await sendToExtension(session, {
74
+ type: "Weavetab_RESTORE_GROUPS",
75
+ groups: entries,
76
+ });
77
+ }
78
+ catch { /* best-effort */ }
79
+ }
80
+ //# sourceMappingURL=groups.js.map
@@ -0,0 +1,30 @@
1
+ import { BrowserClient } from "../cdp/connector.js";
2
+ export declare class SessionRegistry {
3
+ private sessions;
4
+ private activeTargetIdByAgent;
5
+ private onNewTabCallbacks;
6
+ private heartbeatTimestamps;
7
+ private heartbeatIntervals;
8
+ private browserContextsByAgent;
9
+ constructor();
10
+ private loadPersistence;
11
+ private savePersistence;
12
+ getBrowserContextIdForAgent(agentId: string): string | undefined;
13
+ getAgentForBrowserContextId(contextId: string): string | undefined;
14
+ setBrowserContextIdForAgent(agentId: string, contextId: string): void;
15
+ getActive(agentId?: string): BrowserClient | null;
16
+ getMostRecent(agentId?: string): BrowserClient | null;
17
+ setActive(targetId: string, agentId: string): void;
18
+ getAll(): BrowserClient[];
19
+ getAllForAgent(agentId: string): BrowserClient[];
20
+ register(targetId: string, client: BrowserClient): void;
21
+ unregister(targetId: string): void;
22
+ getHealth(targetId: string): "alive" | "dead" | "unreachable";
23
+ beatHeartbeat(targetId: string): void;
24
+ private startHeartbeat;
25
+ private stopHeartbeat;
26
+ onNewTab(callback: (targetId: string) => void): void;
27
+ offNewTab(callback: (targetId: string) => void): void;
28
+ notifyNewTab(targetId: string): void;
29
+ }
30
+ export declare const sessionRegistry: SessionRegistry;
@@ -0,0 +1,155 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getGlobalSystemDir } from "../config/loader.js";
4
+ const HEARTBEAT_INTERVAL_MS = 5000;
5
+ const HEARTBEAT_MISSED_LIMIT = 3;
6
+ const HEARTBEAT_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * HEARTBEAT_MISSED_LIMIT;
7
+ const REGISTRY_FILE = path.join(getGlobalSystemDir("registry"), "contexts.json");
8
+ export class SessionRegistry {
9
+ sessions = new Map();
10
+ activeTargetIdByAgent = new Map(); // agentId -> targetId
11
+ onNewTabCallbacks = [];
12
+ heartbeatTimestamps = new Map();
13
+ heartbeatIntervals = new Map();
14
+ browserContextsByAgent = new Map(); // agentId -> browserContextId
15
+ constructor() {
16
+ this.loadPersistence();
17
+ }
18
+ loadPersistence() {
19
+ try {
20
+ if (fs.existsSync(REGISTRY_FILE)) {
21
+ const data = JSON.parse(fs.readFileSync(REGISTRY_FILE, "utf-8"));
22
+ for (const [agentId, contextId] of Object.entries(data)) {
23
+ this.browserContextsByAgent.set(agentId, contextId);
24
+ }
25
+ }
26
+ }
27
+ catch { /* non-fatal */ }
28
+ }
29
+ savePersistence() {
30
+ try {
31
+ const data = {};
32
+ for (const [agentId, contextId] of this.browserContextsByAgent.entries()) {
33
+ data[agentId] = contextId;
34
+ }
35
+ fs.writeFileSync(REGISTRY_FILE, JSON.stringify(data, null, 2), "utf-8");
36
+ }
37
+ catch { /* non-fatal */ }
38
+ }
39
+ getBrowserContextIdForAgent(agentId) {
40
+ return this.browserContextsByAgent.get(agentId);
41
+ }
42
+ getAgentForBrowserContextId(contextId) {
43
+ for (const [agentId, cid] of this.browserContextsByAgent.entries()) {
44
+ if (cid === contextId)
45
+ return agentId;
46
+ }
47
+ return undefined;
48
+ }
49
+ setBrowserContextIdForAgent(agentId, contextId) {
50
+ this.browserContextsByAgent.set(agentId, contextId);
51
+ this.savePersistence();
52
+ }
53
+ getActive(agentId) {
54
+ if (!agentId)
55
+ return null;
56
+ const targetId = this.activeTargetIdByAgent.get(agentId);
57
+ if (!targetId)
58
+ return null;
59
+ return this.sessions.get(targetId) || null;
60
+ }
61
+ getMostRecent(agentId) {
62
+ const values = Array.from(this.sessions.values()).filter(c => c.agentId === agentId);
63
+ return values.length > 0 ? values[values.length - 1] : null;
64
+ }
65
+ setActive(targetId, agentId) {
66
+ if (this.sessions.has(targetId)) {
67
+ this.activeTargetIdByAgent.set(agentId, targetId);
68
+ }
69
+ }
70
+ getAll() {
71
+ return Array.from(this.sessions.values());
72
+ }
73
+ getAllForAgent(agentId) {
74
+ return Array.from(this.sessions.values()).filter(c => c.agentId === agentId);
75
+ }
76
+ register(targetId, client) {
77
+ this.sessions.set(targetId, client);
78
+ this.heartbeatTimestamps.set(targetId, Date.now());
79
+ this.startHeartbeat(targetId, client.session);
80
+ if (!this.activeTargetIdByAgent.has(client.agentId)) {
81
+ this.activeTargetIdByAgent.set(client.agentId, targetId);
82
+ }
83
+ }
84
+ unregister(targetId) {
85
+ this.stopHeartbeat(targetId);
86
+ const client = this.sessions.get(targetId);
87
+ if (client) {
88
+ client.session.close().catch(() => { });
89
+ this.sessions.delete(targetId);
90
+ const activeForAgent = this.activeTargetIdByAgent.get(client.agentId);
91
+ if (activeForAgent === targetId) {
92
+ const agentTabs = Array.from(this.sessions.values()).filter(c => c.agentId === client.agentId);
93
+ if (agentTabs.length > 0) {
94
+ this.activeTargetIdByAgent.set(client.agentId, agentTabs[agentTabs.length - 1].targetId);
95
+ }
96
+ else {
97
+ this.activeTargetIdByAgent.delete(client.agentId);
98
+ }
99
+ }
100
+ }
101
+ this.heartbeatTimestamps.delete(targetId);
102
+ }
103
+ getHealth(targetId) {
104
+ if (!this.sessions.has(targetId))
105
+ return "unreachable";
106
+ const lastBeat = this.heartbeatTimestamps.get(targetId);
107
+ if (!lastBeat)
108
+ return "unreachable";
109
+ const elapsed = Date.now() - lastBeat;
110
+ if (elapsed > HEARTBEAT_TIMEOUT_MS)
111
+ return "dead";
112
+ return "alive";
113
+ }
114
+ beatHeartbeat(targetId) {
115
+ this.heartbeatTimestamps.set(targetId, Date.now());
116
+ }
117
+ startHeartbeat(targetId, session) {
118
+ if (this.heartbeatIntervals.has(targetId))
119
+ return;
120
+ const interval = setInterval(async () => {
121
+ try {
122
+ const { result } = await session.Runtime.evaluate({
123
+ expression: `document.readyState`,
124
+ returnByValue: true,
125
+ });
126
+ if (result?.value) {
127
+ this.beatHeartbeat(targetId);
128
+ }
129
+ }
130
+ catch {
131
+ // heartbeat failed — tab may be dead, will be caught by getHealth()
132
+ }
133
+ }, HEARTBEAT_INTERVAL_MS);
134
+ this.heartbeatIntervals.set(targetId, interval);
135
+ }
136
+ stopHeartbeat(targetId) {
137
+ const interval = this.heartbeatIntervals.get(targetId);
138
+ if (interval) {
139
+ clearInterval(interval);
140
+ this.heartbeatIntervals.delete(targetId);
141
+ }
142
+ }
143
+ onNewTab(callback) {
144
+ this.onNewTabCallbacks.push(callback);
145
+ }
146
+ offNewTab(callback) {
147
+ this.onNewTabCallbacks = this.onNewTabCallbacks.filter(cb => cb !== callback);
148
+ }
149
+ notifyNewTab(targetId) {
150
+ for (const cb of this.onNewTabCallbacks) {
151
+ cb(targetId);
152
+ }
153
+ }
154
+ }
155
+ export const sessionRegistry = new SessionRegistry();
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Weavetab — tools/accessibility.ts
3
+ * browser_accessibility: returns the full Chrome accessibility tree via Accessibility.getFullAXTree.
4
+ *
5
+ * Use this to understand ARIA-heavy UIs when the standard DOM map is insufficient.
6
+ * Weavetab by fy2ne
7
+ */
8
+ import CDP from "chrome-remote-interface";
9
+ import type { Config } from "../config/loader.js";
10
+ export interface AccessibilityResult {
11
+ success: boolean;
12
+ tree: any;
13
+ }
14
+ export declare function browserAccessibility(session: CDP.Client, _config: Config): Promise<AccessibilityResult>;
@@ -0,0 +1,32 @@
1
+ export async function browserAccessibility(session, _config) {
2
+ try {
3
+ // Enable Accessibility domain
4
+ await session.Accessibility.enable();
5
+ const tree = await session.Accessibility.getFullAXTree();
6
+ // Simplify tree for LLM consumption
7
+ const simplify = (node) => {
8
+ if (node.ignored)
9
+ return null;
10
+ const role = node.role?.value;
11
+ const name = node.name?.value;
12
+ const value = node.value?.value;
13
+ const children = (node.children || []).map(simplify).filter(Boolean);
14
+ if (!name && children.length === 0 && !value)
15
+ return null; // skip noise
16
+ return {
17
+ role,
18
+ name: name || undefined,
19
+ value: value || undefined,
20
+ nodeId: node.nodeId,
21
+ backendDOMNodeId: node.backendDOMNodeId,
22
+ children: children.length > 0 ? children : undefined
23
+ };
24
+ };
25
+ const root = tree.nodes.find((n) => !n.parentId);
26
+ const simplifiedTree = simplify(root);
27
+ return { success: true, tree: simplifiedTree };
28
+ }
29
+ catch (error) {
30
+ throw new Error(`Failed to get accessibility tree: ${error}`);
31
+ }
32
+ }
@@ -0,0 +1,14 @@
1
+ import CDP from "chrome-remote-interface";
2
+ export interface AskResult {
3
+ success: true;
4
+ response: string;
5
+ timeout?: boolean;
6
+ }
7
+ export declare function browserAsk(session: CDP.Client, args: {
8
+ question: string;
9
+ }): Promise<AskResult | {
10
+ success: false;
11
+ error_code: string;
12
+ message: string;
13
+ }>;
14
+ //# sourceMappingURL=ask.d.ts.map
@@ -0,0 +1,49 @@
1
+ // src/tools/ask.ts
2
+ // browser_ask — renders ask UI in content.js via sendToExtension, polls result
3
+ import { logAction } from "../audit/logger.js";
4
+ import { sendToExtension } from "../cdp/helpers.js";
5
+ function sleep(ms) {
6
+ return new Promise(r => setTimeout(r, ms));
7
+ }
8
+ export async function browserAsk(session, args) {
9
+ const { question } = args;
10
+ const ASK_TIMEOUT_MS = 300000;
11
+ const COUNTDOWN_SEC = 300;
12
+ logAction("browser_ask", "prompting", question);
13
+ // Show HUD hint + waiting state
14
+ await sendToExtension(session, { type: 'Weavetab_STATE', state: 'waiting', hud: { action: question.slice(0, 40) + (question.length > 40 ? '...' : '') } });
15
+ // Show ask UI via content.js
16
+ await sendToExtension(session, { type: 'Weavetab_ASK', question }).catch(() => { });
17
+ // Poll for result via DOM bridge (shared between isolated & main worlds)
18
+ const start = Date.now();
19
+ while (Date.now() - start < ASK_TIMEOUT_MS) {
20
+ await sleep(500);
21
+ try {
22
+ const { result } = await session.Runtime.evaluate({
23
+ expression: `(() => { try {
24
+ var el = document.getElementById('__wt_ask_bridge');
25
+ if (!el || el.dataset.status === 'pending') return null;
26
+ return JSON.stringify({ status: el.dataset.status, result: el.dataset.result });
27
+ } catch(e) { return null; } })()`,
28
+ returnByValue: true,
29
+ });
30
+ if (result?.value) {
31
+ const data = JSON.parse(result.value);
32
+ if (data.status === 'submitted') {
33
+ logAction("browser_ask", "answered", data.result);
34
+ return { success: true, response: data.result };
35
+ }
36
+ if (data.status === 'cancelled' || data.status === 'timeout') {
37
+ logAction("browser_ask", data.status, "");
38
+ return { success: true, response: "", timeout: true };
39
+ }
40
+ }
41
+ }
42
+ catch {
43
+ // polling error — retry
44
+ }
45
+ }
46
+ logAction("browser_ask", "timeout", "");
47
+ return { success: true, response: "", timeout: true };
48
+ }
49
+ //# sourceMappingURL=ask.js.map