@vosjs/cli 0.33.0 → 0.34.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.
- package/README.md +1 -1
- package/dist/{chunk-6O55OD6D.js → chunk-43FCXWNS.js} +163 -43
- package/dist/chunk-43FCXWNS.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +28 -2
- package/dist/index.js +1 -1
- package/dist/{run-X6XJCMIZ.js → run-EOIIBITT.js} +2 -2
- package/package.json +5 -5
- package/schema/actions.schema.json +24 -38
- package/dist/chunk-6O55OD6D.js.map +0 -1
- /package/dist/{run-X6XJCMIZ.js.map → run-EOIIBITT.js.map} +0 -0
package/README.md
CHANGED
|
@@ -143,7 +143,7 @@ A re-record replaces the footage, the cursor track, the frames and everything de
|
|
|
143
143
|
}
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
Seven verbs: `wait`, `hover` (`ms` 700), `click
|
|
146
|
+
Seven verbs: `wait`, `hover` (`ms` 700, the dwell), `click` (`ms` the settle after the press, 150), `type` (`delayMs` 40 per key, paced by the clock; `focus: false` skips the focusing click, for a submitting Enter; `ms` the settle after, 150), `scroll` (`ms` the settle, 200), `move`, `drag` (press, move, release: a range input, a canvas element, a timeline clip; `ms` the travel, 700). The recorder's pace is the script's: a gesture is driven by the clock, so a page that answers each pointer sample slowly costs samples, never seconds, and the only pauses are the ones the script names (`wait`, a hover's dwell, a settle). `vos record` ends with a pace line, what the script asked, what the gestures added, what the page cost, and the steps that ran slow, and the `--json` done event carries it as `pace`. Every step takes an optional unique `id`; give steps ids so a span anchored to a step survives script edits and `vos plan --reuse` can follow it, and so a layer can name the element a step touched (`overlays[].pin.step`; the recorder keeps each hover, click, type and drag step's element rect on `meta.steps[].rect`). Because the CLI issues every input itself, the cursor track is synthesized with exact coordinates, exact timing and fresh element rects, which is what powers element-aware auto-zoom and click effects downstream. The schema is [`schema/actions.schema.json`](./schema/actions.schema.json); `vos validate actions.json` checks a script without running anything.
|
|
147
147
|
|
|
148
148
|
Verified the flow in agent-browser already? `vos actions from-agent-browser steps.jsonl [--out actions.json] [--url] [--viewport WxH]` writes the script from that walk (each command kept beside its `--json` result, the batch record shape; refs resolve through the last `snapshot -i`), and names every step the recorder cannot follow rather than dropping it.
|
|
149
149
|
|
|
@@ -156,6 +156,8 @@ function validateActions(value) {
|
|
|
156
156
|
errors.push(`${at2}: ${s.do} needs a selector`);
|
|
157
157
|
if (s.do === "wait" && typeof s.ms !== "number")
|
|
158
158
|
errors.push(`${at2}: wait needs ms`);
|
|
159
|
+
if (s.ms !== void 0 && (typeof s.ms !== "number" || !Number.isFinite(s.ms) || s.ms < 0))
|
|
160
|
+
errors.push(`${at2}: ms must be a number of milliseconds \u2265 0`);
|
|
159
161
|
if (s.do === "type" && typeof s.text !== "string")
|
|
160
162
|
errors.push(`${at2}: type needs text`);
|
|
161
163
|
if (s.do === "type" && s.focus !== void 0 && typeof s.focus !== "boolean")
|
|
@@ -6120,9 +6122,99 @@ function cappedLine(capSeconds) {
|
|
|
6120
6122
|
return `stopped at ${formatDurationCap(capSeconds)} (--max-duration ${capSeconds}); the remaining steps did not run`;
|
|
6121
6123
|
}
|
|
6122
6124
|
|
|
6125
|
+
// src/plugin/pace.ts
|
|
6126
|
+
var POINTER_MS_PER_PX = 0.7;
|
|
6127
|
+
var POINTER_MIN_MS = 250;
|
|
6128
|
+
var POINTER_MAX_MS = 800;
|
|
6129
|
+
var MOTION_TICK_MS = 16;
|
|
6130
|
+
var SETTLE_MS = {
|
|
6131
|
+
click: 150,
|
|
6132
|
+
type: 150,
|
|
6133
|
+
scroll: 200,
|
|
6134
|
+
drag: 80
|
|
6135
|
+
};
|
|
6136
|
+
var PRESS_LEAD_MS = 80;
|
|
6137
|
+
var PRESS_HOLD_MS = 70;
|
|
6138
|
+
var SCROLL_SETTLE_MS = 120;
|
|
6139
|
+
var TRAILING_HOLD_MS = 400;
|
|
6140
|
+
function pointerTravelMs(dist) {
|
|
6141
|
+
return Math.min(
|
|
6142
|
+
POINTER_MAX_MS,
|
|
6143
|
+
Math.max(POINTER_MIN_MS, Math.round(dist * POINTER_MS_PER_PX))
|
|
6144
|
+
);
|
|
6145
|
+
}
|
|
6146
|
+
function settleMs(step) {
|
|
6147
|
+
if (typeof step.ms === "number" && step.ms >= 0) return step.ms;
|
|
6148
|
+
const d = step.do;
|
|
6149
|
+
return SETTLE_MS[d] ?? 0;
|
|
6150
|
+
}
|
|
6151
|
+
var easeInOutCubic = (u) => u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
|
6152
|
+
async function clockMotion(dur, at2, clock) {
|
|
6153
|
+
const start = clock.now();
|
|
6154
|
+
let samples = 0;
|
|
6155
|
+
for (; ; ) {
|
|
6156
|
+
const elapsed = clock.now() - start;
|
|
6157
|
+
if (elapsed >= dur) break;
|
|
6158
|
+
await at2(easeInOutCubic(Math.min(1, elapsed / dur)));
|
|
6159
|
+
samples++;
|
|
6160
|
+
const next = start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS;
|
|
6161
|
+
const wait = Math.min(next, start + dur) - clock.now();
|
|
6162
|
+
if (wait > 0) await clock.sleep(wait);
|
|
6163
|
+
}
|
|
6164
|
+
await at2(1);
|
|
6165
|
+
return samples + 1;
|
|
6166
|
+
}
|
|
6167
|
+
async function clockTyping(chars, delay, type, clock) {
|
|
6168
|
+
const start = clock.now();
|
|
6169
|
+
for (let i = 0; i < chars.length; i++) {
|
|
6170
|
+
await type(chars[i], i);
|
|
6171
|
+
const due = start + (i + 1) * delay;
|
|
6172
|
+
const wait = due - clock.now();
|
|
6173
|
+
if (wait > 0 && i < chars.length - 1) await clock.sleep(wait);
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
function askedMs(step) {
|
|
6177
|
+
switch (step.do) {
|
|
6178
|
+
case "wait":
|
|
6179
|
+
return step.ms ?? 0;
|
|
6180
|
+
case "hover":
|
|
6181
|
+
return step.ms ?? 700;
|
|
6182
|
+
case "drag":
|
|
6183
|
+
return step.ms ?? 700;
|
|
6184
|
+
case "type":
|
|
6185
|
+
return (step.text?.length ?? 0) * (step.delayMs ?? 40);
|
|
6186
|
+
default:
|
|
6187
|
+
return 0;
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
function paceReport(steps) {
|
|
6191
|
+
const askedMs2 = steps.reduce((a, s) => a + s.askedMs, 0);
|
|
6192
|
+
const gestureMs = steps.reduce((a, s) => a + s.gestureMs, 0);
|
|
6193
|
+
const wallMs = steps.reduce((a, s) => a + s.wallMs, 0);
|
|
6194
|
+
const overheadMs = Math.max(0, wallMs - askedMs2 - gestureMs);
|
|
6195
|
+
const slow = steps.filter((s) => s.wallMs > (s.askedMs + s.gestureMs) * 1.34 + 60).map((s) => ({
|
|
6196
|
+
step: s.step,
|
|
6197
|
+
do: s.do,
|
|
6198
|
+
askedMs: s.askedMs,
|
|
6199
|
+
wallMs: s.wallMs
|
|
6200
|
+
}));
|
|
6201
|
+
return {
|
|
6202
|
+
askedMs: askedMs2,
|
|
6203
|
+
gestureMs,
|
|
6204
|
+
wallMs,
|
|
6205
|
+
overheadMs,
|
|
6206
|
+
overheadPct: wallMs > 0 ? Math.round(overheadMs / wallMs * 100) : 0,
|
|
6207
|
+
slow
|
|
6208
|
+
};
|
|
6209
|
+
}
|
|
6210
|
+
function paceLine(r) {
|
|
6211
|
+
const s = (ms) => `${(ms / 1e3).toFixed(1)} s`;
|
|
6212
|
+
const slow = r.slow.length ? `; slow: ${r.slow.map((x) => `#${x.step} ${x.do} ${s(x.wallMs)} for ${s(x.askedMs)} asked`).join(", ")}` : "";
|
|
6213
|
+
return `pace: the script asked ${s(r.askedMs)}, the gestures added ${s(r.gestureMs)}, the take ran ${s(r.wallMs)} (${r.overheadPct} % overhead${slow})`;
|
|
6214
|
+
}
|
|
6215
|
+
|
|
6123
6216
|
// src/plugin/recorder.ts
|
|
6124
6217
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6125
|
-
var easeInOutCubic = (u) => u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
|
6126
6218
|
function jpegDims(buf) {
|
|
6127
6219
|
let i = 2;
|
|
6128
6220
|
while (i < buf.length - 9) {
|
|
@@ -6181,26 +6273,30 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6181
6273
|
const emit = (e) => events.push({ t: now(), ...e });
|
|
6182
6274
|
await page.mouse.move(cur.x, cur.y);
|
|
6183
6275
|
emit({ x: cur.x, y: cur.y, type: "move" });
|
|
6276
|
+
const clock = { now: () => Date.now(), sleep };
|
|
6184
6277
|
const moveTo = async (tx, ty) => {
|
|
6185
6278
|
const dist = Math.hypot(tx - cur.x, ty - cur.y);
|
|
6186
|
-
const dur = Math.min(1200, Math.max(350, dist * 1.4));
|
|
6187
|
-
const steps = Math.max(6, Math.round(dur / 16));
|
|
6188
6279
|
const from = { ...cur };
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6280
|
+
await clockMotion(
|
|
6281
|
+
pointerTravelMs(dist),
|
|
6282
|
+
async (u) => {
|
|
6283
|
+
cur.x = from.x + (tx - from.x) * u;
|
|
6284
|
+
cur.y = from.y + (ty - from.y) * u;
|
|
6285
|
+
await page.mouse.move(cur.x, cur.y);
|
|
6286
|
+
emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: "move" });
|
|
6287
|
+
},
|
|
6288
|
+
clock
|
|
6289
|
+
);
|
|
6197
6290
|
};
|
|
6198
6291
|
const boxOf = async (selector) => {
|
|
6199
6292
|
const loc = page.locator(selector).first();
|
|
6200
6293
|
try {
|
|
6201
6294
|
await loc.waitFor({ state: "visible", timeout: 8e3 });
|
|
6295
|
+
const before = await page.evaluate(() => [window.scrollX, window.scrollY]).catch(() => [0, 0]);
|
|
6202
6296
|
await loc.scrollIntoViewIfNeeded();
|
|
6203
|
-
await
|
|
6297
|
+
const after = await page.evaluate(() => [window.scrollX, window.scrollY]).catch(() => before);
|
|
6298
|
+
if (before[0] !== after[0] || before[1] !== after[1])
|
|
6299
|
+
await sleep(SCROLL_SETTLE_MS);
|
|
6204
6300
|
const bb = await loc.boundingBox();
|
|
6205
6301
|
if (!bb) return null;
|
|
6206
6302
|
return { x: bb.x, y: bb.y, w: bb.width, h: bb.height };
|
|
@@ -6211,7 +6307,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6211
6307
|
};
|
|
6212
6308
|
const clickAt = async (rect) => {
|
|
6213
6309
|
await moveTo(rect.x + rect.w / 2, rect.y + rect.h / 2);
|
|
6214
|
-
await sleep(
|
|
6310
|
+
await sleep(PRESS_LEAD_MS);
|
|
6215
6311
|
emit({
|
|
6216
6312
|
x: Math.round(cur.x),
|
|
6217
6313
|
y: Math.round(cur.y),
|
|
@@ -6220,7 +6316,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6220
6316
|
rect
|
|
6221
6317
|
});
|
|
6222
6318
|
await page.mouse.down();
|
|
6223
|
-
await sleep(
|
|
6319
|
+
await sleep(PRESS_HOLD_MS);
|
|
6224
6320
|
await page.mouse.up();
|
|
6225
6321
|
emit({
|
|
6226
6322
|
x: Math.round(cur.x),
|
|
@@ -6232,6 +6328,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6232
6328
|
};
|
|
6233
6329
|
const skipped = [];
|
|
6234
6330
|
const stepSpans = [];
|
|
6331
|
+
const paces = [];
|
|
6235
6332
|
let capped = false;
|
|
6236
6333
|
for (const [stepIdx, step] of actions.steps.entries()) {
|
|
6237
6334
|
if (capReached(now(), maxSeconds)) {
|
|
@@ -6239,6 +6336,13 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6239
6336
|
break;
|
|
6240
6337
|
}
|
|
6241
6338
|
const stepStart = now();
|
|
6339
|
+
let gestureMs = 0;
|
|
6340
|
+
const gesture = async (fn) => {
|
|
6341
|
+
const t = now();
|
|
6342
|
+
const out = await fn();
|
|
6343
|
+
gestureMs += now() - t;
|
|
6344
|
+
return out;
|
|
6345
|
+
};
|
|
6242
6346
|
const skippedBefore = skipped.length;
|
|
6243
6347
|
const urlBefore = page.url();
|
|
6244
6348
|
let stepRect = null;
|
|
@@ -6253,7 +6357,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6253
6357
|
const rect = await boxOf(step.selector);
|
|
6254
6358
|
stepRect = rect;
|
|
6255
6359
|
if (rect) {
|
|
6256
|
-
await moveTo(rect.x + rect.w / 2, rect.y + rect.h / 2);
|
|
6360
|
+
await gesture(() => moveTo(rect.x + rect.w / 2, rect.y + rect.h / 2));
|
|
6257
6361
|
log(`hover ${step.selector}`);
|
|
6258
6362
|
await sleep(step.ms ?? 700);
|
|
6259
6363
|
} else
|
|
@@ -6264,9 +6368,9 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6264
6368
|
const rect = await boxOf(step.selector);
|
|
6265
6369
|
stepRect = rect;
|
|
6266
6370
|
if (rect) {
|
|
6267
|
-
await clickAt(rect);
|
|
6371
|
+
await gesture(() => clickAt(rect));
|
|
6268
6372
|
log(`click ${step.selector}`);
|
|
6269
|
-
await sleep(
|
|
6373
|
+
await gesture(() => sleep(settleMs(step)));
|
|
6270
6374
|
} else
|
|
6271
6375
|
skipped.push({ step: stepIdx, do: step.do, selector: step.selector });
|
|
6272
6376
|
break;
|
|
@@ -6275,7 +6379,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6275
6379
|
const rect = await boxOf(step.selector);
|
|
6276
6380
|
stepRect = rect;
|
|
6277
6381
|
if (rect) {
|
|
6278
|
-
if (step.focus !== false) await clickAt(rect);
|
|
6382
|
+
if (step.focus !== false) await gesture(() => clickAt(rect));
|
|
6279
6383
|
const delay = step.delayMs ?? 40;
|
|
6280
6384
|
const ping = () => emit({
|
|
6281
6385
|
x: Math.round(rect.x + rect.w / 2),
|
|
@@ -6284,17 +6388,21 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6284
6388
|
rect
|
|
6285
6389
|
});
|
|
6286
6390
|
let lastPing = -Infinity;
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6391
|
+
await clockTyping(
|
|
6392
|
+
[...step.text],
|
|
6393
|
+
delay,
|
|
6394
|
+
async (ch) => {
|
|
6395
|
+
if (now() - lastPing >= 350) {
|
|
6396
|
+
lastPing = now();
|
|
6397
|
+
ping();
|
|
6398
|
+
}
|
|
6399
|
+
await page.keyboard.type(ch);
|
|
6400
|
+
},
|
|
6401
|
+
clock
|
|
6402
|
+
);
|
|
6295
6403
|
ping();
|
|
6296
6404
|
log(`type "${step.text}" into ${step.selector}`);
|
|
6297
|
-
await sleep(
|
|
6405
|
+
await gesture(() => sleep(settleMs(step)));
|
|
6298
6406
|
} else
|
|
6299
6407
|
skipped.push({ step: stepIdx, do: step.do, selector: step.selector });
|
|
6300
6408
|
break;
|
|
@@ -6307,7 +6415,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6307
6415
|
emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: "scroll" });
|
|
6308
6416
|
await sleep(40);
|
|
6309
6417
|
}
|
|
6310
|
-
await sleep(
|
|
6418
|
+
await gesture(() => sleep(settleMs(step)));
|
|
6311
6419
|
break;
|
|
6312
6420
|
}
|
|
6313
6421
|
case "drag": {
|
|
@@ -6331,8 +6439,8 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6331
6439
|
start = { x: step.x, y: step.y };
|
|
6332
6440
|
}
|
|
6333
6441
|
if (!start) break;
|
|
6334
|
-
await moveTo(start.x, start.y);
|
|
6335
|
-
await sleep(
|
|
6442
|
+
await gesture(() => moveTo(start.x, start.y));
|
|
6443
|
+
await gesture(() => sleep(PRESS_LEAD_MS));
|
|
6336
6444
|
emit({
|
|
6337
6445
|
x: Math.round(cur.x),
|
|
6338
6446
|
y: Math.round(cur.y),
|
|
@@ -6341,19 +6449,20 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6341
6449
|
rect
|
|
6342
6450
|
});
|
|
6343
6451
|
await page.mouse.down();
|
|
6344
|
-
await sleep(
|
|
6452
|
+
await gesture(() => sleep(PRESS_HOLD_MS));
|
|
6345
6453
|
const dur = step.ms ?? 700;
|
|
6346
|
-
const steps = Math.max(8, Math.round(dur / 16));
|
|
6347
6454
|
const from = { ...cur };
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6455
|
+
await clockMotion(
|
|
6456
|
+
dur,
|
|
6457
|
+
async (u) => {
|
|
6458
|
+
cur.x = from.x + (step.tx - from.x) * u;
|
|
6459
|
+
cur.y = from.y + (step.ty - from.y) * u;
|
|
6460
|
+
await page.mouse.move(cur.x, cur.y);
|
|
6461
|
+
emit({ x: Math.round(cur.x), y: Math.round(cur.y), type: "move" });
|
|
6462
|
+
},
|
|
6463
|
+
clock
|
|
6464
|
+
);
|
|
6465
|
+
await gesture(() => sleep(settleMs({ do: "drag" })));
|
|
6357
6466
|
await page.mouse.up();
|
|
6358
6467
|
emit({
|
|
6359
6468
|
x: Math.round(cur.x),
|
|
@@ -6388,8 +6497,15 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6388
6497
|
}
|
|
6389
6498
|
} : {}
|
|
6390
6499
|
});
|
|
6500
|
+
paces.push({
|
|
6501
|
+
step: stepIdx,
|
|
6502
|
+
do: step.do,
|
|
6503
|
+
askedMs: askedMs(step),
|
|
6504
|
+
gestureMs,
|
|
6505
|
+
wallMs: now() - stepStart
|
|
6506
|
+
});
|
|
6391
6507
|
}
|
|
6392
|
-
if (!capped) await sleep(
|
|
6508
|
+
if (!capped) await sleep(TRAILING_HOLD_MS);
|
|
6393
6509
|
if (capped || capReached(now(), maxSeconds)) {
|
|
6394
6510
|
capped = true;
|
|
6395
6511
|
log(` ${cappedLine(maxSeconds)}`);
|
|
@@ -6441,10 +6557,13 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6441
6557
|
await writeJson(paths.meta, meta, true);
|
|
6442
6558
|
await writeJson(paths.framesIndex, frames);
|
|
6443
6559
|
await writeJson(paths.actions, { ...actions, url }, true);
|
|
6560
|
+
const pace = paceReport(paces);
|
|
6561
|
+
log(` ${paceLine(pace)}`);
|
|
6444
6562
|
return {
|
|
6445
6563
|
events,
|
|
6446
6564
|
frames,
|
|
6447
6565
|
meta,
|
|
6566
|
+
pace,
|
|
6448
6567
|
skipped,
|
|
6449
6568
|
navTimeout,
|
|
6450
6569
|
freezes,
|
|
@@ -9944,6 +10063,7 @@ async function cmdRecord(argv) {
|
|
|
9944
10063
|
freezes: rec.freezes,
|
|
9945
10064
|
freezePct: rec.freezePct,
|
|
9946
10065
|
capped: rec.capped,
|
|
10066
|
+
pace: rec.pace,
|
|
9947
10067
|
...strictFail ? { strictFailed: true } : {}
|
|
9948
10068
|
},
|
|
9949
10069
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}` : `Take ready: ${outDir}
|
|
@@ -11119,4 +11239,4 @@ export {
|
|
|
11119
11239
|
convertAgentBrowser,
|
|
11120
11240
|
run
|
|
11121
11241
|
};
|
|
11122
|
-
//# sourceMappingURL=chunk-
|
|
11242
|
+
//# sourceMappingURL=chunk-43FCXWNS.js.map
|