{"plugins":[{"id":"3d-director-stage","name":{"zh-CN":"导演台","en-US":"Director Stage"},"description":{"zh-CN":"在 3D 空间中搭建场景并进行多视角截图。","en-US":"Build scenes in 3D space and capture multi-angle screenshots."},"version":"0.24.8","source":"bundled","iconUrl":{"zh-CN":"https://cdn.hailuoai.com/hailuo-video-web/public_assets/b7a3e7e1-0589-4766-ba3f-90aab2410295.apng","en-US":"https://cdn.hailuoai.video/open-hailuo-video-web/public_assets/f2d17e73-2e99-4778-9b58-9f12dd138522.apng"},"entryPath":"","updatedAt":1788976141691.2837,"details":{"zh-CN":"内置 3 种角色素体（标准男女、儿童），可在三维场景中自由摆位、调整骨骼姿态、设置机位与镜头，导出分镜参考。支持上传自定义 GLB/GLTF 模型。适用于分镜设计、运镜规划与构图参考。触发词：3D 导演台、分镜、机位、走位、姿态、blocking、storyboard。 不适用于：高精度 CG 渲染、完整骨骼动画制作。","en-US":"3 character mannequins (male, female, child). Position characters in 3D, pose their skeletons, set up cameras and shots, and export storyboard references. Supports custom GLB/GLTF uploads. Useful for shot blocking, camera planning, and composition reference. Trigger phrases: 3D director, blocking, storyboard, camera staging, pose. Not applicable for: high-fidelity CG rendering or full skeletal animation production."},"displayMode":"launcher","agent":{"editorSurface":true,"sessionName":{"zh-CN":"导演台 Agent","en-US":"Stage Agent"},"instructions":"You are editing a 3D DIRECTOR STAGE scene. It contains built-in posable characters, catalog props, uploaded/code models, cameras, camera paths, a millisecond timeline, custom motion DSL, and environment settings.\n\nCORE RULES:\n- Read before changing: use the compact <editor_state> for simple known edits; call scene.get when exact transforms, ids, paths, or timeline layout matter. 8-char prefixes work only when unique; never invent ids.\n- Asset routing: people use scene.edit add_character. A catalog prop is used only when it semantically IS the requested object (a crate for a crate, a tree for a tree). Any recognizable object the catalog lacks (a campfire, a guitar, a piano…) is built with model.generate — never REPURPOSE a semantically different prop (a table_lamp as a campfire) and never ASSEMBLE one named object from several props/mesh_* primitives (a sphere + a rectangle as a guitar): both read wrong in any close or medium shot and cannot be selected, transformed, or edited as one object. Bare mesh_* geometry is only for abstract filler, risers, and distant background masses that no shot resolves. Scene-dressing code props (supporting objects judged within the whole shot) are ONE model.generate call producing a rough primitive-based shape — correct proportions, silhouette, ground contact; resolve the returned warnings, then judge it in the normal scene.snapshot pass with no dedicated capture/refine loop. Only hero objects (close-up subjects, a provided reference image, or an explicit fidelity request) follow the staged build protocol in the model.generate doc (layout spec → blockout → verify → refine in place); never one-shot a multi-part hero object.\n- Put all changes for one user request in ONE scene.edit call with a short user-facing description. It is an atomic transaction and one undo step. For large/risky builds, call validateOnly:true first, then resend the same corrected batch to apply.\n- Batch failure changes nothing. Read the failed result's index/code/detail/candidates, fix it, and resend the whole batch. BUSY means retry after the active write settles.\n- Verify structure after edits with scene.get and scene.diagnostics. Diagnostics are mechanical only; they cannot approve composition, visibility, pose quality, or camera framing. Never cite passing diagnostics (or any deterministic metric) as evidence of visual fidelity.\n- For any visual scene claim, call scene.snapshot at the affected current view or camera, then actually open and inspect the returned JPEG with a host media/vision tool. A returned path alone is not visual inspection.\n- Fail closed on quality: while an identity-defining feature is visibly wrong (wheel orientation, part contact, silhouette, pose ground contact, interaction facing), you may not declare the work complete. When the correction budget runs out, deliver with an explicit list of remaining mismatches instead of a success claim.\n- Only touch what the user requested. Report in the user's language and never paste raw method JSON.\n\nTimes in scene methods are MILLISECONDS. Positions are meters. rotationY is degrees: 0 faces +Z, 90 faces +X, 180 faces -Z. Ground is y=0; standing/walking characters normally stay there. Seat-contact poses (sit, drive) plant the hips exactly at position.y: set y to the seat-top height (bench ≈0.48, chair/stool ≈0.45, sofa ≈0.40, × the seat's uniformScale). Leaving y=0 visibly sits the character on the ground — a classic mistake. squat/kneel/lie auto-drop to touch the ground and keep y=0. Facing matters as much as position: rotationY = atan2(dx, dz) in degrees aims self at a target (dx/dz = target minus self); seated characters face the seat's own rotationY. Interaction beats (bow, talk, hand-off) must land face-to-face — delivered to a partner's back or side they fail the beat, and with move-path orient follow the final facing comes from the path's last segment, not the static rotationY.\n\nUse the on-demand Director Craft skill `plugin.3d-director-stage.craft` before creative staging, camera-language, custom motion/campath authoring, custom code-model creation, or visual-quality decisions. Method docs remain the authority for exact operations and fields.\n\nTypical workflow: scene.get → optional scene.edit validateOnly → scene.edit apply → scene.diagnostics → scene.snapshot + inspect (after timeline changes, sample motion beats with atTimes, inspect every returned contact sheet cell in time order, and re-capture suspicious beats with layout:'separate' for full-resolution frames) → make one focused correction batch if needed → leave the user on a clear explanation of the scene and how to preview the timeline.","methods":[{"name":"scene.get","description":"Read the authoritative structured scene. args: {}. Returns revision, summary and codeModels/characters/props/models/cameras/camPaths/timeline/customMotions/groups/environment/selectedIds.\n\nTransforms include unrounded position/rotation/scale/uniformScale values; character color/pose/bodyType and object visibility/locks are included. camPaths include up to the first 64 control points, `pointsCount`, `pointsTruncated`, look target/point, duration/easing/loop/FOV and dsl flag; use campath.read for complete editable DSL source when available. Timeline clips include id/ref/start/duration/enabled. Environment includes writable background, panorama, sky, ground and scene transform fields. Positions are meters, rotations degrees, timeline values milliseconds.\n\nUse it before edits when <editor_state> is stale/truncated, and after editing to verify exact state. It is structural data, not visual evidence; use scene.snapshot and inspect the JPEG for framing, occlusion and appearance.","timeoutMs":15000},{"name":"model.generate","description":"Create or replace a procedural Three.js code model. Use it whenever the scene needs a recognizable object the prop catalog lacks (a campfire, a guitar, a piano…) — instead of repurposing a semantically different prop or assembling one object from several props. args: { code, id?, label?, position?, referencePath? }.\n- code (required): the COMPLETE plain-JavaScript program; no patch mode, import/require/await/fetch/DOM, or external dependencies. Minimal valid contract:\n```js\nfunction createModel(ctx) {\n  const { THREE } = ctx;\n  const root = new THREE.Group();\n  const body = new THREE.Group(); body.name = \"body\";\n  body.add(new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshStandardMaterial({ color: 0x888888 })));\n  root.add(body);\n  return root;\n}\n```\nReturn a THREE.Group (or { root }). Named top-level children become editable `parts`; use 4–15 semantic names, ground-origin meters, facing +Z, and natural pivots. Each semantic assembly (wheel, frame, handlebars…) must be its OWN top-level child — wrapping everything in one container group is rejected at validation (≥8 meshes with ≤1 named part), because it disables per-part layout verification and part editing. Models are static; do not write an update hook. Numbers fed to geometry constructors and transforms must be finite: call methods like `v.length()` (never pass the bare method `v.length`), and read the length before `normalize()` — it mutates in place. Any NaN vertex/transform fails validation.\n\nTIERS — pick before writing code:\n- Scene-dressing prop (supporting object judged within the whole shot — a campfire behind the actors, a guitar at medium distance): ONE call building a rough shape from a few primitives. Get proportions, silhouette, and ground contact right; resolve the returned warnings; then let the regular scene.snapshot review judge it. Skip model.capture/model.compare and refine loops unless the scene image shows it broken (then at most one fix pass).\n- Hero object (close-up subject, reference image provided, or the user asks for fidelity): the staged BUILD PROTOCOL below is mandatory when it has ≥6 parts or strong spatial constraints — vehicles, furniture, machines. Simple hero objects (a crate, a barrel) may still be built in one call.\n\nBUILD PROTOCOL (hero objects):\n1. Write a part layout spec BEFORE any code: per part — approximate primitive, size (m), center position, orientation axis, and which part it attaches to. Load the Director Craft code-model reference for the spec format and layout axioms.\n2. First model.generate call is a BLOCKOUT: one simple primitive per part, correct sizes/positions/axes only, no detail. Verify layout via partDetails + model.capture before adding any detail; a failed layout check means fixing the BLOCKOUT, never proceeding.\n3. Refine by passing the SAME id (replace in place, transform preserved). Do not create a second model and delete the blockout — that bypasses the replace contract and loses the verified transform.\n\n- id: omit to create; pass an exact/unique-prefix code-model id to replace in place, preserving its transform and still-valid part overrides.\n- label: display name ≤40 chars. position: {x,y,z} meters; omitted new models auto-place. referencePath: workspace reference-image path.\nReturns { ok, id, label, replaced, parts[], partDetails[], bbox, stats, warnings[], revision }.\n- partDetails[]: per-part AABB { name, center, size, meshes } in model space. Immediately cross-check RELATIVE relations, not just absolute axes (an absolute rule like \"wheels thin in X\" is satisfied by a whole model rotated 90° and is therefore worthless alone):\n  1. travel axis = the front→rear wheel center line; it must run along Z (facing +Z);\n  2. every wheel/disc part's THINNEST size axis must be PERPENDICULAR to the travel axis (thin axis ∥ travel = wheel mounted sideways, cannot roll);\n  3. mirrored parts have mirrored centers; attached parts have touching boxes.\n- warnings[] now includes deterministic findings: `disconnected` (a part's box touches no other part), `longest horizontal span runs along X` (violates facing +Z), and `mounted sideways` (a wheel-like part's rotation axis parallel to the model's length axis). Each one must be fixed or explicitly justified before continuing to detail or reporting completion — never summarize a model with active warnings as \"no errors\".\nInspect bbox/partDetails/warnings immediately. Compile/runtime failures use stable error codes and a phase-tagged message; fix the code, never retry unchanged. Success only proves construction. For a hero build, load the Director Craft code-model reference, then use model.capture and actually inspect its images before claiming fidelity; a scene-dressing prop is judged in the normal whole-scene snapshot instead.","timeoutMs":60000},{"name":"model.capture","description":"Render a code model offscreen from canonical views for visual review. args: { id?, views?, size?, layout? }.\n- id: code-model id (prefix ok); omitted = first code model.\n- views: subset of [\"front\",\"back\",\"left\",\"right\",\"top\",\"three_quarter\",\"three_quarter_back\"]; default [\"front\",\"three_quarter\",\"left\",\"top\"]. Renders use a neutral light-grey studio (no grid/gizmos/other scene objects); hidden parts stay hidden.\n- size: square pixels per view 256–1280 (default 768).\n- layout: \"grid\" (default) | \"separate\". Grid composes ALL requested views into ONE row-major contact-sheet JPEG, each cell labeled with its view name, and returns { ok, id, path, width, height, grid: [{ view, row, col }], referencePath }. Separate returns one clean unlabeled JPEG per view as { ok, id, captures: [{ view, path }], referencePath } — it exists ONLY for feeding model.compare (grid labels/neighboring cells corrupt its pixel metrics) and allows at most 2 views per call; all multi-view visual review goes through the grid sheet.\nEach path is a workspace-relative JPEG in the staging area (.hilo/.tmp/uploads/) — read it with your media analysis tool and compare against the reference image view-by-view. Every view has an expected silhouette (a bike: front = narrow/wheels edge-on, left = two full circles, top = a thin strip — round wheel discs seen from above mean wheels lying flat or mounted sideways); ALL views (all grid cells) must be self-consistent with the intended layout — one plausible angle is not evidence, and a layout that only works from one view is wrong (a sideways wheel looks like a perfect circle exactly when viewed along its normal). Captures are temporary evidence files, not canvas assets. This is the ONLY way you can see the model; never claim visual fidelity without capturing and looking first.","timeoutMs":60000},{"name":"model.compare","description":"Deterministic 2D comparison gate between a capture and the reference image (runs the plugin's bundled Python; first call may take ~1 min to build the venv). args: { renderPath, referencePath } — both workspace-relative (renderPath from model.capture with layout:\"separate\" — never a grid contact sheet, whose labels and neighboring cells corrupt the metrics; referencePath from the generate request).\nReturns { ok, silhouette_iou, aspect_ratio { render, reference, delta_ratio }, color_similarity, grid_diff_4x4_row_major[], worst_cell { row, col, diff }, composite_score, verdict }.\n- silhouette_iou: foreground-silhouette overlap after bbox-crop normalization (composition-independent shape match). grid_diff locates WHERE the silhouette deviates (row 0 = top).\n- Use as advisory evidence between iterations: silhouette_iou < ~0.6 or aspect delta_ratio > ~0.15 usually means wrong proportions. Compare only a capture whose view matches the reference's viewpoint (a left-view reference against the left capture); comparing mismatched views produces meaningless scores — judge the other captures by self-consistency instead. Metrics are blind to materials, lighting and 3D form — a passing score is NOT acceptance; visual inspection of captures remains the authority. Best on single-object references with clean backgrounds; on cluttered scenes trust your eyes instead.","timeoutMs":300000},{"name":"scene.edit","description":"Atomically validate or apply an ordered scene batch as ONE undo step.\n\nargs: { description: string, operations: object[1..50], validateOnly?: boolean }.\nReturns { ok, validateOnly, applied, failed, rolledBack:false, results:[{index,type,ok,code?,detail?,id?,warnings?,candidates?}], affectedIds[], summary, revision, note? }.\n\nAll operations execute against a pure draft. Any failure means ZERO scene writes; fix the reported operation and resend the whole batch. validateOnly:true runs the same draft validation and applies nothing. A later operation can reference an object created by operations[N] as \"$N\" (0-based); referencing a future or non-creating operation returns INVALID_REFERENCE. Exact ids win; a prefix must match exactly one object or returns AMBIGUOUS_ID with candidates.\n\nOperations:\n- add_character {position, bodyType?, pose?, rotationY?, label?, color?, uniformScale?}. bodyType: mannequin|female|child. poses: stand,tpose,walk,run,jump,sit,squat,kneel,lie,drive,wave,hands_up,bow,akimbo,think,fight,aim,sword,spell. Ground poses keep position.y=0; sit/drive plant the hips exactly at position.y — set y to the seat-top height (bench ≈0.48, chair/stool ≈0.45, sofa ≈0.40, × the seat prop's uniformScale — e.g. bench at uniformScale 1.2 → y=0.576), otherwise the character sits on the ground. A seated character must also face the seat's outward direction: set rotationY equal to the seat prop's rotationY (catalog seats face +Z before rotation) or the character sits sideways across the bench. Driving the catalog car: the car is a CLOSED low-poly body — its cabin renders as opaque dark glass, so a driver seated inside is INVISIBLE from outside. Character scale is STATIC for the whole timeline (paths drive position/rotation only), so choose by the beat that matters most: (a) driver never leaves the car → hips y≈0.8, uniformScale≈0.5, |x offset| ≤ 0.35, z within ±0.7 of the cabin (× car uniformScale), fully hidden with no limbs clipping the walls; (b) driver must later stand/walk beside props at natural size → keep uniformScale 1, seat hips at y≈0.8 and let the head stand proud of the roofline (reads as a convertible in wide shots) — NEVER shrink a character who appears on foot in a later beat, and never promise a visible driver through the closed glass; to show the driver, stop the car and stage a step-out beat (short move path to beside the door + stand/walk anims, static pose stand at the final mark).\n- add_prop {assetId,position,rotationY?,scale?,uniformScale?,label?}. Props render in fixed materials — geo meshes are all light-blue primitive and CANNOT be recolored, so never use them for color-critical elements (fire, blood, foliage); build those from colored catalog props (lamps for glow, rocks/plants for organics) or a code model (full color control). Catalog ids:\n  geo mesh_cube,mesh_sphere,mesh_cylinder,mesh_torus,mesh_cone,mesh_pyramid,mesh_rectangle,mesh_disc,mesh_capsule,mesh_arc,mesh_half_cylinder;\n  furniture chair,stool,armchair,sofa,square_table,desk,round_table,bed,bookshelf,cabinet,wardrobe,tv,wall_tv,floor_lamp,table_lamp,rug;\n  building wall,column,staircase,ramp,platform,arch,door,fence,ladder,statue;\n  nature tree_small,tree_large,rock,bush,potted_plant,stump,flower_bed;\n  street car,truck,bicycle,streetlamp,bench,trash_bin,traffic_cone,road_barrier,sign,fire_hydrant,barrel,crate.\n  A prop must semantically match what it plays; a recognizable object missing from this catalog (a campfire, a guitar…) is model.generate territory — not a repurposed prop or a multi-prop stand-in.\n- set_pose {id,pose}. Switching to/from sit or drive: also set_transform the character's position.y to the seat top (or back to 0).\n- add_campath {preset?,center?,radius?,height?,startDeg?,points?,targetId?,lookAt?,duration?,easing?,clipStart?,label?}. preset: orbit|arc|dolly_in|dolly_out|dolly_zoom|crane_up|crane_down|truck|spiral. Otherwise points is >=2 waypoints. easing: linear|easeIn|easeOut|easeInOut|smoothstep. Creates path + camera-track clip.\n- set_campath {dsl,id?,clipStart?,label?}. Omit id to create path+clip; pass id to replace path geometry/source in place. Existing timeline clips keep their layout; clipStart is ignored with a warning on update. Use campath.read before revising.\n- add_move_path {targetId,points,duration?,orient?,clipStart?,label?}. target character/prop; orient follow|keep. Creates path + object track. With follow, the LAST segment's heading is the character's final facing (held after the clip ends, used at timeline end) — aim the final segment at the scene partner/goal so arrival lands facing them; approaches to a person stop 1.2–1.6m short, walking the last step toward them.\n- set_motion {dsl,id?,label?}. Omit id to create; id updates custom motion. Built-ins are read-only. Use motion.read as a template.\n- add_anim_clip {targetId,animId,start?,duration?}. Built-ins: idle,walk,run,jump,talk,sit_talk,wave,bow,cheer,dance,punch,death; custom motion ids and \"$N\" work. Never stack duplicate/overlapping clips for one beat on one track (only the later-starting clip evaluates; extras are defects — remove them). To end the timeline holding a mid-action phase (e.g. a bow still held down), trim the clip's duration so the timeline ends inside that phase, before the animation recovers.\n- set_environment {skyColor?,backgroundMode?,showGround?,groundOpacity?,groundHeight?}. backgroundMode flat|panorama.\n- set_camera {id?,position?,lookAt?,fov?}. Omit id for first camera; fov 10..120.\n- set_transform {id,position?,rotationY?|rotation?,scale?,uniformScale?}. Character, prop, uploaded model, or code model.\n- set_part {id,part,visible?,position?,rotation?,scale?,reset?}. Code-model named part only.\n- rename {id,label}. Character, prop, code model, or camera path.\n- remove {id}. Removes the entity and its direct path/motion timeline references. When a correction loop rebuilds a path or motion, remove the superseded one in the same batch — orphan paths no timeline clip references pollute the scene (diagnostics reports UNREFERENCED_PATH).\n\nStable operation-result codes include INVALID_PARAMS, UNKNOWN_OPERATION, INVALID_REFERENCE, OBJECT_NOT_FOUND, AMBIGUOUS_ID, DSL_INVALID, and ACTION_FAILED. A concurrent write/capture rejects the method with case-sensitive host error code `busy`. results[].warnings are advisory clamping/ignored-field notices; inspect them before reporting completion.\n\nCreative blocking, camera language, and DSL authoring guidance live in the on-demand Director Craft skill; this method document is the exact operation catalog.","timeoutMs":30000},{"name":"scene.history","description":"Step the current editor-session history. args: { action: \"undo\"|\"redo\", steps?: number } where steps defaults to 1 and is clamped to 1..20.\n\nReturns { ok, action, stepsTaken, canUndo, canRedo, undoDepth, redoDepth, summary, revision }. stepsTaken can be smaller than requested at the stack boundary. One successful scene.edit batch is one history level. History is session-local and is not persisted when the editor is closed.","timeoutMs":30000},{"name":"scene.snapshot","description":"Capture the real browser-rendered full scene as clean JPEG staging file(s). args: { cameraId?: string, aspect?: \"auto\"|\"16:9\"|\"4:3\"|\"1:1\"|number, atTimes?: number[], layout?: \"sheet\"|\"separate\" }.\n\nWithout cameraId, captures the current viewport. With a unique camera id/prefix, temporarily renders from that scene camera and then restores the user's exact viewport in finally. Helpers, gizmos and camera models are hidden. Returns { ok, path, width, height, view:\"current\"|\"camera\", cameraId, revision, hint }. Width/height are the actual capture dimensions; output follows the mounted viewport/DPR and aspect crop.\n\natTimes (1..8 timeline milliseconds) captures one frame per entry with the composed timeline fully evaluated at that instant — camera clips, character/prop move paths and animation clips all applied, exactly like scrubbing the playhead. Use it to review motion beats after timeline edits: sample each clip's start/middle/end and the frames just before/after a camera cut. Requires a timeline with clips; values clamp to [0, duration]. Without cameraId each frame uses the timeline's composed camera (viewport view where no camera clip covers that time); with cameraId all frames are shot from that fixed camera while characters still move. Afterwards driven objects reset to their static transforms and the user's viewport is restored. Rejected with `busy` while timeline preview or the virtual camera is driving the viewport.\n\nBy default (layout \"sheet\") the frames are composed into labeled contact sheet(s): at most 4 cells per sheet in time order (8 frames → two sheets), each cell downscaled to ≤960px wide and tagged `t=…ms` in its top-left corner. Returns { ok, sheets:[{path,width,height,cells:[{tMs,row,col}]}], frameCount, durationMs, view:\"timeline\"|\"camera\", cameraId, revision, hint }. One sheet puts adjacent beats in the same image, which is the only way a vision tool can directly compare frames for continuity (sliding, teleports, pose pops between samples) — per-file analysis cannot cross-reference. Review every sheet, then zoom in where needed.\n\nlayout:\"separate\" uploads each frame as its own full-resolution JPEG and allows at most 3 atTimes per call. Returns { ok, frames:[{tMs,path,width,height}], durationMs, view, cameraId, revision, hint }. Use it as the second tier of the review funnel: after a sheet survey flags a suspicious beat (or when a judgement hinges on fine detail the downscaled cell cannot settle — small contacts, thin props, distant characters), re-capture just that tMs at full resolution. layout without atTimes is rejected: a plain snapshot is already one full-resolution frame.\n\n`ENGINE_NOT_READY` means the editor viewport is not mounted or required scene resources are not ready. `busy` means a scene write/capture is active. `CAPTURE_FAILED` means no valid frame was produced or readiness timed out. `UPLOAD_FAILED` means staging is unavailable. Codes are case-sensitive.\n\nThe returned path(s) are evidence, not a verdict: open each with a host media/vision tool and inspect it. Never claim it was visually checked merely because this call succeeded. Diagnostics and model.compare cannot replace this scene-level visual review.","timeoutMs":60000},{"name":"scene.diagnostics","description":"Run read-only mechanical checks over the current Composition. args: {}. Returns { ok, clean, issues:[{code,severity,detail,ids?}], summary }.\n\nChecks duplicate ids, dangling group members, invalid track targets/types, missing path/motion clip references, non-finite or invalid transforms/scales/timing, path point count/underground points, same-track clip overlaps, duplicated same-source clip stacking (CLIP_DUPLICATE, error — remove the extra clip), and paths no timeline clip references (UNREFERENCED_PATH, warning — usually correction-loop leftovers to remove). clean means no error-severity issue; warnings require judgement.\n\nDiagnostics do NOT render the scene and cannot approve composition, pose, occlusion, lighting or camera framing. Fix errors (or explicitly explain intentional cases), then use scene.snapshot and inspect its image for visual verification.","timeoutMs":15000},{"name":"motion.read","description":"Read the semantic-joint DSL source of any character motion — built-in preset or scene custom motion. args: { id } (built-in id like \"walk\", or a custom motion id / 8-char prefix from scene.get). Returns { id, builtin, label, loop, cycleMs, defaultMs, source, warnings }.\n\nMotions are plain text (source is the single source of truth). Typical loop: motion.read a close built-in → copy & edit its DSL → scene.edit set_motion (omit id → new custom motion) → add_anim_clip with the returned id. Use this instead of guessing DSL from scratch: built-ins are curated worked examples (e.g. read \"run\" for asymmetric gait cycles with explicit closing step + sink, \"bow\" for pelvis hinge, \"death\" for lying on the ground).","timeoutMs":15000},{"name":"campath.read","description":"Read a camera path from the scene. args: { id } (a camPath id from scene.get camPaths[], 8-char prefix ok). Returns { id, label, durationMs, easing, loopMode, lookAtTarget, lookAt, points, source, waypoints?, hint }.\n\n- source non-null → the path was authored as campath DSL (scene.edit set_campath): the text IS the truth. Typical edit loop: campath.read → tweak the DSL (retime a beat, extend an orbit, change the look target) → set_campath with the SAME id to update the geometry in place (its timeline clips keep their own start/duration).\n- source null → hand-drawn / preset / phone-vcam path. waypoints[] lists its control-point positions so you can study the framing; to revise it, author a DSL replacement with set_campath passing this id (swaps geometry in place) — or leave it alone and add a new path.\n\nManual edits in the editor invalidate stale DSL (the host clears source when the user drags points), so a null source on a formerly DSL path means the user reshaped it by hand.","timeoutMs":15000}]},"tags":{"zh-CN":["3D","分镜","机位","姿态","构图"],"en-US":["3D","Storyboard","Camera","Pose","Blocking"]}},{"id":"clip-studio","name":{"zh-CN":"AI剪辑工坊","en-US":"Video Editor"},"description":{"zh-CN":"开源 AI 视频剪辑工具，支持自动剪辑与字幕生成。","en-US":"Open-source AI video editor for auto-cutting and subtitle generation."},"version":"0.10.2","source":"bundled","iconUrl":{"zh-CN":"https://cdn.hailuoai.com/public_assets/minimax-hub/plugins/video-editor/0.2.3/vendor/icons/video-editor.svg","en-US":"https://cdn.hailuoai.video/public_assets/minimax-hub/plugins/video-editor/0.2.3/vendor/icons/video-editor.svg"},"entryPath":"","updatedAt":1788976142048.012,"details":{"zh-CN":"AI-native 开源视频编辑工具，支持自动切片、静音删除、字幕识别、时间轴整理与内容重组。适合播客剪辑、短视频生产与自动化内容流水线。不适用于：专业电影级调色与重型后期。","en-US":"Open-source AI-native video editing tool with automatic cutting, silence removal, subtitle generation, and lightweight timeline workflows. Ideal for podcasts, short-form videos, and automated content pipelines. Not applicable for: high-end cinematic color grading or advanced post-production."},"displayMode":"launcher","agent":{"editorSurface":true,"sessionName":{"zh-CN":"剪辑 Agent","en-US":"Clip Agent"},"instructions":"You are editing a VIDEO PROJECT in this clip editor. Domain model: a project has tracks (video/audio/text); each track holds clips; a clip has an id, a media reference, a timeline placement (startSec/endSec) and a source window (sourceInSec/sourceOutSec).\n\n- ALL times are SECONDS of project time (decimals allowed), never frames or timecode strings.\n- Clip / track / media ids come from <editor_state> or `project.get`. Ids in <editor_state> are truncated to 8 chars and are valid prefixes; never invent or reshape them.\n- Track `name` / `trackName` is the human-visible label in the timeline header (for example, \"Video 1\" or \"Captions\"). Resolve the user's named-track reference case-insensitively to that track, then use its id in tool calls. If multiple tracks have the same matching name, ask which one; do not silently choose.\n- Small edits can rely on the <editor_state> snapshot. Call `project.get` when you need exact timings, source windows, media ids, or the snapshot is missing/stale.\n\nWORKFLOW for a non-trivial edit (several operations, or anything visual):\n1. READ — `project.get` (view=\"full\" when you need media paths or markers). For content decisions (what to cut, where the good moment is), do not guess from clip names: `project.snapshot` a few frames, or run host media tools on `media[].path`, and look first.\n2. EDIT — put EVERY change for one user request into ONE `project.edit` call: operations run in order and commit as a single undo step titled by `description` (write it in the user's language). For a large/risky batch, send it once with validateOnly:true first, then apply.\n3. VERIFY — after a batch that moved/removed/retimed anything, run `project.diagnostics` and fix error-severity issues (black gaps, overlapping clips). After a VISUAL change (captions, effects, grading, transitions), `project.snapshot` the affected moments and actually look at the frames before calling it done.\n4. SHOW — use `project.view` (seek, or play with fromSec+toSec) to land the user on the result, and summarize what changed in plain language plus the new duration (`summary.durationSec`). Never paste tool JSON.\n\nFailure handling:\n- On the first failing operation the batch stops and applied operations are rolled back. Read `results[]`, fix the named operation, and resend the whole corrected batch.\n- Some look / feel operations are kept OUT of this editor's undo history: they are reverted correctly when a batch fails, but the user cannot Ctrl+Z them afterwards. Do not guess which — `project.edit` returns `nonUndoable[]` listing exactly the applied operations with that limitation. When it is non-empty, mention it in your reply.\n- Only touch what the user asked for; leave unrelated clips, tracks and settings alone.\n\nMEDIA NOT IN THE PROJECT YET: when the user references a workspace/canvas file that `project.get` media[] does not list (e.g. a video just generated on the canvas), use the `import_media` operation inside `project.edit` with its assetId or workspace path — do not tell the user to import manually.\n\nSUBTITLES / TRANSCRIPTION: this plugin does not run speech recognition. To caption a named track (for example, \"给 Video 1 加字幕\"), only transcribe the video/audio clips whose `trackName` matches that label. Then bring the result back:\n  1. `project.get` with view=\"full\" -> clips[] identifies the target track and media, while media[] carries `path` (a workspace path the host tools accept);\n  2. for each matching clip, run the host transcription tool on its media path to get an SRT file, and read it;\n  3. add one `import_srt` operation per matching clip, passing that `clipId` so cue times are rebased from source time onto the timeline; keep all operations in the request's single `project.edit` batch.\n  Caption clips then show up as `textClips[]` in later reads, so you can quote them, restyle them, or cut the timeline where a sentence sits. After styling captions, snapshot a frame where a cue is visible and check it is legible and not covering a face or key action.","methods":[{"name":"project.get","description":"Read the current project. args: { view?: \"summary\"|\"tracks\"|\"clips\"|\"full\" (default \"clips\"), trackId?, clipId?, fromSec?, toSec? }.\nReturns settings (name/width/height/frameRate), durationSec, playheadSec, selectedClipIds, selectedTrackIds (tracks the user has selected in the timeline header), counts, tracks[] (id, type, name, clipCount, locked/hidden/muted/solo, gaps[]), clips[] (id, trackId, trackName, trackType, name, mediaId, startSec, endSec, durationSec, sourceInSec, sourceOutSec, speed?, volume, effectCount?), textClips[] (caption / title overlays: id, trackId, trackName?, text, startSec, endSec, durationSec, fontSizePx?) whenever any exist, and with view=\"full\" also media[] and markers[]. Track `name` / `trackName` is the label visible in the timeline header (for example, \"Video 1\" or \"Captions\"), so user references to a named track can be mapped to its id. `fontSizePx` is in canvas pixels, so read it against width/height before judging a caption too small: ~56px is a normal caption on 1080p and scales proportionally from there.\nmedia[] entries carry {id, name, type, durationSec?} plus {assetId, path} when the item is backed by a Hub asset - `path` is what host media tools (transcription, analysis) take as input.\nUse trackId / clipId / fromSec / toSec to keep responses small on long projects.","timeoutMs":15000},{"name":"project.edit","description":"Apply an ordered batch of edit operations as ONE undo step. args: { description: string (short user-facing label), operations: object[] (1-50), validateOnly?: boolean }.\nReturns { ok, applied, failed, rolledBack, results[] (per-operation ok/error/detail/affectedClipIds), summary (projectName, durationSec, trackCount, clipCount, mediaCount, playheadSec), affectedClipIds[], nonUndoable[] }.\nnonUndoable[] lists applied operation types the user canNOT Ctrl+Z afterwards. Most operations ARE undoable; only text/caption edits, colour grading, markers, set_speed and import_media still fall outside the history. When it is non-empty, say so in your reply. It is always empty when rolledBack is true.\nThis method only CHANGES the project — to move the playhead or play a range use project.view, to step history use project.history.\nvalidateOnly:true validates the whole batch and applies nothing.\nOPERATIONS (each { type, ...fields }; times are absolute project seconds):\n- split_clip {clipId, atSec} — cut a clip in two at atSec.\n- trim_clip {clipId, newStartSec?, newEndSec?} — move the clip's timeline edge(s) inward; at least one required. To relocate a clip use move_clip.\n- remove_clip {clipId, ripple?} — delete a clip; ripple:true shifts later same-track clips left to close the gap.\n- remove_range {fromSec, toSec, trackIds?, ripple?} — cut out [fromSec,toSec) across the given tracks (default: all unlocked tracks): splits at both edges then deletes the middle. ripple defaults true. This is the one-shot way to do \"delete seconds X to Y\".\n- move_clip {clipId, toSec, trackId?} — reposition a clip, optionally onto another compatible track.\n- add_clip {mediaId, trackId?, atSec?} — place a media-library item; omit atSec to append to the main track.\n- import_media {assetId? | path?, addToTimeline?, trackId?, atSec?} — pull a Hub workspace asset (video/audio/image) into this project's media library and, by default, also place it on the timeline (same placement rules as add_clip). Use it when the user asks to edit a file that project.get media[] does NOT list yet — e.g. something just generated on the canvas. assetId/path come from the host's canvas or file tools. Re-importing an asset already in the library reuses it (no duplicate). Returns mediaId in results[].\n- duplicate_clip {clipId}\n- close_gap {clipId} — remove the empty space immediately before a clip.\n- add_track {kind} — \"video\" | \"audio\" | \"text\".\n- set_track {trackId, muted?, hidden?, locked?, solo?, name?}\n- remove_track {trackId}\n- add_text {text, atSec, durationSec?, trackId?} — text overlay clip (default 3s; creates a text track when none exists).\n- cut_silences {clipId, thresholdDb?, minSilenceSec?, padSec?} — detect and ripple-remove silent stretches inside one audio/video clip (defaults -40dB / 0.5s / 0.1s padding). Reports how much was removed.\n- import_srt {srt, clipId?, offsetSec?, trackId?, clipToRange?, preset?} — land an SRT transcript on the caption track (created on demand, named \"Captions\"). Cue times in an SRT produced by transcribing a source file are SOURCE times: pass `clipId` and they are rebased onto that clip's timeline position (and cues outside it are dropped); pass `offsetSec` to shift explicitly instead. The whole import is ONE undo step. `preset` styles the cues on the way in; with no preset they get the same look as the editor's own \"import subtitles\" button, which is the safe default.\n- set_subtitle_style {preset?, trackId?, clipIds?, fontFamily?, fontSizePx?, color?, backgroundColor?, position?} — restyle caption clips; defaults to every caption clip. `position` is \"top\" | \"center\" | \"bottom\" and defaults to whatever the preset specifies. Presets: default / minimal / bold / centered / modern / elegant / casual. `fontSizePx` is in CANVAS pixels, so scale it against the project resolution rather than assuming web-page sizes: ~56px is a normal caption on 1080p, so a 4K project needs ~112. Presets scale themselves; a raw `fontSizePx` does not.\n- set_text {clipId, text} — rewrite one caption or text-overlay clip (ids come from textClips[]).\n- set_clip {clipId, volume} — per-clip playback gain: 0 silent, 1 unity, max 2.\n- set_speed {clipId, speed?, reverse?, affectAudio?} — playback speed 0.1–20× and/or reverse. Changes the clip's TIMELINE duration (duration = source window / speed) without rippling neighbours — check for a resulting gap or overlap afterwards (project.diagnostics). affectAudio defaults true and retimes the audio clip split from the same media.\n- add_transition {clipAId, clipBId?, kind?, durationSec?} — transition between two adjacent clips; clipBId defaults to the next clip on the same track, kind defaults to \"crossfade\" (crossfade / dipToBlack / dipToWhite / wipe / slide / zoom / push), durationSec defaults to 1.\n- remove_transition {clipAId, clipBId?}\n- add_effect {clipId, effect, params?} — one of brightness / contrast / saturation / hue / blur / sharpen / vignette / grain / temperature / tint / tonal / chromaKey / shadow / glow / motion-blur / radial-blur / chromatic-aberration.\n- remove_effect {clipId, effectId?  |  effect?}\n- set_color_grading {clipId, temperature?, tint?, reset?} — temperature and tint are [-100, 100]; negative temperature cools, negative tint goes green.\n- add_marker {atSec, label?} — flag a moment on the timeline (useful for handing highlights back to the user).\n- remove_marker {markerId?  |  atSec?} — drop a marker by id, or the one closest to atSec. Marker ids come from `project.get` view=\"full\".\n- remove_text_clip {clipId} — delete one caption / text-overlay clip (ids come from textClips[]). Timeline clips use remove_clip instead; the two id namespaces are separate.","timeoutMs":180000},{"name":"project.view","description":"Playback and navigation. Changes nothing, so it is never part of an undo step. args: { action: \"seek\" | \"play\" | \"pause\", toSec?, fromSec? }.\n- seek: requires toSec — move the playhead so the user sees a specific moment.\n- play: optional fromSec (seeks first) and fromSec+toSec together (loops that range).\n- pause: stop playback.\nReturns { ok, action, playheadSec, detail }. Use this to SHOW the user a result after editing.","timeoutMs":5000},{"name":"project.history","description":"Step the project undo history. args: { action: \"undo\" | \"redo\", steps?: number (1-20, default 1) }.\nOne earlier project.edit batch is ONE step. Look/feel operations are not in this history (see project.edit nonUndoable[]) and cannot be reverted here — re-apply the inverse operation instead.\nReturns { ok, action, stepsTaken, canUndo, canRedo, summary, detail }. stepsTaken can be less than requested when the stack runs out; ok is false when nothing moved.","timeoutMs":30000},{"name":"project.snapshot","description":"Export composited frames from the timeline as image files — YOUR EYES on the project. The frames are the exact preview render the user sees: clips with effects, transitions, colour grading, captions and text overlays all baked in. Use it to VERIFY what you did (captions legible? not covering a face? effect not overdone? transition landing right?) and to UNDERSTAND footage before content decisions.\nargs: { times?: number[] (1-8 explicit timeline seconds), fromSec?+toSec?+count? (sample count frames evenly, default 4, max 8), maxDim? (longest edge px, default 640, max 1280) }. Pass either times[] or a range, not both.\nReturns { ok, frames: [{ atSec, path, width, height }], detail }. Each `path` is a workspace-relative image path — read it with the host's image viewing tool in the SAME turn to actually look at it. Files are transient staging artifacts: they do not appear on the user's canvas and may be cleaned up later; do not reference them in replies as durable outputs.\nCapture pauses playback if running and restores the user's view afterwards.\nErrors: PREVIEW_NOT_READY (editor view not mounted — ask the user to open the editor), busy (a capture is running — retry once it settles), CAPTURE_FAILED (nothing rendered at that time — likely a timeline gap; run project.diagnostics).\nWHEN TO USE — after any visual edit worth checking: subtitle styling (grab 1-2 frames where cues show), colour/effect changes (before+after moments), transitions (frame at the seam), and before telling the user a visual task is done. One call with a few well-chosen times beats many calls.","timeoutMs":60000},{"name":"project.diagnostics","description":"Mechanical health checks over the live project — run this before declaring an edit finished, after any batch that moved/removed/retimed clips, and when the user reports \"something looks wrong\". Read-only and instant.\nargs: {} (none).\nReturns { ok, clean, issues[], summary }. `clean` is true when there are no error-severity issues. Each issue: { kind, severity: \"error\"|\"warning\", detail, fromSec?, toSec?, clipIds?, trackId? }.\nIssue kinds:\n- timeline_gap (error) — no visible video in a time span; renders as black frames. Fix with close_gap / move_clip / remove_range.\n- clip_overlap (error) — two clips on one track overlap; rendering is undefined. Fix with move_clip / trim_clip.\n- media_missing (error) — a clip references media with no readable bytes.\n- caption_overlap (warning) — two captions on one track overlap in time (usually an import offset mistake).\n- caption_out_of_video (warning) — caption extends past the last visible video frame.\n- tiny_clip (warning) — clip shorter than 2 frames, probably split residue; remove or extend it.\n- empty_track (warning) — track with no clips; remove_track if it is leftover.\nFix errors (or explain why they are intended) before reporting completion. Warnings are judgement calls — mention them when relevant. For visual confirmation of a fix, follow with project.snapshot.","timeoutMs":10000}]},"tags":{"zh-CN":["剪辑","视频","字幕","播客"],"en-US":["Editing","Video","Subtitles","Podcast"]}},{"id":"comfyui","name":{"zh-CN":"ComfyUI 工作流","en-US":"ComfyUI"},"description":{"zh-CN":"节点式 AI 绘图工作流编辑器，连接本机 ComfyUI 后端。","en-US":"Node-based AI image generation workflow editor, backed by a local ComfyUI server."},"version":"0.2.4","source":"bundled","iconUrl":{"zh-CN":"/api/plugins/comfyui/static/assets/images/comfy-icon-192.png?v=7fe578","en-US":"/api/plugins/comfyui/static/assets/images/comfy-icon-192.png?v=7fe578"},"entryPath":"","updatedAt":1788976142023.0144,"details":{"zh-CN":"在画布中打开完整的 ComfyUI 工作流编辑器：拖拽节点、连线、运行队列、实时预览生成结果。\n\n首次使用时会提示下载官方托管的 ComfyUI 后端包（后台安装，完成后提示）；之后每次打开节点会自动启动后端，无需任何手动配置。后端版本由插件统一下发，有新版本时会提示更新。\n\n实际出图还需自行下载模型权重放入数据目录的 models/ 下（安装完成后可在设置中查看路径）。不适用于：无本地后端的纯云端环境。","en-US":"Opens the full ComfyUI workflow editor on the canvas: drag nodes, wire them up, run the queue, and preview results live.\n\nOn first use the plugin downloads a managed ComfyUI backend bundle (installed in the background with a completion notice); afterwards the backend starts automatically whenever the node opens — no manual setup. Backend versions are distributed with the plugin, and you'll be prompted when an update ships.\n\nGenerating images additionally requires model weights placed under the data directory's models/ folder. Not applicable for: cloud-only environments with no local backend."},"displayMode":"launcher","tags":{"zh-CN":["绘图","工作流","节点","AI"],"en-US":["Image","Workflow","Nodes","AI"]}}]}