hyperframes: add HTML/CSS/GSAP as a parallel composition runtime

Separates creative grammar (renderer_family) from technical engine
(render_runtime) so HyperFrames can stand alongside Remotion as a
first-class runtime instead of masquerading as a Remotion sub-case.
Locks runtime choice at proposal stage and enforces it end-to-end: the
schemas require it, video_compose routes by it, the reviewer fails
closed on silent swaps, and a parametrized contract test walks every
pipeline manifest to ensure each planning-stage skill explains the
conversation to the user. Adds hyperframes_compose (scaffold/lint/
validate/render/doctor/add_block), a playbook -> CSS style bridge, and
vendored HyperFrames Layer 3 skills from commit d291358, pinned via
PROVENANCE.md for future re-sync. Final_review now records
render_runtime_used and runtime_swap_detected so compose lies are
catchable after the fact.
This commit is contained in:
calesthio
2026-04-18 18:37:00 -07:00
parent 9e17263aa5
commit b4f7ec4eee
118 changed files with 10128 additions and 126 deletions
+156
View File
@@ -15,6 +15,43 @@ from typing import Any, Optional
from tools.base_tool import BaseTool, ToolStatus, ToolTier, ToolStability
# Unicode punctuation that breaks on Windows cp1252 stdout. Map each to an
# ASCII equivalent. This only touches strings rendered by registry helpers
# that an agent is likely to print to the user at preflight — not docstrings,
# comments, or markdown.
_UNICODE_DASH_REPLACEMENTS = {
"\u2014": "--", # em dash
"\u2013": "-", # en dash
"\u2212": "-", # minus sign
"\u2018": "'", # left single quote
"\u2019": "'", # right single quote
"\u201c": '"', # left double quote
"\u201d": '"', # right double quote
"\u2026": "...", # ellipsis
}
def _scrub_unicode_dashes(value: Any) -> Any:
"""Recursively normalize unicode punctuation in str leaves to ASCII.
Used to keep `provider_menu_summary()` output readable on Windows cp1252
stdout. Does NOT modify dict/list structure or non-string values.
"""
if isinstance(value, str):
out = value
for needle, repl in _UNICODE_DASH_REPLACEMENTS.items():
if needle in out:
out = out.replace(needle, repl)
return out
if isinstance(value, list):
return [_scrub_unicode_dashes(item) for item in value]
if isinstance(value, tuple):
return tuple(_scrub_unicode_dashes(item) for item in value)
if isinstance(value, dict):
return {k: _scrub_unicode_dashes(v) for k, v in value.items()}
return value
class ToolRegistry:
"""Central registry of all OpenMontage tools."""
@@ -258,6 +295,125 @@ class ToolRegistry:
return dict(sorted(menu.items()))
def provider_menu_summary(self) -> dict[str, Any]:
"""Compact, human-ready rollup of provider_menu() for onboarding/preflight.
Returns a dict shaped for the "N of M configured" capability menu the
agent is supposed to present to the user per AGENT_GUIDE.md → "Provider
Menu (Mandatory at Preflight)". Collapses the firehose of
support_envelope() into something the agent can paraphrase in plain
language in a few lines.
Example output (abbreviated):
{
"composition_runtimes": {
"ffmpeg": True,
"remotion": True,
"hyperframes": True,
},
"capabilities": [
{"capability": "video_generation", "configured": 10, "total": 16,
"available_providers": ["fal", "heygen", ...],
"unavailable_providers": ["openai", ...]},
...
],
"setup_offers": [
{"capability": "music_generation", "tool": "suno_music",
"install_instructions": "Add SUNO_API_KEY to .env"},
...
],
"runtime_warnings": [
"hyperframes: npm package `hyperframes` not resolvable: ...",
...
],
}
Agents should use this as the source for the preflight capability
menu rather than rendering `support_envelope()` or `provider_menu()`
raw. See AGENT_GUIDE.md > "Provider Menu (Mandatory at Preflight)".
"""
self.ensure_discovered()
menu = self.provider_menu()
# Composition runtimes — lift from video_compose.get_info() since
# they're the signal the runtime-selection contract depends on.
comp_runtimes: dict[str, bool] = {}
runtime_warnings: list[str] = []
vc = self._tools.get("video_compose")
if vc is not None:
info = vc.get_info()
engines = info.get("render_engines") or {}
comp_runtimes = {k: bool(v) for k, v in engines.items()}
# If hyperframes_compose is registered, surface its npm-resolve reasons
# explicitly — those are the "looks available but isn't" failures.
hf = self._tools.get("hyperframes_compose")
if hf is not None:
hf_info = hf.get_info()
rc = hf_info.get("hyperframes_runtime") or {}
for reason in rc.get("reasons") or []:
runtime_warnings.append(f"hyperframes: {reason}")
# Capabilities rollup (configured/total + provider lists).
# When a provider has multiple tools (e.g. seedance-fal and
# seedance-replicate both reporting provider="seedance"), a
# naive set-split shows the provider in BOTH available and
# unavailable — confusing for users. Dedupe: if the provider has
# any available tool, do NOT list it as unavailable.
capabilities: list[dict[str, Any]] = []
for cap, bucket in menu.items():
available_providers = {
e.get("provider") for e in bucket.get("available", [])
} - {None}
unavailable_providers = (
{e.get("provider") for e in bucket.get("unavailable", [])}
- {None}
- available_providers # provider with any available tool wins
)
capabilities.append(
{
"capability": cap,
"configured": bucket.get("configured", 0),
"total": bucket.get("total", 0),
"available_providers": sorted(available_providers),
"unavailable_providers": sorted(unavailable_providers),
}
)
# Setup offers — unavailable tools that would be 1-minute env-var fixes.
# Filter for short install instructions referencing an env var so the
# agent can lead with the easy wins.
setup_offers: list[dict[str, Any]] = []
for cap, bucket in menu.items():
for entry in bucket.get("unavailable", []):
hint = entry.get("install_instructions") or ""
# Heuristic: 1-minute fixes mention an env var or API key.
if any(k in hint.lower() for k in ["api key", "env", "_key=", "_api"]):
setup_offers.append(
{
"capability": cap,
"tool": entry.get("name"),
"provider": entry.get("provider"),
"install_instructions": hint,
}
)
result = {
"composition_runtimes": comp_runtimes,
"capabilities": capabilities,
"setup_offers": setup_offers,
"runtime_warnings": runtime_warnings,
}
# Normalize em-dashes and en-dashes to ASCII so preflight output prints
# cleanly on Windows cp1252 stdout (the default on Git Bash / PowerShell
# without PYTHONIOENCODING=utf-8). Agents paste this dict into chat; a
# mojibake `` in an install_instructions string looks like a bug.
# Markdown docs keep their typographic dashes; this only touches the
# runtime-reported strings.
return _scrub_unicode_dashes(result)
# Post-hoc fix: narrow helper that keeps the registry output stdout-safe on
# Windows cp1252 without imposing a new style rule on every tool author.
def gpu_required_tools(self) -> list[str]:
"""List tools that require GPU (VRAM > 0)."""
return [