Repository navigation
feat(cli): add --lang and auto-infer phonemizer locale from voice prefix - #351
Merged
Merged
Conversation
`hyperframes tts` was calling Kokoro's `model.create(text, voice=, speed=)` with no language argument, so Kokoro's default phonemizer (en-us) was applied regardless of the voice selected. Picking `ef_dora` or `jf_alpha` and feeding it Spanish or Japanese text produced English-phonemized output. Closes #349. - `manager.ts`: add `SUPPORTED_LANGS`, `inferLangFromVoiceId`, and `isSupportedLang`. Attach a `defaultLang` field to every bundled voice and expand the bundled list with `ef_dora`, `ff_siwis`, `jf_alpha`, `zf_xiaobei` so `--list` surfaces multilingual options. - `synthesize.ts`: accept optional `lang: SupportedLang` in `SynthesizeOptions`, forward it to the Python worker as `argv[7]`. The worker introspects `Kokoro.create`'s signature and only passes `lang=` when the installed kokoro-onnx version supports it. Returned metadata now includes `lang` and `langApplied` so callers can detect silent no-ops. Bump the cached script filename to `synth-v2.py` so existing installs pick up the new script automatically. - `commands/tts.ts`: add `--lang, -l` with validation against `SUPPORTED_LANGS`. Resolution order is explicit `--lang` > inferred from voice prefix > `en-us`. When explicit lang disagrees with the voice-implied lang (legitimate for stylized accents), emit a dim-level hint; suppress under `--json`. When kokoro-onnx silently ignores the kwarg, log that too. Update `--list` with a new "Lang code" column and add multilingual examples. - Tests: new `manager.test.ts` covering every supported prefix, the unknown-prefix fallback, case-insensitivity, `isSupportedLang` validation, and a regression guard that every bundled voice has a valid `defaultLang` matching its ID. - Docs: `docs/packages/cli.mdx` and `skills/hyperframes/references/tts.md` updated with the flag, examples, the espeak-ng dependency note for non-English phonemization, and the voice-prefix → lang table. Backward compatibility: - English voices (a*/b* prefixes) continue to phonemize as en-us / en-gb — no change. - Non-English voices now phonemize correctly by default (bug fix, not a regression). - Older kokoro-onnx versions that don't know the `lang` kwarg keep working via signature introspection; the CLI logs a dim note if `--lang` was requested but ignored. Verification: - `bun --cwd packages/cli test` — 128 tests pass (incl. 17 new). - `bunx oxlint` and `bunx oxfmt --check` clean on changed files. - `bun run build` succeeds. - `npx tsx packages/cli/src/cli.ts tts --help` / `--list` render cleanly; invalid `--lang` produces a clean error with the valid-codes list.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
miguel-heygen
approved these changes
Apr 20, 2026
Post-review cleanup on #351. Net -21 lines. - Drop `defaultLang` field + `makeVoice()` helper from VoiceInfo — compute via `inferLangFromVoiceId(v.id)` at read time in listVoices. The only reader was the --list table; caching the derived value on every voice added a self-consistency invariant we had to test. - Drop redundant `lang` field from SynthesizeResult — caller already knows the requested lang since it passed it in; only `langApplied` carries information the caller can't derive. - Use `errorBox` for --lang validation to match the house style in render.ts (other validation errors already use errorBox). - Reuse existing `langList` module constant in the validation error instead of re-joining SUPPORTED_LANGS. - Inline `DEFAULT_LANG` — used once in inferLangFromVoiceId. - Trim WHAT-restating comments and the duplicate prefix-enumeration JSDoc on inferLangFromVoiceId (VOICE_PREFIX_LANG already carries per-row comments). - Clean up orphaned `synth*.py` files in ~/.cache/hyperframes/tts when writing the current versioned script, so repeated upgrades don't leak files. - Drop the `EN-US` case-sensitive-rejection test assertion — the CLI lowercases input before validation, so accepting mixed case is a feature, not a bug. Tests: 16/16 in `manager.test.ts`, 127/127 full CLI suite pass. Lint + format + typecheck clean.
|
Tested locally end-to-end on this PR branch and can confirm the fix works as intended. Local validation performed
Result
From a practical E2E perspective, the fix behaves as expected and addresses the multilingual phonemization issue reported in #349. |
jrusso1020
added a commit
to jrusso1020/hermes-agent
that referenced
this pull request
Apr 27, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
kshitijk4poor
pushed a commit
to NousResearch/hermes-agent
that referenced
this pull request
May 4, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the #333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
bot-ted
added a commit
to bot-ted/hermes-agent
that referenced
this pull request
May 6, 2026
* feat(kanban-dashboard): per-platform home-channel notification toggles (#19864) * revert: auto-subscribe gateway chat on tool-driven kanban_create (#19718) Reverts ff3d2773e2. Teknium reviewed the merged PR and decided this behavior isn't wanted — tool-driven kanban_create should not mirror the slash-command path's auto-subscribe. Orchestrators that want their originating chat notified can call kanban_notify-subscribe explicitly; we're not going to make it implicit. * feat(kanban-dashboard): per-platform home-channel notification toggles Adds a "Notify home channels" section to the task drawer in the kanban dashboard plugin. Each platform where the user has set a home channel (/sethome, TELEGRAM_HOME_CHANNEL env var, gateway.platforms.<p>.home_channel in config.yaml) gets a toggle pill. Toggling on writes a kanban_notify_subs row keyed to that platform's home (chat_id + thread_id); toggling off removes it. The existing gateway notifier watcher delivers completed / blocked / gave_up events without any new plumbing — this is purely a GUI surface over existing machinery. Replaces the reverted auto-subscribe behavior from #19718 with an explicit, per-task, per-platform, user-controlled opt-in. No implicit subscription on tool-driven kanban_create; no CLI commands; no slash commands. Just a toggle in the drawer. Backend (plugins/kanban/dashboard/plugin_api.py): - GET /api/plugins/kanban/home-channels[?task_id=X] Returns every platform with a configured home, plus a per-entry subscribed: bool relative to task_id (false when task_id omitted). Reads the live GatewayConfig via load_gateway_config() so env-var overlays stay honored. - POST /api/plugins/kanban/tasks/:id/home-subscribe/:platform Idempotent add_notify_sub keyed to the platform's home. - DELETE /api/plugins/kanban/tasks/:id/home-subscribe/:platform remove_notify_sub for the same tuple. - 404 when the platform has no home configured, or task_id doesn't exist (POST only). Frontend (plugins/kanban/dashboard/dist/index.js): - TaskDrawer fetches /home-channels on open, keyed on task_id. - HomeSubsSection renders nothing when zero platforms have a home (so users who haven't set one up don't see an empty UI block). - Optimistic toggle with busy flag + revert-on-failure. One pill per platform; ✓ prefix and --on class indicate the subscribed state. CSS (plugins/kanban/dashboard/dist/style.css): - .hermes-kanban-home-subs flex row + .hermes-kanban-home-sub pill style + --on subscribed variant (subtle ring-colored background). Live-tested against a dashboard with TELEGRAM + DISCORD_BOT_TOKEN / HOME_CHANNEL env vars set: drawer shows both pills, toggling each flips its visual state AND writes/removes the correct kanban_notify_subs row (verified via direct DB read). Tests (tests/plugins/test_kanban_dashboard_plugin.py, 11 new, 53/53 pass total): - home-channels lists only platforms with a home (slack with a token but no home is excluded) - no task_id -> all subscribed=false - subscribe creates notify_sub row with correct chat/thread/platform - subscribed=true reflected in subsequent GET - idempotent re-subscribe - unknown platform -> 404 - unknown task -> 404 - unsubscribe removes the row - telegram + discord subscribe/unsubscribe independent - zero homes -> empty list * chore(release): AUTHOR_MAP entries for Tier 1g salvage batch * fix(gateway): use git HEAD SHA, not file mtimes, for stale-code check (#19740) The stale-code self-check (Issue #17648) used sentinel-file mtimes to decide whether the gateway survived a `hermes update` with stale `sys.modules`. That signal false-positives on any write to the sentinel files — including agent-driven edits during Hermes-on-Hermes dev sessions. Telling the agent to patch `run_agent.py` would flip the check to True on the next user message and force a gateway restart even though no update happened. Switch the signal to `git rev-parse HEAD`. Agent file edits don't move HEAD; `hermes update` (git pull) always does. Reading .git/HEAD directly (no subprocess) with a 5s cache keeps the overhead negligible on bursty chats. Non-git installs short-circuit to False — the stale-modules class can't occur without a git-backed update path, so there's nothing to detect. The legacy `_compute_repo_mtime` helper is kept but unused by detection, reserved as a fallback hook for future pip-install update paths. - _read_git_head_sha(): resolves HEAD across main checkout, worktree (follows `gitdir:` + `commondir` pointers), and packed-refs layouts. - _current_git_sha_cached(): per-runner 5s SHA cache. - _detect_stale_code(): boot SHA vs current SHA, returns False when either is unavailable. - Tests cover all four layouts, the agent-edits-don't-trigger regression, and cache behavior. Refs #17648. * fix(gateway): preserve WSL interop PATH in systemd units * fix(doctor): check gh auth status when GITHUB_TOKEN absent hermes doctor showed 'No GITHUB_TOKEN (60 req/hr)' warning even when users had authenticated via gh auth login. Now falls back to gh auth status --json authenticated when GITHUB_TOKEN and GH_TOKEN are both unset. Fixes #16115 * fix(update): sync bundled skills to all profiles, including active (#16176) `hermes update` iterated only non-active profiles when seeding bundled skills. `seed_profile_skills()` uses a subprocess with an explicit HERMES_HOME so it correctly targets any profile path; the `p.name != active` filter was the only thing preventing the active profile from being included, leaving it silently on stale skill content after every update. Drop the filter and update the header line from "other profiles" to "all profiles". The active profile is now seeded on the same path as every other profile. The earlier `sync_skills()` call (module-level HERMES_HOME) remains for backward compatibility; the subprocess-based loop is reliable regardless of which HERMES_HOME the CLI was invoked with. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(email): drop non-allowlisted senders before dispatch to prevent mail loops Add EMAIL_ALLOWED_USERS check in EmailAdapter._dispatch_message() to silently discard emails from senders not in the allowlist. This prevents the adapter from creating thread context and dispatching a MessageEvent for unauthorized senders, which could race with the gateway authorization check and result in SMTP replies being sent despite the handler returning None. Test: tests/gateway/test_email.py::TestDispatchMessage::test_non_allowlisted_sender_dropped Test: tests/gateway/test_email.py::TestDispatchMessage::test_allowlisted_sender_proceeds Test: tests/gateway/test_email.py::TestDispatchMessage::test_empty_allowlist_allows_all * fix(cron): expand \${VAR} refs in config.yaml during job execution (#15890) The cron scheduler's run_job() loaded config.yaml with yaml.safe_load() but never called _expand_env_vars(), so ${HERMES_MODEL} and similar references in model:, fallback_providers:, and other config.yaml fields were forwarded to the LLM API as literal strings, causing HTTP 400 errors. The normal CLI path has always called _expand_env_vars() via load_config(), so this was a cron-only gap. The .env load at the top of run_job() already populates os.environ before config.yaml is read, so the expansion sees the correct values. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(tts): update MiniMax API endpoint to v1/text_to_speech MiniMax deprecated the old v1/t2a_v2 endpoint (api.minimax.io) and moved to v1/text_to_speech (api.minimax.chat). The new API: - Uses a flat payload: {model, text, voice_id} instead of nested voice_setting / audio_setting objects - Returns raw audio bytes (Content-Type: audio/mpeg) instead of JSON with hex-encoded audio - Uses model 'speech-01' instead of 'speech-2.8-hd' - Updated default voice_id to 'female-shaonv' for Chinese TTS The implementation detects Content-Type to handle both old and new API responses, maintaining backward compatibility for any users who manually configured the legacy base_url. * fix(cron): add concurrency regression test for parallel job state writes get_due_jobs() called load_jobs() and save_jobs() without holding _jobs_file_lock, creating a race with the locked mark_job_run() and advance_next_run(). Wrap get_due_jobs() with the lock (delegating to a new _get_due_jobs_locked() inner function) so all load→modify→save cycles are serialised. Add two regression tests: one verifying 3 concurrent mark_job_run() calls each land their correct last_status and last_run_at without overwrites, and a stress test confirming 10 parallel calls each increment their job's completed count to exactly 1. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * test(discord): annotate make_attachment content_type as Optional[str] Copilot review: the helper accepted None in one test but was annotated str. Matches actual usage where no-content-type attachments are a tested scenario. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(file-ops): allow file search in hidden roots * fix(run_agent): acquire lock in IterationBudget.used property The `used` property was reading `self._used` without holding the lock, while `consume()`, `refund()`, and `remaining` all properly acquire `self._lock` before accessing `_used`. This means a concurrent call to `used` during `consume()` or `refund()` could observe a partially- updated value, leading to incorrect iteration budget metrics reported to the gateway, or in extreme cases a ValueError from CPython's list implementation when the internal array resizes during iteration. Fix: acquire the lock in `used` just like `remaining` does. Co-Authored-By: Claude Opus 4.7 <[email protected]> * fix(file_ops): resolve search_files path/line collision for hyphenated numeric filenames * fix(models): strip :cloud/-cloud suffix from models.dev Ollama Cloud IDs models.dev appends :cloud and -cloud suffixes to Ollama Cloud model IDs (e.g. kimi-k2.6:cloud, qwen3-coder:480b-cloud) that the live Ollama Cloud API does not use. Without normalisation, these suffixed IDs bypass the dedup check and appear alongside the correct clean IDs, causing 400/404 errors when users select them in /model or hermes model. Add _strip_ollama_cloud_suffix() and apply it to mdev entries before the dedup merge in fetch_ollama_cloud_models() so all model IDs stored in the disk cache use the canonical form the API accepts. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * docs(cron): lead with agent-driven setup for no-agent mode (#19871) The shipped no-agent docs introduced the feature via CLI first and mentioned the chat path as a two-line afterthought. That buries the actual value prop: the cronjob tool exposes no_agent directly to the agent, so a user can describe a watchdog in plain language and Hermes wires up the script + schedule + delivery without anyone opening an editor. Changes: * cron-script-only.md: promote 'Create One from Chat' above 'Create One from the CLI', flesh it out with a worked transcript (the actual tool calls the agent makes), add subsections covering 'what the agent decides for you' (when to pick no_agent=True vs LLM mode) and 'managing watchdogs from chat' (pause/resume/edit/ remove all agent-accessible). * user-guide/features/cron.md: - Add 'no-agent mode' to the top-level feature list with a cross- link, plus a sentence up top making it clear everything is agent-accessible through the cronjob tool. - Add 'The agent sets these up for you' subsection to the no-agent section showing the exact tool call shape. * automate-with-cron.md: tighten the existing tip box to mention the agent-driven path, not just CLI scheduling. No behavior change — docs only. * fix(skills/email/himalaya): document v1.2.0 folder.aliases syntax The bundled himalaya skill documented folder aliases using a stale TOML schema (`[accounts.NAME.folder.alias]`, singular) that himalaya v1.2.0 silently ignores. The TOML parses without error, but the alias resolver never reads the sub-section — every lookup then falls through to the canonical folder name. Source: in `pimalaya/core` (the `email-lib` crate himalaya v1.2.0 depends on, currently v0.27.0), `email/src/folder/config.rs` defines `FolderConfig { aliases: Option<HashMap<String, String>>, ... }` (plural, no `#[serde(rename)]`/`alias` aliases, no `deny_unknown_fields`), and `account/config/mod.rs::get_folder_alias` returns the input verbatim when no alias is found. So the singular `alias` key deserializes to nothing and lookups silently fall through. On Gmail (where `sent` resolves to `[Gmail]/Sent Mail`, not `Sent`) this means save-to-Sent fails *after* SMTP delivery already succeeded, and `himalaya message send` exits non-zero. Any caller (agent, script, user) that retries on that exit code will re-run the entire send — including SMTP — producing duplicate emails to recipients. Silent ignore + caller-level retry is significantly worse than a config that just doesn't work. This commit updates SKILL.md and references/configuration.md to the v1.2.0 `folder.aliases.X` syntax (plural, dotted keys, directly under the account section), adds a Gmail-specific block with the `[Gmail]/Sent Mail`-style mapping, and adds notes on the failure mode so future readers don't hit the same trap. SKILL.md version bumped 1.0.0 → 1.1.0. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(cli): fix shortcut config conflict in hermes_cli * fix(tui_gateway): guard sys.path against local package shadowing (#15989) When the TUI backend (tui_gateway/entry.py) is spawned by Node.js with the user's CWD containing a local utils/ directory, that directory shadows the installed utils module, causing ImportError in run_agent and hermes_cli. Strip '' and '.' from sys.path and prepend HERMES_PYTHON_SRC_ROOT (already set by hermes_cli before spawning the subprocess) so installed packages always win over CWD artifacts. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(google-workspace): restore required_credential_files in SKILL.md (#16452) PR #9931 ("feat(google-workspace): add --from flag for custom sender display name") accidentally removed the required_credential_files frontmatter block that tells hermes to bind-mount google_token.json and google_client_secret.json into Docker and Modal remote terminals before running setup.py. Without this header the credential files are never registered in the session-scoped ContextVar, so get_credential_file_mounts() returns an empty list at container creation time and the OAuth files are invisible inside the sandbox. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(docs): register cron-script-only guide in sidebar (#19893) PR #19709 added website/docs/guides/cron-script-only.md but never added the entry to website/sidebars.ts, which is explicitly enumerated (not autogenerated). Two consequences: 1. The guide didn't show up in the left-nav "Guides & Tutorials" list — users could only reach it via cross-links from other pages. 2. Landing on the guide page directly made the sidebar disappear entirely (Docusaurus treats unregistered docs as orphaned and renders them without their parent sidebar). Added 'guides/cron-script-only' next to 'guides/automate-with-cron' so it slots in alongside the other cron content. Verified with `npm run build`: no orphan warnings, no broken links, page builds with sidebar intact. No content change, docs only. * feat(skill): add hyperframes optional creative skill Adds an optional creative skill that integrates HyperFrames, an HTML-based video rendering framework, as a sibling to manim-video. Complements manim's math-focused animation with motion-graphics, captioned narration, audio-reactive visuals, shader transitions, and website-to-video production. Scope: - optional-skills/creative/hyperframes/SKILL.md — entry point - references/composition.md — data-attr schema, timeline contract - references/cli.md — every npx hyperframes command - references/gsap.md — GSAP core API for compositions - references/website-to-video.md — 7-step capture-to-video workflow - references/troubleshooting.md — OpenClaw / Chromium 147 fix - scripts/setup.sh — idempotent one-time setup OpenClaw / Chromium 147 fix (hyperframes#294): Pinning hyperframes@>=0.4.2 (commit 4c72ba4 ships the HeadlessExperimental.beginFrame auto-detect + screenshot fallback). setup.sh pre-caches chrome-headless-shell so the fast BeginFrame path is preferred over system Chrome. The PRODUCER_FORCE_SCREENSHOT=true escape hatch is documented in troubleshooting.md and in SKILL.md Pitfalls. Placed under optional-skills/ (not bundled) per CONTRIBUTING.md guidance for heavyweight deps: requires Node.js >= 22, FFmpeg, and ~300 MB chrome-headless-shell download. * docs(skill): sync hyperframes skill with upstream changes Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the #333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover. * docs(skill): add hyperframes inspect command to cli.md + SKILL.md - references/cli.md: add Inspect step (5/7) to Workflow + dedicated `## inspect` section between validate and preview, covering --json/--samples/--at flags and the legacy `hyperframes layout` alias - SKILL.md: rename procedure step 7 to "Lint, validate, inspect, preview, render" with the full pipeline; explain inspect as the layout-side companion to validate (catches overflow / off-frame / occluded text issues that static lint can't see) - SKILL.md verification: lint + validate + inspect as a single combined pass - SKILL.md References list: include `inspect` in the cli.md command list Brings the optional skill in sync with hyperframes-oss main as of 2026-05-03 — `inspect` was added in heygen-com/hyperframes#480 (2026-04-25) and is documented as a real workflow step in skills/hyperframes-cli/SKILL.md. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(cli): guard c-S-c key binding with try/except to prevent startup crash (#19895) PR #19884 added @kb.add('c-S-c') unconditionally. prompt_toolkit raises ValueError("Invalid key: c-S-c") during HermesCLI.__init__ on platforms where this key spec is not recognised — the process exits before reaching the prompt loop. Reported on macOS (#19894) and Linux (#19896) immediately after #19884 landed. Fix: wrap the registration in try/except ValueError so that startup continues cleanly on any platform/version that rejects the spec. Where the spec is accepted the binding is registered normally as a no-op, allowing the terminal to handle Ctrl+Shift+C natively as before. Fixes #19894 Fixes #19896 * feat(kanban-dashboard): sharper home-channel toggle contrast, drop → running action (#19916) Follow-up polish to the kanban dashboard from #19864 and #19705. **Home-channel toggle contrast.** The `.hermes-kanban-home-sub--on` class previously used `color-mix(var(--color-ring) 14%, transparent)` which was nearly invisible on both the default teal and NERV themes — the on/off distinction relied almost entirely on the ✓ prefix glyph. Bump to 32% fill + full-opacity ring border + inner ring shadow + font-weight 600. Still theme-scoped (no hardcoded colors), but reads at a glance on both tested themes. **Drop the → running status action.** Since #19705, `PATCH /tasks/:id` rejects `status=running` with HTTP 400 — only the dispatcher's `claim_task` path legitimately enters that state (so the run row, claim lock, and worker PID are created atomically). The UI button was still present and produced a 400 on click, which is a confusing dead affordance. Remove it from `StatusActions`; add a comment pointing to #19535 so future editors know why it's missing. Live-tested on the default Hermes Teal theme. 53/53 kanban dashboard plugin tests still pass. * refactor(cli): drop dead c-S-c key binding (follow-up to #19895) (#19919) #19884 added a prompt_toolkit key binding for Ctrl+Shift+C to "prevent Hermes from intercepting the keystroke as an interrupt signal." #19895 then wrapped the binding in try/except after discovering it crashed startup with ValueError on every platform. Both PRs were based on a misreading of how terminal key events propagate: 1. Terminal emulators (GNOME Terminal, iTerm2, kitty, Windows Terminal, etc.) intercept Ctrl+Shift+C before the keystroke reaches the application's stdin. prompt_toolkit never sees it. The binding could never have intercepted anything. 2. prompt_toolkit's key spec parser doesn't recognise 'c-S-c' on any platform — the Shift modifier is meaningless on control-sequence keys. Verified: every prompt_toolkit version raises 'Invalid key: c-S-c' at registration time. The handler is dead code. Delete it and leave a comment explaining why no binding is needed here. Ctrl+Q alias (#19884's other addition) stays — that's a real prompt_toolkit key and a legitimate interrupt shortcut. Verified the CLI starts cleanly — key binding phase no longer raises and the subsequent chat flow reaches the provider setup check without error. * fix(local): recover when persistent_shell cwd is deleted (#17558) When a tool call deletes its own working directory (`cd /tmp/foo && rm -rf /tmp/foo`), the next `subprocess.Popen(args, cwd=self.cwd)` raised `FileNotFoundError: [Errno 2]` before bash even started — every subsequent terminal/file-tool call hit the same wedge until the gateway restarted. Fix in `LocalEnvironment._run_bash`: before handing `self.cwd` to Popen, resolve a safe alternative when the path is gone (walk up to the nearest existing ancestor, falling back to `tempfile.gettempdir()` only as a last resort). Log a warning so the recovery is visible — not silent — and update `self.cwd` so the next call doesn't repeat the message. Defense in depth in `LocalEnvironment._update_cwd`: only adopt the new cwd when it still exists as a directory. `pwd -P` from a deleted cwd can leave a stale value in the marker file; refusing to store a missing path keeps `self.cwd` valid by construction. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(local): test root as ancestor candidate; use real pipe for fake stdout Address Copilot review on PR #17569: 1. _resolve_safe_cwd never tested the filesystem root because the loop exited when `os.path.dirname(parent) == parent`, which is true once `parent == '/'`. Restructure so the root is checked before the self-equal exit. Adds `test_returns_root_when_only_root_exists` — regression-guarded by reverting the loop and watching it fail. 2. The fake `Popen.stdout` was a `MagicMock`; `BaseEnvironment._wait_for_process` calls `proc.stdout.fileno()` then `select.select`/`os.read` against it, which raised `TypeError: fileno() returned a non-integer` (visible as a thread exception in test output) and could in theory read from an unrelated real fd. Hand `fake_popen` a real `os.pipe()` with the write end pre-closed so the drain loop sees EOF immediately. Helper records each fd so the test cleans up after itself. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(terminal): guard background process spawn against deleted cwd (#19933) Follow-up to #19928 which fixed the foreground path in _run_bash. The background process spawn in process_registry.py had the same vulnerability: Popen(cwd=session.cwd) and PtyProcess.spawn(cwd=...) would raise FileNotFoundError if the directory was deleted. Apply _resolve_safe_cwd() at session creation time so both the PTY and pipe-mode Popen paths receive a validated cwd. * fix(tui): respect voice.record_key config (supersedes #19028, #19339) (#19835) * fix(tui): respect voice.record_key config instead of hardcoded Ctrl+B Classic CLI loaded ``voice.record_key`` from config.yaml and bound the prompt-toolkit handler dynamically (``cli.py`` paths). The new TUI hard- coded ``Ctrl+B`` everywhere — ``isVoiceToggleKey`` (input handler), ``/voice status`` ("Record key: Ctrl+B"), and ``/voice on`` ("Ctrl+B to start/stop recording"). A user who set ``voice.record_key: ctrl+o`` (or any other key) saw the documented config silently ignored — only Ctrl+B worked, the displayed shortcut lied about it. Wire the configured key end to end through the existing channels: * **Backend** (``tui_gateway/server.py``): ``voice.toggle`` action=status AND action=on/off responses now include ``record_key``, sourced from ``config.get('voice', {}).get('record_key', 'ctrl+b')``. * **Backend types** (``ui-tui/src/gatewayTypes.ts``): ``ConfigFullResponse`` now exposes ``config.voice.record_key`` and ``VoiceToggleResponse`` carries ``record_key`` so the TUI can both bind and display it. * **Frontend parser/formatter** (``ui-tui/src/lib/platform.ts``): ``parseVoiceRecordKey()`` accepts ``ctrl+b`` / ``alt+r`` / ``cmd+space`` and the common aliases (``option``, ``cmd``, ``win``, …); falls back to the documented Ctrl+B for empty / multi-character / malformed input so a typo never silently disables the shortcut. ``formatVoiceRecordKey()`` renders for status text. ``isVoiceToggleKey`` now takes a parsed ``ParsedVoiceRecordKey`` argument; the hardcoded ``ch === 'b'`` is gone. Default arg keeps existing call sites back-compat. * **Hydration** (``ui-tui/src/app/useConfigSync.ts``, ``useMainApp.ts``): startup ``config.get full`` already runs; extract ``cfg.voice.record_key`` from it, parse, push into a new ``voiceRecordKey`` state, and forward to the input handler ctx (``InputHandlerContext.voice.recordKey``). Mtime-poll path also re-applies the parsed key so a hand-edit of config.yaml takes effect the next tick — matches existing behaviour for display options. * **Input handler** (``ui-tui/src/app/useInputHandlers.ts``): ``isVoiceToggleKey(key, ch, voice.recordKey)`` so the configured binding fires. * **Slash command** (``ui-tui/src/app/slash/commands/session.ts``): ``/voice status`` and ``/voice on`` use ``formatVoiceRecordKey`` on the response's ``record_key`` instead of the hardcoded label. Tests: * ``parseVoiceRecordKey`` covers ctrl/alt/cmd/super aliases, multi-char rejection, and empty fallback. * ``formatVoiceRecordKey`` covers the doc examples (``Ctrl+B``, ``Ctrl+O``, ``Alt+R``, ``Cmd+B``). * ``isVoiceToggleKey`` regression: ``ctrl+o`` configured → only ``o`` matches, not ``b``; ``alt+r`` matches both alt-bit and meta-bit encodings (terminal protocol parity); omitted-arg call still binds Ctrl+B for back-compat. Full TUI suite (555 tests) passes; ``tsc --noEmit`` clean. Fixes #18994 Co-authored-by: asheriif <[email protected]> * fix(tui): support named-key tokens in voice.record_key (space, enter, …) Reviewer caught that the round-1 parser in #18994 rejected every multi-character token, so a config value like ``ctrl+space`` (which the CLI happily binds via prompt_toolkit's ``c-space`` rewrite in ``cli.py``) silently fell back to the documented Ctrl+B default — re-introducing the same false-shortcut bug the PR was meant to fix, just at a different surface. Add explicit named-key support that mirrors what the CLI accepts: * ``space`` (alias: ``spc``) → matches ``ch === ' '`` * ``enter`` (alias: ``return``, ``ret``) → matches ``key.return`` * ``tab`` → matches ``key.tab`` * ``escape`` (alias: ``esc``) → matches ``key.escape`` * ``backspace`` (alias: ``bs``) → matches ``key.backspace`` * ``delete`` (alias: ``del``) → matches ``key.delete`` ``ParsedVoiceRecordKey`` gains an optional ``named`` field; ``ch`` holds either a single char (back-compat) or the canonical named token, and the runtime matcher dispatches on ``named`` before checking the modifier shape. Aliases collapse to one canonical name so ``ctrl+esc`` and ``ctrl+escape`` behave identically. Unrecognised multi-character tokens (e.g. ``ctrl+spcae`` typo, or unsupported keys like ``ctrl+f5``) still fall back to the Ctrl+B default rather than silently disabling the binding — keeps the "typo never silently kills the shortcut" guarantee. Tests: * ``parseVoiceRecordKey`` parametrised over every named token + each alias variant. * New ``isVoiceToggleKey`` cases for space (ch-based match), enter (``key.return``), tab, escape, backspace, delete, including modifier-mismatch negatives. * ``formatVoiceRecordKey`` renders named keys in title case (``Ctrl+Space``, ``Ctrl+Enter``). * Existing fall-back-to-Ctrl+B contract preserved for empty input AND unrecognised multi-char tokens. Full TUI suite: 559/559 pass; ``tsc --noEmit`` clean. Refs #18994 (round-1 review feedback) Co-authored-by: asheriif <[email protected]> * test(tui): assert voice.toggle returns configured record_key Salvage the backend regression from #19339 — asserts ``voice.toggle`` action=on AND action=status responses carry the configured ``voice.record_key`` end-to-end through ``_load_cfg()``. Keeps the CLI→TUI parity contract visible in the Python test suite alongside the existing frontend parser/matcher/formatter coverage from #19028. * fix(tui): address Copilot review on #19835 voice.record_key wiring Five tightenings on the parser + matcher + hydration surface, all caught by the Copilot review on the PR — each one turns a silent false-fire or display/binding skew into a deterministic behaviour. * **isVoiceToggleKey ctrl branch was too permissive for named keys.** The doc-default macOS Cmd+B muscle-memory fallback (``isActionMod(key)`` on top of ``key.ctrl``) fired for every configured key, so bare Esc — which hermes-ink reports with ``key.meta`` on some macOS terminals — triggered ``ctrl+escape``, and Alt+Space / Alt+Tab triggered ``ctrl+space`` / ``ctrl+tab``. Gate the fallback to the literal ``ctrl+b`` binding so any custom chord requires the real Ctrl bit. * **Alt branch guarded against Ctrl/Cmd co-press.** Without this, Ctrl+Alt+<letter> and Cmd+Alt+<letter> also fired ``alt+<letter>``. * **Dropped the ``meta`` modifier variant and its alias.** In hermes-ink ``key.meta`` is Alt on xterm-style terminals and Cmd on legacy macOS ones, so a literal ``meta+b`` config displayed as ``Cmd+B`` while matching Alt+B — exactly the kind of false shortcut the PR was meant to remove. ``cmd`` / ``command`` now collapse onto ``super`` (kitty-style ``key.super``, with a macOS ``key.meta`` fallback) and render as ``Cmd+B``. Unknown modifier tokens fall back to the documented Ctrl+B default rather than silently coercing to Ctrl. * **Slash-command display/binding skew.** ``/voice status`` and ``/voice on`` rendered from the fresh gateway ``record_key`` response, but ``useInputHandlers()`` still bound the old key until the next 5s mtime poll. Thread ``setVoiceRecordKey`` through ``SlashHandlerContext.voice`` and push the parsed spec into frontend state on every response so text and binding stay consistent. * **Test coverage for the two paths Copilot flagged.** Added vitest coverage for (a) the three-case ``/voice`` slash output in ``createSlashHandler.test.ts`` and (b) the ``applyDisplay → voice.record_key`` hydration + omit-setter back-compat paths in ``useConfigSync.test.ts``. Plus regression cases for every false-fire scenario above. Suite: 575/575 green, tsc --noEmit clean. * fix(tui): address Copilot round-2 review on #19835 Three tightenings on the surface introduced in the round-1 fix: * **``/voice tts`` reset custom bindings to Ctrl+B.** The ``tts`` branch of ``voice.toggle`` omitted ``record_key`` from its response, so the frontend's ``r.record_key ?? 'ctrl+b'`` coerced a user's custom binding back to the default on every TTS toggle. Two-sided fix: the backend now includes ``record_key`` on the ``tts`` branch (parity with ``status``/``on``/``off``), and the slash handler only pushes frontend state when the response actually carries ``record_key`` — belt-and-suspenders against any future branch forgetting to include it. * **``super+b`` / ``win+b`` / ``cmd+b`` displayed "Cmd+B" on Linux and Windows.** ``formatVoiceRecordKey`` rendered ``mod === 'super'`` as ``Cmd`` universally, which told non-mac users the wrong modifier to press even though ``isVoiceToggleKey`` matched the right event bits. Gate the label to ``isMac`` so non-mac renders ``Super+B``. * **``control+b`` / ``ctrl + b`` lost the macOS Cmd+B fallback.** ``_isDefaultVoiceKey`` keyed off ``parsed.raw`` — so semantically-equal aliases of the documented default dropped into the strict branch even though they bind Ctrl+B. Compare on the parsed spec (mod + ch + named) instead. Coverage added: Linux ``Super+B`` rendering (and macOS ``Cmd+B``), ``control+b`` / ``ctrl + b`` accepting the Cmd+B fallback on darwin, ``/voice tts`` without ``record_key`` not clobbering cached binding, and a backend regression asserting every ``voice.toggle`` branch carries the configured key. Suite: 579/579 TUI vitest green, 2/2 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-3 review on #19835 Three classes of robustness issue caught on the second pass — all revolve around malformed YAML tipping ``parseVoiceRecordKey`` or ``_voice_record_key`` into a crash instead of the documented fallback. * **Parser crashed on non-string YAML scalars.** ``config.get full`` returns raw ``yaml.safe_load`` output, so ``voice.record_key: 1`` or ``voice.record_key: true`` in a hand-edited config would hit ``.trim()`` on a number/bool and throw, breaking startup and every mtime re-apply. Accept ``unknown`` at the signature, guard with ``typeof raw !== 'string'``, and fall back to the default. * **Backend blew up on non-dict ``voice:``.** Same YAML hazard on the gateway side: ``voice: true`` / ``voice: cmd+b`` left ``_load_cfg().get("voice")`` as a bool/str, so ``.get("record_key")`` raised AttributeError and took every ``voice.toggle`` branch down with it. Centralised the lookup in a single ``_voice_record_key()`` helper that ``isinstance``-guards both ``voice`` and ``record_key`` and falls back to ``ctrl+b``. * **Multi-modifier chords silently dropped extras.** The previous validator only checked the first modifier token, so ``ctrl+alt+r`` silently parsed as ``ctrl+r`` and ``cmd+ctrl+b`` as ``super+b`` — a typo bound a different shortcut than the user configured. Reject multi-modifier spellings outright; the classic CLI only supports single-modifier bindings via prompt_toolkit's ``c-x`` / ``a-x`` rewrite, so this matches CLI parity. Coverage added: * ``parseVoiceRecordKey`` fallback on ``1`` / ``true`` / ``null`` / ``undefined`` / ``{}``. * ``parseVoiceRecordKey`` fallback on ``ctrl+alt+r`` / ``cmd+ctrl+b`` / ``alt+ctrl+space``. * ``test_voice_toggle_handles_non_dict_voice_cfg`` exercises every non-dict ``voice:`` shape (bool, str, None, int, list) and asserts each falls back to ``record_key: 'ctrl+b'``. Suite: 581/581 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-4 review on #19835 Four final corners of the voice.record_key surface: * **Bare-char configs silently coerced to ``ctrl+<key>``.** A config like ``voice.record_key: o`` / ``space`` / ``escape`` fell through to the default ``mod = 'ctrl'`` and silently bound Ctrl+O, while the classic CLI's prompt_toolkit would bind the raw key (no rewrite) — so the two runtimes silently disagreed on what "o" means. Require an explicit modifier; bare-char configs fall back to the documented Ctrl+B default. * **Reserved ctrl+<letter> bindings would never fire.** ``useInputHandlers()`` intercepts ``ctrl+c`` (interrupt), ``ctrl+d`` (quit), and ``ctrl+l`` (clear screen) before the voice check runs, so those configs would be advertised in /voice status but the advertised shortcut never actually triggers push-to-talk. Added ``_RESERVED_CTRL_CHARS`` at parse time so the user gets the documented default instead of a dead shortcut. (``alt+c``, ``cmd+l``, etc. are not intercepted and stay usable.) * **``_load_cfg()`` root itself may be a non-dict.** ``_voice_record_key()`` isinstance-guarded the ``voice`` subkey but not the root — a malformed config.yaml that collapsed to a scalar/list at the top level (``config.yaml: true`` or ``[]``) would still raise on ``.get("voice")``. Added the top-level guard too so every malformed shape falls back to ``ctrl+b``. * **Stale header comment on ``isVoiceToggleKey``.** The doc-comment still claimed "On macOS we additionally accept the platform action modifier (Cmd) for the configured letter" even though the implementation gates the Cmd fallback to the documented default only. Rewrote to match. Coverage added: * ``parseVoiceRecordKey`` fallback on bare chars (``o``, ``b``, ``space``, ``escape``). * ``parseVoiceRecordKey`` fallback on ``ctrl+c`` / ``ctrl+d`` / ``ctrl+l``; positive case for ``alt+c`` / ``cmd+l`` still usable. * Backend ``test_voice_toggle_handles_non_dict_voice_cfg`` now exercises 5 non-dict shapes at the YAML root too. Suite: 583/583 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-5 review on #19835 Three follow-ups on the voice matcher's modifier + shift discipline: * **``super`` branch falsely fired on Alt+<key> / bare Esc on macOS.** ``isVoiceToggleKey`` accepted ``isMac && key.meta`` as a Cmd fallback for the ``super`` modifier — but hermes-ink sets ``key.meta`` for plain Alt/Option AND for bare Escape on some macOS terminals. A ``cmd+b`` config silently fired on Alt+B; ``cmd+space`` on Alt+Space; ``cmd+escape`` on bare Esc. Drop the fallback and require the literal ``key.super`` bit. Legacy- terminal users who need Cmd should upgrade to a kitty-protocol terminal or bind ``alt+X`` explicitly. * **Shift bit was never checked.** The parser rejects multi- modifier configs like ``ctrl+shift+tab``, but the runtime matcher didn't check ``key.shift`` — so ``ctrl+tab`` also fired on Ctrl+Shift+Tab and ``alt+enter`` on Alt+Shift+Enter. Early-return on ``key.shift === true`` so the runtime only fires the exact chord the user configured. * **Test leaked ``HERMES_VOICE=1`` into later tests.** ``voice.toggle`` action=on writes to ``os.environ`` directly (CLI parity, runtime-only flag); ``test_voice_toggle_returns_ configured_record_key`` dispatched action=on without letting monkeypatch take ownership of the var first. Any later test that read voice mode in the same Python process could inherit a stale enabled state. Added ``monkeypatch.setenv("HERMES_VOICE", "0")`` up front so monkeypatch restores the original value at teardown. Coverage added: * ``cmd+b`` / ``cmd+space`` / ``cmd+escape`` do NOT fire on ``key.meta``-only events on darwin. * ``ctrl+tab`` / ``alt+enter`` / ``ctrl+o`` reject matches when ``key.shift`` is held; sanity cases without Shift still fire. Suite: 585/585 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot round-6 review on #19835 Three classes of modifier-discipline tightening + one config-surface honesty fix: * **Default ``ctrl+b`` Cmd fallback leaked Alt+B.** The default's macOS Cmd+B muscle-memory path used ``isActionMod(key)``, which returns ``key.meta || key.super`` on darwin. hermes-ink also reports plain Alt as ``key.meta``, so Alt+B silently fired the default binding. Replaced with strict ``isMac && key.super === true`` — kitty-style Cmd+B still works, Alt+B correctly rejected. Legacy-terminal mac users (Terminal.app without CSI-u) now get raw Ctrl+B only; the documented default still works everywhere. * **ctrl / super branches accepted extra modifier bits.** The parser rejects multi-modifier configs like ``ctrl+alt+o``, but the runtime matcher was permissive — ``ctrl+o`` fired on Ctrl+Alt+O / Ctrl+Cmd+O, and ``super+b`` fired on Cmd+Alt+B / Ctrl+Cmd+B. Added strict ``!key.alt && !key.meta && key.super !== true`` on ctrl, and ``!key.ctrl && !key.alt && !key.meta`` on super, so the runtime only fires the exact chord the parser would let you configure. * **Dropped ``cmd`` / ``command`` aliases.** They parsed to ``super`` and rendered as ``Cmd+X``, but legacy macOS terminals report Cmd as ``key.meta`` (same signal as Alt), so a ``cmd+o`` config was advertised as working but never actually fired on Terminal.app-without-CSI-u. That recreated the "displayed shortcut does not work" problem this PR was meant to remove. Users who want the platform action modifier spell it ``super`` / ``win`` — that matches the unambiguous ``key.super`` bit, and kitty-style macOS terminals render it as ``Cmd+X`` via platform-aware formatter. Coverage updated: * Default ctrl+b no longer fires on Alt+B via ``key.meta`` leak; raw Ctrl+B and kitty-style Cmd+B still fire. * ``ctrl+o`` rejects Ctrl+Alt+O / Ctrl+Cmd+O / Ctrl+Meta+O chords. * ``super+b`` rejects Cmd+Alt+B / Cmd+Meta+B / Ctrl+Cmd+B chords. * ``cmd+b`` / ``command+b`` / ``meta+b`` all fall back to the documented default at parse time (joined the ambiguous-mac-mod rejection class). * Round-2 expectations that asserted ``cmd+b`` parsed as super and accepted ``key.meta`` on darwin updated to reflect the new stricter contract. Suite: 588/588 TUI vitest green, 3/3 backend voice tests green, tsc --noEmit clean. * fix(tui): address Copilot follow-up on wire typing + escape precedence Two follow-ups from the latest Copilot pass: * **Config wire typing honesty (`gatewayTypes.ts`)** `config.get full` forwards raw `yaml.safe_load()` output, so `voice.record_key` can be any scalar/container when hand-edited. Typing it as `string` suggests a normalized contract that the backend does not guarantee and makes unsafe callers more likely. Change `ConfigVoiceConfig.record_key` to `unknown` with an explicit comment that callers must normalize at runtime. * **Escape-based voice bindings were swallowed before voice check** `useInputHandlers()` handled `key.escape` for queue-edit cancel and selection clear before `isVoiceToggleKey(...)`, so configured `ctrl+escape` / `alt+escape` / `super+escape` chords were advertised but never toggled recording in those UI states. Add an early escape+voice check before generic Esc handlers so escape-based voice bindings win when configured, while plain Esc behavior remains unchanged. Also updated PR #19835 description text to remove stale cmd/command alias claims and match the current parser contract. * fix(tui): pass configured voice shortcut through TextInput layer Thread the live parsed voiceRecordKey into TextInput so configured voice.record_key chords bubble to useInputHandlers instead of being consumed as editor input. This removes the last hardcoded Ctrl+B pass-through in the composer path while preserving existing global control chord behavior. * fix(tui): require explicit alt bit for escape-based alt chords Hermes-ink reports bare Escape as meta=true+escape=true on some terminals, so a configured alt+escape binding was firing on bare Esc. Require an explicit key.alt bit when the configured named key is escape so plain Esc stays plain Esc; kitty-style alt+escape still fires. * fix(tui): harden voice.record + TextInput paste + super-mod reserved list Three round-7 Copilot follow-ups on #19835: - voice.record start handler used _load_cfg().get('voice', {}).get(...) without shape checks, so malformed YAML (bool/scalar/list) returned 5025 instead of using VAD defaults. Centralized _voice_cfg_dict() helper and type-guarded silence_threshold/silence_duration with numeric fallbacks. - TextInput pass-through check moved above paste/copy handling so configured voice chords (ctrl+v / alt+v / cmd+v) beat the composer's paste/copy defaults. - parser now also rejects super+{c,d,l,v} — on macOS those are copy/exit/clear/paste and would be advertised in /voice status but never actually toggle recording. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <[email protected]> * fix(tui): round-8 Copilot review — allow ctrl+x, gate super reservations to macOS, preserve voice key on transient RPC failure Three round-8 Copilot follow-ups on #19835: - Revert ctrl+x addition to _RESERVED_CTRL_CHARS (landed via Copilot Autofix commit 731ec86): ctrl+x is only claimed during queue-edit (queueEditIdx !== null), so voice works the rest of the session and matches CLI ctrl+<letter> parity. - Gate super+{c,d,l,v} reservation to isMac. Linux/Windows TUI globals key off Ctrl, so kitty/CSI-u super+<letter> configs don't collide on non-mac and should stay usable. - applyDisplay() now skips setVoiceRecordKey when cfg is null so one transient quietRpc() failure after a config edit doesn't clobber the cached binding back to Ctrl+B until the next successful poll. New coverage: - parseVoiceRecordKey preserves ctrl+x on linux - super+{c,d,l,v} rejected on darwin, allowed on linux - applyDisplay(null, ...) leaves voiceRecordKey untouched * fix(cli,tui): normalize voice.record_key aliases across CLI + TUI for parity Round-9 Copilot review on #19835: TUI accepted control+/option+/opt+/super+/win+ aliases but the classic CLI only rewrote literal ctrl+/alt+ before handing to prompt_toolkit, so a TUI-valid config silently bound a different (or no) shortcut in the CLI. - Added normalize_voice_record_key_for_prompt_toolkit() in hermes_cli/voice.py with a single alias table (ctrl/control/alt/option/opt → c-/a-). - Wired it into all three cli.py sites (_enable_voice_mode hint, _show_voice_status display, and the prompt_toolkit binding in _register_voice_handler). - /voice status display now renders control+x as Ctrl+X and option+x as Alt+X (canonical casing) to match TUI formatVoiceRecordKey. - super/win/windows are intentionally left unchanged: prompt_toolkit has no super modifier, so the CLI will reject them loudly at startup rather than silently binding Ctrl+B. Documented this split at both the TUI _MOD_ALIASES comment and the CLI normalizer docstring. - Added tests covering ctrl/control/alt/option/opt mapping, case-insensitivity, non-string fallback, empty-string fallback, and super/win pass-through. * fix(cli): port TUI parser contract into CLI voice.record_key normalizer Round-10 Copilot review on #19835. hermes_cli/voice.py's normalize_voice_record_key_for_prompt_toolkit() previously did blind substring replacement with no trim/validate step, so the CLI diverged from the TUI parser on: - whitespace ('ctrl + b' -> 'c- b' instead of 'c-b') - typoed named keys ('ctrl+spcae' passed through as 'c-spcae' and prompt_toolkit would reject at startup) - bare-char configs ('o' should fall back, not pass through as 'o') - multi-modifier chords ('ctrl+alt+r') - reserved ctrl chars ('ctrl+c/d/l') - unknown modifiers ('meta+b' / 'shift+b') - named-key aliases ('return'/'esc'/'bs'/'del' not collapsed to prompt_toolkit canonicals) Port the TUI parser contract into Python (_VOICE_MOD_ALIASES, _VOICE_NAMED_KEYS, _VOICE_RESERVED_CTRL_CHARS) so one config value binds the same shortcut in both runtimes. Also added format_voice_record_key_for_status() shared between the PTT hint and /voice status display. Non-string scalars (voice.record_key: true / 1) now surface as 'Ctrl+B' instead of the raw scalar — /voice status no longer advertises a shortcut that can never bind. Tests: 29/29 in test_voice_wrapper.py, including 11 new regressions covering whitespace, named-key aliases, typos, bare-char, multi-modifier, reserved ctrl, unknown mods, non-string fallback, and formatter contract. * fix(cli): shape-safe voice config read + graceful super/win fallback Round-11 Copilot review on #19835. Two remaining cross-runtime gaps: 1. load_config().get('voice', {}) still assumed voice was a dict, so a hand-edited voice: true / voice: cmd+b at the top level raised AttributeError before the voice UI could start. Added voice_record_key_from_config(cfg) to hermes_cli/voice.py that isinstance-guards both the root and the voice subkey. All three cli.py read sites (_enable_voice_mode hint, _show_voice_status, PTT binding) now use it. 2. The CLI normalizer previously passed super+/win+/windows+ through unrewritten so prompt_toolkit would reject them loudly at startup — but that crash was a worse UX than a silent fallback. Normalizer now returns c-b for those spellings, and the PTT binding site logs a warning so users see why their TUI-only shortcut isn't binding in the CLI. Coverage: 34/34 in tests/hermes_cli/test_voice_wrapper.py (5 new cases for voice_record_key_from_config + malformed-root + malformed-voice + extractor/normalizer composition). * fix(cli): self-audit cleanup — remaining voice-config shape safety + doc drift Self-review of the voice.record_key change set turned up four remaining items Copilot would very likely flag next round: 1. cli.py _voice_start_continuous still read load_config().get('voice', {}).get('silence_threshold') without an isinstance guard, so a hand-edited voice: true / voice: cmd+b (non-dict) raised AttributeError on VAD recording start. Shape-safe coerce the voice dict and numeric-guard silence_threshold/silence_duration. 2. cli.py _enable_voice_mode's auto_tts check had the same bug — fixed with the same isinstance guard. 3. hermes_cli/voice.py module comment on _VOICE_MOD_ALIASES still said super/win/windows 'pass through unchanged and prompt_toolkit's add() call loudly rejects them at startup'. Round 11 changed the normalizer to silently fall back to c-b with a warning at the binding site; updated the comment to match. 4. ui-tui/src/lib/platform.ts header comment had the same stale 'CLI will loudly reject them at startup' claim; updated to 'falls back to the documented default and logs a warning'. No behavior change on the code paths already covered by test_voice_wrapper.py; the two cli.py fixes are defensive against malformed YAML that previous rounds already hardened in tui_gateway/server.py but missed in the classic CLI. * fix(cli,tui): round-12 Copilot review — alt-collide on mac, bool-in-int guards, voice UI hardcodes, mtime-reload test Five round-12 Copilot review items on #19835: 1. platform.ts: hermes-ink reports Alt as key.meta on many terminals; isActionMod on darwin accepts key.meta as the action modifier. So alt+c/d/l get claimed by isCopyShortcut / isAction('d')/'l') before the voice check. Reject those configs at parse time on macOS only (non-mac keeps them usable). 2. cli.py: four remaining hardcoded 'Ctrl+B' sites in voice-facing UI (_get_voice_status_fragments status bar, _voice_start_recording hints, _get_placeholder composer text) were still lying about non-default configs. Added self._voice_record_key_label() shared helper and wired it into all three sites. 3. server.py + cli.py: bool is a subclass of int, so isinstance(silence_threshold, (int, float)) accepted True/False from malformed YAML and forwarded 1/0 to the VAD engine. Exclude bool explicitly so boolean typos fall back to the documented 200 / 3.0 defaults. 4. useConfigSync.ts: extracted the config.get-full fetch+apply body into a shared hydrateFullConfig() helper. Both the initial hydration and mtime-reload paths now use it, so the polling/RPC wiring is exercised by direct unit tests (4 new cases: fresh apply, reapply on new value, transient RPC failure preserves cache, back-compat without voice setter). 5. Added alt+{c,d,l} rejection regressions on darwin + allow on linux, and bool-leak regressions for both silence_threshold and silence_duration in tests/test_tui_gateway_server.py. Suite: 602/602 TUI vitest, 38/38 backend voice tests, typecheck + lints clean. * fix(cli): cache voice record-key label at binding time + status-bar coverage Round-13 Copilot review on #19835. _voice_record_key_label() was reading live config on every render, which caused two problems: 1. prompt_toolkit registers the push-to-talk binding once at session start (@kb.add(_voice_key)); the binding does NOT re-read config. Editing voice.record_key mid-session would switch the status-bar / placeholder / recording-hint label to the new shortcut while the actual keybinding stayed on the startup chord — reintroducing the display/binding drift this whole PR is fighting. 2. Hot render path: during recording the UI is invalidated every 150ms, so re-loading + deep-merging config on every call added avoidable UI overhead. Fix: cache the label at the same site that registers the prompt_toolkit binding via new set_voice_record_key_cache(raw_key). _voice_record_key_label() now just returns the cached value (falls back to 'Ctrl+B' before startup). Status/placeholder/hint are always in sync with the live binding; no config reload per render. Also added 4 regression cases to tests/cli/test_cli_status_bar.py: configured ctrl+<letter> renders in both wide and compact status bars, configured named key (ctrl+space) renders in the recording hint, pre-startup absent cache falls back to Ctrl+B, and malformed configs (bool True) fall through the formatter to Ctrl+B. Suite: 60/60 test_cli_status_bar + test_voice_wrapper, typecheck + lints clean. * fix(cli): route /voice on + /voice status through startup-pinned label; mac alt+cdl parity Round-14 Copilot review on #19835. All three comments legit: 1. _enable_voice_mode still formatted label from live load_config() — mid-session config edit would make /voice on announce the new shortcut while the prompt_toolkit binding stayed the startup chord. Use self._voice_record_key_label() (cached at binding time, round-13) so /voice on cannot drift from the live binding. 2. _show_voice_status had the same bug — /voice status reported live config instead of the pinned startup binding. Fixed the same way. 3. CLI normalizer accepted alt+c/alt+d/alt+l even though the TUI parser rejects them on macOS (Copilot round-12 — hermes-ink reports Alt as key.meta, isActionMod on darwin accepts it, collides with isCopyShortcut / isAction). Added _VOICE_RESERVED_ALT_CHARS_MAC = {c,d,l} gated to sys.platform == 'darwin' so a shared config like option+c falls back to c-b on both runtimes on macOS; non-mac still binds a-c. Coverage: 4 new tests in test_voice_wrapper.py covering mac alt+cdl rejection, linux alt+cdl allowed, option/opt alias forms, and mac-specific exclusions for other alt letters. 62/62 in voice wrapper + status bar suites. --------- Co-authored-by: Tranquil-Flow <[email protected]> Co-authored-by: asheriif <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> * fix(gateway): handle planned service stops * fix(tui): complete absolute paths as paths * docs: clarify that the Docker terminal backend is a single persistent container (#20003) The docs were ambiguous about whether the Docker terminal backend spins up a fresh container per command or reuses a long-lived one. It's the latter — Hermes starts one container on first use and routes every terminal, file, and execute_code call through docker exec into that same container for the life of the process (across /new, /reset, and delegate_task subagents). Working-directory changes, installed packages, and files in /workspace persist from one tool call to the next, like a local shell. - configuration.md: lead the Docker Backend section with the persistence model before the YAML example; sharpen the Backend Overview table row. - features/tools.md: expand the Docker Backend block (previously just a 2-line YAML stub) with a clear statement of the persistent-container semantics and a pointer to the full lifecycle section. - docker.md: tighten the 'Docker as a terminal backend' bullet and the 'Skills and credential files' paragraph to call out the single-container model explicitly. * test(teams): mock ClientOptions in adapter tests * test(kanban): patch dashboard websocket token stub * chore(security): add OSV-Scanner CI + Dependabot for github-actions only (#20037) Adds two supply-chain controls that complement our existing pinning strategy (full-SHA action pins, exact-version source dep pins via uv.lock / package-lock.json) without undermining it. .github/workflows/osv-scanner.yml Detection-only scan of uv.lock and the ui-tui/website package-locks against the OSV vulnerability database. Runs on PRs that touch lockfiles, on push to main, and weekly against main so CVEs published after merge still surface. Uses Google's officially- recommended reusable workflow pinned by full SHA (v2.3.5). Findings upload to the Security tab; fail-on-vuln is disabled so pre-existing vulns in pinned deps do not block merges — we move pins deliberately, not under CI pressure. .github/dependabot.yml Scoped to github-actions only. Action pins must be moved when upstream publishes patches (often themselves security fixes); Dependabot opens a PR with the new SHA + release notes for normal review. Source-dependency ecosystems (pip, npm) are deliberately NOT enabled — automatic version-bump PRs against uv.lock / package-lock.json would fight our pinning strategy. CVE-driven security updates for source deps are enabled separately via the repo's Dependabot security updates setting (GitHub UI), which fires only when a pinned version becomes known-vulnerable. * docs(teams): add Teams to sidebar Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * docs: add Microsoft Teams to platform lists across docs Update all platform enumeration lists to include Teams: index.md, quickstart.md, integrations/index.md, sessions.md, slash-commands.md, updating.md, hooks.md, hermes-agent skill. Skipped PII redaction docs — Teams uses AAD object IDs, not phone numbers, so redaction doesn't apply there. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * docs(teams): add Teams to messaging/index.md - Add to platform description and intro paragraph - Add row to platform comparison table (images + typing) - Add node to architecture mermaid diagram - Add TEAMS_ALLOWED_USERS to security examples - Add to platform-specific toolsets table - Add to Next Steps links Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(teams): implement threading via app.reply() Wire reply_to into send() using App.reply(conv_id, msg_id, content) which constructs the threaded conversation ID internally. Threads supported in channels and group chats. Update comparison table: Threads ✅ Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(teams): fall back to flat send when threading returns 400 Group chats return 400 for threaded sends. Catch the error and fall back to a flat send so messages always get delivered. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(tests): patch TypingActivityInput after mock on Python <3.12 The SDK requires Python >=3.12 so CI (3.11) falls to the except ImportError branch, leaving TypingActivityInput=None. After loading the adapter module, explicitly restore it from the mock so test_send_typing doesn't silently no-op. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(teams): log reply() fallback for diagnostics The previous bare except swallowed every exception from app.reply() silently. Log at debug so real failures (auth, chat gone) leave a trace while keeping the group-chat 400 fallback working. Also fix the Teams entry's indentation in the messaging flowchart. * fix(nix): refresh stale tui npmDepsHash + fix cache-blind detection (#20144) The fix-lockfiles script used 'nix build .#tui.npmDeps' to detect stale hashes. This always succeeds when the OLD derivation is cached in Cachix or cache.nixos.org — even when the source package-lock.json has changed. Fix: use prefetch-npm-deps to compute the hash directly from the lockfile and compare against what's in the nix file. Falls back to nix build only if prefetch-npm-deps fails. * fix(tui): improve clipboard copy fallbacks * chore(release): map [email protected] → @bjianhang * fix(gateway): preserve pending update prompts across restarts * fix(setup): offer Keep/Replace/Clear when API key already exists hermes setup / hermes model used to silently skip the key prompt when any value was present in .env — even a malformed paste — leaving users with a stuck '✓' and no way to recover without hand-editing .env. Replace the silent acknowledgement at all three API-key provider flows (Kimi, Stepfun, generic) with a single [K]eep / [R]eplace / [C]lear menu via a shared `_prompt_api_key` helper. - K / Enter / Ctrl-C / unknown input → keep (never destroys the key) - R → getpass for new key; empty input cancels and preserves existing - C → clears the env var, tells user to rerun hermes setup, aborts flow LM Studio's no-auth-placeholder substitution stays on first-time entry only; on Replace an empty input means 'cancel', not 'overwrite with dummy key'. 11 unit tests cover all branches incl. garbage-input-keeps-key, Ctrl-C at the choice prompt, Replace-cancel preserving the old key, Clear wiping only the target env var, and lmstudio placeholder semantics. Fixes #16394 Reshapes #18355 — original PR pasted the menu inline at 3 sites with no tests; this consolidates to one helper (+88/-66) with coverage. Co-authored-by: Feranmi10 <[email protected]> * fix(kanban): dispatcher skips ready tasks whose assignee is not a real profile The kanban dispatcher's `_default_spawn` invokes ``hermes -p <task.assignee> chat -q ...``. When ``assignee`` names a control-plane lane (e.g. an interactive Claude Code terminal like ``orion-cc`` / ``orion-research``) instead of a real Hermes profile, the subprocess fails on startup with "Profile 'X' does not exist", gets reaped as a zombie, the TTL/crash detector marks the task back to ``ready``, and the next tick re-spawns the same crashing worker. Result: a permanent crash loop emitting ``spawned=2 crashed=2 every tick`` in the gateway log and burning CPU forever. Reproduce on a fresh Hermes-agent install: # 1. Create a kanban task whose assignee names a non-profile. hermes kanban create --assignee orion-cc --status ready \ --title "Review PR #N" --body "..." # 2. Start the gateway with the embedded dispatcher. hermes gateway run # gateway.log lines every minute: # kanban dispatcher: tick spawned=1 reclaimed=0 crashed=1 ... # 3. ps -ef | grep '[h]ermes.*defunct' shows zombies. Fix --- ``dispatch_once()`` now pre-checks ``hermes_cli.profiles. profile_exists(assignee)`` before claiming. If False, the row is added to ``skipped_unassigned`` (it's effectively "unassigned-to-an-executable-profile") and the dispatcher moves on without claiming, spawning, or counting a crash. The check is opt-in safe: if the import fails (e.g. test isolation, profile module restructured), ``profile_exists`` falls back to ``None`` and the original behaviour is preserved unchanged. This addresses the explicit hint in the kanban task body (``t_2bab06e3``): "Should ready-state tasks auto-spawn at all, or only on explicit orion-cc claim? If spurious, gate the auto-spawn behind a config flag (e.g. only assignee=hermes or assignee=auto)." Profile-existence is a tighter gate than a config flag — it …
nickdlkk
pushed a commit
to nickdlkk/hermes-agent
that referenced
this pull request
May 11, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
jsboige
pushed a commit
to jsboige/hermes-agent
that referenced
this pull request
May 14, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
dannyJ848
pushed a commit
to dannyJ848/hermes-agent
that referenced
this pull request
May 17, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
the-grove-ai
pushed a commit
to the-grove-ai/hermes-autonomaton-refactor
that referenced
this pull request
Jun 2, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the #333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
Seven74AI
pushed a commit
to Seven74AI/hermes-agent
that referenced
this pull request
Jun 13, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
Zollicoff
pushed a commit
to Zollicoff/hyperframes
that referenced
this pull request
Jul 1, 2026
…fix (heygen-com#351) * feat(cli): add --lang and auto-infer phonemizer locale from voice prefix `hyperframes tts` was calling Kokoro's `model.create(text, voice=, speed=)` with no language argument, so Kokoro's default phonemizer (en-us) was applied regardless of the voice selected. Picking `ef_dora` or `jf_alpha` and feeding it Spanish or Japanese text produced English-phonemized output. Closes heygen-com#349. - `manager.ts`: add `SUPPORTED_LANGS`, `inferLangFromVoiceId`, and `isSupportedLang`. Attach a `defaultLang` field to every bundled voice and expand the bundled list with `ef_dora`, `ff_siwis`, `jf_alpha`, `zf_xiaobei` so `--list` surfaces multilingual options. - `synthesize.ts`: accept optional `lang: SupportedLang` in `SynthesizeOptions`, forward it to the Python worker as `argv[7]`. The worker introspects `Kokoro.create`'s signature and only passes `lang=` when the installed kokoro-onnx version supports it. Returned metadata now includes `lang` and `langApplied` so callers can detect silent no-ops. Bump the cached script filename to `synth-v2.py` so existing installs pick up the new script automatically. - `commands/tts.ts`: add `--lang, -l` with validation against `SUPPORTED_LANGS`. Resolution order is explicit `--lang` > inferred from voice prefix > `en-us`. When explicit lang disagrees with the voice-implied lang (legitimate for stylized accents), emit a dim-level hint; suppress under `--json`. When kokoro-onnx silently ignores the kwarg, log that too. Update `--list` with a new "Lang code" column and add multilingual examples. - Tests: new `manager.test.ts` covering every supported prefix, the unknown-prefix fallback, case-insensitivity, `isSupportedLang` validation, and a regression guard that every bundled voice has a valid `defaultLang` matching its ID. - Docs: `docs/packages/cli.mdx` and `skills/hyperframes/references/tts.md` updated with the flag, examples, the espeak-ng dependency note for non-English phonemization, and the voice-prefix → lang table. Backward compatibility: - English voices (a*/b* prefixes) continue to phonemize as en-us / en-gb — no change. - Non-English voices now phonemize correctly by default (bug fix, not a regression). - Older kokoro-onnx versions that don't know the `lang` kwarg keep working via signature introspection; the CLI logs a dim note if `--lang` was requested but ignored. Verification: - `bun --cwd packages/cli test` — 128 tests pass (incl. 17 new). - `bunx oxlint` and `bunx oxfmt --check` clean on changed files. - `bun run build` succeeds. - `npx tsx packages/cli/src/cli.ts tts --help` / `--list` render cleanly; invalid `--lang` produces a clean error with the valid-codes list. * refactor(cli): simplify tts --lang implementation Post-review cleanup on heygen-com#351. Net -21 lines. - Drop `defaultLang` field + `makeVoice()` helper from VoiceInfo — compute via `inferLangFromVoiceId(v.id)` at read time in listVoices. The only reader was the --list table; caching the derived value on every voice added a self-consistency invariant we had to test. - Drop redundant `lang` field from SynthesizeResult — caller already knows the requested lang since it passed it in; only `langApplied` carries information the caller can't derive. - Use `errorBox` for --lang validation to match the house style in render.ts (other validation errors already use errorBox). - Reuse existing `langList` module constant in the validation error instead of re-joining SUPPORTED_LANGS. - Inline `DEFAULT_LANG` — used once in inferLangFromVoiceId. - Trim WHAT-restating comments and the duplicate prefix-enumeration JSDoc on inferLangFromVoiceId (VOICE_PREFIX_LANG already carries per-row comments). - Clean up orphaned `synth*.py` files in ~/.cache/hyperframes/tts when writing the current versioned script, so repeated upgrades don't leak files. - Drop the `EN-US` case-sensitive-rejection test assertion — the CLI lowercases input before validation, so accepting mixed case is a feature, not a bug. Tests: 16/16 in `manager.test.ts`, 127/127 full CLI suite pass. Lint + format + typecheck clean.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
dahans-msft2
pushed a commit
to dahans-msft2/hyperframes
that referenced
this pull request
Aug 6, 2026
…fix (heygen-com#351) * feat(cli): add --lang and auto-infer phonemizer locale from voice prefix `hyperframes tts` was calling Kokoro's `model.create(text, voice=, speed=)` with no language argument, so Kokoro's default phonemizer (en-us) was applied regardless of the voice selected. Picking `ef_dora` or `jf_alpha` and feeding it Spanish or Japanese text produced English-phonemized output. Closes heygen-com#349. - `manager.ts`: add `SUPPORTED_LANGS`, `inferLangFromVoiceId`, and `isSupportedLang`. Attach a `defaultLang` field to every bundled voice and expand the bundled list with `ef_dora`, `ff_siwis`, `jf_alpha`, `zf_xiaobei` so `--list` surfaces multilingual options. - `synthesize.ts`: accept optional `lang: SupportedLang` in `SynthesizeOptions`, forward it to the Python worker as `argv[7]`. The worker introspects `Kokoro.create`'s signature and only passes `lang=` when the installed kokoro-onnx version supports it. Returned metadata now includes `lang` and `langApplied` so callers can detect silent no-ops. Bump the cached script filename to `synth-v2.py` so existing installs pick up the new script automatically. - `commands/tts.ts`: add `--lang, -l` with validation against `SUPPORTED_LANGS`. Resolution order is explicit `--lang` > inferred from voice prefix > `en-us`. When explicit lang disagrees with the voice-implied lang (legitimate for stylized accents), emit a dim-level hint; suppress under `--json`. When kokoro-onnx silently ignores the kwarg, log that too. Update `--list` with a new "Lang code" column and add multilingual examples. - Tests: new `manager.test.ts` covering every supported prefix, the unknown-prefix fallback, case-insensitivity, `isSupportedLang` validation, and a regression guard that every bundled voice has a valid `defaultLang` matching its ID. - Docs: `docs/packages/cli.mdx` and `skills/hyperframes/references/tts.md` updated with the flag, examples, the espeak-ng dependency note for non-English phonemization, and the voice-prefix → lang table. Backward compatibility: - English voices (a*/b* prefixes) continue to phonemize as en-us / en-gb — no change. - Non-English voices now phonemize correctly by default (bug fix, not a regression). - Older kokoro-onnx versions that don't know the `lang` kwarg keep working via signature introspection; the CLI logs a dim note if `--lang` was requested but ignored. Verification: - `bun --cwd packages/cli test` — 128 tests pass (incl. 17 new). - `bunx oxlint` and `bunx oxfmt --check` clean on changed files. - `bun run build` succeeds. - `npx tsx packages/cli/src/cli.ts tts --help` / `--list` render cleanly; invalid `--lang` produces a clean error with the valid-codes list. * refactor(cli): simplify tts --lang implementation Post-review cleanup on heygen-com#351. Net -21 lines. - Drop `defaultLang` field + `makeVoice()` helper from VoiceInfo — compute via `inferLangFromVoiceId(v.id)` at read time in listVoices. The only reader was the --list table; caching the derived value on every voice added a self-consistency invariant we had to test. - Drop redundant `lang` field from SynthesizeResult — caller already knows the requested lang since it passed it in; only `langApplied` carries information the caller can't derive. - Use `errorBox` for --lang validation to match the house style in render.ts (other validation errors already use errorBox). - Reuse existing `langList` module constant in the validation error instead of re-joining SUPPORTED_LANGS. - Inline `DEFAULT_LANG` — used once in inferLangFromVoiceId. - Trim WHAT-restating comments and the duplicate prefix-enumeration JSDoc on inferLangFromVoiceId (VOICE_PREFIX_LANG already carries per-row comments). - Clean up orphaned `synth*.py` files in ~/.cache/hyperframes/tts when writing the current versioned script, so repeated upgrades don't leak files. - Drop the `EN-US` case-sensitive-rejection test assertion — the CLI lowercases input before validation, so accepting mixed case is a feature, not a bug. Tests: 16/16 in `manager.test.ts`, 127/127 full CLI suite pass. Lint + format + typecheck clean.
melon-xf
added a commit
to melon-xf/hermes-agent
that referenced
this pull request
Sep 3, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
fsca8
pushed a commit
to fsca8/hermes-agent
that referenced
this pull request
Oct 6, 2026
Pulls the hyperframes skill up to the latest state of heygen-com/hyperframes skill content. Opened 2026-04-17; upstream has shipped CLI, layout, and path changes since. - SKILL.md: promote the visual-style check to a proper HARD-GATE (DESIGN.md > named style > ask 3 questions, with the NousResearch#333/#3b82f6/Roboto tells); expand Step 6 to cover audio-reactive (mandatory per-frame tl.call() sampling loop — a single long tween does NOT react to audio), caption exit guarantee (hard tl.set kill after group.end), marker highlighting, and scene transitions; add the animation-map script to Verification; link the new features.md. - references/cli.md: add capture and validate (both shipped commands, both referenced from the workflow but missing from the reference). Add --lang to tts with the voice-prefix auto-inference table and espeak-ng dependency note (heygen-com/hyperframes#351, 2026-04-20 — after this PR opened). - references/website-to-video.md: update all paths to the capture/ subfolder layout introduced in heygen-com/hyperframes#345 (capture/screenshots/, capture/assets/, capture/extracted/tokens.json). Old captured/ prefix was broken — agents following the skill were looking for files in wrong locations. - references/features.md (new): distilled coverage for captions (language rule, tone table, word grouping, fitTextFontSize, exit guarantee), TTS (multilingual phonemization, speed tuning), audio-reactive (data format, mapping table, sampling pattern), marker highlighting (highlight/circle/burst/scribble/sketchout), and transitions (energy/ mood tables, presets, shader-compatible CSS rules). Five topics the original PR didn't cover.
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.
What
Fixes multilingual TTS output in
hyperframes tts. Adds:--lang <code>flag for explicit phonemizer locale override--langis omitted (the actual bug fix)kokoro-onnxversion predates thelangkwargCloses #349.
Why
hyperframes ttswas calling Kokoro'smodel.create(text, voice=, speed=)with no language argument. Kokoro's text frontend defaults toen-usregardless of voice, so picking a non-English voice likeef_dora(Spanish) orjf_alpha(Japanese) and feeding it native-language text produced English-phonemized output — every non-English voice was effectively broken.Kokoro's own voice ID convention encodes the language in the first letter (
a=American,b=British,e=Spanish,f=French,h=Hindi,i=Italian,j=Japanese,p=Brazilian Portuguese,z=Mandarin), so the default can be derived mechanically from the voice. Explicit--langis kept for cases where users intentionally want a mismatch (stylized accent, specific locale likeen-gbvsen-us).How
packages/cli/src/tts/manager.tsSUPPORTED_LANGSreadonly tuple of the nine valid phonemizer codesinferLangFromVoiceId(voiceId)helper mapping voice prefixes → localesisSupportedLang()type guarddefaultLangto everyVoiceInfoBUNDLED_VOICESwithef_dora,ff_siwis,jf_alpha,zf_xiaobeiso--listsurfaces multilingual options (it was English-only before)packages/cli/src/tts/synthesize.tslanginSynthesizeOptions; forward asargv[7]to PythonKokoro.create's signature and only passeslang=when the installed kokoro-onnx version supports it — older installs keep working with their default (English) phonemizationlangandlangAppliedso the caller can detect silent no-opssynth-v2.pyso existing users automatically pick up the new script (the old cached copy doesn't know aboutargv[7])packages/cli/src/commands/tts.ts--lang, -lflag with validation againstSUPPORTED_LANGS--lang>inferLangFromVoiceId(voice)>en-us--langdisagrees with voice-implied lang (legitimate for stylized accents, suppressed under--json)langkwarg (old installs)--listadds a "Lang code" column so users can see what each voice phonemizes to by default--helpBackward compatibility
tts "Hello" --voice af_heartlang=en-uslang=en-us(inferred) — unchangedtts "Bonjour" --voice ff_siwistts "..." --voice ef_dora --lang en-uslangkwarg support--langlogs a dim note if requestedEnglish voices (
a*/b*prefixes) are unchanged. Non-English voices now phonemize correctly by default — that's a bug fix, not a regression.Test plan
packages/cli/src/tts/manager.test.ts— every supported prefix, unknown-prefix fallback, case-insensitivity,isSupportedLangacceptance/rejection, regression guard that every bundled voice has a validdefaultLangmatching its IDbun --cwd packages/cli test→ 128 passed (11 test files, 17 new)bunx oxlint+bunx oxfmt --checkclean on all changed filesbun run buildsucceedsnpx tsx packages/cli/src/cli.ts tts --helprenders--langrow and examples correctlynpx tsx packages/cli/src/cli.ts tts --listrenders the new Lang code column and lists the expanded voice setnpx tsx packages/cli/src/cli.ts tts "hi" --lang notrealproduces a clean validation error listing valid codesnpx tsx packages/cli/src/cli.ts tts --lang es(no input) falls through to the "provide text" errornpx hyperframes tts "La reunión empieza a las nueve" --voice ef_dora --output /tmp/es.wavand confirming by ear that it pronounces "reunión" and "nueve" in Spanish rather than English.Notes for reviewers
espeak-nginstalled at the system level (brew install espeak-ng/apt-get install espeak-ng). The docs/skill call this out. A preflight check forespeak-ngwhen--langis non-English would be nice follow-up but the Kokoro error is already reasonably clear.--lang EN-USis rejected (we normalize withtoLowerCase()before checking so--lang EN-US→en-usstill works; explicit invalid values like--lang englishor--lang deare rejected).--langsurface, and the inference logic + CLI surface ship together so users get the fix without needing to know the new flag.