@tyouter/dsh-recorder 0.0.0-stage → 0.1.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.
- package/HANDOFF.md +115 -0
- package/README.md +249 -2
- package/client.js +2004 -0
- package/cordis.patch.yml +13 -0
- package/docs/click-effect.png +0 -0
- package/docs/crop-example.png +0 -0
- package/docs/findings.md +158 -0
- package/docs/panel-history.png +0 -0
- package/docs/panel-preview.png +0 -0
- package/docs/panel-running.png +0 -0
- package/docs/panel-settings.png +0 -0
- package/docs/panel.png +0 -0
- package/docs/script-spec.md +97 -0
- package/examples/notes-demo.json +17 -0
- package/icon.svg +14 -0
- package/index.js +84 -0
- package/lib/api.js +274 -0
- package/lib/capture.js +396 -0
- package/lib/engine.js +472 -0
- package/lib/python.js +98 -0
- package/lib/tools.js +276 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +55 -4
- package/tools/record-window.py +621 -0
package/HANDOFF.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Handoff — build a DSH recording plugin
|
|
2
|
+
|
|
3
|
+
You are taking over a prototype whose goal is: **record a DSH plugin's real interface and produce a GIF
|
|
4
|
+
(or stills) from it**, without hand-building a lookalike page.
|
|
5
|
+
|
|
6
|
+
Read [docs/findings.md](docs/findings.md) first; it contains the measurements. This document is the
|
|
7
|
+
brief: what to build, the seams that already exist, and the traps that already cost time.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The honest starting point
|
|
12
|
+
|
|
13
|
+
Two instruments were tried against `@tyouter/dsh-notes`:
|
|
14
|
+
|
|
15
|
+
| Approach | What it produced | Verdict |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| **Re-render**: a generated HTML page reusing the plugin's renderer + stylesheet, driven by `seek(t)`, captured over CDP | A perfect-looking GIF that was *almost* the app — wrong proportions, invented labels | Rejected by the user: "你的 GIF 不对啊" |
|
|
18
|
+
| **Real capture**: scripted driver inside the real page + window grab from outside | Real pixels, real interactions; the note count went 13 → 14 (proof it ran) | Worked, but the tooling was bolted onto the notes repo and the timing/visibility problems below were never solved |
|
|
19
|
+
|
|
20
|
+
The second approach is the right one. What is missing is the product around it.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What to build
|
|
25
|
+
|
|
26
|
+
A plugin, `@tyouter/dsh-recorder` (name is yours), with two surfaces.
|
|
27
|
+
|
|
28
|
+
### 1. A host tool the agent can call
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
record_start({ target: "window" | "rect", title?, rect?: [l,t,w,h], fps?, width? })
|
|
32
|
+
record_stop() → { gif, frames, seconds, framesCount }
|
|
33
|
+
list_windows() → [{ hwnd, title, rect }]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Reuse `tools/record-window.py` as the engine: it is a complete, working recorder (Win32 enumerate +
|
|
37
|
+
`ImageGrab` + Pillow GIF). Wrap it as a subprocess, or port the ~120 lines into the plugin's host half.
|
|
38
|
+
Return the GIF path so the agent can present it.
|
|
39
|
+
|
|
40
|
+
Requirements the prototype implies:
|
|
41
|
+
|
|
42
|
+
- **start/stop, not fixed duration.** The agent does not know how long a walkthrough will take.
|
|
43
|
+
- **a status line in the UI while recording.** The prototype was silent; the user assumed nothing was
|
|
44
|
+
happening ("我切过去了,那边没有开始自动录制啊").
|
|
45
|
+
- **explicit rect support.** Titles are not unique; the biggest match is often the wrong pane.
|
|
46
|
+
|
|
47
|
+
### 2. A client-side driver contract
|
|
48
|
+
|
|
49
|
+
The half that must live inside the plugin under test. `src/demo-driver.js` is a working reference
|
|
50
|
+
implementation for one plugin; generalise the idea:
|
|
51
|
+
|
|
52
|
+
- a small **driver API** the plugin under test exposes: `startDemo(steps)`, or a generic "act like a
|
|
53
|
+
user" helper layered on selectors;
|
|
54
|
+
- a **dev flag** to arm it (the prototype used a `.demo` file in the data folder plus an env var —
|
|
55
|
+
the file form means arming needs no restart; the *client bundle* still needs a reload);
|
|
56
|
+
- `resetDemo()` so a walk can be replayed without reloading the page;
|
|
57
|
+
- **no network of its own**: the demo should exercise the product's real code paths, never mock them.
|
|
58
|
+
|
|
59
|
+
### 3. Assembling outputs
|
|
60
|
+
|
|
61
|
+
- GIF: already in `record-window.py` (128-colour palette, one shared palette per recording).
|
|
62
|
+
- Stills: pick frames by timestamp; `tools/record-window.py` keeps every frame under `frames/`.
|
|
63
|
+
- Optional later: chapter cards, cursor highlight, zoom — none of this exists yet. Do not promise it in
|
|
64
|
+
a README before it is built; that was the mistake of the re-render approach.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Traps, in the order they cost time
|
|
69
|
+
|
|
70
|
+
1. **One-shot driver.** A module-level `started` flag means "run once per page load". After the first
|
|
71
|
+
run the UI is inert, which reads as a bug. Either reload or expose a reset.
|
|
72
|
+
2. **Recording the wrong window.** The panel can be mounted while the chat pane is in front. Sample a
|
|
73
|
+
frame mid-recording and *look at it*; do not trust the timer.
|
|
74
|
+
3. **Dynamic import does not work in a plugin client half.** The bundle is one factory function; the
|
|
75
|
+
driver must be concatenated into it (that is why `build.mjs` in dsh-notes has a `parts` list).
|
|
76
|
+
4. **React ignores `value = …`.** Use the native setter + `dispatchEvent(new Event('input'))`.
|
|
77
|
+
5. **Console encoding.** The bundled Python defaults to cp1252; `--list` crashes on a Chinese window
|
|
78
|
+
title without `sys.stdout.reconfigure(encoding="utf-8")`.
|
|
79
|
+
6. **The GUI is 401-gated and its port changes on restart.** Do not hard-code a port; read it from the
|
|
80
|
+
app or pass it in.
|
|
81
|
+
7. **The renderer has no debug port** unless the app is started with `--remote-debugging-port`. If you
|
|
82
|
+
want CDP-based capture (cleaner than screen grabbing: no focus, no occlusion, exact frames), that
|
|
83
|
+
restart is part of the design, not an accident.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Suggested order of work
|
|
88
|
+
|
|
89
|
+
1. Wrap `tools/record-window.py` behind a host tool + a `record_start/stop` pair, with a visible status.
|
|
90
|
+
This alone is useful to any plugin author.
|
|
91
|
+
2. Move the driver into a small, documented module (`driveUI({ steps })`) and use the notes walk as the
|
|
92
|
+
first fixture; keep selector strings in the fixture, not in the engine.
|
|
93
|
+
3. Add a "record this panel" flow: open the panel, arm the demo, start the recorder, stop on completion.
|
|
94
|
+
4. Only then: overlays (cursor, zoom, chapter cards) — and only as an opt-in post-process on the frames.
|
|
95
|
+
|
|
96
|
+
## Acceptance for v0.1
|
|
97
|
+
|
|
98
|
+
- Start a recording of a named window or rect, get a GIF back, and the GIF shows the plugin's real UI.
|
|
99
|
+
- Stop reliably; no orphan process; the frames folder is cleaned on the next start.
|
|
100
|
+
- The recording is visible in the UI while it runs.
|
|
101
|
+
- A replay needs no page reload (reset path exists).
|
|
102
|
+
- Nothing in the plugin under test changes except an opt-in dev flag.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Files
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
src/demo-driver.js reference driver (works against dsh-notes 1.1 selectors)
|
|
110
|
+
tools/record-window.py reference recorder (window/rect, fps, GIF, --list)
|
|
111
|
+
docs/findings.md measurements: ports, 401, window rects, what worked, what did not
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The notebook repo (`../dsh-notes`) no longer contains any of this; it was reverted to its product code
|
|
115
|
+
only. Do not add recording concerns back into it.
|
package/README.md
CHANGED
|
@@ -1,3 +1,250 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-recorder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Records a DSH plugin's **real** interface — the pixels of the running app, not a re-render — and turns
|
|
4
|
+
that into a GIF or stills. It also **drives** that interface like a person: typing with real input
|
|
5
|
+
events, clicking buttons, selecting a range of text, switching panels.
|
|
6
|
+
|
|
7
|
+
Two ways to use it:
|
|
8
|
+
|
|
9
|
+
- **The panel**: sidebar → 「录制器」, paste a script, press 「开始录制」. The page executes the script
|
|
10
|
+
while the real window is recorded, then shows the GIF.
|
|
11
|
+
- **Agent tools**: `recorder_scan` → `recorder_demo` for a one-shot recording from a conversation.
|
|
12
|
+
|
|
13
|
+
**Status: v0.1, installed and verified on this machine.** A real notes walk produced a 10.8 s,
|
|
14
|
+
65-frame, 2.0 MB GIF plus three stills (`recordings/rec-20261103-142842/`). The plugin is installed as
|
|
15
|
+
`@tyouter/dsh-recorder` in the `desktop` profile; the row is `include:dsh-recorder`.
|
|
16
|
+
|
|
17
|
+
## The panel
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
Sidebar → 「录制器」 opens a page with two tabs.
|
|
22
|
+
|
|
23
|
+
**录制** — the whole workflow is one centred column:
|
|
24
|
+
|
|
25
|
+
| Part | What it does |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| 录制脚本 | The script, front and centre. JSON steps or one step per line (see below); kept in `localStorage` between visits. |
|
|
28
|
+
| 复制给插件 agent 的 prompt | One click copies a self-contained prompt (the full spec + what to output). Paste it into the agent that owns the plugin, describe the demo, and it returns a script you paste back. `载入示例` restores the notes walk that the verified GIF came from. |
|
|
29
|
+
| 开始录制 | The primary action, a large button under the editor. `排练(不录制)` runs the same steps without recording (real clicks, no video); `停止` ends early. A rehearsal brings the window forward first — a background window throttles the page's timers and would freeze the walk. |
|
|
30
|
+
| 本次录制 | The result of this run: a large GIF preview (click it for a full-size lightbox), its length/frames/size, the path, and the per-step log. The lightbox opens at **100%** — one GIF pixel per device pixel, so the text is exactly the physical size it had on screen; `适应屏幕` fits the whole frame instead (smaller text). |
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
**⚙ 设置** (top right) opens a popover with the parameters — they are no longer mixed into the main
|
|
35
|
+
page:
|
|
36
|
+
|
|
37
|
+
- 输出工作区 — which registered workspace's `recordings/` receives the output.
|
|
38
|
+
- 录制区域 — `功能区` (crop to the main panel), `整个窗口`, `自定义区域` + a CSS selector.
|
|
39
|
+
- 帧率 / GIF 宽度 / 截图时间点 — 6 fps. The GIF width defaults to the content's **physical** pixel
|
|
40
|
+
width (≈1470 on a 125% display) so the preview is never upscaled; lower it to shrink files, and
|
|
41
|
+
remember the encoder cannot add detail beyond the source. Stills are seconds.
|
|
42
|
+
- 铺满当前屏幕 — maximize on the monitor under the cursor.
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
**历史** — a grid of past recordings of the selected workspace (thumbnail, name, length, frames,
|
|
47
|
+
size). Click a card to open it with the GIF, meta, path and copy button. Click the preview (the
|
|
48
|
+
`⤢ 全尺寸` badge) to open the full-size lightbox; click the backdrop or ✕ to close.
|
|
49
|
+
|
|
50
|
+
The detail view also has **删除这段录制**: the first click arms the button, the second confirms and
|
|
51
|
+
removes the recording directory from disk. Only directories directly under a recordings root whose
|
|
52
|
+
name matches `rec-YYYYMMDD-HHMMSS` can be deleted (the route refuses anything else).
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
While a run is active the status pill counts steps (`录制中 00:01 · 3/3 click`) and the floating REC
|
|
59
|
+
pill below the title bar shows the clock and a stop button. A rehearsal gets the same pill in blue
|
|
60
|
+
(`排练 2/10`) with its own stop, so it can be ended even when the script has switched away from this
|
|
61
|
+
page. The panel is in the main area, so a script that opens another plugin's panel will hide it — the
|
|
62
|
+
pill stays available, and coming back to the recorder panel shows the finished result.
|
|
63
|
+
|
|
64
|
+
The pill is **hidden while the pointer is away from the top strip** and appears when the pointer enters
|
|
65
|
+
the top 84 px. That keeps it out of the recording (nothing is drawn over the page during a normal
|
|
66
|
+
walk), while moving the mouse to the top is the stop control when the panel is not visible. It sits at
|
|
67
|
+
`top: 46px` because the very top strip is the window's drag region; a click there belongs to the window
|
|
68
|
+
manager. Pressing 停止 hides the pill immediately while the GIF is assembled, so a long splice never
|
|
69
|
+
looks like an ignored click.
|
|
70
|
+
|
|
71
|
+
### Window size
|
|
72
|
+
|
|
73
|
+
`铺满屏幕` (and, when `maximizeOnStart` is true in the patch, a moment after the plugin activates)
|
|
74
|
+
moves the app window to the monitor under the cursor and maximizes it, so a freshly launched Harness
|
|
75
|
+
fills the current screen instead of opening at its remembered size. Focusing the window for a rehearsal
|
|
76
|
+
never changes its size: a maximized window stays maximized, at the same rect. Window geometry is the
|
|
77
|
+
plugin's business; **UI scale is not** — the interface still follows the Windows display scale of that
|
|
78
|
+
monitor (120 DPI = 125% here). For a 1:1 UI, set that monitor to 100% or launch the app with Chromium's
|
|
79
|
+
`--force-device-scale-factor=1`; a page plugin cannot change the process's DPI mode.
|
|
80
|
+
|
|
81
|
+
Keep the Harness window visible while a walk runs: Chromium throttles a background window's timers,
|
|
82
|
+
which freezes every `sleep`/`wait` in the script (and delays a stop). The rehearsal brings the window
|
|
83
|
+
forward before it starts; the capture does the same by design.
|
|
84
|
+
|
|
85
|
+
### Cursor and click effects
|
|
86
|
+
|
|
87
|
+
The driver paints the pointer into the page, so the effects are part of the recording — no
|
|
88
|
+
post-processing, the frames are the page:
|
|
89
|
+
|
|
90
|
+
- a short blue trail follows a move, including a jump across the screen (two breadcrumbs are laid on
|
|
91
|
+
the straight line when the distance is large);
|
|
92
|
+
- **two expanding blue rings plus a flash** mark the click;
|
|
93
|
+
- the clicked element gets a **temporary blue outline**, so the target is obvious even when the
|
|
94
|
+
pointer is small.
|
|
95
|
+
|
|
96
|
+

|
|
97
|
+
|
|
98
|
+
All of it is disabled under `prefers-reduced-motion` (the outline and the pointer are still drawn).
|
|
99
|
+
|
|
100
|
+
### Recording area
|
|
101
|
+
|
|
102
|
+
`功能区` is the default and is what most demos want: the page measures the main panel's box and sends
|
|
103
|
+
the **insets in CSS pixels plus `devicePixelRatio`**; the Host maps them through the window's real
|
|
104
|
+
client area (Win32 `GetClientRect` + `ClientToScreen`) and crops every frame. The window is still
|
|
105
|
+
maximized for the shoot — the crop is what removes the sidebar and the app title bar, and
|
|
106
|
+
it stays correct on a mixed-DPI desktop because the mapping is measured, not assumed.
|
|
107
|
+
|
|
108
|
+
Because the crop is the **main panel**, the GIF's aspect ratio is the panel's, not the window's
|
|
109
|
+
(e.g. 1544×970 for a 1920×1020 window). The image is still scaled proportionally; if the framing has
|
|
110
|
+
to match the full window, pick `整个窗口`.
|
|
111
|
+
|
|
112
|
+
- `整个窗口` records the maximized window as-is (sidebar included).
|
|
113
|
+
- `自定义区域` takes a CSS selector and crops to that element's box; the element must exist when you
|
|
114
|
+
press start.
|
|
115
|
+
- If another app takes the foreground mid-recording, those frames show that app; the result then
|
|
116
|
+
carries a `⚠` warning rather than pretending the footage is clean.
|
|
117
|
+
|
|
118
|
+

|
|
119
|
+
|
|
120
|
+
The same walk in `功能区` mode: 10.6 s / 64 frames / 705 KB, against 2.0 MB for the full-window cut —
|
|
121
|
+
the sidebar and app title bar are simply not in the frame.
|
|
122
|
+
|
|
123
|
+
### Script syntax
|
|
124
|
+
|
|
125
|
+
One step per line; `|` introduces the text for that step. Lines starting with `#` are comments.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
# 打开记事本,写一段笔记,选中一句
|
|
129
|
+
open notes
|
|
130
|
+
wait .dshn-root
|
|
131
|
+
click .dshn-switch[aria-checked="true"]
|
|
132
|
+
click .dshn-tabs button.dshn-tab:nth-of-type(1)
|
|
133
|
+
set .dshn-textarea |
|
|
134
|
+
click .dshn-textarea
|
|
135
|
+
type .dshn-textarea | 极简笔记\n\n这不是一个新的工具,而是一个关于文字的哲学思考。\n智能不能成为文字的主角。\n功能应当服务于思想。\n书写带来快乐。
|
|
136
|
+
sleep 900
|
|
137
|
+
select .dshn-textarea | 智能不能成为文字的主角。
|
|
138
|
+
sleep 1200
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- `open <panel>` — switch to another plugin's main panel (`notes`, `wuche`, `conversation`, …).
|
|
142
|
+
- `click <selector> [| text]` — click a button/tab; `text` picks among matches.
|
|
143
|
+
- `type <selector> | text` — type character by character (60 ms each); `\n` inserts a newline.
|
|
144
|
+
- `set <selector> | text` — set the value in one go (good for clearing a draft).
|
|
145
|
+
- `select <selector> | text` — highlight that text.
|
|
146
|
+
- `wait <selector> [timeoutMs]` — wait for an element (default 8 s).
|
|
147
|
+
- `sleep <ms>`, `key <selector> <Tab|Enter|Escape>`, `scroll <selector>`, `focus <selector>`.
|
|
148
|
+
|
|
149
|
+
A JSON array of step objects works too — starting the box with `[` switches the parser. Step objects
|
|
150
|
+
use the same `op` names the agent tools use, and can set `ms` on `type`.
|
|
151
|
+
|
|
152
|
+
A step whose element is missing is reported as failed and the walk continues (unless the JSON sets
|
|
153
|
+
`strict: true`). Selectors come from the plugin's real DOM; the agent tool `recorder_scan` prints them.
|
|
154
|
+
|
|
155
|
+
The full, versioned grammar is in [docs/script-spec.md](docs/script-spec.md). Before spending a
|
|
156
|
+
recording, press **排练(不录制)**: it runs the same steps for real and lists `✓`/`✗` per step, so a
|
|
157
|
+
wrong selector shows up in a second instead of in a wasted GIF.
|
|
158
|
+
|
|
159
|
+
### Writing a script with another agent
|
|
160
|
+
|
|
161
|
+
The plugin's own agent knows its components and class names, but not the recorder's grammar. Hand it
|
|
162
|
+
the prompt:
|
|
163
|
+
|
|
164
|
+
1. Press **复制给插件 agent 的 prompt**.
|
|
165
|
+
2. Paste it into that plugin's agent conversation, replace the last paragraph with what you want to
|
|
166
|
+
see (`打开记录页,点 Structure,输入一句话…`), and send.
|
|
167
|
+
3. The agent answers with one code block. Paste it into 录制脚本, press **排练(不录制)**, then **开始录制**.
|
|
168
|
+
|
|
169
|
+
The prompt carries the whole spec, so the other agent does not need this repository. If you also want
|
|
170
|
+
it to use *real* selectors, send it the output of `recorder_scan` (the agent tool) for the panel you
|
|
171
|
+
mean; the prompt tells it to prefer that list.
|
|
172
|
+
|
|
173
|
+
## Agent tools
|
|
174
|
+
|
|
175
|
+
| Tool | What it does |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `recorder_windows` | Lists recordable windows (title, screen rect, size) and reports the capture backend. |
|
|
178
|
+
| `recorder_scan` | Reads the live DOM and returns the visible interactive elements with real CSS selectors. |
|
|
179
|
+
| `recorder_start` / `recorder_stop` | Manual start/stop; `start` returns a `probe` frame to check the target. |
|
|
180
|
+
| `recorder_drive` | Runs steps against the live page without recording, or while a recording runs. |
|
|
181
|
+
| `recorder_demo` | start → drive → settle → stop in one call. |
|
|
182
|
+
|
|
183
|
+
## How it works
|
|
184
|
+
|
|
185
|
+
- **Capturing** pixels happens in the Host half: screen capture of the real window through
|
|
186
|
+
`tools/record-window.py` (Win32 + Pillow). It knows nothing about DSH plugins.
|
|
187
|
+
- **Driving** happens in the page that owns the UI. The recorder's **own client half** lives in the
|
|
188
|
+
same page as every other plugin, so it operates the target's real DOM. The plugin under test does
|
|
189
|
+
not have to change at all — no dev flag, no injected script, no debug port.
|
|
190
|
+
- **Agent-triggered** runs use a job bridge: the page polls `/recorder/api/poll`, runs the handed-out
|
|
191
|
+
steps, and posts the report back. The **panel does not use that bridge** — it runs the same driver
|
|
192
|
+
locally (`POST /start` → steps → `POST /stop`), so a throttled background timer can never stop a
|
|
193
|
+
demo from starting. `/progress` still feeds the pill's step counter on both paths.
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
run / recorder_demo (agent path)
|
|
197
|
+
│ Host: resolve window → activate → maximize → start frames
|
|
198
|
+
├────────► /recorder/api/poll ──► page: run steps (type/click/select/openPanel)
|
|
199
|
+
│ Host: wait for the report ◄── /recorder/api/report (and /progress)
|
|
200
|
+
│ Host: settle → STOP → GIF assembly (Pillow) → restore window
|
|
201
|
+
└── { gif, stills, frames, seconds, walk: [step results] }
|
|
202
|
+
|
|
203
|
+
panel: POST /start → runSteps() in the page → POST /stop (no poll involved)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Because a screen grab cannot see through another app, a run raises the window (minimize + restore,
|
|
207
|
+
which is the one activation Windows never refuses) and maximizes it for the length of the shoot; the
|
|
208
|
+
previous placement is restored afterwards. The painted cursor and the REC pill are part of the page
|
|
209
|
+
and are recorded in both modes.
|
|
210
|
+
|
|
211
|
+
## Installing / updating
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
plugin_manager(install_bundle, target = <this directory>)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
A newly installed bundle's **client** half appeared in the running page without a reload on this
|
|
218
|
+
build. Host-side JavaScript changes are **not** picked up by editing files: disable and re-enable the
|
|
219
|
+
row with `plugin_manager(set_plugin)`, or restart the app.
|
|
220
|
+
|
|
221
|
+
## Limits (v0.1)
|
|
222
|
+
|
|
223
|
+
- One recording at a time; output goes to `<workspace>/recordings/rec-<stamp>/`.
|
|
224
|
+
- Screen capture means *the screen*: if the window is moved or covered mid-recording, the footage shows
|
|
225
|
+
that. `recorder_start` returns a `probe` frame for exactly this check.
|
|
226
|
+
- The recorded window is maximized for the shoot and restored afterwards.
|
|
227
|
+
- No zoom, chapter cards, or post-processed cursor: the cursor is a DOM overlay recorded with the page.
|
|
228
|
+
- The panel's output workspace is a registered-workspace picker; the agent tools use the calling
|
|
229
|
+
session's cwd.
|
|
230
|
+
- The plugin under test is untouched. The notes fixture clears its draft and never saves.
|
|
231
|
+
|
|
232
|
+
## Files
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
index.js Host half: engine wiring, tools, /recorder/api
|
|
236
|
+
lib/engine.js one recording + one page operation at a time; the job bridge; workspaces
|
|
237
|
+
lib/capture.js capture backends (Electron capturePage when available, else screen) + stills
|
|
238
|
+
lib/python.js locating the bundled Python and running the helper
|
|
239
|
+
lib/tools.js the six model-facing tools
|
|
240
|
+
client.js page half: poll loop, step driver, DOM scan, REC pill, cursor, recorder panel
|
|
241
|
+
tools/record-window.py window/rect capture, stop-file protocol, probe frames, GIF assembly
|
|
242
|
+
examples/notes-demo.json the verified walk as JSON steps
|
|
243
|
+
docs/panel.png the recorder page
|
|
244
|
+
docs/panel-running.png the page during a run (REC pill + step counter)
|
|
245
|
+
docs/crop-example.png a 功能区-mode frame (no sidebar, no title bar)
|
|
246
|
+
docs/click-effect.png the pointer, trail and double click ring over a clicked tab
|
|
247
|
+
docs/script-spec.md the versioned script grammar (also the source of the copied prompt)
|
|
248
|
+
HANDOFF.md the original brief (kept as the design record)
|
|
249
|
+
docs/findings.md measurements: what was proved, what failed, and why
|
|
250
|
+
```
|