Skip to content

[Feat] feature-demo skill: polished, narrated demo videos - #1177

Open
mrubens wants to merge 11 commits into
developfrom
feat/feature-demo-skill
Open

[Feat] feature-demo skill: polished, narrated demo videos#1177
mrubens wants to merge 11 commits into
developfrom
feat/feature-demo-skill

Conversation

@mrubens

@mrubens mrubens commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

What

A new feature-demo standard skill that produces polished, Screen Studio-style demo videos of product features: a real recording driven live in the sandbox browser, post-produced with smooth zoom-to-element moves, a synthetic cursor, click ripples, captions, and optional voice-over narration.

Invoke with $feature-demo or by asking for a demo video in a task.

How it works

One declarative demo script drives everything. The capture runner performs the real browser interactions (scroll, hover, click, type) via the agent-browser CLI and records, on the same clock, where the cursor is, when clicks land, and the resolved rectangle of every focused element. The bundled Remotion composition consumes that timeline, so a zoom can never drift from the element it targets.

Pipeline: author script → capture (delegated to the proof-runner subagent, keeping browser containment intact) → narrate (optional) → fit timing → render → verify → upload via manage_artifacts.

The bundled Remotion project is a reference template, not a fixed pipeline stage: the agent copies it into the work dir and adapts the copy (branding, caption styling, layout, presets) when a demo calls for it, installing Remotion's official agent skills (remotion-dev/skills — their sanctioned successor to the deprecated Remotion MCP) for API guidance. The timeline JSON is the stable contract between capture and render, and a render/README.md documents the hard-won invariants worth preserving (counter-scaled cursor, edge-clamp guard, timeline-driven interpolation).

The timing fit encodes what makes these demos feel produced rather than recorded: the dead opening hold is trimmed, the voice-over is paced into a conversational band (pitch-preserving atempo), and the recording's playback rate is solved so each spoken line finishes just before its zoom lands.

Narration without the key in the sandbox

Narration uses ElevenLabs, but the provider key never enters a task sandbox:

  • New control-plane endpoint POST /api/tts/narration (route policy task-token, rate-limited, streamed body cap). The sandbox posts plain caption text with its run-scoped token; the control plane holds R_ELEVENLABS_API_KEY / R_ELEVENLABS_VOICE_ID and returns audio. Same posture as the inference gateway and custom MCP proxy.
  • R_ELEVENLABS_* are registered as control-plane env names, so they are reserved from the generic env editor and stripped from sandbox env injection.
  • Deployments without the vars get a 404 and the skill degrades cleanly to captions-only.

Worker image

Remotion cannot render with the bundled agent-browser Chrome (Chrome 143 removed old-headless), so the image now bakes Remotion's chrome-headless-shell at /opt/remotion/headless-shell (both architectures, pinned, arch-independent symlink). Older snapshots fall back to npx remotion browser ensure at render time — the smoke run below exercised exactly that fallback.

Testing

  • pnpm lint, pnpm check-types, pnpm knip green across workspaces.
  • New unit tests: TTS endpoint (auth, config-off 404, body caps including a lying/endless stream, upstream failure opacity, key injection), skill structure/policy assertions, control-plane env membership, Dockerfile parity.
  • End-to-end smoke inside the real worker sandbox image using the shipped skill files: capture of a live docs page → captions-only timing fit (trimmed 1.21s of dead opening) → npm install + headless-shell fallback → rendered a clean 7.6s 1080p mp4, verified with ffprobe and frame inspection.

Docs

  • Cookbook recipe: Record feature demo videos (setup, narration env vars, tips).
  • environment-variables reference for the two new optional vars, including the recommendation to scope the ElevenLabs key to text-to-speech only.

A new standard skill that produces Screen Studio-style feature demos:
the demo script drives real browser interactions (delegated to the
proof-runner subagent via agent-browser) while logging cursor, click,
and element-rect timelines on the same clock, then a bundled Remotion
composition post-produces the recording with zoom-to-element moves,
click ripples, captions, and optional voice-over narration.

- packages/cloud-agents .../feature-demo: SKILL.md orchestration,
  capture runner, narration + timing-fit scripts, render project
  (excluded from tsc/knip/oxlint like agent-browser; copied verbatim
  into the sandbox skills dir at activation)
- apps/api /api/tts/narration: control-plane TTS endpoint (task-token
  policy + rate limit). ElevenLabs credentials live only on the control
  plane; R_ELEVENLABS_* are control-plane env names, stripped from
  sandbox env injection. Unconfigured deployments 404 and the skill
  degrades to captions-only.
- apps/worker/Dockerfile: bake Remotion's chrome-headless-shell at
  /opt/remotion/headless-shell (the bundled Chrome removed old
  headless); runtime fallback via remotion browser ensure for older
  snapshots.
- docs: cookbook recipe + environment variable reference.
@roomote-community

roomote-community Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

1 issue outstanding. See task

  • packages/cloud-agents/src/server/workflows/skills/standard/feature-demo/scripts/fit-timing.mjs:164-169 Narrated clips are scheduled to end after their focus zooms. — dismissed: narration is intentionally set to continue through the zoom hold.
  • packages/cloud-agents/src/server/workflows/skills/standard/feature-demo/capture/capture.mjs:112-116 The synthetic cursor moves during intervals where the recorded mouse is still stationary.
  • packages/cloud-agents/src/server/workflows/skills/standard/feature-demo/scripts/fit-timing.mjs:146 Narration uses caption display times instead of focus-zoom landing times, so it begins before the zoom starts.
  • apps/worker/Dockerfile:242-246 Comment lines break the continued RUN command and prevent the worker image from building.
  • packages/cloud-agents/src/server/workflows/skills/standard/feature-demo/SKILL.md:89 The timing instructions say narration ends before its zoom, while the fitter intentionally starts it shortly before landing and plays it through the hold.
  • apps/worker/src/run-task/proof-runner-prompt.ts:30 The feature-demo exception authorizes any parent-staged file at a mutable /tmp path as proof-runner code.

Reviewed b3175e1

Comment on lines +52 to +54
const js = `(function(){var e=document.querySelector(${JSON.stringify(
sel,
)});if(!e)return null;var r=e.getBoundingClientRect();return{x:r.x,y:r.y,w:r.width,h:r.height};})()`;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b995257: the selector now travels into the page-eval as base64 with a strict alphabet check before interpolation, so no selector bytes can reach the constructed code.

mrubens added 5 commits August 8, 2026 00:55
eleven_v3 reads slowly and needs the 1.12 atempo speed-up; v2-family
models pace naturally and should not be sped up. The narration endpoint
now returns its modelId, build-narration records it in the manifest,
and fit-timing derives the atempo default from it (argv still wins).
Per review direction: the bundled Remotion project is a starting point
the agent copies and adapts (branding, captions, layout, presets), not
a fixed pipeline stage. The timeline JSON stays the stable contract
between capture and render. When adapting, the skill installs Remotion's
official agent skills (remotion-dev/skills — their sanctioned successor
to the deprecated Remotion MCP) for current API guidance. A render/
README documents the bug classes worth preserving (counter-scaled
cursor, edge-clamp guard, timeline-driven interpolation).
Simplify per review: one model, one settings profile, no
R_ELEVENLABS_MODEL knob and no model-aware atempo plumbing.
multilingual_v2 reads voice clones naturally at a natural pace; the
timing fit's atempo stays available as an explicit argv override.
- capture: bracket cursor motion with a hold key at motion start so the
  synthetic cursor no longer drifts toward its next target through
  waits, holds, and scrolls (and clicks on a fresh element get a short
  bracketed hop instead of a slow glide).
- capture: pass selectors into the page-eval as alphabet-checked base64
  so no selector bytes are interpolated into code (CodeQL).
- fit-timing: solve the playback rate by simulating the schedule
  instead of a closed-form cumulative estimate; a line that waits for a
  late anchor ends later than the estimate, which could push later
  lines past their anchors. Lines intentionally START just before their
  zoom and play over its hold; the docs now say so explicitly.
The cookbook index is generated from recipe frontmatter; run the
generator instead of hand-editing between the markers.
The binary name differs per platform: headless_shell on linux-arm64,
chrome-headless-shell on linux64, so the hardcoded name failed the
amd64 image build. Resolve it with a find over both names (validated
under amd64 emulation and in the arm64 sandbox image).
Comment thread apps/worker/Dockerfile Outdated
Capture emits an explicit per-caption anchor (the zoom's landing time);
the timing fit schedules against it. caption.start begins with the
cursor glide ~moveMs earlier, so anchoring there started narration
before the zoom even began moving. Also hoist the Dockerfile block
comment above the RUN chain.
Comment thread packages/cloud-agents/src/server/workflows/skills/standard/feature-demo/SKILL.md Outdated
Field failure from a real game demo: CDP screencast only emits frames
on visual damage and stamps them without wall-clock gaps, so a 52s
interaction over a static-rendering surface collapsed into a sub-second
video. The capture runner now injects an imperceptible 2px rAF ticker
after record start so compositor damage flows at wall-clock rate
(static-page regression: 7.93s timeline -> 8.0s recording), closes any
pre-existing pages before recording (record start creates a fresh
browser context; a page left over from inspection would split beats
and recording across contexts), and fails loudly when the recording
comes back much shorter than the interaction instead of letting a
garbage video flow downstream. SKILL.md routes that failure to a
'stale sandbox runtime' blocker (old snapshots carry a 2018 legacy
ffmpeg that crashes record stop under real frame volume).
@mrubens

mrubens commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Field-failure fix: static surfaces recorded as sub-second videos

A real dogfood run (game demo) surfaced a capture failure mode none of the development surfaces hit: CDP screencast only emits frames on visual damage, so a statically-rendered app (games between state changes, idle dashboards) collapsed a 52-second interaction into a 0.8s video with no wall-clock mapping. Docs pages and fixtures masked this because scrolling and hover repaint constantly.

Fixed in 8dc12b2, all reproduced/verified against live sandboxes:

  • Capture injects an imperceptible 2px requestAnimationFrame ticker after record start, keeping compositor damage — and therefore frames — flowing at wall-clock rate. Static-page regression test in the worker image: 7.93s timeline → 8.0s recording (previously ~0.2s).
  • record start creates a fresh browser context, so capture now closes pre-existing pages first — otherwise a page left open by an earlier inspection step leaves the beats driving one context while the recorder watches another.
  • Honest-state gate: capture fails loudly when the recording is much shorter than the interaction, instead of letting a garbage video reach render/upload. SKILL.md routes the related record stop ffmpeg crash (old snapshots carry a 2018 legacy-installer ffmpeg) to a stale sandbox runtime blocker.

Second dogfood run failed at the delegation seam in two ways: the
brief's home-relative runner path did not resolve in the delegated
runtime, and the proof-runner refused the capture script as a
disallowed browser path (its instructions allow only the agent-browser
CLI, and a per-brief assurance is not reliable authority).

- The skill now stages the runner at /tmp/feature-demo/capture.mjs
  before delegating (no home-relative paths across the boundary) and
  parent-side paths use $HOME explicitly.
- The proof-runner prompt carries a narrow standing exception: the
  staged runner is an agent-browser orchestrator and running it as the
  brief specifies is compliant; the exception names that one path and
  no other script.
@mrubens

mrubens commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Second dogfood failure: the delegation seam

A second live run failed before capture in two ways, both at the parent→proof-runner boundary:

  1. Home-relative paths don't survive delegation — the brief pointed at ~/.agents/skills/feature-demo/..., which didn't resolve in the delegated runtime. The skill now stages the runner at /tmp/feature-demo/capture.mjs before delegating, and parent-side commands use $HOME explicitly.
  2. The proof-runner refused the runner as a disallowed browser path. Its instructions allow only the agent-browser CLI, and a per-brief assurance turned out to be unreliable authority — correctly so, arguably. The proof-runner prompt now carries a narrow standing exception: the staged runner at exactly that path is a sanctioned agent-browser orchestrator; no other script is covered.

Fixed in b17b6c4 with prompt/skill test coverage for both.

'- Before the first browser command, explicitly load the `agent-browser` skill or CLI-served guidance exactly once. If the OpenCode Skill tool is available, invoke the `agent-browser` skill. If it is not available, run `agent-browser skills get core --full` in the shell and treat that output as the browser usage guide.',
'- `agent-browser` is a command-line executable, not an OpenCode tool or MCP tool. Invoke it with shell commands such as `agent-browser get url`; do not look for an internal tool named `agent-browser`.',
'- `agent-browser` is the only allowed browser automation CLI. Do not use Playwright, browser DevTools, curl-only screenshot substitutes, or any other browser automation path.',
'- One sanctioned exception: the feature-demo capture runner at `/tmp/feature-demo/capture.mjs`, staged there by the parent. It is an `agent-browser` orchestrator — every browser action it performs goes through the `agent-browser` CLI — and running it via `node` with the environment variables the brief specifies is compliant browser work, not a disallowed automation path. Its outputs land where the brief says (typically `/tmp/feature-demo/work/`), not under `/tmp/capture-visual-proof/`. This exception covers exactly that staged runner and no other script.',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exception trusts only a parent-writable /tmp pathname. Any parent task can stage a different capture.mjs at that path and ask the proof runner to execute it, so this turns the intended narrow agent-browser allowance into arbitrary Node execution in the proof-runner context. Stage a trusted immutable copy in the runner's own runtime or verify the staged file against a trusted digest before invoking it.

Live-sandbox controls showed the sparse-frames failure is page-specific:
a WebGL game stalls headless frame production for the whole page while
plain pages on the same sandbox record at full wall-clock rate. SKILL.md
now names that blocker (webgl surface stalls headless recording) so
agents diagnose by target instead of burning retries; the stale-runtime
ffmpeg hint stays scoped to actual record-stop ffmpeg errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants