VIDEOAGENTS UPDATES

Release notes for every VideoAgents version: new features, changes and fixes, straight from the changelog.

Latest: v1.0.42 · Oct 23, 2026

Next release

In development

Added

  • 参考文件 page: a 🔎 图库 button on each image section searches free image libraries and adds picked images to that section (2026-10-23).

    The 视觉风格, 封面, 角色, 场景, 生物, 道具 and 其他 sections each get a 🔎 图库 button next to 上传. It opens a search window whose target section is the one you clicked (a 子目录 typed in that section carries over). Click a section icon under a result to download it into that section; tick several results and press 加入参考文件 to add them together to the target section, with an optional 子目录 and note. A magnifier shows a larger image with the author, source page, license and size. Search and downloads go through the service (GET /api/v1/stock/providers, POST /api/v1/stock/search, POST /api/v1/projects/<project>/references/import); the service takes the download address from its own search results, never from the page. Images whose long side is over 2560 px are scaled down to 2560 (transparent images stay PNG); smaller ones are saved as downloaded. Adding the same image to the same folder again does not download it twice. Sources: Openverse needs no key (Flickr, Wikimedia Commons, Europeana, The Met, Smithsonian, Cleveland and other collections; anonymous limit 20 searches a minute and 200 a day, and at most 240 results per search). Only CC0, public-domain and CC BY images are offered; BY-SA, NC and ND are left out, as for Openverse sound effects. If Client ID / Secret are saved under 音效 › Openverse on the 生成模型 page, image search uses them too. Pexels and Pixabay are listed but stay disabled until a free API key is set: on the ⚙️ 设置 → 存储与图库 page (a 🖼 图库 API Key card with a 测试 button that runs one real search with the key in the form), or, when that is blank, in PEXELS_API_KEY / PIXABAY_API_KEY or the footage library's footageconfig.json (pexels_api_key / pixabay_api_key). The footage library's Pexels / Pixabay video search reads the same keys from that page first. Search results are cached for 24 hours (Pixabay's terms require this), so repeating a search does not use quota. Openverse titles are mostly in English, so a 译成英文 button (enabled when the query contains Chinese, Japanese or Korean) translates the query into 1–3 English keywords with the Agentics engine's current text model and searches again; each click is one model call on that channel. Records: each imported image's source (site, author, license, page, attribution, search terms) is saved in refs/annotations.json and shown on its card; when the image has a note, its NOTES.md line ends with the source and license. A new refs/CREDITS.md, rewritten on every import or delete, lists all imported images and marks the CC BY ones that need a credit if they appear in the film. Clearing an imported image's note keeps its source record. All 11 UI dictionaries are covered (non-English ones not reviewed by native speakers). Not exercised: Pexels and Pixabay against the real services (no keys), and 译成英文 through the Agentics desktop account.

Changed

  • ⚙️ 设置 → 文件托管 is renamed 存储与图库 (2026-10-23).

    The page now holds two things: the object storage that turns local reference files into presigned links for video models (unchanged), and the image-library API keys for 🔎 图库 on the 参考文件 page. The menu entry, page title, the hints on the 虚拟人像资产库 page (its readiness line is now 对象存储自检), server error messages and the agent notes that point users to this page use the new name; the address is still /storage, and saved settings are unchanged.

  • Agents can no longer delete the data folder or a whole project, and each project is snapshotted before an Agent starts (2026-10-23).

    In a reported case a pi Agent typed the data folder path with different letter case (@videoAgents instead of @videoagents); macOS treats both as the same folder, but the tool echoed the wrong spelling, so the Agent believed it had made a duplicate folder and ran rm -rf on it, deleting every project, the bundled Python runtime and the sign-in data. Delete guard: the data folder, the runtime folder, projects/, every project folder, the install folder, the bundled Python, the snapshot folder and the home folder, plus every folder above them, are protected. Deleting one of them, or emptying it with something like rm -rf <project>/*, is refused and nothing is deleted; files and subfolders inside a project can still be deleted. Folders are compared as the same folder on disk, not by spelling, so different letter case, symlinks, .. or a trailing slash make no difference. For the pi and Agentics engines an extension checks each tool call first: a literal delete of a protected folder (including /bin/rm, find … -delete with no name or time filter, and shutil.rmtree('…')) is blocked and the reason goes back to the Agent as the tool result, which 2>/dev/null || true cannot hide, and says the two spellings are the same folder when only the case differs; file paths given to read / write / edit / ls / find / grep are corrected to the spelling on disk, so the Agent never sees a wrong one. For every engine an rm placed first on the Agent's PATH checks the arguments after the shell has expanded them, so variables and wildcards are covered; it only starts Python when an argument is a folder. Engines that run commands in a login or interactive shell may put the system rm back in front, so for engines other than pi and Agentics this is best effort, and there is no rm stand-in on Windows yet. Snapshots: before an Agent starts, its project is copied outside the data folder, to ~/Library/Application Support/VideoAgents-Snapshots on macOS (%LOCALAPPDATA%\VideoAgents-Snapshots on Windows, ~/.local/share/videoagents-snapshots elsewhere, or VIDEOAGENTS_SNAPSHOT_DIR). On macOS this is an APFS clone that takes no extra space at first (a 15 GB project with 20,000 files took 0.65 s); elsewhere it is a tree of hard links, which survives deletion but not files rewritten in place; if the snapshot folder is on another drive the snapshot is skipped rather than copied in full. A project is snapshotted at most every 30 minutes; within 7 days the 3 newest are kept plus the newest of each day, and older ones are removed (VIDEOAGENTS_SNAPSHOT_INTERVAL / _KEEP / _DAYS; VIDEOAGENTS_SNAPSHOT=0 turns snapshots off). Restore from the command line with python -m services.runtime.project_snapshot list and … restore <snapshot> <new project folder>, which never overwrites; there is no button for it yet. Only project folders are snapshotted, not chats or settings. The Agentics engine's one-off image analysis in the footage library now gets only the read tool. Not exercised: Windows.

v1.0.42

Removed

  • ComfyUI video templates: the two Seedance 2.x Comfy Cloud reference-to-video templates (2026-10-23).

    comfy/video-seedance2_5_r2v-cloud-only.json and comfy/video-seedance2_0_r2v-cloud-only.json, with their .md docs, no longer ship, so they are gone from the ComfyUI workflow list on the 生成模型 page. The handling for workflows built on ByteDance2ReferenceNode is unchanged, so a Comfy Cloud workflow you export yourself still works the same way. A setting that still points at one of the removed paths needs a workflow file of your own.

Changed

  • 工作流预览: spent time and per-task time now come from the service's run records (2026-10-23, #134).

    A task's time used to come only from its runs/<task>/meta.json or the final result event of its run_id session log. So most tasks showed no time (many DAG nodes have no run_id), runs that ended in an error counted as 0, a re-run overwrote the earlier time, batch tasks sharing one run counted it once per group, and old meta.json files from an earlier version stood in for later re-runs. A task's time is now the sum of the run records (started → ended, kept in runtime.sqlite3) that belong to it. A run belongs to a task when the node lists it in run_id / prev_run_id / restyle_run_id, or when the first line of the dispatch message names the node ID. If the named node is a fanned-out parent and that line lists grpNNN or grpNNN–grpMMM, the run goes to those instances; a change-order suffix such as -B11R goes to the base node. A run that belongs to k tasks is split k ways. Producer sessions are not counted toward any task. meta.json and the session log are only used when no run is found. 已花费 is now the union of all the project's run periods (including the producer, re-runs and runs that ended in an error), so parallel runs count once. A fanned-out parent's own time counts toward its family, and the detail panel shows how many runs a task had. All 11 UI dictionaries are covered. On one real project, tasks with a recorded time went from 36 to 107 and 已花费 went from 9.3 h to 61.8 h.

  • Volcengine and BytePlus model lists: retired models removed, Seedream 5.0 Flash added (2026-10-23).

    Checked against Ark's online model list. Seedance 1.5 Pro (doubao-seedance-1-5-pro-251215) was shut down on 2026-09-21 and now fails with ClosedEndpoint. Seedance 1.0 Pro / 1.0 Pro Fast and Seedream 5.0 / 5.0 Lite / 4.5 / 4.0 (-250828) are in Ark's tenth retirement batch: no new activations since 2026-09-24, shut down on 2026-11-24. All seven are gone from the Volcengine video and image lists on the 生成模型 page, the storyboard and canvas pickers, and the episode / group video-model choices. The Volcengine image list adds doubao-seedream-5-0-flash-260915 (same features as 5.0 Pro, faster and cheaper) and doubao-seedream-4-0-20260415 (Ark's replacement for Seedream 4.0). A setting that already uses a removed model keeps working: the page shows it under 自定义…, and accounts that activated the retiring ones before 2026-09-24 can call them until 2026-11-24. For 4.0 (20260415), the largest image size (4096×4096) was measured. For 5.0 Flash, the size range is taken from Ark's docs and is the same as 5.0 Pro; it has not been tried, because neither test account has Flash activated. On BytePlus, Seedance 1.5 Pro (seedance-1-5-pro-251215) is in the fourth retirement batch: no new endpoints since 2026-09-15, shut down at 17:00 on 2026-11-11 (UTC+8). It is gone from the BytePlus video list, and accounts that already use it can call it until then. The BytePlus image list adds dola-seedream-5-0-flash-260915. Every other BytePlus model in the lists still works, including Seedance 1.0 Pro / 1.0 Pro Fast, which Volcengine is retiring. seedream-4-0-20260415 is not added on BytePlus, because BytePlus is not retiring seedream-4-0-250828.

Fixed

  • Engine CLIs: the fallback install paths for Kimi Code, OpenCode, Grok and Antigravity now work on Windows, and a failed launch shows the system's error (2026-10-23).

    When a CLI is not on the service's PATH, the service looks in the engine's default install location. For these four engines that location was written without .exe (e.g. ~/.kimi-code/bin/kimi), so on Windows it never matched, and the error only showed the bare command name. The fallbacks now come from the same default install locations that one-click install uses, with .exe on Windows and several candidates for some engines. When starting a CLI fails with "file not found", the "not installed" message now adds the error the system returned, so a transient failure (for example an antivirus scan holding a freshly downloaded runtime) can be told apart from a missing install. Paths on macOS and Linux are unchanged. Not exercised: the antivirus case itself.

  • Image generation no longer gives up after 10 minutes (2026-10-23, #134).

    The shared wait limit for cloud image channels (Agentics, Volcengine Ark, OpenRouter, Fal, MiniMax and RunningHub standard models) rises from 600 s to 1800 s, the same as video and ComfyUI. An Agentics FLUX.2 image-to-image run with a 10-image reference chain took about 706 s and was cancelled before it finished. The four image subprocesses the service starts (sketch generation, storyboard sketches, grid sketches and hand-drawing AI refinement) now wait up to 2100 s instead of 900 / 1200 s. They used to kill genmedia while it was still waiting, which left the remote task running and saved nothing.

  • Agentics TTS Voice Design now sends the voice description as instruction (2026-10-23, #134).

    On 2026-10-14 the Agentics service made instruction the official name of the TTS voice-description parameter. The client only recognised instruct / voice_instruction / voice_description / instructions. It treated a profile that declares instruction as having no description parameter, fell back to Voice Clone and could not produce the first voice sample. instruction is now checked first, and the old names still work for profiles imported earlier. This only takes effect once the Voice Design profile on the service is re-imported, or maps instruction.

  • Agentics digital human: inputs now follow the service's digital-human contract (2026-10-23, #136).

    Digital-human profiles (media type 5) used to be checked against the video parameter and attachment list. The avatar image was sent as first_frame / reference_images and the speech as reference_audios. The avatar image now goes to avatar_image and the speech to driving_audios, checked against the digital-human contract's own parameters (e.g. audio_mode, audio_strength, audio_guidance_scale, fps, background_color) and attachments (avatar_video, dialogue_audio, speaker_masks, background_image / background_video, …). A profile with only attachment mappings is accepted and the prompt may be empty. When the profile requires duration, it is now the exact audio length in seconds, no longer rounded up. code/check_digitalhuman.py also accepts agentics as a clip ledger's provider, so finished Agentics lip-sync clips no longer fail the check as "渠道非法".

  • Audio transcription now says which Python it ran on (2026-10-23, #136).

    When faster-whisper cannot be imported, modules/transcription.py and its doctor command now name the interpreter (sys.executable) and give the import error, instead of only asking for a reinstall. doctor also reports python_executable / python_prefix. The desktop app now lists the selected Python runtime's folder ahead of the login-shell and system paths in the web service's PATH, so python3 in commands finds the app's runtime. The desktop part takes effect after the desktop app is rebuilt.

  • macOS desktop: Agentics runs no longer add an exec icon to the Dock for each running agent (2026-10-23).

    The built-in pi ran on the app's main executable in Node mode. pi sets its process title at start-up, and on macOS that registers the process with the system using the app's own settings, so every running agent showed up in the Dock as a separate app with a generic exec icon until it finished. The desktop app now runs pi on its Electron helper (VideoAgents Helper), which is marked as a background app, so agent runs stay out of the Dock. Windows and the source version are unchanged. Takes effect after the desktop app is rebuilt.

  • Burned-in subtitles, whitebox reel subtitles and animatic captions now follow the requested font size on machines without a Chinese font (2026-10-23).

    When neither data/fonts/ nor the system has a Chinese font (typical on a Linux server), the subtitle renderer fell back to PIL's built-in font at a fixed 10 px, whatever the size the layout asked for. A 1920×1080 subtitle came out at about 1% of the frame height, and shrinking an over-long cue to fit two lines did nothing. The fallback now loads PIL's built-in font at the requested size. It has no Chinese glyphs, so Chinese text still needs a Chinese font installed, but English subtitles render at the right size. The subtitle burn ledger records PIL 默认字体 as the font in that case.

v1.0.41

Added

  • A host check for each speaker's voice pitch in dialogue groups: python3 code/voice_f0_check.py --project <slug> --ep <ep> --group <grp> (2026-10-22, #148).

    The video-generation role notes and WORKFLOW.md required a per-speaker acoustic check (voice_f0_check) on every dialogue group, but no such script ever shipped, so agents either wrote their own copy into each project's code/ (five different versions) or marked the check as not run. The new command splits the group clip by speaker (from the clip's boundary_map and the shot list's on-screen lines), compares each speaker's median pitch with that speaker's own voice sample from the group prompt's audio_refs, and reports PASS (within 20%), WARN (20–70%, noted in the hand-off only) or FAIL (over 70%, or two speakers each closer to the other's sample), with per-speaker and per-segment detail; --json gives machine-readable output. It only judges a group whose audio_plan is dialogue, whose project has 对白配音 = 视频原声 and whose prompt has audio_refs; otherwise it reports SKIP and exits 0. A FAIL sends the group to the re-roll flow, limited by the 重跑次数 setting. The single-clip form agents were used to, <clip> --seg <start>:<end>:<character-id>, also works. The FAIL line was planned at 35%; on 263 real dialogue groups that failed 32% of them, mostly because of steady gaps between a character's sample and the way the model voices that character, which a re-roll does not fix, so it was raised to 70%, roughly the gap between a male and a female voice (8% of those groups fail at that line, about 9% with the swapped-speakers rule).

  • A host check for the Bible skeleton: python3 code/check_bible_skeleton.py --project <slug> (2026-10-22).

    The Bible set-up task (p0-bible-init) was accepted on "skeleton schema complete", but nothing in the program checked that, so each project's memory-bible agent proved it with its own one-off check and the criteria differed from project to project. The new command reports PASS / FAIL / WARN item by item: the Bible files and folders listed in agents/WORKFLOW.md §2 exist and parse; the paths and top-level keys the program itself reads are present, with empty containers accepted (characters/index.json#characters, creatures/index.json#creatures, scenes/index.json#scenes, props.json#props, costumes.json#costumes, the top-level style_fragment_en / style_fragment_ui strings in style.json, and color_script.json#episodes, plus the alternative keys the program also reads); every file and folder name under bible/ is ASCII; and the change log bible/changelog.md exists. Spellings that only some parts of the program read give a warning instead of a failure. Any failure exits non-zero; --json gives machine-readable output. p0-bible-init in agents/workflow.yaml now lists bible_skeleton_ok and ascii_filename, and the memory-bible role notes and agents/WORKFLOW.md say what the skeleton must contain and name the command. Existing projects are not changed; projects started before the creature registry, the two style strings or the colour-script format was introduced report those parts as failing.

  • Whitebox layout check: a scene's aspect-ratio check can be waived with a defect the user signed off (2026-10-22).

    code/whitebox_layout_check.py fails a scene whose dimensions_m X:Z differs from the top-view image by more than 2%. When the top view is a generated sketch the user has accepted as out of proportion (the model is built to the set card instead), that check failed on every run with no way to record the waiver. A scene's whitebox.json can now carry layout_check_waiver: {"defect_id": "DEF-…", "items": ["aspect"]}. The check reads qa/defects/<defect_id>.json and counts the aspect item as waived only when that defect is waived (or waived_by_user), its waiver.signed_by is the user (an agent or full-auto does not count) and its scene_id is this scene. The report then lists the item under waived with the defect ID, and the scene passes unless something else fails. Only aspect can be waived; geometry outside the image still fails. When the conditions are not met, the aspect item still fails and says why the waiver was not accepted. The key is not compiled into the scene and does not count toward the reviewed-camera fingerprints, so adding it does not make reviewed shots or exported videos stale. The scene-modeling role notes and docs/whitebox.md describe it for scenes whose aspect deviation the user has signed off (#154).

  • 全自动模式: a per-project checkbox on the 工作流预览 page that removes manual sign-off (2026-10-22).

    The summary row (the one with ✏️ 修改) gains a 全自动模式 checkbox on the right, off by default, with an i button explaining it. When checked (POST /api/v1/projects/<project>/full-auto {enabled}, stored per project in state.json#full_auto), every sign-off card of that project is answered 签字 by the service as soon as it is created, and every other confirmation card gets its default option, with no pop-up or desktop notification; the producer is then woken with a full-auto sign-off receipt and reviews the gate as usual (gate JSON signer full-auto). The existing pre-sign-off checks still apply (H3V missing prompts, H3W blocking whitebox issues, H3P dispatched prescriptions / post_ok FAIL): a blocked card stays open with the reason shown on it, dispatch.py --confirm … --sign prints 自动签字未通过:<reason> instead of waiting, and when no task is waiting on the card the producer is told to fix it and raise the sign-off again. Agent prompts get a 全自动模式 section only while the box is checked: wherever the spec says to ask or escalate to the user, the agent decides by the standard of getting the film finished (keep finalized settings and assets, safest option with least rework, report what it chose and why); the retry count setting still applies, and once retries run out the best existing version is kept and major/minor defects are waived (signed_by: full-auto); content-safety and copyright blockers are never waived, and the mode never changes settings, turns on paid options, races agents or switches engines. Checking the box immediately answers the cards already waiting, creates and signs any unlocked gate that has no card yet, and, if that did not wake the producer, asks it to check whether it is waiting on a user decision. Unchecking restores manual sign-off; automatic sign-offs already made stay. It does not replace 🤖 自动运行, which still decides whether an idle pipeline is resumed.

  • Ctrl+Enter adds a message to the task an agent is already running (2026-10-22).

    In the console, Enter still sends a new task (queued behind the agent's running one); Ctrl+Enter (⌘+Enter on macOS) sends the message into the running task instead (POST /api/v1/runs/steer), where it reaches the model after the current tool call finishes. This works on the Claude Code, Pi, Agentics and OpenCode 2 engines: Claude Code runs now take their prompt on stdin (--input-format stream-json --replay-user-messages), Pi and Agentics runs use --mode rpc (added messages are sent as steer prompts), and OpenCode 2 receives them through the session inbox (opencode api --standalone, delivery: "steer"). On the other engines (Codex, Kimi Code, Grok Build, Antigravity, OpenCode 1.x) the message is merged into the agent's queued task, or sent as a new task if none is queued. A message sent while the task is still queued or starting is added to its prompt, and one that was not delivered before the task ended is sent as a new task (a task stopped by hand only gets a note). The chat marks each such message with where it went, and the input placeholder mentions the shortcut in all interface languages.

  • Auto backup on sign-off, on the 版本管理 page (2026-10-22).

    A new 💾 自动备份 card (off by default) takes a backup folder; when on, every sign-off (answering 签字 on any sign-off card, from the console, a preview page or Feishu) zips the whole project folder into that folder in the background as <project>-<YYYYMMDD-HHMMSS>.zip. The zip wraps the project in a <project>/ folder, so it can be restored with › 导入项目…. The setting is global (state.json#auto_backup, API GET/POST /api/v1/config/auto-backup); the folder must be an absolute path outside data/projects and is test-written on save. The card shows the last backup (or its error) and how many backups of the current project the folder holds; a failed backup also raises a system notification. Already-compressed media (video, images, audio) is stored without recompression; a sign-off during a running backup queues one more backup; old backups are never deleted automatically. 📁 选择… next to the folder field opens the system folder picker and saves the chosen folder: the desktop app uses its own dialog (pickDirectory, after the desktop app is rebuilt); otherwise the service opens it on its own computer (POST /api/v1/config/auto-backup/pick-dir: macOS choose folder, Windows FolderBrowserDialog, Linux zenity / kdialog). The button only appears when the page is opened from the same computer (localhost / 127.0.0.1) and not on a desktop app connected to a remote service, so the dialog never pops up on another machine.

Changed

  • Whitebox: applying the pending decisions accepted at the H3W sign-off no longer asks for a second sign-off (2026-10-22).

    Signing H3W (白模确认) accepts the recommended option, or the default, for each pending whitebox decision the user has not answered (recorded by=sign:g6w). The producer's role notes and docs/whitebox.md sent the whitebox agent straight on to export after applying those, but the whitebox agent's own notes said to get H3W signed again first, so the two agents disagreed. All three now agree: decisions already on record at sign-off (accepted by signing, or answered before it) are applied and go straight to export. H3W is signed again only when the user makes a new decision after sign-off that changes the picture, or when the whitebox agent fixes a mistake in the whitebox itself. The H3W sign-off card's pending-decision summary now ends with "Signing accepts the recommended option for each unanswered item; applying them afterwards needs no re-sign." (in Chinese when the interface is Chinese) (#158).

  • Workflow DAG: skipped and waived dependencies now count as satisfied (2026-10-22, #149).

    The dispatch frontier (dagcheck.py --frontier, the idle watchdog, the host's sign-off card guard and the full-auto sweep) used to unlock a node only when every dependency was done / passed / passed_human_override. Anything downstream of a condition-skipped node never became dispatchable, e.g. cv3-engine after cv2-beatsync is skipped for code-composed music, contradicting WORKFLOW.md §10.3 rule 6 and the project-type DAGs. Skipped and waived dependencies now count as met; cancelled still does not. Sign-off gates (human nodes) are guarded: a gate whose dependencies are all skipped / waived, with none actually completed, is not treated as unlocked and gets no sign-off card. The workflow page marks gates as waiting for sign-off by the same rule. The main_dag_on_start.skip lists are completed so new projects don't leave main-flow nodes stuck: audio-to-video, digital human and demo video also skip p0-bible-init, p0-parse (p0-parse-* only matched the expanded batches), p0-merge, g0, g6s, g6w, g7p and g9p; mashup and the derivative-novel plugin add g6s, g6w, g7p and g9p (mashup still runs p0, g0 and p1 structure/events). In existing projects, nodes waiting only on skipped / waived dependencies now show up as dispatchable, and gates with at least one completed dependency get their sign-off card at the next service start or run end.

  • Demo videos: the per-section scene-coding tasks now run in parallel (2026-10-22).

    In a demo-video project, the producer sends one cv3-sec task per section to scene-coder. scene-coder keeps a conversation across its tasks, so it used to run them one at a time. All but the first sat queued, even though each section only touches its own sections/<section>.js. Section tasks now run side by side, up to the per-agent concurrency limit in Agent 高级设置. Each starts a fresh conversation and does not overwrite the one scene-coder resumes. Its other tasks (engine, full render, revisions) still run one at a time and continue that conversation. The task is recognised from the task id at the start of the dispatch message (执行工单 cv3-sec-grp0010…) or a task_id: line; if neither is there, the task runs one at a time as before. Concurrent codevideo.py render --section runs now take turns updating codevideo/render.json, so one section's render record no longer overwrites another's.

  • Fresh desktop installs default the music model to Agentics (2026-10-22).

    On a new desktop install, 生成模型 › 音乐 now starts on Agentics (signed-in account, default profile minimax-music-3) instead of 代码作曲, matching the image, video and TTS defaults; the browser version still defaults to 代码作曲. Installs that have already saved a music channel keep it, the 代码作曲 tab stays first, the sound-effects default (代码音效) is unchanged, and 演示视频 projects set to music_source=code still compose in code.

  • Smart Assignment for DeepSeek (opencode engine) now uses DeepSeek V4.1 Flash for the lighter tier (2026-10-22).

    The OpenCode Go model opencode-go/deepseek-v4-flash has been removed from the model catalog (OpenCode 2 rejects it as "Model unavailable"), so the smart_deepseek low tier moves to opencode-go/deepseek-v4.1-flash; the creative-core tier stays on opencode-go/deepseek-v4-pro. The opencode model picker's offline fallback list and the strategy's description (in all interface languages) are updated to match. Models picked explicitly per agent are not changed.

  • OpenCode engine: OpenCode 2 is now supported and is what one-click install sets up (2026-10-22).

    One-click install runs curl -fsSL https://opencode.ai/v2/install | bash (the old script only installs 1.x), or npm install -g @opencode/cli on Windows. The engine checks opencode --version and keeps the 1.x behaviour for 1.x installs. With 2.x: runs use --standalone (a private server tied to the run) instead of OpenCode's background service; the Thinking Effort level is passed as -m model#level only when that model offers the level (2.x rejects unknown levels, where 1.x ignored them); before resuming, the saved session is checked, because 2.x silently starts an empty session under a missing id, and a missing session falls back to a fresh one with the full role; stopping a run first sends SIGINT so OpenCode marks the session interrupted (a killed 2.x session would otherwise be resumed later by OpenCode's background service), and the process group is killed 3 s later; stdin is closed and PWD is set to the working directory (2.x waits for stdin to end and takes its working directory from PWD); the last step's token usage, often missing from 2.x's JSON events, is filled in from opencode session export; shell / subagent tool calls show as Bash / Task. Sign-in status and the OpenCode Go usage key come from opencode auth list / auth export (2.x keeps credentials in its database, not auth.json), the sign-in terminal uses auth login --standalone, the model list drops --refresh, and footage analysis also runs --standalone. Upgrading from 1.x keeps saved sessions. Not covered: on Windows a stopped 2.x run is still force-killed, so OpenCode may resume it later.

  • The built-in engine is now Agentics, a bundled Pi Coding Agent, replacing DeepAgents (2026-10-22).

    The engine formerly shown as deepagents appears as agentics in the top bar and agent model settings (its id stays deepagents, so saved choices, dispatch.py --engine and per-agent settings keep working). It runs [Pi Coding Agent](https://github.com/earendil-works/pi) 1.3.0, which ships with the desktop app (pinned in vendor/pi, about 20 MB compressed) and runs on the app's own Electron Node, so nothing extra has to be installed; from source, run make install-pi (Node.js 22.19+). Its language model is the active channel of 🎨 Generation Models → Language Model/Agentics (renamed from "DeepAgents"): each run gets a private pi configuration with that endpoint and model, the API key is passed only through the environment, and the user's own ~/.pi/agent settings, extensions and logins are never used. The separately installed pi engine is unchanged. Sessions resume with --session; the thinking-effort setting maps to --thinking (not sent to local models). AI shot analysis in the footage library can now read the contact sheet when the chosen model accepts images. The DeepAgents runner and its Python stack (deepagents, langchain-openai, langgraph-checkpoint-sqlite and 40 transitive packages) are removed from the desktop Python runtime, pyproject.toml and the install-deepagents make target; the DEEPAGENTS_* environment variables no longer apply (AGENTICS_CONTEXT_WINDOW / AGENTICS_MAX_OUTPUT_TOKENS set the model limits instead).

  • New desktop installs start on the Agentics engine (2026-10-22).

    When neither the browser nor the service has a saved engine, the desktop app now selects agentics (the web version still picks Kimi Code for Chinese and Claude Code otherwise), and Language Model/Agentics defaults to the Agentics channel. If that channel is active but no account is signed in, the console shows "Sign in to get started" with a Sign in button that runs the desktop sign-in; the web version explains that the channel needs the desktop app or a local model. The Cloud model and OpenRouter tabs of Language Model/Agentics (and the matching top-bar channels) are hidden unless one of them is already the active channel.

  • ComfyUI image templates now share one naming scheme: -t2i for text-to-image, -i2i for image-to-image (2026-10-22).

    The 14 comfy/image-*-api.json templates (and their .md docs) are renamed: text-to-image templates, which had no marker, gain -t2i (e.g. image-flux2-dev-fp8-t2i-api.json), and the image-to-image ones replace -ref10 / -ref3 / -styleref / -img2img with -i2i (e.g. image-flux2-dev-fp8-i2i-api.json, image-qwen-image-edit-2511-i2i-api.json, image-z-image-turbo-bf16-i2i-api.json). Saved settings that point at the old names (genconfig.json workflow / ref_workflow) are rewritten to the new names on load, and an old path still resolves if it reaches the runner directly; custom templates are untouched. The number of reference images a template takes is still read from its LoadImage nodes, not from the file name.

  • Agent role notes now write entity IDs one way, matching what the service reads (2026-10-22).

    The example IDs in the agents' role notes (SOUL.md) disagreed with each other and with the service, so files written by different agents could not reference each other and some IDs were silently missed: the screenplay example wrote scene:sc-cangjinge and char-linxiao (the service only picks up SCN- + digits and CHAR-…), directing / art / video examples wrote characters as c003, char-001 or chr_lin_feng and scenes as s012, the scene index wrote GEO-… where geography defines geo_…, creatures were cre_… in one note and CRE-* in another, events were evt_…, EV-0102 or ev_0042 instead of ev0042, props were prop_017 while the storyboard only picks up PROP-… (most projects already use PROP-0017), the economy example referenced an undefined cur_lingshi_low, and the political example used camp_… IDs with nowhere to define them. agents/WORKFLOW.md (Phase 2) gains an 实体 ID 写法 table — namespace, prefix / form, example, where it is defined and whether the service parses it (scenes SCN-0012, characters CHAR-0003, creatures CRE-0012, events ev0042, costumes CHAR-0003_battle_02, geo_ / nation_ / faction_ / pol_ / camp_ / cur_ / rank_ / mnt_) — and the examples in 26 role notes now follow it. A currency grade is referenced as <currency id>#<grade id> (e.g. cur_lingshi#low, grades get an id), and camps are defined in bible/politics.json#camps[]. The table applies to new projects only: existing projects keep the IDs already issued in their bible/*/index.json and project write rules, with no renaming or migration. A new test (tests/test_soul_id_examples.py) flags the old forms if they come back.

  • Moderation model: Agentics is back as a channel and is now the default (2026-10-22).

    The 🛡 审核模型 card on the 🎨 生成模型 page has two tabs again — Agentics (the signed-in desktop account, the model and 测试) and OpenRouter (API key, model and 测试) — and its 生效渠道 dropdown defaults to Agentics. The Agentics channel calls TypeSafe's Jev decision model (typesafe/jev-1.13 or typesafe/jev-1.14) through the account's OpenRouter wrapper at /api/alpha/decisions, with the same request and response as OpenRouter. A configuration saved by 1.0.40 moves to Agentics, keeping the chosen model, only when it has no OpenRouter key and OPENROUTER_API_KEY is not set; one with a key stays on OpenRouter, and once saved by this version the chosen channel is never switched again. With Agentics active and no account signed in, the refusal judge and 测试 report that the desktop account is not signed in. The NSFW mode dialog now names the active channel instead of always saying OpenRouter. Not exercised: a real call through the Agentics channel (no signed-in desktop account was available).

  • 生成模型 page: the 🧩 语言模型/Agentics card is now the first card, above the image model (2026-10-22).

    Only the order changes; the card's contents and behaviour are the same.

  • New-project wizard: the 代码风格 step no longer links to online sample films (2026-10-22).

    Choosing a full style no longer adds 「在线看样片 ↗」, and choosing the light style no longer adds 「在线看 OPUSCAR 98 ↗」; the selected line keeps the style name, category and one-line description, and the explanation below drops the sentence pointing to the sample link.

Fixed

  • Windows: agent tasks no longer fail at start with "构建 Agent 提示词失败:No module named 'fcntl'" (2026-10-22).

    Building any agent's prompt imported modules/script_keypoints, which imported the Unix-only fcntl module at the top (since 2026-09-13), so on Windows every agent task failed before it started. File locking now goes through one shared module, modules/file_lock.py: fcntl.flock on macOS and Linux, and on Windows msvcrt.locking on the file's first byte, waiting for as long as it takes as flock does. Concurrent writes to the shot-plate library, Canvas, the storyboard, screenplay notes, keypoints, NPC staging, the footage ledger, the Ark asset ledger and the dialogue voice ledger are therefore serialized on Windows too instead of overwriting each other; only when neither lock is available does the write go ahead unlocked. Files the service writes and reads — change and sign-off records, project settings, the novel and brief, NOTES.md, the README, dag.json and others — are now read and written as UTF-8 explicitly, so running from source on a Chinese-locale (GBK) Windows no longer garbles them or fails to read them (an unreadable dag.json left the H3V sign-off card unbound to its gate); the desktop app already started Python with PYTHONUTF8=1. Reference-image paths saved by the shot-plate library now use forward slashes. Not exercised: the Windows lock path under real concurrency (covered by stubbed tests only) and a packaged Windows desktop build.

  • Skills added with "Load skill" are now installed in the data folder, so a desktop update no longer wipes them (2026-10-22).

    The skills dialog in the chat panel used to install an uploaded skill into the agent's own agents/<category>/<agent>/skills/ folder. In the desktop app that folder is inside the signed .app bundle (Resources/backend), which is replaced as a whole when a new version is installed, so every uploaded skill was lost on update; writing into the bundle also breaks its code signature (the same reason Python's __pycache__ is already redirected out of it). Uploaded skills now go to <data folder>/skills/<category>/<agent>/<skill>/SKILL.md (override with VIDEOAGENTS_SKILLS_DIR); the web version uses the same place and no longer writes into the repository's agents/ folder. The skill list reads both the built-in skills and this folder: skills you uploaded carry an "Uploaded" tag, insert their full SKILL.md path when clicked, and can be deleted from the list (click Delete, then Confirm Delete within 4 seconds); built-in skills cannot be deleted. If a later version ships a built-in skill with the same name as one you uploaded, the built-in one wins and your copy is shown greyed out as not in effect, ready to delete. Uploading a skill with the same name as a built-in one is refused. The prompt-skill check (code/prompt_skill_check.py) and run skill records find uploaded skills too. Skills uploaded into an older desktop build were stored inside the old app bundle and are already gone once a new version replaces it; save your own skills before updating and load them again afterwards.

  • Native lead-ins now split at sentence ends and dashes, and English lines are no longer cut mid-word (2026-10-22).

    modules/native_lead only treated commas, enumeration commas and semicolons as split points, so lines such as Wait. Come here. or 放回去。快走! got no auto-suggestion from sync_native_leads.py plan, and the proportional fallback cut at a character index (This w|ay — go!, 放回|去。快走!). Sentence-end punctuation now counts — Chinese 。!? directly (half-width ! ? too when followed by whitespace or a CJK character), ASCII . only before whitespace, with ellipses, decimals and common abbreviations (Mr. / Dr. / St. / vs. / etc. …) excluded — and so do the dashes — / --, still within the first-clause ≤60% limit. Without a usable split point, word-counted lines (time_cost.is_word_text, as in the #124 timing rules) snap to the nearest word boundary; the lead loses trailing whitespace. Auto-suggestions are offered only when a line can be split at punctuation within that limit. A line already synced into its group prompt keeps the split recorded in the prompt's native_leads[] while its text and s are unchanged, so check does not start failing and sync --write does not rewrite the prompt (or shift its H3V fingerprint) because of the new rules; the P8 trigger-word check now looks at the lead actually used.

  • render_whitebox.py --check-only now lists every shot whose camera contract went stale, not just the first one per group (2026-10-22).

    When several shots in one storyboard group had stale review fingerprints (for example after a wording change in each shot's composition.json), the compiler stopped at the first failing shot, so the group's error named only that shot. It now checks every shot of the group and reports them in the group's single error (one entry per shot, each starting with the shot ID); structural errors still stop the group at once. The first output line gains source_groups, the episode's real group count (groups still counts only groups that compiled), and the pending-decision summary now uses the same reading as the H3W sign-off gate, so groups that failed to compile are counted too. Groups that compile are unchanged (#157).

  • The machine-readable workflow no longer asks the voice agent for the retired per-group dialogue dry-voice tracks (2026-10-22).

    Since 2026-07-20 the voice agent produces one 3–5 s voiceprint sample per speaking character and age variant at project level (assets/audio/voice/refs/ + refs/manifest.json, registered in assets/audio/voice/casting.json), and the video model gets each speaker's own sample as a voice reference; the per-group assets/audio/voice/<ep>/lines/ tracks were dropped. The role notes and agents/WORKFLOW.md were updated then, but agents/workflow.yaml still listed lines/ as the output of each episode's p8-voiceprints task and checked it with four lines checks, so the voice agent could refuse the task or produce the retired tracks. p8-voiceprints now only checks that this episode's speaking characters (and variants) are covered by the project voiceprints and casting, adds what is missing, and is checked by voiceprint_coverage_main_100, sample_duration_3_5s, casting_bound and voice_collision — the same names as in agents/WORKFLOW.md and the voice agent's role notes. The p7-video note and the project tree in agents/WORKFLOW.md no longer mention dry-voice tracks. Existing lines/ folders are left as archives. Task graphs already built for existing projects (runs/dag.json) are not rewritten.

  • Agent commands written as python3 … keep working when that interpreter lacks a dependency or cannot load one (2026-10-22).

    Agents run host commands as python3 …, which resolves to whichever interpreter the engine's shell finds first — on the reporting Mac that was conda base, whose scipy 1.15.3 imports fine but fails at scipy.signal on macOS 27 (the OS loader rejects one of its compiled extensions), so 代码作曲 could not run and mixing would hit the same wall. When a host CLI (every code/*.py script, plus modules/genmedia.py, transcription.py, footage.py and footage_library.py) runs on an interpreter other than the service's own, it now test-imports numpy, scipy.signal / fft / ndimage / optimize, soundfile, Pillow, PyYAML and Playwright and, if any fails, reruns itself on the service's interpreter (on desktop, the bundled runtime); before, only a missing Playwright in seven scripts triggered this. The check runs before anything else, including on macOS's own Python 3.9, and its result is cached per interpreter until a package or the OS version changes, so it costs nothing after the first run. Dispatched agents also get a folder with python3 / python launchers for the service's interpreter at the front of PATH; engines whose shell re-runs login profiles (~/.zprofile, ~/.zshrc, conda init) can still push another python3 ahead of it, which the check above covers. The desktop runtime build and the 演示视频 doctor now import the scipy submodules actually used (the build also checks soundfile and soxr), so a package like this fails the check instead of passing a bare import scipy.

  • codevideo.py doctor now accepts --project, as the demo-video workflow docs write it (2026-10-22).

    The 演示视频 workflow (agents/workflows/codevideo.yaml, the film-director's role notes and the task templates copied from them) runs the toolchain check as python3 code/codevideo.py doctor --project <slug>, but doctor was the one subcommand without --project, so the documented command always stopped with a usage error (exit 2) and the host_toolchain_verified check (doctor exits 0) could only pass if the agent dropped the argument. doctor now takes --project like the other subcommands and ignores it (the check does not look at a project); --no-gl and --json are unchanged.

  • Transition design no longer attaches a scene's closing transition line to a cut inside that same scene (2026-10-22).

    When the shot list splits one screenplay scene into two scene_ids (same scene_no), the group boundary between them got the scene's closing transition line as diagnosis.screenplay_hint — in a one-scene episode that is the episode-close line (e.g. cut to black) — and propose then wrote it into the reason of the main design and every alternative ("剧本 转场:…"), so the transition card read as if the script asked for that cut; a time word in that line (e.g. 三日后) could also be taken as this boundary's time word. The line is now attached only where the scene number changes (last group of a scene → first group of the next), the same rule screenplay_link already used. Reasons already written into existing transition designs and shot lists are left as they are (designs you or the transition-design agent already decided keep their text on the next propose; only the diagnosis is refreshed).

  • Demo videos: Chinese in the storyboard contact sheet no longer shows as boxes (2026-10-22).

    The labels above each frame of board_sheet.jpg (written by codevideo.py board, and the sheet from still --sheet) were drawn with Pillow's built-in bitmap font, which has no Chinese glyphs, so a shot description written in Chinese came out as a row of boxes. The labels now use the same font lookup as the whitebox subtitles and the animatic (a font in data/fonts/, then a system Chinese font such as Hiragino Sans GB / PingFang / Noto Sans CJK / Microsoft YaHei, then Pillow's default), and a label too wide for its frame is cut to fit by measured width and ends with …, instead of at a fixed 70 (sheet) or 40 (shot description) characters. The sheet is not part of the render fingerprint, so no section is re-rendered.

  • The interpreter fallback no longer swaps a working interpreter for the service's own when that one is worse (2026-10-22).

    When an agent ran a host CLI on an interpreter other than the service's, a single failed test-import was enough to rerun the command on the service's interpreter, without checking that interpreter. On the reporting Mac the service ran on conda base (whose scipy.signal cannot load on macOS 27); an agent that switched to a healthy environment missing only Playwright to run 代码作曲 through genmedia.py music --provider code was sent back to conda base, where the composer script failed. The fallback now test-imports on the service's interpreter too (cached the same way) and reruns only when the service's interpreter fails strictly fewer of the same imports, so an older desktop runtime that only lacks soundfile is still preferred over an interpreter that lacks everything; otherwise the command stays where it is and prints what each side failed to load. genmedia.py no longer counts a missing Playwright, since it never opens a browser; the code/*.py scripts still do.

  • Scene plates: Chinese entries in a location's architecture negatives no longer go into the English negative prompt (2026-10-22).

    The reverse-view plate and the lighting-variant plates (code/render_scene_plates.py) appended bible/scenes/<scene>/architecture.json#negative to the image model's negative prompt as-is, so a location whose negatives were written in Chinese mixed them into an otherwise English negative. The entries are now split: English (plain ASCII) entries stay in the negative prompt, and any other entry is written word for word into the prompt body as a Must not include: … sentence (before the empty-plate line). Lists and comma / semicolon / 、-separated strings are both split per entry. The prompt template and the style fragment (style_fragment_en) are unchanged.

  • Upscaling with the ffmpeg method can now overwrite the source clip in place (2026-10-22).

    The upscale role notes archive the original and then upscale onto the same path, but genmedia.py upscale --input <clip> --output <same clip> handed one file to ffmpeg as both input and output: written identically, ffmpeg refused (same as Input #0 - exiting) so no final version could be produced; written differently (a relative input and an absolute output, say), ffmpeg emptied the source before reading it, leaving a one-frame clip while reporting success. ffmpeg now writes to a hidden temporary file next to the output and it replaces the output only after ffmpeg succeeds with a non-empty file; on failure the original is left untouched and the temporary file is removed. The step that rescales other methods' results to the delivery size uses the same temporary-file handling, so two clips of one group no longer share a fixed temporary name. --dry-run notes when the output is the input file. The other upscale methods download the full result before saving and were not affected.

  • The 剧本预览 page no longer fails when an episode's chapter_range is written as an array or a string (2026-10-22).

    With a story/story_graph.json that has acts, an episode_plan.json entry whose chapter_range was a chapter-ID array (["ch001"]) or a string ("ch001-ch003") made the derived view crash ('list' object has no attribute 'get'), and because the derived view is built first, the preview request failed even when a valid script_breakdown.json existed (the storyboard and shot-list previews' script links went through the same code). The episode's chapter range is now read from {start, end} (the documented form), {from, to}, an array (first and last ID) or a string, falling back to the episode's chapters[], so the current act is also shown for plans that only list chapters[]. If building the derived view still fails, the preview shows the formal breakdown (or an empty derived view) with the error instead of failing; the page shows the array form as ch001 – ch003 instead of an empty –. Voice-form resolution and the episode plan's roadmap scope also accept the array form; the episode-planner's notes now spell out chapter_range: {start, end} as the form to write.

  • A ticked project skill whose files are gone no longer leaves the agent told it is both enabled and not selected (2026-10-22).

    When a skill ticked in 项目技能 was no longer installed (for example high-density-fight, removed on 2026-09-24, or a skill uploaded to an older desktop build and lost on upgrade), the console silently left it out of the list, so it could not be unticked, while the project setting still had it on: the agent found no SKILL.md, and skill_report.py answered skill is not selected for this agent/run. Such skills now show greyed out in 项目技能, marked 未安装; unticking one and saving removes it from the project settings (ticking a skill that is not installed is still refused). Only when an agent has such a skill, its skill contract gains one line telling it to work without the skill, not to look for its SKILL.md or report it with skill_report.py, and to say so in its receipt; skill_report.py now answers that the skill is not installed. The leftover high-density-fight registration and the fight-choreography skill's reference to it are removed.

  • Narration entries whose header carries extra fields are no longer reported as missing (2026-10-22).

    The generation-group check (check_generation_groups.py, narration_anchors_cover_all), the white-model reel's narration subtitles and the storyboard/shot preview's narration list each read narration.md with a pattern that required anchor: right after the ID and est_duration_s: right after a one-part anchor. Headers such as [N-01 | scene: S01 | scene_id: SCN-0001 | anchor: … | est_duration_s: 4.4 | …], or the four-part anchor the storyboard notes prescribe (anchor: S01 | SCN-… | ev… | …), were not read at all, so the check reported "narration.md 解析不到任何条目" for a complete file and the reel and preview showed no narration. All three now use the same parser as the script breakdown: fields after the ID in any order, extra metadata ignored, multi-part anchors kept whole; a field value may contain square brackets. Entries without est_duration_s are still not counted, and the check now names them ("条目头缺 est_duration_s") instead of reporting the whole file as unreadable. Entries that were already read come out unchanged; white-model reels of episodes that gain narration subtitles will show as needing a re-export.

v1.0.40

Added

  • New projects start by choosing a project type — 影视剧 (default), 音频配画, 数字人 or 混剪 — and the three former VideoAgents-Plugins workflows are merged into the main program (2026-10-20).

    The first step of the new-project wizard is now 项目类型, with icon cards and a short description of the selected type: 🎬 影视剧 (the novel-to-video workflow, unchanged and the default), 🎙️ 音频配画 (a narration recording plus its script gets AI-generated pictures cut to the audio's measured length; gates AVH1–AVH5), 🧑‍💼 数字人 (an audio master plus character photos becomes a lip-synced talking-head video; no sign-off gates) and ✂️ 混剪 (a script plus a local footage library, with optional narration audio, is illustrated with clips cut from the library; gates MH1–MH3); a fifth card, 🖥️ 演示视频, is described below. The last three were the audio-to-video / digital-human / mashup plugins and no longer need to be installed or enabled: the registry services/runtime/project_types.py keeps those names as type ids (film is the default), their members move into agents/15-audio-video, agents/16-mashup, agents/17-digital-human and agents/11-qa/{av-sync-qa,mashup-qa,digital-human-sync-qa} (built-in agents 94 → 113), and their DAGs and guides into agents/workflows/, which ships with the desktop app. The type is stored in settings.json#project ({type, footage_libraries}), written at creation and locked afterwards; existing projects without it are inferred from what the plugin era left behind (av/audio_map.json → 音频配画; audio_map / footage_source / beat_track under mashup/ → 混剪; digital-human/input_manifest.json → 数字人; otherwise 影视剧) and written back the next time project settings are saved. Project creation accepts project_type and footage_libraries (unknown type or missing library: 400) and defer_init. Non-film projects force off narration, dialogue voices, sound split, NPC staging, whitebox, long takes and intro / outro, use minimal transitions with no episode close and auto episode length — those switches serve the novel-to-video workflow or conflict with "the master is the final soundtrack, never re-encoded". The script goes to refs/text/transcript.txt (for 混剪 also novel/original.txt as the p0 input). With defer_init the wizard first uploads audio and photos into refs/ and then calls the new POST /api/v1/projects/<p>/init (one-shot; a second call returns 409). For the new types the initialization message lists the materials found and what is missing, and the orchestrator reports and waits for the user to say start before dispatching along the type's DAG; its run prompt gets a 项目类型 section (authoritative DAG, inputs, footage libraries, dedicated members, main-DAG nodes to skip, gates), and built-in members reused by a type are told that the work order and DAG notes override the main workflow's defaults. Wizard steps differ by type (影视剧 5; 音频配画 4, without 视频节奏; 数字人 / 混剪 3), fields follow the type (narration audio / audio master, character photos, a footage-library multi-select listing only fully segmented libraries), and film-only output settings are hidden. The console's left panel lists only shared members and the current type's team; the 参考文件 page gains a 讲述音频 category (refs/audio/, audio files only; first in non-film projects, hidden in film projects when empty); the workflow preview page gives av / mx / dh nodes and ga / gm gates their own lanes. On the 插件 page a package with one of these names is marked 已并入主程序, registers no members and has no enable / disable switch, and uploading one returns 400. agents/WORKFLOW.md gains §11 on project types; new tests/test_project_types.py. The three types' complete workflows have not yet been run end to end in their merged form.

  • New project type 演示视频 (code-video): a language model writes the film as a web page rendered frame by frame, with no video-generation model; 代码作曲 and 代码音效 are added as the first-listed, new-install default music and sound-effect providers (2026-10-21).

    Meant for product launches, explainers, data stories and brand shorts (30–60 s works best): the user gives a topic or script, a style and a few settings; the team writes codevideo/index.html, whose window.render(t) draws any frame (Canvas 2D / WebGL / three.js), and the host captures it in a headless browser (the desktop app's bundled Playwright Chromium) and encodes with ffmpeg. Narration uses TTS, music is composed in code and sound effects are synthesized at the frame of each on-screen event, so only TTS (and a music model, if chosen) costs money, and a change means editing code and re-rendering only the changed sections. Method and styles come from lemo-opuscar (github.com/lemomo-ai/lemo-opuscar, MIT), synced at a pinned commit by scripts/sync_lemo_opuscar.py: page library, audio tools, OFL font subsets, three.js r170 and 43 full styles; sample-instrument packs, HDRIs, textures and style demo sources download on demand. Host toolchain modules/codevideo/ + code/codevideo.py: codevideo/timeline.json is the single source of truth (section = scene = group) and is exported as a shot list and storyboard so the storyboard page, storyboard preview and post page show it as is; render fingerprints each section (shared inputs, its file and imports, frame range, size, library version) so editing one section re-renders only that one; board renders key frames with the real drawing code; master joins sections with two-pass loudnorm (−14 LUFS, true peak −1.2) into edit/ep01/final.mp4 with subtitles, poster and build.sh; check requires deterministic frames (the same moment captured in forward and reverse order must match), no external requests and no page errors, and checks timeline, clips, loudness and credits; a style's demo source is released only after TREATMENT.md is written. settings.json#project gains code_style (required), storyboard_review (default on), narration, subtitles, length_s, fps and music_source (code default | model | library). The DAG agents/workflows/codevideo.yaml (gates CVH1 storyboard, skipped when 先审故事板 is off, and CVH2 final) runs with a new team 18-code-video/{film-director,scene-coder,code-composer,sound-designer} + 11-qa/code-video-qa (118 built-in agents in 17 departments); the Reviser can edit codevideo objects. The wizard's fifth card has 4 steps (项目类型 → 基础信息, optional assets filed into reference categories → 风格 → 输出设置). The style step offers 完整风格(43) (9 categories, 2D / 3D marks, online demos) and 轻量风格 · OPUSCAR 98(98), the style prompts of lemo-opuscar's OPUSCAR 98 (o98-01…o98-98, filtered by 10 decade chapters), each paired with 3 related full styles as technique references; the prompt is invariant, the director completes the rest in TREATMENT.md, and the original film's characters, plot and title are off limits. New 🖥️ 代码预览 page (/preview/code; top-bar icon only in these projects) plays the film page from a sidecar static server on WEB_PORT+2 (another origin, CSP, sandboxed iframe) with scrubbing, section / shot / hit markers, the beat grid with BPM and line-arrow frame-step buttons; timestamped notes are assigned to section and shot and 📨 提交注释 sends them to the Reviser, and sections re-render as background jobs. In these projects the storyboard page keeps only notes, the post page allows only 音效与声音 recipes (picture recipes and clip editing return 400), and notes from the storyboard page and storyboard preview go to the code Reviser as timestamps. With music_source model or library, the music is beat-aligned: modules/codevideo/beats.py (numpy / scipy only) tracks tempo, beats and downbeats; codevideo.py music-fit (--track MUS-NNNN from the project music library, or --file) cuts a film-length piece starting on a downbeat (looping between downbeats with a 30 ms crossfade when short), writes a cue with its license to assets/audio/bgm/ep01/cue_sheet.json and reconciles the music library; snap moves section boundaries to downbeats and cuts and hits to beats without changing the length (new node cv2-beatsync, check stage beats). New genconfig music.code (auto_fetch on) and sfx.code are first-listed and the new-install defaults (sections already saved keep their provider); genmedia music|sfx --provider code --script <file> runs an agent-written script and writes .meta.json and .credits.json (sample attributions), free of charge. The 代码作曲 tab manages the five sample packs (auto-download switch, demo to audition); 代码音效 has an audition. New endpoints /api/v1/codevideo/packs… and /api/v1/codevideo/audio-demo/{kind}; the desktop runtime adds soundfile 0.13.1 and soxr 1.0.0; THIRD_PARTY_NOTICES.md credits lemo-opuscar, three.js, the fonts and OPUSCAR 98; guide agents/workflows/codevideo.md. A full agent run through the workflow and Windows rendering are not yet exercised.

  • 数字人 projects can use cloned voices instead of a recording (2026-10-20).

    Digital-human projects gain a 声音来源 setting (settings.json#project.voice_source): 原始录音 (recording, the default and the behaviour of existing projects: a recorded conversation is the master) or 克隆声音 (clone: with no recording, upload one voice sample per speaker and write a script, and each line is synthesized in that speaker's sampled voice before the digital humans are made). The 参考文件 page gains a 克隆声音 category (refs/voices/, mp3 / wav / flac / m4a / aac / ogg only), asking for one clean 5–30 s recording per speaker with only that person talking, and only your own voice or voices you are authorized to use. modules/genmedia.py tts gains --clone-from <sample>, which zero-shot clones the sample's timbre and ignores --voice / --character / voiceprint cards. Agentics (a Voice Clone model whose profile accepts reference_audios), ComfyUI (the Voice Clone workflow) and Volcengine Doubao Audio Generation 1.0 can clone; Volcengine Seed-TTS 2.0, OpenRouter, MiniMax and ElevenLabs fail with a message naming the channels that work instead of passing off a library voice as a clone (--dry-run prints the verdict and exits 2 when unsupported). New host CLI code/dialogue_clone.py: plan parses the script (one Speaker: line per line, Chinese or English colon, name optional with a single speaker; timestamps, blank lines and # comments dropped), matches speakers to samples (digital-human/voice_cast.json or --voice, automatic when a sample is named after its speaker), checks sample length (under 2 s blocked, outside 5–30 s a warning) and whether the channel can clone; synth synthesizes line by line (only new or changed lines are redone; --force redoes all), trims edge silence and joins the master assets/audio/master/<ep>.mp3 sample-accurately (0.4 s between lines, --gap), with the timed transcript digital-human/transcript_cloned.txt covering it contiguously and the ledger digital-human/voice_clone.json; nothing is joined while any line failed, and a master it did not produce (such as an uploaded recording) is never overwritten without --force; check runs voice_clone_ok. The new node dh0-voice (09-audio/voice-generation, condition voice_source_clone, skipped for 原始录音) precedes dh0-ingest, which then only verifies the master's fingerprint and treats the cloned transcript as the user's script, so dh0-transcribe is not dispatched. In the wizard, 克隆声音 replaces the audio-master upload with sample uploads into refs/voices/, makes the 台词稿 required, and shows a yellow note when the current TTS channel cannot clone. New tests/test_dialogue_clone.py. Cloned synthesis has not yet been run on a real channel.

  • Style library: all 916 visual styles of Flick's Flickpedia are merged in, growing the library from 152 to 1034 styles (2026-10-21).

    The library behind the console's 画面风格 picker (apps/web/static/styles/style_library.json) now includes every visual style of the Flickpedia gallery (https://flick.art/flickpedia/visual-styles). 34 existing styles matched one to one (by text first, then by comparing covers; styles that only share a broad genre were not merged) keep their name and category, take Flick's image as their cover (styleNNN.webp replaced in place) and get a rewritten prompt that combines Flick's description with what still held in the old one, cleaned of English prefixes, trailing 「,2D / ,3D」, work titles and content descriptions. 882 new styles (style153–style1034.webp, appended in Flick's "Recommended" order) get a Chinese name, a category and a one-paragraph Chinese prompt in the library's existing manner, describing rendering only (technique, colour, light, texture, compositional tendency, mood) with no characters, work titles, AI terms, parameters or aspect ratios; where Flick's description did not match its image (about 80 styles) or was missing (65), the prompt and the category follow the image. The three categories are kept: 2D 597 / 真人 348 / 3D 89. Covers are 640 px wide WebP (quality 78, about 30 KB on average) and ship with the repository. All 11 translations (styles/i18n/<lang>.json) cover the new entries and rewritten prompts, and Arabic's duplicate names for s042 / s049 are fixed. Entries taken from Flick carry "source": "flick.art" and the original Flick name in source_name, and THIRD_PARTY_NOTICES.md records the source and states that these images and descriptions remain subject to Flick's terms. apps/web/check.py now expects 1034 covers and 1034 entries, each with its cover present. Because /static files are served without Cache-Control, browsers kept the old library, translations and same-name covers even after a hard refresh; index.html now appends STYLE_LIB_V (20261021) to every library, translation and cover URL (the Canvas style picker uses the same value), and it must be bumped whenever anything under styles/ changes.

  • World models can now be generated on your own GPU: a new HunyuanWorld channel backed by a remote worker (2026-10-21).

    The 🌍 世界模型 card on the 生成模型 page gains a HunyuanWorld tab next to World Labs, made active with the card's 生效渠道 dropdown (genconfig.json#world.provider: marble, the default, or hunyuan). It drives a small worker service from a separate repository, [AgenticsWorld/VideoAgents-hunyuan-rpc](https://github.com/AgenticsWorld/VideoAgents-hunyuan-rpc), which you deploy on any reachable Windows or Linux host with a 24 GB card (verified on an RTX 3090; its README is the deployment guide). The scene preview's 「生成世界模型」 button then builds the world on that host in about 20–40 minutes instead of spending Marble credits: HunyuanWorld-1.0 layered 3D reconstruction of the scene anchor panorama (bf16 with sequential offload), a bounded multi-view render around the panorama camera (32 views at 1600×900 by default) and 3DGS training with gsplat (MCMC, up to 1 M Gaussians, 8000 steps by default, with scale regularization against needle-like Gaussians at depth edges). Output lands in the same contract as Marble — world/worlds/<key>/world.json (provider: "hunyuan", model: hunyuanworld-1.0+gsplat) plus splats_*.spz, collider.glb and preview images, in the Marble coordinate convention with the same scale calibration (camera_height / fitted ground offset) — so the fullscreen viewer, alignment tweaks, default-world selection, the Director's console world view and the 「世界模型」 shot-plate mode work unchanged. The tab takes the worker address and token (world.hunyuan.base_url / token, sent as X-Worker-Token; HUNYUAN_WORKER_URL / HUNYUAN_WORKER_TOKEN override them) and optional advanced values views / gs_steps / fp8 (empty = 32 / 8000 / off; FP8 is off by default and not recommended on Windows); 验证 (POST /api/v1/providers/hunyuan/test) calls the worker's GET /health and shows the GPU, busy / idle state and free disk, or the self-checks that failed. The CLI code/hunyuan_world.py mirrors the Marble one, plus --resume <job_id> for interrupted downloads, --check and --smoke (4 views / 200 steps) to verify a fresh deployment end to end. The worker runs one job at a time (409 while busy), resumes from stage markers and deletes jobs after 7 days. Limits: sharpness is capped by the training-view resolution; the world only covers the panorama's envelope and degrades away from the camera position; splats are DC-only, so highlights are weaker than Marble's; the first run downloads about 60 GB of weights. All 11 UI dictionaries are covered. HunyuanWorld-1.0 weights are under Tencent's community license (not valid in the EU, UK and South Korea; products above 1 M MAU need a commercial license from Tencent); this repository ships only the client, never the weights. Feature doc: docs/hunyuan_world.md. (#145)

  • Canvas: a new 动图 tab turns the version being viewed into a video, from a reference image or from first / last frames, and a new on-demand station 「画板动图师」 only writes the prompt while the host renders the video (2026-10-21).

    A 动图 tab to the right of 信息 in the Canvas side panel generates a short video from the version being viewed (an edited version works too); results go only into that image's list 本图的动图 and never touch 设为最终版 or production. 生成方式: 参考图 (default) submits in multi-reference mode — the viewed version is @Image 1, and 参考图 2 can add another version as @Image 2 — which is what reference workflows such as ComfyUI's MiniMax-H3 Ref2VA accept; 首尾帧 uses the viewed version as the first frame and another version picked under 尾帧 as the last, and needs a model that supports first / last frames (Seedance on Volcano Engine / BytePlus, Kling on Fal, an H3 FL2VA workflow in ComfyUI). With ComfyUI as the active video provider the request is pre-checked by workflow type (motion_channel_problem: H3 Ref2VA and the Seedance cloud reference templates take reference images only, LTX-2.5 both, the rest first / last frames only) and rejected with a 400 at once instead of failing minutes later. The provider / model dropdowns share the storyboard preview's episode-level list (default 跟随全局, remembered in the browser, global settings untouched; the server accepts only configured providers and their listed or saved models). Length 2–30 s (default 15), resolution 480p / 720p / 1080p (default 480p), sound on by default; the aspect ratio is the common ratio nearest the image. genmedia.generate_video gains explicit provider / model (no episode or group override) and resolution_gate=False, so the resolution picked on the page is not pushed down to draft grade. 🎲 让语言模型看图写一段 has the agent write a prompt into the box, taking any text already there as the idea. 直接发给视频模型,不经 Agent 整理 (on by default) sends the prompt as written, prefixing an English reference-image sentence in 参考图 mode when it names no @Image N; 发送前先看提示词 holds the job for editing and confirmation; unticked, 「画板动图师」 (agents/08-video-gen/canvas-motion, Smart Assignment low tier) looks at the image(s) and submits one prompt with code/canvas_edit.py motion within seconds, and the host renders the video in a subprocess after the agent's turn ends (90-minute timeout). With the toolbar 🔞 active, the NSFW fallback video provider and language model are used and 直接发给视频模型 is ticked automatically. Videos are saved as assets/canvas/<canvas folder>/motion/mNNN.mp4 with a poster frame, ffprobe-measured specs and the exact prompt sent in history.json motions[]; the list plays them in the page, shows and copies that prompt, and offers 用这段提示词, download and delete (moved to trash/). Each image now has three independent job lanes (edit / upscale, video, prompt writing), so editing continues while a video renders. New endpoints POST /api/v1/projects/<p>/canvas/motion and …/canvas/motion/delete; spec in docs/image_canvas.md.

  • Canvas edit: 「🎨 选择风格库」 tries a style from the style library on one image without changing the project's visual style (2026-10-21).

    A 🎨 选择风格库 button to the right of the 整体说明 heading on the Canvas 编辑 tab opens the same style library as the console's 画面风格 picker (style_library.json plus the interface language's translations, 1034 styles; 全部 / 真人 / 3D / 2D filters, search, 🔍 zoom). Picking a style writes 「整体改成「<style name>」画面风格(只换画风,构图、人物、物件和位置保持不变):<style prompt>」 into the overall instruction (in the interface language); picking another replaces that sentence rather than adding a second one, and a hint under the box says this is a style test for this edit only. The request carries style: {id, name} only while that sentence is still in the overall instruction, so deleting it sends a normal edit. With a style, compose_prompt in modules/image_canvas.py no longer opens by asking to keep the lighting, colour and style of [Image 1], which would cancel the change; it asks to keep the framing, composition, camera angle, subjects and their positions and to change the visual style as instructed. The retouch work order gains a 「风格测试」 line, and the 「画板修图师」 SOUL has the agent carry over only the medium, brushwork, palette and lighting of the style description, not its composition, shot size or lens. Jobs, versions and 改用整图 copies record style, and the history marks such versions 「🎨 <style name>」. The Canvas page keeps its own STYLE_LIB_V, which a test holds equal to index.html's. 5 new strings in all 11 dictionaries.

  • Reference files: images on the 参考文件 page open in Canvas (2026-10-20).

    PNG / JPG / WebP files on the 参考文件 page get a 🖌 画板 button to the right of the file name (GIF / BMP / TIFF get none, since Canvas only takes .png / .jpg / .jpeg / .webp), so an uploaded reference image can be edited, upscaled, adjusted, style-tested or animated like any production image. modules/image_canvas.locate treats every path under refs/ as the new kind ref with fixed-path storage, whatever kind the page passes: 设为最终版 writes the version back to the original path, so agents read the adopted image from then on, and the file's note (refs/annotations.json / refs/NOTES.md, keyed by path) stays attached. A reference has no setting card, so the option to send the change to the reviser is not offered. When returning from Canvas with 「<」 the reference list is fetched again (cache: 'no-store', and again on a pageshow restored from the back-forward cache), so the thumbnail shows the adopted version. Canvas labels the kind 参考图.

  • Canvas: a 🔞 NSFW toggle in the toolbar routes an image's edits to the NSFW fallback models, and turning it on ticks 「直接发给图像模型」 so edits skip the retouch agent (2026-10-21).

    In NSFW mode a Canvas edit refused by the language model never moved to a fallback model: the refusal was often not a stock phrase, Canvas work orders carry no task_id and every re-send is a new job, so the rule "re-dispatch the same task_id on the fallback language model" could not apply. Canvas now has an explicit switch instead. A 🔞 NSFW button at the right end of the toolbar, shown only while NSFW mode is on, turns yellow when on; its state is remembered per image in the browser (localStorage canvas_nsfw_<project>|<file>). While it is on, edits and 重绘放大 of that image follow NSFW routing: with a fallback image provider configured the server replaces the job's provider / model with it (the edit and upscale model dropdowns grey out and name it, and the upscale size cap follows it), otherwise the selected model is used; an agent dispatch carries nsfw, so 「画板修图师」 runs on the fallback language model (the current engine when none is set) and its work order gains an nsfw: true line; every image subprocess, the agent's as well as the host's direct, confirmed and upscale runs, gets VIDEOAGENTS_NSFW=1, and genmedia records nsfw_route. Jobs and versions (including 改用整图 copies) record nsfw: true and are marked 🔞 in the job box and the history. 保真超分 is unaffected, and a request carrying nsfw while NSFW mode is off returns 400. Whenever 🔞 becomes active — switched on, remembered as on when the page loads, or NSFW mode turned on in the console before returning to the page — the edit tab's direct-send checkbox, renamed from 直接发送 to 「直接发给图像模型,不经 Agent 整理(快,适合小改)」, is ticked automatically, so the host fills the fixed English template and no language model is involved; the send hint says it was ticked automatically, and unticking it sends through the agent on the fallback language model. When 🔞 becomes inactive only that automatic tick is undone; a tick changed by hand is left as it is. The Canvas state now includes nsfw_mode {enabled, text, image, video}.

  • NSFW mode: refusals worded in plain language can be judged by a moderation model, TypeSafe's Jev decision model on OpenRouter (2026-10-21).

    In NSFW mode a language-model refusal is not switched in-process: the host marks the run nsfw_suspect and the next dispatch of the same task_id uses the fallback language model. That mark relied on stock refusal phrases only (_LLM_REFUSAL_RE), so a refusal in natural wording (「这单我没有提交……我不做这种编辑」) was missed and the fallback never kicked in. A new cost switch, 拒绝判定用审核模型, in the NSFW mode dialog (设置 → 高级 → 「NSFW 模式」; state.json nsfw.judge, default off) adds a second stage. A stock-phrase hit still counts directly; otherwise a zero-cost pre-screen selects only runs that look like "ended without a deliverable" — the run finished normally, its reply is non-empty and under 4,000 characters, and either a Canvas agent's job is still waiting for its prompt or the work order has a task_id and the run wrote no files — and only those have their final reply judged once. modules/refusal_judge.py calls OpenRouter's Decisions API (POST https://openrouter.ai/api/alpha/decisions) with only the last 1,500 characters of the reply (never the original request or images) and one choice question over policy / missing_input / clarification / done / other; only policy counts as a refusal. A failed call is only recorded and the run falls back to phrase matching, and runs already on the fallback model are not marked. nsfw_suspect records by: regex|judge, and the run record carries refusal_judge (verdict, confidence, probabilities, snapshot, cost). A refused Canvas job is marked failed (refused: regex|judge) with a yellow hint to turn on 🔞 in the toolbar and send again, or to configure a fallback language model first; it is never re-sent automatically, and the page's polling waits while the judgment runs. The model is set on a new 🛡 审核模型 card on the 🎨 生成模型 page, stored in the moderation section of the generation config: a single OpenRouter tab with an API key (falling back to OPENROUTER_API_KEY) and the model, typesafe/jev-1.13 (default) or typesafe/jev-1.14. 测试 (POST /api/v1/providers/moderation/test) judges a naturally worded refusal sample once (billed, about $0.00003) and shows the verdict, confidence, snapshot name, cost and latency. The switch cannot be turned on while the card has no key (POST /config/nsfw returns 400), and the dialog's status line names the model in use. All 🔞 controls and markers on the Canvas page (toolbar toggle, job and version markers, the 🔞 suggestion in refusal hints) now appear only while NSFW mode is on, as the storyboard preview's group-card 🔞 already did, and both pages re-read GET /config/nsfw when brought back to the foreground, so switching the mode needs no manual refresh. New cases in tests/test_nsfw_mode.py and tests/test_image_canvas.py.

  • New execution engine: Antigravity CLI (antigravity, command agy) (2026-10-20).

    Google's Antigravity CLI can now run agents: it appears as Antigravity CLI in the top-bar and per-agent engine menus, the executable can be overridden with AGY_BIN, and Smart Assignment gets a smart_antigravity strategy (high tier gemini-4-argon-high, low tier gemini-3.8-flash-medium). Dispatch runs agy --output-format stream-json --dangerously-skip-permissions --print-timeout 0 [--add-dir <data dir>] [--model] [--effort] [--conversation <id>] -p <prompt>: the skip-permissions flag is required (without it, shell commands are soft-refused in print mode while the exit code stays 0); as with codex / kimi / opencode the role brief goes into the first turn's prompt and later turns resume with --conversation; the host run timeout replaces a turn cap; thinking depth maps to --effort. handle_antigravity_event parses init / step_update / result events and maps tool names (write_to_file → Write, replace_file_content → Edit, view_file → Read, run_command → Bash, grep_search → Grep, find_by_name → Glob and so on) and PascalCase parameters to the Claude vocabulary, so output files, activity and the prompt-skill read check work as usual; only result.status=SUCCESS counts as success. When --conversation names a session that no longer exists, agy silently starts a new one with exit code 0, which would leave a resumed turn working without its role: the host compares the init conversation id with the one it resumed, kills the process before any tool call and reruns once with the full role brief. Token usage comes from the cumulative result usage, or is summed per call when a log has no final event. GET /api/v1/engines/antigravity/models parses agy models (30 s cache; six static Gemini entries as fallback). 一键安装 runs the official antigravity.google/cli/install.sh (install.ps1 on Windows; installs to ~/.local/bin/agy or %LOCALAPPDATA%\agy\bin, both of which the desktop app searches); agy has no login subcommand, so 登录 opens a terminal running agy with a hint to sign in with a Google account and then type /exit; sign-in is probed with agy models (no quota used), and a set GEMINI_API_KEY counts as signed in. The footage library's AI shot analysis does not support this engine. Not yet exercised in a real dispatch: the write / edit tools' parameter names are inferred, and if they differ the output-file list stays empty (activity is still logged).

  • Sound effects get their own model setting on the 生成模型 page: Openverse search, ElevenLabs or Fal (2026-10-20).

    A new 🔊 音效模型 card (config section genconfig.sfx) sets the channel that genmedia.py sfx uses for sound-effect and ambience patches, instead of it following whichever key happens to be configured. Openverse does not generate: it searches the CC-licensed and public-domain audio indexed by Openverse (https://api.openverse.org/v1/) with 2–3 English keywords and downloads a ready-made file. Its tab sets the license range (CC0 / public domain only by default, optionally plus CC BY, which requires attribution; CC BY-SA, non-commercial and no-derivatives licenses are never accepted), the sources (Freesound by default, optionally plus Wikimedia Commons) and an optional Client ID / Client Secret (empty = anonymous access, 20 requests a minute and 200 a day; the token is cached in data/.videoagents/openverse_token.json), and offers a 搜索测试 (POST /api/v1/providers/openverse/search) that plays results in the page with duration, license, source and author, tags CC BY items 「须署名」 and shows the remaining quota. The ElevenLabs and Fal tabs generate with ElevenLabs Sound Effects v2 (eleven_text_to_sound_v2 / fal-ai/elevenlabs/sound-effects/v2); an empty API key falls back to the ElevenLabs key of the music / TTS sections or the Fal key of the image / video sections. In genmedia.py sfx, --provider overrides the card per call and the new --query gives the Openverse keywords (extracted from --prompt when omitted, dropping words step by step when nothing matches). Openverse picks material by length (clips of up to 15 s first for one-shots; a loop bed must cover --duration plus the crossfade), skips broken, oversized or unusable files, and --count takes different items; one-shots are trimmed of edge silence, peak-normalized and cut to --duration with a 30 ms fade, and --loop crossfades the overhang back onto the start for a seamless loop. The .meta.json records the license, attribution_required and the author, page and attribution text. Specs: the sound-effect and ambience stations change keywords and retry when Openverse finds nothing, then report it rather than switching to a paid channel on their own; the copyright reviewer checks patch provenance when an episode has patches, and a CC BY item whose attribution is missing from the end credits is a major defect. Behaviour change: an ElevenLabs key alone no longer routes sound effects to ElevenLabs — select ElevenLabs in the card's 生效渠道 dropdown to keep that. All 11 UI dictionaries are updated.

  • ComfyUI local templates for Qwen Image 2.1 bf16, text-to-image and multi-reference image-to-image, that need no custom nodes; the Qwen Image 2.1 GGUF templates are now tested locally (2026-10-20).

    New comfy/image-qwen-image-2.1-bf16-api.json (text-to-image) and comfy/image-qwen-image-2.1-bf16-ref10-api.json (image-to-image, 1–10 reference images), each with a Chinese / English description, listed automatically in the 「文生图API」 / 「图生图API」 dropdowns on the 生成模型 page. They are the GGUF templates from 1.0.39 with node 1 replaced by the core UNETLoader (qwen_image_2.1_bf16.safetensors) and their own output prefixes (videoagents/qwen-image-2.1-bf16, videoagents/qwen-image-2.1-bf16-ref); sampling, sizes, negative handling and positional reference binding are unchanged, so they run on a stock ComfyUI 0.37.0 or later without ComfyUI-GGUF. The model files come from Comfy-Org/Qwen-Image-2.1: diffusion model 14.2 GB, text encoder qwen3vl_8b_bf16 17.5 GB, VAE 0.7 GB (the smaller int8_convrot files, 7.3 / 9.4 GB, are an untested alternative). Comfy Cloud cannot run them as is: the node graph is supported, but its shared model catalog lacks qwen_image_2.1_bf16 and qwen_image_2.1_vae_bf16, so a submission fails validation unless the account has both files. The GGUF description's "Without the GGUF node" section now links to these templates. Tested on Windows with ComfyUI 0.42.1 and an RTX 4080 SUPER with 32 GB of system RAM: bf16 text-to-image took about 105 s for 1024×1024 including the first model load and about 20 s for 1280×720; the two GGUF templates, run with the same prompts and seeds, took about 93 s and 23 s for text-to-image and about 38 s for image-to-image with two references, with nearly identical images. The bf16 image-to-image template produced a correct image but took about 750 s, and ComfyUI stopped answering HTTP for minutes at a time while it ran, so genmedia.py gave up polling and reported a failure although the job finished — on such a card, prefer the Q8_0 GGUF companion for reference images. 3–10 references were not run. The model is under the Qwen Research License, non-commercial use only.

  • Project picker: the top-bar project dropdown becomes a dialog with new / import, two sort orders, folded backups and per-project status (2026-10-21).

    Clicking the project name in the top bar now opens a 项目 dialog with + 新建项目… and › 导入项目… (the existing wizard and zip import), a 按项目名称 / 按最新更新 switch (remembered in the browser) and 10 projects per page. Columns: 项目, 类型 (icon and name from the new-project wizard's type cards), 自动 (🤖 when auto-run is on), 运行 (running agents, plus the number queued), 备份 and 磁盘. Backups are no longer listed on their own: a project named <source>-YYYYMMDD, with an optional suffix (the default name of a copied project, including -1 or a hand-edited suffix), is folded under its source while that source exists; the 备份 column shows the count, with a green dot when one of them is running, and clicking it expands the backups, which open like any project. A backup whose source was deleted is listed normally. Sorting by latest update ranks a source by the newest update across its whole group. The current project is highlighted and the dialog opens on its page (expanding its source when it is a backup); running-state and auto-run changes refresh the dialog live; Esc or a click on the backdrop closes it, and rows can be reached with Tab and opened with Enter. Two new endpoints back it: GET /api/v1/projects/overview (type from settings.json#project.type, inferred from outputs for older projects; auto-run switch; running / queued runs; latest update, from the project folder's first- and second-level entries and run times; backup_of) and GET /api/v1/projects/disk?names=… (folder size without following symlinks, up to 60 projects per request, cached for 10 minutes, fetched only for the rows on the current page because scanning every project is slow). The 版本管理 page drops its own project dropdown and shows the current project's name in its header. The new strings are in all 11 UI dictionaries.

Changed

  • 生成模型 page: tabs are only for viewing and editing, and each card's active provider is chosen in a 「生效渠道」 dropdown in its title bar (2026-10-21).

    Until now the selected tab was the active provider, so changing, say, a ComfyUI workflow while another provider was in use meant switching to the ComfyUI tab, editing and switching back. All ten cards — 图像 / 视频 / TTS / 音乐 / 音效 / 数字人 / 超分 / 世界模型 / 语言模型 / 审核模型 — now have a 生效渠道 dropdown at the right of the title bar: clicking a tab only changes which provider's panel is shown, ✅ always marks the active provider's tab, changing the dropdown also switches to that provider's tab, and the provider written to the config always comes from the dropdown. Providers hidden in the UI (MiniMax for image and video) stay hidden in both the tabs and the dropdown unless they are the active one. The explanatory text in the card title bars is removed (the dropdown's tooltip explains the split), and the TTS card now sits above the music card. One shared controller in apps/web/static/models.html replaces each card's own tab logic; docs and specs that said the provider is picked by its tab (docs/hunyuan_world.md, docs/worldlabs_world.md, agents/WORKFLOW.md, the upscale SOUL) now refer to 生效渠道. The 文件托管 settings page still activates a provider by its tab. All 11 UI dictionaries are updated.

  • 生成模型 page saves automatically, and the 「保存设置」 button is gone (2026-10-21).

    Every change on the page saves the whole configuration with the request the button used (POST /api/v1/config/generation): text, password and number fields when they lose focus or on Enter, dropdowns (including 生效渠道) and checkboxes as soon as they change. Values set by buttons are saved too — Fal 「选用」 (its hint now reads 「已填入 {id}」), adding or removing a RunningHub workflow with 「验证并添加」 / 「移除」, and the model list refreshed by the deepagents 「测试连接」 — while search and test inputs (Fal search, the Openverse 搜索测试, the pending RunningHub ID and note) never trigger a save, and choosing 「自定义…」 waits until the custom field is filled in. Nothing is sent when the content equals what was last saved, so opening the page writes nothing; saves run one at a time and a change made during a save is saved right after it; requests use keepalive, so clicking a link such as 「控制台」 right after typing still saves, and the field being edited is saved when the page is hidden or closed. The save status moved to the top bar (「修改即自动保存」, 「保存中…」, 「✓ 已自动保存」, 「✗ 保存失败」), with the RunningHub cache sync result appended as before. As with the button, each save syncs the selected RunningHub workflows' cache, now more often; after changing a template on RunningHub, run 「验证并添加」 on that workflow again (or change any setting) to refresh it, and the RunningHub skill says so. The AgenticsLLM check before saving now matches the server: only categories newly switched to agentics are checked (the whole config used to be checked, and since the browser version's TTS and deepagents defaults are agentics, every save there was blocked with 「仅支持桌面客户端」); when the check fails, those categories go back to their saved provider with the reason shown in the top bar, and the other changes are saved. All 11 UI dictionaries are updated.

  • Smart Assignment: creative members of the new project types default to the high tier, every 11-qa review agent — including each type's final review — to the low tier (2026-10-21).

    Under a smart strategy (智能分配) an agent's default model comes from its tier. AM_CATEGORY_TIERS in services/runtime/core.py now marks 15-audio-video, 16-mashup, 17-digital-human and 18-code-video as high, so all 20 creative members of 音频配画 / 混剪 / 数字人 / 演示视频 get the high-tier model (opus under smart_claude); previously these categories were unlisted and fell to low, apart from two high exceptions, 15-audio-video/era-researcher and 15-audio-video/visual-scripter, which are now covered by the category. 11-qa stays low as a whole with no exceptions, so all 12 review agents, including the final reviews av-sync-qa, mashup-qa, digital-human-sync-qa and code-video-qa, get the low-tier model (sonnet under smart_claude). A model set by hand for an agent still takes precedence. A new case in tests/test_project_types.py checks that every project-type member outside 11-qa is high and all of 11-qa low.

  • First install: the default execution engine follows the UI language — Kimi Code for Chinese, Claude Code otherwise (2026-10-21).

    When neither the browser's saved preferences nor the server's ui-prefs hold an engine (a first install), restorePrefs in apps/web/static/index.html picks the top-bar engine from the UI language: Chinese → kimicode, any other language → claudecode. The existing logic then takes over: the language model shows 智能分配, the assignment strategy is aligned to smart_kimi (K3 / K2.7 Coding), the choice is saved to the server copy, and the startup CLI check looks for kimi and, if it is missing or not signed in, opens the one-click install / sign-in dialog for Kimi Code rather than Claude Code. Installations that already saved an engine are unaffected, and changing the UI language later does not change the engine. In the desktop app, initializeFirstLoginDefaults (apps/desktop/src/main.ts) no longer seeds ui_prefs / global_model (hard-coded to claude) into the first-login state.json, so the desktop first run behaves like the web one; the file is still written only when it does not exist.

  • Agentics' default language model is now deepseek/deepseek-v4.1-flash-1017 (2026-10-20).

    On 生成模型 › 🧩 语言模型/DeepAgents › Agentics, the default model (DEFAULT_GENCONFIG deepagents.agentics.model in services/runtime/core.py) changes from z-ai/glm-5.3 to deepseek/deepseek-v4.1-flash-1017. Saved configurations that never picked an Agentics model (empty value) fall back to the new default; an explicitly chosen model is kept.

  • English line-duration estimates are calibrated on 63 real syntheses: the default word rate goes from 130 to 160 wpm, narration estimates are computed by the host, and the post-render dialogue check merges inter-sentence pauses (2026-10-20).

    The 130 wpm default introduced in 1.0.39 was a deliberately conservative guess. The new tool code/calibrate_time_cost.py collects English lines (corpus), synthesizes each with the character's frozen voice at natural speed and measures it after the dialogue voice library's silence trimming (synth), and solves the default rate under the time scale's current structure, 0.7 s onset + words ÷ rate + 0.4 s × in-line pauses (fit). The 63-line corpus (41 lines from two English-language projects, 22 written to cover numbers, times, initialisms, abbreviations, long words, fragments, dashes and shouting) was synthesized with Volcengine seed-audio-1.0; corpus, durations and fit report are in docs/time_cost_calibration/. Without a voice-card rate, 130 wpm estimated about 16 % too long (median actual / estimate 0.86, mean error 1.42 s); 160 wpm gives 0.99 and 1.09 s. DEFAULT_WPM in modules/time_cost.py is now 160, so cards with only a Chinese-style speed_cpm (converted at × 160/252) also get faster; the speed_wpm path was already unbiased and is untouched. Counting syllables or splitting pause types did not beat counting words (long polysyllabic words are spoken fast), so both remain diagnostics only. Narration: the new code/narration_est.py sets each narration.md entry's est_duration_s to the time-scale line_est (medium) × a project factor — the 80th percentile of measured / line_est over the project's other episodes' unretimed narrator segments (at least 5, clamped to 0.8–1.5), or 1.15 with no history; across 484 segments in 16 Chinese projects, hand-written estimates ran 1.72 s long on average, while an unbiased estimate would overflow the downstream window in 44 % of segments; the factor brings that to 10 % in a per-episode holdout. --write rewrites only the numbers in entry headers; check-only mode is the new machine check narration_est_consistent (tolerance max(0.25 s, 10 %)) on p5-narration, and the narration station no longer estimates by hand. code/check_dialogue_audible.py now merges voiced runs separated by ≤ 0.8 s (--merge-gap, was 0.5 s): English lines are often several short sentences, and in one English episode 4 false FAILs among 7 dialogue shots became 0 FAIL / 3 WARN, while a genuinely dropped line elsewhere still fails. modules/live_stream.py estimates word-counted lines with the time scale (its old 14 characters per second ran about 38 % short). Chinese and other non-English lines are unchanged. Stored estimates in existing English projects are not rewritten; check_dialogue_fit --write-est refreshes them if line_est_consistent reports a mismatch.

  • 版本管理: 「复制项目」 now returns to the source project's console instead of switching to the copy (2026-10-21).

    When a copy started from 📋 复制项目 on the 🧬 版本管理 page finishes, the saved top-bar project (webui_prefs.project) and the redirect both point to the project that was copied (/?project=<source>, landing on the orchestrator as before), not to the new one; a copy that was still running when the page was reloaded behaves the same when it completes. The card now says the new project can be opened from the top bar's project picker, and the completion notice reads 「✓ 复制完成,正在返回当前项目控制台…」 (both strings re-keyed and retranslated in the 11 UI languages).

  • README rewritten, with screenshots and a 30-second intro video (2026-10-12).

    README.md and README.zh-CN.md are restructured: a one-line pitch with navigation and badges, a Storyboard preview hero image, a 30-second silent intro video, a three-step overview, the supported execution engines and providers as listed on the settings pages, who it is for, features, a nine-screenshot tour, the problems it solves, a quickstart (desktop download or running from source), related repositories (plugins, skills, samples) and community links. Figures were corrected against the code (now 118 agents in 17 departments, up to 12 human sign-off gates, 12 UI languages). Desktop development, packaging, release and integration notes moved to docs/DEVELOPMENT.md / docs/DEVELOPMENT.zh-CN.md; the images and the video master are in docs/assets/readme/.

Fixed

  • Untranslated Chinese in English and other non-Chinese interfaces: Director's console, post-production, storyboard preview and input placeholders (2026-10-12).

    On the 导演台 (Director's console), the object categories in the 「对象」 dropdown and in note entries, and the station names in the 「发给」 dropdown, come from the server as Chinese constants and were joined into the option text before the dictionary lookup, so they never matched; they are now translated first and joined afterwards. Version labels the host writes in a fixed form (「提交前基线」, 「批次 B-NNNN」) now appear in the UI language in the version dropdown, the version list and the viewport version badge. On 后期处理, the group filters 「有草稿 / 有缺陷 / 未动过」 gained dictionary entries, and the A / B monitor badges use the existing 「母本 v0」 entry (so the Chinese badge now reads 「母本 v0」, matching the version dropdown). On the storyboard preview (分镜预览), the placeholders of the transition title card's three line inputs were assembled at run time from 「第」 + number + 「行」 and are now one parameterized entry, 「第 {n} 行」. In the dictionary runtime (apps/web/static/i18n/i18n.js), data-no-i18n on an element itself also blocked its placeholder, so the placeholders of the note boxes on the script and storyboard pages and of the NSFW custom-model input stayed Chinese although the dictionaries had translations; now only an ancestor's marker blocks the placeholder, while the element's own marker still protects the value the user typed (title / alt follow the old rule). Five entries were added to each of the 11 dictionaries.

v1.0.39

Added

  • Canvas (画板, /preview/canvas), phase 1: circle-select editing, upscaling and crop / flip / rotate / scale of an existing image, with every version kept and one set as final (2026-10-10).

    A more precise way to change an image than ✏️ 修改: circle the places to change, write what to change, attach reference images the text can name with @图N, and pick the image provider and model for this page only. A 🖌 画板 button now follows ✏️ 修改 on scene images and shot plates (scene preview page), character and costume images, creature and prop images and storyboard sketches, and sits under the cover on 成片发布. The rule: edits, upscales and adjustments only enter that image's own history (assets/canvas/<flattened original path>.<sha8>/, which stations neither read nor write), and production is touched only by 设为最终版. An edit is sent in one of three ways: through an agent (default) — the new on-demand station 「画板修图师」 (agents/06-art/image-retouch, Smart Assignment low tier) looks at the image, writes the selections and the note into one English prompt and submits it with code/canvas_edit.py submit; 直接发送 — the host fills a fixed English template with the text and generates without an agent; 发送前先看提示词 — the job waits, the prompt can be edited on the page, and the host generates once it is confirmed. 圈外锁定 (on by default when there is a selection) pastes only the selected area, expanded and feathered, back onto the base image; when the mean difference outside it exceeds 0.06 the version is tagged 「圈外漂移」, and 改用整图 saves the model's whole image as another version. Upscaling is run by the host without an agent, as 重绘放大 (an image model redraws from the base image as its only reference, with a fixed prompt) or 保真超分 (local Lanczos interpolation, or Fal's SeedVR2 / Topaz Precision / Real-ESRGAN / AuraSR / Bria upscalers); a model result that is not larger than the base counts as failed and is not padded by interpolation. Crop to selection, flips, rotate and scale are recorded as versions too. 设为最终版 lands by kind of image: a fixed-path image (character, costume, creature, prop, scene image, cover) has the version written back to its original path; a storyboard sketch also has its ledger entry set to mode=canvas; a shot plate keeps its original image and library entry, the version is registered as <key>_revN, and the shots that used the original (or an earlier canvas version) are repointed and their group prompts synced — registering without replacing is possible, and choosing the original version points the shots back. Before applying, the page lists the impact: which shots or group prompts use the image, and what follows (videos already generated do not change; the portrait library needs re-registering; the animatic needs re-rendering; the cover inside a publish package is a copy). Nothing downstream is re-run — a change record runs/revisions/canvas-*.json is written when the image is in use and the version really changed — and ticking 「把这次改动发给修改师」 has the reviser update the setting text only. Because the canvas flips images, the ⇋ 翻转 button under each shot plate on the scene preview page is removed (the backend flip endpoint stays, with no page calling it). Spec: docs/image_canvas.md. Not exercised for real: no image was generated, so how well models follow the circled image, the 0.06 drift threshold, redraw-upscale sizes and the five Fal upscalers are untested; the retouch agent was never dispatched and the desktop app was not tried. Not done: true mask inpainting, outpainting, multiple candidates, entry buttons on the storyboard preview page (分镜预览) and on panoramas; a registered _revN carries no master / grid9 mark, so episodes run later still auto-select the original plate. Restart the service to get the page (the buttons appear on refresh but lead to a 404 until then).

  • Scene links, phases 1 + 2: match cuts and relays between scenes are drafted at the script stage, disposed of one by one by the director, registered into the shot list by the host and written into both sides' group prompts (2026-10-10).

    A match cut needs the last image of one scene and the first image of the next designed as a pair, and both are decided by the script; by the storyboard stage they can only be forced together. Until now script transition lines were a bare CUT TO or free text, nothing reconciled the director's transition list with them (of 8 script match cuts that mapped onto a shot-list boundary, 3 survived as match_cut), and match_cut was a label with no matching sentence in either prompt. Phase 1: the scene-end transition line stays CUT TO by default; only when both ends are really written in the scene text, the link is one of seven whitelisted kinds (shape, action, same camera position, motion, sound, dialogue line, contrast; shape and action are trial kinds) and it joins the adjacent next scene does the line end with {衔接: 类型, 出: …, 入: …} (English anchors {link, out, in}). modules/scene_links.py is the single parser for script transition lines (across all project copies, transition design now reads the transition line of 2,536 scenes instead of 241), and the check scene_link_valid in code/check_screenplay_events.py fails on, among others, an unknown kind, a missing out or in, a link that is not to the adjacent next scene, and MATCH CUT / SMASH CUT without braces. The director's transition list must dispose of every link in one line, 「剧本衔接 Sxx→Syy:采纳 | 修改 | 弃用」 — adopt and modify name the transition type the link lands on (only hard_cut / match_cut / smash_cut / dissolve), drop and modify give a reason — checked by the new code/check_scene_links.py (script_links_disposed; --list lists the links). The script preview page shows the kind, this scene's ending and the next scene's opening, with a 「试验」 tag on the trial kinds. Phase 2: transition_design.propose registers each adopted or modified link on the first group of the later scene as transition_in.link {kind, out, in, out_shot, in_shot, note, trial?, device?} and applies it to shot_list at once; a withdrawn link is removed at the next propose, a boundary already ruled on in the transition card stays as set (「剧本衔接(导演采纳)」 remains a candidate), and in minimal mode the H3A sign-off hook registers the links instead. In cinematic mode a motion relay automatically brings a motion_pair (not with whitebox on) and a sound or line relay a J sound_bridge; classic and custom only offer them as candidates. code/sync_scene_links.py --write writes one 「【场间衔接】」 sentence (Scene link: when the interface is not Chinese) into the previous group's last Shot paragraph and one into this group's Shot 1 paragraph, from a fixed sentence pair per kind; each sentence describes only its own shot, because the two videos are generated separately. New checks: transition_link_valid (part of transition_ok), scene_link_bound (p7-prompt acceptance) and script_links_landed (fails when an adopted link is missing from the shot list, its type differs, the registration is stale or a dropped link is left over). The transition card on the storyboard preview page (分镜预览) shows 「🔗 类型」, the director's disposition and a 「📜 剧本衔接」 tag. Existing projects: screenplays whose generated_at is before 2026-10-10 only get WARNs from scene_link_valid (240 existing screenplays: 0 FAIL), and re-running the proposal on seven real episodes changed no shot list. Spec: docs/scene_links.md. Not done or verified: phase 3 (whitebox check by screen position, the previous group's last frame as a composition reference, picking the best-matching pair near the cut in editing); the screenwriter, director and prompt stations have not been dispatched for real under the new rules, and the effect of the link sentences on generated video is untested; the sound bridge that cinematic mode now adds has not been verified in a real project's final cut. Restart the service for the preview pages, the transition card and the H3A fallback; the check and sync CLIs work immediately.

  • NPC staging: nameless background people and foreground objects give scenes depth (2026-10-09).

    Shots can now be filled out with nameless NPC elements — passers-by, foreground objects — so that a scene has a foreground, a midground and a background. The switch sits on the scene: the script stage judges it, the storyboard places the NPCs shot by shot, and composition, whitebox and prompt only carry it out. The project-level master switch output.npc_staging (auto = judged per scene, the default; off = all off) is a new NPC 参与构图 row in 输出设置 and in the new-project wizard. In p5-breakdown, timeline-story writes scenes[].npc {on, density: sparse|medium|dense|null, reason} into script_breakdown.json for every scene (new check npc_judged, part of code/check_script_breakdown.py: skipped when the master switch is off, WARN only for breakdowns produced before 2026-10-09). On the script preview page and the storyboard page each scene header has a 🚶 chip to set 自动 / 开 / 关 and the density (POST /projects/<p>/episodes/<ep>/npc, stored in the host-owned story/episodes/<ep>/scene_npc.json, which re-analysis does not overwrite). The effective value is: master switch off > the user's value > the automatic judgment > not judged, which counts as off — so existing projects behave as before. Downstream: storyboard draft shots carry npc[{layer: fg|mg|bg, what}] and each scene block an npc_applied receipt (check npc_staging_applied; a scene whose receipt disagrees with the effective value is flagged 「分镜未按此设定」 on the storyboard page — changing the switch does not redo downstream work), and sketch prompts include the NPCs automatically; the shot list copies npc[] (npc_shots_consistent); composition names the NPCs in the matching layers (npc_layers_bound); whitebox uses EXTRA-NPC-xx extras with the legend anonymous NPC passer-by, not a named character; in the group prompt --write adds a host-owned fixed section Background figures (NPC): … End background figures. before Global constraints, and in groups with NPCs the identity lock ("every person on screen must match / no extra or duplicate person / exactly N characters") is narrowed to named characters; running it again after the scene is switched off removes the section (npc_prompt_bound); ambience adds a bed of crowd voices matching the density. Plates still contain no people, and extras named in the script still go through extras. New module modules/npc_staging.py and host CLI code/npc_staging.py (effective-value table, --suggest keyword hints, --check storyboard|shots|composition|prompt|all, --write); see the new docs/npc_staging.md. The UI was exercised on an isolated instance; no real agent work order was run.

  • Execution engines: one-click CLI install, and sign-in in a terminal window (2026-10-09).

    When the top bar is switched to claudecode / codex / kimicode / pi / opencode / grok (or one of them is restored at startup), the local CLI is checked. If it is not installed, a dialog shows the official install command, and 一键安装 runs that vendor's official install script in the background — a fixed command that does not go through a language model: claude.ai/install.sh, chatgpt.com/codex/install.sh, code.kimi.com/kimi-code/install.sh, pi.dev/install.sh, opencode.ai/install, x.ai/cli/install.sh; on Windows each one's install.ps1, except OpenCode, which has none and uses npm install -g opencode-ai. The log is shown live, together with the megabytes downloaded so far. On failure the dialog gives the cause read from the log (network, region restriction, missing Node.js) and offers 在终端中安装, which opens a terminal running the same command so that the script's questions can be answered. Once installed, if the CLI is not signed in the dialog moves on to 登录: a terminal opens with the CLI's own login command (claude auth login / codex login / kimi login / /login inside pi / opencode auth login / grok login), the user authorizes in the browser with their own account, and the dialog closes when polling sees the login; the app never handles or stores a token. In the new services/runtime/engine_setup.py the install subprocess's environment is stripped of VIDEOAGENTS_* variables and of secrets (including the desktop Agentics login token), and the install is stopped only after 10 minutes with neither output nor download growth (overall cap 2 hours), because a fixed time limit would kill slow downloads. When the sign-in state cannot be determined, for example with a custom provider key, nothing is shown. resolve_cli_executable falls back to each vendor's default install location when the command is not on PATH, so a fresh install is detected and usable without restarting the service (no fallback when a *_BIN environment variable is set). /engines/<e>/availability gains logged_in / login_method / install_command / install progress / local_backend; new endpoints POST /engines/<e>/install (mode=background|terminal), GET /engines/<e>/install and POST /engines/<e>/login; install and sign-in are only open to requests from the local machine. The desktop app adds the CLIs' default directories to its search path on Windows (and ~/.pi/agent/bin on macOS), which takes effect with a repackaged build. Exercised for real: a kimi one-click install on an isolated instance (about 480 s on a slow connection) up to the sign-in step, and the Terminal pop-up on macOS. Not exercised: anything on Windows, a complete claude install (only as far as download progress), real browser sign-ins. Known upstream issue: kimi's official install script writes fish_add_path into .zshrc when the rc path contains "fish" (for example in the user name).

  • Agent advanced settings: 多集并行 switch — episodes are produced one at a time by default (2026-10-09).

    Agent 高级设置 gains a 多集并行 checkbox under 单 Agent 并发额度 (state.json episode_parallel, off by default; GET/POST /config/agent-advanced carry it). Off, the orchestrator advances in episode order and works on one episode at a time: the current episode is the lowest-numbered one that still has a node outside a terminal state (done / passed / passed_human_override / skipped / cancelled / waived), and the next episode — including its Phase 5 script nodes — is dispatched only when every node of the current one has ended. Publishing nodes (p11-*) are not counted, because a publish chain is often left pending and would otherwise keep the next episode from ever starting; blocked and failed nodes count as unfinished; a finished episode that is marked dirty becomes the current episode again. Inside an episode, groups, shots and scenes still fan out in parallel by dependency. On, the previous pilot-episode strategy applies (episodes run in parallel in bulk once the first one has passed H4), so tick the box to keep the earlier behaviour. The logic is in services/runtime/dagcheck.py: a node's episode comes from for_each.episode or the single epNN in its id, and a non-human node without an episode number inherits one when all of its dependencies belong to the same episode (human gates, and nodes that also depend on a project-level node, do not). New dagcheck.py --frontier reads the switch from state.json live (override with --episode-serial / --episode-parallel) and lists the current episode, the nodes that can be dispatched and the later-episode nodes held back by episode order. The runtime uses the same frontier: the idle watchdog's wake-up message lists only the current episode and project-level nodes plus a note about what is held; when the current episode is stuck (nothing to dispatch, nothing awaiting sign-off) the user is notified once an hour; sign-off tickets are not created for the held gates of later episodes; dispatching agents get a 「多集推进设定」 section in their run prompt according to the switch; and the end-of-run check in the dispatch rules now runs dagcheck.py --strict --frontier. WORKFLOW §8 「试点集策略」 is rewritten as 「集间推进」 and the orchestrator SOUL is updated. The setting was checked in the UI on an isolated instance and --frontier against existing project data; no real dispatch was run.

  • Scene preview: every shot plate gets a ✏️ 修改 button, and plate revisions accept reference images (2026-10-09).

    On the scene preview page each image in the 分镜背景图 section now has ✏️ 修改. It opens the edit popup at the bottom right and sends the feedback straight to the shot-plates agent (08-video-gen/shot-plates, not through the reviser), which runs code/revise_shot_plate.py in a new library-plate mode, --scene <id> --key <key> (modules/shot_plates.py#revise_library_plate): the library image is passed as [Image 1], a new <key>_revN is generated and added to the library, and the original image is left untouched. By default every shot that references the original is switched to the new plate — the episode indexes are scanned by scene and key, view / crop and the camera fingerprint are not changed, and the affected groups are synced; --only limits the swap to the named shots and --library-only only adds the plate to the library. Both modes (the existing per-shot one and the new library one) gain --ref for user reference images: local image paths attached with the popup's 「+」 are passed as [Image 2…] (up to 4) and copied to plates/revision_refs/ for the record; edit-popup.js gains a hint option, used here to point at 「+」. A revised plate is now always generated at the nine-grid fill-in size, 1920x1080, instead of following the size of the original image. Also fixed: nine-grid keys repeat across scenes, so the scene preview's 「用于分镜」 line, the swap candidates and swap_shot_plate.py --list mixed in shots from other scenes; all three now filter by scene. The shot-plates SOUL, docs/shot_plates.md and docs/reviser.md are updated, and the new strings are in all 11 UI dictionaries.

  • Long-take continuation material is enrolled in the virtual avatar library automatically (2026-10-09).

    A long take continues from the previous group's tail frame or tail segment, and Ark's PrivacyInformation filter rejects such real-person-looking material when it is submitted as is. When the project has long takes switched on, the 虚拟人像资产库 is enabled and the video model is Seedance 2.x on Volcengine Ark, genmedia now enrols the continuation material before it submits a group video, according to the connection mode: the previous group's tail segment (assets/continuity/epNN/<prev>.continuation.mp4, AssetType=Video) or tail frame (assets/clips/epNN/<prev>.last_frame.png, AssetType=Image) is uploaded through a file-hosting presigned URL, and once its review status is Active the request is sent with asset://<id>. New module modules/ark_assets.py; _ark_video_body takes asset_uri_for, which resolves each file after parameter validation has passed, and a dry run enrols nothing. A ledger entry that is already Active is re-checked with GetAsset and enrolled again if the asset is gone (fully automatic management empties the library on an episode change, and assets can be deleted by hand); an asset still under review is waited for, up to 600 s for an image and 900 s for a video. Material that does not meet Ark's asset specification, a failed upload or enrolment, a failed review or a timeout only produces a warning in the generation log (lines starting 「虚拟人像库:」), and the group is submitted the previous way. Material that failed content review is recorded as Failed in the ledger and is not submitted again, while occasional faults such as DownloadFailed are retried next time. The ledger is shared with character images (avatar_assets.json, kind=continuity, slot = project|episode|previous group|type); when the previous group is regenerated, the old asset in the same slot is deleted to save quota. Volcengine OpenAPI signing moved to the shared modules/volc_openapi.py (core._volc_signed_call is now an alias; requests are byte-identical). docs/continuity.md, the video-generation SOUL and WORKFLOW are updated; 15 new tests. Enrolment was not exercised against the real service.

  • Whitebox reel: can be produced before the H3W sign-off, from preview-grade group videos (2026-10-10).

    The 🧊 白模样片 at the top of the Storyboard preview page is what the H3W decision is made from, yet the reel needs every group's camera.mp4 and the whitebox-staging spec forbade exporting before the sign-off — so pressing 重新生成白模样片 on an unsigned episode ended with the agent refusing, while the button's own work order told it to render the missing groups first. What that rule protects against is paying for shot plates before the sign-off and wiring an unconfirmed whitebox into prompts (code/render_shot_plates.py treats the formal assets/whitebox/<ep>/<grp>/manifest.json as proof of sign-off, and a formal export ends by running sync_whitebox_refs); the rendering itself is a local headless browser plus ffmpeg and costs nothing. code/concat_whitebox.py now compiles the current whitebox in memory (the episode.json on disk is not changed), renders a preview video into directing/<ep>/whitebox/preview/<grp>/ for every group whose formal video is missing or whose source fingerprint has changed (same renderer and format; the manifest carries preview: true), and then builds the reel from each group's preview where one exists and its formal video otherwise. --no-render only joins the group videos that already exist; if a preview fails to render, the command exits non-zero instead of quietly joining old videos. A preview never writes the formal manifest and is never wired into a prompt: after a preview reel render_whitebox.py --verify-export still reports those groups as not exported and the shot-plate script still refuses to run. At the formal export after the sign-off, a group whose preview is still current (source fingerprint, renderer fingerprint and format) is promoted by moving the file (receipt videos.promoted) rather than rendered again; groups whose whitebox changed are rendered as usual. The reel manifest gains preview_groups and whitebox_sources, and a reel whose whitebox was changed afterwards (recompile, Director's console overlay) is reported stale with stale_reason=whitebox and whitebox_changed naming the groups. The status line shows 「预览版 · n 组未正式导出」 and 「白模已改(n 组),样片需重出」, and the missing-group note now reads 「n 组尚无白模视频(生成样片时自动补出)」. The whitebox-staging SOUL now forbids only the formal export in a staging work order and gains a conditional preview-reel section: a staging work order builds the reel once before delivery, a revision work order refreshes it only when the episode already has one, and a failure for lack of Chromium does not block delivery (receipt reel_preview). The orchestrator SOUL, WORKFLOW, docs/whitebox.md, code/README.md and all 11 dictionaries follow. Run for real on one unsigned episode (four preview groups, an 88 s reel); promotion was run on a temporary copy. Restart the service: until then the reel plays, but the status line wrongly reports it as possibly stale and the groups as missing.

  • Episode plan: derivative (original-story) mode is now a formal contract (2026-10-09).

    A project that only borrows the source novel's world and does not adapt its plot could never pass the three event-reconciliation machine checks: in one such project the episode plan failed events_classified_once (121 events unclassified) and treatments_complete (all 10 episodes), the screenplay failed dramatized_events_covered (its scenes say [EVENTS] none), and the colour script under --strict had 70 entries without event_refs. The orchestrator could only waive them one work order at a time, under a derivative_mode field it had invented itself and that no contract or code recognised. The field is now defined, as a conditional whitelist: only when brief.md or the user's instruction states in so many words that the source plot is not adapted (「不改编原著情节」 / 「不围绕原小说情节」 / 「只借原著世界观做原创衍生」) may the top level of episode_plan carry derivative_mode: true plus a derivative_note quoting those words and their source, with every episode's events / treatments empty — the whole work is derivative, never half adapted and half waived. A failing check is not a reason to set the field; the orchestrator no longer waives derivative projects per work order, and returns a plan that carries the field without such a basis as a defect. In derivative mode verify_plan (modules/episode_treatments.py) reports the six event checks (events_classified_once, treatments_complete, dramatize_within_cap, hook_points_dramatized, cut_not_on_causal_chain, merge_target_valid) as SKIPPED, adds derivative_mode_consistent (note present, events / treatments empty) and still runs duration_in_budget and ids_valid; only a boolean true counts — a string such as "true" is reconciled as an adaptation, with a WARN. verify_screenplay skips its three event checks, accepts [EVENTS] none / [事件] 无 on a scene's meta line as the legal placeholder, warns when a scene attaches an ev… id, and still runs scene_spacetime_continuous. code/verify_episode_plan.py no longer exits 2 when a derivative project has no events.json, and code/check_color_script.py reads the plan and stops checking event_refs (scene_ids remain required). Hook points are written as hook_open / cliffhanger text with no event attached, and the writing_v1 rubric's 「忠实原著」 item rates fidelity to the borrowed world (Bible settings, geography, institutions, character identities) without deducting for not dramatizing the source plot. WORKFLOW §5A, the episode-planner / screenplay / color-script / orchestrator SOULs, docs/screenplay_anchors.md and code/README.md are updated. Across 62 existing projects, only that one project's results changed (it now passes). Checks and specs only: no service restart is needed. (#128, folding in #123)

  • Subtitles: a basis declaration lets subtitles be shifted from the original rough-cut timeline after the mix is stamped (2026-10-09).

    Once the mix is stamped against the adopted versions (mix_basis current), the timemap drops its post_versions layer, and the audio track and the subtitles shared that one table — on the assumption that the subtitles had already been produced on the post-production timeline. Nothing recorded which timeline a subtitle file was actually on: in one episode subtitles.srt was still on the original rough-cut timeline (two deleted ranges, −9.709 s), finalization copied it unchanged, the last cue ended at 84.06 s in a 79.833 s final, and subtitle_within_final / intro_offset_ok failed. load_timemap now builds the subtitle table separately from the audio table, driven by a new declaration file edit/epNN/subtitles.basis.json: original (rough-cut basis — the post_versions layer is applied to the subtitles even though the mix stamp skipped it), post (already on the post-production timeline — not applied), or no declaration (follows the mix basis, exactly as before). The group-boundary layer always applies to subtitles, and the audio table is not affected by the declaration. finalize_episode.py gains --subs-basis {auto,original,post}: shift / assemble write the declaration (auto deletes it), probe / check use the value for that run only. With no declaration, when the post layer was skipped and the last cue still maps to more than 0.5 s past the end of the programme, shift / assemble now FAIL with subtitle_basis and print the --subs-basis original command, or the alternative of sending the subtitles back to be redone, instead of silently copying the file; the subtitle_within_final failure message in check carries the same hint. render_captions / caption_timeline go through the same load_timemap, so they follow the declaration as well. Applying the post layer to subtitles unconditionally was not an option: episodes whose subtitles are already on the post-production timeline would have been shifted a second time (16–33 cues per episode in one project, by up to −19.2 s). Existing episodes are unchanged: on four real episodes the subtitle table, the audio table and the mapped SRT text are identical before and after, and none of them hits the new FAIL. WORKFLOW §9B and the subtitle / edit SOULs gain conditional clauses: when post-production deleted ranges, subtitles are aligned to the final audio, and the declaration is written only when they can be delivered on the rough-cut basis alone. (#114)

  • Burned-in subtitles: new host CLI code/burn_subtitles.py (2026-10-09).

    With 「内嵌字幕」 on (output.subtitle_burn_in) the edit station was told to burn subtitles_final.* into the picture with ffmpeg's subtitles filter, but the host's ffmpeg is not guaranteed to include libass — a Homebrew ffmpeg 8.1.1 has none of ass / subtitles / drawtext — so the station wrote its own replacement script inside the project, one format per project, with a font size and margins no check could verify. python3 code/burn_subtitles.py burn --project <slug> --ep epNN [--lang en] now does it without libass and with the same output on every machine: it reads the final-basis subtitles subtitles_final[.<lang>|_<lang>].srt (or .ass; the programme-basis subtitles.srt is always refused), draws each on-screen segment with PIL as a full-frame transparent PNG, and overlays the sequence onto a copy of final.mp4, written as final_sub[.<lang>].mp4. The picture is re-encoded at crf 18, every audio track is stream-copied, and the clean final.mp4 is never modified. The style follows the subtitle SOUL: bottom centre, white text with a black outline of about 8% of the font size, no backing box; character height = min(4% of frame height, 5% of frame width), so a portrait frame is limited by its width; bottom margin 3% by default; two lines are kept within 12% of the frame by tightening line spacing first and reducing the font size second; a segment that still wraps to more than two lines at 80% font size is reported as a FAIL for the subtitle station to re-split. The source is checked before burning: a last cue more than 0.5 s past the end of the final, or an intro offset > 0 while the final-basis file is cue-for-cue identical to the programme-basis one (never shifted), is a FAIL and nothing is burned; a subtitle file older than the final is only a WARN. burn then runs check, the machine check subtitle_style_ok: subs_source_ok, style_lines_le_2, style_char_height, style_bottom_margin, style_band_height, duration_match (within one frame, equal frame counts), audio_stream_copy and first_cue_frame (the frame matches the source outside the subtitle area and differs inside it). Results are recorded per output name in edit/epNN/subtitles_burn.json. Other flags: --src, --out, --char-pct (4 or lower), --margin-pct (2–4), --font, --crf, --preset. With burn-in on, the injected output-settings prompt, WORKFLOW §9B and the edit / platform-adapter / subtitle SOULs now allow only this CLI — no hand-written subtitles / ass / drawtext filters and no project-local scripts — and platform-adapter takes final_sub[.<lang>].mp4 as its master, stopping to report when it is missing or failed. finalize_episode.py shift is unchanged. Exercised on a scratch copy of one portrait 1080×1920 final in English and in Chinese (--lang zh): about 25 s per version, all checks PASS. (#133)

  • Reference files: new 其他 section for files of any format (2026-10-10).

    The ten categories of the 参考文件 page (风格 / 封面 / 角色 / 场景 / 生物 / 道具 / 音频 / 视频 / 字体 / 文本) are all defined by purpose, so reference material that fits none of them — a PDF setting book, a spreadsheet, an archive, a project file — had nowhere to go. A new 📎 其他 section, placed under 文本, stores such files in refs/other/. It uses the existing upload / note / delete logic: neither the button nor drag-and-drop filters by file type (only the font category checks extensions), the 100 MB per-file limit is unchanged, images / audio / video are still previewed by extension and everything else shows a generic file icon. Existing projects need no migration: without an other/ directory the section lists as empty, and the first upload creates it. Agents do not go through this directory on every work order (WORKFLOW §2 rule 10 and the injected reference-material prompt): a file in refs/other/ is read only when its note in refs/NOTES.md describes a use that falls on what the work order is about, or when the work order names its path. A file with no note that no work order names is not read, not reported and never blocks; a path that is used is recorded in user_refs, a file whose format could not be read is named in the receipt, and the orchestrator names a file in the relevant work order when its note points at a station or an object. The price of that rule is that a file without a note is never used on an agent's own initiative, which is why the section's caption asks for one (「请添加注释说明用途」). All 11 dictionaries carry the caption. Checked in an isolated instance with PDF and XLSX uploads. Restart the service: until then the page receives no other category and uploads to it are rejected.

  • ComfyUI local / Comfy Cloud templates for Noct Q V4, text-to-image and multi-reference image-to-image (2026-10-10).

    Noct Q V4 is a photorealistic merge of Qwen Image 2.1. New comfy/image-noctq-v4-int8-api.json (text-to-image) and comfy/image-noctq-v4-int8-ref10-api.json (image-to-image, 1–10 reference images), each with a Chinese / English description; the 「文生图API」 / 「图生图API」 dropdowns on the 生成模型 page list them automatically. Sampling follows the author's workflow (TextEncodeQwenImage21 for both the positive and the negative conditioning, KSampler 25 steps, cfg 3.0, euler / simple, native nodes only), and so do the three model files, including the author's recommended int8 text encoder qwen3vl_8b_int8_convrot.safetensors; with the bf16 encoder already at hand, fork the template and change clip_name in node 2. Image-to-image uses the same files: the encoder node takes images.image_1…image_10 plus the VAE, and genmedia binds the Nth --ref to the Nth LoadImage and removes unused slots; an 11th reference is rejected before submission. Two things differ from the author's workflow. Size: the fixed 1024x1536 becomes an EmptyLatentImage at {{GEN_WIDTH}} / {{GEN_HEIGHT}} (the requested aspect ratio within 1.6 MP) plus a final ImageScale to {{WIDTH}}x{{HEIGHT}}, as in the repo's other Qwen templates; image-to-image samples from that empty latent too, rather than the node's own latent output (sized to the first reference), so the output size follows the request, at the cost that an in-place edit can shift — the description shows how to rewire it. Negative prompt: {{NEGATIVE}} feeds the node's negative_prompt (effective at cfg 3; use negative_mode conditioning); the author's recommended negative string is not hard-coded, because its "rough sketch" / "sloppy lines" terms suppress sketch and illustration styles, and is quoted in the description to pass per call. Requirements: ComfyUI 0.37.0 or later; the model is under the Qwen Research License (non-commercial use only) and can generate adult content. On Comfy Cloud none of the three model files is in the public model catalog, so they must be imported into the account first (the catalog does have the bf16 text encoder, which leaves only the diffusion model and the VAE to import). The templates were checked statically against the Comfy Cloud v0.39.2 node catalog; no real image was generated with them, locally or on Comfy Cloud, and time and VRAM with 10 references were not measured.

  • ComfyUI local templates for Qwen Image 2.1 GGUF (Q8_0), text-to-image and multi-reference image-to-image (2026-10-10).

    New comfy/image-qwen-image-2.1-q8-gguf-api.json (text-to-image) and comfy/image-qwen-image-2.1-q8-gguf-ref10-api.json (image-to-image, 1–10 reference images), each with a Chinese / English description, listed automatically in the 「文生图API」 / 「图生图API」 dropdowns on the 生成模型 page. They are the official Comfy-Org Qwen Image 2.1 text-to-image / image-edit templates with the diffusion loader swapped for UnetLoaderGGUF, loading qwen-image-2.1-Q8_0.gguf (another quantization is a one-line change in node 1). The quantized files without "UC" are on the base branch of the abenzerps/Qwen-Image-2.1-Uncensored-GGUF repository and are quantizations of the original weights; only the UC- files on its main branch are the uncensored variant. Sampling is unchanged (TextEncodeQwenImage21, KSampler 25 steps, cfg 1.0, euler / simple). At cfg 1 the negative conditioning takes no part in the computation, so there is no NEGATIVE placeholder: set negative_mode to append_exclusions, or fork the template (raise cfg and put {{NEGATIVE}} in node 4) to make negative terms count. Differences from the source workflows: ResolutionSelector is replaced by an EmptyLatentImage at {{GEN_WIDTH}} / {{GEN_HEIGHT}} (the requested aspect ratio within 1.6 MP) plus a final ImageScale to {{WIDTH}}x{{HEIGHT}}; image-to-image samples from that empty latent with the references as conditioning, rather than editing in place at the first reference's size, so the output size follows the request (an edit can shift as a result; the description shows how to rewire it); references are encoded at the node's default resolution 1024 instead of 0 (original pixels), because the platform's reference images are large and encoding them at original size makes the sequence too long; QwenImage21Cache (set to auto / default, the same as the model's behaviour without the node) is dropped, with a note on adding it back when VRAM is tight; SaveImageAdvanced becomes SaveImage and the ImageCompare preview is removed. Local only: UnetLoaderGGUF comes from the ComfyUI-GGUF custom node pack, which the Comfy Cloud v0.39.2 node catalog does not have, so a cloud submission is rejected with missing_node_type and importing models does not help; the description gives a variant without the GGUF node (node 1 as UNETLoader with a safetensors model), the only form that runs on the cloud. Requirements: ComfyUI 0.37.0 or later; ComfyUI-GGUF from the leejet fork (the architecture list of the original city96 pack has no qwen_image21); the model is under the Qwen Research License, non-commercial use only. The templates were checked statically against the Comfy Cloud v0.39.2 node catalog; no real image was generated with them, and time and VRAM with 10 references and the effect of resolution 1024 on reference fidelity were not measured.

  • New regression tests (2026-10-09).

    Five test files are added: tests/test_dialogue_tts_speed_trim.py (dialogue voice library: speed defaults, casting-key compatibility, silence trimming), tests/test_post_audio_lanes.py (the post page's per-lane playback data for narration, BGM and sound effects), tests/test_shot_plates_background.py (the shot-plate sentences written into video prompts), tests/test_storyboard_sketch_pick.py (the sketch picker behind the storyboard preview's 添加参考图) and tests/test_comfy_image_refs.py (reference images and negative prompts in ComfyUI / RunningHub image workflows). Three cases in test_comfy_image_refs had been written against a design dropped on 2026-09-16 (several --ref images composited into one contact sheet) and are rewritten to the current behaviour: --ref images are bound to LoadImage nodes one by one in order, more reference images than LoadImage nodes fails before any request is sent, and a single reference through the Krea image-to-image template folds the negative prompt into the positive one. The test_continuity_refs fixture is now isolated from the machine's own generation-model configuration, which had made two cases fail on a machine with a real configuration while a clean checkout and CI passed.

Changed

  • The license changes from Apache-2.0 to GPL-3.0 (GPL-3.0-only) (2026-10-10).

    LICENSE is replaced with the official GPLv3 text from gnu.org (line for line the same as GitHub's license template; GitHub's repository page detects the license from this file on the default branch). NOTICE becomes the standard GPL notice (version 3 only, no warranty, a copy of the license in LICENSE), keeps the origin attribution, and states that releases up to and including v1.0.38 were published under the Apache License, Version 2.0, and remain available under those terms. The license section of README.md / README.zh-CN.md is rewritten to the GPL-3.0 requirements: anyone distributing the project or a modified version must release it under the same license and provide the corresponding source code, keep the license and copyright notices, and mark the changes and their date; there is no warranty. CONTRIBUTING.md's redistribution requirements and contribution license are now GPL-3.0-only, and the license field of CITATION.cff and the license / classifier of pyproject.toml match. The metadata says GPL-3.0-only, not "or later". Dependencies were checked and all can be combined into a GPLv3 work, so THIRD_PARTY_NOTICES.md is unchanged: the package-lock production dependencies are MIT / Apache-2.0 / BSD / ISC / LGPL-3.0 / Python-2.0 / BlueOak / 0BSD plus libsignal (GPL-3.0, brought in by baileys), the pyproject dependencies are MIT / BSD / Apache-2.0 / Unlicense and similar, and three.js, Spark and XiaohongshuSkills, which are distributed with the repository, are MIT. tests/test_smoke.py gains test_license_metadata_consistent (LICENSE is GPLv3; pyproject / CITATION / README / CONTRIBUTING / NOTICE agree).

  • Plates and scene images are always 16:9, independent of the project aspect ratio (2026-10-09).

    Background images no longer follow the project (video) aspect ratio, so a portrait project now gets landscape plates too. Fixed at 16:9 (modules/shot_plates.py PLATE_FMT): the nine-grid sheet and its tiles, nine-grid fill-in images, master plates (panorama reprojection / automatic world-model capture), plates regenerated from feedback, manual captures, and the crop box on the scene preview page. The 💾 背景图 capture in the panorama and world-model viewers follows the same rule: the dashed frame and the off-screen render are 1920×1080, the server-side plates/manual handler requires 16:9, and the stored camera facts compute the horizontal field of view at 16:9. Scene images use scene_plates.PLATE_SIZE = 2560x1440 (they had been 16:9 only by accident, through an exception fallback). What describes the video frame still follows the project aspect: the shot's own camera facts and the in-frame sentences in the group prompt. A master plate's field of view is widened through plate_fov_v so that it can hold a shot wider than 16:9; view_fits / find_master judge by each library entry's actual aspect (entry_aspect), so existing portrait plates keep being reused. When the two aspects differ, the episode index view records fraction_w and the group prompt states width and height separately; for 16:9 projects the wording is unchanged. The environment-concept and shot-plates SOULs and the system prompt's 「输出画幅」 section carry the exception, the experimental scripts (pano_plates_test, world_plates_test, grid9_center_test, grid4_test) use the same rule, and the crop hint is updated in all 11 UI dictionaries. Existing non-16:9 plates are not regenerated automatically.

  • Image model lists refreshed for OpenRouter and Fal, six new models wired in; OpenRouter images are requested by resolution tier and Fal's GPT Image is sent an explicit width / height (2026-10-10).

    The lists shared by the 生成模型 page and the preview pages (apps/web/static/image-models.js) were checked against the live catalogs. OpenRouter gains google/gemini-nano-banana-2.1 (now first), bytedance-seed/seedream-5-0-flash, tencent/hy-image-v3.5-preview and black-forest-labs/flux-3-image; ChatGPT Images 2.5 is GPT Image 2.5 (Sunburst precision tier / Flare speed tier), which was already listed and now carries that name in both descriptions. Fal gains google/nano-banana-2.1, openai/gpt-image-2.5/sunburst, bytedance/seedream/v5/pro and flash, blackforestlabs/flux-3 and ideogram/v4.5; the default fal-ai/bytedance/seedream/v5/lite is unchanged. Changes that affect cost: (1) OpenRouter's Images API request now carries resolution for models whose catalog entry lists tiers, chosen by pixel area — the smallest of 1K / 2K / 4K (about 1024² / 2048² / 4096²) with at least 80% of the requested area, or the largest if none reaches it: 1280x720 → 1K, 2560x1440 and the 2858x1608 master plate → 2K, 4K only from about 5.2 MP. The field was not sent before, so every image came out at the model's default tier; 14 of the 24 recommended models now carry a tier (GPT Image, MAI, Muse, FLUX.2 and Recraft have none in the catalog and are unchanged). (2) Models that return image and text (the Nano Banana family, gpt-5-image) went through chat/completions, where aspect ratio and size could only be written into the prompt; those in the catalog now use POST /images first and fall back to chat only on a 404. (3) Fal's GPT Image 2 / 2.5 is sent an explicit width and height instead of only an enum preset such as square_hd / landscape_16_9 (both sides multiples of 16, long side ≤3840, aspect ratio ≤3:1, 655,360–8,294,400 pixels); a size outside these limits is fitted and logged (1920x1080 → 1920x1088), and billing follows the output size. Also: Fal's Seedream 5.0 Pro / Flash output is limited to 1024x1024–2048x2048 total pixels (5.0 Lite / 4.5 go up to 4096x4096), so out-of-range requests are scaled into range first; FLUX.3 on Fal picks its tier (512sq / 768sq / 1k / 2k / 4k) by area and takes up to 10 references; with references, Ideogram V4.5 on Fal treats the first --ref as the base image being edited and up to 4 more as references. Models picked through 「搜索 Fal 模型」 now resolve the edit-image task segment (FLUX.3-style models with references used to get a non-existent endpoint) and, on edit endpoints with a required base-image field plus a reference list, send the first reference as the base image. Scene panoramas: Fal's GPT Image is no longer blocked, since 2880x1440 can now be sent. Not exercised for real: no image was generated — the new models' output, what OpenRouter's Images API actually returns for image-and-text models, how Fal handles out-of-range sizes at run time and a real panorama on Fal's GPT Image are unverified. Known gaps: Fal's Nano Banana still picks its tier by the old rule (long side over 2560 → 4K, so a 2858x1608 master plate comes out at 4K), unlike the area rule above; and the nine-grid sheet's model-maximum resolution does not cover Fal's GPT Image / FLUX.3 / Ideogram. Restart the service for the 「搜索 Fal 模型」 change; the lists apply on a page refresh and genmedia immediately.

  • Desktop on Windows: FFmpeg is downloaded from the product's own release source instead of WinGet (2026-10-10).

    On a Windows machine without WinGet (LTSC, Server, stripped-down systems) 「自动安装」 failed with 「未找到 WinGet,暂时无法安装 FFmpeg」 and left only a manual install. The app now downloads a portable build itself — gyan.dev's ffmpeg-9.0.2-essentials_build.zip (GPL v3, 114,768,076 bytes), a separate artifact that is not merged into the Python runtime package — with version, file name, SHA-256, size and upstream address pinned in the new apps/desktop/ffmpeg-artifact.json. On Windows the install tries this route first: download with byte progress, verify, unpack, delete ffplay.exe (about 100 MB) and move the build to <userData>/ffmpeg/current/, keeping the package's LICENSE and README.txt; no administrator rights are needed. If it does not succeed (the index has no entry, no network, checksum or acceptance failure) it falls back to WinGet as before, and with no WinGet either the manual-download prompt still appears, now with the reason the download failed. <userData>/ffmpeg/current/bin is put first on PATH, and is listed before anything is installed, so a backend that is already running finds FFmpeg after the install without a restart and a broken system FFmpeg does not take precedence. macOS still uses Homebrew. The release pipeline mirrors the package: in the publish job of .github/workflows/desktop.yaml the new apps/desktop/scripts/mirror_ffmpeg.py checks ffmpeg/<file name> on both release sources (a missing file, or a size different from the pinned one, counts as missing) and only then downloads from upstream and verifies size and SHA-256; it runs before the GitHub Release is published, so an unreachable upstream fails the release before anything is out, and once both sources hold the file later releases do not contact upstream. The two distributions each use their own address: update_runtime_index.py --ffmpeg-artifact writes ffmpeg.win.x64 {version, url, sha256, size} with url built from the distribution's --public-base (s3.agentics.world), the same split as the Python runtime, and the desktop accepts only an address under its own distribution's package prefix on an ffmpeg/ path. Older clients read only the python / desktop sections of the index and are unaffected; they use the new route after upgrading. THIRD_PARTY_NOTICES.md records that an unmodified upstream build is distributed, with its license, source version (FFmpeg commit 946fcce07b) and upstream address. Not exercised on an actual Windows machine. A managed FFmpeg that is already installed is not upgraded automatically when the pinned version changes.

  • Whitebox: hand channels now follow the character's anatomical left and right, with a migration CLI for existing plans (2026-10-09).

    The renderer attached right_hand to the shoulder at local +X, but a whitebox character faces local +Z with +Y up, so its anatomical right is local −X: the two channels were mirrored, and a plan that wrote right_hand for a line such as 「右手握…」 moved the left arm. whitebox-renderer.js and modules/whitebox.py now share HAND_SIDES (left_hand at +0.52 × width, right_hand at −0.52 × width); the arm geometry is unchanged. Existing hand tracks were all written as "+X = right_hand", so with the renderer change alone every arm would cross the chest. The new code/whitebox_hand_migrate.py swaps the key names right_hand ↔ left_hand and leaves every coordinate alone, so the rendered picture is the same before and after (identical frame hashes). Migrated, per episode, is the whitebox data the host consumes: the keyframes of actors / scene_actors / extras in whitebox_plans/<grp>.json (each plan gains a top-level "hand_convention": "anatomical"), the Director's console overrides.json, the versions/<grp>/v<N>.json snapshots and whitebox/episode.json — by textual replacement at the located JSON paths, so every other byte stays as it was (3.0 does not become 3). cameras[].placement_fingerprint values, Director's console group approvals and latest-version hashes that were valid before are re-stamped on the migrated content; ones already stale stay stale. Deliberately not migrated: the copies of hand keys that agents wrote into shots/*/blocking.json, shot_list.json and assets/prompts, which are only counted and listed — the characters[] of blocking.json is part of the camera contract's source fingerprint, so editing it would invalidate the whole episode — and the archives under runs/, qa/ and backups. The default is a dry run: each episode is recompiled from the migrated files in a temporary shadow directory and must come out as the original with left and right swapped, with no new compile error and no lost group; an episode that does not is marked blocked and skipped. --apply requires --project <slug> or --all and backs the originals up to qa/whitebox-hand121-<time>/before/, --rollback <backup dir> restores every file not edited since, and the ledger directing/whitebox_hand_migration.json makes a second run a no-op. Nothing is migrated automatically: after upgrading and restarting the service, run --apply on each project still in use before dispatching any whitebox staging. The dry run also lists plans whose text says right or left hand while the channel used ends up on the other side — there the whitebox had been moving the wrong hand all along, and the plan needs restaging. Because the un-migrated copies can lead an agent to write the old convention back, the compiler adds a WARN 「手臂横穿胸前」: a hand channel whose target lies on the far side of the body midline in half or more of its keyframes and passes it by more than 0.25 × the character's width (frames with the two hands within 0.2 × the character's height of each other count as both hands on one object and are left out; creatures are not checked; a brief reach across the body does not trigger it). It is a WARN only, changes no compiled output, fingerprint or picture, and is shown in Chinese only under 「建模依据与检查」 in the whitebox panels; migrated projects compile with none, un-migrated copies of them with up to 134 per project. The whitebox-staging SOUL gains matching conditional clauses (keep hand_convention when editing a plan that has it; swap the key names when copying hand keyframes from pre-2026-10 blocking.json / shot_list copies; on this WARN, swap, or keep an intended crossing and say why in basis). Because the renderer file changed, every previously exported whitebox video is reported stale (re-exporting gives the same picture). (#121)

  • Post page: ticking 片头 / 片尾 now asks for the content first (2026-10-09).

    On the 后期处理 page, ticking 片头 or 片尾 opens a 片头内容 / 片尾内容 dialog. The box may be left empty, in which case the system improvises in the project's style. The switch and the text are saved only on 提交; cancelling reverts the tick. A ticked item gets a ✎ 内容 button for changing the text later. Unticking still saves immediately, and the text is kept. The text is stored in settings.json#packaging.intro_notes / outro_notes; the backend validation and the injection into the packaging station already existed. New strings are in all 11 UI dictionaries.

  • Console chat input: Enter sends, Shift+Enter starts a new line (2026-10-09).

    The two keys are swapped (it used to be Shift+Enter to send and Enter for a new line). The 修改意见 popup (edit-popup.js) shares the shortcut and is swapped as well. Both skip an Enter pressed while an input method is composing (isComposing / keyCode 229), so confirming a candidate with Enter in a Chinese, Japanese or Korean input method does not send the message. The placeholder texts are updated, in all 11 UI dictionaries too. The note dialogs on the script preview and storyboard pages keep Shift+Enter to save.

  • New-project wizard: the 模型限制 preset defaults to the video model currently in effect (2026-10-09).

    When the wizard opens, the 模型限制 step selects the Seedance 2.0 / Seedance 2.5 / MiniMax H3 / Wan 3.0 preset from the family returned by GET /config/video-model, that is, the video model in effect on the 生成模型 page (the whitebox switch follows the model as before: on by default only for Seedance 2.5). A model that fits none of the presets leaves the default, which is now MiniMax H3 (it was a fixed Seedance 2.5), and a yellow note says so and asks to check the four limits against that model's real capabilities. Once a preset is clicked or a value edited in that step, a late response no longer overwrites it; no note is shown when Agentics is not signed in and its profile cannot be fetched; the existing note for a selection that differs from the model in effect stays. The family comes from a new keyword rule, core.video_family_for (lower-cased, non-alphanumerics folded to -, matched in order, first match wins): minimax together with h3, or hailuo-3 / hailuo-h3 (OpenRouter's minimax/hailuo-3[-max] does not contain "h3") → mmh3; seedance / sparkvideo followed by 2.5 → sd25; any other seedance / sparkvideo 2.x, including fast / mini / global (sparkvideo-2.0 is the RunningHub model API's alias for Seedance 2.0) → sd20; wan-3 → wan30; Seedance 1.x, Hailuo 2.3 / 02, Wan 2.x, Kling, Veo, Vidu, Sora, LTX and the like match nothing. video_model_family now uses this rule: Agentics matches on the profile name (with no profile selected, the first catalog entry, as genmedia does); ComfyUI looks at the workflow file name or the selected RunningHub workflow's note first and then at the whole workflow JSON (RunningHub from the local cache only, no network). The older checks (is_minimax_h3, is_seedance20 and so on) are untouched, so the prompt skill and the reference-material limits still do not recognise OpenRouter hailuo-3 or RunningHub sparkvideo-2.0. 40 new tests in tests/test_video_model_family.py. Not exercised: real profile names with Agentics signed in.

Fixed

  • Slug-style character and creature IDs are recognised, and ID extraction is unified in modules/entity_ids (2026-10-09).

    Projects whose IDs are pinyin or English slugs (CHAR-jie-rui-er, CHAR-alice-sister) rather than numbers were not understood: speakers were treated as "speaker is not a character / creature ID" and a whole episode's dialogue voice was skipped; the script breakdown came out with an empty cast and no speaker IDs; the local voice library picked a voice for slug characters as if they were the narrator; and English character names could not be looked up. ID extraction now lives in one place (#117; #120, #122, #125 and #127 are merged into it): CHAR_ID_PAT / ACTOR_ID_PAT, extract_actor_id, normalize_actor_id, is_actor_id and known_actor_ids (the IDs in index.json plus the character-card directory names). The ID part is either digits only or an ASCII slug that starts with a letter and is split on hyphens; it ends at Chinese text, _, a bracket or whitespace. Numeric IDs stop at the digits exactly as they did before, so CHAR-0093-v1 is still CHAR-0093; for slugs, when the project's registered IDs are available, the longest registered ID at a hyphen boundary wins (CHAR-alice-v1 → CHAR-alice, while CHAR-alice-sister stays itself). It is used by check_dialogue_fit, check_generation_groups, dialogue_tts, voice_variants, offscreen_lines, native_lead, dialogue_direction, script_breakdown ([CAST] lines, dialogue parentheticals and leading IDs), storyboard_board (string dialogue_ref), the storyboard preview's dialogue index in services/runtime/core.py and timbre_selector (the old CHAR_0001 underscore form is kept); the prose_hygiene "internal ID" detection recognises slug IDs as well. name_index now also registers the ID itself and original_name as keys, after canonical names and aliases and without overriding existing mappings. #127: for a speaker with no pace setting, a draft placeholder est of 0 is no longer taken as a recorded value — missing, non-numeric or ≤ 0 counts as no record and the project's median pace is used, the real value being written back only with --write-est; a speaker that has a pace and a recorded 0 is still reported by line_est_consistent. Checked read-only against all 241 existing project episodes: numeric-ID projects give the same results as before, and slug projects now resolve their speakers and cast. No data file is rewritten and no whitebox or plate fingerprint changes; in a slug project check_time_budget at script scope now uses the character cards' pace, so its estimates change. Restart the service.

  • Shot-plate library entries are no longer lost when plate jobs overlap (2026-10-08).

    Generating a plate takes from tens of seconds to several minutes, and the result was saved by writing back the whole library as it had been read before generation started, which overwrote any entry another process had added in the meantime. In one project the nine tiles of a scene's nine-grid were lost twice, first to a fill-in image and then to a 修改 (the data was restored from the per-tile sidecar files). The library is now re-read after the nine-grid has been generated, and fill-in, master and revised plates are saved through upsert_library_entry, which takes a file lock, re-reads the library and replaces or appends only its own entry.

  • Dialogue voice library: quiet lines are no longer trimmed as silence (2026-10-10).

    In one episode the line 「Some of them have booked this hall every year of their lives.」 lasted only 3 seconds on the Storyboard preview page and stopped before its second half. The synthesized audio was complete at 5.74 s, but its direction asked for a quiet, low delivery and the whole line was very soft (peak −30.8 dB, mean −50 dB, 10–20 dB below the episode's other lines). Leading and trailing silence was trimmed on entry to the library with a fixed −35 dB threshold, so the last 2.5 s of quiet speech (100 ms RMS −44…−60 dB, where real silence measured −77 / −96 dB) was cut as silence and the library file was 3.19 s. The ledger had flagged the line (checks.est_vs_actual, ratio 0.56), but only as a WARN the page does not show. modules/dialogue_tts.py now measures each line's peak (peak_db, ffmpeg volumedetect) and uses silence_threshold = min(TRIM_NOISE_DB −35, the line's peak − TRIM_PEAK_HEADROOM_DB 18); the same threshold serves the head / tail trim and the pause detection of the paced copies, and the ledger's trim also records peak_db / noise_db_used. With 18 dB of headroom, 290 of the 296 existing source lines (peak −17 dB or louder) keep the same threshold and the same trim, and only 4 lines change by more than 0.05 s (the line above: 3.19 → 5.74 s). 20 dB was rejected because it lowers the threshold for 17 more normally loud lines, one of which then kept an extra 0.06 s, overshot its target duration by just over 2% and lost 0.85 s of in-line pauses to pacing. The trim parameter stamp changed, so each project's existing library is re-trimmed from _raw/ on its next sync — nothing is re-synthesized and nothing is billed — and lines whose trim is unchanged come out as before. Two related points are not addressed: the reel's dialogue track (dialogue_track.build_track) is not loudness-normalised, so a quiet line still plays quietly in the reel, and pacing squeezes every in-line pause over 0.3 s down to 0.3 s once a line exceeds its target by more than 2%, which can remove pauses the direction asked for.

  • Time scale: English and other Latin-script lines are now costed by word (2026-10-09).

    modules/time_cost.py counted every Latin letter as one effective character and divided by the Chinese speech rate (PACE_RATE, 4.2 characters per second, or the voice card's speed_cpm in Chinese characters per minute), so 「Dr. Seldon can't know you were here.」 was estimated at 7.53 s (27 letters, plus the dot of "Dr." counted as a pause). Measured on 39 real seed-audio English samples, speech runs at 1.72 words per second; with no speech rate set, the old estimate was on average about twice too long, and check_dialogue_fit, check_time_budget, line direction and the off-screen line window all ran on it — lines were wrongly judged not to fit, and episode extension and dialogue trimming were triggered for nothing. Russian and other non-Latin alphabets had an effective-character count of 0 and were not costed at all. The script is now recognised per line: a line containing Han characters, kana or hangul, or only digits and punctuation, is costed exactly as before; any other line (Latin, Cyrillic, accented letters…) is costed by word. A word is a whitespace-separated piece containing a letter or digit (can't and well-known are one word each), with letters ÷ 6 as a floor so that one long word, or a script written without spaces, is not costed as a single word. The word rate is the midpoint of the character's voice.json speed_wpm, otherwise a conversion of speed_cpm (× 130/252, which keeps characters' relative speeds; a speed_cpm above 400 is taken as letters per minute and divided by 3.8), otherwise a deliberately conservative default of 130 wpm, then multiplied by the pace-tier factor. The dots of abbreviations such as Dr. / Mr. and decimal points no longer count as in-line pauses. check_dialogue_fit now uses time_cost instead of its own copy of the counting code (a speaker without speed_wpm gets the median of the characters' word rates), words its messages for such lines as 「N 词 × W wpm」 and gives their trim targets as target_words / cut_words (lines under 3 words are exempt); check_time_budget, the off-screen line window, and line direction's target duration and acceptable range (1.2–4.0 words per second) use the same figures. Voice cards in Latin-script projects must carry speed_wpm (voiceprint SOUL, conditional). The effective-character range, which had come to include U+A000–F8FF after a literal endpoint in the source was normalised from U+F900 to U+8C48, is now written as an escape. Recomputed over all 241 project episodes: Chinese lines are unchanged (0 of 190 script-level lines in one project), a few all-English lines in Chinese projects got shorter, and one English-language project's script-level dialogue over ten episodes went from 551.5 s to 365.2 s. An English-language episode already in production reports line_est_consistent FAIL until check_dialogue_fit --write-est is run once; estimates only get shorter, so no new duration failure appears. Restart the service. (#124, folding in #126)

  • Time scale: prompt_beats_bound no longer counts negation or degree uses of 「再」 as a sequence connective (2026-10-09).

    The pattern behind the check counted every 「再」 except 「再次」. A Shot paragraph that has to quote the upstream blocking performance.end_state verbatim — 「…不再回头看我」 in the reported case — was therefore charged one connective for 「不再」 and failed whenever its beat budget was exactly used up, and the prompt station is not allowed to reword a quoted fragment. The pattern in modules/time_cost.py is now (?<![不别没未莫勿无一])再(?![次也不无没度三]): 「再」 preceded by a negation or by 「一」 (不再 / 别再 / 没再 / 未再 / 一再), or followed by 次 / 也 / 不 / 无 / 没 / 度 / 三 (再次 / 再也 / 再不 / 再度 / 再三), is not counted, while 「再抖腕」 and 「停住,再」 still are. The change can only remove matches, so a result can only move from FAIL to PASS and nothing downstream has to be re-run. Recomputed on real episodes, the only episode in production whose result changed is the reported one (over-budget shots 1 → 0); older episodes, where the check only warns, also lose some over-budget shots (32 → 20 and 15 → 12 in two of them). (#119)

  • Transition check: frames are picked by frame number, fixing false reports at hard cuts and at the last content frame before an end-of-episode cut to black (2026-10-09).

    render_transitions.py check extracted its sample frames by seeking to a time in seconds, and most sample points (hard cut + 0.25 s, the sample points of dissolves, fades and black holds) fall exactly on a frame boundary. In a concatenated cut with a 1/12288 time base whose deleted ranges are not frame-aligned, frame timestamps run 4/12288 s early: frame 852 has pts 35.499674 < 35.5, so seeking to 35.5 skipped it and returned frame 853 from that file while the rendered output returned frame 852, hard_cut_positions_intact reported 「sh007->sh008 差 13.5」, and transition_render_ok and finalization exited 1 (#115). For an end-of-episode cut_black the last content frame was derived as container duration − hold − 1 frame; the container duration is rounded to milliseconds (89.542 against an actual 89.541667), which stepped past the last content frame into the first black frame, and transition_frames_verified reported a difference of 70.1 against the source's last frame (#116). Every frame the check fetches — hard-cut samples and the sample 0.5 s before the end, both sides and the midpoint of dissolves and style seams, the three dip points, fade end frames, black pads and freezes, insert-clip midpoints, bridge first and last frames, before and after text overlays — is now addressed by frame number: seconds are converted to a frame number (rounded, with an exact half frame always rounded up, so that source and output stay aligned after a whole-frame shift) and the frame is fetched by seeking to (n − ½)/fps, which is not thrown off by a timestamp less than half a frame away from its nominal value. The end-of-episode close is located from the video stream's frame count instead of the container duration (last content frame = frame count − hold frames − 1). Failure messages now include the frame number. Only the check's frame picking changed; rendering, finals and ledgers are as before. On the episode where both were reported, both version chains now pass, and a deliberate one-frame offset is still caught (13.5 / 14.5 against a threshold of 8 at the hard cut, 70.1 against 12 at the close). (#115, #116)

  • Layout pack: the top-down plan pixel gate no longer depends on Pillow (2026-10-09).

    scene_layout_pack_ok in code/blocking_map_check.py imported Pillow inside a try. Under an interpreter without Pillow, or on any error while reading the image, it only recorded a "cannot read" WARN, so a top-down plan below the 2560x1440-equivalent minimum still passed with exit code 0 and the gate was silently off. Width and height are now read from the file header by modules/whitebox.image_size (PNG IHDR / JPEG SOFn, standard library only — which also covers the common case of a layout_top.png whose payload is actually JPEG), and an image whose size cannot be read is a violation, 「像素闸门未执行(无法读取图片尺寸:…)」, not a WARN. On 1,726 existing layout_top.png files the old and new readers return the same size for every file, and no layout pack's result changed. (#132)

  • Novel parsing: p0-parse no longer judges the UNKNOWN-speaker ratio per batch; the ratio is judged on the whole book at p0-merge (2026-10-09).

    The p0-parse machine check speaker_missing_lt_2pct used a single batch as its denominator. Since the three-stage rework a batch is 10,000–15,000 characters and often holds only 40–60 quoted lines, and with fewer than 50 lines a single UNKNOWN crosses the 2% line (1/47 = 2.13%) — in direct conflict with the same SOUL's hard rule to mark a speaker UNKNOWN with candidates and never guess. A quotation whose speaker the text itself declares unidentifiable can only be UNKNOWN, so such a batch failed every time and redoing it could not help, while the same data passed the check of the same name at p0-merge, on the whole book, at under 1%. On one novel 6 of 77 batches were at or above 2% (the reported one at 3/47 = 6.38%) with the whole book at 39/4852 = 0.8%. In workflow.yaml the p0-parse auto check speaker_missing_lt_2pct is replaced by unknown_speaker_annotated: every speaker: "UNKNOWN" must carry speaker_candidates (an empty array is allowed when the text itself says the speaker cannot be identified; the existing spelling candidates is accepted too) and a one-sentence note on why the speaker could not be decided. p0-merge keeps speaker_missing_lt_2pct (whole book, under 2%). One conditional guard remains at batch level: only when a batch has 5 or more UNKNOWN lines and they make up 10% or more of it does the receipt carry a single WARN line (count, ratio, suspected cause) — no FAIL, and no re-parse on the agent's own initiative; no batch of the novel above reaches it. The novel-parser SOUL and WORKFLOW Phase 0 are updated. Spec change only: no service restart is needed, and no existing project's result changes. (#130)

  • YouTube draft upload: consecutive episodes each get their own tab (2026-10-09).

    ytstudio_draft.py preferred to reuse an existing studio.youtube.com tab. When several episodes are published one after another, the previous episode's upload dialog is still on the visibility step waiting for the user to press save, and the next episode's check-login / upload attached to that same tab — either navigating the earlier dialog away or getting stuck on the browser's "leave site?" prompt (seen as a 30 s Page.enable timeout) — so people wrapped the script in their own workaround. check-login and upload now probe a Studio tab before reusing it: a tab whose URL carries the upload-dialog deep link (d=ud, /videos/upload) or whose ytcp-uploads-dialog is visible is skipped with the reason printed (a tab that does not answer the probe within 5 seconds also counts as occupied), and a blank page or a newly opened tab is used instead; a new tab is brought to the front with /json/activate so that it is not throttled in the background. screenshot does not skip such tabs — they are exactly what it is asked to capture — and warns when several Studio tabs are open and none was specified. Two new global options, accepted before or after the subcommand: --new-tab (always open a new tab; check-login / upload only) and --tab-id (attach to exactly that tab; all three subcommands). They are mutually exclusive, and every run prints TAB_ID: <id>. The script only disconnects and never closes a tab, since the user still has to press save in it. SKILL.md documents the tab rule, screenshot --tab-id, a section on uploading several episodes in a row and the Page.enable timeout. The Bilibili / Douyin / TikTok draft scripts attach with the same reuse-by-URL logic and were not changed. Tested against a fake CDP endpoint only: the dialog-visibility probe and /json/activate have not been run against a real YouTube Studio page. (#129)

  • Browser version: saving generation-model settings always returned 401 (2026-10-09).

    The default provider for image, video, music, speech, digital human and deepagents is agentics, and api_genconfig_set validated the whole merged configuration against "using agentics requires a signed-in desktop account". In the browser version, which has no desktop sign-in, saving any unrelated setting therefore returned 401 AgenticsLLM requires a signed-in desktop account: syncing the model-assignment mode (agentmodel_mode) on an engine switch and syncing the UI language were both blocked, and the front end showed 「切换失败」 and stalled the startup flow. Only the categories that a save newly switches to agentics are checked now: categories that already were agentics, or are by default, no longer block the save, other settings are saved normally and the provider is kept as it was. Choosing AgenticsLLM in the browser version still returns 401 and writes nothing. 3 new cases in tests/test_genconfig_agentics_gate.py.

  • Portrait (9:16) projects: preview pages no longer show over-tall panels and videos (2026-10-10).

    Several previews sized their media by width only, so a 9:16 picture became far taller than the window. Storyboard preview page: the whitebox panel now has a maximum height of min(60vh, 520px), with the width scaled to the aspect and centred (the lazy-mount placeholder uses the same height). 成片发布 page: the final video has a maximum height of min(70vh, 640px), and once the metadata shows it is taller than wide the card is narrowed to the video's width. The 动态样片 player on the storyboard page and the 白模样片 player on the storyboard preview page were fixed at width: min(100%, 720px), so a 9:16 reel was laid out at 720×1280 and then cut off by the section body's 70vh / 60vh limit into an inner scroll area in which the picture and the playback bar never fitted on one screen; portrait reels are now capped at a height of min(70vh, 640px) with the width derived from the aspect, the section bodies have no max-height any more, and the box has its final size before the metadata arrives (the aspect is taken from the project on the storyboard page and from the reel manifest's width / height on the storyboard preview page, then corrected once from the video's real size). The scene preview page's 3D 白模 panel no longer follows the video aspect at all and is fixed at 16:9 (960×540), while the per-group whitebox panels on the storyboard preview and storyboard pages keep the project aspect, because they show the final framing; 创建全景图 is not affected. Landscape layouts are unchanged (a 16:9 reel is still 720×405). These are static-file changes: refresh the page, no restart needed.

v1.0.38

Added

  • Music library: themes, variations and a reuse quota (2026-10-07).

    With the library in place the music station had started scoring whole episodes by reuse alone: in one project episodes 7 and 8 took every cue from episode 6's ten tracks, one track was used five times, two tracks twice in the same episode, a 28-second opening cue was clipped to 6 seconds for a build-up beat, and not one reuse carried a reason. The rule said "reuse when something fits" and only new music had to be justified, so nothing was ever generated. The library is now two-tiered: a track with a theme_id is a season theme (a motif that runs through the season, registered in the library's theme table with a name, what it stands for and an English motif description) and may be reused as is across episodes; a track without one is a one-off cue and stays in its episode. Each cue is now one of three things: reuse as is (use), a theme variation (variant --track shows the base track's motif and prompt; the station regenerates the same motif with a different tempo, instrumentation, intensity or length, and the cue records variant_of; the new track inherits the theme), or new music. A per-episode quota is enforced by music_library_synced: at most 40% of cues reused as is (at least one allowed), at least 30% new or variations, a track at most once per episode and in at most three episodes, theme tracks only, a reused cue at least 60% of the track's length, and the cue's intensity (the 0–1 figures in mood or intensity) within 0.25 of the track's; reuse, variations and new music all need a reason in music_library_report. use refuses a one-off track, a track already reused in three episodes, or (with --need-duration) a cue shorter than 60% of the track, with exit code 2. New CLI commands theme (list / --add), variant, and annotate --theme / --variant-of; search --theme. The 音乐库 page gains a 主题 column, a 复用集数 column (n/3, red when full), a variation badge, a theme filter and the quota in the toolbar. Episodes delivered before this change fail the check as they stand (episodes 7 and 8 of the project above); rescoring them is the user's call. Music SOUL, WORKFLOW Phase 8, workflow.yaml, code/README.md and all 11 dictionaries updated; tests cover the quota rules. Restart the service for the page data.

  • Fal model discovery (2026-10-06).

    生成模型 › 图像 / 视频 › Fal gains a 搜索 Fal 模型 box. It searches Fal's public model catalog by keyword (empty lists the popular ones), merges a model's task endpoints (text-to-video, image-to-video, reference-to-video, edit…) into one row, and 选用 fills the model field: models already in the built-in list are selected, others go into 自定义. Until now a model outside the built-in list was sent a fixed set of field names, which most endpoints reject (Veo 3.1 wants duration: "8s", Kling 2.5 wants "5" and tail_image_url). genmedia now reads that endpoint's parameter list from the catalog and builds the request from it: only fields it accepts are sent, duration, resolution and aspect ratio use its closest allowed value (logged), the endpoint is chosen by input (including a separate first-last-frame endpoint when there is an end frame), and an input it cannot take (end frame, reference images, video or audio) fails with a clear error instead of being dropped. Models with a built-in mapping are unchanged; an endpoint the catalog does not list (a private app) or an unreachable catalog falls back to the old generic mapping. The catalog needs no key but rate-limits anonymous use, so the Fal key is sent when one is configured; endpoint lookups are cached for 24 hours. New module modules/fal_models.py, endpoint GET /api/v1/providers/fal/models. Request shapes were checked against the live catalog; no paid generation was run with a discovered model. Restart the service to get the endpoint.

  • Fal jobs survive an interrupted run (2026-10-06).

    A Fal image, video or sound-effect job used to be lost if the genmedia.py process was killed while waiting (tool timeout, agent lifetime): the request id only went to the log, and running the command again submitted and billed a second job. The request id and its status/result URLs are now written to data/.videoagents/pending_tasks/ as soon as the job is created, before polling starts, and removed once the result is downloaded. Running the same command again picks the unfinished job up instead of submitting a new one. "Same" means same output path, endpoint and request body; the seed and the signature part of presigned URLs are ignored, and a changed prompt or reference submits a new job. If the old job can no longer be queried, or is still unfinished six hours after submission, it is dropped and a new one is submitted. genmedia.py reclaim --output <path> now also works for Fal video jobs without --task-id; it only queries and downloads. Other channels are unchanged. Covered by tests with a fake queue; not yet run against the real Fal service.

  • Sound effect generation: genmedia.py sfx (2026-10-06).

    The sound-effect and ambience stations were told to make a fallback patch when a defect ticket asks for one, but there was no way to generate it. python3 modules/genmedia.py sfx --prompt "<sound description>" --output <file.wav> [--duration s] [--loop] [--count 1-4] now produces it with ElevenLabs Sound Effects v2. There is no new settings tab: it uses the ElevenLabs key already entered for music or TTS (direct API, 0.5–30 s), and otherwise the Fal key entered for image or video (hosted endpoint, 0.5–22 s); --provider forces one. --loop returns a seamlessly looping bed and leaves it untouched. Other sounds have leading and trailing silence trimmed (pauses in the middle are kept) and the peak normalised to -3 dBFS; this needs ffmpeg. Each file gets a .meta.json with the source, the prompt and the measured duration and levels. Agents only call it when a defect ticket asks for a patch. Not yet run against a paid account. Restart the service for the agent instructions to pick it up.

  • Post page: letterbox bars to a final output aspect ratio (2026-10-06).

    后期处理 → 包装 gains a 上下黑边 card under 花字: tick it and pick a final output aspect ratio (16:9, 9:16, 4:3, 3:4, 1:1; output.letterbox_enabled / output.letterbox_aspect, off by default). When the final is rendered, finalize_episode.py assemble scales the picture to fit and pads it with black bars to that ratio, so a 2.35:1 film can ship as 16:9 and a 16:9 film as 9:16. The canvas keeps the cut's short side (2520×1080 becomes 1920×1080; 1920×1080 becomes 1080×1920). Production stays at the project aspect ratio: group clips and cuts never carry bars. The layout is recorded in final_layout.json#letterbox, finalize_episode.py check gains letterbox_applied (fails when the setting changed and the final was not rendered again), and the fancy-text final sizes and anchors its captions to the picture area instead of the bars. Render the final again after changing the setting.

  • Output settings: cinema aspect ratio (2.35:1) (2026-10-06).

    输出设置 → 输出画幅 (and the new-project wizard) gain a third preset, 电影画幅(2.35:1) (output.aspect_preset: "cinema"). Image and video models only accept a fixed list of ratios, and their widescreen entry is 21:9 (≈2.33:1), so the project is produced at 21:9 end to end: agents pass --aspect 21:9, whitebox renders at 952×408, the 1080p master is 2520×1080, and newly generated anchor images use 2968×1272. Also fixes transition insert-clip first frames, which read a settings key that never existed and so were always 1920×1080 regardless of the project aspect (portrait projects now get 1080×1920).

  • Director's console: bulk approve and bulk answer from the top bar (2026-10-05).

    The "✓ Approved n/N" pill is now a menu with Approve all, which approves every group in the episode that has been through whitebox staging and is not yet approved (or whose approval went stale); groups still on an auto-inferred draft are skipped and reported. A new "⚠ Questions a/N" pill next to it shows how many of the episode's Agent questions have an answer (chosen, applied, or accepted with a group approval) out of the total; its menu has Pick recommended for all, which answers every unanswered question — blocking and advisory alike — with the Agent's recommended option, falling back to the provisional choice where no option is recommended, and leaving questions with neither for you to answer one by one. Answers you already gave are not overwritten, and the picks are applied when the episode is submitted, like any other answer. Note that approving a group accepts its whitebox as it stands, so answers that have not been applied yet give way to the approval — pick and submit first if you want the recommended options carried out. New endpoints: POST /director/<ep>/approve-all, POST /whitebox/<ep>/issues/recommended.

  • Video prompt sign-off (H3V, DAG gate g7p, per episode).

    Once every generation group's video prompt in an episode is written (p7-prompt), the pipeline now stops for a user sign-off before any anchor pack (p7-image) or group video (p7-video) is produced. Review the prompts (text plus reference images / videos / audio) on the Shot preview page; the episode row shows the sign-off state with "Sign off / Hold" buttons that answer the same card as the console. The host enforces it twice: signing is refused (409) while any group still has no video prompt, and genmedia.py video refuses to write assets/clips/<ep>/grpNNN*.mp4 while the episode's g7p node exists in the DAG and is not passed (video_prompt_signed, modules/prompt_gate.py). The sign-off releases the first run rather than freezing the prompts: rewriting individual groups afterwards (your own edits, the reviser, defect rework) does not require signing again — the page only notes how many groups changed since sign-off (assets/prompts/<ep>/prompt_signoff.json). Episodes whose DAG was expanded before this change are not blocked; the orchestrator inserts the gate only for episodes that have not started video generation (2026-10-05).

  • Sound split default is now off for new projects (and for existing projects missing the key); auto must be chosen explicitly in 输出设置 (2026-10-03).

  • Sound split: off-screen (O.S.) and voice-over (V.O.) dialogue lines.

    Until now every line in a shot list implied that the speaker was on screen with a moving mouth, so storyboards never used off-screen dialogue, a reaction shot could not carry the other person's line, and a character's inner voice had no channel at all (narration is narrator-only). A new project output setting 「声画分离」 (output.sound_split: auto default / script_only / off) adds a placement field to every dialogue_lines[] entry — on (default), os (speaker in the scene but out of frame: phone, behind a door, walked out of frame) or vo (inner monologue, a letter being read, a remembered voice) — plus heard_in (the shots in the same generation group where the line is heard), source_fx (plain / phone / door / distance / inner / memory), offset_s and a placement_reason {trigger, evidence}. Two layers decide placement: the screenwriter marks objective off-screen sources in the speaker parenthetical ((O.S.) / (V.O.), whitelist S-phone / S-door / S-exit / S-remote / S-inner / S-letter / S-memory; downstream may not move them back on screen), and in auto mode shot-planning may move an on-screen line off screen only for whitelisted directing reasons D1–D5 (reaction-shot carry-over, time-budget overflow instead of trimming text, a fourth speaker's interjection, speaking while leaving frame, an establishing shot before the speaker appears), never for a line that carries the speaker's own performance beat. Off-screen lines are never written as {} into the group video prompt and never get the speaker's audio_ref; they are synthesized in post with the character's cast voice by the new host CLI code/offscreen_lines.py plan|synth|check (new DAG node p8-offscreen, also in native-voice projects — the TTS red line only ever protected lip sync), processed per source_fx, fitted into the heard_in window, and mixed by audio-mixing as a fourth 「画外对白」 track listed in mix_basis.py sources (offscreen_lines[], fingerprinted into mix.json; changing a line without remixing fails mix_basis_current). Group audio_plan is now judged on on-screen lines only (dialogue / voice_over — the old narration_over stays accepted as an alias / ambient_only), speakers_le_3 is now a real machine check that counts only on-screen speakers, dialogue-fit and time-budget checks no longer charge off-screen lines against mouth time, and dub_group.py skips them. The setting is orthogonal to the narration switch: narration covers the narrator's voice, a character's V.O. belongs here; a V.O. line must be in the character's own voice, not narration in disguise. Subtitles still appear for off-screen lines, prefixed (画外) / (V.O.); the shot-preview 「对白语音」 panel can change a line's placement, effect and offset. Spec: WORKFLOW.md §8D, docs/sound_split.md.

  • Transition design stage 4 redefined: sound bridges (J-cut / L-cut) replace audio_lead_s.

    The old audio lead laid the next group's whole native track lead seconds early, which put every dialogue group out of lip sync — that semantics is dropped, and legacy audio_lead_s values are normalized on read into transition_in.sound_bridge = {kind: "j", s, carry: "bed"} (the boundary fingerprint changes, so one remix is required; no compatibility switch). kind is j (next group's sound enters over the current tail) or l (current group's sound carries over the next head), s ∈ (0, 1.5], and carry is bed (a de-vocalized ambience bed pre-rolled or sustained by the new host CLI code/sound_bridge.py build|check into edit/epNN/sound_bridges/) or line (an off-screen line from sound split crosses the cut; only when 「声画分离」 is on and such a line sits next to the cut, otherwise it falls back to bed). New 「声桥默认」 settings transitions.sound_bridge_s (0.5) / sound_bridge_carry (bed); cinematic mode proposes a J bridge on scene changes and time jumps and offers an L candidate when the previous tail has no on-screen dialogue and the next head is an establishing / wide or silent shot; custom maps gain l_cut beside j_cut. The transition card gets a 「声桥」 row (none / J / L + seconds + carry). Audio-mixing now runs the build before laying tracks and keeps every group's native track in sync at its own cum_start_s (J bed: pre-roll file from cum_start_s − s; L bed: sustain file over the first s of the next group with its native track fading in); checks transition_sound_bridge_valid (replaces transition_audio_lead_valid) and sound_bridge_built (p8-mix). Spec: WORKFLOW.md §9C, docs/transition_design.md, docs/sound_split.md.

  • Sound split stage 3: native lead-in (native_lead) — in-group J-cuts generated natively by Seedance 2.5.

    An A/B on fengshen3 ep07 grp008 showed the model will start a line off screen in the preceding shot (lead-in 1.075 s, no repeat, speaker kept out of frame, same voice) but refuses to carry a line's tail past the cut into a shot without the speaker (0/2 — it pulls dialogue earlier and finishes it in the speaker's own shot). So only the J shape ships: an on-screen dialogue_lines[] entry may carry native_lead {shot: the previous shot in the same group, s ≤ min(prev shot, 1.0, 40% of the line), reason {trigger N1 insert/POV shot, N2 listener-first, N3 establishing opener, or U manual}, source}; the line stays on screen for every other rule ({}, audio_ref, speakers_le_3, timing). Prerequisites are machine-checked (native_lead_valid: sound_split=auto, native dialogue voice, effective model Seedance 2.5, previous shot in the same group with no speaker / lines / narration, first line of its shot, lead text without the performance trigger word). The host CLI code/sync_native_leads.py plan|apply|set|clear|check|sync|audible suggests candidates, writes the field, and injects two fixed marker sentences into the group prompt (previous shot: 「【原生先入】… {lead} …」; the line's shot: {full} → {rest} plus a continue-without-repeating sentence; idempotent, restorable, English Native lead: for non-Chinese UIs; prompt writers never hand-write them) — checked by native_lead_bound on p7-prompt. After generation audible verifies the voice onset lands in the previous shot (≥0.3 s) and only logs a WARN otherwise. L-cuts and fully off-screen lines stay with stage 1 (post-synthesized lines) and group boundaries with stage 2 (sound bridges). The 「对白语音」 panel gets a per-line 「原生先入」 toggle and the storyboard page a 🎧 badge. Spec: WORKFLOW.md §8D ⑨, docs/sound_split.md 三期.

  • Time scale: dialogue and action beats are costed in seconds, and the budget is checked at four stages.

    On fengshen3 ep07 group grp011 (28 s, 21 shots) the generated video dropped a 5-second line and scrambled its second half. The cause was not prompt length: three upstream layers were over-allocating time at once — line estimates counted only characters ÷ speech rate (about 30 % short against the dialogue speech library), dialogue shots were sized exactly to the estimate (34 of 44 shots at 95–100 %), and nobody had ever put a number of seconds on action beats (pacing split 28 s top-down, blocking put three beats into a 1.2 s shot, the prompt expanded them into five actions). modules/time_cost.py is now the single scale: a line costs 0.7 s onset/tail + characters ÷ (the character's speech rate × a pace factor fast 1.19 / medium 1 / slow 0.81) + 0.4 s per inner pause, and dialogue-rewrite writes the pace tier pace: fast|medium|slow next to the emotion so shot planning sizes shots by it; an action beat costs its minimum physical time from a keyword table (glance 0.5, throw 0.7, turn 0.7, step 0.8, fall 1.2, rise 1.5, effect 1.0, hold 0.3, default 0.7). code/check_time_budget.py applies it at four points: --scope script before pacing allocates (each scene's alloc_s must cover Σ line estimates with pre/post-speech margins + Σ beats; when the episode total exceeds the budget the host applies the user's order — first extend the episode up to the new 「视频节奏 → 可加长单集」 setting (default 50 %, settings.json#duration.episode_extend_pct, pacing records duration_budget_s + duration_policy), then trim dialogue (trim_targets), then merge short action shots (merge_candidates)); --scope shots for storyboard and shot planning (shot length ≥ beats + lines + margins; dense groups with an average shot under 1.5 s are capped at 15 s, also built into check_generation_groups.py); --scope blocking (Σ beat costs ≤ shot length − line estimate, beats of one character ≥ 0.3 s apart); --scope prompt (sequence connectors per Shot paragraph ≤ blocking beats + 1 — the prompt may detail a beat, never add one). check_dialogue_fit.py and dialogue_direction.py use the same scale, and a dialogue shot must now hold Σ estimate + 0.4 s before speech + 0.3 s after. After generation, code/check_dialogue_audible.py finds voiced segments without transcription and flags a dialogue shot whose longest voiced run inside its planned window is under 30 % of the estimate. Episodes written before 2026-10-03 only get warnings and keep the old estimate for consistency checks.

  • Line direction: director-style prompts and target durations for dialogue speech.

    Dialogue speech used to be synthesized line by line with at most a two-word emotion label, so delivery was flat and durations had to be squeezed into the shot afterwards. Each dialogue line in shot_list.json can now carry a delivery — direction (state + how to play it + pace / volume / pauses, naming the stressed words and how the line ends), scene (where, to whom, what just happened) and target_s — written right after shot planning by the new workstation 07-directing/dialogue-direction (workflow node p6-dialogue-direction, after p6-dialogue-fit, before H3A; only when 「生成对白语音」 is on or dialogue is dubbed). The workstation works only through the host CLI code/dialogue_direction.py (plan gives per-line context: script emotion, shot brief, the speaker's action, previous line, shot length, seconds for each pace; apply --file writes the batch; check is the dialogue_direction_bound machine check); it picks a pace (fast / medium / slow) and the host computes the target duration from the character count and clamps it to what the shot can hold. A direction is voided when the line's text changes later. With a valid direction, 火山 Doubao-音频生成 1.0 is prompted director-style — a one-sentence timbre taken from the voice card instead of the full acoustic spec, the direction, the scene, the target duration, and the reference sample declared as timbre-only — and speaks to the target duration; other providers receive the direction as a tone instruction only. Lines without a direction are synthesized as before and keep their cache keys. The 「对白语音」 panel on the storyboard preview page now lists every line with its script emotion, the library audio and an editable direction / scene / pace or seconds; saving marks only that line stale. dub_group.py passes the direction along and, on Doubao-音频生成 1.0, uses the mouth-open segment as the target duration instead of re-synthesizing at another speed. In a six-line test on fengshen3 ep07 the durations landed within about 0.4 s of the targets and five of six lines fit their shots, against one of six for the two earlier variants.

  • TTS voice modes: 「音色设计」 or 「音色库」 per provider.

    Every provider tab of the TTS card on the 「🎨 生成模型」 page now starts with a 「模式」 selector, and the fields below follow it. 音色设计 (voice design) asks for two models — a Voice Design model that turns the character's voice card into a voice sample, and a Voice Clone model that speaks dialogue and narration with that sample as the reference; 音色库 (voice library) asks for a voice library and a model, and each character gets one voice from the library, recorded in casting.json. Agentics and ComfyUI support both modes (their library is the local reference-audio library data/TimbreModel, used with the Voice Clone profile / workflow); 火山引擎 supports both (design: both slots are Doubao-音频生成 1.0, the only Volcengine model that takes a reference clip per call; library: Doubao-Seed-TTS 2.0 / 1.0); OpenRouter, ElevenLabs and MiniMax are library-only (OpenRouter speech models accept text only, so the local library cannot be used there; voices come from each model's own list). The mode is stored as tts.<provider>.voice_mode; existing configurations keep their behaviour (Volcengine on 音频生成 1.0 and ComfyUI with a Voice Design workflow become 音色设计, everything else 音色库). genmedia.py info prints the active mode.

  • Voice libraries are fetched and matched automatically.

    The new modules/voice_library.py pulls each provider's voice list on demand (Volcengine ListSpeakers, ElevenLabs account voices, MiniMax get_voice, the OpenRouter model catalog's supported_voices, the local TimbreModel catalog), caches it for 24 hours and ranks voices for a character from the voice card: hard gender filter, age band read from the card's age fields, language, shared timbre words, and voices already cast to another character pushed to the bottom. The dubbing workstation gets candidates with python3 modules/genmedia.py voices --character <CHAR-ID> [--variant …] [--search …] [--top N], registers its pick in casting.json, then synthesizes with --voice; the host never picks a character's voice silently at synthesis time. ElevenLabs matching only uses voices already in the account — the public Voice Library is search-only (voices --scope library --search …) and adding a voice to the account (voices --add <voice_id> --owner <public_owner_id>, which takes an account voice slot) happens only when a work order explicitly asks for it. Calls with neither a character nor a voice (narration without a narrator card, live lip-sync) take a narrator-type voice from the library automatically.

  • codex and pi Smart Assignment high tier now resolves to gpt-6.1-sol

    (was gpt-6-sol, which the official catalog now lists as the previous generation; needs codex CLI 0.159 or newer); the low tier stays gpt-6-luna. The tier is resolved at dispatch time, so existing installs on the codex smart policy pick it up after a restart; the top-bar tooltip and all 11 i18n dictionaries are updated. The pi engine's smart policy high tier moves the same way, to openai-codex/gpt-6.1-sol (low tier stays openai-codex/gpt-6-luna); it requires a pi version whose pi --list-models includes that model.

Changed

  • Publishing platforms moved from 输出设置 to the 成片发布 page, with per-channel publish buttons; Xiaohongshu is back (2026-10-07).

    The "发布平台" checkboxes are gone from 输出设置 and the new-project wizard (nothing about channels is shown there any more; new projects start with no channel selected). 🎞️ 成片发布 gains a 📡 发布渠道 section listing every channel — YouTube, Bilibili, TikTok, 抖音 and 小红书 — as a card: tick it to put it on the publish-stage platform list (output.platforms, the same key as before, so aspect-ratio / thumbnail / subtitle / platform-adapter keep reading it), and press 发布到 X to dispatch a work order for that episode and that channel to 12-publishing/publisher (POST /projects/{project}/publish/{ep}/dispatch; POST /projects/{project}/publish/channels saves the ticks). The work order names the channel's skill (skill-youtube/bilibili/tiktok/douyin/xhs-cdp-draft), says whether the publish/<platform>/package/<ep>/ bundle exists (otherwise the master edit/<ep>/final*.mp4 + thumbnail are uploaded as-is, noted in the receipt), and keeps the semi-automatic red line: the draft is filled in the local Chrome, the user presses the platform's publish button themselves. Publishing to an unticked channel ticks it. Each card shows whether the bundle is ready and the latest receipt (publish/receipts/<ep>_<platform>_receipt.json: 待人工提交 / 已由人工提交 / 成功 / 失败); the button is disabled until the episode has a final cut and while a run is in flight.

  • Real-face moderation workaround removed (2026-10-07).

    The conditional "colored-pencil face" salvage rule (character-concept / video-generation SOULs, WORKFLOW §9 notes) and the "real-face references trigger moderation" rationale in the prop SOUL are gone. Photorealistic faces are generated as-is; an Ark PrivacyInformation rejection means the reference image is not in the 虚拟人像资产库 (asset:// submissions pass), so the fix is to enrol the image, never to repaint it.

  • Output settings › Publishing platforms: selection is now optional, Xiaohongshu is hidden, and the per-platform aspect-ratio label is gone (2026-10-06).

    Both the new-project wizard and 输出设置 list YouTube / Bilibili / TikTok / 抖音 only, without the "16:9 / 9:16" suffix after each name. Leaving every platform unchecked is allowed: the project then does no per-platform publishing — aspect_ratio.json carries the master entry only, thumbnail / subtitle are produced once for the master, platform-adapter and publisher are not dispatched, and seo / metadata produce a single platform-neutral copy. Xiaohongshu is no longer part of the default list; a value already stored in an existing project is still honoured until that project's output settings are saved again.

  • Shot plates: the scene nine-grid sheet is now generated at the selected image model's maximum resolution (2026-10-06).

    The 3×3 grid used to be capped at 4.6 MP regardless of model (the Seedream 5.0 Pro limit). The cap now follows the model chosen for scenes (Scene preview › 🎨 image model, or the global one): Seedream 5.0 Pro stays at 4,624,220 px (≈2832×1632 for 16:9), while Seedream 5.0 / 5.0 Lite / 4.5 / 4.0 on Volcengine and BytePlus go up to 16,777,216 px (≈5386×3106, each tile ≈1734×974 instead of ≈910×510); Fal-hosted Seedream and Nano Banana use their 4K tier. Channels whose limit is unknown keep the previous size. The layout template, gutters and the per-tile crop boxes are derived from the same geometry, so splitting follows the new sheet size. Existing grids are reused as before; regenerate one with --no-grid-fallback --force.

  • Video prompts no longer quote dialogue words outside the {} line, and a line is never split across several {} (2026-10-05).

    With performance direction on, the prompt agent was required to repeat the trigger word outside the braces ("说到『X』时…") and allowed to cut one line into several {} with acting notes in between; once per-line delivery notes existed in the shot list, it also copied their quoted stress words into the prompt. Seedance 2.5 spoke a quoted word as dialogue (fengshen3 ep08 sh006: "…死后愚弄百姓!" came out with "愚弄百姓" said twice). Now one shot-list line is exactly one {}; the trigger moment, stress and pauses are pointed at by position ("the second half of the line", "the last four characters") and that phrase is recorded as performance[].trigger_anchor; delivery notes (dialogue_lines[].delivery) are for speech synthesis only and are not copied into video prompts. performance_bound check ② is inverted accordingly: it verifies the anchor phrase, and reports quoted line words (line_words_quoted) and split lines (line_split). Prompts written before this change only get warnings — nothing is rewritten or regenerated automatically; --fresh makes the same findings fail for groups written in the current task.

  • Props get one image each; the size-comparison image is gone and prop size is carried by text (2026-10-05).

    The prop stage (p6-prop-concept) used to produce two images per plot prop: the prop image main_01.png and a scale anchor scale_ref_01.png showing the prop next to everyday objects, and video prompts attached the scale anchor by default. The scale anchor is no longer produced: the objects it placed beside the prop (table tops, bowls, boards) tended to leak into the generated video, and it cost a second generation per prop. Each plot prop now gets only main_01.png, on a plain neutral backdrop with nothing else in frame, and that image is what group prompts and anchor packs attach by default (one per prop). Size is expressed only by the prop card's scale.prompt_token, copied verbatim after the <prop>@Image N: binding sentence; the phrase must carry a comparable reference (a body part of the holder or a common object, plus how it is held), and the binding sentence now also says that only the prop's own shape, material and colour are taken from the image. The concept-coverage audit (§6A) and prop_scale_defined count main_01.png only. code/refs_referenced_check.py (prop_ref_single) now warns when a group attaches a scale anchor or more than one image of the same prop. Existing projects keep their scale_ref_01.png files; already-written group prompts that attach them are not rewritten and only get a warning. Restart the service for the prompt-stage system text to pick this up.

  • Scene top-down plans are drawn in neutral lighting, not in a lighting scheme (2026-10-04).

    layout_top.png is only a spatial reference — the whitebox is measured from it, panoramas and nine-grid plates use it to tell what lies in each direction, and the host reads its colours to tell water from land; nothing downstream uses it as a lighting reference (per-scheme lighting reaches the plates as text from lighting.json). The environment-concept spec still told the agent to follow lighting.json, so plans came out moonlit or dusk-toned (liaozhai3 SCN-0088), and dark plans were misread as water (fengshen3 SCN-0141). The spec now requires flat, even, neutral lighting with no time of day, no cast shadows and no lit lamps, and the checklist field becomes lighting_neutral. The front scene plate main_01.png (whitebox off) still follows its registered master lighting scheme. Existing plans are not regenerated.

  • Nine-grid shot plates are now generated from one centre standpoint, and the plate mode splits into automatic and manual fill-in (2026-10-04).

    The 3×3 grid used to be nine semantic camera positions taken from layout.json#views; in a test on alices ep01 SCN-long-hall only 22 of 67 plate demands found a suitable tile. The grid is now, by default, one standpoint that only turns: the standpoint is the horizontal median of this episode's whitebox cameras in the scene, the camera height is their median height clamped to 0.9–1.6 m, tiles 1–8 look level every 45°, and tile 9 tilts up toward the direction most up-tilted shots face. The reference is the top-down plan with the standpoint and eight arrows marked. On the same scene this raised suitable tiles to 51 of 67. Existing libraries keep their tiles; --grid-layout views restores the old method. 输出设置「背景图模式」now offers 九宫格自动补图 (grid, default: a shot with no suitable tile gets its own plate rendered, as before) and 九宫格手动补图 (grid_manual: nothing is rendered; the shot keeps the nearest tile as a placeholder until you capture a plate with 「💾 背景图」 in the panorama or world-model viewer, and render_shot_plates.py exits 5 with [manual_plate_needed]; --status reports those shots as manual_needed). The scene preview page lists the shots waiting for a manual capture with their bearing, tilt and height, and its per-scene selector now shows only 跟随全局 / 九宫格自动补图 / 九宫格手动补图; 全景图 and 世界模型 are hidden there as they already were in the output settings (stored values keep working). Known limits, left as they are for now: tiles that face a nearby flat wall are often drawn as an enclosed bay or a receding corridor, and a tile one height class away from a ground-level shot still counts as suitable.

  • Scene panoramas: land anchors no longer get water under the camera.

    In a top-down plan, shaded woodland and rock are often the same dark teal as water, and the ground beyond the plan's edge was drawn by stretching the edge pixels to the horizon, so an anchor near a map corner read as surrounded by water; the prompt's ground rule also only described the "standing in water" case (fengshen3 SCN-0036 A5). The host now classifies water in the plan by colour and texture (water_mask): beyond the plan's edge, land sides are drawn in a warm earth tone while water sides still extend the edge pixels, and when the ground under the anchor is classified as land the prompt states that the nadir and near foreground are dry land. The heuristic mistakes blue-grey paving and shaded rock for water, so it only ever asserts land; anchors classified as water or shoreline keep the previous wording. A scene can override the classification with ground_water.png (white = water) next to its plan. Exterior blockout panoramas are re-rendered locally before the next generation (guides version 3); existing panoramas stay valid (2026-10-04).

  • Scene panoramas are always generated independently; chained hole-filling is removed.

    A new panorama used to take the nearest finished panorama of the same lighting scheme, re-project it to the new anchor and hand it to the image model as an authoritative reference. Outside the mapped floor there is no geometry, so most of that reference was the parent copied by direction, and a parent drawn from the wrong viewpoint was reproduced wholesale (fengshen3 SCN-0036: anchor A5, placed high inside the town, came out as its parent A1's view from the water outside the wall). generate_pano now uses only the anchor's own blockout panorama, the top-down plan and the text; relighting an anchor that already has another scheme is unchanged. Removed with it: the chain-parent guards, the parent.consistency score and render_scene_panos.py --trust. Existing panoramas recorded as mode: chain stay valid and become independent when redone (2026-10-04).

  • The regression suite is versioned again.

    tests/ had been local-only since v1.0.4, with a hand-maintained allowlist in .gitignore that let 13 files through; the other 57 files (about 8,300 lines) ran only on the maintainer's machine, so CI covered a fraction of the suite. The ignore block is removed and the whole suite is tracked. The tests are self-contained (temporary project directories, no private project data, no network); cases that need ffmpeg / ffprobe skip when the binaries are missing, and CI now installs ffmpeg so they run there. The three Node checks under tests/*.mjs (whitebox renderer, whitebox i18n, director world) were never run by CI; they now run in the web job through the new npm run test:web.

  • New regression tests for the HTTP layer and for intro-offset finalization.

    tests/test_api_routes.py checks that no route is shadowed by an earlier one with the same method (the /projects/import vs /projects/{project} class of bug), runs every GET route whose parameters can be filled against an empty data directory in an isolated subprocess (temporary DATA_DIR / HOME, narrowed PATH, network disabled) and requires no unhandled 500, a detail on every error response and no side effects for a non-existent project, and checks that every Web console page and preview page is served. tests/test_finalize_intro_offset.py covers code/finalize_episode.py (§9B): SRT / ASS shifting, segment offsets, per-cue comparison, and an end-to-end assemble → shift → check on synthetic clips, including the failure cases intro_offset_ok exists for (subtitles copied without shifting, audio track still starting at 0 s, intro missing from the final file).

  • Regression tests for the fancy-text and generation-group machine checks.

    tests/test_generation_groups_check.py covers rules 1–5 of code/check_generation_groups.py (every shot covered exactly once, contiguous shots within one scene instance, integer duration within the project's group cap and equal to the sum of its shots, at most four characters, the continuity_from chain) plus the greedy --propose draft and the CLI exit codes. tests/test_captions_validate.py covers validate_captions, the message-to-check bucketing, the template protocol, project font resolution and glyph coverage, anchor placement and the render-receipt fingerprint. tests/test_check_captions_cli.py runs code/check_captions.py end to end over a minimal project for all three stages (design / render / final) and asserts, check by check, which problem fails which check and when a receipt goes stale; burned copies and receipts are forged from the engine's own fingerprint functions, so no browser is launched.

  • Post-production dubbing: vocal removal, shot-window onset alignment, and dub-aware mixing.

    The p7-dub helper code/dub_group.py (Output Settings → Dialogue voice = Post-production dubbing) used to keep the video model's own voice under the TTS (only ducked by −26 dB inside the detected speech spans, full level anywhere the detector missed), located speech with silencedetect on the mixed native track (sound effects and ambience counted as speech, lines could land in the wrong shot), and treated "does not fit the mouth span" as a hard overflow. Now the native track is first split by the local MDX-Net separation model already used by the Post page (modules/audio_separation.py): the vocal-free bed becomes the mix base (--keep-native-voice-db, default −60 = removed; --no-separation / --resplit), and the onset of every line is detected on the voice stem with the pitch-based voiced-segment detector shared with dialogue_audible (modules/voice_activity.py), constrained to the line's own shot window from the clip's boundary_map (--pad), with per-line manual override unchanged (--detect-only now prints shot_windows, raw_voiced and notes; --segments). Fitting is onset-first: each line starts at its detected onset, speed ±25 % + tempo ±10 % still try to match the mouth span, but a line only counts as overflow when it would collide with the next line's onset or the end of the clip (segment.room_s; a longer/shorter line is just loose_fit). When the separation model is unavailable the old ducking path is used and vocal_removal_ok reports it. --dry-run now writes dub_manifest.dryrun.json instead of overwriting the real manifest. On the mixing side code/mix_basis.py sources adds a per-group dub_fp (dub timestamp + per-line spans + vocal-removal status) that is stamped into the mix manifest — re-dubbing after the mix makes mix_basis_current FAIL, and an adopted Post version whose base was built before the dub (so its file has no dub) is reported as dub_predates_version (also listed by dub_group.py as post_versions_predate_dub). The Post page's Native lane plays the vocal-free bed (grpNNN.bed.wav) for dubbed groups while the <video> element is muted, so the native and dub lanes no longer play the TTS twice; the Remove vocals / Remove ambience buttons are hidden on those entries. WORKFLOW §8C, workflow.yaml p7-dub (new auto check vocal_removal_ok), voice-generation / audio-mixing SOULs and 11 language dictionaries updated. Unit tests in tests/test_dub_group.py; verified offline on fengshen3 ep07 grp011 with synthetic TTS (vocal spans drop ~20 dB in the bed, picture stream and duration unchanged, idempotent re-run).

  • TTS settings no longer show a default voice, a voice list or previews.

    Because voice selection is automatic, the 「默认音色」 field and the 「拉取音色库 / 搜索 / 试听 / 设为默认 / 加入」 controls are gone from the Volcengine, ElevenLabs, MiniMax and OpenRouter tabs (previously saved default voices are kept in the configuration as a fallback but are not shown). The Volcengine voice library needs the account's Access Key / Secret Key; these are now entered on the TTS › 火山引擎 tab in 音色库 mode (with a link to the console's 「API 访问密钥」 page next to the API Key hint) instead of being borrowed from 文件托管 › 火山引擎 TOS — existing installs have the pair copied over. 「声音复刻 2.0」 is removed from the Volcengine model list (cast entries that already use an S_ cloned voice still synthesize). ElevenLabs defaults to Eleven v3 for new configurations.

  • 音色设计 mode no longer falls back to the local voice library.

    On Agentics and ComfyUI, synthesizing a character's dialogue before that character has a voice sample used to pick a TimbreModel clip silently, mixing the two modes; it now fails with 「先出样本」, and the dialogue voice library marks such lines as unbound with the reason. Narration without a frozen narrator sample is spoken from the voice description by the Voice Design model. The dialogue voice library and dub_group.py decide per mode (one shared rule instead of provider-name checks), which also fixes Agentics lines being skipped as 「未选角」 when the character had a sample but no casting.json entry. In local-library mode a casting.json voice (a catalog file name) is now honoured instead of being re-picked automatically.

Fixed

  • GET /api/v1/projects/{project}/prompt-skill no longer creates the project.

    The handler refreshed the effective-skill snapshot on every read, which wrote settings.json and the refs/ skeleton even when the project did not exist, so a mistyped or already deleted project name came back as an empty project in the project list. For a non-existent project it now returns the defaults without writing anything.

  • code/check_generation_groups.py crashed with IndexError on a group whose shots list was empty or missing, so none of the other groups' problems were reported either; it now reports group_shots_contiguous: 组内没有镜头 for that group and keeps checking.

  • code/check_captions.py: when the fonts or sound-effect manifest was missing, caption_assets_resolved was printed twice, first FAIL and then PASS (the exit code was already 1); only the FAIL line is printed now.

    A caption id that happened to contain a keyword of another check (for example group_id) no longer moves its messages into that check's bucket. validate_captions reported the unsupported cards field twice.

  • Multi-form characters now speak with the right age form.

    Dialogue TTS (the dialogue voice library used by the animatic / whitebox reel, and dub_group.py) decided a speaker's voice form only from the sample file name in the group prompt's audio_refs, using a pattern that did not accept underscores — a form named pre_lotus_child never matched, the speaker silently fell back to default, and the lines were synthesized from the other age's description with no reference sample attached, so the voice drifted from line to line (fengshen3 ep06/ep07, 哪吒). Form resolution now lives in one place, modules/voice_variants.py: group prompt sample name → the voice card's age_variants[].chapter_range against the episode's chapters in story/episode_plan.json (works before any prompt exists) → the form used by the episode's other groups → the only registered form → default. If the form cannot be decided, or the decided form has neither a sample nor a casting entry and there is no base sample to fall back to, the line is skipped with a warning instead of being synthesized with the wrong voice. Each library line records variant_source; the manifest's checks list speaker_variants, variant_warnings (prompt and chapter range disagree) and unanchored_speakers; code/dialogue_tts.py --variants prints the per-group decision without synthesizing; genmedia.py tts warns when a character has form samples but the requested form matches none. Existing lines of affected speakers are re-synthesized on the next sync; other speakers are untouched.

v1.0.37

Added

  • codex model dropdowns follow the official catalog.

    The codex model lists (top bar and per-agent model dialog) are now read from codex debug models via the new GET /api/v1/engines/codex/models (models with visibility=list, sorted by official priority, cached for 5 minutes), so newly released codex models appear automatically once the installed codex CLI is new enough to receive them. If the CLI is missing or the call fails, the dropdown falls back to a built-in list and its tooltip shows the error. Smart Assignment tiers are unchanged and still set by hand.

  • Checked fancy-text types are now expected to appear.

    In manual caption mode a checked type used to mean "allowed, not required", so the caption agent could silently drop a whole type (fengshen3 ep07: a creature appeared on screen but no creature name card was made although 「生物 / 妖兽名牌」 was checked). Now every checked type that has matching content in the episode (a creature's first appearance, a prop's first reveal, a faction's first showing, …) is expected to get at least one caption; if a type is left out, the reason must be written per type in the new top-level type_skips field of captions.json (e.g. {"creature_card": "no creature appears in this episode"}). A missing template is not a valid reason: the agent creates one or reuses a similar template. New design-stage machine check caption_types_covered (manual mode only; types unavailable for the project are not counted). The caption SOUL, the caption-styling skill, WORKFLOW §9A and the injected caption prompt are updated. Existing episodes are not re-run and will fail this check until their caption design is redone.

  • Insert-friendly numbering for new projects.

    Projects created from now on number scenes, generation groups and shots in steps of ten — S010, S020… / grp0010, grp0020… / sh0010, sh0020… — so a scene, group or shot can later be inserted between two existing ones (S011, grp0011, sh0011) without renumbering anything downstream. The scheme is stamped into the project's settings.json#numbering (step: 10) at creation and cannot be changed afterwards; the host injects a 「编号制:预留插入位」 section into every agent's run prompt for these projects only, genmedia's grpsh_id_3digits check expects four digits there, and check_generation_groups.py --propose numbers its draft groups accordingly (new modules/id_scheme.py). Existing projects have no numbering block and keep S01 / grp001 / sh001 unchanged.

  • Project music library.

    Every project now keeps a library of its score under assets/audio/library/music/ (index.json + MUS-NNNN.<ext>, new modules/music_library.py). When the music workstation (09-audio/music, p8-music) delivers an episode, the tracks it added — newly generated ones and the user-provided ones it picked — are copied into the library together with their data (mood, genre, tempo, instruments, duration, generation prompt, license chain), and each cue of the episode is recorded as a use of its track (episode, cue, in/out, scene). Later episodes look in the library first: the workstation lists / searches it with the new host CLI code/music_library.py (list, search, use, sync, annotate, add, check), reuses a fitting track with use (the cue's license becomes source: library + track_id and carries the original license chain) and generates new music only when nothing fits, stating the reason per cue in the cue sheet's music_library_report. Selection order is now user music in refs/music/ → project library → new generation. A track its source episode no longer uses after a rework is marked replaced and is not offered any more (the file stays). New machine check music_library_synced on p8-music; episodes delivered before this feature are backfilled and only get a reminder. Project settings → 「音乐库」 opens the new /music-library page: all tracks with playback, their data, source episode and the episodes / cues using them, filters by episode, source and keyword, and 「同步入库」 to backfill projects that already have scores (nothing is generated). New endpoints GET /api/v1/projects/{project}/previews/music-library and POST /api/v1/projects/{project}/music-library/sync ({ep?}); music and copyright SOULs, WORKFLOW Phase 8 and workflow.yaml updated; UI strings translated in all 11 dictionaries. Requires a service restart and a hard refresh.

  • 「+」attachment button on the console chat box and the preview pages' 「修改」 popup.

    A round 「+」 at the top-right corner of the input (the voice-input microphone, when enabled, sits below it at the bottom-right) (apps/web/static/file-attach.js, shared by index.html and edit-popup.js) lets the user pick local files to send along with the message. Nothing is uploaded: the agent runs on the same machine as the browser, so only the absolute paths are appended to the message as a trailing 「附件(本机文件绝对路径,请直接读取):\n- /abs/path」 paragraph (header follows the UI language) and the agent reads the files itself. Paths come from ① the desktop client's native open dialog (videoagentsDesktop.pickFiles, new desktop:pick-files IPC), ② in the browser, a native dialog opened by the web server on the host (POST /actions/pick-files: macOS osascript, Windows PowerShell, Linux zenity/kdialog; 409 while one is open, 501 when no dialog is available), or ③ a manual paste box (one absolute path per line; also via Shift+click, and used automatically for a remote backend or when no dialog can be shown). Selected files show as removable chips above the input; the console keeps them with the draft (localStorage webui_draft_files) and hands them back if sending fails; the popup clears them on every open. Requires a service restart (new web-server endpoint) and a hard refresh; the desktop bridge takes effect with the next desktop build.

  • Script preview: 「🗒 注释」 buttons and 「📨 提交注释」.

    Every 「✏️ 反馈」 button on the script preview page (whole episode, scene, each action / dialogue / narration / transition block, narration entries, hooks, event cards, pacing, trim suggestions, episode plan) now has a 「🗒 注释」 button next to it that opens the same note dialog as the storyboard page; notes are stored in story/episodes/epNN/script_notes.json keyed by a stable object id (*, S01, S01/b3, nar:ID, hook:ID, event:ID, pacing:S01, trim:S01/n, plan) together with the button's location text (object description + file path), shown in yellow next to the object and in the button tooltip. 「📨 提交注释」 at the right end of the episode row sends all of the episode's notes to the reviser as one revision order (kind=script, files = the episode's screenplay / narration / hooks / pacing / breakdown plus story/events.json / story/episode_plan.json when referenced) and clears the file once the run is dispatched. New endpoints POST /api/v1/projects/{project}/script/{ep}/notes ({key, text, label?}, empty text deletes) and POST …/script/{ep}/notes/submit; the reviser SOUL lists the file as must-read for script-layer objects. Requires a service restart and a hard refresh.

  • Storyboard page and storyboard preview: 「📨 提交注释」 submits every note of the episode to the reviser in one go.

    A new button at the right end of the episode row (both pages) collects all of the episode's notes — the storyboard page's whole-episode / scene / draft-shot notes from directing/epNN/storyboard_notes.json, or the storyboard preview's per-group director notes from assets/notes/epNN/<grp>.json — and the host composes a single revision order for the reviser (kind=storyboard or kind=group), listing each note together with the object it is attached to (scene number / location / scene id; draft shot order, content summary and final shot ids; for groups: scene, shot ids, duration, beat and the group-prompt path; stale notes whose object is gone are marked as such). Once the run is dispatched the notes are cleared: the storyboard notes file is deleted and each group note is removed the same way as 「清空保存」 (its injected Director's note sentence leaves the video_prompt). The button shows the note count, is disabled when the episode has none, asks for confirmation, and the order text follows the UI language (Chinese / English). New endpoints POST /api/v1/projects/{project}/board/{ep}/notes/submit and POST /api/v1/storyboard/notes/submit ({project, ep}); reviser SOUL and WORKFLOW §5 updated. Requires a service restart and a hard refresh.

  • Transition design stage 2: the 07-directing/transition-design workstation and generated establishing shots.

    A new per-episode DAG node p6-transition-design (condition transition_design_enabled = effective transition mode ≠ minimal; after shot-planning / continuity / plates, feeding the H3A gate) runs the host CLI code/transition_design.py propose and then reviews every group boundary: it checks the change class against the director's transition list and the screenplay's 「转场:」 lines, fixes inferred card text with card, and picks between candidates with the new design --alt N | --design '<json>' --note … verb — proposals stay proposed; accepting is the user's decision on the storyboard preview's transition cards, and signing H3A now accepts every remaining proposal into shot_list (the sign card shows the pending count, same precedent as H3W). Boundaries decided by the user or the director's list are locked against the workstation (feedback only), and re-running propose no longer overwrites a workstation design. When the mode allows generative transitions (cinematic / custom), 「换场景 → 定场空镜 + 叠地点字幕」 is now a real image-to-video establishing clip (source.mode = i2v): the host renders the first-frame still from the scene panorama anchor or the first shot's plate (clips --prepare) and composes the prompt; because this involves video generation, the clip itself is produced in Phase 7 by the new per-episode node p7-transition-clips (video-generation, condition transition_clips_requested, after H3A and before H3B) into assets/transitions/epNN/<B-id>.establishing.mp4, checked by clips --check (transition_clips_ready); render_transitions.py build consumes the file (trim to the insert length, location overlay composited as before) and reports missing / inserts_built FAIL instead of silently falling back to a panorama sweep. Classic / minimal modes keep the ffmpeg pan / Ken Burns establishing and never dispatch the clip node. Also: transition_design.py check gains transition_design_generative_allowed and a transition_clips_ready warning, accept-all for re-running the H3A acceptance, the storyboard transition card shows 「🤖 工位建议」, the first-frame still and a 已生成/待生成 chip for i2v inserts, and the agent system prompt states the project's transition mode and whether the two nodes are instantiated. Design note: docs/transition_design.md.

  • Transition design stages 3 and 4: generative bridges, paired camera moves and audio leads (J-cut).

    In modes that allow generative transitions, entering a flashback / dream / imagination block now proposes a bridge insert (2 s image-to-video morph from the previous group's last frame to the next group's first frame, host-composed prompt keyed by block kind, white-dip fallback kept as a candidate; leaving a block offers a "bridge back" candidate; custom mode's bridge option is honoured). The two stills are extracted by transition_design.py clips --prepare from the adjacent group clips, so p7-transition-clips now also depends on the episode's p7-video; the contract requires file and forbids combining with motion_pair. Paired camera moves are a new transition_in.motion_pair {out, in, speed} field with a fixed legal-pair table (MOTION_PAIRS, check motion_pair_valid); they are realised as two host-written sentences in the adjacent group prompts — code/sync_motion_pairs.py --write appends 「【运镜对接】…带出画面…」 to the previous group's last Shot N: paragraph and 「【运镜对接】…接入…」 to the next group's Shot 1: paragraph (English Motion pair: sentences for non-Chinese UIs), idempotently, and motion_pair_bound joins the p7-prompt acceptance list. Audio lead audio_lead_s (0–1 s) is now enforced (transition_audio_lead_valid: hard_cut / dissolve only, no inserts or black hold, not on the first group) and surfaced to the mixer: mix_basis.py sources lists boundaries that only carry a lead, includes the lead in the boundary fingerprint (unchanged for projects without leads) and prints a LEAD line; the audio-mixing SOUL specifies the J-cut (next group's native track starts lead early with crossfades, BGM / narration / total length unchanged), and mix_basis_current reports a lead-only change as stale with a dedicated message. Cinematic mode offers "paired move + audio lead" and "hard cut + audio lead" candidates and puts a 0.5 s lead on the overlay-only scene-change option; custom mode gains motion_pair and j_cut options in the 视频节奏 dialog; the storyboard transition card shows the paired move, the lead and bridge first/last stills. Classic / minimal modes never propose any of these.

  • Scene preview: the per-scene 「背景图模式」 selector shows 「全景图」 and 「世界模型」 again.

    The scene-level plate-mode dropdown on the scene preview page now lists all three modes (跟随全局 / 九宫格 / 全景图 / 世界模型) instead of hiding the panorama and world-model entries; the project-level Output-settings dropdowns still expose only 九宫格 and the default stays grid. Static file only (no service restart), hard-refresh the page.

  • Shot plate 「✏️ 修改」 now regenerates from the current plate instead of re-deciding the camera.

    Sending feedback on a shot plate from the storyboard preview goes to the reviser as before (kind=shot_plate), but the reviser now runs the new host CLI code/revise_shot_plate.py --shot <shNNN> --role <start|end> --change "<change in English>" --note "<user's words>": the shot's current plate is passed as [Image 1] (authoritative for camera, framing, layout, dressing and light; only the requested change may differ), one new plate is generated into the scene library as <key>_rev<N> (revised: true, pano_ref.kind = revision with the source key/kind and the request) and only this shot's start/end entry in directing/<ep>/shot_plates.json is pointed at it (reuse: revised, revised_from kept), followed by the usual group-prompt sync. The original image and its library entry are never touched — whether it came from a nine-grid tile, a nine-grid fill-in, a panorama/world-model capture, a master plate or a hand capture — and other shots that reference the same original are unaffected; 「🔁 换图」 can swap back at any time. Non---force render_shot_plates keeps the revised plate (fresh record) and --status does not flag it as legacy. The storyboard preview labels it 「起点 · 按修改意见重出」, the scene preview and the swap dialog show 「按修改意见重出(基于 <原 key>)」, and the feedback popup now carries the current file plus this command instead of render_shot_plates.py --force. shot-plates / reviser SOULs and docs/shot_plates.md updated. Requires a service restart (preview data) and a hard refresh.

  • Scene preview 3D whitebox: the orbit view is now driven like the world-model viewer.

    The 「旋转视角」 viewport on the scene preview page no longer orbits around the scene centre: drag turns the view in place (yaw/pitch), the wheel moves forward/back along the line of sight, and after clicking the view W/S move along the line of sight, A/D strafe, Q/E raise/lower and Shift triples the speed (movement scales with the scene's framing radius). The initial framing is unchanged. The focused viewport shows a blue inset ring, and the hint line under the toolbar lists the controls. Static files only (no service restart), hard-refresh the page.

  • /stop all chat command.

    Sending /stop all to any agent from any channel (web chat, Feishu, WeChat, WhatsApp) is intercepted in api_chat the same way as /auto — no run is dispatched and no engine quota is used — and stops every running/queued agent task across all projects via the same entry point as the run panel's 「⏹ 停止」 button (api_stop_all, so downstream sees 「已被用户手动停止」 rather than an error). /stop alone reports the current running/queued counts; the reply follows the UI language and warns when the project's auto-run watchdog is still on (it would dispatch again once idle — send /auto off to stop it for good). Requires a service restart.

  • Hand-captured plates from the panorama and world-model viewers.

    The scene preview page's 360° panorama viewer gets a 「💾 背景图」 button (top right, with a dashed frame showing the saved area) and the world-model (Spark) viewer gets the same button in its toolbar; the world viewer now starts with the whitebox wireframe off and no longer draws the panorama-camera marker sphere. Pressing the button renders the current view off-screen at the project plate size and posts it with the derived whitebox camera (position / target / fov) to the new POST /projects/<p>/scenes/<sid>/plates/manual, which validates the aspect, computes the camera facts, stores plates/<plate_key>_hand<stamp>.png (+ sidecar) and adds a master / manual library entry (pano_ref.kind = pano_manual | world_manual, view angles recorded). The plate shows up in the scene's plate list (caption 「手工截取(全景 A3)」 / 「手工截取(世界模型)」) and in the storyboard 「换图」 candidates. Requires a service restart.

  • Plate mode defaults to nine-grid; panorama and world-model options hidden.

    output.plate_mode now defaults to grid for new projects, the two Output-settings dropdowns and the per-scene selector on the scene preview page show only 「九宫格」 (the pano / world options remain as hidden entries so existing projects and scenes keep displaying and running their stored value; the backend still accepts all three). Grid tiles split from the 3×3 sheet are now stored as JPEG under their .png names like every other library plate (was true PNG, ~1.8 MB per tile; existing tiles converted in place). Requires a service restart for the new-project default.

  • Nine-grid plate mode now includes automatic per-shot fallback plates (default; render_shot_plates.py --no-grid-fallback disables).

    In plate_mode = grid each shot still picks the nearest of the nine tiles first; when that tile is unsuitable (bearing delta > 30°, pitch delta > 20°, camera distance > 6 m or height class delta ≥ 2 — GRID9_FIT) the script now renders a dedicated plate for that shot's own whitebox camera, using the scene's top-down plan as [Image 1] and the whole 3×3 grid sheet as [Image 2] (build_grid9_fallback_prompt), stored in the scene plate library as grid9_fallback entries (<plate_key>_g9fb) and reused for nearby cameras (GRID9_FB_TOLERANCE). The episode index records reuse = grid9_fallback plus the rejected tile's scores and the reasons; shots already indexed on an unsuitable tile are re-decided on the next run (the grid sheet itself is never re-rendered; --force redoes only the targeted fallback plates, --no-grid-fallback --force re-renders the grid). code/grid9_compare.py --project <slug> --ep <ep> --scene <sid> writes a comparison page qa/grid9_compare/<sid>.html (whitebox frame / picked tile with scores / fallback plate side by side). Also fixes the plate-prompt inventory counting an object's parts (*_top, *_leg_1…) as several objects ("the glass table (4 of them)").

  • NSFW mode (Settings → Advanced → 「NSFW 模式」, default off).

    When enabled, the user configures three fallback models — a language model (engine + model), an image model (provider + model) and a video model (provider + model) — plus an 「审核拒收后自动切换」 failover switch (default on). Routing is three-layered and host-side (members never pick engines or providers for it): ① explicit marks first — p5-breakdown writes events[].content_flags (nudity / sex / gore / extreme_violence / drugs / self_harm, [] when none) into script_breakdown.json, shot-planning derives a per-group nsfw: true|false in shot_list.json from the scene's events, the user can toggle 🔞 on a group card in the storyboard page (assets/group_settings/epNN/grpNNN.json#nsfw, nsfw_reason: manual, overriding the derived value), and the orchestrator dispatches flagged work with the new optional work-order field nsfw: true / dispatch.py --nsfw — the host then runs that member on the fallback language model and sets VIDEOAGENTS_NSFW=1 so its genmedia image/video calls use the fallback providers (genmedia.py info --group shows 「🔞 NSFW 路由」); ② moderation-rejection failover — when a provider rejects a call for content (Ark *SensitiveContentDetected, MiniMax 1026, Fal/OpenRouter content_policy) genmedia re-submits once on the fallback provider within the same call, logs [genmedia] NSFW 兜底, records meta.json#nsfw_route, and this switch does not count toward the user's retry budget; a language-model refusal is not switched in-process — the host marks the run nsfw_suspect, records the task_id in runs/nsfw_suspect.json, and the second dispatch of the same task_id automatically uses the fallback language model (「第二次尝试才换」); ③ stickiness — a successful failover writes nsfw: true, nsfw_reason: failover to the group's settings so later re-rolls go straight to the fallback provider. A configured-but-unusable fallback (no key) raises instead of silently falling back to the primary model; a missing fallback for a modality only warns. New WARN-only check nsfw_hint (code/check_nsfw_hint.py) flags groups whose shot text / prompt looks sensitive but is not marked, and the storyboard page shows a yellow hint on them. The mode only decides which models are used; content-safety and platform-rating checks are unchanged, and agents must not fabricate content_flags. All agent clauses are conditional on the injected 「## NSFW 模式:开启」 section (WORKFLOW §5/§6/§9, timeline-story and shot-planning SOULs, docs/script_breakdown.md, code/README.md). Requires a service restart.

  • Upscale settings: Agentics tab.

    The "🔍 Upscale" card on the Generation Models page gains an Agentics tab (genconfig.upscale.provider = agentics, upscale.agentics.profile_code, default scale-seedvr2). It reads the same Agentics media catalogue as the video section, filtered to media_type = Scale profiles, and bills the signed-in desktop account. genmedia.py upscale uploads the source clip to the profile's declared video slot, submits the target tier / size / seed through the fixed profile fields, rescales a mismatched output to the delivery size, and records upscale{provider: agentics, model, params.task_id…} in the clip's meta.json; the task is a long task like video, so an interrupted process resumes with reclaim --task-id <UUID> instead of resubmitting. No fallback, as with the other upscale providers.

  • Episode close: the cut no longer stops dead on its last frame.

    A new episode close setting (Video Rhythm dialog and new-project wizard, settings.json#transitions.episode_close, present in every transition mode) decides how the main body ends: fade to black (default: 1.0 s fade + 0.5 s hold), fade to white, cut to black (cut_black, a sudden blackout for suspense: no fade, the last frame cuts straight to a black hold and the audio stops at the same instant), cut to white, or none (hard_cut, the previous behaviour). An episode can override the project value from the new "⏹ Episode end" row in the storyboard page's transition summary bar (POST /transitions/<ep>/close, written to a new optional top-level shot_list.episode_close field; the director's transition list may also name it and shot-planning writes the field; machine check transition_close_valid). render_transitions.py plan appends one to_group: null entry to timeline.transitions[]; render re-encodes only the last group (fade-out / cut + tpad colour hold; a cut's own audio track is faded or cut at the same moment and padded with silence); check folds the hold into duration_as_planned / audio_stream_intact, verifies the last frame and hold midpoint (for cuts also that the last content frame was left untouched), whitelists the window for no_black_frames and records transitions_render.json#episode_close {fade_end_s, end_s}; preview --boundary episode_close renders a tail preview (also from the storyboard row). The hold sits after the end of the picture, so it is deliberately not a timemap op and not part of the mix boundary layer (mix manifests and stamped fingerprints are unaffected); finalize_episode.py assemble reads the ledger and applies the matching afade (fade types) or a 20 ms de-click cut (cut types / hold_audio: mute) to the external mix at the moment the picture is fully black, pads the hold with silence, and no longer warns about the picture being longer than the audio by exactly the hold. Existing projects pick up the default fade on their next transition render (cut_v2 is now produced even for an all-hard-cut episode unless the close is set to none). WORKFLOW §9C, transition / shot-planning / director / audio-mixing SOULs, all 11 UI dictionaries. Requires a service restart.

  • Third background-plate mode: nine-grid (output.plate_mode = grid).

    Next to Panorama and World model (project output settings and the per-scene override on the scene preview page) a whitebox project can now have render_shot_plates.py generate, per scene and lighting scheme, one 3×3 grid image from the nine semantic camera positions in the scene's layout pack (layout.json#views, tile 1..9) with the top-down plan and a host-made blank tiling template as references, split it into nine background plates filed in the plate library (pano_ref.kind = grid9; tiles are not master plates and never go through homography derivation), and then pick for every shot the tile whose synthesised camera (landmark → whitebox coordinates, height by angle, field of view by size) is closest to the shot's whitebox camera (bearing, horizontal distance, height class, pitch, field of view; the score breakdown is logged per shot and stored in the episode index). The grid sheet <scheme>_grid9.png (+ .json ledger) is never attached to video prompts and deliberately avoids the retired grid_9views* file prefix that three checks blacklist; a layout without nine views raises Grid9LayoutError. docs/shot_plates.md "背景图模式", shot-plates SOUL, WORKFLOW §9. Requires a service restart. Exercised once for real on fengshen3 ep06 SCN-0121 (38 shots → one grid, nine tiles).

  • Fancy text (花字) gets its own section on the Post-production page, with an auto/manual mode and a per-type picker.

    The packaging layer used to have one row of five switches ("Intro/outro / caption switches"); it is now two rows: "Intro/outro" (intro, outro, teaser, subtitle burn-in) and "Fancy text" (enable switch, a mode dropdown and a type picker). Mode auto (default) lets the caption agent choose usage types by genre; manual opens a dialog grouped by four categories (narrative info labels, structure and pacing, emotion and variety-show style, functional and compliance) with select-all/clear per category and a "suggest by genre" preset menu, and only the checked types may appear in the episode's captions (checked = allowed, not required). Switching to manual opens the dialog first and falls back to auto if it is cancelled; saving manual with nothing checked is refused by the server. Types that a project cannot use are greyed out with the reason: in audio-to-video projects every caption must come from the master transcript and align to speech, so types that are usually not spoken (inner voice, onomatopoeia, footnotes, disclaimers, …) are unavailable, and types that need upstream data (cultivation table, prop/creature cards, dictionary, world bible) are unavailable until that data exists. The single source is the new modules/caption_catalog.json (35 usage types; the seven legacy type values remain valid; four usages are listed as excluded for now: show-title corner bug/watermark because it persists across groups, teaser hook and credits which belong to the title station, transition cards which belong to the transition station). New settings output.caption_mode / output.caption_types; captions.json gains an optional per-caption tier (headline/keyword/label size band, inferred from the type when absent, em_pct is now validated per band) and a top-level caption_policy stamp; new design-stage machine checks caption_types_allowed (manual mode: every type is in the checked set) and caption_policy_fresh (stamp equals current settings, so changing the selection without re-running the design fails); new CLI render_captions.py policy [--stamp] prints the effective policy and writes the stamp. The caption prompt injected into caption/edit agents was also brought up to date (it still described schema v2 with ≤4 style presets and the old density rules) and now lists the allowed types. WORKFLOW §9A, workflow.yaml, the caption SOUL and the caption-styling skill are updated; all 11 UI dictionaries gain the catalog labels.

  • Issue feedback reports now carry the user's client version.

    The metadata block at the end of an auto-filed GitHub issue (Settings → Advanced → Diagnostics, "Issue feedback") gains a "客户端" line next to the server version: the desktop app passes its own version and build (VIDEOAGENTS_CLIENT_VERSION, VIDEOAGENTS_CLIENT_BUILD = channel/buildHash) to the Python service it launches, so a report reads e.g. desktop 1.0.36 (release/abc123); a browser talking to a locally started service records web. Stored as client_version in the local ledger; the prefilled "submit in browser" link includes it too. Requires a service restart (and a rebuilt desktop app for the desktop value).

  • Claude Opus 5.5 in the language-model menus

    (claude-opus-5-5, listed as opus-5.5 right above opus-5 in both the top-bar model dropdown and the per-agent model dialog). The sonnet alias is now labelled "sonnet (latest)" and sits directly below "opus (latest)", followed by a new "fable (latest)" entry (the CLI fable alias, currently resolving to claude-fable-5-1); model ids are unchanged, so existing settings keep working. Not a default: Smart Assignment still maps to opus / sonnet.

Changed

  • Sample-reel dialogue now keeps pace with the shots.

    Dialogue voiced by the dialogue TTS library ran long in the whitebox reel and the animatic — seed-audio speaks at about 3 characters per second and pauses 0.6–1.7 s at punctuation, while shots are timed from est_duration_s (about 4.2 characters per second) — so lines dragged into the next shot and talked over the next line. The library now also writes a paced copy of every line that runs longer than its est_duration_s (assets/audio/voice/<ep>/tts/_paced/, per-line pace{file,duration_s,tempo,pause_s,target_s} in tts_manifest.json): pauses inside the line are first shortened to 0.3 s, and only what still does not fit is sped up without changing pitch, capped by the new project setting output.dialogue_tts_max_tempo (1.0–2.0, default 1.5; 1.0 turns pacing off; CLI code/dialogue_tts.py --max-tempo). This is post-processing of audio already synthesized — no TTS call is made and nothing is billed. Both sample reels use the paced copies (line_audio(paced=True)); the library files themselves stay at natural speed, and dubbing (code/dub_group.py) keeps using those. When a line still runs past its shot, the first line of the next shot now waits for it to finish (delay_s, at most half of that shot) instead of overlapping. Reels built before this change are rebuilt with the 「白模样片」/「动态样片」 button.

Fixed

  • Network fail-fast message now follows the interface language.

    The run error, the chat entry and the live run log written when an agent run is killed after Connection dropped / connection timeout (🔌 net_error) were Chinese-only; with a non-Chinese interface language they now read in English (Network interruption: … (CLI api_retry #n, error: …) (network fail-fast after Ns of running)), same rule as other server-side texts. The 🔌 marker and the net_error field, which the orchestrator relies on, are unchanged. Requires a service restart.

  • Fancy-text (花字) rendering now follows the post-production timeline

    (code/render_captions.py, new modules/caption_timeline.py; fengshen3 ep06 DEF-ep06-caption-0001). The caption tool only knew the v0 master clips and the original rough-cut timeline: copies were burned from the masters (adopted post versions such as graded or cut-down groups were lost) and the SFX track / speech alignment used the rough-cut clock, so a final with cut-outs (−19.24 s) and title-card inserts (+10 s) drifted. One resolver now answers "which file, which second" for every caption: the burn source is each group's current adopted version (master when none), local_start/local_end stay in master-v0 coordinates and are mapped through the version's time_ops (a caption inside a deleted range is dropped with a warning), episode-level start/end, word_track.json and speech-lookup are all on the cut basis (adopted versions + boundary layer, no intro), and the final composition adds the intro offset from final_layout.json. The caption-final work order becomes one host command, render_captions.py final: it composites all stickers onto the clean final.mp4 (same EDL by construction, so post versions, transition cards and intro line up), builds the SFX track on the same clock, muxes a:0 premix / a:1 archive, and writes edit/epNN/final_caption.json (idempotent; sfx-track + mux --video remain as the legacy path). speech-align folds the transcript and the mixed audio to the same clock before aligning (the mix may or may not already carry the boundary layer, decided by the mix manifest exactly as finalize_episode.py does, whose load_timemap moved to modules/timemap_layers.py); speech-snap writes local back in v0 coordinates. check_captions.py: caption_time_consistent now verifies the cut-basis start and fails captions that fall inside a deleted range, render receipts are keyed to the adopted version (src_version), and a new final-stage caption_final_fresh compares the receipt with the current clean final / captions / audio / timeline. New render_captions.py timeline prints the resolved axis. WORKFLOW §9A, the caption and edit SOULs, workflow.yaml and the injected caption prompt describe the new contract. Existing projects: re-run speech-align → speech-snap → render → final (old copies and word tracks are reported stale).

  • BGM no longer drops out inside transition inserts (title cards / establishing shots / holds).

    The mix (assets/audio/final/epNN.wav) was built on the pre-transition timeline and finalize_episode.py then cut it open at every boundary insert and filled the gap according to the op's audio policy (mute → digital silence; sustain only loops the last second), so the music stopped for the whole card (fengshen3 ep06: −91 dB for 2.5 s under cue 04 and cue 06). The boundary layer is now part of the mix basis: code/mix_basis.py sources derives it from shot_list.transition_in (freeze / hold / inserts, frame-quantised like render_transitions.pad_ops) and reports it as boundaries[] with each group's cum_start_s already including the preceding boundaries, so the mixing agent lays BGM and narration on the final, transition-inclusive timeline (a cue spanning an insert keeps playing) while the native track leaves a gap per the boundary's audio policy; stamp records the boundary fingerprint and checks wav length = Σgroups + Σboundaries. finalize_episode.py compares that fingerprint with the cut's transitions_render.json#timemap: when they match the external audio is no longer remapped through the boundary layer (subtitles still are); a mismatch, an old manifest without the layer while the cut has inserts, or a mix with the layer but a cut that never went through render_transitions fails mix_basis_current / refuses to assemble and asks for a p8-mix rerun. WORKFLOW §8B ④ and the audio-mixing SOUL describe the new placement rule.

  • Transition title cards and overlay captions now use the project's fonts

    (refs/fonts/, WORKFLOW §2 rule 9). render_transitions.py build used to pick the first usable face from the global data/fonts/ directory or the system, ignoring fonts uploaded on the Reference Files page, and settings.json#transitions.card_style was never read at render time. The host now resolves the card font once per project (modules/transition_design.py resolve_card_font): the optional new transitions.card_font setting (a project-relative path such as refs/fonts/huiwen.ttf, or a caption-engine id such as proj:Huiwen-mincho) wins, otherwise the first file in refs/fonts/ is used for every card and overlay in the project, otherwise the previous global/system order applies. The font file (name / size / mtime) is part of the build fingerprint whenever a boundary renders text, so a changed or newly uploaded font rebuilds the affected cards on the next build / render without --force; projects without project fonts keep their existing fingerprints. Build logs and meta.json record the face that was used. render_transitions.py check gains a staleness test inside inserts_built: a segment or overlay built after the transition cut was rendered (e.g. build after a font change without render) now fails with "构建晚于成片(重跑 render)" instead of passing and showing the boundary as rendered on the storyboard page.

v1.0.36

Added

  • Transition design between shot groups

    (docs/transition_design.md, WORKFLOW §9C "过场设计"). Group boundaries become first-class objects: a per-episode design ledger directing/epNN/transition_design.json diagnoses every boundary (scene change / time jump / cast / lighting / narrative-block edge, the screenplay's 转场: line, the director's packaging notes), proposes a design per the project's transition mode and keeps the user's decisions; shot_list.transition_in stays the single final field and is only written back by the host (code/transition_design.py apply). transition_in gains join.style (a controlled xfade style family for dissolves: wipe / blur_through / zoom_through / iris / pixelize / fadegrays), inserts[] (boundary insert segments: title_card, establishing from the scene panorama (yaw sweep) or the master plate (Ken Burns), timelapse between two lighting schemes of the same anchor, bridge for a generated clip; 0.5–4 s each, ≤ 6 s per boundary, per-segment join_out seam and audio policy) and overlay_card (location/time text over the first seconds of the incoming group, no length change). render_transitions.py gains build (renders the segments and overlay PNGs by design+asset fingerprint into edit/epNN/transitions/<B-from-to>/) and preview (boundary preview clip: 2 s + transition + 2 s through the same filter chain as the final cut); inserts ride the existing pad path (inserted at the boundary, film lengthens by the sum, same timemap table for audio/subtitles) and the check gains insert_budget_ok, inserts_built and insert_frames_verified. The storyboard page shows a transition card between every two groups (collapsed to a thin line when nothing changes and it is a hard cut): diagnosis tags, the design chain (previous tail frame → seam → inserts → seam → next head frame), preview, accept / candidates / keep hard cut / edit text / feedback, plus a summary bar with the episode-level mode dropdown and the insert budget. Transition mode lives in the Video Rhythm dialog and the new-project wizard (settings.json#transitions): minimal (status quo), classic (default for new projects; existing projects without the section are minimal), cinematic, custom (the four sub-options — explanatory cards, time budget %, generative transitions, establishing first shot — are only shown for custom; presets derive them). The mode takes precedence over prose statements such as "no title cards" in the director's plan. The post page transition recipe accepts fade_white and a dissolve seam style and keeps inserts accepted on the storyboard page. All 11 language dictionaries updated.

  • Audio mix follows adopted post-production versions

    (code/mix_basis.py, modules/mix_manifest.py, WORKFLOW §8B ④). p8-mix used to read only the v0 master clips, so cut-outs / slow motion / inserted holds adopted on the Post page could only be applied afterwards by hard-slicing the finished mix along the timemap (BGM jumping at cut points, narration lines cut mid-sentence, music slowed together with slow motion). The mixing agent now takes its per-group sources from mix_basis.py sources (the group's current adopted version, otherwise the master; with measured durations, post-edit start times and the version's time_ops) and stamps assets/audio/final/epNN.mix.json on delivery. post_apply.py check gains mix_basis_current and finalize_episode.py consults the manifest: when it matches the current adopted pointers the post_versions timemap layer is no longer applied to the audio track / subtitles (boundary pads still are); a stale manifest whose mix already carries post-edit timing fails post_ok (H3P refuses to sign) and refuses to assemble until p8-mix is re-run; projects without a manifest keep the previous remap behaviour.

  • Unified upscale settings

    (genconfig.upscale, Generation Models page → "🔍 Upscale" tab under Digital Human): the upscale provider is now chosen explicitly and no longer follows the video provider. Providers: ffmpeg (built-in non-AI interpolation, default; filter / crf / preset), volcengine (Seedance 2.5 Draft mode: when active, Ark Seedance 2.5 groups generated at 480p are submitted with draft=true and the draft task id is recorded in grpNNN.meta.json#draft_task; upscaling then asks Ark to render the 1080p final from that id — 7-day validity, final resolution must be 1080p), minimax (Regenerate-2K with its own region / API key on the tab, model fixed to Regenerate-2K, applicability conditions listed on the tab), comfyui (SeedVR2-class workflows with their own connection and comfy/scale-* templates; the bundled template was renamed comfy/scale-seedvr2-api.json). There is deliberately no fallback: when the chosen provider fails preflight or errors, the upscale task fails and is reported to the user (selecting ffmpeg is the explicit way to ask for interpolation). genmedia.py upscale routes on this section only, auto-rescales 2K/1080p outputs to the delivery size, and merges an upscale{provider, method, params, source, target…} block into the clip's meta.json.

  • Final resolution 2K

    option in Output Settings (project settings and the new-project wizard).

  • Host artifact check for upscales

    (code/check_upscale_artifacts.py): interpolation outputs are judged by round-trip halo / smear / flicker; generative outputs (SeedVR2 / MiniMax / Draft-mode finals) by flicker + downsample-to-source SSIM, so real super-resolution is no longer failed by the halo detector.

  • Publish page

    (/preview/videos) now lists every group clip with its upscale method tag (interpolated / AI upscale / not upscaled) read from meta.json.

Changed

  • codex / pi engines gain gpt-6-sol and gpt-6-luna.

    Both appear at the top of the codex model dropdowns (top bar and per-agent model dialog); the pi dropdown keeps listing whatever pi --list-models returns. Smart Assignment for both engines now resolves the high tier to gpt-6-sol and the low tier to gpt-6-luna (codex: was gpt-6-astra / gpt-5.6-terra; pi: was openai-codex/gpt-5.6-sol / openai-codex/gpt-5.6-terra). The tier is resolved at dispatch time, so existing installs on a smart policy pick it up after a restart; the top-bar tooltips and all 11 i18n dictionaries are updated.

  • Post page: groups with new versions pending review are now obvious.

    The left group list shows a yellow v{n} pending review badge (plus a yellow edge bar) on any group that has a shot-editing / import / insert-hold version newer than its current pointer, a new Pending review filter lists them, and the defect / dispatched dots moved from the second line to the group-id line so they are no longer clipped by the status box. The right column gets a highlighted bar above Shot editing with Compare v{n} (A|B against the current version) and a yellow Set v{n} current button, so adopting a cut no longer requires opening the collapsed Shot editing panel. All 11 language dictionaries updated.

  • Output Settings: "Generate dialogue speech" is mandatory in post-production dubbing mode.

    In the project Output Settings dialog and the new-project wizard, choosing Post-production dubbing for Dialogue voice ticks Generate dialogue speech and locks it (a note says it is required in this mode); switching back to Native video audio unlocks it. The server normalises the setting on save (output.dialogue_voice=dubbing always stores output.dialogue_tts=true, including API calls and re-saved legacy projects), modules/dialogue_tts.enabled() treats dubbing projects as enabled even before they are re-saved, and the agent role prompt no longer describes a dubbing-without-library branch. All 11 language dictionaries updated.

  • Agents no longer run local speech recognition on their own.

    Only the transcription workstations (09-audio/audio-transcription, caption speech-align, digital-human dh0-transcribe, mashup alignment) may call faster-whisper / modules/transcription.py; every other agent is denied by the PreToolUse hook (.claude/hooks/orchestrator_guard.py) and by the shared runtime discipline, so a video-generation agent can no longer download Whisper weights just to "verify the dialogue track" after a clip is rendered. Explicit user requests go through the transcription agent (or a host-level VIDEOAGENTS_ALLOW_ASR=1).

  • The upscale agent SOUL and WORKFLOW §7B gain a hard rule: upscaling is only allowed through modules/genmedia.py upscale; agents must not hand-write ffmpeg scaling, bypass the routing, or reuse legacy per-project upscale scripts.

    The MiniMax Regenerate-2K skill injection was retired (the skill file became agents/08-video-gen/upscale/docs/minimax-regenerate-2k.md); selecting minimax on the Upscale tab is the only switch.

  • Shot timing in video prompts

    (shot_timing_bound, code/sync_shot_timing.py): each shot's duration_s from shot_list.json is now written into the group video prompt in the effective model's official form. Seedance 2.5 / Wan 3.0 get continuous integer-second segments (0-2秒:, sub-second cuts merged as 8-10秒(Shot 5–Shot 8):; 0-2s: on non-Chinese UIs) on the first shot of each segment; MiniMax H3 gets official cut timestamps At MM:SS.mmm, on Shot 2+; Seedance 2.0 stays untagged per its official guide (stale tags are stripped). The sync is idempotent (--write, original prompt backed up once) and the check enforces presence, continuity, the last segment ending at round(total_duration_s), and Σ duration_s == total_duration_s. Wired into the p7-prompt validation list and the prompt agent's SOUL/runtime contract.

  • Long take › Join method

    (duration.long_take_mode, shown in the new-project wizard and the Duration Settings dialog only while Long take is on): last_frame (default) chains every same-scene group boundary on the previous group's last-frame screenshot; tail_video chains on the previous group's last 2–3 s of video (.continuation.mp4 in video_refs), falling back to the last frame when the model takes no reference video, the tail is shorter than 2 s or the video budget is full. The choice applies regardless of the continuity plan's boundary_type: previously only a continuous boundary ever produced a tail video, and since continuity-planning marks reverse angles and reframes as cut, most projects never got one. boundary_type now only shapes the opening declaration the host writes into the prompt (continuous: extend forward, no cut at the opening; cut: same location/lighting/positions, then the planned framing) and is recorded as continuity_ref.boundary. Cross-scene boundaries, transitions and anchor: none remain hard breaks. Existing projects default to last_frame; after changing the join method re-run sync_continuity_refs.py --write (--prepare for groups whose predecessor is already rendered). A running instance must be restarted.

Fixed

  • code/render_transitions.py dropped the tail frames the edit step pads onto group clips: the segmented renderer referenced whole group files, so a timeline whose entries are longer than their clips (fengshen3 ep06: 10 groups padded by one frame each to cover the clip's native audio, recorded as tail_pad_frames) produced a cut_v2 10 frames short with the picture running 0.42 s ahead of the audio at the end (hard_cut_positions_intact FAIL, DEF-ep06-edit-0002).

    Every timeline entry now gets a frame plan (round(duration × fps) frames: taken from the clip from in, the remainder cloned from the last frame); only verbatim whole-file groups are stream-copied, padded or trimmed groups are re-encoded to exactly their planned length, render-run sources are built per input through filter_complex instead of the concat demuxer, and boundary requantisation uses the same frame plan (it previously bailed out on entries carrying timeline_in).

  • modules/timemap.compose mis-composed nested edits: a second-layer edit falling inside a first-layer interval (slow motion then cutting that same span, or partially deleting an inserted hold) was appended as a parallel op whose delta cancelled the first one, so effective_time_ops, build-cut in/out mapping and the legacy audio/subtitle remap were wrong for such version chains (fengshen3 ep06 grp019 composed to Δ0 instead of −0.71 s).

    Compose now scans source-basis breakpoints and derives each segment's output length through both layers.

v1.0.35

Added

  • Project dropdown › Import Project…

    (above "New Project…"): pick a .zip of a project folder (either the folder itself or its contents at the zip root); the host streams it to disk, extracts it into data/projects/<name>/ (name = the zip's top-level folder, else the file name; a clashing name gets a -2/-3 suffix; path-traversal entries and __MACOSX/.DS_Store noise are rejected or dropped) and the console switches to the new project. POST /api/v1/projects/import?filename=…&name=… with the raw zip as the body.

  • Output settings › Background plate mode

    (output.plate_mode, shown only when whitebox is on; in the new-project wizard and in project settings): pano (default) keeps the existing flow — panorama anchors planned from whitebox camera positions, panoramas re-projected per shot direction and regenerated; world lets you pick the anchor yourself on the scene preview page, generate a world model (World Labs Marble) from that panorama, and render_shot_plates.py then screenshots the world model per master-camera direction (modules/worldlabs.py#render_world_views, headless Chromium + Spark) as the reference for the regenerated plate. Per-scene override in the scene preview "Shot background plates" panel (POST /projects/<p>/scenes/<sid>/plates/plate-mode, stored in plates/index.json#mode). Scenes in world mode without a world model stop the chain with exit code 4 [world_missing]; the world model is never generated automatically. The "World model" panel now sits above "Shot background plates" on the scene preview page.

  • Settings › Advanced › Diagnostic Data › Issue feedback

    (on by default): when an agent confirms during a run that a problem is a defect in the host code, or that the host needs a new feature, it files an issue with code/report_issue.py; the host scrubs it (paths, project names, keys), de-duplicates it by signature and lists it in the dialog, where Submit in browser opens a pre-filled GitHub "new issue" page for you to confirm (GitHub does not accept anonymous issues, so nothing is posted without you). The Diagnostic Data menu item shows the number of issues waiting. Headless setups can set the environment variable VIDEOAGENTS_GITHUB_TOKEN (classic token, public_repo) to let the host publish directly.

  • Scene preview: 360° panorama viewer

    — clicking a rendered panorama now opens a draggable 360° view (toggle back to the flat image); the anchor map drawn on the top view can be zoomed and panned.

  • Storyboard preview: 📍 "View set" button per shot

    (whitebox on): jumps that group's whitebox timeline to the shot's start without playing; shown only for shots that have a whitebox camera.

  • Live page: local ComfyUI channel next to Fal

    — a prompt is required and reference images are not used on ComfyUI; new FastH3 VSA T2VA workflow template and model catalogue. The list of linking modes (last-frame linking, no linking, …) now follows the selected model.

  • Scene panoramas: render_scene_panos.py --adopt also accepts .redo-* archives

    (the previous official panorama archived by --redo), so a worse re-render can be rolled back without paying for a new one.

Removed

  • Diagnostic Data: local event collection, lesson cards (runs/<task_id>/lesson.md) and the diagnostic bundle export

    (modules/diagnostics.py, the /diagnostics* endpoints, the diagnostics_enabled setting). The Debug mode switch is kept. Existing files under data/.videoagents/telemetry/ are no longer read and can be deleted.

Changed

  • Desktop: the default language-model engine on first launch is now Claude

    (model = smart allocation), the same as the web console; it used to be DeepAgents / Agentics.

  • Host-generated text follows the UI language

    : gate / guard messages, agent receipts, whitebox issue text, engine status, the digital-human panel, the scene image panel and the background-plate machine sentences are Chinese on a Chinese UI and English on every other language; the 11 UI dictionaries were completed for these areas.

  • Scene preview page is laid out in sections

    (panorama, world model, background plates, whitebox, …).

  • Storyboard preview: the whitebox issues panel is collapsed by default

    and keeps the open state once you expand it.

  • Scene panoramas: anchor placement and coverage

    — the anchor height is now the standing surface plus eye height (anchors on raised whitebox blocks no longer end up inside solid volumes, anchors over water are handled); the service radius is scaled from the top-view width and relaxed per indoor / outdoor anchor (mixed scenes included); each anchor is classified indoor / outdoor so an indoor anchor no longer renders as an outdoor / sky-visible panorama; open exteriors get panorama projection cues; a parent anchor whose whitebox-consistency check is WARN is not used as the chained reference.

Fixed

  • "Hold" on a sign-off card could be treated as a sign-off when the UI language was not Chinese.

    The host, the waiting dispatch.py and the producer's rules all compare the raw answer with the Chinese keys 签字 / 暂缓; when the producer built a sign-off card with translated buttons (--options "Sign off,Hold") the answer Hold matched neither and was read as approval. The host now normalizes options, defaults and answers back to the canonical keys (all 11 UI dictionaries plus common synonyms: Sign off/Hold, Rerun/Skip, Freigeben/Zurückstellen, 承認/保留, …) on card creation and on every answer path (console popup, storyboard / post / director pages, Feishu cards), an empty sign-off answer is rejected instead of defaulting to 签字, and the producer rules state that only an exact 签字 counts as approval.

  • Screenplay / dialogue / narration files written in a non-Chinese output language no longer parse as empty.

    The host parsers (script breakdown, dialogue fit, INT/EXT registry check, event-treatment check, storyboard narration placement) keyed on Chinese labels such as [事件]/[出场]/[时长], 动作:, 转场:, 旁白候选, 本场无对白, 定稿. A fixed anchor contract (docs/screenplay_anchors.md) now defines an English canonical form for every anchor ([EVENTS]/[CAST]/[DURATION], ACTION:, TRANSITION: or a bare CUT TO:, TIME:/SOUND:/SFX:/MUSIC:, [NARRATION (…)]:, [NO DIALOGUE], [NO NARRATION], - **final**:, INT./EXT./INTERIOR/EXTERIOR, scene start / end of scene), the scene heading's last field is read as the time of day by position in any language, English speaker names may contain spaces, and the writing agents' rules tell them to use exactly this form when the output language is not Chinese. Existing Chinese screenplays parse as before.

  • Seedance 2.5 background-plate machine sentences now follow the UI language.

    The host-written 【场景】 slot block and the per-shot "场景激活:/本镜画内…/构图层次:" sentences that sync_shot_plates.py --write injects into video_prompt were always Chinese, even inside an English prompt; they are now written in English (【Scene】 … reference [Image N], Scene activation: use Scene A ([Image 2]); do not use …, In frame left to right:, Composition layers:) whenever the UI language is not Chinese. Both wordings are recognised by shot_plate_bound, both are stripped and rewritten on the next --write so switching the UI language leaves no leftovers, and a Shot N|title. heading ending in an English period is now accepted as a shot heading.

  • Other host checks and parsers that assumed Chinese text

    (found in the same audit): the reviser's ## 变更记录 report section is now recognised under its English and other UI-language headings (## Change log, …), so downstream dirty-marking and stale-signature rebuilds no longer silently drop when the report is written in English; the Seedance 2.5 structure check accepts a fixed English label set (【Characters】 / 【Action & sound】 / Use: / Not used: / 【Unused assets】 / 【Consistency】) next to the official Chinese labels, and the prompt rules list them; sign-off cards bind to the right DAG gate from an English question (H3W … (ep06)) even when several episodes' gates are open; gate episode numbers, screenplay narration anchors (scene start / end of scene), dialogue.md **final** lines and table headers, voice-card negations (avoid …), and publishing lint values with half-width parentheses are read in English as well. On the web UI, the storyboard prompt editor and the copy button now use the stored video_prompt instead of reading back page text the UI dictionary may have rewritten, screenplay cells on the script preview are excluded from dictionary translation so key-point anchors keep the original text, edit buttons carry the episode in data-ep instead of scraping it from a translated label, director-console note targets store the id rather than a translated label, and the storyboard redraw marker is no longer part of a translatable string.

  • Run-log progress line and tool receipts were always Chinese

    regardless of the UI language ("💭 思考中…", "⚙ 执行工具中…", "⏳ 等待模型下一步…", "✗ 工具报错" …). These are host status strings pushed by the runtime over the progress / tool SSE events; they now have entries in all 11 UI dictionaries, and the variable parts (tool name, argument size, thinking-token estimate) are placed after the fixed text so the prefix rule translates the whole line.

  • Scene panoramas: the column ↔ yaw formula was inverted

    (panoramas came out facing the wrong way), panoramas were sometimes rendered outside the set, and a mis-classified panorama could pass the check.

  • Background plates: people appeared in empty-set plates

    (Ark merges the negative prompt into the positive text) — person words are filtered from plate prompts; the layout PNG no longer grows too large after a direction change; a plate prompt rejected by Ark's text pre-check as sensitive content (two style sentences becoming adjacent once the subject clause is removed) is rewritten.

  • Director console: shot size labels

    (wide / medium / close-up / extreme close-up) used wrong thresholds.

  • Desktop: logging in with a phone number failed.

  • Defect tickets menu item did not open inside the desktop app.

  • Live page: switching channels

    could leave the model / linking-mode selectors out of sync.

  • Whitebox: the check run after sign-off

    raised on groups whose issues had all been decided; whitebox rendering uses the host-injected interpreter path when Playwright is missing.

v1.0.34

Added

  • Fal video channel: MiniMax H3 Max Lip Sync

    (minimax/h3-max/lip-sync/image-to-video) — one portrait image plus one driving audio clip (at least 5 s; Fal clips anything past 14.8 s), no prompt, output length follows the audio, 480P/768P/1080P/2K. genmedia video takes the image from --first-frame (or a single --ref) and the audio from --audio-ref; prompt, duration and aspect are ignored with a stderr note. Live page: choosing this model turns the prompt box into a script box (what the character says). The script is packed by sentence into chunks of about "Clip length" seconds (5–13), each chunk is synthesized with the current TTS settings and default voice from the Generation Models page, and the speech drives the lips (speech under 5 s is padded with silence; over 14.8 s the chunk is halved and re-synthesized). Round 1 uses reference #1 as the portrait, later rounds the previous clip's last frame ("No linking" uses reference #1 every round). When the script runs out the stream waits instead of stopping: "Update script" speaks text appended after the original next, while a rewritten script starts over.

  • Generation-model settings › TTS › Agentics: separate Voice Design and Voice Clone profiles

    (tts.agentics.design, default qwen3tts-voicedesign; tts.agentics.clone, default qwen3tts-clone), mirroring the ComfyUI TTS channel's two workflows and the image channel's text-to-image / image-to-image pair. genmedia tts uses the Voice Design profile when the output is a voice sample (*_voiceprint.*), sending a voice description assembled from the character's voice.json (narrator: voice card / --instructions) as the new contract parameter instruct — the same name as the description input of the Qwen3-TTS VoiceDesign node that the RunningHub direct-binding targets (voice_instruction / voice_description / instructions are accepted as aliases; whichever the profile declares is sent); every other line uses the Voice Clone profile, whose reference audio now prefers the project's frozen sample assets/audio/voice/refs/<CHAR>[_<variant>]_voiceprint.mp3 before the TimbreModel auto-pick. A Voice Design profile that declares no description parameter falls back to Voice Clone with a stderr note. A legacy single profile_code is migrated to both sides; an empty value falls back to the defaults. genmedia info prints both sides for the two-profile Agentics channels.

  • Generation-model settings › TTS › ComfyUI: two workflows — Voice Design and Voice Clone

    (mirrors the image channel's text-to-image / image-to-image pair). The main workflow (workflow / rh_workflow_id) is now labelled Voice Clone (voice sample + dialogue text → speech); a new second selector (design_workflow / rh_design_workflow_id) is Voice Design (a text description of the voice → a voice sample, no reference audio). genmedia tts chooses by purpose: an output file named *_voiceprint.* (a character / narrator voice sample) uses Voice Design with a description assembled from the character's voice.json acoustic fields (narrator: voice card / --instructions), everything else uses Voice Clone with the project's frozen sample assets/audio/voice/refs/<CHAR>[_<variant>]_voiceprint.mp3 as the reference, falling back to the TimbreModel auto-pick (with a stderr hint) when the project has no sample yet. With no Voice Design workflow configured the behaviour is unchanged. RunningHub workflows without placeholders are bound directly (dialogue text, instruct / voice_instruction description slot, seed; a wired LoadAudio in a design workflow is rejected); local / Comfy Cloud templates use the new {{VOICE_DESC}} placeholder. The TTS direct-binding also accepts target_text as the dialogue slot (Qwen3-TTS clone node).

  • ComfyUI templates for local / Comfy Cloud: Krea 2 Turbo, Qwen-Image-Edit 2511, Ideogram 4, InfiniteTalk fp8

    — comfy/image-krea2-turbo-api.json (text-to-image), comfy/image-krea2-turbo-styleref-api.json (1–3 style references, needs the official krea2_style_reference LoRA; does not preserve identity), comfy/image-qwen-image-edit-2511-ref3-api.json (1–3 identity / layout references, Lightning 4 steps), comfy/image-ideogram4-int8-api.json (text-to-image, Quality preset; non-commercial weights; may return a model-drawn "blocked by safety filter" card while the job succeeds) and comfy/digitalhuman-infinitetalk-fp8-lightx2v-api.json (single speaker, fp8 + lightx2v 4-step LoRA, portrait aspect kept), each with a bilingual .md. New image placeholders {{GEN_WIDTH}} / {{GEN_HEIGHT}}: the requested aspect ratio scaled down to at most 1.6 MP (multiples of 16), for models whose native resolution is below the 2560×1440 platform spec — templates generate at GEN_* and upscale to WIDTH×HEIGHT with a final ImageScale, the same policy the RunningHub direct-binding already applied.

  • ComfyUI templates for local / Comfy Cloud: MiniMax Music 3 and Qwen3-TTS

    — comfy/music-minimax-music3-api.json (native ComfyUI nodes, Apache 2.0 weights; the requested duration is an upper bound and the piece may come back shorter), comfy/tts-qwen3tts-clone-api.json (Voice Clone slot) and comfy/tts-qwen3tts-voicedesign-api.json (Voice Design slot, new {{VOICE_DESC}} placeholder; both need the flybirdxx/ComfyUI-Qwen-TTS node pack), each with a bilingual .md.

  • ComfyUI image channel: FLUX.2 [dev] templates for local / Comfy Cloud

    — comfy/image-flux2-dev-fp8-api.json (text-to-image) and comfy/image-flux2-dev-fp8-ref10-api.json (1–10 reference images via a chained ReferenceLatent row per image), each with a bilingual .md. genmedia image now accepts several --ref on local / Comfy Cloud when the reference workflow has more than one LoadImage: references are bound in order and every unused slot is removed as a whole chain (the same logic now also covers RunningHub workflows built this way, which used to fail unless all slots were filled). Single-image img2img templates keep the one---ref limit. The storyboard-sketch reference capacity follows the template's LoadImage count in all three run modes. FLUX.2 [dev] weights are non-commercial — confirm the licence before commercial use.

  • Defect tickets page (Project settings › Defect tickets, /defects)

    : defect tickets moved out of the release page into their own page; "Handle" dispatches the ticket to the executive producer through the edit popup.

  • Hand-drawn canvas: "✨ AI refine" checkbox

    (on by default, shared by storyboard sketches and storyboard reference images): checked, the drawing is redrawn by the image model (falling back to the original on failure); unchecked, the original drawing is used as is.

  • Storyboard sketches: every image channel can use character reference images

    — sketches pass character sheets whenever the configured image-to-image profile / workflow accepts reference images (capacity follows the template), instead of only on selected channels.

  • Scene preview: top-view option

    for the whitebox viewer (11 languages); the whitebox preview now opens in the orbiting view by default.

  • Desktop: Window › New Window

    .

Changed

  • Generation-model settings › Image › Agentics

    : the default profiles are now flux2-dev-t2i (text-to-image, was ideogram-4-t2i) and flux2-dev-i2i-10ref (image-to-image with up to 10 reference images, was qwen-image-i2i).

  • Generation-model settings › Language model / DeepAgents › Agentics

    : the default model is now z-ai/glm-5.3 (was z-ai/glm-5.3-flash).

  • Generation-model settings › Music › Agentics: default profile is now minimax-music-3

    (was empty, i.e. the first music profile in the service catalogue). A saved config whose Agentics music profile was never picked (empty) falls back to this default. Note that MiniMax Music 3 treats the requested duration as an upper bound and may return a shorter piece.

  • Agentics image channel: negative prompts on profiles without a negative_prompt parameter

    are merged into the positive prompt as Exclude from the image: … (same wording as the RunningHub channel), with a stderr note, instead of being dropped.

  • RunningHub image-to-image templates

    : LoadImage nodes are bound in reference order ("which reference image"), not node-id order.

  • Director console

    : layout rework; clicking an object in the left list or the viewport now updates the object of the pending edit in "My changes" consistently; approving a group waives that group's unanswered whitebox issues, so the pipeline no longer stops on them.

  • Whitebox identity colours

    : creatures and mounts get their own fixed colour for the whole episode instead of sharing one with a registered character or their rider.

  • Post-processing page

    : the left panel groups shot groups by scene.

Fixed

  • Director console › edit queue

    : answering an agent question no longer reloads the page on every click.

v1.0.33

Changed

  • Groups without a whitebox plan no longer auto-play a whitebox draft

    : in the director console, scene preview and storyboard preview an unplanned group shows a placeholder instead of silently playing a host-derived draft; drafts stay view-only with a watermark.

Fixed

  • Director console no longer fails to open when the first episode has no data

    : an episode without a shot list now shows a "no shot list" notice and lets you switch to an episode that has one (11 languages).

  • Desktop: Generation Models → Image model → Agentics raised an error on save

    : the empty-model check read a single select that no longer exists now that Agentics image has separate text-to-image / image-to-image profiles.

  • Desktop: the DeepAgents engine (the desktop default, Agentics channel) failed to start

    with "DeepAgents Python runtime not found … backend/.venv-deepagents/bin/python": the bundled Python runtime did not ship the deepagents stack, so the interpreter lookup fell through to a project-root venv that never exists inside the app bundle. apps/desktop/runtime-requirements.in now pins deepagents, langchain-openai and langgraph-checkpoint-sqlite (lock regenerated — pure additions, no existing pin changed; about +135 MB unpacked), and build_runtime.py import-checks them. The desktop app picks the new runtime up automatically on launch (lock hash mismatch → runtime update).

v1.0.32

Added

  • Whitebox self-check (Settings › Advanced › Agent advanced settings › "Whitebox self-check", on by default)

    : the whitebox staging agent used to verify its work numerically only (contract checks, facing angles, clearances), so framing mistakes — a back view read as a front view, a subject hidden behind a prop — were first seen by the user in the preview page. With the switch on, after compiling it runs the new host command render_whitebox.py --stills <groups…> on the groups it changed: a local headless render of each shot's camera view at the start / middle / end and camera keyframes plus one spatial view, tiled into one contact sheet per group (directing/<ep>/whitebox/stills/<grp>.jpg), which the agent reads to check framing, occlusion, facing and shot size and reports as stills_checked. No generation model is called and no camera.mp4 is exported (that still needs the H3W sign-off). Switch it off to keep numeric checks only. See docs/whitebox.md › "白模自检静帧".

  • Storyboard preview › episode video model: Agentics channel

    — the provider dropdown at the top of the storyboard page now lists Agentics (greyed out until the desktop account is signed in) and its second-level dropdown offers the signed-in account's video profiles from the service catalogue, so an episode (and a group via the group card) can switch to another Agentics profile without changing the global setting. video_model stores the profile_code.

  • Storyboard sketches: Agentics defaults to text-to-image

    — like ComfyUI, the Agentics image channel now draws sketches without character reference images (so genmedia uses the text-to-image profile); the 2×2 grid batch is still rendered as a grid (only ComfyUI still falls back to one shot per image), with the grid prompt describing characters from text instead of "the attached images".

  • Generation-model settings › Image › Agentics: separate text-to-image and image-to-image profiles

    (image.agentics.t2i, default ideogram-4-t2i; image.agentics.i2i, default qwen-image-i2i), mirroring ComfyUI's workflow / reference workflow pair. genmedia image picks the image-to-image profile whenever reference images are attached and the text-to-image profile otherwise; an explicit --model still wins. On the scene / character / creature / prop preview pages the image-model picker offers only "Follow global" for Agentics. A legacy single profile_code is migrated into both sides.

  • Generation-model settings › Language model / DeepAgents › Agentics

    : the default model is now z-ai/glm-5.3-flash (was anthropic/claude-sonnet-5). A saved config whose Agentics model was never picked (empty) falls back to this default instead of keeping the empty value.

  • Scene plates (front / reverse) for projects with the whitebox switched off

    (docs/scene_plates.md): the scene-consistency scheme for every video model other than Seedance 2.5's whitebox chain. Phase 4 environment-concept produces a front plate (main_01.png, standing at the entrance looking in) and registers standing / looking / in-frame / behind lists in assets/concepts/scenes/<sid>/scene_plates.json; storyboard and shot-planning tag every shot with plate_view: front|reverse; the new p6-scene-plates node (code/render_scene_plates.py, condition = whitebox off) renders a reverse plate (looking back at the entrance, front plate as --ref) only for scenes that need one; code/sync_scene_plates.py --write attaches the plates after the character sheets, writes the Scene plates: block and a per-shot Scene plate: this shot uses [Image N] … and not [Image M]. line (check scene_plate_bound). New output setting output.scene_plates = auto (default: reverse on demand) / single (front only, e.g. flat animation) / pair (both at Phase 4), with a per-scene override on the scene preview page; the storyboard preview shows which plate each shot uses. The whitebox chain (whitebox video + panorama + shot plates) is untouched and remains the Seedance 2.5 default.

  • Automatic flashback grading (agents/10-editing/grade-planner, DAG node p9-grade-plan)

    : grading intent for flashback / dream / imagination segments used to live only as text in the colour script and screenplay, and the user had to open every grading recipe by hand. A new station now runs after p9-transition whenever the episode's shot list carries narrative_blocks: it reads the intent written for each block (flashback_variant, per-beat flashback_note, screenplay grading conventions), translates it into ffmpeg-class grading recipes, renders them through the host CLI and stops at Rendered — adopting or discarding stays the user's decision at the H3P sign-off, where such recipes carry a "🤖 Auto" badge. Blocks with no written intent are skipped and reported; the station never writes ffmpeg itself and never edits the ledger directly.

  • Colour script: locked field contract + color_script_ok check (code/check_color_script.py)

    : bible/color_script.json only ever specified "palette / mood / rationale per act", so projects drifted into a dozen shapes (acts / segments / beats; ep: 1 / episode / episode_id; colour names instead of hex values; scene links under six different keys or none). The station's contract is now fixed — episodes[].episode_id: "epNN", key_palette, acts[] with event_refs, scene_ids, hex palette, mood, rationale, optional variant — and any flashback / dream look must be defined once under top-level variants.<kind> with numeric grade values (contrast, temperature_shift_k, saturation, soften, grain, luma_step_pct) that the grade planner executes as written instead of guessing from prose. The check runs with --strict on delivery (DAG p4-color-script); existing projects only get warnings for contract fields.

  • Post-processing: narrative-block scope

    — recipes gain a fifth scope level, block (shot_list narrative_block.id), between scene and shot group. A scene is often shared by a present-day segment and a flashback, so a scene-scoped recipe would grade both; a block-scoped one covers exactly the flashback's groups. The post page shows a "Narrative block" tab and scope chip for groups that belong to one, and the timeline draws block recipes across the block's span.

  • Post-processing: "Soften" recipe (soften)

    — a Gaussian blur mixed with the original by strength (radius scales with frame height), for the soft-focus look of memory / dream segments; previously only approximated with the glow control.

  • post_apply.py: blocks [--json] [--stats] [--verify --by <station>] lists narrative blocks with their groups, versions and grading recipes, measures the luminance step between a block and the present-day groups around it, and runs the grade_plan_proposed check; propose --kind … (--block | --scene | --group [--t0 --t1] | --episode) --params '<JSON>' --note … --by <station> is the only way a station opens a recipe — parameters are clamped to the catalogue, the note is mandatory, and the same (station, kind, scope) is idempotent: an undecided recipe is updated in place and returned to draft instead of piling up duplicates.

    check gains flashback_graded (WARN): every group of a flashback block should sit on an adopted version that carries a grading recipe.

  • Post-processing: black / freeze pacing pads (/preview/post › Shot editing › "⬛ Black / freeze")

    : control the rhythm of a cut by inserting a freeze of the previous frame and/or a stretch of pure black. At the playhead it builds a new version of the group (POST …/post/<ep>/insert-hold); at the group's start or end it is written as that group's "Group transition (in)" recipe (transition_in.hold_s / freeze_s / hold_audio, optionally submitted at once) and rendered by render_transitions.py at finalize — hard_cut+hold cuts to black and holds, dip_black+hold fades out/in around the hold, fade_black+hold fades out then cuts in. Sound under black can sustain the previous second, fade or mute; when dialogue or narration sits right before the insert point the host downgrades "sustain" to "fade" and says so. Episode playback previews the freeze and the black between groups; the group list marks padded entries with ⬛. Contract check transition_pad_valid (0–3 s each, black only with to-black types, not on the first group, reason required, Σ ≤ 5 % of the episode budget).

  • Wan 3.0 default prompt skill (agents/08-video-gen/prompt/skills/wan3-pe)

    : the official Wan 3.0 request-formatting skill is installed with the repository and becomes the automatic "video prompt skill" whenever the model that actually runs video generation is Alibaba Wan 3.0 (Fal-hosted alibaba/wan-3.0 family, incl. episode/group overrides), alongside sd25-pe / sd20-pe / h3-pe. The prompt agent gets a dedicated injection block (five-section 【核心任务】/【情节概要】/【音频风格】/【运镜与核心约束】/【负面提示词】structure, per-asset responsibility lines in 【核心任务】, 图N/视频N/音频N ↔ [Image N]/[Video N]/[Audio N] numbering discipline, timestamps only when the shot list gives them) and the same prompt_skill_applied receipt contract; the performance-direction engine adapter gains a Wan 3.0 section. No Wan-specific structure check yet.

  • Time map for runtime edits (modules/timemap.py)

    : pads, mid-group inserts and range deletions change the length of the cut, which used to break the 0-second base of the external mix and subtitles. Every such edit is now a deterministic op ([src_t0, src_t1) → out_len); post_apply.py sync-timeline writes edit/epNN/timemap.json for version-level edits and render_transitions.py records boundary pads in transitions_render.json#timemap. finalize_episode.py composes both layers, remaps the external mix to final_audio_timemapped.wav (sustain / fade / mute under black), shifts every subtitle cue piecewise before adding the intro offset, and checks subtitle_offset_all_cues / audio_offset_measured against the map. Nothing changes for episodes without such edits.

  • Two fight-choreography prompt skills (off by default, pick them as the project's "video prompt skill")

    : agents/08-video-gen/prompt/skills/fight-choreography writes a shot's fight beats move by move (attack → reaction → recovery, weapon reach, contact frames, with wuxia reference cases in references/wuxia-cases.md), and high-density-fight adds a group-level contract (fight_plan) for sequences where several exchanges have to land inside one group without the model averaging them into flailing. Neither is active unless selected.

  • Screenplay preview (/preview/script): concept-art thumbnails

    — scene headings carry a wide scene thumbnail and character chips a round portrait (outline keeps the dialogue colour); clicking either opens a full-size layer on the page, whose caption links to that asset's preview page (/preview/{scenes|characters|creatures}?project&id) for costumes, panoramas and the rest. The storyboard preview's asset chips got the same caption links. Characters in a scene are read from the breakdown as before; creatures are deliberately not shown on the screenplay page (the breakdown contract has no reliable creature field yet — see docs/script_breakdown.md), while the storyboard page keeps showing them from the shot registration.

  • Scene / character / creature / prop preview pages: filter by episode

    — a dropdown in front of the search box lists the project's episodes and shows only the assets that episode uses. modules/asset_episodes.py resolves the mapping from the shot list, storyboard, breakdown and the asset cards' own episodes field, matching by id as well as by name.

  • Scene INT/EXT registration + scene_int_ext check (code/check_scene_int_ext.py, modules/scene_int_ext.py)

    : bible/scenes/index.json now carries int_ext per scene, and the check compares every screenplay scene heading's INT/EXT against the registered space. Screenplay headings that point at no registered space are reported as scene_gaps[] to be created rather than silently ignored; existing projects only get warnings.

  • Generation-model settings › Digital human: Agentics channel

    — the digital-human card gets an Agentics tab (signed-in desktop account + a media_type=DigitalHuman profile such as infiniteTalk-1char / infiniteTalk-2char), reading the same Agentics media catalogue as the video section. Plugins use whichever channel is selected, as before.

  • Settings › Advanced › Diagnostic Data › "Debug mode" (off by default)

    : developer entry points in the console are hidden unless it is on — currently the per-Agent SOUL.md button, which lets you read that Agent's identity text. Collection and export behaviour is unchanged; the new read-only GET /api/v1/config/diagnostics returns both switches so pages can decide what to show at startup.

  • Style library in every interface language

    : the picker's style names and the prompt written into "Visual style" now follow the interface language (apps/web/static/styles/i18n/<lang>.json per style id, falling back to Chinese when a translation is missing), and a style stays highlighted after switching languages because both the translated and the Chinese prompt count as a match. apps/web/check.py fails the static package when a locale's file does not cover every style id.

  • GitHub release notes are taken from CHANGELOG.md

    (scripts/release_notes.py): the desktop release workflow publishes the tag version's own CHANGELOG section as the release body, falling back to auto-generated notes (with a warning) when the version has no section.

Changed

  • Storyboard page no longer shows the "Dialogue speech" panel

    : the dialogue speech library is built from shot_list.json, which does not exist until the storyboard is signed off and shot planning has run, so on the storyboard page the panel could only ever say "no shot list yet". It now lives on the storyboard-preview (shot) page only, and only when "Generate dialogue speech" is switched on in Output Settings (it used to show a one-line "switched off" hint otherwise). The animatic still picks up the library automatically when one exists, and still flags itself as out of date when the library changes.

  • Post page scene palettes now work for every colour-script shape

    (modules/color_script.py): the host used to read one layout only (episodes[].episode + segments[].scenes), so on most projects the scene swatches were empty and a scene_palette recipe left blank failed with "palette is empty". All host reads go through one tolerant reader: episode keys are normalised, acts / segments / beats are unified, scenes match by screenplay number or SCN- id, colour names resolve through the file's legend, and a blank scene_palette falls back scene number → scene id → episode palette. When a scene is shared by a present-day act and a flashback act, the swatch shows the present-day palette. post_apply.py blocks --json also returns the colour script's variants and the episode's variant acts.

  • Footage Library (/footage): site subtitles for link imports

    — after a YouTube / Bilibili video is downloaded, a second yt-dlp --skip-download --write-subs --write-auto-subs pass fetches the site's own subtitles (languages from VIDEOAGENTS_FOOTAGE_SUB_LANGS, default zh.*,en.*) as source/source.<lang>.srt, recorded in project.json#source.subtitles (manual before auto captions, then language order). Subtitle failures never block the video. 🎙 Speech to subtitles now applies the best site subtitle file to the clips by timestamp (tags and rolling auto-caption duplicates stripped) and only falls back to local Whisper when there is none; a 🎙 Re-run with Whisper button (POST …/transcribe {force_asr: true}) appears when site subtitles exist. The project info panel lists the downloaded subtitle files. All 11 locales updated.

  • Footage Library (/footage): project tags

    — each footage project has a tag field on its page (type several as #tag1 #tag2, separated by spaces; the # prefix is optional; saved on blur to project.json#tags, at most 30 tags of 40 characters, PATCH …/settings {tags}). The project list shows every project's tags, a tag cloud of all tags in use, and a #tag filter box: several tags must all match (case-insensitive); clicking a tag chip toggles it in the filter; a 🏷 button on each row edits that project's tags in place. List rows open the project when clicked anywhere outside a button, link or tag (the separate "Open" button is gone). GET …/footage/projects returns tags per project. All 11 locales updated.

  • Footage Library (/footage): analysis-question templates

    — the "Reset to default" button next to the AI analysis question becomes 📋 Templates, opening a picker with three presets (1 "Shot content analysis" — picture, gist, subject, tags; 2 "Shot design analysis" — shot size, lighting, camera movement, art style, technique, set; 3 "Visual style analysis" — style, cuts, stable range). Picking one fills the question box and saves it (confirmation when the box already has different text). Template 1 is the default: new footage projects are created with it loaded, and it remains the fallback when the question is empty. GET …/footage/projects/<name> now returns analysis_prompt_templates. All 11 locales updated.

  • Footage Library (/footage)

    : importing a video no longer runs speech-to-text automatically — the pipeline stops after shot detection and clip export, and subtitles are generated on demand with the 🎙 Speech to subtitles button (formerly "Re-transcribe"; the overwrite confirmation is only shown when clips already have subtitles). The "clip file" link on each clip card is replaced by a ⧉ copy button that copies the clip's project-relative path (data/footage/<name>/clips/…mp4), in the same style as the library-path copy button. All 11 locales updated.

  • Video prompt skills renamed to the <model>-pe convention

    : agents/08-video-gen/prompt/skills/sd20-prompt-writing → sd20-pe and h3-prompt-writing → h3-pe (skill ids 08-video-gen/prompt/sd20-pe, 08-video-gen/prompt/h3-pe; frontmatter names and agents/openai.yaml updated). The runtime resolver, the H3A sign-off / video-model-settings help text (all 11 locales), the prompt agent's SOUL.md, WORKFLOW.md §7F and code/prompt_skill_check.py use the new ids. Legacy ids stored in existing projects (settings.json#prompt_skill.skill_id / effective, project_skills.overrides, episode/group overrides, skill_applied.id receipts) are read as the new ids, so nothing has to be re-selected; receipts still fail the sha256 check because the SKILL.md files changed, which is the existing "skill updated → redo" contract.

  • render_transitions.py check: duration_unchanged becomes duration_as_planned (= source + Σ pads ± 1 frame) when pads exist; audio_stream_intact, transition_frames_verified (black midpoint, freeze frame = previous group's last frame) and hard_cut_positions_intact are pad-aware; the black-frame whitelist includes pad windows.

  • post_apply.py check transitions_synced now compares the pad fields as well as type and duration; shot cut-outs register their removed ranges as time_ops so the mix and subtitles follow them at finalize.

  • A fresh install now defaults to the Agentics channel

    for image, video, music, TTS, digital human and the DeepAgents language model (was Volcengine / OpenRouter / HeyGen / local): signing in to the desktop account is enough to start generating, with no platform keys to apply for first. Installations that have already saved a configuration keep their channels — only the built-in default changed.

  • An Agent's own self-check re-rolls now count against the user's re-run limit

    (Settings › Advanced › Agent advanced settings › re-run count): the character / creature / costume concept stations used to re-generate a sheet whenever their own layout or appearance self-check failed, regardless of the setting. The limit now covers "re-generate after a failed self-check" as well as QA rejections and generation checks; at 0 the Agent keeps the current image and reports the defects in selection.json and its receipt for the user to decide. The clauses written into the station SOUL.md files and WORKFLOW.md ("at most 3 times") were rewritten to defer to the setting.

  • Storyboard sketches on the ComfyUI channel are drawn without reference images

    : ComfyUI treats a reference image as the img2img starting frame, so attaching character sheets locked the composition to the sheet (and failed outright with several), and local models could not follow the strict 2×2 grid layout. On ComfyUI (local / Comfy Cloud / RunningHub) sketches are now pure text-to-image, and the grid batch falls back to one image per shot (ledger records mode=single).

  • Creature chips read the creature library rather than the id prefix

    on the screenplay and storyboard preview pages (a creature without concept art now shows a 🐎 placeholder and a dashed outline instead of a character colour dot); creatures registered only on a shot (or under creatures_union) are picked up, and a library whose top-level key is entries is read too.

  • Preview pages: a separate "<" back button next to "Console"

    (both framed) — the single "← Console" link went to the console even when you had arrived from another preview page; the new button uses browser history and falls back to the console.

Fixed

  • Creature ids are accepted with either prefix, case-insensitively

    (CRE- and cre_, modules/entity_ids.py): a project using cre_sibuxiang ids had its creatures dropped from the blocking-map check, scene cast, storyboard board and whitebox (fengshen3 ep06 strict went from 12 findings to 0).

  • Retired and deferred DAG nodes no longer count as pending work

    : skipped / cancelled / waived terminal states (such as the retired p0-version-init) and blocked (the user's explicit hold) made the watchdog wake the executive producer on every round with nothing to do.

  • Diagnostic-data checkboxes are left-aligned with the other settings rows, and minor console layout polish.

v1.0.31

Added

  • Long take → automatic continuation

    : explicit continuous-action boundaries use the previous group's final 2–3 seconds with forward-extension instructions; cut boundaries, unsupported video-reference routes, short final shots and exhausted video budgets use a last-frame reference. New sync_continuity_refs.py plans forward references and prepares actual clips before generation, preserving character images, reserving mandatory camera whitebox budget, renumbering references and detecting stale predecessor media before submission. Tail clips are silent and stored separately from finished clips. Preview shows video-tail continuation; settings help updated in all 11 locales. Legacy boundaries keep cut continuity; see docs/continuity.md.

  • Storyboard page: notes

    (/preview/board): a "🗒 Note" button on the episode title row, on every scene header and on every shot row (placed right before the "✏️ Feedback / Edit" button) opens a small editor for the user's own thoughts about that spot — shot intent, framing, pacing, treatments to keep or avoid. Notes are saved to directing/<ep>/storyboard_notes.json (keys * = episode, S01 = scene, S01-03 = scene + draft shot order; shot entries carry the shot's content snippet so agents can match them after a re-cut) and shown in yellow on the page. They dispatch nothing: the storyboard, shot-planning and reviser agents must read the file (role prompts and WORKFLOW.md updated) and use the notes as reference for the shot design whenever they rework the episode. POST …/board/<ep>/notes {key, text} (empty text deletes). All 11 language dictionaries updated.

  • Reference Files page: "Fonts" category

    (refs/fonts/, ttf/otf/ttc), placed right before "Text": upload fonts once per project and every later step that sets type — stylized captions, thumbnails, intro/outro title cards, subtitles — must inventory them first and use them consistently across the whole production (WORKFLOW.md §2 rule 9; caption / thumbnail / title role prompts and the runtime reference-material injection updated). Font cards render a live sample of the uploaded face. The caption engine merges project fonts into the font manifest at render/check time under proj:<family> ids (no fonts-scan rerun needed; new render_captions.py fonts-list --project <slug> lists them first), and uploads to the category are validated to font extensions. All 11 language dictionaries updated.

  • ComfyUI / RunningHub MiniMax-H3: reference videos

    : the H3 Ref2VA route (local ComfyUI, Comfy Cloud, RunningHub .cn / .ai — the same official MiniMaxH3ReferenceToVideo node) now accepts --ref-video and is capped at the node's four dynamic inputs: ≤9 reference images, ≤3 reference videos (24 fps frame sequences, 2–15 s each), ≤3 video soundtracks paired by index with their video, ≤3 standalone reference audios. Each reference video is probed with ffprobe and, when needed, transcoded to a 24 fps H.264 MP4 (clipped to 15 s, long edge ≤1344, cached under the runtime dir) before upload; it is wired through LoadVideo → GetVideoComponents, the frames to ref_videos.ref_video_N and, when the file has a soundtrack, its audio to the same-numbered ref_video_audios.ref_video_audio_N. Template leftovers on those sockets (a RunningHub author's demo video chain) are unlinked and collected before submission. Whitebox camera videos and long-take tail continuation now budget the ComfyUI channel accordingly (3 videos / 15 s on an H3 Ref2VA workflow, none on other workflows) instead of treating the channel as unsupported.

  • Post-processing: shot splice (/preview/post)

    : a collapsible "✂ Shot splice" section at the top of the right pane (above the recipe list, collapsed by default, click the header to expand) cuts together two versions of one shot group. Pick the group, import another version of its video (native file dialog on macOS / Windows / Linux that opens in the group's current video folder — POST …/post/<ep>/pick-file + …/import; browser upload …/import-upload as the fallback) — the file is copied to assets/post/<ep>/<grp>/import_<name>.mp4 and registered as a new version so it can be A|B compared. Then mark time ranges on the A version with "Mark range" (each finished mark lands in the cut list; ranges are editable, overlapping ones merge); "Build new version" (…/splice) keeps the rest of A and replaces every cut range with the same time range from the replacement version — one ffmpeg pass normalised to A's size/fps, audio kept (silence where the replacement has none, last-frame padding where it is shorter) — and registers the result as the next vN.mp4 in the version chain without moving the pointer; a "Set current" shortcut adopts it. Imported and spliced versions show their origin in the A/B selectors and the version-chain dialog.

  • Director console (/preview/director)

    : a full-window workbench on the compiled whitebox of one shot group — left: scene → group → shot list; middle: the four whitebox views (top / plate / orbit / camera) with play, frame step, loop, click-to-pick of characters / extras / props / the camera body / scene objects, an object table with per-object state at the playhead (in frame / off frame / exited / hidden by shot list) and the shot's storyboard info; right: the agent's pending whitebox issues, a note queue and the version list. Notes (directing/<ep>/whitebox/director/notes.json, host-owned) are written per object and moment (draft → submitted → applied | failed); "Submit" bundles a group's draft notes plus its decided issues into one batch dispatched to 07-directing/whitebox-staging, which rewrites the plan and source files and recompiles. When the run ends the host snapshots the compiled group as a new version (…/director/versions/<grp>/v<N>.json, a baseline is taken before the first submit); no MP4 is rendered — A|B split / wipe / flash compares two snapshots live in three.js. New versions arrive through a director SSE event (plus the generic run event and polling) and auto-load as A = live, B = previous. Entry link next to "✏️ Edit" on the storyboard page's whitebox panel and in the console header. See docs/whitebox.md "导演台".

  • Director console v2: direct editing with an override layer

    : unlock the edit bar and drag characters, extras, props, the camera body and the camera aim point in the orbit / top / plate / front / side views (move G / rotate R, Shift for height; in the camera view drag to look around and WASD/QE to fly). Moving objects get an auto-keyframe at the playhead; objects that are static for the whole group move as a whole. Selected characters show their motion path: drag waypoints, click the path to insert a keyframe, Delete removes one, labels show time and per-segment speed. A multi-track timeline (shot segments + one lane per object) lets keyframe diamonds be retimed by dragging; numeric X/Y/Z/yaw fields, per-object locks, undo/redo, and 0.1 m / 15° snapping. Every drop is written to a host-owned override file (directing/<ep>/whitebox/director/overrides.json, whole keyframe arrays per object) that compile_episode layers over the agent's plan and immediately recompiles episode.json, so previews, exports and reference wiring see the change with no agent run. Submitting a batch sends the override values verbatim for the Whitebox Staging agent to bake into the plan and source files; on reconcile, objects the plan now matches are dropped from the override layer, the rest stay flagged "not baked". Two new orthographic views (front / side) are page-side cameras so exported video fingerprints are unaffected.

  • Director console v3: the director's station

    : a quad monitor (whitebox camera view / storyboard sketch / shot plate / generated clip, all locked to the playhead), framing guides (thirds, center cross, action-safe) in the camera view, a lens data bar (focal length from FOV, camera height, tilt, distance to the nearest subject and an estimated shot size next to the shot-list size), clearance circles with red intersections and per-object gap warnings, a 180° axis line between the shot's two principals with the camera side and a "crossed the line" flag against the previous shot, floor marks per scene (director/marks.json, snap-to-mark while dragging), a measuring tool, per-group approval with staleness tracking and an "approved n/N" counter, the H3W sign-off card answerable from the console header, and department routing for notes (whitebox staging by default, or scene modeling / shot planning / prompt / reviser) with one batch dispatched per route.

  • Character poses and actions, structured per shot (poses)

    : the storyboard layer now records every on-screen character's body state per shot — pose from a controlled set (stand / sit / lie / kneel / crouch / prone) plus a free-text action in the interface language (passed through verbatim, never translated). Storyboard sketch prompts gain a Body poses and actions: sentence (per character, host-mapped English pose + action; grid prompts keep it uncut), falling back to keyword extraction from the shot text for legacy projects. The field is inherited by the shot list, by blocking (pose per character, staging fragments start with the pose), by the whitebox compiler (keyframe pose priority: blocking → shot list → text guess; renderer and in-frame bounds support the three new poses) and by the video prompt's subject action. New check code/storyboard_pose_check.py (pose_present); the board page shows a 🧍 line per shot.

  • Storyboard sketches: figures first, minimal background; 2×2 batches

    : sketch prompts now ask for clear character line work with correct proportions for the shot size, readable facial expressions, eye lines and gestures, and the stated camera angle/height/lens, while the background is reduced to a few faint lines (no scene reference images exist at this stage). The scene-card description is no longer sent; only a short location hint remains at the end of the prompt. Camera: and Expressions: phrases are derived automatically from the storyboard text (angle, height, lens, facing, movement; emotions and eye lines). Batch sketching drops from 3×3/9 shots to 2×2/4 shots per contact sheet so each tile is about 1230×690 (3×3 tiles were too small to draw expressions and left too little prompt budget per panel); the grid prompt trimming order now cuts location text first and keeps camera/expression/composition longest. Negative prompts add background/architecture terms. Tooltip updated in all 11 locales.

  • Storyboard board: 3×3 batch sketching

    : the per-scene "Draw sketches" button moved to the end of the episode title row and now draws the whole episode as contact sheets — one 3×3 grid image per 9 pending shots (2×2 for 2–4 leftovers, single for one), split into per-shot sketches with the grid source kept under assets/storyboard/<ep>/_grids/. Shots that already have a sketch (single or grid) are skipped; Shift+click redraws all. Per-shot "Draw / Redraw" buttons still produce a single full-size sketch. The animatic now scales every sketch to the canvas so mixed sketch sizes render uniformly.

  • Post-production preview page (/preview/post)

    : a three-pane finishing workbench that sits between the shot-group sign-off (H3B) and the final-cut gate. Every adjustment is a "recipe" with a four-level scope (episode → scene → group → in-group range) across five sections (detail fill, grade & light, VFX, packaging, sound). ffmpeg-class recipes (basic correction, LUT presets, scene palette, reference-frame match, atmosphere/light, deflicker, delogo, overlay asset, watermark) render locally through the new host CLI code/post_apply.py; agent-class recipes (upscale, local repaint, interpolation, relight, generated VFX) dispatch to the new 10-editing/post-finishing agent; record-class recipes are ledgered and landed by the audio/caption stations. Group masters are never overwritten: outputs form a per-group version chain with A|B split/wipe/flash comparison, rollback, an SFX cue table, an episode timeline with three layer lanes, an assembly pre-check (post_ok machine checks), one-click final assembly (cut_post → transitions → finalize) and a new per-episode human gate H3P (g9p) with stale-signature detection and version retention cleanup.

  • Reviser agent for preview-page edits

    : the "✏️ Edit / Feedback" buttons on the character, creature, prop, scene, worldview, script, storyboard-board and storyboard pages now send to a new stateless, concurrent 00-orchestration/reviser agent instead of the Producer. The popup passes a structured target (kind / id / episode / files) with POST /api/v1/runs; the server prepends a [修改单] header, appends the SOUL of the one or two responsible agents (REVISION_KIND_AGENTS) so the reviser edits in that agent's format, and lists earlier revision records for the same object. The reviser edits, regenerates, runs the agent's machine checks and registers versions itself; user notes count as the user's decision, so no evaluation/QA pass is dispatched. On completion the host parses the trailing ## 变更记录 block into runs/revisions/<run_id>.json (GET /api/v1/projects/{project}/revisions). With the popup's new "also rerun affected downstream tasks" box unchecked (default) the record is only attached to the next auto-run status check so the Producer marks dirty nodes without redoing the work; checked, it is delivered to the Producer immediately. The popup also warns when related tasks for the same object are running. Workflow-page DAG edits and buttons already routed to a specific agent are unchanged. See docs/reviser.md.

  • Per-episode channel / model / prompt-skill switches in the storyboard preview header

    : the header line is now 🎬 [channel] [model] · 🧰 [skill] with three dropdowns (e.g. 🎬 火山引擎 Seedance 2.5 · 🧰 手选 → sd25-pe), each defaulting to "follow global". Channel can be switched to any catalogued video channel with an API key configured on the Generation Models page (unconfigured ones are greyed out); switching channel preselects that channel's saved model, and the model list follows the selected channel. Skill offers follow global / auto by episode model / each installed skill / skip. The episode override lives in assets/group_settings/epNN/episode.json (GET/POST /api/v1/projects/<p>/storyboard/<ep>/settings). Resolution order is global → episode → group: existing per-group 🎛 overrides still win (their model list and "follow" entries now come from the episode), genmedia swaps the whole submission config to the episode channel when one is set (resolve_video_override, shared with whitebox reference budgets), shot-plate binding and the prompt_skill_applied check fall back to the episode file, and the prompt/video-generation system prompt lists episode overrides (channel included) next to group ones. Strings added in all 11 locales. A running instance must be restarted.

  • Storyboard preview page (/preview/board)

    : a per-episode view of the storyboard layer's output — the director plan rendered as collapsed Markdown (moved here from the shot preview), a header per scene with the assets involved (scene / cast / creature / prop thumbnails, time of day, allocated vs. draft vs. final seconds), and a per-scene table of draft shots (number / content incl. action, dialogue and narration anchors / pencil sketch). Each scene has a "🖊 出草图" button that renders small hand-drawn-style storyboard sketches through code/storyboard_sketch.py (downscaled character sheets as the only references — no scene images, the location is described in text — and the pencil style described in the prompt only; aspect ratio follows the project output; Ark channels request the Seedream 5.x minimum of 3,686,400 pixels and the result is downscaled to 1280 px), with a separate image channel/model picker in the page header so a cheap model can be used (GET/POST /api/v1/config/sketch-model; genmedia image --provider/--model per-call override). A per-sketch "✏️ 改草图" button dispatches to the new 07-directing/storyboard-sketch agent, the cell shows "redrawing" until the run finishes and the image refreshes automatically; every other change request on the page goes to the producer. Console top bar icon and preview-page menus list it before the shot preview. Two follow-ups on the same page: a new human gate g6s "H3S-故事板确认" between p6-storyboard and p6-shots (the page shows the pending sign-off card with "✅ 签字确认 / ⏸ 暂缓", records directing/<ep>/storyboard_signoff.json and flags the signature as stale when storyboard.json changes afterwards), and an animatic (code/animatic.py, zero generation cost): sketches strung together by final/draft durations with burned-in dialogue/narration subtitles and narration/dialogue tracks mixed in when present, missing sketches replaced by placeholder cards; rendered from the storyboard page or the new "🎞 动态样片" section of the video preview (POST /api/v1/projects/<p>/board/<ep>/animatic).

  • Script preview page

    : new /preview/script (console header icon and every preview page's menu, placed before Storyboard Preview) shows the story layer's output for one episode as a single two-column table: the left column is the screenplay cut into small blocks in original order (scene header, action paragraphs, consecutive dialogue lines, narration, transition) and the right column carries the information tied to that block — per scene the duration split into dialogue / narration / silent time with a three-colour bar, emotion face + intensity, tempo (🐢 ▶️ ⚡), beat / dramatic purpose, event-card summaries, hooks placed in the scene, trim suggestions and incompressible items; per dialogue line emotion, estimated seconds and style hits; per narration item its N-id, seconds, tone and anchor. An episode row on top pairs the logline / plan beats with budget vs estimate, chapter range, covered events and the selected hooks. Every row has a “✏️ 反馈” button that opens the shared edit popup pre-filled with the block's location and excerpt, addressed to the responsible story agent (screenplay / dialogue-rewrite / narration / hook / pacing / episode-planner / event). Data comes from the new per-episode script breakdown story/episodes/<ep>/script_breakdown.json (schema script_breakdown/1.0 with scenes[].blocks[], owner 01-story/timeline-story, new DAG node p5-breakdown between p5-pacing and the H3 gate, machine check code/check_script_breakdown.py); projects without it get a view derived server-side by modules/script_breakdown.py from screenplay.md / dialogue.md / narration.md / hooks.json / pacing.json / episode_plan.json (several historical screenplay layouts are recognised; final narration entries replace the screenplay's narration candidates when they match) plus a “🔁 重新分析” button that dispatches the breakdown work order to the timeline-story agent and refreshes when the run ends. The screenplay and narration text blocks were removed from the storyboard preview (and their raw text from its API payload; per-shot narration alignment stays). Strings added in all 11 locales; docs/script_breakdown.md documents the schema. A running instance must be restarted.

  • Fal tab in Generation Models → Image

    : new image.fal channel (placed before MiniMax) with Fal-hosted image endpoints — Seedream 5.0 Lite (default) / 4.5, Nano Banana Pro / 2, GPT Image 2.5 Flare / GPT Image 2, FLUX.2 Pro / Max, FLUX Kontext Max, Qwen Image 3, HunyuanImage 3.0 — plus "Custom…" for any full endpoint ID. The stored model ID is the endpoint family prefix; modules/genmedia.py image uses the text-to-image endpoint without references and switches to the family's edit / multi endpoint when --ref is given (HunyuanImage has no edit endpoint and refuses references). Fields are mapped per family: explicit {width,height} for Seedream / FLUX.2 / Qwen / Hunyuan, size presets for GPT Image, aspect ratio (+ 1K/2K/4K tier) for Nano Banana and Kontext; --negative goes to negative_prompt on Qwen / Hunyuan and is appended to the prompt elsewhere; --seed is honoured except on GPT Image; reference caps 10 (Seedream) / 14 (Nano Banana) / 16 (GPT Image) / 3 (Qwen) are checked before submission. Submission goes through the shared queue.fal.run runner (now common to image and video), downloading images[0].url. The Fal API key is shared between the image and video tabs (either one is enough; FAL_KEY env fallback). --dry-run prints the resolved endpoint and fields. WORKFLOW.md channel table and all 11 language dictionaries updated. Not yet exercised against a real Fal key. A running instance must be restarted.

  • Wan 3.0 preset in Model Limits

    : the shot-group settings dialog and the new-project wizard's "模型限制" step get a fourth default-value button, Wan 3.0 (30 s / 10 reference images / 5 reference videos / 5 reference audios, matching video_model_caps); shot_group.preset accepts wan30, and the wizard's effective-model hint (GET /config/video-model) reports the wan30 family when the active video model is Fal alibaba/wan-3.0. A running instance must be restarted.

  • Wan 3.0 on the Fal video channel

    : alibaba/wan-3.0 joins the Fal model list (Generation Models → Video → Fal, the group-level model catalog and /live). modules/genmedia.py video maps it to text-to-video / image-to-video (start_image_url + optional end_image_url) / reference-to-video (reference_image_urls / reference_video_urls / reference_audio_urls) from the inputs, with the official limits enforced before submission: integer duration 2–30 s, 480p/720p/1080p (360p→480p, 4k→1080p), aspect 16:9/4:3/1:1/3:4/9:16 (others fall back to adaptive; first/last-frame runs always adapt to the image), --seed honoured, --generate-audio mapped to audio, ≤10 reference images, ≤5 reference videos and ≤5 reference audios with each kind totalling ≤15 s (ffprobe pre-check). Capability tables (video_model_caps, whitebox/continuity video budgets) know the model, so per-group reference caps and the live-stream duration range follow it; all 11 language dictionaries updated. Not yet exercised against a real Fal key. A running instance must be restarted.

  • Whitebox-visible cast only in group reference images

    : on projects with the whitebox chain enabled, a group's refs may only carry character/creature sheets for cast that actually appears in that group's camera-view whitebox video. modules/whitebox_refs.appearing_cast decides per actor (scene presence, visible keyframes, per-shot visible_actor_ids, and a bounding-sphere frustum test against each shot's camera with a 10% frame margin; occlusion is not considered). sync_scene_cast.py attaches images only for visible cast and records whitebox_hidden reasons for the rest; sync_whitebox_refs.py --write removes stale hidden-cast images, renumbers [Image N], restricts the Whitebox legend: to visible cast, and records whitebox_refs.cast (visible / hidden / dropped_refs). Both checks and refs_referenced_check.py report leftover hidden-cast images as violations; when the prompt body still cites such an image the refs are left untouched and the violation asks for a prompt edit first. Groups not yet compiled into the whitebox episode, or projects with the setting off, are unaffected.

Changed

  • Output settings: whitebox, narration and dialogue-voice generation are off unless explicitly enabled

    : output.spatial_blocking (whitebox / precise character blocking) and output.narration_enabled no longer fall back to on when a project's settings.json lacks the key — legacy projects that never configured the three toggles (whitebox, narration, generate dialogue voice) now all read as off, matching the new-project baseline and the existing output.dialogue_tts behaviour. Every consumer (server defaults, agent system-prompt lines, preview pages, host CLIs and checks, whitebox/continuity reference wiring, the Output Settings dialog) checks for an explicit true; open the Output Settings dialog and tick the toggle to re-enable a feature on an existing project.

  • Prop references default to the scale-anchor image only

    : a story prop now contributes a single image to a shot group's refs and anchor pack by default — its scale_ref_01.png (prop with a no-person scale reference in frame). The style/close-up image main_01.png is no longer attached alongside it (previously the first-appearance group carried both and later groups carried only main_01); it is added only on demand when the group actually has a detail close-up of that prop. Updated the prompt, image-generation and prop SOULs, WORKFLOW/workflow.yaml (prop_ref_listed), and code/refs_referenced_check.py now warns (prop_ref_single) when one prop directory contributes two or more refs, or only a style image without the scale anchor.

  • Per-page image model for scene / character / creature / prop previews

    : each of the four asset preview pages now has a "🎨 Image model" channel/model picker in its header, like the storyboard page's sketch model. The choice is stored in state.json image_model_prefs.<kind> (GET/POST /api/v1/config/image-model/<kind>; the sketch model moved to the same store, /config/sketch-model still works) and is applied automatically by genmedia whenever an image is written under assets/concepts/<kind>/ (concept art, costume sheets, panoramas, shot plates, layout maps), so the concept-art agents need no changes. Explicit --provider/--model still wins; a chosen channel that is no longer configured falls back to the global image channel with a warning. Empty = follow the global Generation Models setting.

  • Live run logs no longer go quiet between tool calls

    : the Claude and Grok engines now run with --include-partial-messages, so assistant text streams as it is generated and long tool-call arguments (multi-KB heredocs) report their size while being produced instead of appearing only once complete. Every engine now shows a thinking heartbeat (Claude/Grok estimated thinking tokens, pi thinking characters, Codex/opencode turn markers) and a tool receipt (output size and first line, error flag) after each tool finishes; DeepAgents runner emits a new tool_result event for this. The run card's progress line and the chat live box share the new lightweight progress SSE event. Incremental stream_event lines are fed to the UI only and are not written to runs/<id>.jsonl, so usage accounting and log size are unchanged.

  • Saving Generation Models no longer messages the producer

    : the "[settings changed]" chat that was auto-sent to the workflow orchestrator after every save on the Generation Models page is gone (channel/model switches take effect on the next genmedia submission anyway, and the notice only tempted the orchestrator to dispatch work). Project-settings and creative-brief changes still notify the orchestrator as before.

  • Per-agent image/video provider overrides removed

    : the “🧠 Agent model config” dialog now only sets the execution engine and language model. The image-provider / video-provider selectors, their image_provider / video_provider fields in data/.videoagents/agentmodels.json, the PUT /api/v1/agents/models validation for them and the runtime lookups (genmedia provider resolution, active_video_provider, RunningHub / Agentics detection, avatar auto-management) are deleted rather than hidden. Image and video providers are configured in one place only, the 🎨 Generation Models page; stale override fields in existing agentmodels.json files are ignored on read. The unused “Image Model” string was dropped from all 11 locales.

  • Whitebox export merged into the whitebox staging agent

    : the 07-directing/whitebox-export agent is removed; its duties (export each group's camera-view camera.mp4 + manifest.json after the user signs the H3W whitebox gate, verify with render_whitebox.py --verify-export, auto-wire video_refs via sync_whitebox_refs.py --write) now live in 07-directing/whitebox-staging under a new “视频导出” section of its SOUL.md. The p6-whitebox-export work order in workflow.yaml keeps its id and gate dependency but is dispatched to whitebox-staging; every message previously addressed to the export agent (orchestrator SOUL, WORKFLOW.md, docs, project-settings prompt) now goes to whitebox-staging. The retired agent name was dropped from all 11 locales.

  • Retry count now defaults to 0

    (Settings → Advanced → Agent Advanced Settings): agents no longer rerun / reroll automatically unless the user raises the cap — the first failed acceptance score, QA review or machine check is escalated to the user straight away. MAX_RETRIES_DEFAULT = 0 on the backend (max_retries_default in GET /api/v1/config/agent-advanced), the dialog's fallback value and the WORKFLOW.md rework rule follow suit; a max_retries already saved in state.json is kept as is. 11 i18n languages updated.

  • Whitebox export is camera-view only.

    code/render_whitebox.py (and the compatibility export API) now renders and saves just assets/whitebox/<ep>/<grp>/camera.mp4 + manifest.json; the top-down top.mp4 is no longer produced (the top view stays available interactively in the scene/storyboard previews). code/sync_whitebox_refs.py attaches only camera.mp4 as [Video 1], the Whitebox reference: block no longer carries a top-down sentence or the camera-box/shooting-direction legend, whitebox_refs.top is gone, and the project setting output.whitebox_top_video is retired (a leftover key in settings.json is ignored). Existing whitebox folders are re-rendered on the next run because the manifest file list and renderer fingerprint changed; a stale top.mp4 is deleted during that re-render, and a pre-change manifest still attaches camera.mp4 as long as that file exists.

  • Context agent removed.

    The 00-orchestration/context agent (上下文管家) and the two-tier full/inline Context Package scheme are gone: the orchestrator now inlines every work order's context (input file paths for the current Bible/upstream artifacts/relevant defect tickets + hard constraints) in instruction/inputs, and retry orders (attempt > 1) must carry the previous failure reason and the evaluation notes (runs/<task_id>/eval.json listed in inputs). runs/<task_id>/context.md is no longer produced, so the run record shrinks from four files to three (result.json, eval.json, meta.json) in agents/workflow.yaml and every plugin DAG; the before_dispatch service hook, the context_package work-order field, the agent's stateless/tier entries in the runtime, and its i18n strings in all 11 locales are removed, and every SOUL input table now points at the work order itself.

  • Project skills

    is now available in Project Settings. Installed skills are grouped by Agent name and directory, with searchable one-line descriptions and project-scoped checkboxes. Only the selected video-model prompt skill starts enabled; other skills, including performance direction, require opt-in. Activation rules apply to resumed conversations and group overrides. Disabling performance direction also skips its upstream requirements and downstream checks.

  • codex Smart Assignment high tier now resolves to gpt-6-astra

    (was gpt-5.6-sol); the low tier stays gpt-5.6-terra. The tier is resolved at dispatch time, so existing installs on the codex smart policy pick it up after a restart; the top-bar tooltip and all 11 i18n dictionaries are updated.

  • "Precise character positions" now means whitebox-video positioning, and the whitebox camera view is wired into video generation as a reference video.

    The project output setting output.spatial_blocking (人物精确空间位置, default on) now also drives the whitebox chain (whitebox_requested in workflow.yaml = this switch): scene-modeling and whitebox-staging run for every project with the switch on. After code/render_whitebox.py exports a group's assets/whitebox/<ep>/<grp>/{camera.mp4,top.mp4}, the new host CLI code/sync_whitebox_refs.py --write (called automatically by the export, and by the prompt agent after writing a prompt) attaches them to the group prompt: video_refs gets camera.mp4 first ([Video 1], the real-camera previs that fixes framing, screen position, depth, facing and timing) and, when the effective video model's reference-video budget allows, top.mp4 ([Video 2], spatial understanding only) — Seedance 2.0 allows 3 videos / 15 s total so groups longer than 7.5 s carry the camera view only, Seedance 2.5 allows 10 / 30 s, comfyui/runninghub channels get none; the decision is recorded in whitebox_refs.skipped_reason. The same tool inserts a fixed English block before Shot 1: — Whitebox reference: (what each video is for), Whitebox legend: (each figure's colour ↔ character label and id from directing/<ep>/whitebox/episode.json, riders sharing their mount's colour, extras; white eyes/nose tip = facing; the dark camera box and its line = camera and shooting direction in the top view) and a "do not reproduce the whitebox look" sentence — and adds No whitebox look … to Global constraints:; the original prompt is backed up once under directing/<ep>/whitebox/prompt_backups/. New check whitebox_ref_bound (same script without --write) gates p7-prompt; refs_referenced_check.py now also verifies [Video N] references (videoref_all_referenced). video-generation passes video_refs in order as --ref-video (Ark/MiniMax reference videos need a public URL, so object storage must be configured under Settings → File hosting). Settings tooltip and 11 i18n dictionaries updated; a running instance must be restarted. The MiniMax H3 preset in New project → step 4/6 Video model settings (and the matching model hard-limit table) now allows 3 reference videos and 3 reference audios (was 0 / 2), so H3 groups can carry the whitebox camera view too.

  • Character positions in video prompts go back to the landmark + screen-side wording, and the annotated blocking map is retired

    (spatial blocking on). Now that each shot group has a 3D whitebox reference video (assets/whitebox/<ep>/<grp>/{top.mp4,camera.mp4}, see docs/whitebox.md) carrying character positions and paths, two earlier layers became redundant and are rolled back: (1) the 2026-09-03 camera-view rewrite (commit 7440c15) — space_fragment_en is again a short landmark-relation + screen-side + facing phrase, frame_position and the per-group blocking_map.station_table are no longer required, the prompt no longer carries a Blocking table: block, and code/camera_view_check.py plus the camera_view_consistent / station_table_ok / station_table_bound checks are removed; (2) the lettered blocking-map rendering — code/render_blocking_map.py is renamed to code/blocking_map_check.py and only validates the blocking_map data (scene_layout_pack_ok, blocking_map_present, label_ok, creature_blocking_ok); it no longer writes directing/epNN/blocking_maps/*.png, the Map markers: sentence is gone, and the storyboard preview shows blocking-map characters as colour chips (same palette as the whitebox models) instead of A = CHAR-id letters. Group prompt refs now attach the scene's clean assets/concepts/scenes/<sid>/layout_top.png directly (no character markers) together with grid_9views.png; layout_map_bound_check.py requires both, flags any remaining blocking_maps/ ref as a violation, and keeps the Map usage: / no top-down or bird's-eye view rules. The storyboard-preview "add reference" endpoint no longer accepts paths under directing/. Existing group prompts that still reference blocking_maps/grpNNN.png fail layout_map_bound until the prompt is re-dispatched; blocking_map data itself (start/path/end, route_en, label, mounted) is unchanged and remains the source for the whitebox compiler.

Fixed

  • Whitebox cast filter: pose-aware bounding box, and off-camera characters stay out of the prompt text

    : appearing_cast (which decides whose reference images a whitebox group may carry) tested a bounding sphere against the camera frustum; for a 1.7 m figure that sphere is 0.9 m in radius, so a character seated well outside the frame (liaozhai3 ep01 grp014, 范生) still counted as "in frame" and got a reference image and staging text. The test now uses an axis-aligned box sized by pose (stand / sit / lie). sync_whitebox_refs.py also reports whitebox_hidden_mention when the prompt body names a character the whitebox does not show (by id or whitebox label) outside host-written blocks, verbatim route_en fragments, director's notes and explicit "does not appear" sentences. Prompt SOUL / WORKFLOW / docs/whitebox.md updated: such characters get no image, no @Image binding and no mention.

  • Prompt fixed blocks no longer drift or duplicate across re-syncs

    : Scene presence references, Whitebox reference and Shot plates blocks now keep a canonical order (presence → whitebox → plates → Shot 1:), the presence block is removed reliably even after another sync trimmed its trailing newline (previously it could be appended twice), and the whitebox sync updates its note in place, so repeated --write runs of the three syncs leave prompts unchanged.

  • Whitebox reel in the video preview

    : /preview/videos shows a new “🧊 Whitebox reel” section under the final-cut videos. It looks for assets/whitebox/<ep>/<ep>-camera.mp4 and plays it in the same card format as the final cut (size, duration, group count, resolution/fps), flags the reel as stale when any group's camera.mp4 was re-rendered after the reel, and lists groups still missing a whitebox video. “🔄 Regenerate whitebox reel” dispatches a work order to 07-directing/whitebox-staging (engine follows the top-bar setting) and refreshes the page when the run finishes. New host CLI code/concat_whitebox.py --project <slug> --ep <ep> [--allow-missing] [--status] concatenates every group's camera.mp4 in shot_list order with ffmpeg (stream copy when all groups share the same size/fps, otherwise re-encoded to the project format), verifies the total duration with ffprobe, publishes atomically and writes episode-manifest.json (whitebox_episode_export.v1 with group_order/group_sources). render_whitebox.py refreshes an existing reel automatically after re-rendering groups. Strings added in all 11 locales.

  • Project skill execution records

    : Project Settings → Project Skills → Execution Records lists Agent, skill name and content version, execution times, episode/group, trigger reason and lifecycle status. Project-scoped metadata survives restarts. Agents report start/completion/skip/failure through a shared CLI; incomplete reports remain unverified instead of being inferred from file reads. No prompt comparisons or artifact snapshots are stored.

  • Reference videos in the storyboard preview

    : a new “🎬 Video” button next to “🖼 Image” on each group card opens a dialog listing the group's video_refs with their [Video N] numbers (matching the prompt), inline playback, per-video duration and the total duration against the effective video model's limit. Videos can be uploaded from disk (mp4/mov/webm, ≤45MB) or removed after a confirmation (pipeline-injected whitebox / continuation videos need a stronger confirmation; prompt references are renumbered and their fixed blocks rolled back). When the count or total duration exceeds the model's limit, the card and dialog warn and leave the decision to the user: remove videos or switch the group's model via “🎛 Model” — agents must report rather than trim. Strings added in all 11 locales.

v1.0.30

Added

  • Live → Director mode

    (link_mode: director, fourth option in Continuity mode on /live; model fixed to minimax/h3-max/director, the Fal WMA realtime endpoint, which is not part of the genmedia catalog). Instead of the generate-then-play clip loop, the browser opens a WebRTC session with Fal: the SDP offer goes through the API bridge (POST /api/v1/live/director/bridge/{path}, the Fal key is attached server-side and never sent to the page), video + audio arrive continuously in ~10 s chunks straight into the page's <video>, and the prompt travels over the data channel — a new ✏️ Update prompt (applies immediately) button changes what is being generated mid-stream. Fal keeps its own short-term memory across chunks, so no last-frame continuation is needed; a new Advanced field Memory (segments) (1–50, default 12) sets how many recent segment prompts are kept as context. Reference image #1 is used as the first frame (uploaded through the configured object storage, or inlined at ≤480 px when no storage is configured); resolution 480p/768p; clip length / unplayed-queue cap / max clips do not apply. There is no job subprocess: the session is alive as long as the page keeps polling (45 s without a heartbeat = disconnected; POST /api/v1/live/director/event, GET /api/v1/live/director/config). The page records the incoming stream with MediaRecorder and uploads it in order (POST /api/v1/live/director/record, segments/recording.webm); on Stop the recording is transcoded to seg_0001.mp4 and its last frame extracted, so session history replay and Continue from the last frame keep working for director sessions. The status panel shows connection state, chunks received / played, generation time, buffered chunks, route and recording state; browsers without WebRTC are refused, an ICE failure shows a UDP-blocked hint, and reopening the page after a lost connection closes the stale session out. Fal limits: billed per second with a 60 s minimum, sessions capped at 2 minutes by default (longer needs Fal approval); closing the page ends the session. 11 i18n languages updated. A running instance must be restarted.

  • Live → Continuity mode gains a third option, *No continuity (text-to-video)

    * (link_mode: none, Advanced options on /live). Every clip is generated from the prompt alone through the model's text-to-video endpoint: no reference images, no previous-clip last frame, and no continuity clause appended to the prompt, so clips do not join visually (useful for prompt-only streams or when the reference/last-frame chain keeps degrading). Reference images are left untouched but not submitted; the prompt becomes mandatory in this mode. The last frame is still extracted per clip, so switching back to a continuity mode later or Continue from the last frame keeps working. 11 i18n languages updated.

  • Live → “📚 Session” menu

    (/live top bar, hover to open): New session clears the current session view, resets the prompt/settings to defaults and clears the reference images (old session folders are kept; POST /api/v1/live/sessions/new, refused while streaming). Sessions lists every session under data/live/sessions/ (newest first, with last-frame thumbnail, status, model / aspect / resolution / clip length, clip count and total duration, reference count, disk size, prompt excerpt). Load switches the page to that session — its clips replay from the first one, and Continue from the last frame of the previous session now continues from it — optionally restoring the session's prompt and model settings into the form and replacing the current reference images with the session's snapshot (both on by default). Each session can also be deleted individually. Loading and deleting are refused while a stream is running. Endpoints: GET /api/v1/live/sessions, POST /api/v1/live/sessions/{sid}/load (apply_settings, restore_refs), DELETE /api/v1/live/sessions/{sid}. 11 i18n languages updated.

  • Fal video: MiniMax H3 Max Turbo

    (minimax/h3-max-turbo) added to the Generation Models → Video → Fal catalog and the /live model list — text-to-video and first/last-frame only, 480P/768P. The /live page now reads the same Fal model catalog as the settings page (VIDEO_MODEL_CATALOG["fal"]), so new entries appear there without a separate list; H3 Max Turbo, which has no reference-to-video endpoint, is driven there like Kling (reference #1 as the first frame, then the previous clip's last frame).

  • AgenticsLLM is now a first-class signed-in desktop provider

    for image, video, music, TTS, and DeepAgents. Each Generation Models section puts AgenticsLLM first, shows the desktop account (or a login action), and loads its selectable media profiles from agentics-service; all media profiles are fetched in one request and split into image/video/music/TTS lists by media_type. Generation follows the authenticated profile/task API, including profile-filtered fixed parameters, presigned reference-file uploads, idempotent task creation, polling, and immediate artifact download. DeepAgents uses the authenticated Agentics OpenAI-compatible wrapper and the service-managed model list, while the separate OpenRouter tabs now always mean a user-owned OpenRouter key. Older desktop configs that used an empty OpenRouter key as the implicit account wrapper migrate to AgenticsLLM; fresh desktop installs default every media channel and the DeepAgents model to Agentics. make desktop-dev and packaged dev channel builds now select the complete development service set together (https://devapi.agentics.world, https://devsso.agentics.world, and the dev OpenRouter wrapper), with a separate development login store so test credentials never overwrite production credentials; a 401 from the service clears the stored login and asks to sign in again. The web-only deployment remains unsupported for AgenticsLLM because it never receives the desktop JWT.

  • Agentics media profiles now follow the summary/detail and fixed-field v1 contract

    : the four media selectors share one lightweight catalog request and show only each profile's display name, while generation fetches only the selected profile detail and caches it by profile_code + updated_at. Image, video, music, and TTS requests now submit only their defined fixed parameter names; validate JSON types, numeric limits, required fields, and profile value_map choices; and use indexed fixed attachment tokens with profile min_items/max_items. fill: repeat_last stays server-side, so one H3 reference image is uploaded once rather than duplicated for workflow slots. Structured validation errors preserve their field/reason, deterministic 4xx responses are not retried, stale unknown_field/unsupported_field responses refresh the selected detail once, and timed-out/interrupted tasks use the cancel endpoint. The conditional video-generation skill documents this contract without duplicating model prompt skills.

  • AgenticsLLM TTS reference audio is profile-driven

    : automatic timbre selection runs only when the selected profile maps files.reference_audios; text/voice-ID profiles preserve voice_id and perform no unrelated upload. Index-TTS sends one indexed reference audio and lets the service map it to the workflow target, without temporary symlinks or legacy node tokens.

  • Mashup host toolchain: footage-library channel (v3) and ASR word-level audio/visual alignment (v4).

    modules/footage.py search --provider library searches one or several footage libraries next to youtube/pexels/pixabay, catalog lists a library, still renders a Ken Burns clip from a single frame (--zoom in|out|none, --pan left|right|none, zero-tolerance duration) and doctor reports the library inventory. New code/mashup_align.py build --project <slug> --ep epNN --transcript <file> produces mashup/beat_track.json and edit/epNN/word_track.json in one run: faster-whisper word timestamps are aligned to the transcript with difflib (the transcript stays the only text source, ASR only supplies time), sentence boundaries take the measured word edge / pause midpoint (speechalign.beats_from_asr), and the same pass writes the word track used by shot-designer word snapping, cutter in-point formulas, subtitle fine-tuning and the new checks; --backend auto|asr|char_rate falls back to the previous char-rate estimate (also when the ASR match ratio is < 0.6) and records the backend in beat_track.source. code/check_footage.py gains six checks — word_track_fresh, beat_boundary_on_word, shot_cut_on_word (both skipped when the backend is not asr_word) and the rhythm trio rhythm_differentiated / no_uniform_shots / fast_cut_present. Footage-library analysis now writes 4–16-frame contact sheets with absolute-second stamps (sheet_stamps), a seven-line issue summary (adds cut-in / stability / subject) and env-configurable split parameters. Cut/still clips normalise colour range (yuvj/full-range clips are rejected by clip_spec_normalized) and stills are scaled straight from the video frame with lanczos instead of via an intermediate JPEG.

  • Footage library: delete a single clip

    (🗑 on each clip card; DELETE /api/v1/footage/projects/{name}/clips/{clip_id}). Removes the clips.json entry and the clip's mp4 / thumbnail / contact sheet and updates the counters; the remaining clip ids and indexes are not renumbered (mashup and other consumers address clips globally as <library>:<clip_id>, so a deleted id stays a gap). Refused with 409 while the library pipeline is running. The footage page also replaces all six native confirm() dialogs with a themed confirmation popup (danger actions get a red confirm button with focus on Cancel; Esc / backdrop cancel, Enter confirms only when focus is not on Cancel). 11 i18n languages updated.

Changed

  • New-project default shot duration is now 1–10 s

    (was 2–8 s): wizard defaults, API defaults (shot_min_s / shot_max_s), the runtime duration defaults and WORKFLOW.md all updated. Existing projects keep their saved values.

  • Confirmation popups on the console home lay out their actions vertically

    : the sign / retry / skip buttons of a pending confirmation each take a full-width row with wrapping labels instead of being squeezed side by side, and the countdown sits right-aligned beneath them.

  • Image generation waits up to 600 s

    (was 300 s) for every cloud image channel (OpenRouter, Volcengine, MiniMax, RunningHub, Agentics …), via one shared IMAGE_TIMEOUT constant in modules/genmedia.py.

  • Generation Models page

    : the MiniMax tab under Music is hidden — MiniMax's /v1/music_generation returns HTTP 410 (code 2153) for new users in both regions and every API version (verified 2026-09-03); the backend and saved configs are kept, so it can be re-listed once the service reopens. The text-model tab is labelled Agentics (was AgenticsLLM), and the Music / TTS section captions are shortened.

Fixed

  • Transition rendering on ffmpeg 8 no longer truncates the episode.

    ffmpeg 8 rebuilds the filtergraph wherever input stream parameters change and resets trim counters, which cut a 4130-frame concat down to 1668 frames. code/render_transitions.py render now works segment-wise: hard-cut groups reference their original clips with no re-encode and only the groups touched by a dissolve/fade are re-encoded (60–70 groups produced in one pass in practice); dissolve lengths are even-framed, boundaries are re-quantised from the actual group frame counts (fixing millisecond truncation in timeline.json), the pixel verification samples reference frames outside the transition window with a threshold that adapts to the difference between the two groups, and the whole-file two-pass path stays as the fallback for fade types. Segment paths use positional indexes (a group id that appears twice no longer collapses), dissolve boundaries are consumed one by one (duplicate adjacent pairs no longer clash), sources with an audio track are remuxed with -c:a copy in the final assembly, a timeline entry without src is reported as a contract [FAIL] instead of a traceback, and requantize_cuts leaves trimmed / speed-changed sub-range entries alone. Also fixes the black-level jump players showed at full-range JPEG stills mixed into a concat.

  • Footage page: the custom confirmation popup no longer treats Enter as "confirm" when focus is on Cancel (danger mode focuses Cancel by default, so a stray Enter could trigger an irreversible delete).

    Speech alignment: beats_from_asr anchor search no longer crosses sentence boundaries (a neighbouring sentence's speech could be labelled as this sentence's measured edge), and find_transcript verifies the beat_track episode for mashup projects; mashup_align.py labels char-rate fallback boundaries truthfully (silence_mid only when the snap actually moved, otherwise char_rate).

  • Storyboard preview: BGM cues are now placed by the first shot of the first group in covers_groups (entries may carry annotations such as grp001(…); only the group id is read), falling back to beat_ref, then the scene's first shot, then the episode start; scene ids written as S01 match as well as SCN-xxxx.

    Shot indexing tolerates storyboard_ref values of the form S01/shots_draft/order:1 (extra path segment, as seen in liaozhai2 ep01/ep02).

  • AgenticsLLM long-running video tasks now survive transient network failures

    : DNS, TCP, TLS handshake, response-read and retryable HTTP failures are classified as transport interruptions and keep polling the same task with bounded backoff and one-minute heartbeats. Agentics video waiting is extended to two hours and no longer cancels an unconfirmed task at the local deadline. The generic reclaim command now accepts an Agentics task UUID, allowing an interrupted Agent process to resume polling and artifact download without creating or billing a second generation; completed artifact downloads also retry transient failures. The video-generation skill now requires recovery of the existing task before any resubmission.

v1.0.29

Added

  • Settings → Advanced → Live

    (/live, apps/web/static/live.html): an "always-on stream" built from reference images + a prompt. Upload up to 8 reference images (PNG/JPG/WebP, frozen while running), write a prompt, pick the video provider (Fal only for now) and model (catalog and API Key shared with Generation Models → Video → Fal; default minimax/h3-max), choose 16:9 or 9:16 and the clip length, then Start (turns into Stop). The job loop generates clips one after another at 480p (768p optional in Advanced): every round submits the reference images plus the previous clip's last frame (extracted with ffmpeg) as references — or, in the optional last frame as first frame mode / for Kling endpoints, uses the last frame as the first frame — with a continuity clause appended to the prompt. The page plays clips in order on an A/B double-buffered <video> pair for gapless joins; while the next clip is not ready it replays the latest one, appends new clips after the current one ends, and after Stop loops the whole playlist. The unplayed queue is capped (default 3 clips, adjustable 1–10): generation pauses when that many clips are waiting and resumes as soon as the page reports it has moved on (the played position is sent with each status poll). Extra guards: the prompt can be changed mid-stream (applies to the next clip), max-clip count, auto-stop after N minutes with nobody watching (default 10), consecutive-failure limit with backoff, Fal request cancel on Stop, resume-from-last-frame of the previous session, disk usage + clear-history, autoplay-muted fallback with click-to-unmute. Data lives in data/live/ (refs/, sessions/<id>/segments/seg_NNNN.mp4|png, session.json written by the job subprocess, control.json written by the API). Implementation modules/live_stream.py (job runs as python3 modules/live_stream.py job <sid>; VIDEOAGENTS_LIVE_FAKE=1 synthesises test clips with ffmpeg instead of calling Fal), endpoints /api/v1/live… (status, refs add/delete, start, stop, prompt, settings, clear, file serving) and web action /actions/open-live-folder. Known limits: real-time is not achievable (a clip takes minutes to generate), the low-res last frame degrades quality/identity over many rounds, and audio/visual seams between clips can jump. 11 i18n languages updated.

  • Fal tab in Generation Models → Video

    (placed before the MiniMax tab). New video.fal channel: API Key (created at fal.ai/dashboard/keys, API scope is enough) plus a model list of Fal-hosted endpoints — MiniMax H3 Max (default) / H3, Seedance 2.5 / 2.0, Kling 3.0 Pro / Standard — with "Custom…" for any full endpoint ID. The stored model ID is the endpoint family prefix (bytedance/seedance-2.0, minimax/h3, fal-ai/kling-video/v3/pro); modules/genmedia.py video appends the task segment automatically from the inputs (text-to-video / image-to-video with first/last frame / reference-to-video with --ref/--ref-video/--audio-ref), or uses a full endpoint ID verbatim. Submission goes through the queue.fal.run async queue (Authorization: Key, poll status_url, download video.url); images/audio are inlined as data URIs, reference videos go through the configured object storage like the Ark/MiniMax channels. Per-family rules mirror the official channels: Seedance duration 4–15 s (2.0) / 4–30 s (2.5), 360p→480p and 2.5's 4k→1080p mapped, reference caps 9/3/3 (2.0) or 30/10/10 (2.5), --seed ignored (no seed input on Fal Seedance/Kling); MiniMax H3 480P/768P/2K/4K mapped from the project tiers, ≤12 reference files; Kling first/last frame only, 3–15 s. --dry-run prints the resolved endpoint and request fields without submitting. Wired into the per-agent video-channel override (fal), the group-level model catalog, FAL_KEY env fallback, WORKFLOW.md channel table and all 11 language dictionaries. A running instance must be restarted.

  • Claude Fable 5.1 in the language-model menus

    (claude-fable-5-1, listed as fable-5.1 right below fable-5 in both the top-bar model dropdown and the per-agent model dialog). Not a default: Smart Assignment still maps to opus / sonnet.

Changed

  • Agent runs now fail fast on API connection drops instead of waiting through the CLI's retry loop.

    When the claude CLI reports a connection-level API error (Connection dropped (ECONNRESET) / connection timeout, i.e. an api_retry event with no HTTP status), the runtime kills the process immediately rather than letting it back off and retry up to 10 times (which left every concurrent agent stuck in a "retrying" wait). The run ends as error with net_error: true; dispatch.py output carries a 🔌 marker and the agent chat log starts with a 🔌 line, so the orchestrator treats it as a network incident (not counted as an attempt; re-dispatch once, then escalate to the user) rather than a program failure. HTTP-status errors (429/529/5xx) are still retried by the CLI. VIDEOAGENTS_NET_RETRY_LIMIT (default 0) tolerates that many connection-level retries before failing.

Fixed

  • Runs that ended with an API error were recorded as done.

    The claude CLI emits a result event with is_error: true whose text is the error message (e.g. API Error: Connection dropped (ECONNRESET)); the runtime stored it as the run result and marked the run successful. Such results are now routed to error and the run status is error.

  • Video prompts now describe character positions from each shot's camera, backed by a per-group global blocking table

    (spatial blocking on). Per-shot space_fragment_en in blocking.json is now a camera-view sentence — frame side / depth layer / facing relative to camera / relation to other characters in frame + one landmark word — written from the shot's view_tile camera axis instead of copying the top-down map's compass wording, plus a structured frame_position {side, depth, facing_camera}. New code/camera_view_check.py (camera_view_consistent) verifies each fragment geometrically against the group blocking_map and the layout.json view axis (wrong side / wrong depth order / compass words = FAIL; legacy shots without frame_position = WARN). Shot-planning (drafted by storyboard) now finalises blocking_map.station_table[] — six fields per character or independent creature: id, zone, fixed anchor landmark, body facing, neighbors, invariant relations — camera-independent, checked by render_blocking_map.py (station_table_ok); the prompt agent copies it verbatim into a fixed Blocking table: block before Shot 1: (station_table_bound, layout_map_bound_check.py rule ⑧). Prompts must also state that the top-down blocking map is a spatial position reference only and never the picture (fixed Map usage: sentence + no top-down or bird's-eye view, no map or floor-plan imagery in Global constraints; map_reference_only, same script). Existing projects only get WARNs on the blocking-table rules until blocking / shot-planning are re-dispatched; already-written group prompts without the Map usage: sentence now FAIL layout_map_bound and need a rewrite.

v1.0.28

Added

  • Retry-confirmation wait time is now a setting

    (Agent Advanced Settings → confirm_timeout, default 60s, range 5–3600s). Retry-type confirmation popups (dispatch.py --confirm — "Retry?" / "Skip") used to fall to the default answer after a hard-coded 60 seconds; the countdown now follows the setting, so unattended pipelines can be given more (or less) human-decision time before auto-defaulting. The server treats the value as both the default and the cap for a single confirmation (an agent's explicit shorter --timeout still applies, a longer one is clamped down), the create response returns the effective timeout and dispatch.py aligns its local waiting deadline to it instead of assuming 60s, and the orchestrator's injected dispatch rules quote the configured value rather than instructing a literal --timeout 60. Sign-type popups (H-gate manual sign-off) remain non-expiring and are unaffected. Global setting, persisted in state.json, applies to confirmations raised after saving; 11 UI languages. A running instance must be restarted.

  • Long-take switch in the new-project wizard and Duration Settings

    (duration.long_take, default off). Controls whether shot groups chain on the previous group's last-frame screenshot: when on, the existing tail-frame anchor flow applies unchanged — continuity-planning marks same-scene group boundaries anchor: last_frame, the prompt agent appends the previous group's *.last_frame.png to refs with the opening declaration sentence, video-generation runs groups serially and the tailframe_declared check applies. When off (the new default), no tail-frame reference image is ever attached: continuity-planning marks every group boundary anchor: none, the prompt agent opens the first shot with a text-only reframed continuation sentence (no last-frame [Image N] reference — scene/lighting/blocking continuity rides on the scene anchors and verbatim fragments), and since no continuation chain exists, groups may generate in parallel; --return-last-frame still saves the tail frame for previews/transition rendering (last_frame_saved unchanged) and tail-frame checks simply never trigger. Rationale: at low draft resolutions the tail frame itself is low-res, and using it as a reference visibly degrades the next group's image quality and face consistency (dzg6 ep01 grp028→grp029 face drift), so both the wizard step 3 and the Duration Settings dialog show a warning against enabling the switch while the draft resolution is 360p/480p. Wired end to end: wizard + dialog checkbox with low-res warning, _validate_duration, DEFAULT_GENCONFIG, a dedicated long-take line in every agent's injected duration-settings section, conditional clauses in continuity-planning / prompt / video-generation SOULs, WORKFLOW.md §7A and workflow.yaml, and all 11 language dictionaries. Existing projects get the switch off by default (their next dispatched tasks stop attaching tail-frame refs); a running instance must be restarted.

Fixed

  • Desktop packaging now bundles the comfy/ workflow folder

    (*.json/*.md copied to backend/comfy), so packaged desktop builds ship the ComfyUI workflow definitions that previously existed only in source checkouts.

v1.0.27

Added

  • Project-level narrator voice card with a frozen voice sample, surfaced as a hardcoded "Narration" entry in the Character Preview

    (narration switch on only). New data contract assets/audio/voice/narrator.json (narrator-voice-v1: voice description designed from the project's brief/tone, design_rationale, tts_provider/tts_model snapshot, tts_voice picked from the channel's voice library for list-based channels (empty for description-customized ones), voice_selection_reason, voiceprint, status) plus the frozen sample refs/NARRATOR_voiceprint.mp3 — both produced once by 09-audio/voice-generation (new duty 7: design a description from the project's tone; synthesize directly on description-customized channels like Doubao seed-audio-1.0, or auto-pick a fitting reference voice from the channel's voice library on list-based channels; the narrator voice may not collide with any cast character voice). From then on modules/genmedia.py tts pins the narration voice to the card automatically whenever no --character/--voice is passed — same channel uses the frozen tts_voice, seed-audio uses the frozen description plus the sample as its @音频1 reference anchor, ComfyUI uses the frozen sample directly as the reference audio — so changing the TTS settings on the Generation Models page no longer changes the narration voice (a channel mismatch warns, falls back once, and asks for a card re-cast as a controlled change; projects without a card keep the old default-voice behavior, and --dry-run shows narrator-card:…). The Character Preview pins a 🎙 Narration pseudo-entry first in the list when output.narration_enabled is on; its detail pane shows the voice card and plays the frozen sample (edit button routes feedback to the producer as usual). The Generation-Models TTS blurb "the selected channel's default voice is the narration voice" is replaced by a pointer to the narrator voice card, and the seed-audio description-mode hint now names the card as the narration-description source. Updated: narrator/voice-generation SOULs, WORKFLOW.md (§8A sample block, voice-generation and narrator station rows), workflow.yaml p8-voiceprints note, the runtime TTS tool prompt line, and all 11 language dictionaries. A running instance must be restarted.

  • Narration switch in the new-project wizard and Output Settings

    (output.narration_enabled; new projects default to off — the wizard checkbox starts unchecked and project creation writes False into the baseline, mirroring the review-baseline precedent — while projects created before this feature lack the key and keep resolving to on). When turned off the project commits to a film with no narration at all: the narration-script and narrator-TTS stations (p5-narration / p8-narrator, condition: narration_enabled in workflow.yaml) are never dispatched and gates do not HOLD on their missing outputs; shot-planning writes no narration_anchors and audio_plan may not use narration_over (dialogue-free groups become ambient_only with the usual silent_rationale review, except an unclear silent group is no longer sent back for extra narration — it escalates to a script change or the user); the prompt agent drops the narration voice-over declaration sentence (the no-dialogue constraint stays), audio-mixing mixes two stems (native + BGM) instead of three, and subtitles cover dialogue only. Machine checks skip themselves: check_narration_sync.py exits PASS with skipped: narration off, and check_generation_groups.py skips narration_anchors_cover_all / narration_window_gte_est_x1.15 while still validating audio_plan (a leftover narration_anchors block or a narration_over plan is now a FAIL). Wired end to end like the fancy-text switch: wizard step 2 + Output Settings checkbox, _validate_output, DEFAULT_GENCONFIG, a dedicated narration line in every agent's injected output-settings section, a narration-off note in the producer's project-init briefing, narration_enabled() helper in code/_common.py, WORKFLOW.md (§7D switch clause, §8B note, Phase 5/8 station rows), narration/narrator SOUL dispatch preconditions, and all 11 language dictionaries. Existing projects keep narration on; a running instance must be restarted.

  • genmedia reclaim — claim an already-created Ark video task without resubmitting

    (modules/genmedia.py reclaim --task-id cgt-… --output out.mp4 [--return-last-frame tail.png], Volcengine/BytePlus only). When a submission is cut off at the transport layer (large multi-reference payloads) or the tool times out during the download phase, the task often already exists — or has already succeeded — on the Ark side, and resubmitting means paying twice. reclaim only queries the task and downloads its product (both zero-billing): a succeeded task is fetched immediately, a queued/running one is polled to completion, failed/unknown ids error out honestly (fail-fast on HTTP 404 instead of retrying). The usage ledger / .meta.json entry is backfilled from the task detail (model, resolution, duration), so token accounting stays correct even though the claiming config may differ; the tail frame is saved when the original submission requested --return-last-frame. The Ark polling/download loop is now shared between normal generation and reclaim, the generation-timeout error message points at the exact reclaim command to run, and reclaim works even when the active video channel has been switched away from Ark (it falls back to the configured Ark key). Reclaim records no generation-diagnostics event (nothing new is generated). Verified live: a bogus id fails fast with "task not found"; the succeeded task cgt-20260831230504-lb8w2 (whose download had been cut off by a 10-minute tool timeout) was reclaimed end-to-end — 10s clip + tail frame + ledger with the task's real model/resolution — with no new billing.

  • Repeated --ref/--audio-ref/--ref-video flags no longer silently overwrite each other

    (modules/genmedia.py). All four multi-value reference options (image --ref, video --ref, --ref-video, --audio-ref) were nargs="+" with the default store action, so --ref A --ref B kept only B — during the dzg6 regeneration an agent wrote one flag per file and both character sheets were dropped from the request without any error (the post-gate caught it as "0 asset:// replacements"). The options now use action="extend": space-separated (--ref A B), repeated (--ref A --ref B) and mixed forms all accumulate into the full list; omitting the flag still yields None, so downstream behavior is unchanged.

  • Character reference images can no longer silently fall back to inline base64 when the Virtual Portrait Library is enabled

    (modules/genmedia.py). Root cause of the dzg6 ep01 face-consistency incident (2026-08-31): a retry wave's driver process failed to resolve asset:// URIs, _avatar_asset_uri swallowed every error and returned None, and grp003–005 (plus the b03 batch) were submitted with base64-inline character sheets — faces and costumes drifted badly, while asset:// submissions (grp002/grp010) matched the sheets closely. Now, when the library is enabled, any portrait reference (assets/concepts/characters/**) that cannot be resolved to an Active library asset aborts the generation with an actionable error (re-register in Settings → Advanced → Virtual Portrait Library and wait for Active; or deliberately disable the library to go inline; or remove the image from the group's refs) instead of degrading silently — including when the ledger is unreadable, the asset is not yet Active, or the selected video model is not Seedance 2.x (no asset:// support). Non-portrait references (blocking maps, scene grids, props) keep the inline path.

  • Turn limit for engine runs

    (Agent Advanced Settings → max_turns, default 250). Caps the number of engine conversation turns inside a single work order via the headless --max-turns flag — claude and grok engines only (codex/pi/opencode have no such flag and are unaffected; deepagents keeps its framework's own recursion limit) — as a deliberate brake on runaway loops. Hitting the cap cuts the engine off with no resume (the receipt only shows exit code 1; artifacts already written to disk survive but verification and the receipt are missing), so for high-volume stations shrink the dispatch batch width first, then raise this value. Global setting; applies to runs started after saving. 11 UI languages.

  • Footage library page: each library row gets a 📋 copy relative path button (copies data/footage/<name>/, with a fallback for non-clipboard browsers), and the shot-splitting minimum shot length becomes a per-library setting (min_scene_len_s, saved on blur/change; scene switches within this window after the previous cut are merged — lower it for fast-cut footage, then hit ✂ Re-split).

    The built-in default drops from 2.0s to 0.5s.

  • Skills dialog: every installed skill card gets a View text action that shows the skill's full SKILL.md inside the dialog (read-only, with a back-to-list button), served by a new API endpoint.

  • Virtual Portrait Library page: a Delete All button clears the entire library after an explicit confirmation (irreversible; reports the number of deleted assets when done).

  • Ark video submit-dedup can no longer claim another group's task

    (_find_recent_ark_task). After a network error during task creation, the dedup scan matched any recent task whose listed duration happened to be missing — queued tasks often omit it — so a concurrent batch's task could be adopted and its finished video downloaded under the wrong group name (dzg6 grp019 got a byte-identical copy of grp004's 14s clip instead of its own 8s one). The scan now requires a provably matching duration (fetching the task detail when the list item lacks it; unverifiable = no match), filters by model, and refuses to claim when more than one candidate fits the window — returning "not found" (surfacing the failure for manual reclaim via the task list) rather than guessing. Duplicate billing on a false negative is acceptable; a wrong-content clip is not.

Changed

  • The "Volcengine video reference image ≥3,686,400-pixel hard limit" is retired from all process docs

    (user re-test 2026-09-01: the API no longer rejects small reference images; the 2026-07-23 rejection of 854x480 is obsolete). Every "hard limit / rejected on submission" claim is removed from WORKFLOW.md §9 and the SOULs of prompt / image-generation / character-consistency / character-concept / costume-concept / creature-concept / environment-concept, the two sheet-template JSONs, the runtime media-station prompt (services/runtime/core.py) and code/render_blocking_map.py (comment/message only; the MIN_PIXELS layout-pack output spec stays as a platform spec). Consequences spelled out where the old rule used to block judgment: refs have no minimum-pixel gate — small images such as a previous group's 480p tail frame may be attached as-is, and reused images must no longer be upscaled/regenerated just to clear a pixel bar. Newly generated concept sheets/anchors keep the 2560x1440 (16:9) / 1440x2560 (9:16) platform output spec, now justified as a quality spec rather than an API limit. Root context: dzg6 ep01 was generated without tail-frame continuation anchors partly under this belief.

  • Prompt-agent refs existence check now exempts forward-referenced predecessor tail frames

    (agents/08-video-gen/prompt/SOUL.md). The tail-frame continuation chain is designed as a forward reference — assets/clips/epNN/<prev>.last_frame.png has a deterministic path but only materializes when the previous group is generated, with video-generation serializing on the chain. The SOUL's "every refs path must exist" self-check contradicted that and led the prompt agent on dzg6 ep01 to rule "don't reference a nonexistent tail frame", silently dropping the continuation anchors of all 52 chained groups (the orchestrator then codified group-parallel generation on top of it). The check now explicitly carves out *.last_frame.png forward references: a missing tail frame means "wait for the previous group", never "omit the anchor".

  • Agent Advanced Settings: the default auto-retry / re-roll cap (max_retries) for fresh installs drops from 3 to 1 — a task that fails acceptance/QA/machine checks is redone at most once before escalating to the user.

    Installs that have saved a value in state.json keep it; the dialog help text, its fallback value and the workflow docs' "(default)" mentions are updated across 11 UI languages. A running instance must be restarted.

  • Agent skills entry reworked

    (chat header). The per-agent skills button is renamed from "🧰 SKILL" to Skill (localized, e.g. 技能 on the Chinese UI; toolbox icon dropped) and now shows for every selected agent — with the installed-skill count appended when there are any — instead of only for agents that already have skills installed. In the dialog, "Manage skill packs…" is replaced by Load skill: pick a skill zip package (a skill directory wrapping SKILL.md, or SKILL.md at the zip root, in which case the skill directory is named after the zip file) and it is extracted into the current agent's skills/ directory via the new POST /api/v1/config/skills/upload?agent=… (raw-zip body like plugin install with the same 50MB/200MB/5000-file limits, staged extraction with path-traversal guard, __MACOSX/.DS_Store noise skipped, 409 when a skill of the same name already exists). The Settings → Advanced → Skill Packs page is removed; GET/POST /api/v1/config/skills and the skills_disabled state keep working, so existing disabled entries stay in force and are still flagged "disabled" in the list, and prompts/docs no longer point to the removed page. 11 UI languages updated. A running instance must be restarted.

  • Storyboard preview: the camera-move line's start/end framing (起幅/落幅) moves from a hover tooltip to a click-to-expand detail panel under the line — selectable and copyable, collapsed by default with a ▸/▾ arrow; shots without framing notes render as before.

  • New-project wizard and Output Settings: the Fancy text row now sits above Burned-in subtitles (order swapped, both places).

Fixed

  • Storyboard preview: costume chips/links were missing for projects whose shot list has no per-shot costumes / per-group costumes_by_char (e.g. dzg5, dzg6).

    The continuity fallback in _group_costumes only understood the flat costume_states shape from the SOUL example ({shot_id, CHAR-xxxx: {outfit}}), while real continuity-planning output nests the map under a characters key — so the fallback never matched. It now accepts both shapes; verified dzg6 64/65 and dzg5 64/66 ep01 groups regain 👕 chips with sheet deep links, polan3 (shot_list source) unchanged. (Entry moved out of the 1.0.26 section: the commit landed after the v1.0.26 tag and actually ships with this release.)

v1.0.26

Added

  • Description-customized character voices via Doubao Audio Generation 1.0 (seed-audio-1.0)

    . The Volcengine TTS channel gains the seed-audio-1.0 model (first in the list, so it is the default for fresh configs; same new-console X-Api-Key, non-streaming POST /api/v3/tts/create): instead of casting a speaker from the voice library, the voice is generated directly from a text description — modules/genmedia.py tts --character CHAR-XXXX [--variant …] builds the description automatically from the character's voiceprint card (bible/characters/<id>/voice.json, acoustic fields only: gender/presented_gender, pitch, timbre, accent, with age_variants overriding per form; narrative text such as reference_style deliberately stays out after a live test showed the model reads plot text into the audio), narration uses --instructions as the voice description (built-in fallback narrator voice), and --voice must not be passed (values that look like library speaker names are ignored with a warning, guarding against stale casting entries). Voice consistency across calls: since the same description yields a different voice each run, genmedia automatically attaches the project's frozen voiceprint sample (assets/audio/voice/refs/<CHAR>[_<variant>]_voiceprint.mp3, narrator optionally NARRATOR_voiceprint.mp3) as a @音频1 reference anchor — a variant sample being (re)generated anchors to the base sample (same voice, aged per description), and per-line code/dub_group.py dubbing in this mode blanks the casting speaker override and stays on one voice per character. UI: the Generation-Models page Volcengine TTS panel hides the default-voice input and the whole voice-library block while seed-audio-1.0 is selected and shows a description-mode hint instead (11 UI languages). casting.json entries in this mode register tts_model: seed-audio-1.0 plus the logged voice_desc with tts_voice empty; voice_collision becomes "similar timbre/pitch descriptions of co-speakers". Docs: WORKFLOW.md §8A, voice-generation / narrator / voiceprint SOULs, runtime TTS prompt lines, code/README.md. Verified: live synthesis against liaozhai voiceprint cards (male/female, aged/ghost timbres, age-variant via base-sample anchor), dry-run description preview, stale speaker-name rejection, py_compile/apps/web/check.py. BytePlus has no seed-audio endpoint, so this stays Volcengine-only; a running instance must be restarted.

  • Dialogue-length check-and-trim step after storyboard finalisation

    (WORKFLOW.md §7D ①′, new pipeline node p6-dialogue-fit). Dialogue is written in Phase 5 but shot/group durations are only fixed in Phase 6, so lines routinely no longer fit; the existing Σ台词估时 ≤ 组时长×0.7 rule was declared in workflow.yaml but had no script — shot-planning "self-checked" it, and dzg5 ep01 needed an ad-hoc p5-dialogue-ep01-trim round. New host CLI code/check_dialogue_fit.py --project <slug> --ep epNN [grp…] [--ratio 0.7] [--shot-ratio 1.0] [--write-est] [--strict] re-estimates every line from its text and the speaker's bible/characters/<CHAR>/voice.json#speed_cpm midpoint (effective chars = CJK + alphanumerics; speakers without a rate keep their recorded estimate and get a WARN) and checks dialogue_fit_group (group Σ ≤ total×0.7, FAIL), dialogue_fit_shot (Σ ≤ shot length FAIL, >85 % WARN), line_le_cap (single line ≤ settings.json#duration.shot_max_s×0.7), line_est_consistent (recorded est_duration_s vs recomputed, catches stale estimates after a trim) and lines_text_match_source (every shot_list.dialogue_lines[] line exists in the screenplay dialogue layer with the same speaker; inline lines or dialogue_refs ids resolved through dialogue.md, same three layouts as the runtime), plus WARN source_lines_covered. It writes directing/epNN/dialogue_fit.json with per-group/shot/line estimates and trim_targets[] — the seconds to cut per over-capacity group and a per-line target_chars allocation (lines under 6 characters exempt) for dialogue-rewrite to work from; --write-est recomputes and writes back est_duration_s in shot_list.json, screenplay.md and dialogue.md (numbers only). Without a shot_list it degrades to a screenplay-only mode usable as the Phase 5 line_duration_fits check. Workflow: p6-dialogue-fit (agent 01-story/dialogue-rewrite, depends on p6-shots) runs the check; PASS closes the ticket with the report, FAIL means trimming the over-capacity groups on the dialogue text layer only — screenplay.md (source of truth), dialogue.md and the mirrored shot_list.dialogue_lines[].text — then --write-est and re-check; after three rounds the ticket escalates to shot-planning for shot-length/group changes. p6-blocking (performance trigger words come from the lines) and the H3A gate g6 now depend on it; the orchestrator dispatches it unconditionally after every p6-shots. Storyboard is told to size draft groups so lines fit (0.7) instead of leaving trimming to the new step. Updated: workflow.yaml, WORKFLOW.md (Phase 5/6 rows, §7D, G6/H3A), SOULs of dialogue-rewrite / shot-planning / storyboard / workflow-orchestrator, code/README.md, check_generation_groups.py docstring, the runtime duration-settings prompt line; dzg5 runs/dag.json got the node inserted (pending) with g6 depending on it. Verified: dzg5 ep01 full run 53 dialogue groups / 110 lines → 0 FAIL (20 shots ≥85 % saturated as WARN, two unnamed extras without a voice rate as WARN), report written; a sabotaged scratch copy (one line lengthened) fires all five FAIL classes with a sane trim target, --write-est resyncs the estimates, exit codes 0/1/--strict, dagcheck --strict on dzg5 passes. Existing projects that already passed H3A are unaffected; episodes finalised but not yet signed should get the node inserted as dzg5 did. A running instance must be restarted for the prompt line.

  • Per-group video model and prompt-skill override from the storyboard preview

    (WORKFLOW.md §7F ⑦). Each generation-group card gets a 🎛 模型 button (left of 📄 Prompt) that opens a dialog with two selects: the group's video model — restricted to the models of the current video channel (the channel itself cannot be changed; ComfyUI-style workflow channels have no model id and are not overridable) — and the group's prompt skill (global default / auto = resolved from the group's own model / any installed skill / off); saving both back to "follow global" removes the override. Stored user-owned in assets/group_settings/epNN/grpNNN.json with a runtime effective snapshot that is refreshed together with the project-level prompt-skill snapshot. Consumers: the prompt and video-generation system prompts list every overridden group; code/prompt_skill_check.py compares each group against its own snapshot first; the prompt_skill_read run check accepts the project skill or any overridden group's skill; modules/genmedia.py video derives the group from --output …/epNN/grpNNN.mp4 (or explicit --group), switches to the group's model within the same channel and enforces that model's reference caps, and genmedia info --group epNN/grpNNN shows the effective model. New GET/POST /api/v1/projects/{project}/storyboard/{ep}/{grp}/settings, group_settings SSE event. The storyboard header no longer has the prompt-skill select; it shows the current global video model and skill read-only (change them in the Generation Models page / Video Model Settings). Reference-image cap policy change: the prompt agent now attaches every reference the group actually needs (all mandatory anchors plus the optional ones it needs) instead of trimming to the project cap or failing; a group over its effective cap (override model's hard limit, else the project setting) is stored complete and flagged blocked_refs_cap, and the preview card banner tells the user to switch that group's model via 🎛 模型 or remove images. Manually adding references from the preview is no longer blocked at the project cap (only at the 30-image hard maximum of any model); the picker shows the group's effective cap and an over-cap notice. Prompt / video-generation SOULs, WORKFLOW.md and the injected cap paragraph updated; 11 UI languages. A running instance must be restarted (API + genmedia changes).

  • Video prompt skills are now a project-level setting with a hard trigger contract instead of a soft system-prompt hint

    (WORKFLOW.md §7F). Root cause from polan2 (2026-08-27): the runtime injected the Seedance 2.0 skill block only when the global video channel matched, and even when injected nothing verified that the prompt agent actually read skills/sd20-prompt-writing/SKILL.md — the user had to re-instruct it by hand. New contract: settings.json#prompt_skill {mode: auto|manual|off, skill_id} — auto (default) resolves from the model that really runs video generation (the video-generation agent's per-agent channel override now takes precedence over the global channel; Seedance 2.5→sd25-pe, Seedance 2.0 family→sd20-prompt-writing, MiniMax H3 on any channel→h3-prompt-writing, no match = no skill plus a warning), manual picks any installed prompt-agent skill, off skips. The resolved result is snapshotted into prompt_skill.effective {skill_id, mode, resolved_from, reason, decided_at} whenever a prompt work order is dispatched, the setting is saved or the H3A storyboard gate is signed. The prompt agent's system prompt gets a fixed prompt-skill contract section (skill in effect + SKILL.md path, must Read before writing, receipt format, check command); every group prompt assets/prompts/epNN/grpNNN.json must carry skill_applied ({id, sha256 of the current SKILL.md, checklist[≥3 {item, pass}]} or {id: null, reason}), verified by the new code/prompt_skill_check.py (prompt_skill_applied, added to workflow.yaml p7-prompt). Second, behavioural check prompt_skill_read: when a prompt-agent run ends, the runtime inspects the run's tool activity — a run that never read skills/<dir>/ is flipped to error (dispatched runs go back to the orchestrator's rework loop; user-chat runs get one automatic follow-up work order in the same session; codex has no read events and is exempt). UI: the shot-group settings dialog, the H3A sign-off popup and the storyboard preview header all expose the same "prompt skill" selector (auto → resolved skill / each installed skill / skip) with live warnings; run cards show a 🧰 skill chip, red ✗ when the read check failed; new GET/POST /api/v1/projects/{project}/prompt-skill and a prompt_skill SSE event keep tabs in sync (11 UI languages). Prompt / orchestrator SOULs and WORKFLOW.md Phase 7 · H3A rows updated. Verified by unit-driving the resolver (auto → sd20 on this machine, manual → h3 with mismatch warning, off → user_skipped), the post-run check (Read / cat / no read / codex), the check script on fixture groups (PASS / missing field / stale sha256 / id mismatch / --expect none) and node --check on all inline JS and dictionaries. Existing projects' legacy group prompts lack skill_applied and will fail the new check until regenerated; a running instance must be restarted.

  • Inter-group transitions are now designed in shot planning and rendered by a host CLI

    (WORKFLOW.md §9C). Contract: shot_list.generation_groups[].transition_in (type ∈ hard_cut default / renderable dissolve, fade_black, fade_white, dip_black, dip_white / annotation-only smash_cut, match_cut; duration_s, intent, reason, source) plus narrative_block {id, kind, role} for flashback / dream / montage / imagination blocks (entry group and the group after the block's end must both carry transition_in). The director's plan gets a fixed transition list section that storyboard (groups_draft[].transition_in) and shot-planning inherit; continuity-planning mirrors it into group_transitions[].transition and requires anchor: none on renderable-transition boundaries; the prompt agent must never write inter-group transition wording into video_prompt. New machine check transition_ok in code/check_generation_groups.py (enum / duration range 0.25–1.0 s dissolve, 0.3–1.5 s fade·dip / non-hard-cut needs intent+reason / Σ renderable ≤ 1 % of the episode budget / blocks contiguous and paired; --skip-transition to bypass). New host CLI code/render_transitions.py plan | render | check: plan derives every group boundary from timeline.json#tracks.video and writes timeline.transitions[] (every boundary gets an entry, non-hard-cut ones only from shot_list); render turns cut_v1.mp4 into cut_v2.mp4 with pad compensation — both sides clone half the transition length of freeze frames via tpad before xfade, every duration frame-quantised, fades bounded with enable windows, pts re-stamped after each stage — so the total duration stays within one frame and the external audio track / subtitles / narration anchors / intro_offset_ok are untouched (audio is stream-copied); check (transition_render_ok) verifies boundary coverage, shot_list agreement, unchanged duration, intact audio, blend/black/white frames at each transition and no drift at hard cuts, and writes edit/epNN/transitions_render.json with a black_frame_whitelist for the black-frame check. The storyboard preview page shows a transition chip (⟿ type · duration · intent) and a narrative-block chip (⏱ flashback · entry/exit) on group cards (11 UI languages). Agent SOULs (director / storyboard / shot-planning / continuity-planning / prompt / transition / edit), WORKFLOW.md Phase 6·9 rows and workflow.yaml validations (transition_ok, transition_anchor_consistent, transition_render_ok) updated; the transition agent may no longer hand-write ffmpeg. Verified on a synthetic 4-group episode (episode fade-in, dissolve, dip-to-white, fade-to-black then hard cut): 480/480 frames, duration unchanged, all 6 checks PASS.

Changed

  • Project settings: the Shot Group Settings dialog (generation-group duration cap / per-group reference caps / prompt skill) is renamed Video Model Settings — in the header menu, the dialog title, the new-project wizard step, the storyboard preview's reference-cap warning and every runtime/agent-facing mention (system prompts, SOULs, WORKFLOW.md, check scripts).

    The shot_group settings key and behaviour are unchanged; 11 UI languages updated.

  • Version management → Copy project: the pre-filled new-project name (<project>-YYYYMMDD) now checks the current project list and appends -1, -2, … when a project with that name already exists (e.g. a second copy on the same day), instead of failing with "Project already exists" on submit.

    The list is re-fetched each time the source project changes.

  • Preview navigation: the Creature Preview entry now sits directly under Character Preview (previously after Prop Preview) in the header dropdown on the main page and in the page switcher shared by all preview pages.

  • Storyboard preview: on each generation-group card the cast information — the blocking-map letter table (A = CHAR-0002, B = CRE-001), the 👕 per-character costume chips and the 🐎 creature chips — moves out of the crowded first header line into its own row directly above the 💡 lighting line.

    Chip content, tooltips and deep links are unchanged.

  • Virtual Portrait Library page: the "Fully automatic management" checkbox is renamed Auto-clean and its explanation moves from the label's hover text to a small ⓘ icon next to it ("Suitable when the total number of portrait assets is small: the system automatically clears the current portrait library before each episode is generated").

    Behaviour and the auto_manage setting are unchanged; 11 UI languages updated.

  • Virtual Portrait Library registration no longer falls back to inline base64 when the object-storage upload fails: Ark CreateAsset only accepts public HTTP(S) URLs (verified — data: URIs are rejected with URL must be a valid HTTP or HTTPS URL), so the fallback only masked the real cause.

    A new pre-flight check (GET /api/v1/avatar-assets/ready, shown as a status line on the /avatars page and run before "Fully automatic management" starts) verifies that the active File Hosting channel has bucket/AK/SK configured and that its SDK (tos/oss2/qcloud_cos/boto3) is importable in the current Python runtime; upload failures now surface the upload subprocess's stderr (e.g. the missing-SDK message) instead of a misleading URL error. Ark asset API calls also retry with exponential backoff (up to 6 attempts, 2s→30s + jitter) on throttling errors such as QuotaWriteQPMExceeded, and the automatic clear/register loops pace requests at ~0.6s intervals. All 11 language dictionaries updated.

  • Desktop bundled Python runtime now includes the Volcano TOS SDK (tos==2.9.2 plus its 5 dependencies — existing pins unchanged) and the build self-check imports it; previously the bundled runtime lacked every storage SDK, so registering portrait assets from the desktop app always failed with the misleading URL error above.

  • Default Volcano Ark image model switched from Seedream 5.0 Lite to Seedream 5.0 Pro (doubao-seedream-5-0-pro-260628, new configs only — a saved selection is kept); the Volcano Ark dropdown entry for Seedream 5.0 Lite now reads just "Seedream 5.0 Lite" without the faces/Seedance-review note.

    BytePlus defaults and labels are unchanged.

  • /auto is now a chat command handled the same way as /clear: sending /auto, /auto on, /auto off or /auto off all to any agent from any channel (web chat, Feishu, WeChat, WhatsApp) is intercepted in api_chat — no run is dispatched and no engine quota is used — and toggles the idle-watchdog auto-run of the chat's current project via the same entry point as the 🤖 button (api_watchdog_set); the reply is written to the chat and pushed back to the originating channel like any other reply.

    api_watchdog_set now broadcasts a watchdog SSE event whenever a project's switch changes, and the web run panel's 🤖 auto-run button updates from it, so a toggle made by the command (or from another tab/channel) is reflected immediately. The Feishu-only /auto implementation (local interception in feishu.py, the per-project status card with on/off buttons, and the generic card-button passthrough in feishu_bridge.py) is removed; Feishu confirm/sign cards are unchanged.

Fixed

  • Storyboard preview no longer shows empty shot cards for shots that shot-planning split from one storyboard draft. dzg5 ep01 had 6 shots (sh011/sh012, sh044/sh044b, sh058/sh059 → groups grp006, grp025, grp025b, grp033) whose storyboard_ref is written as S02/order:3/split:a; the draft lookup regex only accepted S02/order:3, so content, dialogue and camera fallbacks all resolved to nothing.

    The lookup now tolerates a /split:<part> suffix, and shot entries' canonical content_brief and dialogue_lines[{speaker, text}] fields (which shot-planning emits with the split-specific text) are read directly — and take precedence over the storyboard draft, since a split parent's draft holds the whole un-split shot's content and lines. Dialogue on all dzg5 shots now resolves speaker names instead of showing the raw S02/CHAR-0002:… draft string; thedoor / tothemoon / offer / polan2 previews are byte-identical before and after.

v1.0.25

Added

  • Footage Library: the AI analysis question is now editable per project at the top of the project view (auto-saved on blur, reset-to-default button; stored as analysis_prompt in project.json, POST /api/v1/footage/projects/{name}/settings).

    The contact-sheet path, answer language and "do not modify files" framing are prepended by the host, so the box only holds the question itself. The engine chip now also shows the language model in use (engine · model). The engine is asked for plain text instead of JSON — the final answer text (claude/grok/kimi result, codex -o last message, opencode/pi last assistant text, ``` fences stripped) is written straight into the clip's notes box, so there is no JSON parsing left to fail; analysis keeps the raw text and the question that produced it.

  • Settings → Advanced → Footage Library (/footage, apps/web/static/footage.html): a local video / YouTube·Bilibili-link → shot-split → clip library, ported from the zhiyou-fenjing workbench.

    Each footage project is one folder data/footage/<name>/ (default name fpYYYYMMDD-HHMMSS, ASCII letters/digits only; delete = remove the folder, with confirmation). Input: chunked file upload (8 MB raw-body chunks, no multipart) or a link downloaded by yt-dlp at ≤1080p; the source is then compressed to a 480p silent analysis proxy, split into shots with PySceneDetect (AdaptiveDetector → ContentDetector, falling back to the 2 fps whole-frame difference peak method for animation/dissolve cuts), every shot is exported from the original as clips/<name>_clip_NNN.mp4 + thumbnail, and the audio is transcribed with the bundled faster-whisper (modules/transcription.py, model auto-downloaded on first use; segments are assigned to clips by midpoint/overlap). Each clip card has an inline player, an editable subtitle box and an editable visual notes box (both auto-save on blur), and an AI analysis button that builds a contact sheet from 2–8 moments of the clip and sends it with the question to the current default CLI engine (claude/kimi/grok/opencode/pi read the image via their own file tool, codex via --image + --output-schema; the __smart policy resolves to the engine's low tier); the structured answer (content / message / style / tags) fills the notes box and is saved. Analyse all, re-transcribe, re-split, re-import and open folder actions; output is the clip files plus clips.json (schema videoagents.footage.clips.v1: file, thumbnail, contact sheet, start/end seconds and timecodes, subtitle, notes, raw analysis and engine meta). Pipelines run as python3 modules/footage_library.py job … subprocesses (progress polled from project.json), so the API process never loads OpenCV/torch — on macOS OpenCV and PyAV (faster-whisper) each bundle their own libavdevice and crash when loaded in one process — and ASR is one more subprocess level down. New endpoints under /api/v1/footage/projects… (list/create/get/delete, upload, download, reprocess, transcribe, cancel, analyze-all, clip update, clip analyze, file serving with Range support) and the web action /actions/open-footage-folder. New host dependencies scenedetect (pulls opencv-python), Pillow and yt-dlp in pyproject.toml / desktop runtime-requirements.in (the lock file must be regenerated with uv pip compile). VIDEOAGENTS_FOOTAGE_PROXY_HEIGHT / VISUAL_DIFF_THRESHOLD / VIDEOAGENTS_FOOTAGE_SKIP_ASR env knobs. 11 i18n languages updated.

  • Chat panel: a 🧰 SKILL button now appears in the chat header (left of the model chip) whenever the selected agent has skills installed under its skills/ directory.

    Clicking it opens a list of that agent's skills (name, activation kind / runtime condition, description, SKILL.md path; skills unticked under Settings → Advanced → Skill Packs are greyed and labelled disabled). Clicking a skill inserts its agent-relative path (e.g. skills/sd25-pe) at the cursor in the input box, so the user can tell the agent to use that skill for a specific task; a link in the dialog jumps to the Skill Packs settings. The button hides for agents without skills and refreshes after the Skill Packs settings are saved. 11 i18n languages updated.

  • New machine check code/refs_referenced_check.py (refs_all_referenced / audioref_all_referenced / prop_ref_bound / tailframe_declared): every image in a group prompt's refs must be referenced in the prompt text at least once as [Image N] or <subject>@Image N (and every audio_refs entry as [Audio N] / @Audio N); prop reference images must be bound with a <prop>@Image N sentence in the shot where the prop first appears (plain [Image N] is a WARN, a violation with --strict); when refs carry the previous group's *.last_frame.png the prompt must open with opening continues from [Image N] or same location and lighting as [Image N]; out-of-range indices are reported too. genmedia sends every ref to the model regardless, so an unmentioned image is an unexplained reference the model may ignore (wasting a slot) or misapply (the prop scale-anchor's board and bowls leaking into the scene).

    The existing imageref_bound check only validates references that were written, never images that were attached but never referenced — polan3 ep01 had all five prop anchors attached-but-unreferenced and one tail frame without an opening sentence. Prompt SOUL / WORKFLOW §7A / video-generation SOUL / code/README.md updated: prompt stage runs it over the whole batch, video-generation re-checks the group before launching.

  • Storyboard preview: a group reference that is listed in the group's prompt refs but whose file does not exist yet (typically the previous group's grpNNN.last_frame.png, which is only written by --return-last-frame after that group's video is generated) now renders as a ⏳ placeholder cell labelled [Image N] instead of being silently skipped, so the card's image count and numbering match the 📄 Prompt panel and the [Image N] references in the prompt text.

    Every reference thumbnail's tooltip now also shows its [Image N] index.

  • Storyboard preview: each shot card now shows the shot's camera movement (🎥) under the 📷 camera position, read from directing/<ep>/shots/<shot_id>/camera.json — the project's own label (movement_label / movement_zh / movement_cn, or a non-ASCII movement) when present, otherwise the movement enum (static / push_in / pan / …) mapped through the UI dictionary; rig and speed_curve follow in small grey text and hovering shows the start/end frame descriptions.

    Each generation-group card now shows the group's lighting (💡): time_of_day plus the scheme resolved from lighting_scheme_id in the scene's bible/scenes/<scene_id>/lighting.json (scheme label, or key source · direction · colour temperature · contrast; hover shows the scheme ID and its prompt fragment). Groups without a pinned scheme fall back to the unique scheme matching the time of day (or the scene's only scheme for pre-2026-07-20 projects); an unresolvable scheme ID is flagged in grey. Projects without camera designs or lighting matrices (e.g. mashup projects) show neither line.

  • Storyboard preview: the 📷 camera-position line also accepts a camera field on the shot (or the storyboard draft) — some projects' shot lists wrote the position under that name instead of camera_position, leaving the line blank.

  • Readable hand-held props (documents, letters, phone screens…) get a facing anchor to stop the "text side faces the audience" mistake: bible/props.json entries can carry a readable_face marker (face_desc / back_desc), the prop agent renders such a prop's main_01 style image as a single labeled front/back two-panel sheet (neutral grey, "正面/背面" captions, generated in one pass), and when a group has a character holding and reading such a prop the prompt agent writes one plain sentence in that Shot section stating the facing (readable side toward the in-scene reader, back toward the camera — in the prompt body, since negative never reaches the request) and identifies the sheet panel by its label.

    Deliberately kept to these three pieces after a seven-round trial on a real group: the earlier hard machine gates, QA spot check and separate back-view render were dropped as not worth their weight.

Changed

  • Desktop app account: the settings menu item that toggled between 登录 / 退出当前登录 is replaced by a single 账号 entry that opens an account dialog — when signed out it shows a note and a 登录 button; when signed in it shows the account ID (email), the balance and a 退出登录 button.

    The account ID and balance are no longer shown in the top bar. The entry stays hidden in plain browser mode (no desktop bridge). 11 i18n languages updated (the old login strings were untranslated).

  • Storyboard preview: the group-chain chip no longer reads continuity_from as "continues from the previous group's last frame" — that field is just the previous group's id and is set across scene changes too.

    The chip now follows the continuity plan's group_transitions[].anchor: last_frame → ⛓ 续接 grpNNN 尾帧, none → ✂ 硬切自 grpNNN(不传尾帧) (hover shows the planner's reason), and ⛓ 前组 grpNNN while continuity has not decided yet. Once the group's prompt exists the chip follows whether its refs actually carry a *.last_frame.png, and flags ⚠ when that disagrees with the plan.

  • Storyboard preview: generation-group cards no longer embed the costume sheet and creature sheet thumbnails in the media strip; the 👕 / 🐎 header chips (with the missing-sheet ⚠ warning and deep links to the character / creature preview pages) now carry that information for every entry.

    Reference images are chosen by the prompt stage per the spec and written into the group's refs (already shown as 📎 pipeline refs), so pasting the sheets before that stage only duplicated the same image on the card.

  • Reference-image cap overflow is now a hard failure instead of a silent trim (refs_mandatory_le_cap): the prompt stage first drops optional refs in a fixed order (extra face anchor → prop close-ups → sketch renders → previous-group last frame); if the mandatory anchors alone (one sheet per character, one per creature, blocking map + 9-view grid, prop scale anchors) still exceed the project's Shot Group Settings cap, the group FAILs — the agent must not drop a mandatory image or re-split the group, writes status: "blocked_refs_cap" + blocked_reason into assets/prompts/<ep>/<grp>.json, and the orchestrator tells the user to either remove reference images manually or switch to a video model with a higher cap (e.g.

    Seedance 2.5) and raise max_ref_images accordingly. The storyboard preview group card shows a yellow banner for such groups (refs_total/refs_cap/refs_blocked in the preview payload; 11 i18n languages), video-generation refuses to start them, and genmedia's over-cap error now spells out the same two options. Rules updated in prompt/image-generation/video-generation SOULs, WORKFLOW prompt row, and the injected Shot Group Settings system-prompt line.

  • Character / scene / prop / creature previews (apps/web/static/preview_{characters,scenes,props,creatures}.html): the list-card image badge now reads 🖼N instead of 图N (tooltip keeps the localized "图{n}" text); in the character preview the 👕 done/total costume badge no longer inherits the costume-card .cos styles (renamed to .badge.cosb) — it was rendering as a taller, padded box; all badges are pinned to a 16px inline-flex line so emoji and text chips sit at the same height.

  • Blocking maps (code/render_blocking_map.py) now draw only the lettered start/end markers and movement arrows — no legend band, no character names, no CHAR ids, no route text (small text in a reference image is unreadable to video models and leaks into frames).

    The letter↔character mapping lives solely in the prompt's Map markers: A = <label> (<CHAR id>) sentence. New label_ok check: each blocking_map.characters[].label must be a short canonical name (no pronouns, no parentheses/notes, ≤8 CJK chars or ≤3 words) and identical for the same character across all groups of an episode; layout_map_bound_check.py now requires the Map markers sentence and the subject definition <label>@Image N to use that exact label. Agent rules (storyboard / shot-planning / prompt SOUL, WORKFLOW): the renderer is a host CLI — agents must not copy, rewrite or replace it with a project-local script. Markers are drawn in reverse array order (A on top) and near-coincident markers raise a WARN suggesting an xy nudge upstream. The storyboard preview group card now lists characters in map-letter order as A = CHAR-id、B = CHAR-id (letter colored like its marker; hover shows the label and route sentence; characters missing from the blocking map show as ? = CHAR-id) so reviewers can audit positions against blocking_maps/grpNNN.png.

  • Creatures now have a place in blocking maps (creature_blocking_ok): every creature in a group's creatures_union is either an independent entry in blocking_map (id CRE-*, its own letter, diamond start / triangle end markers, own route — for led, tethered, solo or handled animals) or ridden via the rider's mounted field (no marker, same track as the rider); both at once, or neither, is a violation.

    Letter pool grows to A–L. The storyboard preview group card shows 🐎 creature chips and the creature sheet next to costume sheets, and shot cards list creature ids, so creature anchors are reviewable before the prompt stage. Blocking SOUL: creature shots write creatures[] position fragments instead of ad-hoc fields. Sheet thumbnails in the group card are captioned by id (CHAR-0002 / COS-009, CRE-001) with the name and outfit label on hover; header chips are kept only for entries that have no thumbnail (missing sheet / skipped / unknown id), so the same costume or creature is no longer listed twice.

  • Desktop (Windows): when the optional FFmpeg auto-install cannot run because WinGet is missing, the failure dialog no longer sends users to the Microsoft Store and a restart.

    It now explains that FFmpeg is only needed for AI auto-editing and nothing else in VideoAgents is affected, points at the official download page (https://ffmpeg.org/download.html) for a manual install, and offers 前往下载 (opens the page in the browser) / 暂时跳过. Other install failures keep the previous generic dialog.

Fixed

  • Storyboard preview: shot dialogue now shows the actual lines instead of bare IDs such as S04-D05 / LN-ep01-01.

    The preview API resolves dialogue_refs / dialogue_ref against story/episodes/<ep>/dialogue.md (table rows, [ID] speaker: line lines, and ### [ID] + **定稿** blocks) and returns dialogue_lines (speaker name + text); embedded dialogue{text} entries are used directly, legacy projects that wrote the line text into dialogue_ref render unchanged, and IDs missing from dialogue.md fall back to the greyed ID. Array-valued dialogue_ref is joined with / instead of being stringified as S04-D05,S04-D06.

v1.0.24

Added

  • Intro/outro assembly now has a deterministic host CLI, code/finalize_episode.py, so the timeline shift that a prepended intro requires is no longer left to the agent's own ffmpeg arithmetic (weaker models kept forgetting to shift the audio track and/or the subtitles, so final.mp4 played the narration and captions one intro-length early).

    assemble concatenates intro + programme video + final_audio + outro + teaser into final.mp4 in one pass — the audio rides along with the programme segment, so the offset is inherent (no -itsoffset, never -shortest, silent intros get a silent track, resolution/fps normalised to the programme); shift writes subtitles_final.srt/.ass shifted by the ffprobe-measured intro length (verbatim copy when the intro is disabled); check is the new machine check intro_offset_ok: final duration = Σ measured segments, every subtitle cue = programme cue + intro (±200 ms, same count), and the final's audio cross-correlated against final_audio must lag by exactly the intro length (±80 ms, start/middle/end windows agree = no drift); placement.json declared vs measured durations only warn. A ledger edit/epNN/final_layout.json records the layout (cut_offset_s, per-segment start/measured duration, check result). WORKFLOW.md §9B, the edit/subtitle/title/platform-adapter SOULs and the packaging/burn-in prompt injection all point at the CLI; platform-adapter re-runs check before packaging.

  • New execution engine grok (Grok Build CLI, install from https://grok.com/build; listed as grok in the engine menus, above deepagents).

    Runs grok -p … --output-format streaming-messages-json --always-approve --max-turns … --rules <role prompt> — the stream is the Anthropic Messages wire format, so the existing Claude event handler and token accounting are reused after normalising Grok's built-in tool names (write/search_replace/read_file/run_terminal_command → Write/Edit/Read/Bash); sessions resume via --resume <uuid> (a stale id falls back to a fresh session like the other engines); Thinking Effort maps to --reasoning-effort (no max, mapped to xhigh). The language-model menu is populated from grok models (GET /api/v1/engines/grok/models, 30 s TTL, static fallback Grok 4.6 / 4.5) and a Grok Smart Assignment policy (smart_grok: creative-core agents → grok-4.6, others → grok-4.5) is the default when the engine is selected. The binary is resolved via GROK_BIN, then PATH, then ~/.grok/bin / ~/.local/bin; the macOS desktop client adds ~/.grok/bin to the inherited PATH. --engine grok is accepted by dispatch.py and the run/global-model APIs. All 11 language dictionaries updated.

  • Settings → Advanced → Skill Packs: a new dialog that auto-scans every agent's skills/<skill>/SKILL.md (built-in agents and enabled plugin agents alike, name/description read from the frontmatter) and lists them grouped by agent with a checkbox each.

    Semantics are "ticked = allowed": a ticked skill's load instruction is injected into that agent's runtime prompt (for the existing conditional skills — Seedance 2.0/2.5 and H3 prompt writing, MiniMax Regenerate-2K upscale, RunningHub cloud workflow — the runtime condition still has to hold, the checkbox is only the master switch); an unticked skill is never injected, and for skills hard-referenced by SOUL.md (caption styling, audio transcription, publisher platform drafts) the prompt additionally declares them disabled for the run. Skills not covered by the built-in registry get a generic "read SKILL.md first, apply if its description matches the job" instruction when ticked. Everything is enabled by default (newly added skills are enabled automatically), stored globally in state.json#skills_disabled, effective for runs started after saving. New endpoints GET/POST /api/v1/config/skills. All 11 language dictionaries updated.

  • Digital human generation is now a built-in generation channel: a new Digital Human card in Settings → Generation Models with HeyGen, Kling AI and ComfyUI providers (bundled InfiniteTalk workflow comfy/digitalhuman-infinitetalk-api.json; RunningHub and Comfy Cloud are available as ComfyUI run modes), per-provider connection tests, the host-side dialogue-video pipeline (modules/dialogue_video.py, modules/digitalhuman.py) and code/check_digitalhuman.py machine checks.

    The agent workflow itself ships as the separately installable digital-human plugin (its source tree was removed from the repository, as for the other plugins).

  • Audio transcription agent (09-audio/audio-transcription) with the bundled audio-transcription skill and modules/transcription.py, backed by local faster-whisper (models are downloaded to data/models/faster-whisper/ on first use).

    It feeds the digital-human ingest, whose ready_for_digital_human=false gate blocks paid generation until the transcript is ready.

  • Desktop account sign-in (OAuth) and account management in the desktop app.

    When signed in and no personal OpenRouter key is configured, OpenRouter requests are routed through the account's wrapper endpoint with the account token; a user-owned OpenRouter key always takes precedence.

  • Desktop: optional FFmpeg environment setup on launch.

    After the main window opens, the app checks for a usable FFmpeg; if none is found it offers a one-click install (Homebrew on Apple Silicon, WinGet on Windows x64) with progress, and the offer can be dismissed.

  • Costume sheet pipeline: new 06-art/costume-concept agent renders a four-view sheet per non-default costume (sheet_<COS-id>.png, anchored to the character sheet, visual_en written verbatim into the prompt) with a costume_sheets.json ledger as the single source for p6 refs, §6A audits, p7 refs and previews; new p4-costume-sheet station, storyboard/shot_list costumes fields (costume changes only at group boundaries), machine checks costume_refs_valid / costume_ref_listed / costume_ref_attached, a 👕 Costumes section on the character preview page and costume chips/images on storyboard group cards.

    All 11 language dictionaries updated.

  • Creature/mount concept pipeline: new 06-art/creature-concept agent renders three-view sheets plus stage variants for CRE-* entries (no rider on mount sheets; human-creature scale via scale.prompt_token), new p4-creature-concept station, storyboard creatures / creatures_union / mounted fields, machine checks creature_refs_valid / creature_ref_listed / creature_ref_attached, and a new Creatures preview page wired into navigation and the storyboard image picker.

    All 11 language dictionaries updated.

  • Performance direction: the prompt agent gains a performance-direction skill (AU calibration, five-zone observation, eight-dimension formula; engine-agnostic).

    Blocking emits intent-layer performance beats, the prompt writes evidence-layer prose, AU data stays in JSON and never enters the prompt body; new performance_bound machine check (code/performance_bound_check.py).

  • Agent Advanced Settings → Thinking depth: one setting translated per engine (claude --effort, codex model_reasoning_effort with max→xhigh, pi --thinking, opencode --variant, deepagents --reasoning-effort on cloud/OpenRouter endpoints; kimi has no such parameter); default keeps each engine's own default.

    All 11 language dictionaries updated.

Changed

  • RunningHub is no longer a separate channel tab: in Settings → Generation Models (image/video/music/TTS) it is back inside the ComfyUI tab as a run mode (Local / Cloud (Comfy Cloud) / RunningHub China (.cn) / RunningHub International (.ai)), together with the per-site keys, cloud-workflow favorites and instance type; the per-agent model override dialog lists ComfyUI only.

    Stored configuration was never split, so a configuration saved with the RunningHub tab active (provider=comfyui + mode=rh_*) simply opens the ComfyUI tab with the matching RunningHub run mode selected; a per-agent runninghub override saved by the previous UI is treated as comfyui (run mode follows the global ComfyUI section). The RunningHub video skill still honours the per-agent channel override. Reverts the separate-tab change (fc786da).

  • Digital human: the RunningHub tab is merged into the ComfyUI tab as a run mode as well, and the digital-human ComfyUI tab gains Cloud (Comfy Cloud) alongside Local.

    digital_human.comfyui now has the same shape as the other ComfyUI sections (mode, url, cloud_api_key, workflow, rh_api_key_cn/ai, rh_workflow_id, rh_workflows, rh_instance_type); an old digital_human.runninghub section is migrated on load (fields moved into comfyui.rh_*, provider=runninghub becomes comfyui with mode=<site>), both by the service and by modules/digitalhuman.py reading the file directly. The connection test follows the run mode (local/Comfy Cloud check the InfiniteTalk/MultiTalk nodes and the workflow file; RunningHub checks the key and balance), and the per-segment job ledger keeps recording RunningHub runs as runninghub so interrupted tasks resume against the same remote task.

  • MiniMax H3 prompt-writing skill trigger is now engine-agnostic: H3 is an open-weight model that other channels (RunningHub, ComfyUI, …) can run, so the h3-prompt-writing skill is injected whenever the effective video model id / ComfyUI workflow file name / cached RunningHub workflow JSON contains both minimax and h3 (case-insensitive, any separator or order — e.g.

    minimax/h3-preview, MiniMax: H3, MiniMaxH3ReferenceToVideo), instead of requiring the literal minimax-h3 token or the exact H3 node class name. The Skill Packs condition text (all 11 language dictionaries) and the prompt agent's SOUL wording are updated accordingly.

  • Scene layout packs: the 3x3 multi-angle sheet (grid_9views.png) now follows explicit camera-diversity rules written into layout.json#views before generation.

    Each view carries a new angle field (eye | low | high_oblique); no tile may be a vertical top-down view (that is the job of layout_top.png, and the plan image was being copied into tiles), (camera_from, looking_at) pairs must be pairwise distinct, at most 3 tiles may look at the same landmark, at most 4 wide tiles, at least 2 close/detail tiles, at least 1 low tile, and at most 3 tiles may share the same ±30° camera bearing computed from landmark coordinates (so linear spaces such as alleys no longer yield seven near-identical corridor shots). The rules are self-checked by the environment-concept agent and documented in its SOUL, the grid template JSON (view_rules) and WORKFLOW.md; no new script check. Existing projects are unaffected until a scene is regenerated.

  • Scene top-down layout image (layout_top.png) prompt guidance: the environment-concept SOUL now prescribes true-nadir orthographic wording (no bird's-eye), describing doors and windows as gaps in the wall lines rather than by their elevation features (door leaves, bars, lattices — which a model can only satisfy by laying them flat on the floor), explicitly asserting the near (bottom) wall, and adding matching negative terms.

    Landmark coordinates for openings are calibrated to the wall-line gap, never to a door drawn on the floor.

  • The install-deepagents Makefile target was removed; the deepagents engine environment is installed from the [deepagents] extra declared in pyproject.

Fixed

  • opencode engine on machines where ~/.cache/opencode or ~/.local/state/opencode/locks became root-owned (after running opencode under sudo): the model snapshot could never refresh, so newly released models were missing from opencode models and run -m failed with an unrelated "UnknownError: Unexpected server error".

    The cache/state directories are now redirected to the runtime directory when unwritable (with a hint for the permanent fix), and a missing or >24h-old snapshot triggers an explicit opencode models --refresh.

  • OpenRouter account-wrapper base URLs now append /api/v1 in both the deepagents runner and genmedia, matching the wrapper's OpenAI-compatible layout; requests previously targeted the module root and failed.

v1.0.23

Changed

  • The desktop download-progress window (app updates and the first-launch Python runtime download) now shows a live stats line under the progress bar — downloaded / total size, percentage, and a smoothed download speed (e.g.

    123.4 MB / 274.0 MB · 45% · 3.2 MB/s). Also fixes the progress bar itself, which previously rendered as full from the start (<progress> was fed 0–100 values against its default max of 1); DOM updates are now throttled to at most once per 200 ms instead of once per network chunk.

v1.0.22

Added

  • Virtual Portrait Library "Fully automatic management" option (checkbox to the left of Save Settings on the /avatars page, off by default; stored as avatar_assets.auto_manage).

    When the library is enabled, the option is on, and the effective video channel for the video-generation agent is Volcano Engine, every 08-video-gen/video-generation run is prepared automatically before the agent starts: on the first run for a given (project, episode) the library is cleared (page-by-page ListAssets + DeleteAsset — Ark caps the asset count), then every character concept image referenced by that episode's group prompt refs (assets/concepts/characters/ paths in assets/prompts/epNN/grp*.json) is registered, and the run waits up to 10 minutes for the asynchronous review to reach Active (genmedia then submits those refs as asset:// URIs by sha256 ledger match). Subsequent runs for the same episode are idempotent (no re-clear; already-registered images are skipped); failures only warn in the run's progress line and never block generation. Episode is parsed from the work order's epNN mention; concurrent runs are serialized. All 11 language dictionaries updated.

  • Episode duration can now be set to "auto from script": both the new-project wizard (duration step) and the per-project Duration Settings dialog gain an "Auto from script" checkbox (unchecked by default; when checked the minutes input is disabled).

    Stored as duration.episode_minutes: "auto" in the project's settings.json — no fixed per-episode budget is injected into agent prompts; instead episode-planner decides the episode split and per-episode length from the script's structure, and pacing/edit follow the actual budgets written in episode_plan. All 11 language dictionaries updated.

  • The group video-prompt popup on the storyboard preview page is now editable: an Edit button switches the prompt text to an editable box, turns into Save, and writes the edited video_prompt straight back to the group's prompt file via the new POST /api/v1/storyboard/prompt endpoint (a note is appended to the file recording the manual edit; regenerating the group still rewrites the prompt from the design files).

    The previous "read-only; use 📝 Notes" hint is gone.

Changed

  • Smart Assignment is now built into the top-bar language-model dropdown and is the default: for every engine except deepagents the first dropdown item is "Smart Assignment", which picks a model per agent based on task complexity.

    The pi engine gains its own smart policy (high tier → openai-codex/gpt-5.6-sol, low tier → gpt-5.6-terra) and the claude high tier now uses the latest-Opus alias. Switching the engine or picking a model syncs all agents at once and clears per-agent overrides; the old Settings → Model Strategy submenu is removed. Existing installs are migrated once at startup to the smart policy matching their current engine. All 11 language dictionaries and both READMEs updated.

  • Resource-usage monitoring now follows the selected engine automatically: engines manually enabled in the Resource Usage settings always show, and in addition the usage check for the engine currently selected in the top bar turns on by itself (pi counts toward Codex, since pi typically runs on a ChatGPT/Codex subscription; deepagents has no usage source).

    The settings dialog keeps showing only the manual switches, so saving never freezes an automatic state into a manual one. The manual Codex switch also no longer defaults to on for fresh installs — selecting codex/pi in the top bar is what enables it now, so users who never touch Codex don't get their ~/.codex/sessions directory scanned.

  • Kimi usage can now be read without pasting an API key: when the key field in Resource Usage settings is empty, the probe reads the local kimi CLI login credentials (new and old data directories), falling back to the KIMI_CODE_API_KEY/KIMI_API_KEY/MOONSHOT_API_KEY environment variables.

    Expired tokens are skipped and never refreshed (re-run kimi login to restore the panel), matching the claude probe's no-refresh policy.

  • Group anchor packs are now reference-based for reused images: image-generation no longer copies concept-library images (character sheets, scene grid-9 sheets, prop scale refs) into assets/keyframes/epNN/<grp>/ — a reused anchor is registered in the pack's meta.json only (file: null, source: reuse:<path>, plus a new source_sha256 provenance fingerprint recording the exact bytes referenced at pack time), and group prompt refs / video requests keep pointing at the original concept-library paths (an audit of the offer project showed all 36 groups' submitted Seedance refs already bypassed the copies entirely).

    Only genuinely new files — gap-fill generations, sketch renders, approved opening frames, upscaled to-spec variants — are written into the pack. This avoids mass file duplication, lets a concept-image update propagate to every group, and keeps the avatar-asset ledger (keyed by content sha256) free of diverging-copy re-registrations. Machine checks adapt: resolution/aspect dereference the source path for reference-style anchors, prop_ref_attached means "registered in meta and target file exists", and the per-position [Image N] mapping is guarded by the now-canonical imageref_order_bound check (meta anchors[].image_index vs prompt refs). Legacy copy-style packs stay valid (checks branch on file); the storyboard preview needs no change — it already falls back to showing the prompt's concept-image refs when a pack contains no images.

  • The rendered blocking map (render_blocking_map.py) now supports CJK text on the image: the font loader prefers system CJK fonts (PingFang/STHeiti/Hiragino on macOS, Noto Sans CJK on Linux, Microsoft YaHei/SimHei on Windows), falling back to the previous Latin fonts with a WARN when legend text contains CJK glyphs the fallback cannot draw.

    The legend also moved: it is now a solid black strip appended below the layout image (the canvas grows in height) instead of a semi-transparent box overlaid on the top-left corner, so it no longer occludes any part of the map. It shows each character's name/label next to its marker letter — A = 章墨 (CHAR-0001) [moves] instead of the old ASCII-stripped id-only line — plus the character's route_en movement sentence, wrapped to fit (word-wrap for Latin text, per-character wrap for CJK). Chinese labels and route sentences from Chinese-UI projects therefore render correctly on the map. The Map markers sentence and verbatim route_en injection in the video prompt are unchanged and still machine-checked by layout_map_bound_check.py (image and prompt text double-anchor the mapping). Existing projects keep their current maps until render_blocking_map.py is re-run.

  • Default shot duration range is now 2–8 seconds (minimum lowered from 4s): new-project wizard defaults, Duration Settings placeholders, the server-side default config, and the WORKFLOW.md note all updated.

    Existing projects with saved settings are unaffected.

  • The group video-prompt popup no longer shows a byte-limit warning ("limit 5000 bytes (model)…" with the red/yellow over-limit coloring) — the limit was only a reference value and differs per model; the popup now just shows bytes used and character count.

  • Prompt language follow-up (second revision of the 2026-08-24 rule): (1) the blocking-map chain no longer requires English — route_en/offset_en, layout.json landmark name_en/desc_en and landmark words inside fragments now follow the UI language too (field names keep the _en suffix; the rendered blocking map's legend now also renders CJK — see the entry above); the route_en pure-ASCII machine check in render_blocking_map.py is dropped (replaced by non-empty + length ≤40 English words or ≤60 chars).

    (2) Image-generation prompts (concept/anchor/reference images via genmedia image) also follow the UI language: the style section is taken verbatim from style_fragment_ui (falling back to the English style_fragment_en on legacy projects), while negative prompt lists stay English (negative_prompt_en — machine checks match English substrings). Verbatim-injection discipline and the legacy-English-project rule are unchanged.

  • Upstream video-prompt fragments now follow the UI language (previously English-only): the injected style anchor string (new style.json field style_fragment_ui; the English style_fragment_en/negative_prompt_en remain for the image pipeline), blocking space_fragment_en, lighting prompt_fragment_en, costume visual_en, prop scale.prompt_token, and SFX/ambience cues are produced in the interface language for new artifacts (field names keep their historical _en suffix).

    Verbatim-injection discipline is unchanged — existing English fragments are still injected as-is and switching an existing project's language requires regenerating the fragments upstream as a set. Exceptions stay English: structural anchors (Overall visual style:/Shot N:/[Image N]…), fixed constraint sentences (Identity lock, Spatial layout declaration, Global constraints), and dialogue (the blocking-map chain was initially kept English too — lifted the same day by the second revision above).

  • Upgrading from v1.0.20 or earlier now resets the agent conversation-memory quota to the 16 KB default on first startup, regardless of the old on/off switch — installs that lacked the memory slider previously mapped a disabled switch to 0 KB.

    Installs that already set the slider (v1.0.21+) are unaffected.

v1.0.21

Added

  • /clear chat command: sending /clear to any agent clears its stored session records for the current project across all engines, so the next message starts a completely fresh session (engine-side history files stay on disk — only the resume linkage is removed).

    Useful when a bloated session makes resume requests too large for the network, or to shed stale context. If the agent still has queued/running tasks the reply warns that their sessions will be re-recorded on finish.

  • Signature gates now trigger the same cleanup automatically: when the user signs off a phase gate, the orchestrator's session records are cleared before it is woken (a run still in progress is flagged and cleaned when it ends), so the post-gate phase starts on a fresh session — project state is always file-based, and the Producer's conversation no longer grows without bound as the project advances.

  • Storyboard preview reference labels are now clickable: 📎/🖼 labels that reference character/scene/prop concept images link straight to the matching preview page, which deep-links via ?id= and auto-selects the asset (selection, detail panel and list scroll into place).

    All 11 language dictionaries updated.

Changed

  • Agent conversation memory is now a size slider instead of an on/off switch (Settings → Advanced → Agent Advanced Settings): 0–256 KB, default 16 KB.

    A stateful agent resumes its previous session until the chat history exceeds the quota, then automatically starts a fresh one — resume re-sends the full history every turn, so larger values are slower and costlier (a single codex run once accumulated 14M input tokens). 0 disables memory entirely. The old boolean agent_memory setting migrates automatically, and the deepagents engine keeps its stricter 32 KB cap.

  • Default music generation channel switched from Eleven Music to MiniMax.

  • make run-auto now performs a clean backend restart: processes still listening on the API port are identified, verified to actually be the VideoAgents API service (never an unrelated process that happens to hold the port), and terminated before the new instance starts.

  • The model-strategy label "DeepSeek Smart Assignment" is renamed "OpenCode Smart Assignment" to match the channel it runs on.

Fixed

  • Storyboard blocking-map rendering: the legend box is now composited as a separate translucent layer, so character markers that fall inside the legend area show through instead of being blanked out by the legend background.

  • genmedia.py info/dry-run output for the comfyui provider now always states the mode — RunningHub (rh_cn/rh_ai + workflow id + instance type), Comfy Cloud, or local (+ URL).

    Previously a RunningHub configuration printed the same output as an unconfigured local ComfyUI, and agents following the "don't run without a configured channel" red line wrongly refused to submit.

  • Session resume for lazily-persisted engines (pi, opencode): runs that were stopped or failed before producing any assistant output no longer store a session id — resuming such never-written "ghost" sessions always failed with "No session found" — and that message is now also recognized as an invalid-session error that transparently falls back to a fresh session.

v1.0.20

Added

  • Network Check panel (Settings → Advanced → 网络检测) for diagnosing whether a VPN/proxy actually covers the command-line environment: many VPNs only take over the browser's system proxy while curl/git/pip in the terminal still connect directly, which silently breaks CLI-based agents.

    A deliberately minimal panel: the current egress IP + geolocation (ip-api.com with an ipinfo.io fallback), a single connectivity column over a fixed site list — baidu.com as a mainland-China baseline (to tell "offline" from "blocked"), google.com / www.youtube.com / openai.com, and, as a real-usability check, the Claude Code install script https://claude.ai/install.sh (strict HEAD probe, headers only; only 2xx counts as reachable, so a Cloudflare 403 challenge or region block shows as failure — i.e. whether curl -fsSL https://claude.ai/install.sh | bash would be accepted) — and a plain-language verdict (baseline dead → the network itself is down; overseas sites dead → the terminal needs a proxy; all green → the terminal's network is fine). Every probe is issued exactly the way the terminal would run curl (a curl subprocess with no forced proxy flags, following the process's default network environment; urllib fallback when curl is absent), with per-site latency shown. Implemented as the standard-library-only services/runtime/netcheck.py behind three endpoints — POST /api/v1/network/probe, POST /api/v1/network/ip, and GET /api/v1/network/proxy (API-only local-proxy discovery: system proxy via scutil/registry + proxy env vars + a scan of common local proxy ports for Clash/V2Ray/Surge/Privoxy/Astrill/sing-box, returning a ready-made export https_proxy=… one-liner); probe targets are a server-side whitelist and proxy parameters must be loopback addresses. All 11 language dictionaries updated.

  • New Output Settings switch Precise character positions (output.spatial_blocking, default on; New Project wizard step 2 and Project Settings → Output Settings, injected into every agent's "用户输出设定" prompt block).

    On = the scene layout-pack + character-route flow below; off = the legacy single scene-concept-image flow (no layout pack, no blocking_map, prompt binds the scene concept image as an ordinary [Image N] ref, and the scene_layout_pack_ok / blocking_map_present / blocking_on_map / layout_map_bound checks are skipped — the two check scripts read the project's settings.json and report skipped).

  • Scene "layout pack" replaces the single main-view scene concept image (Phase 4 environment-concept): every scene now gets a top-down spatial layout map (assets/concepts/scenes/<id>/layout_top.png, clean, no people/labels), a 3x3 multi-angle scene sheet (grid_9views.png, generated with the layout map + new agents/06-art/environment-concept/templates/scene_grid_template.png as refs so all nine tiles show the same space) and a text source of truth layout.json (schema scene_layout.v1: map orientation, ≥3 landmarks with normalized xy and canonical name_en, semantics of tiles 1–9).

    Machine check scene_layout_pack_ok (code/render_blocking_map.py --scene <id> --check-only); the §6A concept coverage audit counts legacy main_*.png-only scenes as gaps.

  • Storyboard groups now carry per-group character staging on the layout map: each groups_draft[] (and, after shot-planning, each generation_groups[]) has scene_refs + blocking_map — for every on-screen character a start (required), optional path / end (landmark ids from layout.json, optional xy nudge) and an English route_en sentence; each shot has view_tile (nearest tile of the 3x3 sheet).

    New code/render_blocking_map.py renders the annotations (colored start ●, end ■, arrows, legend) onto the layout map → directing/epNN/blocking_maps/<grp>.png and enforces blocking_map_present (landmark refs valid, route_en English, character set = characters_union, and start-of-group continuity with the previous group's end in the same scene). Per-shot blocking must lie on the group route and take landmark words verbatim from layout.json#name_en (blocking_on_map).

  • Prompt agent: groups with a blocking_map must put the rendered layout map and the scene's grid_9views.png in refs (after character sheets), write a fixed "Spatial layout: [Image N] is the top-down layout map … do not render the map, its markers, arrows or labels. [Image M] is the 3x3 multi-angle sheet … do not copy its tiling." sentence before Shot 1: followed by Map markers: A = <name> (<CHAR id>), B = … (the rendered map's legend carries only letter = CHAR id — no CJK glyphs in the render font — so the name↔id↔letter mapping travels in prompt text), inject each character's route_en verbatim and may cite framed like tile <view_tile> of [Image M] per shot.

    New machine check layout_map_bound (code/layout_map_bound_check.py), also re-run by video-generation before submitting. Storyboard preview shows the group's rendered layout map among pipeline refs.

  • Captions ("花字") now appear and disappear exactly when their words are spoken.

    A new per-episode word-level speech timeline edit/epNN/word_track.json (schema wordtrack.v1, built by render_captions.py speech-align from the sentence-level transcript — av/epNN/beat_track.json for audio-to-video projects, edit/epNN/subtitles.srt for the main pipeline — plus the episode audio) gives every spoken character/word its start/end; backends: interp (zero-dependency: sentence timecodes + ffmpeg silence detection + per-character weighting, sentences that fell entirely into silence are merged with their neighbour), whisper (optional faster-whisper word timestamps aligned to the transcript, transcript stays the text authority) and import (external ASR word lists such as Volcengine file-recognition words[], seconds or milliseconds). speech-lookup --text prints when a caption's text is spoken; speech-snap rewrites every caption's start/end/local_start/local_end to the spoken span (moves the caption to the group where the words start, clips at the group end, min 0.25 s), and the new design-stage machine check caption_speech_aligned (±0.15 s, fails on a missing/stale word_track; speech_free: true exempts non-spoken on-screen annotations in main-pipeline projects only) is wired into workflow.yaml, WORKFLOW.md §9A, the caption SOUL and the caption-styling skill (modules/speechalign.py).

  • Bundled LTX-2.5 Distilled text-to-video ComfyUI workflow (video-ltx2.5-distilled-bf16-t2v-api.json + bilingual documentation), wired into genmedia with resolution/aspect handling for the model.

  • Storyboard-group settings gain per-model "Defaults" preset buttons (Seedance 2.0 / Seedance 2.5 / MiniMax H3) that fill duration and reference caps in one click; the recommended defaults are now 15 s / 9 reference images / 3 videos / 3 audio segments.

Changed

  • Seedance 2.0-family video models (including fast/mini) now auto-inject the bundled sd20-prompt-writing skill into the prompt agent, the same way 2.5 and H3 models already inject theirs.

  • Minor UI polish: the standing explanation paragraphs under "Output aspect" and "Dialogue voice" in the wizard/Output Settings are collapsed behind inline ⓘ icons, and a redundant settings caption was removed.

Fixed

  • The web service no longer restarts a healthy API process on a transient health-check failure (which surfaced as Windows error 10054 mid-run): a short streak of failed probes is tolerated before the restart fires.

  • DeepAgents runs that hit the engine's recursion_limit now resume automatically from the checkpoint instead of failing the whole task.

v1.0.19

Added

  • Stylized captions ("花字") post-production chain: a caption designer agent (10-editing/caption) plans per-group headline/keyword captions with entrance/exit animations and paired sound effects, the host renders them into a separate captioned edition of every clip and the final cut (the original edition is kept alongside), and the storyboard preview shows a ✨ caption button, a design dialog with per-caption ✏️ edit shortcuts that hand off to the Producer, and captioned clips side by side.

    The rendering engine is HTML+CSS templates (captpl.v1, Playwright batch rendering with seek-idempotent clocks, glyph-coverage and alpha gates before burning; the earlier ASS/libass path was retired), captions.json is at schema v3 with project-local templates, and fonts/SFX are synced from the remote asset repository (assets-sync, local data/fonts|sfx are pure caches). Enabled per project via output.caption_enabled (default off) with new p9-caption-render/p9-caption-final stations; the accompanying caption-styling skill fixes size calibration, animation and SFX discipline. All 11 language dictionaries updated.

  • RunningHub is now its own channel tab in Settings → Generation Models (image/video/music/TTS, placed left of ComfyUI) and in the per-agent model override dialog: site (.cn/.ai), the two keys, cloud-workflow favorites and instance type all live there, while the ComfyUI tab keeps only local/Comfy Cloud.

    Stored configuration is unchanged (RunningHub active = provider=comfyui + mode=rh_*), old configs are placed on the new tab automatically, and the RunningHub skill injection now honours per-agent overrides.

  • Bundled SeedVR2 video-upscale workflow (video-upscale-seedvr2-api.json + documentation) usable through genmedia.py upscale on local ComfyUI or Comfy Cloud, with resolution/aspect handling adjusted for it.

  • Dialogue voice mode in Output Settings (New Project wizard step 2 and Project Settings → Output Settings): "Native video audio" (default) keeps the existing paradigm — dialogue speech is generated natively by the video model along with the picture and no dialogue TTS is ever produced. "Post-production dubbing" adds a per-group p7-dub task after video generation: the group clip's native track is analysed for the moments each character speaks, every frozen line is synthesized with the character's cast TTS voice (casting.json / voice.json), fitted to the on-screen mouth timing (speed ±25% + tempo ±10%, start aligned to the speech onset) and mixed back over the ducked native track with the video stream untouched (native audio backed up as grpNNN.native_audio.wav).

    Implemented by the new code/dub_group.py helper (auto speech-segment detection with --detect-only/--segments manual override, dry-run, idempotent re-runs, refuses to run in native mode) and wired through workflow.yaml (p7-dub, downstream lipsync/upscale/mix take the dubbed clip), WORKFLOW.md §8C plus voice-generation / lip-sync / audio-mixing / audio-qa / orchestrator SOULs. Persisted as output.dialogue_voice (native|dubbing), injected into every agent's role prompt, validated by POST /api/v1/projects/{p}/config; all 11 language dictionaries updated.

  • Reference Files page: new "Video" category (refs/video/, mp4/mov/webm) placed after "Audio" for user-supplied reference clips (motion / camera movement / pacing / transition examples) with inline video preview; video-generation agents must inventory it first and inject clips via genmedia.py video --ref-video on models that support reference video (WORKFLOW.md §2 rule 8, role-prompt injection updated).

    Category order changed so "Thumbnails" now sits right below "Visual Style". All 11 language dictionaries updated.

Changed

  • genmedia.py upscale now supports RunningHub as the SeedVR2 backend.

    When the video ComfyUI run mode is RunningHub, the upscale submits the cloud workflow selected on the RunningHub tab instead of refusing ("暂不支持 RunningHub"). Because workspace exports normally carry no {{VIDEO}}/{{WIDTH}} placeholders, the tool binds nodes directly (idempotent when placeholders exist): the single *LoadVideo* node gets the uploaded source clip, the resize node upstream of SeedVR2Preprocess gets the target size (ImageScaleByAspectRatio V2 — scale_to_length or its Int primitive per scale_to_side, keeping the source aspect; ResizeImageMaskNode / width+height nodes get both dimensions), the primary sampler gets --seed; VHS meta-batching (VHS_BatchManager + meta_batch links) is stripped because VHS re-queues the prompt server-side under new prompt ids that RunningHub does not track (the task reported SUCCESS with an empty output list), and preview-only VHS_VideoCombine sinks are pruned so the cloud encodes once and the download picks the real output. RunningHub product downloads now retry up to 4 times on a mid-stream network break (IncompleteRead/timeout) instead of failing a task that already succeeded and was billed. Verified on a real 864×480 / 12.25 s clip → 1280×712 (source aspect kept by the cloud workflow's longest-side scaling), 24 fps, 294 frames and audio preserved. --dry-run validates the binding against the cached cloud workflow without uploading or creating a task.

  • Settings → Advanced: the "Concurrency & Timeouts" and "Agent Memory" dialogs are merged into a single "Agent Advanced Settings" dialog (per-agent concurrency, run / no-output timeouts, conversation memory switch) that gains a new Retry count setting (default 3, range 0–10): the cap on how many times agents automatically rerun / reroll — acceptance-scoring, QA or machine-check failures sent back by the Producer, and media generation rerolls after failed machine checks.

    Once a task hits the cap and still fails it is escalated to the user; 0 disables auto-retry entirely. Persisted in state.json as max_retries, exposed via the new combined GET/POST /api/v1/config/agent-advanced (the previous /config/concurrency and /config/agent-memory endpoints keep working), and injected into every agent's runtime prompt as a "用户重跑次数设定" section that overrides the "at most 3 times / ≤3 rerolls / max_retries: 3" hard-coded in the orchestrator dispatch rules, SOULs and WORKFLOW.md (those documents now note the setting). All 11 language dictionaries updated.

  • Prompt agent (08-video-gen/prompt) no longer enforces the legacy "video_prompt < 1000 words" acceptance rule (SOUL.md DoD + WORKFLOW.md §7 table).

    None of the bundled official prompt skills set a numeric cap (sd25-pe explicitly says no fixed word limit; sd20 only asks for concision; H3's 350–500 English words is a target range for the detailed_description section, not a ceiling), the split()-based word count is meaningless for CJK prose since prompt language follows the UI language, and shipped projects routinely exceeded 1000 words while passing acceptance. Replaced by a de-redundancy check (no restated sentences, no repeated style/constraint clauses, no re-described appearance inside Shot sections) plus the H3-specific 350–500-word guidance; video_prompt_word_count stays as an observational field only.

  • Delivery discipline for structured deliverables: every agent's runtime prompt, the orchestrator dispatch rules and WORKFLOW.md §2/§6 now state that JSON/MD/YAML design outputs must be written directly as final files — no "write a Python generator script, then run it" detour, no splitting one batch work order into several 2–3-item rounds, and no per-file duplication of shared boilerplate (input lists, coordinate/frame conventions).

    Scripts are only for real computation/media/check work and belong in the project code/, never in runs/<task_id>/. Batch (for_each-merged) work orders must end with an explicit "write the JSON files directly, no generator scripts, no batching" line. Motivated by an archigram p6-composition run that produced 13 valid composition.json files via seven generator scripts in six rounds, taking 4× as long as the sibling camera/blocking batches.

  • Manually stopped runs are now recorded as such instead of looking like ordinary failures.

    Stopping a queued/running task from the Runs panel (⏹) sets a stopped: "user" field on the run (stopped: "shutdown" when the service itself is closing; both exposed via /runs and /runs/{id}), forces the final error text to "已被用户手动停止" even if the engine reported an abort first, and writes a chat entry that opens with an explicit "⏹ 已被用户手动停止 … 非程序错误,无需追查失败原因" banner (partial output preserved below it) instead of a bare truncated reply flagged as an error. dispatch.py --status/--runs/--wait/--wait-all now print the same marker (and --status prints the error line for failed runs, which it previously omitted), and the chat header shows "⏹ 已手动停止" rather than "⚠ 运行出错". WORKFLOW.md §5 and the orchestrator SOUL gain a rule: a manually stopped child task is not a failure to investigate or retry automatically — the user decides whether to redispatch. Failed runs that had partial output also now append the error reason to their chat entry.

  • Run timeouts are now configurable and smarter.

    The former fixed 3600s per-run cap is replaced by two settings in Settings → Advanced → "Concurrency & Timeouts" (the renamed "Concurrency" dialog): a wall-clock run timeout (default 2 hours; the Producer automatically gets 4×) and a no-output timeout that terminates a run whose engine event stream has been silent for the configured duration (default 30 minutes, 0 disables; the Producer is exempt since it mostly waits silently on sub-tasks). Timed-out runs now kill the whole child process tree (previously only the engine binary, leaving yt-dlp/ffmpeg/dispatch descendants orphaned). Both values persist in state.json, are exposed via GET/POST /api/v1/config/concurrency (run_timeout / idle_timeout, seconds), and apply to runs started after saving. dispatch.py --wait/--wait-all default wait raised from 3600s to 7200s to match. All 11 language dictionaries updated.

  • RunningHub submissions now prune inert "island" nodes from the workflow before task creation: note/template text nodes left in the cloud workflow by its author (e.g.

    JjkText blocks holding multi-kilobyte prompt-writing templates) were submitted verbatim with every billed request. A node is removed only when it has no downstream consumers, no incoming node links (pure literals) and is not a Save/Preview output node; pruned node ids are echoed in the submission log. Verified against a captured production payload: the three H3 template nodes are dropped (−16.6K characters) while the execution graph is untouched.

  • Prop reference images must not contain people: the scale-anchor image now uses inanimate reference objects, character/costume sheets are no longer passed as --ref for props (people trigger channel moderation rejects and dilute the prop subject), and a prop_image_no_person machine check is added to the prop agent's DoD.

  • Small UI polish: elapsed times over 60 s are shown as 1m0s/1h1m0s, the chat box gains a subtle glow, and the MiniMax settings hints link straight to the API-key console pages.

Fixed

  • Comfy Cloud generation and upscale no longer fail after billing: result polling used /history/{prompt_id}, which is 404 on Comfy Cloud, so every Cloud job was submitted and charged but never collected.

    Polling now goes through /jobs/{prompt_id} with the response normalized to the local history shape; the local ComfyUI path is unchanged.

v1.0.18

Added

  • Plugin repository link in the Agent Plugins dialog: a "🔗 插件仓库" entry next to the "Install plugin package (zip)" button opens the official plugin collection at github.com/AgenticsWorld/VideoAgents-Plugins in a new tab.

    All 11 language dictionaries updated.

  • OpenCode execution engine (opencode in the top-bar engine selector, default model DeepSeek V4 Pro): runs agents through opencode run --format json --auto with persistent --session continuation, streams text/tool/token events into the run panel, and counts token usage per model call.

    The language-model menus are populated dynamically from opencode models (with a static DeepSeek V4 Pro/Flash fallback covering both the Zen opencode/ and the Go-subscription opencode-go/ channels), and OPENCODE_BIN overrides the executable (~/.opencode/bin is probed automatically, including by the desktop client).

  • DeepSeek Smart Assignment model strategy (Settings → Model Strategy): creative-core agents run on opencode-go/deepseek-v4-pro, all other agents on opencode-go/deepseek-v4-flash (the OpenCode Go subscription channel), using the same task-complexity tiers as the existing Claude/Codex/Kimi smart strategies.

  • OpenCode Go usage in the Resource Usage panel: a new toggle in Settings → Resource Usage queries the OpenCode Go subscription quota (5-hour/weekly windows) via the official console endpoint, using either a configured API Key or, when left blank, the local opencode login credential.

    All 11 language dictionaries updated.

  • RunningHub account balance in the Resource Usage panel: a new toggle with separate API keys for the .ai and .cn sites (the two accounts are not interchangeable; keys are stored locally, independent of the generation-models page), showing remaining RH coins and money per configured site with the usual 10-minute cache and force-refresh.

  • RunningHub placeholder-free direct binding extended from video to the image, music and TTS ComfyUI branches: cloud workflow exports without {{TOKEN}} placeholders previously let the template author's demo prompts, lyrics, images and voices silently blend into billed production requests.

    The prompt/negative/seed/reference image (image), style prompt/lyrics/duration/seed (music) and line text/reference voice/seed (TTS) are now bound by following the node wiring, with every unresolvable case failing before submission so nothing is billed. Task submission also prints the RunningHub taskId immediately for billing reconciliation, and status polling tolerates up to 3 transient network drops before giving up with a check-the-web-console warning.

  • RunningHub instance type selector: each media type's ComfyUI section gains a Standard/Plus/Ultra run-mode dropdown (Standard keeps the previous behavior; other tiers are passed through to task creation and echoed in the submission log).

  • Style library grew by 58 animation styles (52 2D / 6 3D, 94 → 152 total), and every style card gains a zoom button for full-size preview.

Changed

  • Saving Settings → Generation Models now auto-syncs the local caches of every currently selected RunningHub workflow (image main/reference, video, music, TTS) to the latest cloud version.

    Previously the cache never expired, so edits made in the RunningHub web editor (e.g. swapping the diffusion model) silently did not take effect until the cache file was deleted by hand. Sync failures do not block saving — submissions keep using the old cache and the save status shows a prominent warning; when a cloud workflow's content actually changed, the producer agent is notified to update dependent agents. All 11 language dictionaries updated.

  • MiniMax H3 Ref2VA reference-image sizing is now selectable per request: genmedia.py video gains --ref-image-size match|max (default stays match, which downscales references to the generation's pixel area; max passes short edge ≤2048 through untouched for better identity fidelity at higher time/cost).

    The chosen value is force-applied to RunningHub cloud workflows at submission time, and non-H3 channels/workflows reject the flag instead of silently ignoring it.

  • Character reference sheet template redesigned from five panels to four: the two stacked head-and-shoulders close-ups on the right are merged into a single full-height large close-up (three full-body views front/side/back on the left are unchanged).

    Template PNG, panel semantics JSON, the character-concept agent's layout prompt/self-check contract and WORKFLOW.md updated accordingly.

  • Settings menu reorganized: Plugins is promoted from the Advanced submenu to a top-level item right below Resource Usage, and the Advanced submenu now sits above Interface Language.

Fixed

  • Desktop auto-update failures: __pycache__/*.pyc are excluded from the packaged plugins, and the Python bytecode cache is redirected outside the app bundle (PYTHONPYCACHEPREFIX) — on macOS, bytecode written inside the signed .app invalidated the signature and Gatekeeper refused the next launch.

    The updater also resolves the correct .app root on macOS, validates the application path before downloading, and rejects App-Translocation launches up front with a move-to-Applications hint.

v1.0.15

Added

  • Virtual Portrait Library (Settings → Advanced): integration with Volcano Ark's private virtual-portrait media-asset library.

    A dedicated settings page walks through activating the Advanced Creation Package and configuring Volcano Engine IAM Access Key / Secret Key (falling back to the File Hosting TOS credentials when left blank), with an enable/disable toggle; once enabled it lists every asset in the account's library with total count, name search, 50-per-page pagination, thumbnails, review status and irreversible-delete actions. On the Character Preview page every character image gains a one-click Register button with live status (registering is asynchronous: Processing → Active/Failed, tracked in a content-hash ledger so identical files are registered once). During video generation, reference images whose content hash matches an Active library asset are automatically submitted as asset://<asset-ID> URIs instead of inline base64 (Seedance 2.x only), which avoids face-reference moderation blocks; the Storyboard Preview marks such references with a 🛡 badge. Also adds a genmedia upload subcommand that uploads a local file through the configured object-storage channel and prints a presigned URL. All 11 language dictionaries updated.

  • runninghub-cloud-workflow skill for the video-generation agent: documents the parameterization mechanics, asset upload and wiring conventions, the honest-reporting rules for parameters that cannot reach the cloud on placeholder-free templates (--seed, and --resolution/--aspect), promptTips/failedReason troubleshooting, and billing discipline.

    Injected only when the video channel is ComfyUI on a RunningHub site.

Changed

  • Minor UI copy: the TOS file-hosting hint now links to the Volcano IAM Key Management console for obtaining the AccessKey, and the ComfyUI workflow selector labels were renamed from "workflow JSON" to "workflow API".

Fixed

  • RunningHub placeholder-free cloud workflows regressed to silently running with the template author's demo prompt and default duration (the working implementation had lived only in uncommitted worktree files and was lost to an overwrite).

    Prompt and duration injection are reimplemented by following the H3 node wiring — the prompt rewrites the upstream text primitive, the duration rewrites the frame-count primitive using the workflow's duration-to-frame-count conversion semantics — and any unrecognizable wiring now fails before submission, so no request is sent and nothing is billed. Reference wiring also detaches all leftover template ref_images.*/ref_audios.* connections and deletes orphaned loader nodes first, so the author's demo assets can no longer silently blend into production requests when fewer references are submitted than the template has slots.

v1.0.14

Added

  • Diagnostics data (Settings → Advanced → Diagnostics): agent issues and lessons collected after distribution can be manually exported as a diagnostics package for the developers — local collection only, manual export only, never uploaded automatically.

    A single privacy boundary module enforces a field whitelist at the entry point (prompts, project content, file paths and API keys can never get in), error messages are templated into 200-char patterns with 16-char signatures so identical failures cluster across machines, and project names are stored as 12-char hashes. Two collection points cover run-level events (engine/agent/status/duration/error signature) and generation-level events (kind/provider/model/outcome/duration). Optional per-run lesson cards (runs/*/lesson.md, written only under strict conditions) can be previewed and individually selected — none are exported by default. The export zip carries a versioned manifest, event files, an aggregated summary, and the selected lesson cards.

  • Seedance 2.x reference-to-video on Comfy Cloud: two bundled workflow templates (Seedance 2.5 and 2.0, built on the paid ByteDance2ReferenceNode API node, Comfy Cloud only) wired into a dedicated genmedia branch — reference images uploaded per item and attached dynamically, all validation performed before anything is billed (local-ComfyUI rejection, unsupported input modes, reference-count caps of 9/30, integer duration ranges, resolution clamping, ratio fallback), audio generation on by default, and the continuity last-frame extracted locally from the finished clip.

  • sd20-prompt-writing skill: the official Doubao Seedance 2.0 prompt-writing guide ships with the repo (quick-reference entry plus a fully reorganized reference including formulas, worked examples and a 12-item troubleshooting section), structured to match the existing h3-prompt-writing skill.

Changed

  • MiniMax and RunningHub API keys are now stored separately per interface region/site (the global and China platforms use non-interchangeable accounts and keys; a shared single key field could silently overwrite one side when switching region or run mode).

    Existing single-key configs are migrated automatically into both slots with unchanged behavior, and the settings page shows the key field matching the selected region/run mode.

  • Default Volcano Ark / BytePlus image model switched to Seedream 5.0 Lite, whose output containing faces passes Seedance review by default; the dropdown labels note this recommendation.

  • The audio-to-video plugin's source tree was removed from the repository — it ships solely as a standalone installable plugin package (as introduced in v1.0.12).

Fixed

  • MiniMax-H3 reference audio no longer rejected with error 2013: audio MIME types are now mapped explicitly (mp3/wav/m4a/aac/flac/ogg) instead of relying on mimetypes guesses (.mp3 → audio/mpeg fell outside H3's whitelist), and unsupported extensions fail fast with a transcode hint rather than sending a request that is guaranteed to be refused.

  • Comfy Cloud component preflight and connection test no longer break on the missing per-node object_info endpoint (Cloud returns 404 and only supports the full listing): the preflight now performs a single full fetch, and gzip transfer encoding shrinks the ~9.4 MB plaintext response to ~0.7 MB, which also stops local proxies from truncating the stream mid-body.

  • Clip-cutter concurrent-overwrite hazard fixed: the host footage module gained an atomic JSON write primitive and all plugin-side writes are forced through it.

  • Footage downloads no longer stall indefinitely on unstable proxy exits: yt-dlp now runs with a 30-second socket timeout and 3 retries, failing fast instead of hanging until the outer one-hour timeout kills it.

v1.0.13

Added

  • MiniMax generation provider across all four media types, each as a new tab placed before ComfyUI on the 🎨 generation-models page: image (Image-01, single subject-reference image), video (MiniMax-H3 only — the project resolution tiers are mapped onto H3's two-tier 768P/2K ladder automatically, duration 4–15 integer seconds, first/last frame plus multi-reference image/audio/video modes, native audio-video co-generation, continuity anchor extracted locally from the finished clip), music (Music 3.0/2.6 with the same force-instrumental toggle as Eleven Music; auto-written lyrics when disabled), and TTS (Speech 2.8 family with a voice-library browser backed by get_voice).

    A per-tab API-region selector covers the non-interchangeable global (api.minimax.io) and China (api.minimaxi.com) platforms, MINIMAX_API_KEY works as the environment fallback, and MiniMax is selectable in per-agent image/video provider overrides.

  • ComfyUI channel cloud execution: besides the local server, every ComfyUI tab (image/video/music/TTS) can now run on Comfy Cloud (cloud.comfy.org, X-API-Key, same API surface passed through) or on RunningHub's hosted platform — China (.cn) and International (.ai) sites with non-interchangeable keys.

    RunningHub workflows are added by pasting a workflow ID or page link (verified, cached locally, reported with node/placeholder/H3 details), driven through the existing {{TOKEN}} placeholder pipeline with automatic input upload, status polling and result download; the connection test reports account balance and concurrent-task count.

  • Seedance 2.5 video models in both the Volcano Ark and BytePlus/Dreamina dropdowns, with a project-level storyboard-group settings block and generation-aware parameter validation in genmedia; the official sd25-pe prompt-writing skill ships with the repo and is injected into the prompt agent only when a 2.5 model is selected.

  • MiniMax H3 prompt-writing skill: the official h3-prompt-writing skill (base + reference-to-video guides) is injected into the prompt agent only when an H3 channel is active.

  • Video upscaling via MiniMax Regenerate-2K: a new upscale-agent skill plus a genmedia upscale subcommand — H3 768P-output preflight (fps/audio/dimensions/frame-count), fixed 2K output rescaled to the delivery profile in post, --source-task-id shortcut that skips re-uploading the source, and >45MB sources routed through presigned object storage.

  • Pi execution engine: run Agents through the local pi CLI with JSON event streaming, session resume, per-run model selection, and dynamic language-model menus populated from the models available to the current Pi login.

  • DeepAgents cloud model channel: the language-model page gained a third channel tab (default DeepSeek endpoint, v4-flash/pro) wired to the top-bar model dropdown and backend model resolution.

  • Feishu asset push: finished concept art (characters/scenes/props) and clips are pushed automatically to the bound Feishu chat as captioned images and playable videos; oversized files degrade to a text notice, the pushed ledger survives restarts, and regenerated files are pushed again.

  • Feishu /auto command: toggle per-project auto-run from chat — a status card with one row and an on/off button per project, plus /auto on|off [project] and /auto off all.

    Handled locally without engine quota, so it keeps working when engines are exhausted or stalled.

  • Reference panel revamped from "reference images" to "reference files": a new text category, a copy-project-relative-path button on every file card, and the music category re-scoped as "audio" (background-track candidates the music agent picks up first).

    Accepted image formats extended with bmp/tiff to match Seedance's official list.

  • Resource-usage panel: Codex and KimiCode usage checking gained on/off switches, and usage bars now show when the quota window resets.

  • Workflow preview: the "spent" row now includes project-level video/image/language token consumption, and Volcano Ark image generation records usage to the ledger.

  • Voice library moved to a remote catalog: a built-in 58-voice catalog (gender/age/pitch/tags metadata) backs the timbre selector, and selected voices are downloaded on first use into the local cache with atomic writes — no bundled audio needed.

  • Bundled local ComfyUI media workflows, renamed and categorized with bilingual documentation, a console dropdown selector and a documentation popup.

  • Host-side footage module for the mashup plugin: footage ingestion and integrity checking.

  • Character sheets rejected by real-face review can now be salvaged conditionally: the face is rendered in colored pencil while body and background keep their original texture.

  • Watchdog wake-up messages now carry the project's standing instructions.

  • Desktop packaging and updater now distinguish distribution sources across build info, the runtime index and runtime downloads.

Changed

  • Standardized local development and CI/desktop packaging on Node.js 24.19.0 through .nvmrc; GitHub Actions now reads the same version file instead of independently pinning Node 22.

  • Generation-models page copy reworked: sections renamed to "×× models", and the language-model / DeepAgents channel tabs show a ✅ marker on the active channel.

  • Seedance 2.0 dropdown labels now note the intended use — Fast "suited to animation", Mini "suited to advertising" — on both the Volcano and Dreamina channels.

  • Workflow preview no longer shows money estimates anywhere (only claude-engine sessions report a USD cost and session logs are TTL-cleaned, so the figures were inevitably incomplete and misleading); duration statistics remain.

  • Confirmation/signature popups moved from the top-right to the bottom-right corner; multiple popups still stack upward.

Fixed

  • Pi streaming replies no longer render one token per vertical line while a run is active; consecutive text deltas are coalesced into one live text block while preserving tool-event ordering.

  • Long engine/model identifiers in the run panel now truncate with a tooltip instead of pushing durations and per-run stop buttons under the scrollbar.

  • make run now exits cleanly on the first Ctrl+C even while the browser's SSE stream is open; shutdown also reaps the managed API, draw sidecar, active Agent process groups, messaging relays, and keep-awake helper, with a bounded timeout fallback.

  • Page loads no longer stall for seconds: SSE disconnect cleanup was moved off the event loop, codex usage caching got a matching TTL, and startup requests are issued in parallel.

  • Signature confirmations no longer re-pop after the waiting dispatcher times out: re-asking the same question reuses the unanswered confirm, so clicking the original popup takes effect immediately, and an answer given within 10 minutes is back-filled to the new waiter.

  • Preview sidebar entries (characters/scenes/props/worldview) no longer squeeze long names down to a single character: name and badge share the first line, the id moves to its own smaller second line.

  • Workflow preview durations: the meta.json end time is accepted under both ended_at and finished_at (both spellings exist in stored runs), restoring durations for tasks that only wrote finished_at.

  • Windows desktop updates: fixed updates failing to install or not being applied after download.

  • CI: npm ci no longer fails on a lock-file desync — sharp's 26 platform packages and @emnapi/runtime (pulled in by the baileys dependency) were missing from package-lock.json.

v1.0.12

Added

  • audio-to-video plugin: builds a picture track for narration audio (podcasts, audiobooks, storytelling) with the timeline locked to the audio — ingest/transcript alignment, era research, timeline planning and visual scripting stations plus an A/V-sync QA gate.

    Ships as a standalone installable plugin package (moved out of the built-in set) with a host-toolchain preflight, and suspends the main DAG's audiovisual branch on start via the main_dag_on_start.skip mechanism from v1.0.10.

  • scripts/package_plugin.py (make plugin-package): declarative plugin packaging driven by the plugin's own manifest.

Changed

  • WebUI: the active image/video generation channel is now chosen on the 🎨 generation-models page — the active tab is the active channel, applied on save — matching how music/TTS/file hosting already work; the two top-bar dropdowns were removed.

    Provider hint texts were trimmed, with recharge/console links added for OpenRouter and Volcano Ark. All 11 language dictionaries updated.

  • Resource-usage panel: when Claude usage checking is disabled, the claudecode row is hidden entirely instead of showing an empty "not enabled" bar; enabling it in settings refreshes the panel immediately.

  • genmedia: Volcano Ark image generation now requests URL responses and downloads the image, avoiding oversized base64 response bodies.

  • Agent workflow spec: the character-concept station now delivers a single character sheet per character (2560x1440 five-panel layout — full-body front/side/back plus two close-ups) as the one anchor file consumed downstream, generated one image at a time with defect-driven targeted re-rolls instead of multi-candidate batches; user review feedback is the only driver for revisions.

Fixed

  • Console input drafts are no longer lost when switching pages or refreshing, and a failed send returns the typed content to the box.

  • Desktop: upgrading the Python environment no longer hangs.

  • CI: the WebUI legacy-API-path check no longer misfires on external https:// links inside hint texts (an ElevenLabs link had broken the main-branch build); the link itself now points to the correct ElevenLabs API-keys page.

v1.0.11

Added

  • deepagents engine session memory: the runner gained --checkpoint-db / --thread-id, persisting conversation history (including tool results) across processes via LangGraph SqliteSaver — aligning deepagents with the claude/codex session-resume semantics.

    Stateful agents now keep context across dispatches; a fuse resets sessions whose history exceeds 32KB (OpenAI-compatible endpoints resend the full history each turn with no vendor prompt cache). make install-deepagents now also installs langgraph-checkpoint-sqlite.

  • Desktop forced updates: the runtime index can declare a minimumVersion; app builds older than it are required to update before continuing (the requirement is cached so it also applies offline).

  • Desktop app upgrades now carry the Python runtime along: the build embeds the requirements-lock hash, and when the installed runtime's hash no longer matches, the app checks the runtime index once and auto-downloads the newer runtime package — old users no longer keep a stale cached runtime that's missing newly added dependencies.

    Any check/download failure falls back to the existing runtime without blocking startup.

  • Storyboard preview: group cards now also display reference images written into the group prompt by the pipeline or agents (concept art, chained tail frames…), marked 📎 and counted in the navigation badge — previously only keyframe anchor packs and user-added refs were shown.

    User-added refs keep their 🖼 marker and remain the only removable kind; refs pointing into the group's own anchor pack and byte-identical copies of anchor-pack images are skipped to avoid duplicates.

Changed

  • New-project wizard defaults trimmed to a minimal production config: no publish platform preselected (at least one must still be chosen), output language follows the current UI language, quality reviewers default to 0 (skip review), and intro/outro/next-episode-preview are unchecked.

  • Aspect-ratio machine checks relaxed to directional: 16:9 / 9:16 describe the general format, and engine-native near-ratio output (e.g. 864x496 draft renders) passes instead of failing a strict width×9 = height×16 equality — no more per-group user waivers for engine rounding.

    The ≥3,686,400-pixel floor for video reference images is unchanged.

  • Run panel caps the agent list at the 100 most recent entries.

  • Agent workflow spec: production-required fields may no longer ship as UNKNOWN placeholders.

    Design-layer stations (worldbuilding, characters, scenes, art) that find no basis in the source text or upstream Bible must invent a self-consistent value, mark it inferred: true with a design rationale, and continue; a new no_unknown_placeholder machine check bounces UNKNOWN/TBD placeholders. Fact-extraction stations still mark UNKNOWN (it stays the downstream fill-me signal), and filling per this rule no longer costs faithfulness points in scoring.

  • Agent workflow spec: context-package scheduling tightened again — inline work orders are exempted from the context.md run-record requirement in the machine-readable yaml itself, the full-package whitelist narrowed (creative work alone no longer justifies a full package when input paths can be listed explicitly), and duplicate packaging is banned (unexpired packages are reused; fan-out batches share a base package plus per-instance increments).

Fixed

  • Windows: preview pages no longer scramble assets in subfolders — all relative paths in asset/audio/ref URLs are POSIX-normalized (backslash paths broke subfolder grouping in character/scene/prop previews and image loading; hand-drawn/uploaded refs persisted into group prompts are now written with forward slashes at the source).

  • Windows: Feishu SDK modules are imported via shortened paths, fixing failures from the 260-character path limit.

  • Prop preview: asset documents whose array key is entries instead of props now render their cards.

  • Version page: the suggested name for a copied project is now date-only (no time-of-day suffix).

Added

  • Plugin flows can suspend the main DAG's audiovisual branch when they start (main_dag_on_start.skip): the derivative-novel plugin declares voiceprint/lighting design (p3-voice, p3-lighting), every p4–p11 stage node and gates g4–g10, and starting a novel marks them skipped (with a recorded reason) so scheduling doesn't wander into audiovisual nodes irrelevant to text-only work.

    The skip only applies when none of the listed nodes have started; a project with any node already dispatched or running is treated as active video production and left untouched. Skipped is suspension, not abandonment — nodes restore to pending whenever video production resumes, and gates treat skipped dependencies as satisfied.

Changed

  • Feishu setup page: the unbound hint is now a single sentence linking to the Feishu Open Platform to create a "Feishu agent application" — the new app template ships with bot capability, long-connection event subscription, callback subscription and message permissions, so the old four-step manual walkthrough was removed.

    All 11 language dictionaries updated.

Fixed

  • The Feishu SDK (lark-oapi) is now a core dependency and is bundled into the desktop runtime package — binding Feishu no longer fails with "Feishu SDK not installed" on standard installs and desktop builds.

  • Dispatched tasks with no explicit model and no agent-level override now inherit the top bar's global model choice (when its engine matches the task's engine).

    Previously such tasks silently ran on the engine CLI's default model — the top-bar selection never took effect — and the run panel badge showed only the engine name without a model.

v1.0.9

Added

  • Phone messaging channels: the Advanced menu's "ClawBot WeChat" page became "Phone Messages" with three channels — ClawBot WeChat (flow unchanged), Feishu/Lark and WhatsApp.

    Feishu binds with app credentials and receives messages over the official SDK's WebSocket long connection (no public callback URL needed); WhatsApp connects your own account as a linked device (Baileys multi-device), using self-chat as the command channel — messages from other people are never read or forwarded. Inbound texts go to the chief producer; its replies and system messages are pushed back to every bound channel, and user commands arriving from one channel are mirrored to the others with a source label so all endpoints see the same conversation.

  • Feishu interactive sign-off cards: confirmations and human-gate sign-offs raised by agents (dispatch.py --confirm / --sign) are now also pushed to the bound Feishu account as interactive cards with buttons.

    Tapping a button answers the same approval as the web popup (either endpoint wins, the other closes); cards are finalized after answering — buttons removed and replaced by the chosen answer — and time-outs / stale approvals render as expired cards. Requires enabling the long-connection callback subscription in the Feishu developer console (setup page updated, all 11 language dictionaries +1 entry).

Changed

  • Agent workflow spec: Context Packages are now two-tier.

    The context agent assembles a full package only for three kinds of work orders — retries (attempt > 1), tasks that need Bible slicing, and tasks that need defect-history aggregation; every other order inlines its context (input paths + hard constraints) in the work order itself and skips the context agent entirely. Previously the spec mandated a context-agent call before every dispatch (~4 minutes per call measured; 395 calls for 310 work orders on one project). The four-file run-record check exempts context.md for inline orders, and Bible reads no longer go through memory-bible arbitration (writes and conflict reports only).

v1.0.8

Added

  • "Smart Kimi" model policy (smart_kimi): routes each agent to a Kimi model by task complexity — creative-core agents to K3, the rest to K2.7 Coding.

  • Hand-drawn reference images, revamped: phone-submitted sketches with annotations now generate a finished image through the configured image engine before entering the group's reference images, instead of feeding raw line art directly into refs and the prompt; the entry point moved into the "Add reference image" dialog.

  • "Add reference image" dialog: local upload (magic-number validated png/jpg/webp/gif, ASCII-sanitized filenames) alongside asset-library selection.

  • Reference image management: the dialog shows the group's current refs as a thumbnail strip with [Image N] badges and click-to-zoom; user-added images can be removed with a confirmation prompt, and group cards show the refs too.

  • Version management page: "Copy project" section — duplicate the whole current project directory as a new project with name validation.

  • Workflow preview: "✏️ Edit" button next to the critical path — prefills a chief-producer message for adjusting the entire workflow (runs/dag.json).

  • Concept art archiving convention: superseded candidates, intermediate attempts and test images move to a candidates/ subdirectory; the main directory keeps only the finalized latest versions.

  • Production packages are published to Aliyun OSS.

Changed

  • Video prompt spec aligned with the official Seedance 2.0 prompt guide: per-character subject definitions up front with [Image N] anchors (bare names afterwards), one camera move per shot (new machine check one_move_per_shot), the full official twin-prevention constraint, [Audio N] voice anchors with mandatory timbre phrases, <>-wrapped sound cues, quantified action-writing rules, and reference-image ordering guidance (character images first, single-view preferred).

  • line-editor became a stateless fan-out station (one order per chapter, concurrent); prose-writer stays stateful and serial.

  • Storyboard preview: removed the "(no keyframe)" placeholder — the media column is simply omitted when a group has no keyframes.

Fixed

  • Phone QR access to the hand-drawn canvas: LAN IP detection no longer returns proxy virtual-NIC addresses (Surge/Clash TUN fake-IP range filtered, private-subnet probing first, VIDEOAGENTS_LAN_IP override); a new LAN sidecar listener serves only the draw page, its token-gated session API and static assets while the console stays loopback-only; the session URL under the QR code is now a clickable explicit http:// link so browsers don't auto-upgrade a hand-typed address to unreachable HTTPS.

v1.0.7

Added

  • Storyboard preview: per-group "🖼 Image" button — browse concept-library assets by character/scene/prop category and pick an image to add to the group's reference images (refs).

    Shares the 9-image cap with hand-drawn sketches (enforced on both frontend and backend); images already in refs are greyed out to prevent duplicates. New backend POST /storyboard/refs (core.api_grpref_add) validates the assets/concepts/ prefix and guards against path traversal. All 11 language dictionaries updated.

  • Storyboard preview: per-generation-group "📄 Prompt" button — popup showing the group's video prompt and byte usage.

  • Run panel: inline "⏹" button on each run entry to stop that single agent (queued or running) without affecting other runs; new backend POST /runs/{run_id}/cancel.

Changed

  • Fresh-install provider defaults: image/video/TTS now default to Volcano Engine and music generation to ElevenLabs (DEFAULT_GENCONFIG provider defaults plus the models.html initial fallback).

    Existing installs' genconfig.json is unaffected.

  • Generation model settings: the active channel for music generation and TTS voice models now follows the selected tab (✅ marker), matching the file-hosting storage channel UX; the per-tab check circles were removed.

    All 11 language dictionaries updated.

  • Video prompt body language now follows the user's UI language instead of defaulting to English.

  • WebUI link color switched to high-contrast orange (--link: #fb923c) with a new global a fallback rule — unstyled links no longer fall back to the browser's default dark blue, which was hard to read on dark backgrounds; explicit link rules (e.g.

    .hint a) updated to match, navigation/icons/menus untouched.

Fixed

  • WeChat binding: fetching the bind QR code now retries up to 2 times on transient network/SSL EOF errors (proxy hiccups causing peer disconnects) before reporting failure.

Fixed

  • deepagents: raise the default LangGraph recursion_limit from 50 to 250 (500 for dispatchers) so multi-tool runs no longer fail with GraphRecursionError mid-task.

  • deepagents: use one real host-path view for filesystem tools and shell execution, with explicit workspace/project roots, so absolute project paths no longer alternate between virtual /data paths and nested workspace mirrors.

  • orchestrator/dispatch: after errors or rework, do not switch execution engines; force --engine overrides are ignored.

v1.0.6

Added

  • "Concurrency" dialog under Settings → Advanced (slider + number box, 1-8, default 5): per-agent concurrency quota for stateless fan-out agents.

    Stateful creative agents always stay serial to protect session continuity, and every run still passes the global 8-process gate. Persisted in state.json via the new GET/POST /api/v1/config/concurrency; takes effect immediately for subsequently queued tickets.

  • "Agent Memory" toggle under Settings → Advanced (default on): when turned off, every agent — including the Producer — starts a fresh session per run, and cross-ticket information travels only through on-disk artifacts; session ids keep being recorded while off, so re-enabling resumes from the most recent session.

    New GET/POST /api/v1/config/agent-memory.

Changed

  • Same-agent execution gate upgraded from a boolean mutex to per-agent semaphores, and the stateless fan-out set expanded: the 05-scenes/, 03-characters/ and 06-art/ prefixes plus 01-story/novel-parser now start a fresh session per ticket and may run in parallel up to the concurrency quota — per-scene/per-character fan-outs (e.g. 70 p3-environment tickets on a large project) no longer serialize behind one agent lock.

  • Refreshed desktop app icon.

Fixed

  • Desktop auto-update no longer gets stuck mid-update (download/install flow hardened).

v1.0.5

Added

  • Quality-judge slider in review settings (0-100, default 60, listed first): the value is the evaluation acceptance threshold below which verdicts become "must fix as instructed"; 0 disables evaluation tickets entirely (no scoring for the whole run).

    Synced to the new-project wizard; prompt injection covers the three states and names own_review explicitly.

  • H1A "characters & assets confirmation" human sign-off gate added to the default workflow's g3 gate, between H1 and H2; WORKFLOW.md hard rules, flowchart, gate notes and H-point summary table updated.

  • The console footer now shows the serving version (read from the API health __version__), so release bumps surface automatically in the UI.

  • Character/scene/prop preview galleries collect subdirectories (e.g. candidates) into folder cards opened on click, keeping drafts out of the final-image grid.

Changed

  • claude engine wired to opus-5: the top-bar default is the "opus (latest)" alias with opus-5 as the second choice; the smart-assignment high tier resolves to it (smart_claude → opus-5, smart_codex → gpt-5.6-sol).

  • Smart-assignment tiers rebalanced: novel-parser promoted to the high-complexity tier (back to the 01-story category default); the whole 02-worldbuilding and 04-creatures categories demoted to the low tier, with world kept high as the creative core.

  • Run panel cards no longer embed an activity-log expander; errors are shown inline in red instead.

Fixed

  • deepagents engine: interpreter auto-discovery, and local endpoints connect directly instead of going through the system proxy.

v1.0.4

Changed

  • Group anchor packs are now reuse-first: concept-library images (character three-views, scene concepts, prop scale refs) are reused directly as Seedance 2.0 reference images, and groups fully covered by the library generate zero new images (generation_channel: reuse-only).

    Per-group composed opening anchor frames (anchor_opening) are no longer produced by default — the multi-reference video mode is mutually exclusive with first-frame input, so composed openings never entered video requests (evidence: 86 such frames across one episode, none used); they remain allowed only as an orchestrator-approved first-frame/split fallback. Gap-fill anchor generation (missing costume state, expression, prop close-up) must record a gap_reason, enforced by the new reuse_first_ok machine check.

  • Reused anchors skip character-consistency correction and visual-QA composition scoring (their source concept images were already reviewed at library intake); reuse-only groups pass straight through, saving generation quota and QA time.

  • The prompt station emits anchor-image prompts only for genuine concept-library gaps instead of routinely scripting an opening frame per group.

  • tests/ removed from version control (local-only from now on).

Fixed

  • UI language preference is now saved globally in state.json (dual-written with genconfig for backward compatibility, read preferring state; the ui-prefs API returns it and the browser restores from the server before auto-detection) — fixes the desktop app reverting to browser-detected language after each randomized-port restart.

v1.0.3

Added

  • WeChat ClawBot integration: bind an account by scanning a QR code on the new clawbot.html page and exchange messages with the chief producer directly from WeChat; channel implementation in services/runtime/wechat.py, speaker credentials rebound to the TOS file-hosting channel AK/SK.

  • blocking_bound machine check: each scene's blocking (staging) fragment is injected verbatim into generation prompts and validated by code/blocking_bound_check.py, preventing character spatial drift within a scene.

  • costume_bound deterministic injection: the continuity costume state is copied verbatim into image/video prompts, preventing wardrobe drift across shots.

  • Identity lock extended to all groups: prevents three-view character reference images from duplicating the character as a second person in frame; when the cast changes across a group boundary, the seam shot must switch composition so new characters cannot appear mid-shot out of nowhere.

  • H3B visual-generation sign-off gate in the default workflow (g7): each episode's group clips are reviewed before entering editing.

  • System sleep prevention on macOS/Windows while auto-run is active.

  • Version management page gains an enable toggle (default off); when disabled, no version-management tickets are dispatched.

  • Settings menu reorganized: new "Advanced" submenu, "Agent Model" renamed to "Model Strategy"; the Web UI now displays the actual serving port.

Changed

  • p0 novel parsing switched to chapter-level map+merge: batched scanning, per-chapter fan-out parsing, and mechanical merging, so large novels no longer bottleneck on a single parse pass.

  • fusion-fiction plugin simplified: branch-project cloning and version management removed (users manage versions themselves); the soul reconciliation baseline is stored as a snapshot.

  • Plugins are now disabled by default after installation and must be enabled manually on the Plugins page.

  • Review intensity defaults to 0 with no review during the draft phase; the per-session fuse is lowered and the stateless station pool expanded.

  • Racing (parallel competitive generation) now runs only on explicit user instruction, never automatically.

  • Volcano video generation: input image minimum pixel hard limit (≥3,686,400 px per image, e.g. 2560x1440 for 16:9) consolidated into prompts.

  • Desktop/runtime hardening: local API calls bypass system and environment proxies; installer names include the version; macOS notarization; Intel macOS release builds dropped; Windows runtime junction packaging and Node 24 artifact workflows fixed.

v1.0.2

Added

  • Declarative agent plugin system: a plugin contains a plugin.json manifest, SOUL.md agents, and optional standalone workflows/*.yaml DAGs.

    Bundled plugins live under plugins/; uploaded plugins use the writable runtime data directory. Plugin agents and DAG nodes are registered dynamically, injected into system prompts, and tracked in runs/dag.json alongside the built-in workflow. The typed /api/v1/plugins resource supports list/install/update/delete with collision and path-traversal validation; plugin authoring guide in plugins/README.md and WORKFLOW.md §10.

  • Official plugin derivative-fiction: an 8-station team that writes derivative novels grounded in a project's canon bible/characters/assets — planning, outline, prose style bible, per-chapter planning/writing/line-editing, stateless prose QA, and release packaging (EPUB/TXT/platform chapter bundles) via a standalone novel.yaml DAG with three sign-off gates.

    New canon settings flow through bible-delta (canon stays read-only).

  • Official plugin fusion-fiction: a 5+1-station team that fuses two books in branch-project mode — book A supplies story and character souls, book B (a classic, reconstructible from knowledge without source text) supplies the world and character appearances.

    Core artifacts: dimension allocation matrix, character mapping table, and concept conversion dictionary (with a modern-vocabulary blacklist); five sign-off gates, results written directly into the cloned branch's canon bible/ and story/.

Fixed

  • Server-side self-initiated conversations (settings-change notices, project kickoff dispatch, watchdog wake-ups) now follow the top-bar global engine/model instead of silently falling back to claude: the browser preference is synced through /api/v1/config/global-model, and all dispatch paths resolve the model through a single agent_effective_model chain.

  • vc.py show now emits the stored blob byte-exact (the internal git wrapper stripped the trailing newline, making exported files one byte short and causing false sha256 mismatches).

Changed

  • Product and architecture regression tests remain versioned in tests/; checks that depend on private local data stay outside the public suite.

v1.0.1

Added

  • run.sh one-command launcher, equivalent to make run-auto (no make required); auto-detects python/python3 and passes arguments through to the server.

  • Hard validation grpsh_id_3digits on media generation: output paths whose grp/sh numbers are not 3-digit zero-padded (e.g.

    grp01 vs grp001) are rejected with the corrected name, preventing filename drift that broke previews and machine checks.

Changed

  • Preserved the original HTML/CSS WebUI under apps/web/static, migrated its JavaScript directly to the typed /api/v1 FastAPI surface, and retained no legacy API adapter.

  • Added a Python Web gateway shared by URL and Electron clients for static pages and same-origin API proxying; Electron supports local or remote Python services and consumes GitHub Release updates.

  • Persisted runs, approvals, and replayable event sequences in SQLite while retaining the established creative execution core and data/projects/ resource model.

  • Migrated project asset history and version-clone operations from system Git commands to pygit2.

  • Added GitHub Actions packaging for macOS x64/arm64 and Windows x64 clients without bundling third-party execution environments.

  • Style library: all 85 live-action prompts expanded from short tags into full style descriptions (era/genre positioning, palette, lighting, film texture, contrast, composition and mood); garbled movie-name entries normalized.

Fixed

  • Preview pages now tolerantly match legacy drifted asset filenames by prefix word + numeric ID (grp01 matches grp001), so existing assets stay visible while new outputs are always written with canonical names.

v1.0.0

Added

  • Reference images page: upload, preview, and annotate visual/character/scene/prop/music/cover references; annotations sync to refs/NOTES.md and are read by visual and music agents.

  • Design brief split into main concept + design style, with a built-in style library picker (94 curated styles across live-action/3D/2D categories).

  • Workflow preview page: runs/dag.json rendered as stage swimlanes with dependency lines, fan-out aggregation, gate/sign-off markers, and remaining time/cost estimates.

  • Worldview preview page, plus preview enhancements across characters/scenes/props/storyboard: inline ✏️ edit-request buttons, audio players for voices/narration/music, and a collapsible directing plan block.

  • TTS channels: Volcano Doubao Seed-TTS 2.0 and ElevenLabs, with voice library search/audition and one-click default voice; narrator voice now follows the active channel's default voice.

  • Music generation via ElevenLabs Eleven Music with per-cue duration control.

  • Publishing skills: semi-automated draft upload to YouTube, Douyin, Xiaohongshu, TikTok, and Bilibili (final publish click stays manual).

  • Resource usage panel: session/weekly usage across the three engines plus OpenRouter/Volcano balances; engine CLI detection with install guidance; Kimi Code engine support.

  • Idle watchdog that wakes the orchestrator when the pipeline stalls, with per-project switches and usage-threshold gating.

  • Sign-off confirm dialogs that never auto-confirm, separate from ordinary retry confirms.

  • Optional subtitle burn-in for final renders.

  • Character gender field with hard constraints through voiceprint/casting/keyframe consistency checks; per-character voiceprint anchoring for TTS.

  • ASCII-only output filename rule enforced across all 83 agents.

  • UI language auto-alignment between browser and server, with agent reports following the UI language; i18n maintained across 11 languages.

Changed

  • Volcano TTS authentication migrated to the new console's single X-Api-Key header (legacy App ID + Access Token removed).

  • Final-render subtitles standardized on subtitles_final.srt (intro-offset corrected); master assembly naming locked down.

Fixed

  • Final subtitle timing offset caused by intro length; watchdog engine fallback now uses the orchestrator's configured engine.

  • Made the test import path explicit for pytest 9 and GitHub-hosted runners.

  • Updated GitHub Actions to their Node.js 24 releases.

v0.1.0

Added

  • Initial open-source release of the 83-agent production workflow.

  • Local Web console, media adapters, project versioning, and validation tools.

  • English and Chinese documentation, security policy, contribution guide, and CI.

Security

  • Localhost-only Web binding and acceptEdits agent permissions by default.

  • Claude OAuth usage probing changed to explicit opt-in.