@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
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Weavetab — tools/checkpoint.ts
3
+ * browser_checkpoint: Session time-travel and rollback.
4
+ *
5
+ * Captures and restores named checkpoints of browser state:
6
+ * - Cookies (HttpOnly and Secure via CDP Network)
7
+ * - LocalStorage & SessionStorage
8
+ * - URL, page title, and viewport scroll position
9
+ * - Optional lightweight DOM snapshot
10
+ *
11
+ * Saves tokens and time when trial-and-error automation branches fail.
12
+ *
13
+ * Weavetab by fy2ne
14
+ */
15
+ import { logAction } from "../audit/logger.js";
16
+ import { recordAction } from "../intelligence/trail.js";
17
+ import { clearTabCache } from "../state/cache.js";
18
+ import { sessionRegistry } from "../state/session.js";
19
+ import { getConfigAgentId } from "../config/loader.js";
20
+ // In-memory checkpoint registry keyed by targetId -> name -> CheckpointData
21
+ const checkpointRegistry = new Map();
22
+ export function getCheckpointsForTarget(targetId) {
23
+ let map = checkpointRegistry.get(targetId);
24
+ if (!map) {
25
+ map = new Map();
26
+ checkpointRegistry.set(targetId, map);
27
+ }
28
+ return map;
29
+ }
30
+ export function clearAllCheckpoints() {
31
+ checkpointRegistry.clear();
32
+ }
33
+ export async function browserCheckpoint(session, args, config) {
34
+ let targetId = "default";
35
+ const active = sessionRegistry.getActive(getConfigAgentId(config));
36
+ if (active)
37
+ targetId = active.targetId;
38
+ const targetCheckpoints = getCheckpointsForTarget(targetId);
39
+ // ── ACTION: LIST ─────────────────────────────────────────────────────────────
40
+ if (args.action === "list") {
41
+ const list = Array.from(targetCheckpoints.values()).map((cp) => ({
42
+ name: cp.name,
43
+ url: cp.url,
44
+ title: cp.title,
45
+ timestamp: cp.timestamp,
46
+ cookiesCount: cp.cookies.length,
47
+ localStorageKeys: Object.keys(cp.localStorage).length,
48
+ sessionStorageKeys: Object.keys(cp.sessionStorage).length,
49
+ hasDomSnapshot: !!cp.domSnapshot,
50
+ }));
51
+ return {
52
+ success: true,
53
+ action: "list",
54
+ count: list.length,
55
+ checkpoints: list,
56
+ };
57
+ }
58
+ // ── ACTION: DELETE ───────────────────────────────────────────────────────────
59
+ if (args.action === "delete") {
60
+ if (!args.name) {
61
+ return {
62
+ success: false,
63
+ error_code: "INVALID_ARGS",
64
+ message: "Checkpoint 'name' is required for action 'delete'.",
65
+ };
66
+ }
67
+ const existed = targetCheckpoints.delete(args.name);
68
+ return {
69
+ success: existed,
70
+ action: "delete",
71
+ name: args.name,
72
+ message: existed
73
+ ? `Checkpoint '${args.name}' deleted.`
74
+ : `Checkpoint '${args.name}' not found.`,
75
+ };
76
+ }
77
+ // ── ACTION: SAVE ─────────────────────────────────────────────────────────────
78
+ if (args.action === "save") {
79
+ if (!args.name) {
80
+ return {
81
+ success: false,
82
+ error_code: "INVALID_ARGS",
83
+ message: "Checkpoint 'name' is required for action 'save'.",
84
+ };
85
+ }
86
+ const { Page, Network, Runtime } = session;
87
+ const { frameTree } = await Page.getFrameTree();
88
+ const currentUrl = frameTree.frame.url;
89
+ // 1. Snapshot cookies
90
+ let cookies = [];
91
+ try {
92
+ if (typeof Network.getCookies === "function") {
93
+ const res = await Network.getCookies({ urls: [currentUrl] });
94
+ cookies = res.cookies || [];
95
+ }
96
+ else if (typeof Network.getAllCookies === "function") {
97
+ const res = await Network.getAllCookies();
98
+ cookies = res.cookies || [];
99
+ }
100
+ }
101
+ catch {
102
+ // Cookies best-effort
103
+ }
104
+ // 2. Snapshot LocalStorage, SessionStorage, Scroll, and Title
105
+ let storageData = {
106
+ localStorage: {},
107
+ sessionStorage: {},
108
+ scroll: { x: 0, y: 0 },
109
+ title: "",
110
+ };
111
+ try {
112
+ const evalRes = await Runtime.evaluate({
113
+ expression: `(function() {
114
+ var ls = {};
115
+ try {
116
+ for (var i = 0; i < localStorage.length; i++) {
117
+ var k = localStorage.key(i);
118
+ if (k) ls[k] = localStorage.getItem(k);
119
+ }
120
+ } catch(e) {}
121
+ var ss = {};
122
+ try {
123
+ for (var j = 0; j < sessionStorage.length; j++) {
124
+ var sk = sessionStorage.key(j);
125
+ if (sk) ss[sk] = sessionStorage.getItem(sk);
126
+ }
127
+ } catch(e) {}
128
+ return {
129
+ localStorage: ls,
130
+ sessionStorage: ss,
131
+ scroll: { x: window.scrollX || 0, y: window.scrollY || 0 },
132
+ title: document.title || ""
133
+ };
134
+ })()`,
135
+ returnByValue: true,
136
+ });
137
+ if (evalRes?.result?.value) {
138
+ storageData = evalRes.result.value;
139
+ }
140
+ }
141
+ catch {
142
+ // Runtime best-effort
143
+ }
144
+ // 3. Optional lightweight DOM capture
145
+ let domSnapshot;
146
+ if (args.includeDom) {
147
+ try {
148
+ const domRes = await Runtime.evaluate({
149
+ expression: `document.documentElement.outerHTML.slice(0, 100000)`,
150
+ returnByValue: true,
151
+ });
152
+ domSnapshot = domRes?.result?.value;
153
+ }
154
+ catch {
155
+ // ignore
156
+ }
157
+ }
158
+ const checkpoint = {
159
+ name: args.name,
160
+ url: currentUrl,
161
+ title: storageData.title,
162
+ timestamp: new Date().toISOString(),
163
+ cookies,
164
+ localStorage: storageData.localStorage,
165
+ sessionStorage: storageData.sessionStorage,
166
+ scroll: storageData.scroll,
167
+ domSnapshot,
168
+ };
169
+ targetCheckpoints.set(args.name, checkpoint);
170
+ logAction("browser_checkpoint", args.name, `Saved checkpoint at ${currentUrl}`);
171
+ recordAction({
172
+ timestamp: checkpoint.timestamp,
173
+ tool: "browser_checkpoint",
174
+ input: { action: "save", name: args.name, url: currentUrl },
175
+ result: { success: true },
176
+ });
177
+ return {
178
+ success: true,
179
+ action: "save",
180
+ name: args.name,
181
+ url: currentUrl,
182
+ title: storageData.title,
183
+ timestamp: checkpoint.timestamp,
184
+ cookiesCount: cookies.length,
185
+ localStorageKeysCount: Object.keys(storageData.localStorage).length,
186
+ sessionStorageKeysCount: Object.keys(storageData.sessionStorage).length,
187
+ };
188
+ }
189
+ // ── ACTION: RESTORE ──────────────────────────────────────────────────────────
190
+ if (args.action === "restore") {
191
+ if (!args.name) {
192
+ return {
193
+ success: false,
194
+ error_code: "INVALID_ARGS",
195
+ message: "Checkpoint 'name' is required for action 'restore'.",
196
+ };
197
+ }
198
+ const checkpoint = targetCheckpoints.get(args.name);
199
+ if (!checkpoint) {
200
+ return {
201
+ success: false,
202
+ error_code: "CHECKPOINT_NOT_FOUND",
203
+ message: `No checkpoint found with name '${args.name}'. Use action: 'list' to see available checkpoints.`,
204
+ };
205
+ }
206
+ const { Page, Network, Runtime } = session;
207
+ const { frameTree } = await Page.getFrameTree();
208
+ const currentUrl = frameTree.frame.url;
209
+ // 1. Navigate to checkpoint URL if different
210
+ if (currentUrl !== checkpoint.url) {
211
+ await Page.navigate({ url: checkpoint.url });
212
+ await Page.loadEventFired().catch(() => { });
213
+ await new Promise((r) => setTimeout(r, 400));
214
+ }
215
+ // 2. Restore Cookies
216
+ if (checkpoint.cookies && checkpoint.cookies.length > 0) {
217
+ try {
218
+ if (typeof Network.setCookies === "function") {
219
+ await Network.setCookies({ cookies: checkpoint.cookies });
220
+ }
221
+ }
222
+ catch {
223
+ // Cookies best-effort
224
+ }
225
+ }
226
+ // 3. Restore LocalStorage, SessionStorage, and Scroll position
227
+ try {
228
+ await Runtime.evaluate({
229
+ expression: `(function(ls, ss, scroll) {
230
+ try {
231
+ localStorage.clear();
232
+ for (var k in ls) { localStorage.setItem(k, ls[k]); }
233
+ } catch(e) {}
234
+ try {
235
+ sessionStorage.clear();
236
+ for (var sk in ss) { sessionStorage.setItem(sk, ss[sk]); }
237
+ } catch(e) {}
238
+ if (scroll) {
239
+ window.scrollTo(scroll.x, scroll.y);
240
+ }
241
+ })(${JSON.stringify(checkpoint.localStorage)}, ${JSON.stringify(checkpoint.sessionStorage)}, ${JSON.stringify(checkpoint.scroll)})`,
242
+ returnByValue: false,
243
+ });
244
+ }
245
+ catch {
246
+ // Storage best-effort
247
+ }
248
+ // Invalidate cached action map for this target so fresh refs get generated
249
+ clearTabCache(targetId);
250
+ logAction("browser_checkpoint", args.name, `Restored checkpoint to ${checkpoint.url}`);
251
+ recordAction({
252
+ timestamp: new Date().toISOString(),
253
+ tool: "browser_checkpoint",
254
+ input: { action: "restore", name: args.name, targetUrl: checkpoint.url },
255
+ result: { success: true },
256
+ });
257
+ return {
258
+ success: true,
259
+ action: "restore",
260
+ name: args.name,
261
+ url: checkpoint.url,
262
+ title: checkpoint.title,
263
+ restoredTimestamp: checkpoint.timestamp,
264
+ };
265
+ }
266
+ return {
267
+ success: false,
268
+ error_code: "INVALID_ACTION",
269
+ message: `Unsupported action '${args.action}'. Supported actions: save, restore, list, delete.`,
270
+ };
271
+ }
@@ -33,4 +33,5 @@ export declare function weaveClick(session: CDP.Client, args: {
33
33
  x?: number;
34
34
  y?: number;
35
35
  include_map?: boolean;
36
+ mark?: number;
36
37
  }, config: Config): Promise<ClickResult | WeaveError>;
@@ -1,6 +1,6 @@
1
1
  // src/tools/click.ts
2
2
  // Priority 2: Sub-10ms Click Path & Predictive Action Queue
3
- import { buildActionMap, findAllMatches, safeSelector, toMarkdownTable, resolveByAria } from "../cdp/walker.js";
3
+ import { buildActionMap, findAllMatches, safeSelector, toMarkdownTable, resolveByAria, recoverDetachedRef } from "../cdp/walker.js";
4
4
  import { logAction } from "../audit/logger.js";
5
5
  import { recordAction, getLoopDiagnostics } from "../intelligence/trail.js";
6
6
  import { waitForDOMStable } from "../sensors/guard.js";
@@ -15,6 +15,7 @@ import { sendToExtension } from "../cdp/helpers.js";
15
15
  import { labelCache } from "../cdp/label_cache.js";
16
16
  import { getBestSelectorForIntent, logMemorySuccess, logMemoryFailure } from "../intelligence/sessionMemory.js";
17
17
  import { verifyActionability, quickHitCheck } from "../sensors/actionability.js";
18
+ import { marksGet } from "../intelligence/marks.js";
18
19
  /** Converts SelectorFingerprint (sessionMemory) to the format verifyActionability expects. */
19
20
  function fpToOpts(fingerprint) {
20
21
  if (!fingerprint)
@@ -34,6 +35,36 @@ export async function weaveClick(session, args, config) {
34
35
  const startMs = Date.now();
35
36
  const clientMatch = sessionRegistry.getAll().find(c => c.session === session);
36
37
  const targetId = clientMatch?.targetId ?? session.targetId ?? "default";
38
+ // ── MARK FAST PATH: resolve by SoM mark number ─────────────────────────────
39
+ // Highest priority — zero DOM walk, straight coordinate dispatch.
40
+ if (args.mark !== undefined) {
41
+ const entry = marksGet(targetId, args.mark);
42
+ if (!entry) {
43
+ return {
44
+ success: false,
45
+ error_code: "MARK_NOT_FOUND",
46
+ message: `[!] Mark [${args.mark}] not found. Call browser_map({ visual: true }) or browser_map({ visual: "auto" }) first to generate marks.`,
47
+ };
48
+ }
49
+ // Ghost-move to coordinates, then dispatch hardware click
50
+ await ghostMove(session, entry.x, entry.y, config).catch(() => { });
51
+ await Input.dispatchMouseEvent({ type: "mousePressed", x: entry.x, y: entry.y, button: "left", clickCount: 1 });
52
+ await Input.dispatchMouseEvent({ type: "mouseReleased", x: entry.x, y: entry.y, button: "left", clickCount: 1 });
53
+ const initialUrl = (await Page.getFrameTree()).frameTree.frame.url;
54
+ logAction("browser_click", initialUrl, `mark:${args.mark} @(${entry.x},${entry.y})`);
55
+ return {
56
+ success: true,
57
+ clicked: entry.label ?? `mark:${args.mark}`,
58
+ ref: entry.ref ?? `mark:${args.mark}`,
59
+ execution_ms: Date.now() - startMs,
60
+ tab_id: targetId,
61
+ url: initialUrl,
62
+ title: "",
63
+ url_changed: false,
64
+ dom_changed: false,
65
+ stuck_warning: false,
66
+ };
67
+ }
37
68
  // Track URL before click
38
69
  const initialUrl = (await Page.getFrameTree()).frameTree.frame.url;
39
70
  // 1. Resolve Element and Selector
@@ -56,8 +87,8 @@ export async function weaveClick(session, args, config) {
56
87
  if (memHit) {
57
88
  const actCheck = await verifyActionability(session, memHit.selector, {
58
89
  fingerprint: fpToOpts(memHit.fingerprint),
59
- stabilityTimeoutMs: config.actionabilityTimeoutMs ?? 400,
60
- totalTimeoutMs: (config.actionabilityTimeoutMs ?? 400) * 3,
90
+ stabilityTimeoutMs: config.actionabilityTimeoutMs ?? 200,
91
+ totalTimeoutMs: (config.actionabilityTimeoutMs ?? 200) * 3,
61
92
  });
62
93
  if (actCheck.status === 'ACTIONABLE') {
63
94
  args.id = memHit.selector;
@@ -101,6 +132,14 @@ export async function weaveClick(session, args, config) {
101
132
  cacheActionMap(targetId, map);
102
133
  element = map.elements.find((el) => el.id === reqId);
103
134
  }
135
+ // If still not found, trigger the SPA auto-recovery heuristic before hard-failing
136
+ if (!element && reqId.startsWith("w:")) {
137
+ const recovery = await recoverDetachedRef(session, targetId, reqId);
138
+ if (recovery.recovered && recovery.element) {
139
+ element = recovery.element;
140
+ sniperWarning = recovery.warning || "";
141
+ }
142
+ }
104
143
  // Fast-fail if still not found
105
144
  if (!element && reqId.startsWith("w:")) {
106
145
  return {
@@ -210,7 +249,7 @@ export async function weaveClick(session, args, config) {
210
249
  }
211
250
  const loopDiag = getLoopDiagnostics("browser_click", element.id);
212
251
  if (loopDiag.severity === "critical") {
213
- return { success: false, error_code: "STUCK_LOOP_WARNING", message: `[!] Loop detected: You have tried to click [${element.id}] ${loopDiag.strikes} times without success. Call browser_reset_loop_counter if this is a new task.` };
252
+ return { success: false, error_code: "STUCK_LOOP_WARNING", message: `[!] Loop detected: You have tried to click [${element.id}] ${loopDiag.strikes} times without success. Run browser_map({ force: true }) if this is a new task.` };
214
253
  }
215
254
  if (loopDiag.severity === "warning") {
216
255
  logAction("browser_click", element.id, `loop warning: ${loopDiag.strikes} consecutive strikes on [${element.id}]`);
@@ -220,8 +259,8 @@ export async function weaveClick(session, args, config) {
220
259
  const selector = safeSelector(element);
221
260
  if (selector) {
222
261
  const actCheck = await verifyActionability(session, selector, {
223
- stabilityTimeoutMs: config.actionabilityTimeoutMs ?? 400,
224
- totalTimeoutMs: (config.actionabilityTimeoutMs ?? 400) * 4,
262
+ stabilityTimeoutMs: config.actionabilityTimeoutMs ?? 200,
263
+ totalTimeoutMs: (config.actionabilityTimeoutMs ?? 200) * 3,
225
264
  });
226
265
  if (actCheck.status === 'ACTIONABLE') {
227
266
  coords = { x: actCheck.x, y: actCheck.y, visible: true };
@@ -236,7 +275,20 @@ export async function weaveClick(session, args, config) {
236
275
  }
237
276
  }
238
277
  else if (actCheck.status === 'NOT_FOUND') {
239
- return { success: false, error_code: "ELEMENT_NOT_FOUND", message: `[!] Element ${selector} was in the cached map but no longer exists in the DOM. Run browser_map again to refresh refs.` };
278
+ if (element.id.startsWith("w:")) {
279
+ const recovery = await recoverDetachedRef(session, targetId, element.id, element);
280
+ if (recovery.recovered && recovery.element) {
281
+ element = recovery.element;
282
+ coords = { x: element.x, y: element.y, visible: true };
283
+ sniperWarning = recovery.warning || "";
284
+ }
285
+ else {
286
+ return { success: false, error_code: "ELEMENT_NOT_FOUND", message: `[!] Element ${selector} was in the cached map but no longer exists in the DOM. Run browser_map again to refresh refs.` };
287
+ }
288
+ }
289
+ else {
290
+ return { success: false, error_code: "ELEMENT_NOT_FOUND", message: `[!] Element ${selector} was in the cached map but no longer exists in the DOM. Run browser_map again to refresh refs.` };
291
+ }
240
292
  }
241
293
  else if (actCheck.status === 'INVISIBLE') {
242
294
  return { success: false, error_code: "ELEMENT_INVISIBLE", message: `[!] Element ${selector} has zero dimensions (hidden or collapsed). Use browser_inspect to check display/visibility, or scroll to reveal it.` };
@@ -272,15 +324,15 @@ export async function weaveClick(session, args, config) {
272
324
  async function performClick(cx, cy) {
273
325
  await ghostMove(session, cx, cy, config, element_.label);
274
326
  sendToExtension(session, { type: 'Weavetab_STATE', cursor: { x: cx, y: cy }, state: 'clicking', ripple: { cx, cy } }).catch(() => { });
275
- // ── Pre-dispatch hit-target check (Playwright Step 6) ──────────────────
276
- // Verify the coordinate is still clear right before the event fires.
277
- // This catches overlays/modals that appeared since verifyActionability ran.
327
+ // ── Pre-dispatch hit-target check (lightweight) ─────────────────────────
328
+ // verifyActionability already confirmed actionable; this is a last-chance race guard.
329
+ // We do NOT throw here — transient tooltips/overlays cause false positives.
330
+ // If genuinely blocked, the post-click stuck detection handles the fallback.
278
331
  const targetSel = safeSelector(element_) ?? "";
279
332
  if (targetSel) {
280
333
  const hitCheck = await quickHitCheck(session, cx, cy, targetSel);
281
334
  if (!hitCheck.clear) {
282
- // Raise to performClick caller so the retry loop can try another alignment
283
- throw Object.assign(new Error(hitCheck.blockerDescription ?? "hit-target blocked"), { code: "HIT_TARGET_BLOCKED" });
335
+ logAction("browser_click", targetSel, `[quickHitCheck] soft-blocked by ${hitCheck.blockerDescription ?? "unknown"} — proceeding anyway`);
284
336
  }
285
337
  }
286
338
  if (!config.headless)
@@ -299,13 +351,13 @@ export async function weaveClick(session, args, config) {
299
351
  // Check if a dialog was opened by the click immediately. If a dialog is open,
300
352
  // the click succeeded in triggering an action. Waiting for DOM stable will
301
353
  // just hang for 5-10s because Runtime.evaluate blocks on JS dialogs.
302
- await new Promise(r => setTimeout(r, 100)); // give dialog time to trigger
354
+ await new Promise(r => setTimeout(r, 30)); // give dialog time to trigger
303
355
  if (hasOpenDialog(session)) {
304
356
  return { stuck: false, url: currentUrl, newMap: null, dialog_open: true };
305
357
  }
306
358
  await Promise.all([
307
- waitForDOMStable(session, 50, 300),
308
- waitForNetworkIdle(session, 50, 200).catch(() => { })
359
+ waitForDOMStable(session, 20, 150),
360
+ waitForNetworkIdle(session, 30, 150).catch(() => { })
309
361
  ]);
310
362
  const finalUrl = (await Page.getFrameTree()).frameTree.frame.url;
311
363
  // Fast path: URL changed → definitely not stuck, skip map rebuild
@@ -445,63 +497,15 @@ export async function weaveClick(session, args, config) {
445
497
  const stuck = false;
446
498
  return { stuck, url: finalUrl, newMap };
447
499
  }
448
- let { x: clickX, y: clickY } = coords;
449
- let hitTargetBlocked = false;
450
- try {
451
- await performClick(clickX, clickY);
452
- }
453
- catch (err) {
454
- if (err?.code === "HIT_TARGET_BLOCKED") {
455
- hitTargetBlocked = true;
456
- }
457
- else {
458
- throw err;
459
- }
460
- }
500
+ const { x: clickX, y: clickY } = coords;
501
+ await performClick(clickX, clickY);
461
502
  // ── Post-click: get page state (URL, map, DOM delta) ─────────────────────
462
503
  // The 'stuck' flag is purely informational — it powers stuck_warning in the
463
- // result. A dispatched, non-blocked click is NOT retried based on DOM delta
464
- // alone, because: (a) many real interactions produce no measurable DOM delta
465
- // (static buttons, CSS-only toggles, anchor scrolls); (b) retrying doubles the
466
- // click on checkboxes/accordions, net-zeroing the primary action.
467
- let postCheck = hitTargetBlocked
468
- ? { stuck: true, url: initialUrl, newMap: null }
469
- : await isClickStuck(initialUrl);
470
- // SPA JS-click fallback: only when the first click was genuinely hit-blocked
471
- if (hitTargetBlocked && element_) {
472
- const sel = safeSelector(element_);
473
- if (sel) {
474
- try {
475
- const { result: isButton } = await session.Runtime.evaluate({
476
- expression: `(function(s) {
477
- var el = document.querySelector(s);
478
- if (!el) return false;
479
- var tag = el.tagName.toLowerCase();
480
- var role = (el.getAttribute('role') || '').toLowerCase();
481
- if (tag === 'button' || tag === 'a' || tag === 'input' || tag === 'label' || tag === 'select') return true;
482
- if (['button','link','menuitem','menuitemcheckbox','menuitemradio',
483
- 'radio','checkbox','tab','option','treeitem','gridcell','switch'].includes(role)) return true;
484
- if (el.onclick || el.getAttribute('onclick')) return true;
485
- if (el.isContentEditable || tag === 'summary') return true;
486
- return false;
487
- })(${JSON.stringify(sel)})`,
488
- returnByValue: true,
489
- }).catch(() => ({ result: { value: false } }));
490
- if (isButton?.value) {
491
- await session.Runtime.evaluate({
492
- expression: `(function(s) {
493
- var el = document.querySelector(s);
494
- if (el) { el.focus(); el.click(); }
495
- })(${JSON.stringify(sel)})`,
496
- returnByValue: false,
497
- }).catch(() => { });
498
- await new Promise(r => setTimeout(r, 300));
499
- postCheck = { stuck: false, url: initialUrl, newMap: null };
500
- }
501
- }
502
- catch { /* non-fatal */ }
503
- }
504
- }
504
+ // result. A dispatched click is NOT retried based on DOM delta alone, because:
505
+ // (a) many real interactions produce no measurable DOM delta (static buttons,
506
+ // CSS-only toggles, anchor scrolls);
507
+ // (b) retrying doubles the click on checkboxes/accordions, net-zeroing it.
508
+ const postCheck = await isClickStuck(initialUrl);
505
509
  const executionMs = Date.now() - startMs;
506
510
  const { stuck: isStuck, url: finalUrl, newMap } = postCheck;
507
511
  if (neededScroll) {
@@ -630,7 +630,7 @@ ${tailwindPath ? "4. `tailwind.theme.json` — Tailwind CSS `theme.extend` confi
630
630
  htmlBytes: beautifulHtml.length
631
631
  }
632
632
  },
633
- message: `Design extracted successfully to ${targetDir}. Saved tokens.json, template.html, styles.css, and README.md.`
633
+ message: `design saved to ${targetDir}. Saved tokens.json, template.html, styles.css, and README.md.`
634
634
  };
635
635
  }
636
636
  finally {
@@ -77,7 +77,7 @@ export async function browserEval(session, options, _config) {
77
77
  // result.value is undefined and result.description is unhelpfully a type string.
78
78
  // Return a clear message so agents know the eval succeeded — it just returned no value.
79
79
  const rawResult = result.type === 'undefined'
80
- ? "(expression ran successfully — returned undefined. To get a confirmation value, append ; 'ok' or return a string explicitly.)"
80
+ ? "(eval returned undefined. append ; 'ok' or return a string explicitly.)"
81
81
  : result.value !== undefined ? result.value : result.description;
82
82
  const redactedResult = typeof rawResult === 'string'
83
83
  ? SessionSecretTracker.redact(targetId, rawResult)
@@ -1,7 +1,12 @@
1
1
  import CDP from "chrome-remote-interface";
2
2
  import type { Config } from "../config/loader.js";
3
3
  export interface FillField {
4
- ref: string;
4
+ ref?: string;
5
+ selector?: string;
6
+ target?: string;
7
+ name?: string;
8
+ label?: string;
9
+ placeholder?: string;
5
10
  value: string;
6
11
  }
7
12
  /** A single cell write for Google Sheets / Canvas grid filling. */
@@ -15,6 +20,8 @@ export interface FillOptions {
15
20
  fields?: FillField[];
16
21
  submit?: string;
17
22
  scan_only?: boolean;
23
+ scan?: boolean;
24
+ inputs?: boolean;
18
25
  blast_mode?: boolean;
19
26
  /** Grid-fill mode for Google Sheets and other canvas-rendered spreadsheets.
20
27
  * Provide cell addresses in A1 notation with their values.
@@ -27,6 +34,18 @@ export interface FillResult {
27
34
  succeeded: number;
28
35
  failed: number;
29
36
  submitted: boolean;
37
+ inputs?: Array<{
38
+ ref: string;
39
+ tag: string;
40
+ type: string;
41
+ name?: string;
42
+ label?: string;
43
+ placeholder?: string;
44
+ value: string;
45
+ focused?: boolean;
46
+ disabled?: boolean;
47
+ visible?: boolean;
48
+ }>;
30
49
  results: {
31
50
  ref: string;
32
51
  field_type: string;