Repository navigation
Conversation
a304a0f to
994da3d
Compare
- Enable the Director Stage entry in the shared creation toolbar and open the stage editor automatically after creating the node. - Pin node, document, and keyframe identity across editor.save, undo, redo, and storage reload with a service-level command test. - Add a browser acceptance flow covering creation, editing, undo, redo, and reopen, runnable in en-US and zh-CN via DIRECTOR_STAGE_LOCALE. - npm test (676 passing at this commit's tree state). - .venv/bin/python -m pytest tests/test_creation_director.py. - node tests/browser/director_stage_entry.mjs plus the DIRECTOR_STAGE_LOCALE=zh-CN run, against the isolated creation_server.py fixture. Refs: ThinkFlowLab#241
## summary - Extend StageObject with kind 'gltf' carrying a registered AssetRef, a character or scene role, a glb or gltf container format, and the animation clip names discovered in the imported asset. - Reject incomplete or incompatible imports at the contract boundary: missing asset, role, or format, non-file assets, motion clips on scene assets, motion without animation data, mismatched animation flags, and blank animation names. - Prove the persistence boundary with a command-level test: forged asset references are rejected without state changes and replays return the original receipt. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_contracts.py (89 passing at this commit's tree state). - .venv/bin/python scripts/generate_contracts.py --check. - npm test (676 passing) and npm run build against the old projection reading the regenerated contract fixture. Refs: ThinkFlowLab#241
## summary - Import GLB and self-contained GLTF files as stage objects with a character or scene role; FBX files and GLTF resources with external dependencies are rejected with bilingual capability notices. - Keep blob preview URLs out of persisted identity: saves register the imported file as a project asset and the stage object pins its AssetRef, so imported objects survive reopen, undo, and redo. - Surface per-object capability boundaries in the editor: static use without animation data, discovered clip names, and motion clips only when the asset carries animation data. ## validation - npm test (679 passing) and .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_contracts.py tests/test_creation_director.py. - .venv/bin/python scripts/generate_contracts.py --check and npm run check:i18n (2888 keys, 0 problems). - node tests/browser/director_stage_entry.mjs plus the DIRECTOR_STAGE_LOCALE=zh-CN run against the isolated creation_server.py fixture. Refs: ThinkFlowLab#241
## summary - Detect GLB and GLTF containers from actual bytes during upload probing, record model/gltf-binary or model/gltf+json MIME types, and mark whether every buffer and image is embedded in a new selfContained metadata flag. - Reject director documents whose imported stage objects pin assets that are not self-contained models of the declared container format, so no entry point can smuggle external-resource files into a stage document. ## validation - Verified on the full model import and animation tree: .venv/bin/python -m pytest tests (1096 passed, 6 skipped) and .venv/bin/python scripts/generate_contracts.py --check. Refs: ThinkFlowLab#241
…mations ## summary - Probe import bytes in the editor: the container format comes from the GLB header or GLTF document, not the file extension, and corrupt or external-resource files are rejected with localized notices before anything reaches the project. - Map procedural motion presets onto imported animation clips through shared name hints in stageMotion, so preview, stills, and MP4 rendering pick the same clip from the same stage sampling. - Cover the flow end to end with an animated GLB fixture: clip discovery, motion binding at the playhead, playhead scrub animation, and a 1280x720 MP4 rendered through the shared sampler. ## validation - Verified on the full model import and animation tree: npm test (680 passing), npm run build, npm run check:i18n, and the isolated browser checks tests/browser/director_stage_entry.mjs (en-US and zh-CN) and tests/browser/director_stage_animation.mjs. Refs: ThinkFlowLab#241
…l capture ## summary - renderVideo parsed the stage into a local `snapshot` that shadowed the component's `snapshot()` still-capture helper; rename the local to `exportStage` so the two are no longer confused.
## summary - stopAllAction leaves imported skeletons frozen at the last sampled pose; capture the authored rest pose when a runtime binding is created and restore it whenever a sample leaves a motion clip. - Build preview and export bindings through one createImportedRuntime helper, and clone export runners from the pristine cached scene so frames outside clips render the authored rest pose too.
…sources ## summary - A GLB whose header is valid but whose embedded JSON chunk is missing, unparsable, or not a glTF document was reported as an unsupported external-resource import; probeStageAsset now returns a distinct corrupt result with its own bilingual message. - Move the byte probe next to the stage document format and export it so container checks are testable without a browser.
## summary
- The director projection derived upload names as `${name}.${format}`;
renaming a stage object to "hero/v2" made the whole save fail on the
backend path-character check. Derive through stageUploadTitle, which
replaces path separators and control characters, trims, caps the
length under the 255-character upload limit, and falls back to the
localized model title for blank names.
- Cover the sanitization with Node tests.
## validation
- node --test tests/directorStageAssets.test.ts
## summary - The .gltf probe read whole files into memory under only the generic 512 MiB asset cap. inspect_file now rejects GLB and .gltf uploads above MAX_STAGE_MODEL_BYTES (100 MiB, matching the browser import limit) with a bilingual 413 before any full read. - Oversized JSON without a .gltf suffix is no longer parsed as a document and stays a generic file instead of spiking memory. - Cover oversized GLB, oversized .gltf and oversized plain JSON. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py -q
## summary - The load effect cached resolved scenes only; while a slow load was in flight, a re-run fetched the same asset again and the loser's scene was overwritten in the cache without ever being disposed, leaking its WebGL buffers. Cache the in-flight promise under the cache key so concurrent runs share one load. - Evict a promise from the cache when it rejects so later runs can retry, and dispose resolved scenes on unmount through the promise.
## summary
- StageObject.animationNames and hasAnimation were client-attested and
never compared with the pinned asset. The upload probe now records the
animation names a model container declares (declared names trimmed,
unnamed clips falling back to animation_{index}, matching what the
browser loader derives) as MediaMetadata.animationNames, and snapshot
validation rejects stage objects whose animation metadata disagrees
with the pinned asset bytes.
- Align the front-end unnamed-clip fallback with the same convention.
- Regenerate contracts, extend the fixture and cover forged names and
the fallback naming with Python tests.
## validation
- .venv/bin/python -m pytest tests/test_director_stage_assets.py
tests/test_creation_contracts.py -q
- .venv/bin/python scripts/generate_contracts.py --check
- npm exec tsc -- --noEmit
## summary - Exported stage JSON keeps pinned asset identity but carries no model bytes, so importing it into another project produced objects that could neither preview nor apply: the editor showed an unavailable asset and every save failed the backend asset check until the objects were deleted by hand. - The workspace now passes the open project's registry to the stage editor (same consistency the backend snapshot check demands), and import rejects up front with a bilingual message naming the objects whose assets are not registered in this project. - Cover the foreign/registered/unregistered distinction with Node tests.
## summary - Python: a GLB with a valid header whose declared length exceeds the file stays a generic file asset, and stage objects pinning it are rejected. - Node: byte-level cases for the shared stage asset probe (self-contained and external models, truncated GLB, corrupt or non-document JSON chunks), and a projection read where the pinned model's content is unavailable — the reopened stage keeps its registered asset identity and stays a valid document without the preview URL. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py -q - node --test tests/directorStageAssets.test.ts - npm exec tsc -- --noEmit
## summary - Add zh-CN text to all seven StageObject import validation errors in the domain contract, matching the dual-language pattern already used by the stage checks in project.py. - Chinese wording follows the stage terminology in the editors message catalogs (舞台对象, 素材, 动作片段), so receipt issues stay readable in both locales. - Validation rules and error conditions are unchanged; only the message text now carries both languages. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_director.py tests/test_creation_contracts.py (98 passed, working tree identical to the staged content)
…d pin procedural rejects ## summary - Deliver the missing-or-inconsistent pinned asset reference rejection in both Chinese and English, matching the other snapshot boundary messages. - Cover procedural stage objects that carry imported asset or animation metadata, which the stage object contract rejects. ## validation - Verified on the full still and MP4 registration tree: .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_contracts.py. Refs: ThinkFlowLab#241
…daries
- Probe glTF documents for Draco, meshopt and KTX2 extensions and reject
them with a bilingual notice for needing decoders the browser loader
cannot provide, instead of mislabeling the bytes as corrupt.
- Route stage and layer JSON imports through a text boundary that reports
unparsable files (including binary model bytes fed to the JSON picker)
with a localized notice instead of an engine SyntaxError quoting them.
- Keep cached imported scenes resident while any live clone references
them, and dispose them only after the last one is gone.
- Share one selection-highlight pass across placeholder and imported
clones so unlit materials without emissive survive selection.
- Preserve the loader's animation_{index} fallback for unnamed imported
clips in committed metadata, and stream-scan leading whitespace before
glTF JSON heads in the upload probe (also catching pathological deeply
nested JSON via RecursionError).
- Verified on the staged tree: node_modules/.bin/tsc --noEmit,
node --test tests/canvasStage.test.ts tests/directorStageAssets.test.ts
tests/layerDocument.test.ts (32 passing), and
PYTHONPATH=packages/core/src .venv/bin/python -m pytest
tests/test_director_stage_assets.py (26 passed).
… retargeting
## summary
- Route every stage numeric field through one NumberField text-to-value
boundary: only canonical decimal text commits, so leading zeros ("03"),
trailing dots, exponents and empty input never become values and a
cleared field can no longer silently write 0.
- Replace the read-only time readout with an editable current-time field,
clamped to [0, duration] and quantized to hundredths so the playhead,
the slider and keyframe identity share one time grid.
- Extract stageCameraAt as the single shot-resolution source shared with
the sampler: choosing an edit-target camera or duplicating a camera no
longer flips cameraSource off storyboard cuts, and in storyboard mode
the selection retargets the shot covering the playhead instead.
## validation
- Verified on the staged tree: node_modules/.bin/tsc --noEmit, node --test
tests/canvasStage.test.ts (14 passing, including stageCameraAt boundary,
gap and clamping coverage), and npm run check:i18n (2896 keys,
0 problems).
- node tests/browser/director_stage_entry.mjs covers the numeric contract,
clamped quantized playhead entry, and storyboard-preserving camera
selection end to end in both locales.
## summary - Split Director Stage scrolling into two regions — subjects with the stage and timeline on one side, the inspector on the other — so neither column drags the other along. Region scrollbars stay thin and quiet until the region is hovered or dragged; narrow layouts (<=1100px) keep the previous single shared scroll. - Size the stage frame to a comfortable centered width (900px for 16:9) shared by the viewport, transport, playhead track, keyframes and storyboard, so the draggable track lines up exactly with the picture instead of overrunning it, and portrait stages stay height-capped and centered. - Pin the footer hint below the two regions instead of letting it ride inside the layout container, where it could drift off the page once the layout became position-managed. ## validation - Verified on the staged tree: node_modules/.bin/tsc --noEmit and npm test (693 passing). - Measured in the browser across 1920x1080, 1440x900, 1229x691, 1280x720, 1366x640, 1100x700 and 1000x620 viewports: the frame stays centered with the track exactly matching its width, both regions scroll independently, the bottom action bar and hint remain inside the window, and narrow viewports fall back to one scroll. - node tests/browser/director_stage_entry.mjs passes in en-US and zh-CN after the restructure.
- Add an optional still field to DirectorData that pins the captured frame as a project image asset, so the still survives reopen with a real identity instead of an untracked node data URL. - Reject director documents whose still pins a missing or non-image asset at the snapshot boundary, alongside the existing model checks. - Cover the field with service tests for round-trip, dangling references, and non-image rejections. - Verified on the full still and MP4 registration tree: .venv/bin/python -m pytest tests (1104 passed, 6 skipped) and .venv/bin/python scripts/generate_contracts.py --check. Refs: ThinkFlowLab#241
- Capture stage stills at the encoder aspect through the same sampler as the MP4 render: the first apply auto-captures a poster, later applies keep the pinned still, and an explicit capture registers a distinct image asset while the previous one stays registered. - Route stills through the save projection: captured bytes are imported as project image assets, the director document pins the resulting AssetRef, and reopening restores the node thumbnail from the asset instead of a data URL. - Audit the director MP4 path: published documents freeze a copy of the director document with its source editor identity. - Cover the chain end to end: still capture, no-overwrite semantics, MP4 output nodes pinning real video assets, timeline clips referencing the published assetId, and reload persistence. - Verified on the full still and MP4 registration tree: npm test (685 passing), npm run build, npm run check:i18n (2891 keys, 0 problems), and the isolated browser checks director_stage_output.mjs, director_stage_entry.mjs (en-US and zh-CN), and director_stage_animation.mjs. Refs: ThinkFlowLab#241
- Capture stills at exactly the encoder resolution on high-DPI displays by pinning the renderer pixel ratio during capture. - Clone the export scene only after render preparation so a model whose preview load lands mid-preparation no longer exports as its placeholder, and dispose placeholder-owned GPU resources. - Reject stage JSON exports that contain imports lacking registered assets, seed the shared asset cache during import to avoid double parses, widen the upload probe for whitespace-prefixed glTF documents, and count surrogate pairs when truncating upload titles. - Hide the material color picker for imported models and verify the still dimensions under a doubled device scale factor. - Verified on this tree: npm test (688 passing), .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_contracts.py, npm run build, and npm run check:i18n (2892 keys, 0 problems). Refs: ThinkFlowLab#241
…aying them ## summary - Treat a transport-level 4xx refusal (400, 401, 403, 413, 422) on an editor delivery as definitive, the same as a command rejection: the stored pending plan is dropped instead of being replayed forever from sessionStorage. A plan prepared by an older schema (for example one the current backend rejects with 422 invalid_input at the HTTP boundary) now costs at most one failed attempt before the next one replans with current code; unknown replies and lost responses keep their retained delivery and replay the exact same command. ## validation - Verified on the staged tree: npm test (693 passing) including a unit test that seeds a refused stored delivery and asserts it replays once and replans on the retry. - node tests/browser/director_stage_delivery.mjs reproduces the reported stuck Director Stage end to end: a poisoned stored delivery surfaces the error once, the next render replans and completes the MP4, and apply recovers over the same stored entry.
- Derive a stagePreview flag at projection by resolving a rendered asset's editor document to its director execution snapshot, so timeline composition renders keep the full toolbar. - Add STAGE_PREVIEW_EXCLUDED_VIDEO_TOOLS to the shared tool catalog and filter the video selection toolbar through it; preview outputs keep frame capture, trim, crop, and universal actions only, while ordinary video nodes are unchanged. - Accept the optional flag in archive provenance validation and cover the exclusion set, predicate, and projection stamping with tests. - npx tsc --noEmit; npm test (697 passing); npm run check:i18n (0 problems). - .venv/bin/python -m pytest tests/test_creation_media_publication.py tests/test_creation_canvas_service.py (33 passing). - Browser: selected the published preview node in the live project and read the toolbar in both zh-CN and en-US.
- Expose canvas.publishMedia and timeline.publishRender to agent runtimes. File registration keeps going through artifact.publish, so asset.import stays excluded together with its HTTP upload staging. - Accept agent-registered deliverables as publication and render outputs: the provenance rule now takes upload or agent_written while keeping the freshness invariant of unused, never-referenced, kind- and duration-matching assets. - Walk editor document union branches when synthesizing command help examples instead of assuming the timeline default, and describe the CLI timeline group that the shared catalog now exposes. - Prove the whole chain with real tool dispatch: create and edit a director stage, register a rendered MP4, publish it with the frozen director snapshot, reference it from a timeline clip, publish the timeline render, and verify replay idempotency, conflicting input, used-output rejection, and the identical rejection on the user path. - .venv/bin/python -m pytest tests (1107 passed, 6 skipped) including the new tests/test_director_agent_chain.py. - npm test (688 passing), npm run build, npm run check:i18n, generate_contracts.py --check, and the isolated browser checks director_stage_output.mjs and director_stage_entry.mjs on the relaxed publication rules. Refs: ThinkFlowLab#241
994da3d to
2bd41d3
Compare
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 56a1d9384f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| source: await this.client | ||
| .mediaUrl(object.asset.assetId) | ||
| .catch(() => undefined), |
There was a problem hiding this comment.
Include imported models in shared media transfers
When a saved stage contains a GLB/GLTF object, its preview URL is stored in source, but canvasArchive.ts:23 and clipboardMedia.ts:72 only traverse src, sourceImageUri, and output. Consequently, .canvas export rejects the remaining blob: URL, while cross-project paste captures no model bytes and retains the source project's AssetRef, making the pasted stage unsavable. Integrate imported models into the shared byte-transfer and target-asset remapping paths.
AGENTS.md reference: AGENTS.md:L45-L45
Useful? React with 👍 / 👎.
| for (const [id, binding] of previewRuntime.imported) { | ||
| const item = exportStage.objects.find((object) => object.id === id); |
There was a problem hiding this comment.
Await every imported model before cloning the export scene
For a reopened stage whose model load is still pending or has failed, ready already permits MP4 rendering because it only tracks WebGL initialization. This loop visits models already installed in previewRuntime.imported, so it never waits for outstanding model loads; the cloned scene retains their placeholder boxes. onPrepareRender saves the document but does not establish model readiness, allowing an MP4 that omits its declared models to be encoded and published. Resolve every imported object in exportStage.objects, and reject unavailable inputs, before taking the scene snapshot.
Useful? React with 👍 / 👎.
| if asset.metadata.mimeType != STAGE_MODEL_MIME[obj.format] or asset.metadata.selfContained is not True: | ||
| raise ValueError('Imported stage objects require a matching self-contained GLB or GLTF asset. 导入的舞台对象需要匹配的自包含 GLB 或 GLTF 素材。') |
There was a problem hiding this comment.
Enforce stage decoder support at the backend boundary
An agent can register a self-contained model requiring Draco, meshopt, or KTX2 and attach it to a director document: inspect_file records the matching MIME type and selfContained=True without checking decoder requirements, and this validator accepts it. The browser's importAsset explicitly rejects these models, while loadStageAsset constructs a GLTFLoader without those decoders, so the accepted agent-created stage cannot load its pinned model in the UI. Enforce the supported decoder requirements through shared backend probing and validation rather than only through the browser import precheck.
AGENTS.md reference: AGENTS.md:L61-L62
Useful? React with 👍 / 👎.
| const next = parseLocalStageDocument(await file.text(), t); | ||
| const foreign = isRegisteredAsset | ||
| ? unregisteredStageAssets(next, isRegisteredAsset) | ||
| : []; |
There was a problem hiding this comment.
Rehydrate model sources when importing stage JSON
Exporting stage JSON now removes every object's source. Importing that export into another stage in the same project passes the asset-registration check, but the parsed document is installed without reconstructing those URLs. The model-loading effect then explicitly skips every imported object without item.source, reporting available project assets as unavailable and displaying placeholders until the editor is closed and reopened. Resolve registered asset pins to current-project media URLs before installing the imported stage.
Useful? React with 👍 / 👎.
| if (material instanceof THREE.MeshStandardMaterial) | ||
| material.emissive.set(highlighted ? "#1e1536" : "#000000"); |
There was a problem hiding this comment.
Preserve imported materials' authored emissive values
For a GLTF material with a non-black emissiveFactor, this highlight operation replaces its authored emission with either the selection color or black. Imported clones share materials with the cached scene, so the original value is lost as soon as highlighting runs; the MP4 material-cloning path also unconditionally zeros emission. Models containing emissive surfaces therefore produce previews, stills, and videos with altered materials. Preserve the authored emissive value and apply a reversible highlight instead of assuming every material starts with black emission.
Useful? React with 👍 / 👎.
| binding.mixer.setTime( | ||
| clip && value.motion ? value.motionTime * value.motion.speed : 0, | ||
| ); |
There was a problem hiding this comment.
Honor motion amplitude for imported animation clips
For animated imported characters, the inspector still exposes the Amplitude slider and persists motion.amount, but the imported-animation path only consumes the preset and speed. Setting amplitude to zero still plays the selected clip at full weight, and intermediate slider values produce identical motion in both preview and MP4 output. Apply the amount to the animation action's weight, or stop exposing and accepting this parameter for imported clips, so the saved edit is not silently ignored.
Useful? React with 👍 / 👎.
| {nodePorts(kind).source && ( | ||
| <Handle |
There was a problem hiding this comment.
Retain proxy anchors for stage publication edges
Publishing a director MP4 still creates a post_edit_result edge from the external stage item to its preview in CreationProjection.read (creationProjection.ts:946-955). Since nodePorts('external').source is now false, this gate removes the stage's only source handle, preventing React Flow from resolving that edge's endpoint and rendering the provenance line. These proxy handles were already invisible and non-connectable (nodes.css:1-3), so retain rendering anchors for persisted relationships independently of the generation ports offered to users.
Useful? React with 👍 / 👎.
Closes #241
Summary
Agents publish stage and timeline outputs through the same shared commands the browser uses, completing #241's user/agent equivalence for this capability. Slice 4 of 4, stacked on #330.
artifact.publishtocanvas.publishMedia/timeline.publishRender(packages/core/src/opendirector/agent/tools.py,cli.py), reusing the same validation, receipts, freshness, and provenance invariants as the UI path.tests/test_director_agent_chain.pycovers the chain end to end: publish, replay of an acknowledged delivery refused, conflicting input on the same identity rejected, execution id reuse bounded.Linked issue
Labels
type:feat,area:agentTest plan
npx tsc --noEmitclean;npm test697/697;npm run check:i18n0 problems;.venv/bin/python -m pytest tests/test_director_agent_chain.py2 passed.Evidence that it works
tests/test_director_agent_chain.py.Provider verification
Bilingual UI