Repository navigation
feat(skills): soft-launch reverse-any-video, and fix what Wanbi's Pro test found - #7006
Conversation
…nd stop on a stuck shot From Wanbi's Pro test of reverse-any-video (11-01, checked against 0.9.2): - A standard install never found the skill: it was internal, and /hyperframes did not route to it. It is now an on-demand skill on every catalog surface, and the routing line names it. - The cloud render in SKILL.md had no --quality high, so the final rendered at standard. - A re-shoot that stayed pending had no rule; the run waited four hours. people.md now runs the command once more after generate's 30-minute stop, then treats the shot as failed.
…ts a HeyGen voice Wanbi's Pro run fell back to Kokoro: --voice needed an id, and the only way to list ids was the separate heygen CLI and its own login. /v3/voices answers on the HyperFrames sign-in, so: - --voice <language> picks HeyGen's first public voice in it and names it in the output. - A missing --voice lists six English voices to pick from instead of a bare error. - A voice id still goes straight through, with no extra call.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed at 0f80cdd689a0241358d7336343a4811ee1ccf204.
The public skill remains on demand rather than joining the four core installs; its manifest is synchronized. The reverse recipe now explicitly asks cloud rendering for high quality (skills/reverse-any-video/SKILL.md:34), and voice lookup uses the existing HyperFrames credential resolver (packages/cli/src/heygen/v3.ts:269-278).
Important — retain the resolved voice across paid retries (packages/cli/src/commands/media-use/generate.ts:128, packages/cli/src/heygen/generate.ts:475-478). Every invocation of --voice Chinese selects the catalog's current first voice. The resolved ID becomes part of the generation body and its idempotency key (generate.ts:524-525). If the catalog order changes while a job is pending, the identical command sends a different body/key and can start another paid voice/avatar job instead of resuming the accepted one. Naming the selected voice in output does not protect the timeout path, which returns an error before that output is produced.
Reproduced with real pickVoice, generate and runJob, mocked HTTP and a fake clock advanced after an accepted pending POST to reach the actual 30-minute timeout. Same script/output/language, first voice A then B: the POST key changes from hf-f957f49ae1e6e1ece90af2b72ea0c7c1872357d4 to hf-fdd348e0f2c5f394a54ff1ea949b8388d0ebc2f0. Stable-catalog and explicit-ID controls preserve the key. The executed witness uses voice generation; avatar exposure follows the same command-level lookup and body/key construction in source. No live duplicate charge was made or observed. Catalog ordering is already treated as mutable by skills/media-use/audio/scripts/lib/tts.mjs:69-73.
Fix: persist the resolved voice with the logical generation request before paid dispatch, scoped to its credential/account, and reuse that binding when resuming instead of querying the first voice again. The existing job log can carry the binding and original-request identity; a global language-only cache would mix unrelated selections. Add a regression that changes the catalog after a pending timeout and requires the retry to retain the original voice/body/key, for both voice and avatar callers. That clears this finding.
The pending-shot instruction in skills/reverse-any-video/references/people.md:21 matches the existing video resume path: unchanged prepared input retains its key, and a pending timeout does not mark that key failed. No full 60-minute re-shoot was run.
Audited: changed CLI voice command, generation and v3 integration; skill-install tier logic; changed skill instructions and catalog sections.
Trusting: existing rendering/provider internals outside the changed paths; generated manifest content independently checked. No live Pro reverse run, HeyGen speech/avatar generation, or cloud render was performed; the author's live Chinese result is not my validation.
Validation: bun run --cwd packages/cli test src/heygen src/utils/skillsManifest.test.ts src/commands/coreSkillContent.test.ts src/commands/skills.test.ts — 166/166 pass. bun run build, bun run lint, manifest --check (8 skills) and byte-identical scaffold templates pass. The independent adversarial pass reproduced the retry issue; I reran its three timeout controls with the same result. The new skill wording has not been exercised in a complete reverse session.
Need: yes · Size: ok · Reuse: existing credential resolver and paid-job identity/logging; retain voice resolution with that identity · Simpler: one persisted request binding rather than a second retry mechanism · Structure: fine
Risk: medium — mutable voice selection can change the identity of an already-paid request on retry.
Verdict: REQUEST CHANGES
Reasoning: Catalog exposure and quality updates check out, but language-based voice selection currently breaks the same-command paid-job recovery guarantee when the catalog changes.
— hf-review
… miao/reverse-soft-launch # Conflicts: # skills-manifest.json
Edit accuracy: accurate 271 (base branch 271), smooth 121 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (2)
|
…-runs Rames: `--voice Chinese` re-picked HeyGen's first voice on every run, and the voice is part of the body and the idempotency key. If the catalog reordered while a job was pending, the same command could start a second paid job instead of resuming the first. The pick is now bound to the request (every field, plus the account scope a job bills) in the project's heygen-jobs.jsonl, or the CLI config dir outside a project, and a re-run reuses it. Regression: a pending timeout, a reordered catalog, the same command, for voice and avatar; the second POST keeps the voice, body and key.
|
Thanks Rames, fixed in 188c413 (after merging main in 9d0a295). A language pick is now bound to its request: every field of the request plus the account scope a job bills ( Regression in |
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed at 188c413ca885a5c247a07bdae075eb96bda386fa (re-review of the four-file fix and main integration since 0f80cdd68).
Persisting the language pick before paid dispatch now preserves the original voice/body/key with a stable account scope (packages/cli/src/heygen/generate.ts:495–504). Both voice and avatar regressions exercise a pending timeout and reordered catalog; explicit voice IDs retain their direct path.
Important — an unavailable OAuth identity must not become a different request scope (packages/cli/src/heygen/generate.ts:498). senderScope() uses jobSender(), which converts any /v3/users/me error into a null scope (packages/cli/src/heygen/v3.ts:58–64). The new ?? "" saves the pick under an anonymous binding. When that endpoint recovers, the same OAuth credential and command hash to the real account binding, miss the saved pick, and can choose another voice and paid key. The reverse transition, known scope to unavailable identity, also changes the lookup identity in source; that direction was not separately executed. This is a remaining case of the earlier paid-retry finding, introduced by the new fallback binding.
Reproduced using the real credential resolver, AuthClient, pickVoice, generate and runJob, with mocked HTTP and Date.now advanced to the actual 30-minute pending timeout: /v3/users/me returns 503 during the first pick, recovers before paid dispatch, and the catalog reorders before the identical retry. Voice A becomes B and the POST key changes from hf-f957f49ae1e6e1ece90af2b72ea0c7c1872357d4 to hf-fdd348e0f2c5f394a54ff1ea949b8388d0ebc2f0. A stable-account control preserves voice/body/key. The fresh adversarial agent reproduced this and I independently reran its witness. No live job or duplicate charge was made.
Concrete fix: resolve the scope once before reading/writing the language binding; if it is null, return a retryable Fix before selecting a new voice or submitting a paid job. Do not persist or look up a blank-account binding. Add OAuth identity-failure/recovery regressions in both directions, requiring either safe refusal before POST or reuse of the original voice/body/key. Apply the shared guard to voice and avatar. The executed witness is voice; avatar shares this lookup path.
Nit: README.md:85 still says a non-interactive install gets “all 7” although the public catalog now has 8; update the count (also noted by Jerrai).
Audited: the full changed voice command, generate and v3 files, their tests, account scope and journal readers; main integration and the original public-catalog/skill changes carried forward.
Trusting: provider/render internals outside the delta and existing Studio changes merged from main. No live HeyGen request or full reverse session; public catalog ordering and the author's Pro check remain separately reported evidence.
Validation: bun run --cwd packages/cli test src/heygen src/utils/skillsManifest.test.ts src/commands/coreSkillContent.test.ts src/commands/skills.test.ts — 168/168 pass. bun run build passes. Disabling the saved-pick lookup makes both new retry regressions fail (2 failed); restored code passes both. Independent OAuth timeout witness and stable-account control reproduce the gap above despite the passing suite.
Need: yes · Size: ok · Reuse: existing account scope and paid-job journal · Simpler: refuse an unknown scope rather than create an anonymous binding · Structure: fine
Risk: medium — a transient identity-service failure can change a paid retry's request identity.
Verdict: REQUEST CHANGES
Reasoning: The stable-scope catalog-reorder case is fixed, but OAuth identity failure/recovery still permits a different voice and paid request key on the same command.
— hf-review
…o none Rames: when /v3/users/me failed, senderScope() was null and the pick was bound to an empty scope; once the account came back, the same command missed that binding and could pick another voice and paid key. pickVoice now stops before picking or posting while the account is unknown, and never reads or writes a blank-account binding. Regressions, both directions: unknown then known refuses with no POST, then picks once; known then unknown refuses the re-run with no second POST, then resumes with the same body and key. Also README: a non-interactive install gets all 8 skills, not 7.
|
Thanks Rames, fixed in a23ab12. Regressions, both directions: unknown then known refuses with no POST and no voice lookup, then picks once the account answers; known then unknown (catalog reordered meanwhile) refuses the re-run with no second POST, then resumes with the same voice, body and Idempotency-Key. Both fail with the guard removed. README now says a non-interactive install gets all 8. |
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed at a23ab12e18a96d3efda1d030beb2944add4132b3; delta since 188c413ca is the unknown-account guard, regressions for the two transition directions and README count.
Both earlier findings are resolved. A language selection is retained with its request/account before paid dispatch; catalog reordering preserves its voice/body/key. An unavailable account now throws a retryable Fix before journal lookup/write, catalog selection or paid submission (packages/cli/src/heygen/generate.ts:495–502). The production voice and avatar callers share this guard (packages/cli/src/commands/media-use/generate.ts:150). Explicit voice IDs retain their direct path. README.md:85 now says 8.
Validation: bun run build and bun run lint pass. bun review-artifacts/7006/r3/oauth-controls.mjs uses mocked HTTP with the real credential resolver, AuthClient, pickVoice and runJob: voice and avatar both pass unknown→known, known→unknown→known, zero catalog/POST calls and unchanged journal during refusal, and identical voice/body/key after a 30-minute pending timeout plus catalog reorder. No live HTTP is used. bun run --cwd packages/cli test src/heygen src/utils/skillsManifest.test.ts src/commands/coreSkillContent.test.ts src/commands/skills.test.ts — 170/170 pass. Removing the unknown-scope guard makes both new transition regressions fail (2 failed); restoring it passes both. Fresh independent source and review-body passes found no additional blocker.
Audited: this delta, the shared voice-pick and journal/account boundaries, both production callers, and the previously reviewed public-skill/quality/retry changes carried forward.
Trusting: unchanged provider/render internals and existing main integration outside this delta. No live HeyGen generation, full Pro reverse run or cloud render was performed. The new scope-transition unit tests mock senderScope; the independent HTTP controls exercise the real resolver/AuthClient/senderScope chain.
Need: yes · Size: ok · Reuse: existing account scope and paid-job journal · Simpler: one early unknown-scope refusal · Structure: fine
Risk: medium — changes language resolution and recovery identity for paid voice/avatar jobs.
Verdict: APPROVE
Reasoning: Persisted selection and the early account guard close the catalog-reorder and transient-identity retry findings; no new blocker found in the delta.
— hf-review
What
From Wanbi's Pro test of reverse-any-video (11-01, CLI 0.8.225), the four points still present on 0.9.2:
reverse-any-videois no longerinternal. It is an on-demand skill on every catalog surface (CLAUDE.md, AGENTS.md, README, docs/guides/skills.mdx, both CLI templates, skill-lifecycle, init-and-scaffold, the fresh-install test), the count goes from 7 to 8, and/hyperframesroutes "Copy a video" to it.--quality high, ashyperframes-cli's paid-plan rule already says. Cloud defaults tostandard.people.mdhad rules for a refused or failed shot, none for one that stays pending. A shot still waiting when generate stops at 30 minutes now gets the same command once more (it resumes the job, no second charge), then counts as failed while the rest gets built.media-use generate --type voice|avatarneeded a voice id, and the only way to list them was the separateheygenCLI with its own login, so the run fell back to Kokoro./v3/voicesanswers on the HyperFrames sign-in, so--voicenow also takes a language (--voice Chinese): HeyGen's first public voice in it, named in the output. A missing--voicelists six voices to pick from. An id still goes straight through.Not changed: naming a recommended model in the skill. One film, five runs, and the skill stays agent-neutral.
Test plan
fails without the fix:
heygen/generate.test.ts"picks HeyGen's first public voice for a language"src/heygen,skillsManifest,coreSkillContent,commands/skills: all pass. Typecheck,bun run lint(skill budgets, cross-references, mirror) and the comment ratchet pass. Both CLI templates stay byte-identical.--voice Chinese --planpicked "Shuxian - Clear & Trustworthy"; a real 2.4 s speech job with--voice Chinesecame back as an mp3; a missing--voicelisted six English voices.hyperframes/SKILL.mdstays inside its 7168-byte budget (7165): the routing line now reads "Skills and the pinned CLI" for "Installing, refreshing or upgrading skills and the pinned CLI".--quality highare skill text; a new reverse run on a Pro account checks them.