autokap 1.5.4 → 1.5.7
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/assets/skill/OPCODE-REFERENCE.md +13 -0
- package/assets/skill/SKILL.md +5 -5
- package/dist/action-verifier.d.ts +1 -1
- package/dist/action-verifier.js +68 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +9 -6
- package/dist/cli-contract.d.ts +1 -0
- package/dist/cli-contract.js +1 -0
- package/dist/cli-runner-local.d.ts +1 -0
- package/dist/cli-runner-local.js +1 -0
- package/dist/cli-runner.d.ts +6 -0
- package/dist/cli-runner.js +6 -3
- package/dist/cli.js +1 -0
- package/dist/execution-schema.d.ts +105 -45
- package/dist/execution-schema.js +35 -2
- package/dist/execution-types.d.ts +4 -2
- package/dist/opcode-runner.js +137 -36
- package/dist/overlay-engine.js +18 -4
- package/dist/postcondition.js +74 -41
- package/dist/program-signing.d.ts +10 -0
- package/dist/recovery-chain.js +17 -32
- package/dist/selector-resolver.js +25 -4
- package/dist/smart-wait.js +11 -2
- package/dist/video-narration-schema.d.ts +10 -0
- package/dist/web-playwright-local.d.ts +2 -2
- package/dist/web-playwright-local.js +2 -2
- package/package.json +1 -1
|
@@ -47,6 +47,8 @@ Fallback when CSS selector fails. The runtime resolves via Playwright semantic l
|
|
|
47
47
|
| `threshold` | number (0-1) | `screenshot_stable` | Pixel diff tolerance. Default: `0.01` |
|
|
48
48
|
| `waitMs` | number | all except `always` | Max polling time (ms). Default: `5000` |
|
|
49
49
|
|
|
50
|
+
`any_change` is verifier-backed: the runtime must observe a URL, DOM, element state, overlay, or scroll change after the action. No-op actions fail.
|
|
51
|
+
|
|
50
52
|
---
|
|
51
53
|
|
|
52
54
|
## NAVIGATE
|
|
@@ -353,6 +355,8 @@ Assert the current URL matches a pattern. Pure validation, no navigation.
|
|
|
353
355
|
**Can:** Validate the browser is on the expected page. Useful as a checkpoint before capture.
|
|
354
356
|
**Cannot:** Navigate or change the URL. Use NAVIGATE for that.
|
|
355
357
|
|
|
358
|
+
`urlPattern` is the assertion source of truth. Use `postcondition: { "type": "always" }` or repeat the same `route_matches` pattern.
|
|
359
|
+
|
|
356
360
|
```json
|
|
357
361
|
{ "kind": "ASSERT_ROUTE", "urlPattern": "/dashboard/**", "postcondition": { "type": "route_matches", "pattern": "/dashboard/**" } }
|
|
358
362
|
```
|
|
@@ -369,6 +373,8 @@ Assert that specific elements are visible on the page. Pure validation.
|
|
|
369
373
|
**Can:** Validate page state before capture. Confirm multiple elements are rendered.
|
|
370
374
|
**Cannot:** Interact with elements or wait for them. Use WAIT_FOR for dynamic elements.
|
|
371
375
|
|
|
376
|
+
`selectors` and `matchAll` are the assertion source of truth. Use `postcondition: { "type": "always" }` or an `element_visible` postcondition for one of the asserted selectors.
|
|
377
|
+
|
|
372
378
|
```json
|
|
373
379
|
{ "kind": "ASSERT_SURFACE", "selectors": ["[data-ak=\"header\"]", "[data-ak=\"sidebar\"]"], "matchAll": true, "postcondition": { "type": "always" } }
|
|
374
380
|
```
|
|
@@ -382,16 +388,23 @@ Capture a screenshot of the viewport or a specific element.
|
|
|
382
388
|
| `captureId` | string | no | Stable identifier for Studio/dev links. Should match preset page/element id |
|
|
383
389
|
| `captureName` | string | no | Human-readable label shown in Studio |
|
|
384
390
|
| `elementSelector` | string | no | CSS selector for element-level capture (crops to element bounds) |
|
|
391
|
+
| `outscale` | OutscaleConfig | no | Padding around the captured element (only applied with `elementSelector`). Typically set by the user post-generation. Omit by default. |
|
|
385
392
|
|
|
386
393
|
**Can:** Full-page viewport capture, element-level capture (cropped), LLM verification (detects blank/error/loading/overlay states), alt text generation, favicon extraction.
|
|
387
394
|
**Cannot:** Capture content below the fold in a single shot (use SCROLL first). Capture cross-origin iframe content.
|
|
388
395
|
|
|
389
396
|
**Tip:** Without `elementSelector`, captures the full viewport. With `elementSelector`, captures only that element's bounding box — useful for component-level screenshots.
|
|
390
397
|
|
|
398
|
+
**`outscale` shape:** all fields optional. `padding` is uniform (pixels). `paddingTop/Right/Bottom/Left` override per side. `paddingPercent` (0–100) scales with the element. `clampToViewport` (default `true`) prevents the crop from exceeding the document. `backgroundColor` fills any uncovered area.
|
|
399
|
+
|
|
391
400
|
```json
|
|
392
401
|
{ "kind": "CAPTURE_SCREENSHOT", "captureId": "dashboard-main", "captureName": "Dashboard", "elementSelector": "[data-ak=\"main-content\"]", "postcondition": { "type": "always" } }
|
|
393
402
|
```
|
|
394
403
|
|
|
404
|
+
```json
|
|
405
|
+
{ "kind": "CAPTURE_SCREENSHOT", "captureName": "Pricing card", "elementSelector": "[data-ak=\"pricing\"]", "outscale": { "padding": 24 }, "postcondition": { "type": "always" } }
|
|
406
|
+
```
|
|
407
|
+
|
|
395
408
|
## BEGIN_CLIP
|
|
396
409
|
|
|
397
410
|
Start recording a clip. All interactions between BEGIN_CLIP and END_CLIP are recorded.
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -150,7 +150,7 @@ interface VariantSpec {
|
|
|
150
150
|
| `DISMISS_OVERLAYS` | no | — | `overlay_dismissed` | Always after NAVIGATE |
|
|
151
151
|
| `CLICK` | yes | `button?` | `element_visible` / `route_matches` | Postcondition = what CHANGED |
|
|
152
152
|
| `TYPE` | yes | `text`, `clearFirst` | `any_change` | `{{email}}` / `{{password}}` for creds |
|
|
153
|
-
| `PRESS_KEY` | no | `key` | `any_change` | `"Enter"`, `"Escape"`, `"Tab"`, etc
|
|
153
|
+
| `PRESS_KEY` | no | `key` | specific result / `any_change` | `"Enter"`, `"Escape"`, `"Tab"`, etc.; use `any_change` only when the key must visibly change state |
|
|
154
154
|
| `WAIT_FOR` | yes* | `state` | `element_visible` | `"visible"` or `"attached"` (DOM only) |
|
|
155
155
|
| `SLEEP` | no | `durationMs` (1..60000), `narrationTextByLocale?` | `always` | Pause N ms. Reserved for video narration anchors. Use `durationMs: 1` as a placeholder; AutoKap rewrites it during `autokap run` after generating TTS. |
|
|
156
156
|
| `SCROLL` | no | `direction`, `targetSelector?`, `amount?` | `element_visible` | Use `targetSelector` for precise scroll |
|
|
@@ -161,9 +161,9 @@ interface VariantSpec {
|
|
|
161
161
|
| `DRAG` | yes | `toSelector?` / `toTarget?` / `offset?` | `element_visible` / `any_change` | Animated cursor A→B. Use `toSelector` for Kanban-style drops, `offset` for sliders / canvas |
|
|
162
162
|
| `SET_LOCALE` | no | `locale`, `method`, `storageHints?` | `always` | Use `"$variant"`. Prefer `method: "storage"` |
|
|
163
163
|
| `SET_THEME` | no | `theme`, `method`, `storageHints?` | `always` | Use `"$variant"`. Prefer `method: "storage"` |
|
|
164
|
-
| `ASSERT_ROUTE` | no | `urlPattern` | `route_matches` |
|
|
165
|
-
| `ASSERT_SURFACE` | no | `selectors[]`, `matchAll` | `always` |
|
|
166
|
-
| `CAPTURE_SCREENSHOT` | no | `captureId`, `captureName`, `elementSelector?` | `always` | `elementSelector` for element-level crop |
|
|
164
|
+
| `ASSERT_ROUTE` | no | `urlPattern` | `always` / matching `route_matches` | `urlPattern` is the source of truth |
|
|
165
|
+
| `ASSERT_SURFACE` | no | `selectors[]`, `matchAll` | `always` / matching `element_visible` | `selectors` + `matchAll` are the source of truth |
|
|
166
|
+
| `CAPTURE_SCREENSHOT` | no | `captureId`, `captureName`, `elementSelector?`, `outscale?` | `always` | `elementSelector` for element-level crop. `outscale` adds padding around the element (user-edited post-generation; omit by default) |
|
|
167
167
|
| `BEGIN_CLIP` | no | `clipId`, `clipName` | `always` | Start recording |
|
|
168
168
|
| `END_CLIP` | no | `clipId`, `clipName` | `always` | Stop recording. Same `clipId` as BEGIN_CLIP |
|
|
169
169
|
| `CLONE_ELEMENT` | yes | `sourceSelector`, `containerSelector`, `count` | `always` | **Non-blocking.** Duplicate a template element N times |
|
|
@@ -183,7 +183,7 @@ interface VariantSpec {
|
|
|
183
183
|
| `text_contains` | `selector`, `text` | After TYPE, SELECT_OPTION |
|
|
184
184
|
| `overlay_dismissed` | — | After DISMISS_OVERLAYS |
|
|
185
185
|
| `screenshot_stable` | `threshold?` (0-1), `waitMs?` | Before CAPTURE_SCREENSHOT when page has animations |
|
|
186
|
-
| `any_change` | — |
|
|
186
|
+
| `any_change` | — | Verifier-backed state change after TYPE, CLICK, SELECT_OPTION, DOUBLE_CLICK, DRAG, etc.; no-op actions fail |
|
|
187
187
|
| `always` | — | CAPTURE_SCREENSHOT, SET_LOCALE, SET_THEME, CHECK, BEGIN/END_CLIP |
|
|
188
188
|
|
|
189
189
|
## What Presets Can and Cannot Do
|
|
@@ -13,7 +13,7 @@ export interface ActionVerification {
|
|
|
13
13
|
/** Summary for logging */
|
|
14
14
|
summary: string;
|
|
15
15
|
}
|
|
16
|
-
export type ActionChangeKind = 'url_changed' | 'tree_structure_changed' | 'node_appeared' | 'node_disappeared' | 'overlay_changed' | 'no_change';
|
|
16
|
+
export type ActionChangeKind = 'url_changed' | 'tree_structure_changed' | 'node_appeared' | 'node_disappeared' | 'node_state_changed' | 'scroll_changed' | 'overlay_changed' | 'no_change';
|
|
17
17
|
export interface ActionChange {
|
|
18
18
|
kind: ActionChangeKind;
|
|
19
19
|
detail: string;
|
package/dist/action-verifier.js
CHANGED
|
@@ -99,6 +99,38 @@ export class ActionVerifier {
|
|
|
99
99
|
detail: `title: "${this.beforeTree.page.title}" -> "${afterTree.page.title}"`,
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
+
// 6. Check focused/value/selection/text state on stable nodes. This is
|
|
103
|
+
// what makes `any_change` meaningful for TYPE, CHECK, SELECT_OPTION and
|
|
104
|
+
// tab-like controls that update state without changing the DOM shape.
|
|
105
|
+
const beforeState = collectVisibleNodeState(this.beforeTree.root);
|
|
106
|
+
const afterState = collectVisibleNodeState(afterTree.root);
|
|
107
|
+
const changedStates = [];
|
|
108
|
+
for (const [key, before] of beforeState) {
|
|
109
|
+
const after = afterState.get(key);
|
|
110
|
+
if (!after)
|
|
111
|
+
continue;
|
|
112
|
+
if (before !== after) {
|
|
113
|
+
changedStates.push(key);
|
|
114
|
+
if (changedStates.length >= 5)
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (changedStates.length > 0) {
|
|
119
|
+
changes.push({
|
|
120
|
+
kind: 'node_state_changed',
|
|
121
|
+
detail: `${changedStates.length} visible node state change(s)`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// 7. Check document or nested scroll movement. SCROLL often changes only
|
|
125
|
+
// scroll offsets; without this, a valid scroll can look like a no-op.
|
|
126
|
+
const beforeScroll = collectScrollState(this.beforeTree);
|
|
127
|
+
const afterScroll = collectScrollState(afterTree);
|
|
128
|
+
if (beforeScroll !== afterScroll) {
|
|
129
|
+
changes.push({
|
|
130
|
+
kind: 'scroll_changed',
|
|
131
|
+
detail: `${beforeScroll} -> ${afterScroll}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
102
134
|
// If no changes detected at all
|
|
103
135
|
if (changes.length === 0) {
|
|
104
136
|
changes.push({ kind: 'no_change', detail: 'no detectable changes in URL, tree, or overlays' });
|
|
@@ -130,4 +162,40 @@ function collectVisibleInteractiveIds(node) {
|
|
|
130
162
|
walk(node);
|
|
131
163
|
return ids;
|
|
132
164
|
}
|
|
165
|
+
function collectVisibleNodeState(root) {
|
|
166
|
+
const states = new Map();
|
|
167
|
+
function keyFor(node) {
|
|
168
|
+
return node.sourceRef || node.id;
|
|
169
|
+
}
|
|
170
|
+
function walk(node) {
|
|
171
|
+
if (node.visible) {
|
|
172
|
+
states.set(keyFor(node), JSON.stringify({
|
|
173
|
+
label: node.label,
|
|
174
|
+
value: node.value ?? '',
|
|
175
|
+
disabled: node.state.disabled,
|
|
176
|
+
focused: node.state.focused,
|
|
177
|
+
checked: node.state.checked ?? null,
|
|
178
|
+
expanded: node.state.expanded ?? null,
|
|
179
|
+
selected: node.state.selected ?? null,
|
|
180
|
+
ownText: node.attributes.__ownText ?? '',
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
node.children.forEach(walk);
|
|
184
|
+
}
|
|
185
|
+
walk(root);
|
|
186
|
+
return states;
|
|
187
|
+
}
|
|
188
|
+
function collectScrollState(tree) {
|
|
189
|
+
const parts = [
|
|
190
|
+
`page:${tree.page.scroll.scrollTop},${tree.page.scroll.scrollLeft}`,
|
|
191
|
+
];
|
|
192
|
+
function walk(node) {
|
|
193
|
+
if (node.scroll) {
|
|
194
|
+
parts.push(`${node.sourceRef || node.id}:${node.scroll.scrollTop},${node.scroll.scrollLeft}`);
|
|
195
|
+
}
|
|
196
|
+
node.children.forEach(walk);
|
|
197
|
+
}
|
|
198
|
+
walk(tree.root);
|
|
199
|
+
return parts.join('|');
|
|
200
|
+
}
|
|
133
201
|
//# sourceMappingURL=action-verifier.js.map
|
package/dist/browser.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface StableSelectorSnapshot {
|
|
|
14
14
|
ariaLabel?: string | null;
|
|
15
15
|
title?: string | null;
|
|
16
16
|
placeholder?: string | null;
|
|
17
|
+
dataAk?: string | null;
|
|
18
|
+
dataAkInteract?: string | null;
|
|
17
19
|
dataTestId?: string | null;
|
|
18
20
|
dataTest?: string | null;
|
|
19
21
|
dataQa?: string | null;
|
package/dist/browser.js
CHANGED
|
@@ -746,6 +746,8 @@ export function buildStableCssSelectorCandidates(snapshot) {
|
|
|
746
746
|
push(`#${escapeCssIdentifier(snapshot.id)}`);
|
|
747
747
|
}
|
|
748
748
|
for (const [attr, value] of [
|
|
749
|
+
['data-ak', snapshot.dataAk],
|
|
750
|
+
['data-ak-interact', snapshot.dataAkInteract],
|
|
749
751
|
['data-testid', snapshot.dataTestId],
|
|
750
752
|
['data-test', snapshot.dataTest],
|
|
751
753
|
['data-qa', snapshot.dataQa],
|
|
@@ -1740,7 +1742,7 @@ export class Browser {
|
|
|
1740
1742
|
if (id) {
|
|
1741
1743
|
push(`#${CSS.escape(id)}`);
|
|
1742
1744
|
}
|
|
1743
|
-
for (const attr of ['data-testid', 'data-test', 'data-qa', 'data-cy']) {
|
|
1745
|
+
for (const attr of ['data-ak', 'data-ak-interact', 'data-testid', 'data-test', 'data-qa', 'data-cy']) {
|
|
1744
1746
|
const value = node.getAttribute(attr);
|
|
1745
1747
|
if (!value)
|
|
1746
1748
|
continue;
|
|
@@ -1922,7 +1924,7 @@ export class Browser {
|
|
|
1922
1924
|
try {
|
|
1923
1925
|
return await page.evaluate(() => {
|
|
1924
1926
|
const SKIP_TAGS = new Set(['STYLE', 'SCRIPT', 'SVG', 'NOSCRIPT', 'LINK', 'META', 'BR', 'HR', 'IFRAME', 'CANVAS', 'VIDEO', 'AUDIO', 'SOURCE', 'PICTURE', 'TEMPLATE']);
|
|
1925
|
-
const KEEP_ATTRS = new Set(['id', 'role', 'aria-label', 'aria-expanded', 'aria-haspopup', 'aria-controls', 'aria-selected', 'aria-checked', 'aria-disabled', 'href', 'type', 'name', 'placeholder', 'alt', 'title', 'value', 'for', 'action', 'method', 'data-testid']);
|
|
1927
|
+
const KEEP_ATTRS = new Set(['id', 'role', 'aria-label', 'aria-expanded', 'aria-haspopup', 'aria-controls', 'aria-selected', 'aria-checked', 'aria-disabled', 'href', 'type', 'name', 'placeholder', 'alt', 'title', 'value', 'for', 'action', 'method', 'data-ak', 'data-ak-interact', 'data-ak-fill-input', 'data-ak-fill-trigger', 'data-testid']);
|
|
1926
1928
|
const MAX_CHARS = 4000;
|
|
1927
1929
|
let output = '';
|
|
1928
1930
|
let stopped = false;
|
|
@@ -2335,7 +2337,7 @@ export class Browser {
|
|
|
2335
2337
|
const snapshot = await page.evaluate(() => {
|
|
2336
2338
|
const SKIP_TAGS = new Set(['STYLE', 'SCRIPT', 'LINK', 'META', 'NOSCRIPT', 'SOURCE', 'PATH', 'G', 'DEFS', 'CLIPPATH']);
|
|
2337
2339
|
const SEMANTIC_TAGS = new Set(['NAV', 'MAIN', 'HEADER', 'FOOTER', 'SECTION', 'ARTICLE', 'ASIDE', 'FORM']);
|
|
2338
|
-
const TEST_ID_ATTRS = ['data-testid', 'data-test', 'data-qa', 'data-cy'];
|
|
2340
|
+
const TEST_ID_ATTRS = ['data-ak', 'data-ak-interact', 'data-testid', 'data-test', 'data-qa', 'data-cy'];
|
|
2339
2341
|
const quoteForSelector = (value) => value
|
|
2340
2342
|
.replace(/\\/g, '\\\\')
|
|
2341
2343
|
.replace(/"/g, '\\"');
|
|
@@ -2537,7 +2539,8 @@ export class Browser {
|
|
|
2537
2539
|
const attrs = {};
|
|
2538
2540
|
const copyAttrs = [
|
|
2539
2541
|
'id', 'name', 'type', 'href', 'placeholder', 'title', 'role', 'aria-label',
|
|
2540
|
-
'aria-controls', 'aria-expanded', 'aria-haspopup', 'aria-modal', 'data-
|
|
2542
|
+
'aria-controls', 'aria-expanded', 'aria-haspopup', 'aria-modal', 'data-ak',
|
|
2543
|
+
'data-ak-interact', 'data-ak-fill-input', 'data-ak-fill-trigger', 'data-testid',
|
|
2541
2544
|
'data-test', 'data-qa', 'data-cy', 'alt',
|
|
2542
2545
|
];
|
|
2543
2546
|
for (const attr of copyAttrs) {
|
|
@@ -2983,7 +2986,7 @@ export class Browser {
|
|
|
2983
2986
|
const id = node.getAttribute('id');
|
|
2984
2987
|
if (id)
|
|
2985
2988
|
return `#${CSS.escape(id)}`;
|
|
2986
|
-
for (const attr of ['data-testid', 'data-test', 'data-qa', 'data-cy']) {
|
|
2989
|
+
for (const attr of ['data-ak', 'data-ak-interact', 'data-testid', 'data-test', 'data-qa', 'data-cy']) {
|
|
2987
2990
|
const value = node.getAttribute(attr);
|
|
2988
2991
|
if (value)
|
|
2989
2992
|
return `[${attr}="${quoteForSelector(value)}"]`;
|
|
@@ -4588,7 +4591,7 @@ export class Browser {
|
|
|
4588
4591
|
if (id) {
|
|
4589
4592
|
push(`#${CSS.escape(id)}`);
|
|
4590
4593
|
}
|
|
4591
|
-
for (const attr of ['data-testid', 'data-test', 'data-qa', 'data-cy']) {
|
|
4594
|
+
for (const attr of ['data-ak', 'data-ak-interact', 'data-testid', 'data-test', 'data-qa', 'data-cy']) {
|
|
4592
4595
|
const value = node.getAttribute(attr);
|
|
4593
4596
|
if (!value)
|
|
4594
4597
|
continue;
|
package/dist/cli-contract.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare function buildCliRunCommand(presetId: string, options?: {
|
|
|
17
17
|
local?: boolean;
|
|
18
18
|
env?: string;
|
|
19
19
|
dry?: boolean;
|
|
20
|
+
regenerateTts?: boolean;
|
|
20
21
|
}): string;
|
|
21
22
|
export declare function buildCliInstalledSetupCommand(cliKey: string): string;
|
|
22
23
|
export declare const CLI_PUBLIC_COMMANDS: CliPublicCommandDescriptor[];
|
package/dist/cli-contract.js
CHANGED
|
@@ -12,6 +12,7 @@ export function buildCliRunCommand(presetId, options = {}) {
|
|
|
12
12
|
options.local ? " --local" : "",
|
|
13
13
|
options.env ? ` --env ${options.env}` : "",
|
|
14
14
|
options.dry ? " --dry" : "",
|
|
15
|
+
options.regenerateTts ? " --regenerate-tts" : "",
|
|
15
16
|
].join("");
|
|
16
17
|
return `autokap run${flags} ${presetId}`;
|
|
17
18
|
}
|
package/dist/cli-runner-local.js
CHANGED
|
@@ -31,6 +31,7 @@ export async function runLocal(presetId, opts) {
|
|
|
31
31
|
program,
|
|
32
32
|
allowUploadFailure: opts.allowUploadFailure,
|
|
33
33
|
dryRun: opts.dry,
|
|
34
|
+
regenerateTts: opts.regenerateTts,
|
|
34
35
|
headed: opts.headed,
|
|
35
36
|
onProgress: (event) => {
|
|
36
37
|
const prefix = `[capture][${event.variantId}]`;
|
package/dist/cli-runner.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface CLIRunnerOptions {
|
|
|
31
31
|
allowUploadFailure?: boolean;
|
|
32
32
|
/** Dry run: skip capture opcodes (CAPTURE_SCREENSHOT/BEGIN_CLIP/END_CLIP) and upload. 0 credits charged. */
|
|
33
33
|
dryRun?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Force fresh TTS synthesis for every narration segment instead of reusing
|
|
36
|
+
* cached audio. Only meaningful when `mediaMode === 'video'`. Every reused
|
|
37
|
+
* segment becomes billable at full rate.
|
|
38
|
+
*/
|
|
39
|
+
regenerateTts?: boolean;
|
|
34
40
|
/** Selector memory map (fetched from server or cached locally) */
|
|
35
41
|
selectorMemory?: Record<string, string[]>;
|
|
36
42
|
/** Show browser window. Default: false (headless) */
|
package/dist/cli-runner.js
CHANGED
|
@@ -176,7 +176,7 @@ export async function runCapture(options) {
|
|
|
176
176
|
return { success: false, runId, error: error instanceof Error ? error.message : String(error) };
|
|
177
177
|
}
|
|
178
178
|
if (!options.program && program.mediaMode === 'video') {
|
|
179
|
-
const prepareResult = await prepareVideoSpeechForRun(config, options.presetId, runId);
|
|
179
|
+
const prepareResult = await prepareVideoSpeechForRun(config, options.presetId, runId, options.regenerateTts ?? false);
|
|
180
180
|
if (!prepareResult.success) {
|
|
181
181
|
return { success: false, runId, error: prepareResult.error };
|
|
182
182
|
}
|
|
@@ -396,7 +396,10 @@ async function fetchProgram(config, presetId, environmentName) {
|
|
|
396
396
|
}
|
|
397
397
|
return { success: false, error: 'failed to fetch program: retry attempts exhausted' };
|
|
398
398
|
}
|
|
399
|
-
async function prepareVideoSpeechForRun(config, videoId, runId) {
|
|
399
|
+
async function prepareVideoSpeechForRun(config, videoId, runId, regenerateTts) {
|
|
400
|
+
if (regenerateTts) {
|
|
401
|
+
logger.info('[capture] Forcing TTS regeneration — all cached segments will be re-synthesized and billed.');
|
|
402
|
+
}
|
|
400
403
|
logger.info('[capture] Generating speech, may take a few seconds...');
|
|
401
404
|
const url = `${config.apiBaseUrl}/api/cli/video-prepare`;
|
|
402
405
|
let response;
|
|
@@ -408,7 +411,7 @@ async function prepareVideoSpeechForRun(config, videoId, runId) {
|
|
|
408
411
|
'Content-Type': 'application/json',
|
|
409
412
|
[CLI_VERSION_HEADER]: APP_VERSION,
|
|
410
413
|
},
|
|
411
|
-
body: JSON.stringify({ videoId, runId }),
|
|
414
|
+
body: JSON.stringify(regenerateTts ? { videoId, runId, regenerateTts: true } : { videoId, runId }),
|
|
412
415
|
});
|
|
413
416
|
}
|
|
414
417
|
catch (err) {
|
package/dist/cli.js
CHANGED
|
@@ -251,6 +251,7 @@ program
|
|
|
251
251
|
.option('--output <dir>', 'Optional output directory for local artifact copies')
|
|
252
252
|
.option('--program <file>', 'Path to a program JSON file')
|
|
253
253
|
.option('--dry', 'Dry run: execute all opcodes without capturing or uploading artifacts (0 credits charged)', false)
|
|
254
|
+
.option('--regenerate-tts', 'Force fresh TTS synthesis for video presets — ignore cached audio segments. Reused segments become billable at full rate.', false)
|
|
254
255
|
.option('--debug', 'Verbose logging: per-substep timing, opcode dumps, recovery strategy traces', false)
|
|
255
256
|
.action(async (presetId, opts) => {
|
|
256
257
|
if (opts.debug) {
|
|
@@ -29,6 +29,16 @@ export declare const RecoveryPolicySchema: z.ZodObject<{
|
|
|
29
29
|
allowReload: z.ZodBoolean;
|
|
30
30
|
allowHealer: z.ZodBoolean;
|
|
31
31
|
}, z.core.$strict>;
|
|
32
|
+
export declare const OutscaleConfigSchema: z.ZodObject<{
|
|
33
|
+
padding: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
paddingTop: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
paddingRight: z.ZodOptional<z.ZodNumber>;
|
|
36
|
+
paddingBottom: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
paddingLeft: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
paddingPercent: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
clampToViewport: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
backgroundColor: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, z.core.$strict>;
|
|
32
42
|
export declare const ExecutionOpcodeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
33
43
|
url: z.ZodString;
|
|
34
44
|
description: z.ZodString;
|
|
@@ -495,6 +505,16 @@ export declare const ExecutionOpcodeSchema: z.ZodDiscriminatedUnion<[z.ZodObject
|
|
|
495
505
|
captureId: z.ZodOptional<z.ZodString>;
|
|
496
506
|
captureName: z.ZodOptional<z.ZodString>;
|
|
497
507
|
elementSelector: z.ZodOptional<z.ZodString>;
|
|
508
|
+
outscale: z.ZodOptional<z.ZodObject<{
|
|
509
|
+
padding: z.ZodOptional<z.ZodNumber>;
|
|
510
|
+
paddingTop: z.ZodOptional<z.ZodNumber>;
|
|
511
|
+
paddingRight: z.ZodOptional<z.ZodNumber>;
|
|
512
|
+
paddingBottom: z.ZodOptional<z.ZodNumber>;
|
|
513
|
+
paddingLeft: z.ZodOptional<z.ZodNumber>;
|
|
514
|
+
paddingPercent: z.ZodOptional<z.ZodNumber>;
|
|
515
|
+
clampToViewport: z.ZodOptional<z.ZodBoolean>;
|
|
516
|
+
backgroundColor: z.ZodOptional<z.ZodString>;
|
|
517
|
+
}, z.core.$strict>>;
|
|
498
518
|
description: z.ZodString;
|
|
499
519
|
postcondition: z.ZodObject<{
|
|
500
520
|
type: z.ZodEnum<{
|
|
@@ -1688,6 +1708,16 @@ export declare const ExecutionProgramSchema: z.ZodObject<{
|
|
|
1688
1708
|
captureId: z.ZodOptional<z.ZodString>;
|
|
1689
1709
|
captureName: z.ZodOptional<z.ZodString>;
|
|
1690
1710
|
elementSelector: z.ZodOptional<z.ZodString>;
|
|
1711
|
+
outscale: z.ZodOptional<z.ZodObject<{
|
|
1712
|
+
padding: z.ZodOptional<z.ZodNumber>;
|
|
1713
|
+
paddingTop: z.ZodOptional<z.ZodNumber>;
|
|
1714
|
+
paddingRight: z.ZodOptional<z.ZodNumber>;
|
|
1715
|
+
paddingBottom: z.ZodOptional<z.ZodNumber>;
|
|
1716
|
+
paddingLeft: z.ZodOptional<z.ZodNumber>;
|
|
1717
|
+
paddingPercent: z.ZodOptional<z.ZodNumber>;
|
|
1718
|
+
clampToViewport: z.ZodOptional<z.ZodBoolean>;
|
|
1719
|
+
backgroundColor: z.ZodOptional<z.ZodString>;
|
|
1720
|
+
}, z.core.$strict>>;
|
|
1691
1721
|
description: z.ZodString;
|
|
1692
1722
|
postcondition: z.ZodObject<{
|
|
1693
1723
|
type: z.ZodEnum<{
|
|
@@ -2665,6 +2695,16 @@ export declare const HealerPatchSchema: z.ZodObject<{
|
|
|
2665
2695
|
captureId: z.ZodOptional<z.ZodString>;
|
|
2666
2696
|
captureName: z.ZodOptional<z.ZodString>;
|
|
2667
2697
|
elementSelector: z.ZodOptional<z.ZodString>;
|
|
2698
|
+
outscale: z.ZodOptional<z.ZodObject<{
|
|
2699
|
+
padding: z.ZodOptional<z.ZodNumber>;
|
|
2700
|
+
paddingTop: z.ZodOptional<z.ZodNumber>;
|
|
2701
|
+
paddingRight: z.ZodOptional<z.ZodNumber>;
|
|
2702
|
+
paddingBottom: z.ZodOptional<z.ZodNumber>;
|
|
2703
|
+
paddingLeft: z.ZodOptional<z.ZodNumber>;
|
|
2704
|
+
paddingPercent: z.ZodOptional<z.ZodNumber>;
|
|
2705
|
+
clampToViewport: z.ZodOptional<z.ZodBoolean>;
|
|
2706
|
+
backgroundColor: z.ZodOptional<z.ZodString>;
|
|
2707
|
+
}, z.core.$strict>>;
|
|
2668
2708
|
description: z.ZodString;
|
|
2669
2709
|
postcondition: z.ZodObject<{
|
|
2670
2710
|
type: z.ZodEnum<{
|
|
@@ -3588,6 +3628,16 @@ export declare const HealerPatchSchema: z.ZodObject<{
|
|
|
3588
3628
|
captureId: z.ZodOptional<z.ZodString>;
|
|
3589
3629
|
captureName: z.ZodOptional<z.ZodString>;
|
|
3590
3630
|
elementSelector: z.ZodOptional<z.ZodString>;
|
|
3631
|
+
outscale: z.ZodOptional<z.ZodObject<{
|
|
3632
|
+
padding: z.ZodOptional<z.ZodNumber>;
|
|
3633
|
+
paddingTop: z.ZodOptional<z.ZodNumber>;
|
|
3634
|
+
paddingRight: z.ZodOptional<z.ZodNumber>;
|
|
3635
|
+
paddingBottom: z.ZodOptional<z.ZodNumber>;
|
|
3636
|
+
paddingLeft: z.ZodOptional<z.ZodNumber>;
|
|
3637
|
+
paddingPercent: z.ZodOptional<z.ZodNumber>;
|
|
3638
|
+
clampToViewport: z.ZodOptional<z.ZodBoolean>;
|
|
3639
|
+
backgroundColor: z.ZodOptional<z.ZodString>;
|
|
3640
|
+
}, z.core.$strict>>;
|
|
3591
3641
|
description: z.ZodString;
|
|
3592
3642
|
postcondition: z.ZodObject<{
|
|
3593
3643
|
type: z.ZodEnum<{
|
|
@@ -4151,6 +4201,51 @@ export declare function safeParseProgramResult(data: unknown): z.ZodSafeParseRes
|
|
|
4151
4201
|
}[] | undefined;
|
|
4152
4202
|
};
|
|
4153
4203
|
steps: ({
|
|
4204
|
+
urlPattern: string;
|
|
4205
|
+
description: string;
|
|
4206
|
+
postcondition: {
|
|
4207
|
+
type: "route_matches" | "element_visible" | "element_absent" | "text_contains" | "overlay_dismissed" | "screenshot_stable" | "any_change" | "always";
|
|
4208
|
+
pattern?: string | undefined;
|
|
4209
|
+
selector?: string | undefined;
|
|
4210
|
+
text?: string | undefined;
|
|
4211
|
+
threshold?: number | undefined;
|
|
4212
|
+
waitMs?: number | undefined;
|
|
4213
|
+
};
|
|
4214
|
+
recovery: {
|
|
4215
|
+
retries: number;
|
|
4216
|
+
useSelectorMemory: boolean;
|
|
4217
|
+
useAltInteraction: boolean;
|
|
4218
|
+
allowReload: boolean;
|
|
4219
|
+
allowHealer: boolean;
|
|
4220
|
+
};
|
|
4221
|
+
timeoutMs: number;
|
|
4222
|
+
maxFailures: number;
|
|
4223
|
+
kind: "ASSERT_ROUTE";
|
|
4224
|
+
stepId?: string | undefined;
|
|
4225
|
+
} | {
|
|
4226
|
+
selectors: string[];
|
|
4227
|
+
matchAll: boolean;
|
|
4228
|
+
description: string;
|
|
4229
|
+
postcondition: {
|
|
4230
|
+
type: "route_matches" | "element_visible" | "element_absent" | "text_contains" | "overlay_dismissed" | "screenshot_stable" | "any_change" | "always";
|
|
4231
|
+
pattern?: string | undefined;
|
|
4232
|
+
selector?: string | undefined;
|
|
4233
|
+
text?: string | undefined;
|
|
4234
|
+
threshold?: number | undefined;
|
|
4235
|
+
waitMs?: number | undefined;
|
|
4236
|
+
};
|
|
4237
|
+
recovery: {
|
|
4238
|
+
retries: number;
|
|
4239
|
+
useSelectorMemory: boolean;
|
|
4240
|
+
useAltInteraction: boolean;
|
|
4241
|
+
allowReload: boolean;
|
|
4242
|
+
allowHealer: boolean;
|
|
4243
|
+
};
|
|
4244
|
+
timeoutMs: number;
|
|
4245
|
+
maxFailures: number;
|
|
4246
|
+
kind: "ASSERT_SURFACE";
|
|
4247
|
+
stepId?: string | undefined;
|
|
4248
|
+
} | {
|
|
4154
4249
|
state: "visible" | "attached";
|
|
4155
4250
|
description: string;
|
|
4156
4251
|
postcondition: {
|
|
@@ -4420,51 +4515,6 @@ export declare function safeParseProgramResult(data: unknown): z.ZodSafeParseRes
|
|
|
4420
4515
|
maxFailures: number;
|
|
4421
4516
|
kind: "DISMISS_OVERLAYS";
|
|
4422
4517
|
stepId?: string | undefined;
|
|
4423
|
-
} | {
|
|
4424
|
-
urlPattern: string;
|
|
4425
|
-
description: string;
|
|
4426
|
-
postcondition: {
|
|
4427
|
-
type: "route_matches" | "element_visible" | "element_absent" | "text_contains" | "overlay_dismissed" | "screenshot_stable" | "any_change" | "always";
|
|
4428
|
-
pattern?: string | undefined;
|
|
4429
|
-
selector?: string | undefined;
|
|
4430
|
-
text?: string | undefined;
|
|
4431
|
-
threshold?: number | undefined;
|
|
4432
|
-
waitMs?: number | undefined;
|
|
4433
|
-
};
|
|
4434
|
-
recovery: {
|
|
4435
|
-
retries: number;
|
|
4436
|
-
useSelectorMemory: boolean;
|
|
4437
|
-
useAltInteraction: boolean;
|
|
4438
|
-
allowReload: boolean;
|
|
4439
|
-
allowHealer: boolean;
|
|
4440
|
-
};
|
|
4441
|
-
timeoutMs: number;
|
|
4442
|
-
maxFailures: number;
|
|
4443
|
-
kind: "ASSERT_ROUTE";
|
|
4444
|
-
stepId?: string | undefined;
|
|
4445
|
-
} | {
|
|
4446
|
-
selectors: string[];
|
|
4447
|
-
matchAll: boolean;
|
|
4448
|
-
description: string;
|
|
4449
|
-
postcondition: {
|
|
4450
|
-
type: "route_matches" | "element_visible" | "element_absent" | "text_contains" | "overlay_dismissed" | "screenshot_stable" | "any_change" | "always";
|
|
4451
|
-
pattern?: string | undefined;
|
|
4452
|
-
selector?: string | undefined;
|
|
4453
|
-
text?: string | undefined;
|
|
4454
|
-
threshold?: number | undefined;
|
|
4455
|
-
waitMs?: number | undefined;
|
|
4456
|
-
};
|
|
4457
|
-
recovery: {
|
|
4458
|
-
retries: number;
|
|
4459
|
-
useSelectorMemory: boolean;
|
|
4460
|
-
useAltInteraction: boolean;
|
|
4461
|
-
allowReload: boolean;
|
|
4462
|
-
allowHealer: boolean;
|
|
4463
|
-
};
|
|
4464
|
-
timeoutMs: number;
|
|
4465
|
-
maxFailures: number;
|
|
4466
|
-
kind: "ASSERT_SURFACE";
|
|
4467
|
-
stepId?: string | undefined;
|
|
4468
4518
|
} | {
|
|
4469
4519
|
selector: string;
|
|
4470
4520
|
description: string;
|
|
@@ -4610,6 +4660,16 @@ export declare function safeParseProgramResult(data: unknown): z.ZodSafeParseRes
|
|
|
4610
4660
|
captureId?: string | undefined;
|
|
4611
4661
|
captureName?: string | undefined;
|
|
4612
4662
|
elementSelector?: string | undefined;
|
|
4663
|
+
outscale?: {
|
|
4664
|
+
padding?: number | undefined;
|
|
4665
|
+
paddingTop?: number | undefined;
|
|
4666
|
+
paddingRight?: number | undefined;
|
|
4667
|
+
paddingBottom?: number | undefined;
|
|
4668
|
+
paddingLeft?: number | undefined;
|
|
4669
|
+
paddingPercent?: number | undefined;
|
|
4670
|
+
clampToViewport?: boolean | undefined;
|
|
4671
|
+
backgroundColor?: string | undefined;
|
|
4672
|
+
} | undefined;
|
|
4613
4673
|
stepId?: string | undefined;
|
|
4614
4674
|
} | {
|
|
4615
4675
|
description: string;
|
package/dist/execution-schema.js
CHANGED
|
@@ -102,13 +102,35 @@ const AssertRouteOpcodeSchema = z.object({
|
|
|
102
102
|
kind: z.literal('ASSERT_ROUTE'),
|
|
103
103
|
...opcodeBase,
|
|
104
104
|
urlPattern: z.string().min(1),
|
|
105
|
-
}).strict()
|
|
105
|
+
}).strict().superRefine((value, ctx) => {
|
|
106
|
+
const post = value.postcondition;
|
|
107
|
+
if (post.type === 'always')
|
|
108
|
+
return;
|
|
109
|
+
if (post.type === 'route_matches' && post.pattern === value.urlPattern)
|
|
110
|
+
return;
|
|
111
|
+
ctx.addIssue({
|
|
112
|
+
code: z.ZodIssueCode.custom,
|
|
113
|
+
message: 'ASSERT_ROUTE uses `urlPattern` as its source of truth; postcondition must be `always` or `route_matches` with the same pattern',
|
|
114
|
+
path: ['postcondition'],
|
|
115
|
+
});
|
|
116
|
+
});
|
|
106
117
|
const AssertSurfaceOpcodeSchema = z.object({
|
|
107
118
|
kind: z.literal('ASSERT_SURFACE'),
|
|
108
119
|
...opcodeBase,
|
|
109
120
|
selectors: z.array(z.string().min(1)).min(1),
|
|
110
121
|
matchAll: z.boolean(),
|
|
111
|
-
}).strict()
|
|
122
|
+
}).strict().superRefine((value, ctx) => {
|
|
123
|
+
const post = value.postcondition;
|
|
124
|
+
if (post.type === 'always')
|
|
125
|
+
return;
|
|
126
|
+
if (post.type === 'element_visible' && post.selector && value.selectors.includes(post.selector))
|
|
127
|
+
return;
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: z.ZodIssueCode.custom,
|
|
130
|
+
message: 'ASSERT_SURFACE uses `selectors`/`matchAll` as its source of truth; postcondition must be `always` or `element_visible` for one of the asserted selectors',
|
|
131
|
+
path: ['postcondition'],
|
|
132
|
+
});
|
|
133
|
+
});
|
|
112
134
|
const SemanticTargetSchema = z.object({
|
|
113
135
|
text: z.string().optional(),
|
|
114
136
|
role: z.string().optional(),
|
|
@@ -242,12 +264,23 @@ const ScrollOpcodeSchema = z.object({
|
|
|
242
264
|
targetSelector: z.string().optional(),
|
|
243
265
|
target: SemanticTargetSchema.optional(),
|
|
244
266
|
}).strict();
|
|
267
|
+
export const OutscaleConfigSchema = z.object({
|
|
268
|
+
padding: z.number().min(0).optional(),
|
|
269
|
+
paddingTop: z.number().min(0).optional(),
|
|
270
|
+
paddingRight: z.number().min(0).optional(),
|
|
271
|
+
paddingBottom: z.number().min(0).optional(),
|
|
272
|
+
paddingLeft: z.number().min(0).optional(),
|
|
273
|
+
paddingPercent: z.number().min(0).max(100).optional(),
|
|
274
|
+
clampToViewport: z.boolean().optional(),
|
|
275
|
+
backgroundColor: z.string().optional(),
|
|
276
|
+
}).strict();
|
|
245
277
|
const CaptureScreenshotOpcodeSchema = z.object({
|
|
246
278
|
kind: z.literal('CAPTURE_SCREENSHOT'),
|
|
247
279
|
...opcodeBase,
|
|
248
280
|
captureId: z.string().optional(),
|
|
249
281
|
captureName: z.string().optional(),
|
|
250
282
|
elementSelector: z.string().optional(),
|
|
283
|
+
outscale: OutscaleConfigSchema.optional(),
|
|
251
284
|
}).strict();
|
|
252
285
|
const BeginClipOpcodeSchema = z.object({
|
|
253
286
|
kind: z.literal('BEGIN_CLIP'),
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* All types for the compiled execution model:
|
|
5
5
|
* preset (natural language) -> ExecutionProgram (typed IR) -> deterministic runtime
|
|
6
6
|
*/
|
|
7
|
-
import type { AKTree, BrowserStorageState, BrowserSessionStorageState, VideoCursorTheme, VideoPageSignals } from './types.js';
|
|
7
|
+
import type { AKTree, BrowserStorageState, BrowserSessionStorageState, OutscaleConfig, VideoCursorTheme, VideoPageSignals } from './types.js';
|
|
8
8
|
import type { MockupOptions } from './mockup.js';
|
|
9
9
|
/** Sentinel value that resolves to the current variant's locale or theme at runtime */
|
|
10
10
|
export declare const VARIANT_PLACEHOLDER: "$variant";
|
|
@@ -247,6 +247,8 @@ export interface CaptureScreenshotOpcode extends OpcodeBase {
|
|
|
247
247
|
captureName?: string;
|
|
248
248
|
/** Optional element selector for element-level capture */
|
|
249
249
|
elementSelector?: string;
|
|
250
|
+
/** Optional padding around the captured element. Only applied when `elementSelector` is set. */
|
|
251
|
+
outscale?: OutscaleConfig;
|
|
250
252
|
}
|
|
251
253
|
export interface BeginClipOpcode extends OpcodeBase {
|
|
252
254
|
kind: 'BEGIN_CLIP';
|
|
@@ -809,7 +811,7 @@ export interface RuntimeAdapter {
|
|
|
809
811
|
method: string | null;
|
|
810
812
|
}>;
|
|
811
813
|
takeScreenshot(): Promise<Buffer>;
|
|
812
|
-
takeElementScreenshot?(selector: string): Promise<Buffer>;
|
|
814
|
+
takeElementScreenshot?(selector: string, outscale?: OutscaleConfig): Promise<Buffer>;
|
|
813
815
|
takeCleanScreenshot(): Promise<Buffer>;
|
|
814
816
|
beginRecording(options: RecordingOptions): Promise<void>;
|
|
815
817
|
endRecording(): Promise<RecordingResult>;
|