With preload="metadata" browsers paint NOTHING until playback, so the Video
tab's hero monitor and every filmstrip thumbnail sat black — clips looked
missing while their audio played fine ('no picture, but sound'). The media
fragment makes the browser seek and paint the frame at 0.1 s without playing.
Verified in-browser: fresh load paints hero + all thumbnails; selecting a
scene paints its first frame immediately.
(cherry picked from commit a81280b1543b9f6aa913be8373f0a390d3db5871)
Some exceptions stringify EMPTY (httpx.ReadTimeout/ReadError, TimeoutError):
a sub-agent killed mid-stream by a backend restart reported 'Sub-agent
failed: ' with nothing after the colon, and no traceback was logged anywhere
(observed live 2026-08-25). The handler now logger.exception()s and formats
'Type: message' (or bare 'Type' when the message is empty).
(cherry picked from commit ff138a9a01a93714dc6d8d2bce3ee57ace03647d)
The destructive guard only gated replace=true — an unconfirmed
generate_story(replace=false) on a non-empty project silently APPENDED a
duplicate 25-beat set (25 -> 50, observed live 2026-08-25), and the guard's
own message recommended exactly that escape. Both modes are now gated on a
non-empty project with mode-specific messages that point at the granular
tools; 'append' joins the confirmation cues.
And the granular tools now exist for beats: add_beat (insert-with-shift, or
append when order_index omitted), update_beat (project-scoped id check),
delete_beat (destructive-flagged; later beats renumber to close the gap) —
before this, bulk regeneration was the only way to change a beat, which is
what boxed the story sub-agent into the append in the first place. The story
role's allowlist gains all three.
(cherry picked from commit 14ca52f5c02d1e8dd52e0f58e5fd0b8593816c9b)
On a large project (43 scenes with full action paragraphs) get_workspace
serializes past the 4000-char tool-result cap, so the tail-chop landed
mid-beats and characters/locations/items were ALWAYS below the waterline —
observed live: an assets sub-agent re-called get_workspace in a loop, blind
to the sections it needed, with no editor tools in its allowlist anyway.
- get_workspace gains sections=[beats|characters|locations|items|scenes];
project + stats always included; omitted sections listed; invalid sections
error names the valid set; no-arg call unchanged (back-compat).
- All three truncation sites share TRUNCATE_NOTE, which names the way out
(sections=[...], scoped tools) instead of a bare '[truncated]'.
- The assets role gains update_character / update_location / update_item
(deletes deliberately excluded).
(cherry picked from commit 9460b6c2e398bad5403ff73e4e8e7d2a96478821)
Once a session linked to a project, the agent could neither create a fresh
project (create_project is sandbox-only) nor switch (link_project is
sandbox-only) — and no unlink existed. Observed live: a linked agent burned
three failed tool calls and ~7 minutes of reasoning discovering the box, then
overwrote the linked project as its only option.
unlink_project (linked-only) clears the session binding and nothing else; the
blind_only rejection message now points to it, so the model recovers in one
step; link_project's description documents the switch path.
(cherry picked from commit f4fc7cde6461529177563d58383c5c50287026e1)
The packaged FastAPI static mount returned JSON 404 for /project/{id}. Serve the SvelteKit fallback page for client routes, and point README at source until a zip is trustworthy.
Extends the 1.2.1 Agent-loop fix to every LLM caller: with completions
streamed internally, the 120 s httpx timeout bounds the gap BETWEEN chunks
(liveness) instead of total generation time, so a thinking model that
streams reasoning_content can legitimately work past 120 s while a dead
server still fails fast. chat_stream gains response_format= with an
in-stream HTTP-400 drop-ladder (response_format first, then tools); servers
that reject stream:true entirely fall back to one plain blocking POST.
A reasoning-only stream (zero content accumulated) raises ValueError so
generate_structured's json_object retry fires instead of returning blank.
Queues clips one POST at a time in timeline order rather than a single
all-scenes request: the H3 prompt rewrite runs synchronously inside each
generate-videos request (~1-3 min per scene on local models), so one bulk
request for a long timeline would sit for many minutes and risk a client
timeout. The label is state-aware - 'Generate all (n)' targets only scenes
without a clip (skipping done and in-flight scenes), flipping to 'Regenerate
all' when the timeline is complete. A scene that fails to queue toasts and
the loop continues. Rendering itself stays serialized by the queue worker.
Workflow resolution matches the per-scene Generate button (session pick ->
scene's stored workflow -> first enabled video workflow), which also heals
scenes whose stored workflow was deleted - without it they enqueue a job
doomed to 'No workflow found for job'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds scenes.chain_from_prev (additive migration, same pattern as the existing
ones) with a toggle chip on every Script-tab scene card except the first.
The frame is resolved at JOB RUN time, not enqueue time: when queueing a whole
timeline the previous scene has not rendered yet at enqueue, but the worker is
sequential, so by the time scene N runs the clip for N-1 exists. The worker
finds the nearest earlier scene with a clip, extracts its last frame (ffmpeg,
already a runtime dependency for export), and swaps it into whichever ref slot
held the location image - located by value match against the enqueue-time
location path, so it works for any workflow layout (generic ref slots or
role-tagged), with the location-role node as fallback. No previous clip or a
failed extraction falls back to the scene's own refs with an event-bus notice,
never a hard failure.
On reference-style video workflows this yields scene-to-scene continuity (the
frame acts as the scene reference); on workflows with a pinned first-image
slot it opens frame-exact.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>