autokap 1.1.0 → 1.1.3
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 +1 -41
- package/assets/skill/README.md +0 -1
- package/assets/skill/SKILL.md +9 -32
- package/assets/skill/references/examples.md +1 -17
- package/dist/billing-operation-logging.d.ts +1 -3
- package/dist/billing-operation-logging.js +0 -4
- package/dist/browser.js +164 -1
- package/dist/capture-strategy.d.ts +2 -8
- package/dist/capture-strategy.js +2 -30
- package/dist/cli-config.d.ts +2 -1
- package/dist/cli-config.js +18 -2
- package/dist/cli-contract.d.ts +1 -0
- package/dist/cli-contract.js +8 -2
- package/dist/cli-runner-local.d.ts +2 -0
- package/dist/cli-runner-local.js +12 -21
- package/dist/cli-runner.d.ts +4 -0
- package/dist/cli-runner.js +45 -53
- package/dist/cli.js +89 -44
- package/dist/execution-schema.d.ts +143 -331
- package/dist/execution-schema.js +43 -28
- package/dist/execution-types.d.ts +6 -151
- package/dist/execution-types.js +1 -3
- package/dist/logger.js +1 -1
- package/dist/mockup-html.d.ts +2 -0
- package/dist/mockup-html.js +13 -10
- package/dist/mockup.js +2 -2
- package/dist/opcode-actions.js +0 -2
- package/dist/opcode-runner.js +0 -121
- package/dist/program-signing.d.ts +50 -72
- package/dist/security.js +2 -2
- package/dist/server-capture-runtime.d.ts +0 -1
- package/dist/server-capture-runtime.js +0 -3
- package/dist/server-credit-usage.d.ts +1 -1
- package/dist/skill-packaging.d.ts +1 -1
- package/dist/skill-packaging.js +1 -11
- package/dist/types.d.ts +2 -2
- package/dist/web-playwright-local.d.ts +0 -14
- package/dist/web-playwright-local.js +2 -194
- package/package.json +2 -19
- package/readme.md +13 -0
- package/assets/skill/STUDIO-SKILL.md +0 -476
- package/assets/skill/references/interactive-demo.md +0 -225
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* preset (natural language) -> ExecutionProgram (typed IR) -> deterministic runtime
|
|
6
6
|
*/
|
|
7
7
|
import type { AKTree, BrowserStorageState, BrowserSessionStorageState, VideoCursorTheme, VideoPageSignals } from './types.js';
|
|
8
|
+
import type { MockupOptions } from './mockup.js';
|
|
8
9
|
/** Sentinel value that resolves to the current variant's locale or theme at runtime */
|
|
9
10
|
export declare const VARIANT_PLACEHOLDER: "$variant";
|
|
10
|
-
export declare const OPCODE_KINDS: readonly ["NAVIGATE", "DISMISS_OVERLAYS", "ASSERT_ROUTE", "ASSERT_SURFACE", "CLICK", "TYPE", "PRESS_KEY", "WAIT_FOR", "SET_LOCALE", "SET_THEME", "SCROLL", "CAPTURE_SCREENSHOT", "
|
|
11
|
+
export declare const OPCODE_KINDS: readonly ["NAVIGATE", "DISMISS_OVERLAYS", "ASSERT_ROUTE", "ASSERT_SURFACE", "CLICK", "TYPE", "PRESS_KEY", "WAIT_FOR", "SET_LOCALE", "SET_THEME", "SCROLL", "CAPTURE_SCREENSHOT", "BEGIN_CLIP", "END_CLIP", "HOVER", "SELECT_OPTION", "CHECK", "DOUBLE_CLICK", "DRAG", "CLONE_ELEMENT", "INJECT_MOCK_DATA", "REMOVE_ELEMENT", "SET_ATTRIBUTE"];
|
|
11
12
|
export type OpcodeKind = (typeof OPCODE_KINDS)[number];
|
|
12
13
|
/**
|
|
13
14
|
* Soft opcodes are non-blocking — if their action or postcondition fails at
|
|
@@ -214,98 +215,6 @@ export interface CaptureScreenshotOpcode extends OpcodeBase {
|
|
|
214
215
|
/** Optional element selector for element-level capture */
|
|
215
216
|
elementSelector?: string;
|
|
216
217
|
}
|
|
217
|
-
/**
|
|
218
|
-
* Capture the full serialized DOM of the current page as an interactive demo
|
|
219
|
-
* state. Produces an HTML artifact (no PNG) that the demo player can replay
|
|
220
|
-
* client-side. Used by AUT-121 Interactive Demos preset type.
|
|
221
|
-
*
|
|
222
|
-
* The runner runs `smartWaitForStability` before serializing, sanitizes the
|
|
223
|
-
* HTML (strips `<script>`, `on*` attributes, third-party iframes), and pushes
|
|
224
|
-
* an `ArtifactResult` with `mediaMode: 'dom'`.
|
|
225
|
-
*/
|
|
226
|
-
export interface CaptureDomOpcode extends OpcodeBase {
|
|
227
|
-
kind: 'CAPTURE_DOM';
|
|
228
|
-
/**
|
|
229
|
-
* Stable identifier for this state (kebab-case, e.g. `dashboard`,
|
|
230
|
-
* `dashboard-chat-open`). Referenced by the interaction script's
|
|
231
|
-
* `state` / `target` fields and used as the storage filename.
|
|
232
|
-
*/
|
|
233
|
-
stateName: string;
|
|
234
|
-
/**
|
|
235
|
-
* Optional CSS selector of the subtree to serialize instead of the full
|
|
236
|
-
* page. Use this when the interactive demo should focus on one specific
|
|
237
|
-
* surface (modal, dashboard shell, pricing calculator, editor panel, ...).
|
|
238
|
-
*
|
|
239
|
-
* The adapter keeps the current `<head>` and `<body>` attributes so global
|
|
240
|
-
* styles/themes still apply, but rewrites the body to contain only the
|
|
241
|
-
* selected branch (including its ancestor shell) and sizes the captured
|
|
242
|
-
* viewport to that subtree's bounding box.
|
|
243
|
-
*/
|
|
244
|
-
selector?: string;
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Capture a single subtree of the current page as a reusable fragment
|
|
248
|
-
* (modal, popover, dropdown, tooltip, sheet, etc.). Phase 5 of AUT-121.
|
|
249
|
-
*
|
|
250
|
-
* Fragments are mounted/unmounted by the player on top of a base state via
|
|
251
|
-
* `show_fragment` / `hide_fragment` actions, so a single capture can power
|
|
252
|
-
* many local interactions without swapping the entire DOM.
|
|
253
|
-
*
|
|
254
|
-
* The runner optionally clicks `triggerSelector` first (typically a hidden
|
|
255
|
-
* `data-ak-fragment-trigger="..."` button exposed by the user's app for
|
|
256
|
-
* fragments that aren't reachable through visible UI), waits for `selector`
|
|
257
|
-
* to be present, then serializes the matching subtree's `outerHTML`.
|
|
258
|
-
*/
|
|
259
|
-
export interface CaptureFragmentOpcode extends OpcodeBase {
|
|
260
|
-
kind: 'CAPTURE_FRAGMENT';
|
|
261
|
-
/**
|
|
262
|
-
* Stable kebab-case identifier for the fragment, unique within
|
|
263
|
-
* `parentState`. Mirrors `data-ak-fragment="<name>"` in the source code.
|
|
264
|
-
*
|
|
265
|
-
* Multiple `CAPTURE_FRAGMENT` opcodes can target the same `fragmentName`
|
|
266
|
-
* with different `variantName` values to capture multiple versions of
|
|
267
|
-
* the same subtree (e.g. "default" / "purple" / "text-bottom"). The
|
|
268
|
-
* player swaps variants in place at runtime via
|
|
269
|
-
* `api.fragment.show(name, variantName)`.
|
|
270
|
-
*/
|
|
271
|
-
fragmentName: string;
|
|
272
|
-
/**
|
|
273
|
-
* Variant identifier for this capture, kebab-case. Defaults to `'default'`.
|
|
274
|
-
* Use distinct variant names when capturing the same fragment in different
|
|
275
|
-
* states (e.g. with a different background applied). The player can swap
|
|
276
|
-
* between variants in place without losing scroll/focus/sibling layout.
|
|
277
|
-
*/
|
|
278
|
-
variantName?: string;
|
|
279
|
-
/**
|
|
280
|
-
* The captured base state (`CAPTURE_DOM.stateName`) this fragment lives
|
|
281
|
-
* on top of. Used to scope the fragment in storage and at runtime.
|
|
282
|
-
*/
|
|
283
|
-
parentState: string;
|
|
284
|
-
/** CSS selector of the subtree wrapper. Typically `[data-ak-fragment="<name>"]`. */
|
|
285
|
-
selector: string;
|
|
286
|
-
/**
|
|
287
|
-
* Optional hidden trigger button selector (mirrors the mock data
|
|
288
|
-
* pattern). If provided, the runner clicks it via `clickHidden()` before
|
|
289
|
-
* waiting for `selector` to appear — useful for fragments that aren't
|
|
290
|
-
* reachable via existing UI buttons.
|
|
291
|
-
*/
|
|
292
|
-
triggerSelector?: string;
|
|
293
|
-
/**
|
|
294
|
-
* How the player should mount the fragment back onto the base state at
|
|
295
|
-
* runtime. Default: `inline` (mount in place of the original wrapper).
|
|
296
|
-
* - `overlay`: position absolute at the top-left of the iframe body
|
|
297
|
-
* - `inline`: mount in place of the original wrapper
|
|
298
|
-
* - `portal:<target>`: mount inside a `[data-ak-mount-target="<target>"]` element
|
|
299
|
-
*/
|
|
300
|
-
mountStrategy?: 'overlay' | 'inline' | string;
|
|
301
|
-
/**
|
|
302
|
-
* Optional override for the base-state element where the fragment
|
|
303
|
-
* should be mounted (CSS selector). When omitted, the player uses the
|
|
304
|
-
* fragment's source location (the element matching `selector` in the
|
|
305
|
-
* captured base state).
|
|
306
|
-
*/
|
|
307
|
-
mountTargetSelector?: string;
|
|
308
|
-
}
|
|
309
218
|
export interface BeginClipOpcode extends OpcodeBase {
|
|
310
219
|
kind: 'BEGIN_CLIP';
|
|
311
220
|
/** Stable preset clip identifier for Studio and dev links */
|
|
@@ -481,7 +390,7 @@ export interface SetAttributeOpcode extends OpcodeBase {
|
|
|
481
390
|
/** Attribute value */
|
|
482
391
|
value: string;
|
|
483
392
|
}
|
|
484
|
-
export type ExecutionOpcode = NavigateOpcode | DismissOverlaysOpcode | AssertRouteOpcode | AssertSurfaceOpcode | ClickOpcode | TypeOpcode | PressKeyOpcode | WaitForOpcode | SetLocaleOpcode | SetThemeOpcode | ScrollOpcode | CaptureScreenshotOpcode |
|
|
393
|
+
export type ExecutionOpcode = NavigateOpcode | DismissOverlaysOpcode | AssertRouteOpcode | AssertSurfaceOpcode | ClickOpcode | TypeOpcode | PressKeyOpcode | WaitForOpcode | SetLocaleOpcode | SetThemeOpcode | ScrollOpcode | CaptureScreenshotOpcode | BeginClipOpcode | EndClipOpcode | HoverOpcode | SelectOptionOpcode | CheckOpcode | DoubleClickOpcode | DragOpcode | CloneElementOpcode | InjectMockDataOpcode | RemoveElementOpcode | SetAttributeOpcode;
|
|
485
394
|
export interface VariantSpec {
|
|
486
395
|
id: string;
|
|
487
396
|
viewport: {
|
|
@@ -497,6 +406,8 @@ export interface VariantSpec {
|
|
|
497
406
|
targetLabel?: string;
|
|
498
407
|
/** Device frame label (e.g. "iPhone 15 Pro") for mockup rendering */
|
|
499
408
|
deviceFrame?: string;
|
|
409
|
+
/** Per-variant device/browser frame options persisted from the preset config */
|
|
410
|
+
mockupOptions?: MockupOptions;
|
|
500
411
|
}
|
|
501
412
|
export interface PreconditionSpec {
|
|
502
413
|
/**
|
|
@@ -541,7 +452,7 @@ export interface PreconditionSpec {
|
|
|
541
452
|
path?: string;
|
|
542
453
|
}>;
|
|
543
454
|
}
|
|
544
|
-
export declare const MEDIA_MODES: readonly ["screenshot", "clip"
|
|
455
|
+
export declare const MEDIA_MODES: readonly ["screenshot", "clip"];
|
|
545
456
|
export type MediaMode = (typeof MEDIA_MODES)[number];
|
|
546
457
|
export interface ArtifactSpec {
|
|
547
458
|
mediaMode: MediaMode;
|
|
@@ -560,11 +471,6 @@ export interface ArtifactSpec {
|
|
|
560
471
|
applyMockup?: boolean;
|
|
561
472
|
/** Whether to add status bar. Default: false */
|
|
562
473
|
applyStatusBar?: boolean;
|
|
563
|
-
/** Options specific to DOM captures (Interactive Demos) */
|
|
564
|
-
domOptions?: {
|
|
565
|
-
/** Whether to sanitize the DOM (strip <script>, on*, third-party iframes). Default: true */
|
|
566
|
-
sanitize?: boolean;
|
|
567
|
-
};
|
|
568
474
|
}
|
|
569
475
|
export interface ExecutionProgram {
|
|
570
476
|
presetId: string;
|
|
@@ -676,31 +582,6 @@ export interface ArtifactResult {
|
|
|
676
582
|
/** Favicon extracted from the captured page */
|
|
677
583
|
tabIconData?: Buffer;
|
|
678
584
|
tabIconMimeType?: string;
|
|
679
|
-
/** Stable identifier of the captured demo state (kebab-case, from CaptureDomOpcode.stateName) */
|
|
680
|
-
stateName?: string;
|
|
681
|
-
/** Sanitized HTML body (also stored as buffer for upload) */
|
|
682
|
-
domHtml?: string;
|
|
683
|
-
/** Asset URLs referenced by the captured DOM (img, source, link rel="stylesheet"). Phase 3 will resolve these via CAS. */
|
|
684
|
-
domAssetUrls?: string[];
|
|
685
|
-
/** Byte size of the sanitized HTML, for telemetry */
|
|
686
|
-
domHtmlBytes?: number;
|
|
687
|
-
/** PNG thumbnail screenshot taken at the time of DOM capture */
|
|
688
|
-
domThumbnailBuffer?: Buffer;
|
|
689
|
-
/** Stable kebab-case identifier of the captured fragment, from CaptureFragmentOpcode.fragmentName */
|
|
690
|
-
fragmentName?: string;
|
|
691
|
-
/**
|
|
692
|
-
* Variant of this fragment capture (Phase 8). Same `fragmentName` can be
|
|
693
|
-
* captured under multiple variants — e.g. "default", "purple-bg",
|
|
694
|
-
* "text-bottom" — and the player swaps between them in place. Defaults to
|
|
695
|
-
* `'default'` when the opcode does not specify one.
|
|
696
|
-
*/
|
|
697
|
-
fragmentVariantName?: string;
|
|
698
|
-
/** Parent base state this fragment belongs to */
|
|
699
|
-
parentStateName?: string;
|
|
700
|
-
/** Where the player should mount the fragment at runtime */
|
|
701
|
-
mountStrategy?: string;
|
|
702
|
-
/** Optional override for the base-state element to mount into (CSS selector) */
|
|
703
|
-
mountTargetSelector?: string;
|
|
704
585
|
}
|
|
705
586
|
export type LLMStepType = 'capture_verification' | 'alt_text_generation' | 'healer_invocation';
|
|
706
587
|
export interface LLMStepUsage {
|
|
@@ -790,32 +671,6 @@ export interface RuntimeAdapter {
|
|
|
790
671
|
takeScreenshot(): Promise<Buffer>;
|
|
791
672
|
takeElementScreenshot?(selector: string): Promise<Buffer>;
|
|
792
673
|
takeCleanScreenshot(): Promise<Buffer>;
|
|
793
|
-
/**
|
|
794
|
-
* Serialize the current page DOM into a sanitized HTML string for the
|
|
795
|
-
* Interactive Demo pipeline. Returns the HTML and the asset URLs found
|
|
796
|
-
* during sanitization (img/source/link). Optional — adapters that don't
|
|
797
|
-
* implement this cannot run `CAPTURE_DOM` opcodes.
|
|
798
|
-
*/
|
|
799
|
-
serializeDom?(selector?: string): Promise<{
|
|
800
|
-
html: string;
|
|
801
|
-
assetUrls: string[];
|
|
802
|
-
viewport: {
|
|
803
|
-
width: number;
|
|
804
|
-
height: number;
|
|
805
|
-
};
|
|
806
|
-
capturedAt: string;
|
|
807
|
-
}>;
|
|
808
|
-
/**
|
|
809
|
-
* Serialize a single subtree of the page (matched by `selector`) as a
|
|
810
|
-
* sanitized fragment HTML string. Optional — adapters that don't
|
|
811
|
-
* implement this cannot run `CAPTURE_FRAGMENT` opcodes. Phase 5 of
|
|
812
|
-
* AUT-121 Interactive Demos.
|
|
813
|
-
*/
|
|
814
|
-
serializeFragment?(selector: string): Promise<{
|
|
815
|
-
html: string;
|
|
816
|
-
assetUrls: string[];
|
|
817
|
-
capturedAt: string;
|
|
818
|
-
}>;
|
|
819
674
|
beginRecording(options: RecordingOptions): Promise<void>;
|
|
820
675
|
endRecording(): Promise<RecordingResult>;
|
|
821
676
|
setLocale(locale: string): Promise<void>;
|
package/dist/execution-types.js
CHANGED
|
@@ -20,8 +20,6 @@ export const OPCODE_KINDS = [
|
|
|
20
20
|
'SET_THEME',
|
|
21
21
|
'SCROLL',
|
|
22
22
|
'CAPTURE_SCREENSHOT',
|
|
23
|
-
'CAPTURE_DOM',
|
|
24
|
-
'CAPTURE_FRAGMENT',
|
|
25
23
|
'BEGIN_CLIP',
|
|
26
24
|
'END_CLIP',
|
|
27
25
|
'HOVER',
|
|
@@ -57,7 +55,7 @@ export const DEFAULT_RECOVERY_POLICY = {
|
|
|
57
55
|
allowHealer: false,
|
|
58
56
|
};
|
|
59
57
|
// ── Artifact spec ───────────────────────────────────────────────────
|
|
60
|
-
export const MEDIA_MODES = ['screenshot', 'clip'
|
|
58
|
+
export const MEDIA_MODES = ['screenshot', 'clip'];
|
|
61
59
|
export const DEFAULT_CIRCUIT_BREAKER = {
|
|
62
60
|
maxPerOpcode: 3,
|
|
63
61
|
maxPerPage: 5,
|
package/dist/logger.js
CHANGED
|
@@ -82,7 +82,7 @@ export const logger = {
|
|
|
82
82
|
emit({ level: 'success', message: msg, timestamp: Date.now(), kind: 'system' });
|
|
83
83
|
},
|
|
84
84
|
error(msg) {
|
|
85
|
-
console.
|
|
85
|
+
console.error(chalk.red('[ERROR]'), msg);
|
|
86
86
|
emit({ level: 'error', message: msg, timestamp: Date.now(), kind: 'system' });
|
|
87
87
|
},
|
|
88
88
|
ai(msg) {
|
package/dist/mockup-html.d.ts
CHANGED
|
@@ -96,6 +96,8 @@ export interface MockupLayout {
|
|
|
96
96
|
};
|
|
97
97
|
/** CSS border-radius for the screen area */
|
|
98
98
|
screenBorderRadius: string;
|
|
99
|
+
/** CSS border-radius for the content area */
|
|
100
|
+
contentBorderRadius: string;
|
|
99
101
|
}
|
|
100
102
|
/**
|
|
101
103
|
* Compute the mockup layout — positions and dimensions for all elements.
|
package/dist/mockup-html.js
CHANGED
|
@@ -41,6 +41,13 @@ export function computeMockupLayout(params) {
|
|
|
41
41
|
const screenBorderRadius = isLaptop
|
|
42
42
|
? `${screenCornerRadius}px ${screenCornerRadius}px 0 0`
|
|
43
43
|
: `${screenCornerRadius}px`;
|
|
44
|
+
const contentBorderRadius = topPx > 0 && bottomPx > 0
|
|
45
|
+
? "0"
|
|
46
|
+
: topPx > 0
|
|
47
|
+
? (isLaptop ? "0" : `0 0 ${screenCornerRadius}px ${screenCornerRadius}px`)
|
|
48
|
+
: bottomPx > 0
|
|
49
|
+
? `${screenCornerRadius}px ${screenCornerRadius}px 0 0`
|
|
50
|
+
: screenBorderRadius;
|
|
44
51
|
return {
|
|
45
52
|
containerWidth,
|
|
46
53
|
containerHeight,
|
|
@@ -57,6 +64,7 @@ export function computeMockupLayout(params) {
|
|
|
57
64
|
height: screenRect.height,
|
|
58
65
|
},
|
|
59
66
|
screenBorderRadius,
|
|
67
|
+
contentBorderRadius,
|
|
60
68
|
};
|
|
61
69
|
}
|
|
62
70
|
// ── HTML Generation ─────────────────────────────────────────────────────
|
|
@@ -95,6 +103,10 @@ function generateMockupHtmlInternal(params, options) {
|
|
|
95
103
|
.split(' ')
|
|
96
104
|
.map((v) => px(parseFloat(v)) + 'px')
|
|
97
105
|
.join(' ');
|
|
106
|
+
const contentBorderRadiusCss = layout.contentBorderRadius
|
|
107
|
+
.split(' ')
|
|
108
|
+
.map((v) => (v === '0' ? '0' : px(parseFloat(v)) + 'px'))
|
|
109
|
+
.join(' ');
|
|
98
110
|
// ── Screen background + safe area fills (z:1) ──
|
|
99
111
|
let safeAreaFillsHtml = '';
|
|
100
112
|
const topColor = safeAreaColors?.top ?? screenBackground;
|
|
@@ -117,16 +129,7 @@ function generateMockupHtmlInternal(params, options) {
|
|
|
117
129
|
// ── Content (z:2) ──
|
|
118
130
|
let contentSlotHtml = '';
|
|
119
131
|
if (contentHtml) {
|
|
120
|
-
|
|
121
|
-
// touches the screen edge (no safe area on that side).
|
|
122
|
-
const contentBorderRadius = topPx > 0 && bottomPx > 0
|
|
123
|
-
? '0' // Content doesn't touch any rounded edge
|
|
124
|
-
: topPx > 0
|
|
125
|
-
? (isLaptop ? '0' : `0 0 ${px(cornerPx)}px ${px(cornerPx)}px`)
|
|
126
|
-
: bottomPx > 0
|
|
127
|
-
? `${px(cornerPx)}px ${px(cornerPx)}px 0 0`
|
|
128
|
-
: screenBorderRadiusCss;
|
|
129
|
-
contentSlotHtml = `<div style="position:absolute;left:${px(ca.x)}px;top:${px(ca.y)}px;width:${px(ca.width)}px;height:${px(ca.height)}px;z-index:2;overflow:hidden;border-radius:${contentBorderRadius}">${contentHtml}</div>`;
|
|
132
|
+
contentSlotHtml = `<div style="position:absolute;left:${px(ca.x)}px;top:${px(ca.y)}px;width:${px(ca.width)}px;height:${px(ca.height)}px;z-index:2;overflow:hidden;border-radius:${contentBorderRadiusCss}">${contentHtml}</div>`;
|
|
130
133
|
}
|
|
131
134
|
// ── Home indicator (z:4) ──
|
|
132
135
|
let homeIndicatorHtml = '';
|
package/dist/mockup.js
CHANGED
|
@@ -637,7 +637,7 @@ export async function applyDeviceFrame(screenshot, deviceId, options) {
|
|
|
637
637
|
compositeInputs.push({ input: frameData, left: 0, top: 0 });
|
|
638
638
|
}
|
|
639
639
|
// z:1 — Screen background with rounded corners + safe area fills
|
|
640
|
-
// Build the screen content as a sub-
|
|
640
|
+
// Build the screen content as a masked sub-layer
|
|
641
641
|
const screenW = p(sr.width);
|
|
642
642
|
const screenH = p(sr.height);
|
|
643
643
|
if (screenW > 0 && screenH > 0) {
|
|
@@ -779,7 +779,7 @@ export async function applyDeviceFrame(screenshot, deviceId, options) {
|
|
|
779
779
|
left: 0, top: 0,
|
|
780
780
|
});
|
|
781
781
|
}
|
|
782
|
-
// Final
|
|
782
|
+
// Final assembled image
|
|
783
783
|
return sharp({
|
|
784
784
|
create: { width: pw, height: ph, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
|
|
785
785
|
}).composite(compositeInputs).png().toBuffer();
|
package/dist/opcode-actions.js
CHANGED
|
@@ -214,8 +214,6 @@ export async function executeOpcodeCoreAction(opcode, adapter, context = {}) {
|
|
|
214
214
|
case 'INJECT_MOCK_DATA':
|
|
215
215
|
return applyInjectMockDataOpcode(opcode, adapter, context);
|
|
216
216
|
case 'CAPTURE_SCREENSHOT':
|
|
217
|
-
case 'CAPTURE_DOM':
|
|
218
|
-
case 'CAPTURE_FRAGMENT':
|
|
219
217
|
case 'BEGIN_CLIP':
|
|
220
218
|
case 'END_CLIP':
|
|
221
219
|
case 'ASSERT_ROUTE':
|
package/dist/opcode-runner.js
CHANGED
|
@@ -23,8 +23,6 @@ function formatOpcodeDebug(opcode) {
|
|
|
23
23
|
fields.push(`url="${o.url}"`);
|
|
24
24
|
if (typeof o.text === 'string')
|
|
25
25
|
fields.push(`text="${o.text.slice(0, 40)}"`);
|
|
26
|
-
if (typeof o.stateName === 'string')
|
|
27
|
-
fields.push(`stateName="${o.stateName}"`);
|
|
28
26
|
if (typeof o.groupName === 'string')
|
|
29
27
|
fields.push(`group="${o.groupName}"`);
|
|
30
28
|
if (opcode.postcondition)
|
|
@@ -588,125 +586,6 @@ async function executeOpcodeAction(opcode, opcodeIndex, adapter, artifacts, tele
|
|
|
588
586
|
});
|
|
589
587
|
break;
|
|
590
588
|
}
|
|
591
|
-
case 'CAPTURE_DOM': {
|
|
592
|
-
if (opcode.selector) {
|
|
593
|
-
const attached = await adapter.waitFor({
|
|
594
|
-
selector: opcode.selector,
|
|
595
|
-
state: 'attached',
|
|
596
|
-
timeoutMs: 5000,
|
|
597
|
-
});
|
|
598
|
-
if (!attached) {
|
|
599
|
-
return {
|
|
600
|
-
success: false,
|
|
601
|
-
error: `CAPTURE_DOM could not find selector "${opcode.selector}" in the DOM`,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
await smartWaitForStability(adapter, { maxWaitMs: 5000 });
|
|
606
|
-
if (!adapter.serializeDom) {
|
|
607
|
-
return {
|
|
608
|
-
success: false,
|
|
609
|
-
error: 'CAPTURE_DOM requires an adapter that implements serializeDom() — Interactive Demos pipeline (AUT-121)',
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
const captureUrl = await adapter.getCurrentUrl();
|
|
613
|
-
const serialized = await adapter.serializeDom(opcode.selector);
|
|
614
|
-
const buffer = Buffer.from(serialized.html, 'utf8');
|
|
615
|
-
// Take a viewport screenshot for the thumbnail
|
|
616
|
-
const thumbnailBuffer = await adapter.takeScreenshot();
|
|
617
|
-
artifacts.push({
|
|
618
|
-
mediaMode: 'dom',
|
|
619
|
-
buffer,
|
|
620
|
-
mimeType: 'text/html; charset=utf-8',
|
|
621
|
-
captureType: opcode.selector ? 'element' : 'fullpage',
|
|
622
|
-
captureUrl,
|
|
623
|
-
dimensions: serialized.viewport,
|
|
624
|
-
captureId: opcode.stateName,
|
|
625
|
-
captureName: opcode.stateName,
|
|
626
|
-
stepDescription: opcode.description,
|
|
627
|
-
stepIndex: opcodeIndex,
|
|
628
|
-
variantId: currentVariant?.id,
|
|
629
|
-
elementSelector: opcode.selector,
|
|
630
|
-
stateName: opcode.stateName,
|
|
631
|
-
domHtml: serialized.html,
|
|
632
|
-
domAssetUrls: serialized.assetUrls,
|
|
633
|
-
domHtmlBytes: buffer.byteLength,
|
|
634
|
-
domThumbnailBuffer: thumbnailBuffer,
|
|
635
|
-
});
|
|
636
|
-
break;
|
|
637
|
-
}
|
|
638
|
-
case 'CAPTURE_FRAGMENT': {
|
|
639
|
-
if (!adapter.serializeFragment) {
|
|
640
|
-
return {
|
|
641
|
-
success: false,
|
|
642
|
-
error: 'CAPTURE_FRAGMENT requires an adapter that implements serializeFragment() — Interactive Demos Phase 5',
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
// Optional hidden trigger button (mirrors the mock data trigger
|
|
646
|
-
// pattern). Useful for fragments that aren't reachable via
|
|
647
|
-
// visible UI. The trigger is clicked via clickHidden() so it
|
|
648
|
-
// bypasses visibility/disabled checks.
|
|
649
|
-
if (opcode.triggerSelector) {
|
|
650
|
-
if (!adapter.clickHidden) {
|
|
651
|
-
return {
|
|
652
|
-
success: false,
|
|
653
|
-
error: `CAPTURE_FRAGMENT.triggerSelector requires adapter.clickHidden() support`,
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
try {
|
|
657
|
-
await adapter.clickHidden({ selector: opcode.triggerSelector });
|
|
658
|
-
}
|
|
659
|
-
catch (err) {
|
|
660
|
-
return {
|
|
661
|
-
success: false,
|
|
662
|
-
error: `CAPTURE_FRAGMENT trigger click failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
// Wait for the fragment selector to be present in the DOM (the
|
|
667
|
-
// trigger may have inserted it dynamically). Mirror the screenshot
|
|
668
|
-
// smart-wait so animations / portal mounts settle before serialization.
|
|
669
|
-
const visible = await adapter.waitFor({
|
|
670
|
-
selector: opcode.selector,
|
|
671
|
-
state: 'attached',
|
|
672
|
-
timeoutMs: 5000,
|
|
673
|
-
});
|
|
674
|
-
if (!visible) {
|
|
675
|
-
return {
|
|
676
|
-
success: false,
|
|
677
|
-
error: `CAPTURE_FRAGMENT could not find selector "${opcode.selector}" in the DOM`,
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
await smartWaitForStability(adapter, { maxWaitMs: 3000 });
|
|
681
|
-
const captureUrl = await adapter.getCurrentUrl();
|
|
682
|
-
const serialized = await adapter.serializeFragment(opcode.selector);
|
|
683
|
-
const buffer = Buffer.from(serialized.html, 'utf8');
|
|
684
|
-
const variantName = opcode.variantName ?? 'default';
|
|
685
|
-
artifacts.push({
|
|
686
|
-
mediaMode: 'dom',
|
|
687
|
-
buffer,
|
|
688
|
-
mimeType: 'text/html; charset=utf-8',
|
|
689
|
-
captureType: 'element',
|
|
690
|
-
captureUrl,
|
|
691
|
-
// captureId is the unique upload key — including the variant
|
|
692
|
-
// ensures multiple variants of the same fragment do not collide.
|
|
693
|
-
captureId: `${opcode.parentState}/${opcode.fragmentName}/${variantName}`,
|
|
694
|
-
captureName: opcode.fragmentName,
|
|
695
|
-
elementSelector: opcode.selector,
|
|
696
|
-
stepDescription: opcode.description,
|
|
697
|
-
stepIndex: opcodeIndex,
|
|
698
|
-
variantId: currentVariant?.id,
|
|
699
|
-
fragmentName: opcode.fragmentName,
|
|
700
|
-
fragmentVariantName: variantName,
|
|
701
|
-
parentStateName: opcode.parentState,
|
|
702
|
-
mountStrategy: opcode.mountStrategy ?? 'inline',
|
|
703
|
-
mountTargetSelector: opcode.mountTargetSelector,
|
|
704
|
-
domHtml: serialized.html,
|
|
705
|
-
domAssetUrls: serialized.assetUrls,
|
|
706
|
-
domHtmlBytes: buffer.byteLength,
|
|
707
|
-
});
|
|
708
|
-
break;
|
|
709
|
-
}
|
|
710
589
|
default: {
|
|
711
590
|
const _exhaustive = opcode;
|
|
712
591
|
return { success: false, error: `unknown opcode kind: ${opcode.kind}` };
|
|
@@ -34,7 +34,6 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
|
|
|
34
34
|
mediaMode: z.ZodEnum<{
|
|
35
35
|
clip: "clip";
|
|
36
36
|
screenshot: "screenshot";
|
|
37
|
-
dom: "dom";
|
|
38
37
|
}>;
|
|
39
38
|
baseUrl: z.ZodString;
|
|
40
39
|
maxParallelCaptures: z.ZodOptional<z.ZodNumber>;
|
|
@@ -53,6 +52,56 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
|
|
|
53
52
|
targetId: z.ZodOptional<z.ZodString>;
|
|
54
53
|
targetLabel: z.ZodOptional<z.ZodString>;
|
|
55
54
|
deviceFrame: z.ZodOptional<z.ZodString>;
|
|
55
|
+
mockupOptions: z.ZodOptional<z.ZodObject<{
|
|
56
|
+
orientation: z.ZodOptional<z.ZodEnum<{
|
|
57
|
+
portrait: "portrait";
|
|
58
|
+
landscape: "landscape";
|
|
59
|
+
}>>;
|
|
60
|
+
outputScale: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
showStatusBar: z.ZodOptional<z.ZodBoolean>;
|
|
62
|
+
showSafeAreaTop: z.ZodOptional<z.ZodBoolean>;
|
|
63
|
+
showSafeAreaBottom: z.ZodOptional<z.ZodBoolean>;
|
|
64
|
+
showSafeAreaLeft: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
showSafeAreaRight: z.ZodOptional<z.ZodBoolean>;
|
|
66
|
+
showHomeIndicator: z.ZodOptional<z.ZodBoolean>;
|
|
67
|
+
safeAreaTopColor: z.ZodOptional<z.ZodString>;
|
|
68
|
+
safeAreaBottomColor: z.ZodOptional<z.ZodString>;
|
|
69
|
+
safeAreaLeftColor: z.ZodOptional<z.ZodString>;
|
|
70
|
+
safeAreaRightColor: z.ZodOptional<z.ZodString>;
|
|
71
|
+
statusBar: z.ZodOptional<z.ZodObject<{
|
|
72
|
+
time: z.ZodOptional<z.ZodString>;
|
|
73
|
+
date: z.ZodOptional<z.ZodString>;
|
|
74
|
+
menuBarApp: z.ZodOptional<z.ZodString>;
|
|
75
|
+
menuBarItems: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
76
|
+
signalStrength: z.ZodOptional<z.ZodNumber>;
|
|
77
|
+
showNetworkType: z.ZodOptional<z.ZodBoolean>;
|
|
78
|
+
networkType: z.ZodOptional<z.ZodString>;
|
|
79
|
+
wifiStrength: z.ZodOptional<z.ZodNumber>;
|
|
80
|
+
batteryLevel: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
batteryCharging: z.ZodOptional<z.ZodBoolean>;
|
|
82
|
+
showBatteryPercentage: z.ZodOptional<z.ZodBoolean>;
|
|
83
|
+
colorScheme: z.ZodOptional<z.ZodEnum<{
|
|
84
|
+
light: "light";
|
|
85
|
+
dark: "dark";
|
|
86
|
+
}>>;
|
|
87
|
+
carrierName: z.ZodOptional<z.ZodString>;
|
|
88
|
+
autoLocale: z.ZodOptional<z.ZodBoolean>;
|
|
89
|
+
}, z.core.$strict>>;
|
|
90
|
+
browserBar: z.ZodOptional<z.ZodObject<{
|
|
91
|
+
url: z.ZodOptional<z.ZodString>;
|
|
92
|
+
pageTitle: z.ZodOptional<z.ZodString>;
|
|
93
|
+
tabIconUrl: z.ZodOptional<z.ZodString>;
|
|
94
|
+
}, z.core.$strict>>;
|
|
95
|
+
windowBorder: z.ZodOptional<z.ZodObject<{
|
|
96
|
+
color: z.ZodString;
|
|
97
|
+
width: z.ZodNumber;
|
|
98
|
+
radius: z.ZodNumber;
|
|
99
|
+
}, z.core.$strict>>;
|
|
100
|
+
colorScheme: z.ZodOptional<z.ZodEnum<{
|
|
101
|
+
light: "light";
|
|
102
|
+
dark: "dark";
|
|
103
|
+
}>>;
|
|
104
|
+
}, z.core.$strict>>;
|
|
56
105
|
}, z.core.$strict>>;
|
|
57
106
|
preconditions: z.ZodObject<{
|
|
58
107
|
credentialsId: z.ZodOptional<z.ZodString>;
|
|
@@ -542,73 +591,6 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
|
|
|
542
591
|
timeoutMs: z.ZodNumber;
|
|
543
592
|
maxFailures: z.ZodNumber;
|
|
544
593
|
kind: z.ZodLiteral<"CAPTURE_SCREENSHOT">;
|
|
545
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
546
|
-
stateName: z.ZodString;
|
|
547
|
-
selector: z.ZodOptional<z.ZodString>;
|
|
548
|
-
description: z.ZodString;
|
|
549
|
-
postcondition: z.ZodObject<{
|
|
550
|
-
type: z.ZodEnum<{
|
|
551
|
-
route_matches: "route_matches";
|
|
552
|
-
element_visible: "element_visible";
|
|
553
|
-
element_absent: "element_absent";
|
|
554
|
-
text_contains: "text_contains";
|
|
555
|
-
overlay_dismissed: "overlay_dismissed";
|
|
556
|
-
screenshot_stable: "screenshot_stable";
|
|
557
|
-
any_change: "any_change";
|
|
558
|
-
always: "always";
|
|
559
|
-
}>;
|
|
560
|
-
pattern: z.ZodOptional<z.ZodString>;
|
|
561
|
-
selector: z.ZodOptional<z.ZodString>;
|
|
562
|
-
text: z.ZodOptional<z.ZodString>;
|
|
563
|
-
threshold: z.ZodOptional<z.ZodNumber>;
|
|
564
|
-
waitMs: z.ZodOptional<z.ZodNumber>;
|
|
565
|
-
}, z.core.$strict>;
|
|
566
|
-
recovery: z.ZodObject<{
|
|
567
|
-
retries: z.ZodNumber;
|
|
568
|
-
useSelectorMemory: z.ZodBoolean;
|
|
569
|
-
useAltInteraction: z.ZodBoolean;
|
|
570
|
-
allowReload: z.ZodBoolean;
|
|
571
|
-
allowHealer: z.ZodBoolean;
|
|
572
|
-
}, z.core.$strict>;
|
|
573
|
-
timeoutMs: z.ZodNumber;
|
|
574
|
-
maxFailures: z.ZodNumber;
|
|
575
|
-
kind: z.ZodLiteral<"CAPTURE_DOM">;
|
|
576
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
577
|
-
fragmentName: z.ZodString;
|
|
578
|
-
variantName: z.ZodOptional<z.ZodString>;
|
|
579
|
-
parentState: z.ZodString;
|
|
580
|
-
selector: z.ZodString;
|
|
581
|
-
triggerSelector: z.ZodOptional<z.ZodString>;
|
|
582
|
-
mountStrategy: z.ZodOptional<z.ZodString>;
|
|
583
|
-
mountTargetSelector: z.ZodOptional<z.ZodString>;
|
|
584
|
-
description: z.ZodString;
|
|
585
|
-
postcondition: z.ZodObject<{
|
|
586
|
-
type: z.ZodEnum<{
|
|
587
|
-
route_matches: "route_matches";
|
|
588
|
-
element_visible: "element_visible";
|
|
589
|
-
element_absent: "element_absent";
|
|
590
|
-
text_contains: "text_contains";
|
|
591
|
-
overlay_dismissed: "overlay_dismissed";
|
|
592
|
-
screenshot_stable: "screenshot_stable";
|
|
593
|
-
any_change: "any_change";
|
|
594
|
-
always: "always";
|
|
595
|
-
}>;
|
|
596
|
-
pattern: z.ZodOptional<z.ZodString>;
|
|
597
|
-
selector: z.ZodOptional<z.ZodString>;
|
|
598
|
-
text: z.ZodOptional<z.ZodString>;
|
|
599
|
-
threshold: z.ZodOptional<z.ZodNumber>;
|
|
600
|
-
waitMs: z.ZodOptional<z.ZodNumber>;
|
|
601
|
-
}, z.core.$strict>;
|
|
602
|
-
recovery: z.ZodObject<{
|
|
603
|
-
retries: z.ZodNumber;
|
|
604
|
-
useSelectorMemory: z.ZodBoolean;
|
|
605
|
-
useAltInteraction: z.ZodBoolean;
|
|
606
|
-
allowReload: z.ZodBoolean;
|
|
607
|
-
allowHealer: z.ZodBoolean;
|
|
608
|
-
}, z.core.$strict>;
|
|
609
|
-
timeoutMs: z.ZodNumber;
|
|
610
|
-
maxFailures: z.ZodNumber;
|
|
611
|
-
kind: z.ZodLiteral<"CAPTURE_FRAGMENT">;
|
|
612
594
|
}, z.core.$strict>, z.ZodObject<{
|
|
613
595
|
clipId: z.ZodOptional<z.ZodString>;
|
|
614
596
|
clipName: z.ZodOptional<z.ZodString>;
|
|
@@ -1030,7 +1012,6 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
|
|
|
1030
1012
|
mediaMode: z.ZodEnum<{
|
|
1031
1013
|
clip: "clip";
|
|
1032
1014
|
screenshot: "screenshot";
|
|
1033
|
-
dom: "dom";
|
|
1034
1015
|
}>;
|
|
1035
1016
|
format: z.ZodOptional<z.ZodObject<{
|
|
1036
1017
|
clipFormat: z.ZodOptional<z.ZodEnum<{
|
|
@@ -1051,9 +1032,6 @@ export declare const SignedExecutionProgramEnvelopeSchema: z.ZodObject<{
|
|
|
1051
1032
|
maxClipDurationSec: z.ZodOptional<z.ZodNumber>;
|
|
1052
1033
|
applyMockup: z.ZodOptional<z.ZodBoolean>;
|
|
1053
1034
|
applyStatusBar: z.ZodOptional<z.ZodBoolean>;
|
|
1054
|
-
domOptions: z.ZodOptional<z.ZodObject<{
|
|
1055
|
-
sanitize: z.ZodOptional<z.ZodBoolean>;
|
|
1056
|
-
}, z.core.$strict>>;
|
|
1057
1035
|
}, z.core.$strict>;
|
|
1058
1036
|
outputScale: z.ZodOptional<z.ZodNumber>;
|
|
1059
1037
|
compileFingerprint: z.ZodString;
|
package/dist/security.js
CHANGED
|
@@ -399,7 +399,7 @@ function evaluateTypingTarget(target, typedText, context) {
|
|
|
399
399
|
return { allowed: false, reason: 'Blocked by security policy: target field is not a safe text input.', target };
|
|
400
400
|
}
|
|
401
401
|
if (TYPE_BLOCK_RE.test(text)) {
|
|
402
|
-
return { allowed: false, reason: 'Blocked by security policy: content
|
|
402
|
+
return { allowed: false, reason: 'Blocked by security policy: rich-content field.', target };
|
|
403
403
|
}
|
|
404
404
|
if (authContext) {
|
|
405
405
|
return { allowed: true, target };
|
|
@@ -408,7 +408,7 @@ function evaluateTypingTarget(target, typedText, context) {
|
|
|
408
408
|
return { allowed: true, target };
|
|
409
409
|
}
|
|
410
410
|
// Default: allow typing into text-like inputs that passed all safety checks above
|
|
411
|
-
// (not a textarea, not a content
|
|
411
|
+
// (not a textarea, not a rich-content field, not a non-text input type)
|
|
412
412
|
return { allowed: true, target };
|
|
413
413
|
}
|
|
414
414
|
export function evaluateResolvedActionSecurity(action, args, context, target) {
|