feat(video): switch 0.19.0 renderer Remotion → HyperFrames (HTML-native, Apache-2.0)#314
Open
rennf93 wants to merge 156 commits into
Open
feat(video): switch 0.19.0 renderer Remotion → HyperFrames (HTML-native, Apache-2.0)#314rennf93 wants to merge 156 commits into
rennf93 wants to merge 156 commits into
Conversation
…strings (controller cleanup)
…ry + folded prose fixes
|
Thanks for opening your first pull request on RoboCo! Quick checklist before review (most of these are enforced by CI, but worth a glance):
See CONTRIBUTING.md for the full workflow and the Code of Conduct for the community standards we follow. Welcome aboard — a maintainer will review shortly. |
…highlight
Final whole-branch review (Opus) triaged two FIX items from the SDD nits
ledger; the rest ship as-is.
- render.js: a synchronous throw from createRenderJob (post-mkdtemp, not
awaited) left an empty outDir on disk — the outer catch only reclaimed
extractDir. Reclaim outDir too when it exists, and correct the stale
comment that claimed the out dir was never created.
- {vertical,square}.html: the 4th highlights <li> lived in the DOM hidden
only by JS, so a no-JS / failed-script render would show an empty bullet.
Start it style="display:none" and reveal on populate, so an unscripted
render shows nothing instead.
Vitest smoke (release-announcement.test.js) 4/4 green; render.js syntax
checked. Python suite untouched by this fix (JS/HTML only).
T7 widened _build_app's db_session param to AsyncSession | None (to drop the
4x # type: ignore[arg-type] on the DB-independent _build_app(None, ...) calls)
but left the inner _override_db fixture typed AsyncIterator[AsyncSession] —
so 'yield db_session' yielded AsyncSession | None into a declared AsyncSession,
and mypy failed at test_video_routes.py:177 ('Incompatible types in yield').
The DB-independent media tests pass db_session=None deliberately: their route
uses a monkeypatched task service and never awaits the session, so yielding
None is safe at runtime. Type the override's yield as AsyncSession | None to
match — no cast, no # type: ignore, no assert, runtime behavior unchanged.
The 3 media tests (3 passed) and the 19 db-gated tests (skipped locally) hold.
reflow_md.py walks the filesystem via rglob('*.md') and skips tooling dirs
(.venv, .mypy_cache, .pytest_cache, ...) but not .superpowers/ — the
superpowers SDD workflow's scratch dir (briefs, reports, progress ledger,
all gitignored). A dev running SDD locally would hit a false markdown-prose
gate failure on those transient files. Add .superpowers to SKIP_DIRS,
consistent with the existing tooling-scratch exclusions.
compositionId flowed unvalidated from the POST body into path.join under extractDir/motion/compositions/, so a '../..'-style value could escape the composition dir (CodeQL: Uncontrolled data used in path expression). Validate at the trust boundary in server.js (/^[A-Za-z0-9_-]+$/) and add a path.resolve + startsWith containment check in render.js so it stays safe regardless of caller.
flow_server._build_headers and do_server._build_headers constructed only X-Agent-ID/Role/Correlation-ID, omitting X-Agent-Token and X-Agent-Team (unlike ApiClient._get_agent_headers used by the other MCP servers). Latent since the gateway refactor — surfaced when ROBOCO_AGENT_AUTH_REQUIRED=true was armed on the NAS, 401-ing every flow/do verb with 'Missing X-Agent-Token header'. Add both headers (mirroring ApiClient) so the HMAC gate passes. Tests assert the headers are now injected.
Mirrors ci_watch_enabled (migration 048): the global ROBOCO_VIDEO_ENGINE_ENABLED flag arms the subsystem; the new projects.video_engine_enabled column (migration 063) opts a repo into authoring against its motion/ dir. VideoEngine._opted_in_project no-ops open_video_task at the single chokepoint covering all three trigger paths (on-release, on-spotlight, CEO on-demand) until the operator flips it in the panel edit-project dialog. Existing projects stay opted out (server_default=false).
… sites The prior fix (6ed4e13) covered the flow/do MCP servers but missed four other agent->orchestrator call sites that built the header dict by hand and omitted X-Agent-Token and/or X-Agent-Team. With ROBOCO_AGENT_AUTH_REQUIRED armed on the NAS, every one 401s: - agent_sdk/server.py: the session-end post-mortem flush (/api/journals/me/entries), A2A persistence + offline fallback (/api/a2a/*), and the stopped-without-transition auto-substitute (/api/tasks/auto-substitute) — all sent only X-Agent-ID/Role, so each 401'd 'Missing X-Agent-Token'. Add a shared _agent_headers() helper (mirroring flow_server._build_headers) and route all four through it. - agent_sdk/secretary_driver.py: _headers() sent the token but not the team, so the HMAC gate 401'd with signature mismatch (secretary is board-team; token signed with team='board', verified with team=''). Add the team header. - mcp/git_readonly.py: the read-only git MCP sent only X-Agent-ID/Role — no token, no team — so /api/git/* 401'd once auth was armed. Convert the static _HEADERS to a _headers() helper with team + token. - runtime/orchestrator.py: the cell-PM auto-submit self-API call acted as a PM with a hand-built {X-Agent-ID, X-Agent-Role} dict — no token, no team — 401ing under auth-required. Add _agent_api_headers(uuid, role) mirroring _system_api_headers, and use it. Tests: _agent_headers round-trip (token + team, team-omitted when None), _agent_api_headers carries a signed PM token + team.
…,L27,L14,L28,H19,M13,M14,H20,M16,M45,L25,M15,L24)
…001), hoist AuditLogTable import (PLC0415)
…the GrokCliProvider
…reflects grok agents
…ep so live cost reflects Anthropic cache spend
…ud 429 parks instead of crash-respawning
…ens roll into usage/cost summaries
…r's revisit counter survives a restart
…rates after 2026-08-31
…ilently falling back to a zero-usage env id
…ken-sweep test signatures)
…ck stays reachable
… tokens are bounded
…ry so a stolen cookie's exp is fixed
…t revokes the current jti
…w + jti revocation
[phase9] Auth hardening: bound agent tokens (iat/exp) + JWT jti revocation
[scan-fix] Phase 8: LLM providers / usage / billing
[Phase 7] schemas/conventions/MegaTask/API fixes (15 findings, scan-fix program)
Phase 6 — Panel frontend hardening (rides 0.19.0)
Phase 5 — Background-engine hardening (rides 0.19.0)
Phase 4 — Git / workflow hardening (rides 0.19.0)
Phase 3 — Data-integrity fixes (rides 0.19.0)
Phase 2 — State-machine & gateway integrity (rides 0.19.0)
[Phase 1/scan] Auth & security exposure hardening (0.19.0)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Switches the 0.19.0 video renderer from Remotion to HyperFrames (HeyGen, Apache 2.0) before release.
Why: HyperFrames is the better fit for RoboCo's agent-authored video pipeline — LLMs write HTML more naturally than JSX (the whole point of an AI-agent workforce), HyperFrames renders seek-driven byte-identical MP4s, and Apache 2.0 protects the CLA's future commercial-dual-license option (Remotion's source-available license requires a paid Company License for for-profits >3 employees).
Scope is bounded because the render boundary is renderer-agnostic: the orchestrator only calls
renderer.render(source_dir, composition_id, input_props, orientation, render_key)over HTTP+tarball. Only the sidecar guts, themotion/composition format (TSX→HTML), the render-client rename, and docs/tests change. The render loop,propose_video, thevideo_draftmarker, the panel queue, CEO gates, the heartbeat mutex, and the X/TikTok posters stay byte-identical (Fork A —input_propspreserved).What changed (T1–T7)
render.jsrewritten to@hyperframes/producer(createRenderJob+executeRenderJob); dropped the Remotion bundle-cache machinery;ensure-browser.mjsverifies Chrome Headless Shell via@hyperframes/engine.motion/TSX → HTML. Newmotion/compositions/release-announcement/{vertical,square}.html+theme.css+props.js(readswindow.__PROPS__+window.__ORIENTATION__); vitest HTML-structure smoke test (4/4 green). Lint-generatedindex.htmlgitignored (sidecar renders<orientation>.htmldirectly).remotion_client.py→video_renderer_client.py;RemotionRenderer→VideoRenderer,NullRemotionRenderer→NullVideoRenderer,get_remotion_renderer→get_video_renderer; configremotion_base_url→video_renderer_base_url; envROBOCO_REMOTION_BASE_URL→ROBOCO_VIDEO_RENDERER_BASE_URL; orchestrator import + factory; test rename + monkeypatch paths.git mv remotion-renderer/ → video-renderer/;docker/remotion.Dockerfile→docker/video-renderer.Dockerfile(added system ffmpeg — HyperFrames shells out to ffmpeg on PATH, unlike@remotion/rendererwhich bundled it); all three compose files (.yaml+.yml+registry.yml) service/image/container/env renamed;release.yml,Makefile,.dockerignore/.gitignorepath comments.CLAUDE.mdvideo-engine paragraph rewritten for HyperFrames (+ m2/m3 prose fixes);CHANGELOG[0.19.0];docs/rag/architecture/{video-engine,minio-storage}.md;docs/map/video-engine.mdadded (+ complete-map, prompts-roles-taxonomy, deployment-tooling);motion/README.md;do_server.pydocstring.docs/internal/...§11.xspec refs fromtiktok_client.py+x_video_client.py(parenthetical preserved);minio_client.pyglobal+# noqa: PLW0603→ module-level dict cache (test-monkeypatch-compatible, notlru_cache);test_video_routes.py_build_appdb_session: AsyncSession | None+ dropped 4×# type: ignore[arg-type]. Net −6 suppressions (2 noqa + 4 type:ignore removed, none added).Verification
uv run ruff format . && uv run ruff check . && uv run mypy roboco/clean — zero newnoqa/type: ignore.git grep -niE 'remotion' -- roboco/ motion/ video-renderer/ docker/ docker-compose*.yml .github/ Makefile→ onlydocker/video-renderer.Dockerfile:7(brief-mandated historical contrast comment) +motion/README.md:35(easing-rationale prose). No live code/config refs.alembic heads→ single head062_tiktok_credentials(untouched — no migration in this PR).docker buildunverified locally (daemon unavailable in sandbox); CEO builds on the NAS.Known minor nits (pending final whole-branch review)
A consolidated nits list lives at
.superpowers/sdd/progress.md(gitignored scratch). None block merge — all Critical/Important findings across the 6 task reviews were fixed inline; remaining items are Optional-polish (T1 outDir edge-case leak, trailing newlines, T2 redundantdata-fps/non-standarddata-composition-duration, T2 4th-highlight<li>DOM-by-default) or Confirmed-non-defects (HyperFrames lint false-positives structurally unavoidable per Fork A). The final whole-branch review will triage which to fix before merge.License
AGPL-3.0 only. HyperFrames is Apache 2.0 (compatible). No MIT/license reintroduction anywhere.
Built via subagent-driven development (7 tasks, fresh implementer per task, task review after each — all approved). Branch
feature/video-engine-hyperframesfromcebbd73e.