@petukhovart/agent-view 0.14.0 → 0.16.1

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 (42) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/README.md +61 -12
  4. package/dist/cdp/png.d.ts +15 -0
  5. package/dist/cdp/png.d.ts.map +1 -0
  6. package/dist/cdp/png.js +147 -0
  7. package/dist/cdp/png.js.map +1 -0
  8. package/dist/cdp/transport.d.ts.map +1 -1
  9. package/dist/cdp/transport.js +82 -43
  10. package/dist/cdp/transport.js.map +1 -1
  11. package/dist/cdp/types.d.ts +33 -5
  12. package/dist/cdp/types.d.ts.map +1 -1
  13. package/dist/cdp/types.js.map +1 -1
  14. package/dist/cli/commands/drag.d.ts +4 -0
  15. package/dist/cli/commands/drag.d.ts.map +1 -1
  16. package/dist/cli/commands/drag.js +18 -0
  17. package/dist/cli/commands/drag.js.map +1 -1
  18. package/dist/cli/commands/heap.d.ts +14 -0
  19. package/dist/cli/commands/heap.d.ts.map +1 -0
  20. package/dist/cli/commands/heap.js +25 -0
  21. package/dist/cli/commands/heap.js.map +1 -0
  22. package/dist/cli/index.js +67 -1
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/inspectors/coverage/index.d.ts.map +1 -1
  25. package/dist/inspectors/coverage/index.js +2 -5
  26. package/dist/inspectors/coverage/index.js.map +1 -1
  27. package/dist/inspectors/format.d.ts +6 -0
  28. package/dist/inspectors/format.d.ts.map +1 -0
  29. package/dist/inspectors/format.js +17 -0
  30. package/dist/inspectors/format.js.map +1 -0
  31. package/dist/inspectors/heap/index.d.ts +65 -0
  32. package/dist/inspectors/heap/index.d.ts.map +1 -0
  33. package/dist/inspectors/heap/index.js +269 -0
  34. package/dist/inspectors/heap/index.js.map +1 -0
  35. package/dist/server/server.d.ts +12 -0
  36. package/dist/server/server.d.ts.map +1 -1
  37. package/dist/server/server.js +93 -14
  38. package/dist/server/server.js.map +1 -1
  39. package/package.json +2 -1
  40. package/skills/verify/SKILL.md +2 -0
  41. package/skills/verify/references/commands.md +35 -2
  42. package/skills/verify/references/memory-leaks.md +94 -0
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "agent-view",
10
10
  "source": "./",
11
- "description": "Visual verification CLI for desktop apps (Electron/Tauri/Browser) via Chrome DevTools Protocol. Inspect DOM, interact with UI, capture screenshots, read state, and prove which code an action reached."
11
+ "description": "Visual verification CLI for Electron/Tauri/Chromium apps via Chrome DevTools Protocol. Ships with verify skill for easy and cheap checks against a live app, including heap-snapshot memory-leak debugging."
12
12
  }
13
13
  ]
14
14
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-view",
3
- "description": "Visual verification CLI for desktop apps (Electron/Tauri/Browser) via Chrome DevTools Protocol. Ships the verify skill: DOM, screenshots, eval/watch state, console and log feed, network, and reachability checks against a live app.",
4
- "version": "0.14.0",
3
+ "description": "Visual verification CLI for Electron/Tauri/Chromium apps via Chrome DevTools Protocol. Ships with verify skill for easy and cheap checks against a live app, including heap-snapshot memory-leak debugging.",
4
+ "version": "0.16.1",
5
5
  "keywords": [
6
6
  "cdp",
7
7
  "electron",
package/README.md CHANGED
@@ -31,10 +31,10 @@ Works with any agent that can run shell commands. There's a Claude Code plugin i
31
31
 
32
32
  - Reads state inside `SharedWorker`, `ServiceWorker`, and dedicated workers. Half of a modern app's state lives there, and most browser-automation tools don't follow it.
33
33
  - Every command takes `--window <id>`. Settings, tray, and detached windows in Electron and Tauri apps work the same as the main window.
34
- - Electron, Tauri (WebView2 and WebKit), and plain Chromium. One CLI, same commands.
34
+ - Electron, Tauri on Windows (WebView2), and plain Chromium. One CLI, same commands. Tauri on macOS/Linux embeds WebKit, which has no Chrome DevTools Protocol, so it is out of reach.
35
35
  - `click`, `fill`, and `drag` fire real CDP input events. Vue `v-model`, React controlled inputs, and native fields actually accept the value; synthetic DOM events fail silently there.
36
36
  - `watch` emits RFC-6902 JSON-patches of any JS expression between two events. Answers "what mutated after the click?" without parsing screenshots.
37
- - `dom` returns the accessibility tree with `[ref=N]` handles. `--compact` cuts deep trees by 40–60%; `--diff`, `--count`, and `--max-lines` keep output bounded. `screenshot --crop` and WebP scaling do the same for vision tokens.
37
+ - `dom` returns the accessibility tree with `[ref=N]` handles. `--compact` cuts deep trees by 40–60%; `--diff`, `--count`, and `--max-lines` keep output bounded. `screenshot --crop` and `--scale` do the same for vision tokens.
38
38
  - Lazy CDP daemon, one persistent socket, 300 ms AX-tree cache. `dom → click → dom` in about 17 ms.
39
39
 
40
40
  ---
@@ -86,7 +86,7 @@ In `package.json`, wrap the dev script with [`cross-env`](https://www.npmjs.com/
86
86
  }
87
87
  ```
88
88
 
89
- Then `npm run dev` as usual. Devtools must be enabled in `tauri.conf.json` (default in `tauri dev`; for release builds, enable the `devtools` Cargo feature). macOS/Linux WebKit use different env vars; see [Enabling CDP](#enabling-cdp).
89
+ Then `npm run dev` as usual. Devtools must be enabled in `tauri.conf.json` (default in `tauri dev`; for release builds, enable the `devtools` Cargo feature). Windows only: on macOS/Linux Tauri embeds WebKit, which speaks the WebKit Remote Inspector protocol, not CDP, so agent-view cannot attach there.
90
90
  </details>
91
91
 
92
92
  <details>
@@ -147,8 +147,8 @@ The daemon is why `dom → click → dom` runs in ~17ms total: one persistent CD
147
147
  | Command | What it gives the agent |
148
148
  |---------------|-----------------------------------------------------------------------------------------|
149
149
  | `dom` | Accessibility tree with `[ref=N]` handles. Flags: `--filter`, `--compact`, `--count`, `--max-lines`, `--diff`. |
150
- | `screenshot` | PNG, or scaled WebP, or `--crop <element>` for one element only. Cuts vision tokens. |
151
- | `click` / `fill` / `drag` | Real CDP input events. Works with Vue/React/native and HTML5/pointer DnD. |
150
+ | `screenshot` | PNG, scaled with `--scale`, or `--crop <element>` for one element only. Cuts vision tokens. |
151
+ | `click` / `fill` / `drag` | Real CDP input events. Works with Vue/React/native; `drag` does HTML5 DnD through `Input.dragIntercepted` and pointer DnD through mouse events. |
152
152
  | `eval` | Run JS in the page's main world. Read store/state directly instead of scraping DOM. |
153
153
  | `watch` | Stream JSON-patch diffs of any expression. Answers "what changed between click and final state?". |
154
154
  | `console` | `console.log` + `Log.entryAdded` per page **and per worker**, with `--follow --until <pattern>`. |
@@ -156,6 +156,7 @@ The daemon is why `dom → click → dom` runs in ~17ms total: one persistent CD
156
156
  | `network` | Request/response timeline, headers, timing, bodies, and WebSocket/SSE frames. Filters: `--url`, `--method`, `--status`, `--type`. Captures page-load traffic. |
157
157
  | `coverage` | Which functions ran since the last `--clear`. Turns "does any action reach this code?" into a positive answer. Filters: `--filter`, `--file`, `--count`. |
158
158
  | `listeners` | Event handlers bound to a node, each with the `file:line` it was declared at. |
159
+ | `heap` | Named heap snapshots diffed by class: what grew after repeating an action, how many are detached DOM nodes, and what retains them. |
159
160
  | `upload` | Puts files into a file input — no picker opens. `--selector` reaches the hidden inputs that have no ref. |
160
161
  | `dialog` | JS modals are answered automatically so they cannot freeze the run; sets the answer, and pre-answers native file pickers with `arm`. |
161
162
  | `wait` | Block until an element appears (default 10s). |
@@ -266,7 +267,7 @@ npx electron-vite dev -- --remote-debugging-port=9876
266
267
 
267
268
  | Runtime | Setup |
268
269
  |----------------------|--------------------------------------------|
269
- | **Tauri** | CDP via devtools configuration |
270
+ | **Tauri (Windows)** | `WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS`, see above. WebKit builds (macOS/Linux) are not supported |
270
271
  | **Any Chromium app** | `--remote-debugging-port=9876` launch flag |
271
272
 
272
273
  ### Verify CDP is working
@@ -390,16 +391,37 @@ agent-view fill 3 "hello@example.com"
390
391
 
391
392
  ### `drag`
392
393
 
393
- HTML5 / pointer-driven drag-and-drop via CDP `Input.dispatchMouseEvent` (`mousePressed` → N × `mouseMoved` → `mouseReleased`). Real mouse events, not synthesized JS events; works with `vue-draggable-resizable`, `react-grid-layout`, gridstack, kanban boards, file drop zones, map pin drags, resize handles.
394
+ Drag-and-drop over CDP. Two paths, picked automatically:
395
+
396
+ - **HTML5** (`draggable=true`, `dragstart`/`dragover`/`drop`). Plain mouse events never start one in Chromium: `dragstart` does not fire, `dataTransfer` stays empty, `drop` never arrives. So `drag` enables `Input.setInterceptDrags`, presses and moves, and when Chromium reports `Input.dragIntercepted` it finishes the gesture with `Input.dispatchDragEvent` (`dragEnter` → N × `dragOver` → `drop`), carrying the app's real `dataTransfer` payload. The same mechanism Puppeteer uses.
397
+ - **Pointer** (`mousedown`/`mousemove`/`mouseup`, pointer events). Used when no `dragIntercepted` arrives within 1 s: `vue-draggable-resizable`, `react-grid-layout`, gridstack, canvas, resize handles.
394
398
 
395
399
  ```bash
396
400
  agent-view drag --from 42 --to 88 # ref → ref
397
401
  agent-view drag --from-pos 86,792 --to-pos 640,200 # coord → coord (canvas, custom DnD)
398
402
  agent-view drag --from 42 --to-pos 640,200 # mixed
399
403
  agent-view drag --from 5 --to 9 --steps 20 --hold-ms 150
404
+ agent-view drag --from-pos 385,303 --to-pos 1250,589 --cancel # HTML5: dragCancel instead of drop (Esc mid-drag)
405
+ agent-view drag --from-pos 385,303 --to-pos 1250,589 --html5 # fail instead of falling back to pointer
406
+ agent-view drag --from-pos 385,303 --to-pos 1250,589 --pointer # never intercept
407
+ ```
408
+
409
+ The output names the path and, for HTML5, the intercepted MIME types and data — check that the payload is the app's (`application/json`, a custom type), not a text selection:
410
+
411
+ ```
412
+ Dragged (385, 303) → (1250, 589) via html5 drop (dragOperationsMask=3)
413
+ text/plain: "1:hr:4100@UInt16"
414
+ application/json: "[{\"id\":\"1:hr:4100@UInt16\",\"name\":\"Float HiLo\",...}]"
400
415
  ```
401
416
 
402
- `--steps` (default 10) controls intermediate `mouseMoved` events so libraries that throttle on movement deltas still see continuous motion. `--hold-ms` inserts a pause between press and the first move (some libs require >100ms for touch-style activation). `--button` accepts `left|right|middle`.
417
+ ```
418
+ Dragged (485, 140) → (1250, 589) via pointer
419
+ warning: no HTML5 drag started (Input.dragIntercepted not fired within 1 s): the element at --from is not draggable, or a control (input/select/button) under the cursor swallowed dragstart
420
+ ```
421
+
422
+ Pitfalls of the HTML5 path: start on a cell without a control — an `<input>`, `<select>` or `<button>` under the cursor swallows `dragstart` even inside a `draggable` row; Chromium needs several `mouseMoved` past its drag threshold, so `--steps 0` will not start one. `--mask <n>` overrides `dragOperationsMask` on the dispatched events — diagnostics for `effectAllowed` / `dropEffect` mismatches, which the browser enforces only on a real drag.
423
+
424
+ `--steps` (default 10) controls intermediate `mouseMoved` / `dragOver` events so libraries that throttle on movement deltas still see continuous motion. `--hold-ms` inserts a pause between press and the first move (some libs require >100ms for touch-style activation). `--button` accepts `left|right|middle`.
403
425
 
404
426
  ### `upload`
405
427
 
@@ -441,18 +463,18 @@ Out of reach: `showOpenFilePicker()` (File System Access API) exposes no input t
441
463
 
442
464
  ### `screenshot`
443
465
 
444
- Captures a screenshot, saves to temp dir, prints the file path. PNG by default; WebP (q=80) when `--scale` is set (JPEG fallback for older Chrome/Electron).
466
+ Captures a screenshot, saves to temp dir, prints the file path. Always PNG.
445
467
 
446
468
  ```bash
447
469
  agent-view screenshot
448
- agent-view screenshot --scale 0.5 # Half-res WebP (~3× fewer vision tokens)
449
- agent-view screenshot --scale 0.25 # Quarter-res WebP (~12× fewer, 1 tile)
470
+ agent-view screenshot --scale 0.5 # Half-res (~3× fewer vision tokens)
471
+ agent-view screenshot --scale 0.25 # Quarter-res (~12× fewer, 1 tile)
450
472
  agent-view screenshot --crop "Sidebar" # Crop to element bounding box (~12× fewer in best case)
451
473
  agent-view screenshot --crop "Chart" --scale 0.5 # Crop + scale (stacks)
452
474
  agent-view screenshot --crop "Active bookings" --crop-up 1 # Crop the card, not its heading
453
475
  ```
454
476
 
455
- `--scale` accepts a factor in `(0, 1]`. CDP-side clip + WebP encode; recommended for agent loops where vision tokens dominate cost.
477
+ `--scale` accepts a factor in `(0, 1]`; recommended for agent loops where vision tokens dominate cost. Crop and scale run inside the server on a plain full-viewport capture: a CDP `clip` resizes the app's render view until the capture completes, and on a hidden or minimised window it never completes, leaving the app drawn in a corner.
456
478
 
457
479
  `--crop <filter>` resolves a DOM element by the same filter syntax as `dom --filter`, then crops the screenshot to its bounding box before encoding. One tile (~1.6k vision tokens) instead of twelve (~19k) in the best case. If the filter matches nothing a warning is emitted to stderr and the full window is captured instead. Combines naturally with `--scale`.
458
480
 
@@ -637,6 +659,33 @@ A node with no handlers prints `(no listeners on this node)` and exits 0. A `--f
637
659
 
638
660
  `--selector` is the escape hatch for a node the AX tree never exposes — a hidden or `aria-hidden` element has no `[ref=N]` at all, so no other flag can address it. A selector matching nothing is an error: `No element matches selector "<css>"`.
639
661
 
662
+ ### `heap`
663
+
664
+ Takes named V8 heap snapshots and compares them by class. Answers "does repeating this action leak memory, and what holds the leaked objects?" without the multi-hundred-MB `.heapsnapshot` ever reaching the agent: the server parses it and keeps only the class table and the graph.
665
+
666
+ ```bash
667
+ agent-view heap take --name baseline # full GC, then snapshot
668
+ # … repeat the suspected action ~10 times with click / fill / eval …
669
+ agent-view heap take --name target
670
+ agent-view heap diff baseline target # per-class growth, largest first
671
+ agent-view heap diff --detached # detached DOM nodes only
672
+ agent-view heap retainers "Detached <div class=\"row\">" # who holds them, one hop up
673
+ agent-view heap summary --filter "OrderRow" # one snapshot's classes by size
674
+ agent-view heap list # snapshots the server holds
675
+ agent-view heap clear
676
+ ```
677
+
678
+ ```
679
+ baseline → target (page:Orders): +3,201 nodes, +2.4 MB, detached +240 (+960 KB)
680
+ class Δcount Δsize count
681
+ Detached <div class="row"> +240 +960 KB 252
682
+ OrderRowVM +10 +12 KB 10
683
+ ```
684
+
685
+ A `Δcount` that is a multiple of the repetition count is the leak; `+3` is noise. `retainers` names the object and property holding the instances (`Array []`, `RowRegistry .el`), counting distinct instances and skipping weak edges. One hop only: retained sizes, dominators and full retaining paths are not computed, and for those the app's own DevTools Memory panel shows the same class names on the same CDP port.
686
+
687
+ Snapshots live in the lazy server and vanish when it idles out (5 min) or on `clear`. `--target <worker>` snapshots a worker's own heap. The full method, with a table of what each diff shape usually means, is in [`skills/verify/references/memory-leaks.md`](skills/verify/references/memory-leaks.md).
688
+
640
689
  ### `watch`
641
690
 
642
691
  Polls a JS expression and streams JSON-patch (RFC 6902) diffs as it changes. Closes the "what changed between click and final state?" gap that screenshots and DOM dumps can't cover. **Requires `"allowEval": true`** (same gate as `eval`).
@@ -0,0 +1,15 @@
1
+ export type PixelRect = {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ };
7
+ /**
8
+ * Crops an 8-bit RGB/RGBA PNG to `rect` (image pixels, clamped to the image)
9
+ * and downscales it by `scale` with an area average. Chromium can do both
10
+ * through `Page.captureScreenshot({ clip })`, but a clip resizes the render
11
+ * view for the capture and restores it only if the capture completes — a
12
+ * hidden window never completes it, so the app stays shrunk.
13
+ */
14
+ export declare function cropScalePng(png: Buffer, rect: PixelRect | undefined, scale: number): Buffer;
15
+ //# sourceMappingURL=png.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png.d.ts","sourceRoot":"","sources":["../../src/cdp/png.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAM/E;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA6B5F"}
@@ -0,0 +1,147 @@
1
+ import { deflateSync, inflateSync } from 'node:zlib';
2
+ const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
3
+ /**
4
+ * Crops an 8-bit RGB/RGBA PNG to `rect` (image pixels, clamped to the image)
5
+ * and downscales it by `scale` with an area average. Chromium can do both
6
+ * through `Page.captureScreenshot({ clip })`, but a clip resizes the render
7
+ * view for the capture and restores it only if the capture completes — a
8
+ * hidden window never completes it, so the app stays shrunk.
9
+ */
10
+ export function cropScalePng(png, rect, scale) {
11
+ const src = decode(png);
12
+ const x0 = clamp(Math.round(rect?.x ?? 0), 0, src.width - 1);
13
+ const y0 = clamp(Math.round(rect?.y ?? 0), 0, src.height - 1);
14
+ const x1 = clamp(Math.round((rect?.x ?? 0) + (rect?.width ?? src.width)), x0 + 1, src.width);
15
+ const y1 = clamp(Math.round((rect?.y ?? 0) + (rect?.height ?? src.height)), y0 + 1, src.height);
16
+ const width = Math.max(1, Math.round((x1 - x0) * scale));
17
+ const height = Math.max(1, Math.round((y1 - y0) * scale));
18
+ const rgba = Buffer.alloc(width * height * 4);
19
+ for (let y = 0; y < height; y++) {
20
+ const sy0 = y0 + Math.floor((y * (y1 - y0)) / height);
21
+ const sy1 = Math.max(sy0 + 1, y0 + Math.floor(((y + 1) * (y1 - y0)) / height));
22
+ for (let x = 0; x < width; x++) {
23
+ const sx0 = x0 + Math.floor((x * (x1 - x0)) / width);
24
+ const sx1 = Math.max(sx0 + 1, x0 + Math.floor(((x + 1) * (x1 - x0)) / width));
25
+ const sum = [0, 0, 0, 0];
26
+ for (let sy = sy0; sy < sy1; sy++) {
27
+ for (let sx = sx0; sx < sx1; sx++) {
28
+ const i = (sy * src.width + sx) * 4;
29
+ for (let c = 0; c < 4; c++)
30
+ sum[c] += src.rgba[i + c];
31
+ }
32
+ }
33
+ const n = (sy1 - sy0) * (sx1 - sx0);
34
+ const o = (y * width + x) * 4;
35
+ for (let c = 0; c < 4; c++)
36
+ rgba[o + c] = Math.round(sum[c] / n);
37
+ }
38
+ }
39
+ return encode({ width, height, rgba });
40
+ }
41
+ function clamp(v, min, max) {
42
+ return Math.min(Math.max(v, min), max);
43
+ }
44
+ function decode(png) {
45
+ if (!png.subarray(0, 8).equals(SIGNATURE))
46
+ throw new Error('Not a PNG');
47
+ let width = 0;
48
+ let height = 0;
49
+ let channels = 0;
50
+ const idat = [];
51
+ for (let off = 8; off < png.length;) {
52
+ const len = png.readUInt32BE(off);
53
+ const type = png.toString('latin1', off + 4, off + 8);
54
+ const data = png.subarray(off + 8, off + 8 + len);
55
+ if (type === 'IHDR') {
56
+ width = data.readUInt32BE(0);
57
+ height = data.readUInt32BE(4);
58
+ const colorType = data[9];
59
+ if (data[8] !== 8 || (colorType !== 2 && colorType !== 6) || data[12] !== 0) {
60
+ throw new Error(`Unsupported PNG: bit depth ${data[8]}, color type ${colorType}, interlace ${data[12]}`);
61
+ }
62
+ channels = colorType === 6 ? 4 : 3;
63
+ }
64
+ else if (type === 'IDAT') {
65
+ idat.push(data);
66
+ }
67
+ else if (type === 'IEND') {
68
+ break;
69
+ }
70
+ off += 12 + len;
71
+ }
72
+ const raw = inflateSync(Buffer.concat(idat));
73
+ const stride = width * channels;
74
+ const pixels = Buffer.alloc(stride * height);
75
+ for (let y = 0; y < height; y++) {
76
+ const filter = raw[y * (stride + 1)];
77
+ const line = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1));
78
+ const row = y * stride;
79
+ for (let i = 0; i < stride; i++) {
80
+ const a = i >= channels ? pixels[row + i - channels] : 0;
81
+ const b = y > 0 ? pixels[row - stride + i] : 0;
82
+ const c = i >= channels && y > 0 ? pixels[row - stride + i - channels] : 0;
83
+ pixels[row + i] = (line[i] + unfilter(filter, a, b, c)) & 0xff;
84
+ }
85
+ }
86
+ if (channels === 4)
87
+ return { width, height, rgba: pixels };
88
+ const rgba = Buffer.alloc(width * height * 4, 0xff);
89
+ for (let p = 0; p < width * height; p++)
90
+ pixels.copy(rgba, p * 4, p * 3, p * 3 + 3);
91
+ return { width, height, rgba };
92
+ }
93
+ function unfilter(filter, a, b, c) {
94
+ switch (filter) {
95
+ case 0: return 0;
96
+ case 1: return a;
97
+ case 2: return b;
98
+ case 3: return (a + b) >> 1;
99
+ case 4: {
100
+ const p = a + b - c;
101
+ const pa = Math.abs(p - a);
102
+ const pb = Math.abs(p - b);
103
+ const pc = Math.abs(p - c);
104
+ return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
105
+ }
106
+ default: throw new Error(`Unknown PNG filter ${filter}`);
107
+ }
108
+ }
109
+ function encode({ width, height, rgba }) {
110
+ const stride = width * 4;
111
+ const raw = Buffer.alloc((stride + 1) * height);
112
+ for (let y = 0; y < height; y++) {
113
+ // Filter 1 (Sub) — flat UI areas become runs of zeros and deflate well.
114
+ raw[y * (stride + 1)] = 1;
115
+ for (let i = 0; i < stride; i++) {
116
+ const left = i >= 4 ? rgba[y * stride + i - 4] : 0;
117
+ raw[y * (stride + 1) + 1 + i] = (rgba[y * stride + i] - left) & 0xff;
118
+ }
119
+ }
120
+ const ihdr = Buffer.alloc(13);
121
+ ihdr.writeUInt32BE(width, 0);
122
+ ihdr.writeUInt32BE(height, 4);
123
+ ihdr[8] = 8;
124
+ ihdr[9] = 6;
125
+ return Buffer.concat([SIGNATURE, chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw)), chunk('IEND', Buffer.alloc(0))]);
126
+ }
127
+ function chunk(type, data) {
128
+ const head = Buffer.alloc(8);
129
+ head.writeUInt32BE(data.length, 0);
130
+ head.write(type, 4, 'latin1');
131
+ const crc = Buffer.alloc(4);
132
+ crc.writeUInt32BE(crc32(Buffer.concat([head.subarray(4), data])), 0);
133
+ return Buffer.concat([head, data, crc]);
134
+ }
135
+ const CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
136
+ let c = n;
137
+ for (let k = 0; k < 8; k++)
138
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
139
+ return c >>> 0;
140
+ });
141
+ function crc32(buf) {
142
+ let c = 0xffffffff;
143
+ for (const byte of buf)
144
+ c = CRC_TABLE[(c ^ byte) & 0xff] ^ (c >>> 8);
145
+ return (c ^ 0xffffffff) >>> 0;
146
+ }
147
+ //# sourceMappingURL=png.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png.js","sourceRoot":"","sources":["../../src/cdp/png.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/cdp/transport.ts"],"names":[],"mappings":"AAEA,OAAO,EAaL,KAAK,SAAS,EAId,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAcjB,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AA4BhD,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAmCD,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAkBpE;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQ9E;AAuxBD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CA8BhG;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,WAAW,CAAC,CAoVtB"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/cdp/transport.ts"],"names":[],"mappings":"AAEA,OAAO,EAaL,KAAK,SAAS,EAKd,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAgBjB,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AA6BhD,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAmCD,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAkBpE;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQ9E;AAgzBD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CA+BhG;AAMD,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,WAAW,CAAC,CAoWtB"}
@@ -1,6 +1,7 @@
1
1
  // @ts-expect-error no types available for chrome-remote-interface
2
2
  import CDP from 'chrome-remote-interface';
3
3
  import { TargetType, ConsoleLevel, ConsoleSource, EvaluationError, MouseButton, NetworkEventKind, NetworkResourceType, JsDialogType, FileChooserMode, } from './types.js';
4
+ import { cropScalePng } from './png.js';
4
5
  // CDP hosts to try: IPv4 first, then IPv6 (WebView2/Tauri often listens on ::1)
5
6
  const CDP_HOSTS = ['127.0.0.1', '::1'];
6
7
  // Maps targetId → host for connection routing
@@ -665,6 +666,19 @@ function attachCoverage(client) {
665
666
  },
666
667
  };
667
668
  }
669
+ /** Chunks arrive before the command resolves, so subscribing first and joining after is the whole protocol. */
670
+ async function takeHeapSnapshotImpl(client) {
671
+ const HeapProfiler = client.HeapProfiler;
672
+ const chunks = [];
673
+ const unsubscribe = HeapProfiler.addHeapSnapshotChunk(({ chunk }) => { chunks.push(chunk); });
674
+ try {
675
+ await HeapProfiler.takeHeapSnapshot({ reportProgress: false, captureNumericValue: false });
676
+ }
677
+ finally {
678
+ unsubscribe();
679
+ }
680
+ return chunks.join('');
681
+ }
668
682
  /** Bounds the wait for the `scriptParsed` replay when an id is still missing. */
669
683
  const SCRIPT_PARSED_DEADLINE_MS = 1_000;
670
684
  const SCRIPT_PARSED_POLL_MS = 20;
@@ -733,11 +747,14 @@ export async function connectToRuntime(port, target) {
733
747
  getResponseBody: (requestId) => networkSub.getResponseBody(requestId),
734
748
  startCoverage: () => coverage.start(),
735
749
  takeCoverage: () => coverage.take(),
750
+ takeHeapSnapshot: () => takeHeapSnapshotImpl(client),
736
751
  async close() {
737
752
  await client.close();
738
753
  },
739
754
  };
740
755
  }
756
+ const DRAG_INTERCEPT_TIMEOUT_MS = 1000;
757
+ const DRAG_NOT_INTERCEPTED = 'no HTML5 drag started (Input.dragIntercepted not fired within 1 s): the element at --from is not draggable, or a control (input/select/button) under the cursor swallowed dragstart';
741
758
  export async function connectToPage(port, target, cache) {
742
759
  if (target.type !== TargetType.Page && target.type !== TargetType.Iframe) {
743
760
  throw new Error(`connectToPage requires a page/iframe target, got: ${target.type}`);
@@ -777,8 +794,6 @@ export async function connectToPage(port, target, cache) {
777
794
  });
778
795
  // null = not yet tested; true = available; false = unavailable (API not supported)
779
796
  let queryAXTreeAvailable = null;
780
- // null = not yet tested; true = webp supported; false = not supported (old Chrome/Electron)
781
- let webpSupported = null;
782
797
  Page.frameNavigated(async () => {
783
798
  cache.invalidate(cacheKey);
784
799
  fileChooserSub.forget();
@@ -802,21 +817,64 @@ export async function connectToPage(port, target, cache) {
802
817
  await Input.dispatchMouseEvent({ type: 'mouseReleased', x, y, button, clickCount: i });
803
818
  }
804
819
  }
820
+ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
805
821
  async function dispatchDrag(from, to, opts) {
806
822
  const steps = Math.max(0, opts?.steps ?? 10);
807
823
  const button = opts?.button ?? MouseButton.Left;
808
824
  const holdMs = Math.max(0, opts?.holdMs ?? 0);
809
- await Input.dispatchMouseEvent({ type: 'mousePressed', x: from.x, y: from.y, button, clickCount: 1 });
810
- if (holdMs > 0)
811
- await new Promise(r => setTimeout(r, holdMs));
812
- for (let i = 1; i <= steps; i++) {
825
+ const mode = opts?.mode ?? 'auto';
826
+ const at = (i) => {
813
827
  const t = i / (steps + 1);
814
- const x = from.x + (to.x - from.x) * t;
815
- const y = from.y + (to.y - from.y) * t;
816
- await Input.dispatchMouseEvent({ type: 'mouseMoved', x, y, button });
828
+ return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };
829
+ };
830
+ // With interception on, Chromium reports a starting HTML5 drag as `Input.dragIntercepted`
831
+ // instead of showing the native drag image; after that only `dispatchDragEvent` reaches the page.
832
+ let intercepted;
833
+ const unsub = mode === 'pointer' ? undefined : Input.dragIntercepted(({ data }) => { intercepted = data; });
834
+ if (unsub)
835
+ await Input.setInterceptDrags({ enabled: true });
836
+ try {
837
+ await Input.dispatchMouseEvent({ type: 'mouseMoved', x: from.x, y: from.y });
838
+ await Input.dispatchMouseEvent({ type: 'mousePressed', x: from.x, y: from.y, button, clickCount: 1 });
839
+ if (holdMs > 0)
840
+ await sleep(holdMs);
841
+ // Chromium starts an HTML5 drag only after several mouseMoved past its threshold; the
842
+ // pause between them is what lets the renderer process each one.
843
+ let i = 1;
844
+ for (; i <= steps + 1 && !intercepted; i++) {
845
+ const p = at(i);
846
+ await Input.dispatchMouseEvent({ type: 'mouseMoved', x: p.x, y: p.y, button });
847
+ if (unsub)
848
+ await sleep(20);
849
+ }
850
+ if (unsub && !intercepted) {
851
+ const deadline = Date.now() + DRAG_INTERCEPT_TIMEOUT_MS;
852
+ while (!intercepted && Date.now() < deadline)
853
+ await sleep(50);
854
+ }
855
+ if (intercepted) {
856
+ const data = opts?.mask === undefined ? intercepted : { ...intercepted, dragOperationsMask: opts.mask };
857
+ const ended = opts?.cancel ? 'dragCancel' : 'drop';
858
+ await Input.dispatchDragEvent({ type: 'dragEnter', x: at(i - 1).x, y: at(i - 1).y, data });
859
+ for (; i <= steps + 1; i++) {
860
+ const p = at(i);
861
+ await Input.dispatchDragEvent({ type: 'dragOver', x: p.x, y: p.y, data });
862
+ await sleep(20);
863
+ }
864
+ await Input.dispatchDragEvent({ type: ended, x: to.x, y: to.y, data });
865
+ await Input.dispatchMouseEvent({ type: 'mouseReleased', x: to.x, y: to.y, button, clickCount: 1 });
866
+ return { path: 'html5', ended, data };
867
+ }
868
+ await Input.dispatchMouseEvent({ type: 'mouseReleased', x: to.x, y: to.y, button, clickCount: 1 });
869
+ if (mode === 'html5')
870
+ throw new Error(DRAG_NOT_INTERCEPTED);
871
+ return { path: 'pointer', warning: unsub ? DRAG_NOT_INTERCEPTED : undefined };
872
+ }
873
+ finally {
874
+ unsub?.();
875
+ if (unsub)
876
+ await Input.setInterceptDrags({ enabled: false });
817
877
  }
818
- await Input.dispatchMouseEvent({ type: 'mouseMoved', x: to.x, y: to.y, button });
819
- await Input.dispatchMouseEvent({ type: 'mouseReleased', x: to.x, y: to.y, button, clickCount: 1 });
820
878
  }
821
879
  async function scrollNodeIntoView(backendNodeId) {
822
880
  const { object } = await DOM.resolveNode({ backendNodeId });
@@ -892,6 +950,7 @@ export async function connectToPage(port, target, cache) {
892
950
  getResponseBody: (requestId) => networkSub.getResponseBody(requestId),
893
951
  startCoverage: () => coverage.start(),
894
952
  takeCoverage: () => coverage.take(),
953
+ takeHeapSnapshot: () => takeHeapSnapshotImpl(client),
895
954
  async getEventListeners(backendDOMNodeId, depth = 0) {
896
955
  const { object } = await DOM.resolveNode({ backendNodeId: backendDOMNodeId });
897
956
  return listenersOn(object.objectId, depth);
@@ -953,38 +1012,18 @@ export async function connectToPage(port, target, cache) {
953
1012
  }
954
1013
  },
955
1014
  async captureScreenshot(opts) {
1015
+ // Never pass `clip` to Chromium: see cropScalePng for why.
1016
+ const { data } = await Page.captureScreenshot({ format: 'png' });
1017
+ const png = Buffer.from(data, 'base64');
956
1018
  const scale = opts?.scale ?? 1;
957
- const explicitClip = opts?.clip;
958
- if (explicitClip !== undefined) {
959
- const format = scale < 1 ? 'jpeg' : 'png';
960
- const params = { format, clip: { ...explicitClip, scale } };
961
- if (format === 'jpeg')
962
- params.quality = 80;
963
- const { data } = await Page.captureScreenshot(params);
964
- return { buffer: Buffer.from(data, 'base64'), format };
965
- }
966
- if (scale >= 1) {
967
- const { data } = await Page.captureScreenshot({ format: 'png' });
968
- return { buffer: Buffer.from(data, 'base64'), format: 'png' };
969
- }
970
- const { cssLayoutViewport } = await Page.getLayoutMetrics();
971
- const clip = { x: 0, y: 0, width: cssLayoutViewport.clientWidth, height: cssLayoutViewport.clientHeight, scale };
972
- if (webpSupported !== false) {
973
- try {
974
- const { data } = await Page.captureScreenshot({ format: 'webp', quality: 80, clip });
975
- webpSupported = true;
976
- return { buffer: Buffer.from(data, 'base64'), format: 'webp' };
977
- }
978
- catch {
979
- if (webpSupported === null) {
980
- // eslint-disable-next-line no-console
981
- console.error('[agent-view] WebP not supported by this Chrome/Electron version, falling back to JPEG');
982
- webpSupported = false;
983
- }
984
- }
985
- }
986
- const { data } = await Page.captureScreenshot({ format: 'jpeg', quality: 80, clip });
987
- return { buffer: Buffer.from(data, 'base64'), format: 'jpeg' };
1019
+ if (opts?.clip === undefined && scale >= 1)
1020
+ return { buffer: png, format: 'png' };
1021
+ // The capture is in device pixels, clip rects are in CSS pixels.
1022
+ const { layoutViewport, cssLayoutViewport } = await Page.getLayoutMetrics();
1023
+ const dpr = layoutViewport.clientWidth / cssLayoutViewport.clientWidth;
1024
+ const clip = opts?.clip;
1025
+ const rect = clip && { x: clip.x * dpr, y: clip.y * dpr, width: clip.width * dpr, height: clip.height * dpr };
1026
+ return { buffer: cropScalePng(png, rect, Math.min(scale, 1)), format: 'png' };
988
1027
  },
989
1028
  async clickByNodeId(backendNodeId, opts) {
990
1029
  const { x, y } = await resolveBoxCenter(backendNodeId, true);
@@ -1002,7 +1041,7 @@ export async function connectToPage(port, target, cache) {
1002
1041
  return resolveBoxRect(nodeId, opts?.scrollIntoView ?? true);
1003
1042
  },
1004
1043
  async dragBetweenPositions(from, to, opts) {
1005
- await dispatchDrag(from, to, opts);
1044
+ return dispatchDrag(from, to, opts);
1006
1045
  },
1007
1046
  async uploadByNodeId(backendNodeId, files) {
1008
1047
  await DOM.setFileInputFiles({ backendNodeId, files });