autokap 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/assets/skill/OPCODE-REFERENCE.md +1 -41
  2. package/assets/skill/README.md +0 -1
  3. package/assets/skill/SKILL.md +9 -32
  4. package/assets/skill/references/examples.md +1 -17
  5. package/dist/billing-operation-logging.d.ts +1 -3
  6. package/dist/billing-operation-logging.js +0 -4
  7. package/dist/capture-strategy.d.ts +2 -8
  8. package/dist/capture-strategy.js +2 -30
  9. package/dist/cli-config.d.ts +2 -1
  10. package/dist/cli-config.js +18 -2
  11. package/dist/cli-contract.d.ts +1 -0
  12. package/dist/cli-contract.js +8 -2
  13. package/dist/cli-runner-local.d.ts +2 -0
  14. package/dist/cli-runner-local.js +12 -21
  15. package/dist/cli-runner.d.ts +4 -0
  16. package/dist/cli-runner.js +30 -50
  17. package/dist/cli.js +89 -44
  18. package/dist/execution-schema.d.ts +143 -331
  19. package/dist/execution-schema.js +43 -28
  20. package/dist/execution-types.d.ts +6 -151
  21. package/dist/execution-types.js +1 -3
  22. package/dist/logger.js +1 -1
  23. package/dist/mockup-html.d.ts +2 -0
  24. package/dist/mockup-html.js +13 -10
  25. package/dist/mockup.js +2 -2
  26. package/dist/opcode-actions.js +0 -2
  27. package/dist/opcode-runner.js +0 -121
  28. package/dist/program-signing.d.ts +50 -72
  29. package/dist/security.js +2 -2
  30. package/dist/server-capture-runtime.d.ts +0 -1
  31. package/dist/server-capture-runtime.js +0 -3
  32. package/dist/server-credit-usage.d.ts +1 -1
  33. package/dist/skill-packaging.d.ts +1 -1
  34. package/dist/skill-packaging.js +1 -11
  35. package/dist/types.d.ts +2 -2
  36. package/dist/web-playwright-local.d.ts +0 -14
  37. package/dist/web-playwright-local.js +0 -194
  38. package/package.json +1 -18
  39. package/readme.md +13 -0
  40. package/assets/skill/STUDIO-SKILL.md +0 -476
  41. 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", "CAPTURE_DOM", "CAPTURE_FRAGMENT", "BEGIN_CLIP", "END_CLIP", "HOVER", "SELECT_OPTION", "CHECK", "DOUBLE_CLICK", "DRAG", "CLONE_ELEMENT", "INJECT_MOCK_DATA", "REMOVE_ELEMENT", "SET_ATTRIBUTE"];
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 | CaptureDomOpcode | CaptureFragmentOpcode | BeginClipOpcode | EndClipOpcode | HoverOpcode | SelectOptionOpcode | CheckOpcode | DoubleClickOpcode | DragOpcode | CloneElementOpcode | InjectMockDataOpcode | RemoveElementOpcode | SetAttributeOpcode;
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", "dom"];
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>;
@@ -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', 'dom'];
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.log(chalk.red('[ERROR]'), msg);
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) {
@@ -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.
@@ -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
- // Content area border-radius: only round corners where the content
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-composition with rounded-corner mask
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 composition
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();
@@ -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':
@@ -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-composition field.', target };
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-composition field, not a non-text input type)
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) {
@@ -18,7 +18,6 @@ export interface BillingPlanEntitlements {
18
18
  aiDailyCostLimitUsd: number;
19
19
  apiRateLimitRpm: number;
20
20
  maxDevLinks: number | null;
21
- maxCompositions: number | null;
22
21
  maxTeamMembersPerProject: number | null;
23
22
  }
24
23
  export interface BillingPlan {